leudz / shipyard

Entity Component System focused on usability and flexibility.
Other
738 stars 44 forks source link

[new feature] deleting entity's components temporarily #171

Closed PudgeKim closed 1 year ago

PudgeKim commented 1 year ago

Suppose EntityId 1 has three components. (Component A, B, C) I hope this kind of feature is added.

// entities=EntitiesViewMut
// Just mark components of Entity1 have been deleted
entities.mark_to_delete(entity_id); // or should it be all_storages instead of entities?

// the code below should skip Entity1's component
component_a.iter().for_each(|_| { ... }); // or create a new method instead of iter.
component_b.iter().for_each(|_| { ... });
component_c.iter().for_each(|_| { ... });

entities.unmark(entity_id);

What do you think about it?

leudz commented 1 year ago

How is it different from having another storage with a Disabled component? The iteration call site would be a bit more complicated but not that much. If you really want it, you could make a custom view that hides the second storage.

Alternatively you could (ab)use the deletion tracking. If you don't already use it, you could delete the components, they wouldn't show up in the iteration of course. When you want to unmark you just have to go through the deleted components and add them back.

Some archetype-based ecs have an issue opened to disable components/entities (bevy for example) but it's mainly about performance. With archetype if you add/remove a component, all components of the entity have to move.

PudgeKim commented 1 year ago

If you really want it, you could make a custom view that hides the second storage.

Could you give me some more explanation about this? I know what a custom view is but I don't know about hiding the second storage.

leudz commented 1 year ago

It's quite a bit of boilerplate but you only need it once. Borrow, AllStoragesBorrow and BorrowInfo can be derived but not the iterators traits because we do custom things inside.

With this version you add/remove the Disabled component to mark/unmark an entity. Then when you want to iterate but skip marked entities you use DisabledView/DisabledViewMut instead of the regular ones. It will basically mimic (&view, !&disabled) but all wrapped in a single type.

If you wanted to disable a single component and not the entity you could make Disabled generic and the rest should be almost identical.

use shipyard::iter::{AbstractMut, IntoAbstract};
use shipyard::*;

fn main() {
    let mut world = World::new();
    world.add_entity(A);
    world.add_entity((A, Disabled));
    world.add_entity(A);

    world.run(sys);
}

#[derive(Component, Debug)]
struct A;

fn sys(mut vm_a: DisabledViewMut<A>) {
    for (id, a) in (&mut vm_a).iter().with_id() {
        dbg!(id, a);
    }
}

#[derive(Component)]
struct Disabled;

#[derive(Borrow, AllStoragesBorrow, BorrowInfo)]
struct DisabledView<'v, T: Component + Send + Sync> {
    view: View<'v, T>,
    disabled: View<'v, Disabled>,
}

struct DisabledAbsView<'v, T: Component + Send + Sync>(
    (
        <&'v View<'v, T> as IntoAbstract>::AbsView,
        Not<<&'v View<'v, Disabled> as IntoAbstract>::AbsView>,
    ),
);

impl<'v, T: Component + Send + Sync> IntoAbstract for &'v DisabledView<'v, T> {
    type AbsView = DisabledAbsView<'v, T>;

    fn into_abstract(self) -> Self::AbsView {
        DisabledAbsView((self.view.into_abstract(), (!&self.disabled).into_abstract()))
    }

    fn len(&self) -> Option<usize> {
        (&self.view).len()
    }

    fn type_id(&self) -> info::TypeId {
        (&self.view).type_id()
    }

    fn inner_type_id(&self) -> info::TypeId {
        (&self.view).inner_type_id()
    }

    fn dense(&self) -> *const EntityId {
        (&self.view).dense()
    }

    fn is_not(&self) -> bool {
        true
    }
}

impl<'v, T: Component + Send + Sync> AbstractMut for DisabledAbsView<'v, T> {
    type Out = <<&'v View<'v, T> as IntoAbstract>::AbsView as AbstractMut>::Out;
    type Index = <<&'v View<'v, T> as IntoAbstract>::AbsView as AbstractMut>::Index;

    unsafe fn get_data(&self, index: usize) -> Self::Out {
        self.0.get_data(index).0
    }

    unsafe fn get_datas(&self, index: Self::Index) -> Self::Out {
        self.0.get_data(index).0
    }

    fn indices_of(&self, entity_id: EntityId, index: usize, mask: u16) -> Option<Self::Index> {
        Some(self.0.indices_of(entity_id, index, mask)?.0)
    }

    unsafe fn indices_of_unchecked(
        &self,
        _entity_id: EntityId,
        _index: usize,
        _mask: u16,
    ) -> Self::Index {
        unreachable!()
    }

    unsafe fn get_id(&self, _index: usize) -> EntityId {
        unreachable!()
    }

    fn len(&self) -> usize {
        self.0.len()
    }
}

#[derive(Borrow, AllStoragesBorrow, BorrowInfo)]
struct DisabledViewMut<'v, T: Component + Send + Sync> {
    view: ViewMut<'v, T>,
    // Only needs to be mut if you want to be able to (un)mark entities with it
    disabled: ViewMut<'v, Disabled>,
}

struct DisabledAbsViewMut<'tmp, 'v, T: Component + Send + Sync>(
    (
        <&'tmp mut ViewMut<'v, T> as IntoAbstract>::AbsView,
        Not<<&'tmp ViewMut<'v, Disabled> as IntoAbstract>::AbsView>,
    ),
);

impl<'tmp, 'v, T: Component + Send + Sync> IntoAbstract for &'tmp mut DisabledViewMut<'v, T> {
    type AbsView = DisabledAbsViewMut<'tmp, 'v, T>;

    fn into_abstract(self) -> Self::AbsView {
        DisabledAbsViewMut((
            (&mut self.view).into_abstract(),
            (!&self.disabled).into_abstract(),
        ))
    }

    fn len(&self) -> Option<usize> {
        (&self.view).len()
    }

    fn type_id(&self) -> info::TypeId {
        (&self.view).type_id()
    }

    fn inner_type_id(&self) -> info::TypeId {
        (&self.view).inner_type_id()
    }

    fn dense(&self) -> *const EntityId {
        (&self.view).dense()
    }

    fn is_not(&self) -> bool {
        true
    }
}

impl<'tmp, 'v, T: Component + Send + Sync> AbstractMut for DisabledAbsViewMut<'tmp, 'v, T> {
    type Out = <<&'tmp mut ViewMut<'v, T> as IntoAbstract>::AbsView as AbstractMut>::Out;
    type Index = <<&'tmp mut ViewMut<'v, T> as IntoAbstract>::AbsView as AbstractMut>::Index;

    unsafe fn get_data(&self, index: usize) -> Self::Out {
        self.0.get_data(index).0
    }

    unsafe fn get_datas(&self, index: Self::Index) -> Self::Out {
        self.0.get_data(index).0
    }

    fn indices_of(&self, entity_id: EntityId, index: usize, mask: u16) -> Option<Self::Index> {
        Some(self.0.indices_of(entity_id, index, mask)?.0)
    }

    unsafe fn indices_of_unchecked(
        &self,
        _entity_id: EntityId,
        _index: usize,
        _mask: u16,
    ) -> Self::Index {
        unreachable!()
    }

    unsafe fn get_id(&self, _index: usize) -> EntityId {
        unreachable!()
    }

    fn len(&self) -> usize {
        self.0.len()
    }
}
PudgeKim commented 1 year ago

Thank you for a detailed explanation!