leudz / shipyard

Entity Component System focused on usability and flexibility.
Other
754 stars 46 forks source link

How to get+modify a component on one entity? #175

Closed progressiveCaveman closed 1 year ago

progressiveCaveman commented 1 year ago

I am converting my project from hecs to shipyard. I have a common pattern where I have an entityId and I want to check for a component and modify it, like so:

if let Ok(mut vs) = self.world.get_mut::<Viewshed>(*player_id) { 
    vs.dirty = true; 
}

The best way I can see in the documents to do this in shipyard is to create an iterator over all viewsheds, then get the entity. This is rather ugly and I imagine it's not efficient:

self.world.run(|mut vs: ViewMut<Viewshed>| {
    if let Ok(mut vs) = (&mut vs).get(*player_id) {
        vs.dirty = true; 
    }
});

Is there a better way to do this?

leudz commented 1 year ago

There isn't any equivalent but nicer version. World::get could exist but usually get happens in systems where you have views and not World. (I've added more and more "shortcut methods" to World, at the beginning everything had to go through views explicitly)

If you don't check for the component then you can use indexing vs[*player_id].dirty = true; but it'll panic if the player doesn't have the component.

About performance, views are not iterators. View/ViewMut are basically references to SparseSet, a data-structure close to a Vec. So vs.get is almost like vec.get.

progressiveCaveman commented 1 year ago

Thanks for the quick reply. I see what you're saying about the sparseset.

How would this work with a function that returns an EntityID that needs to look for a component? For example, if I wanted to make a get viewshed function:

fn get_viewshed(entity: EntityID) -> Option<ViewShed> {
    self.world.run(|mut vs: ViewMut<Viewshed>| {
        return (&mut vs).get(*entity); //this doesn't work, how can I do this? 
    });
}

Looking further into the documentation, maybe I'm looking for borrow?

leudz commented 1 year ago

If you return an owned type then you can do:

fn get_viewshed(&self, entity: EntityID) -> Option<ViewShed> {
    self.world.run(|mut vs: ViewMut<Viewshed>| {
        (&mut vs).get(*entity).ok().copied() // <- no semi-colon and I'm assuming Viewshed is Copy
    }) // <- no semi-colon
}

And yes you could use borrow instead.

If you want to return a reference to a component then it's basically not possible, you'd need the same kind of types that World::get would use. Something like Ref/RefMut to hold the locks. View/ViewMut are references but also keep locks to make sure it's not possible to break borrow checking rules.

progressiveCaveman commented 1 year ago

Ok, thanks. I am starting to see more of how I can use views in my systems to get the info I need. I didn't see anything in docs about run returning the value of the closure, that might be good to add (Or I didn't read well enough)

leudz commented 1 year ago

World::get is available on master et I added a mention to the return value for run.