Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Easy Query from World (too hard to get a Query<(x,y,z)> without a system) #2687

Closed
tom-leys opened this issue Aug 19, 2021 · 9 comments
Closed
Labels
A-ECS Entities, components, systems, and events C-Usability A simple quality-of-life change that makes Bevy easier to use

Comments

@tom-leys
Copy link

tom-leys commented Aug 19, 2021

What problem does this solve or what need does it fill?

Given access to a &mut World, I need to run queries, modify / create components and access resources. I can only do this in the context of a system. I'd like something simpler.

I use these for world-to-world messaging, sync with outside stuff (Godot), unit tests and actions for my LockStep implimentation.

What solution would you like?

I'd like to be able to do this:

let  (     mut commands,
            mut client_list,
            mut actor_inputs,
            mut biped,
            mut ships,
        ) : (Commands,
             ResMut<ClientList>,
             Query<&mut ActorInput>,
             Query<&mut Biped>,
             Query<(Entity, &SpaceShip)>) = world.system_data();

another example would be

        for (ship, pos) in world.system_data::<Query<(&SpaceShip, &Pos)>>().into_iter() {
            built_str +=
                format!("{:?} : ({}, {} : {} degrees)\n", ship, pos.x, pos.y, pos.facing).as_str();
        }

I initially thought this is what world.query did. It allowed you to unpack any query that you could have made the argument list of a system. I.e anything with signature SystemParam. This is what Specs does with specs::World::system_data.

It could take a mutable reference to the world and lock the entire world until you return the SystemParam, i.e query(&'a mut self) -> SystemParam<'a... >

What alternative(s) have you considered?

I tried using world.query() but it returns just one query and using it is awkward. You need to keep passing world in. I intitially thought I got a useful bevy_ecs::Query but instead got some QueryState, which is something I don't know how to turn into a Query.

I considered using World::resource_scope but it supports just one resource at a time, and not Queries or components at all.

I tried creating some traits for worlds and queries that made them easier to use, but I can't make a method in world that returns an easy to use Iter without storing a reference to world in the iter and ... it started getting too hard.

An iterator in world would allow this (with a custom iterator)

for (ship, pos) in world.query_iter::<(&SpaceShip, &Pos)>(){
    // ...
}

I tried making short-lived systems to wrap my code which needed queries, but that requires gymnastics to pass in &self since &self is not 'static. I used rc::Rc and rc::Weak to get around that:

impl ExecutesAction for AdjustFloor {
    type UndoData = AdjustFloorUndo;

    fn apply(&self, world: &mut World) -> Result<AdjustFloorUndo, ActionResult> {
        let undo = AdjustFloorUndo {
            area: self.area,
            flags: self.flags,
            prev_floor: None, // world.get_resource::<Floor>().get_subsection(self.area), // Will need to save previous walls here...
        };

        let self_ptr = Rc::new(*self);
        if self.flag_add() {
            (|this:In<Weak<AdjustFloor>>, mut q: (Commands, ResMut<Floor>, ResMut<ModuleMap>)|
                {
                    let this = this.0.upgrade().unwrap();
                    AdjustFloor::do_add_floors_and_walls(&this, q)
                }).system().run(Rc::downgrade(&self_ptr), world);
        } else {
            (|this:In<Weak<AdjustFloor>>, mut q: (Commands, ResMut<Floor>, ResMut<ModuleMap>)|
                {
                    let this = this.0.upgrade().unwrap();
                    AdjustFloor::do_remove_floors_and_walls(&this, q)
                }).system().run(Rc::downgrade(&self_ptr), world);
        }

        Ok(undo)
    }

In Specs I used to just call do_add_floors_and_walls with a reference to a specs::World and then do this:

    fn do_add_floors_and_walls(&self, world: &mut World) {
        let (mut floor, events, entities, mut modules, sync, mut map) = world.system_data::<(
            Write<Floor>,
            Read<WorldEvents>,
            Entities,
            WriteStorage<ProductionModule>,
            ReadStorage<SyncStatus>,
            Write<ModuleMap>,
        )>();

Additional context

My Game (recent blog) has multiple Worlds, one per spaceship. There is one more world that ties them together, the "Space World"

I'm using multiple words because I want each space-ship to be its own deterministic sim and keeping them separate makes this easier.

@tom-leys tom-leys added C-Enhancement A new feature S-Needs-Triage This issue needs to be labelled labels Aug 19, 2021
@alice-i-cecile alice-i-cecile added A-ECS Entities, components, systems, and events C-Usability A simple quality-of-life change that makes Bevy easier to use and removed C-Enhancement A new feature S-Needs-Triage This issue needs to be labelled labels Aug 19, 2021
@mockersf
Copy link
Member

mockersf commented Aug 19, 2021

Would being able to run a system directly from the world work for you? There is #2427 open for that.

You could then do (not checked if it works, but it should look like that)

fn my_exclusive_system(world: World) {
    // do stuff...
    ...

    // then you need to work with a query or with a resource
    (|query: Query<(&ComponentA, &ComponentB)>, resource: ResMut<MyResource>| { ... }).run_direct(&mut world);
}

@tom-leys
Copy link
Author

That's not a good solution, since passing self into systems is annoying, as is creating little system lambdas when you just want to do "something"

@tom-leys
Copy link
Author

I've just realised that Hecs (which bevy_ecs is based on) has exactly the type of Query I was thinking of : https://docs.rs/hecs/0.6.0/hecs/struct.World.html#method.query

@bjorn3
Copy link
Contributor

bjorn3 commented Aug 20, 2021

Bevy_ecs used to be based on hecs. Since bevy 0.5 it is completely independent.

Bevy_ecs does have a World::query method though. Does that work for you?

@tom-leys
Copy link
Author

When developing a system my method is passed mut ships: Query<&SpaceShip, Entity> for instance. If I create a sub-method, I pass in ships: &mut Query<&SpaceShip, Entity>.

I cannot get either of these types from World. I can only get those by making a small system. There is no conversion from the type returned by World::query into Query - in fact I cannot find any way to make a Query myself. I imagine there is some non-documented way to do it.

So this means I cannot write tidy unit-tests for my sub-functions, whatever they may be.

@cart
Copy link
Member

cart commented Aug 21, 2021

Bevy's main branch has SystemState, which appears to be exactly what you're looking for:
#2283

let mut state: SystemState<(Commands, ResMut<ClientList>, Query<&mut ActorInput>)> = SystemState::new(&mut world);
let (commands, client_list, actor_input) = state.get_mut(&mut World);

SystemState "caches" state in the same way a system does, which ensures that state.get_mut() doesn't do redundant work.

@tom-leys
Copy link
Author

tom-leys commented Aug 22, 2021

@cart that does look like exactly what I need. It would be handy to have a SystemState::get_new method since I'll often be immediately applying it.

I can see how easy it would be to forget to call .apply

@tom-leys
Copy link
Author

@alice-i-cecile , @cart - I'm going to keep working with Specs ECS for now and probably won't revisit this for some months. If in the mean time you wish to close the issue that's fine with me.

If you want, instead, to use this as an opportunity to refine your interface - make it easier for other users.... also fine to leave it open.

@alice-i-cecile
Copy link
Member

I'm going to close this out; we'll see how SystemState pans out and add helper functions as the need arises :) Thanks for the feedback!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-ECS Entities, components, systems, and events C-Usability A simple quality-of-life change that makes Bevy easier to use
Projects
None yet
Development

No branches or pull requests

5 participants