#Struggling with the borrow checker due to conflicting mutable and immutable references to world.

11 messages · Page 1 of 1 (latest)

rugged gale
#

Hello, I've been ramming my head against the borrow checker due to conflicting mutable and immutable references to world, whilst trying to make a custom command.

What I'm trying to do is to make a generic custom command that spawns a sprite stack from a given AssetCollection struct.

An AssetCollection struct looks like this (it uses bevy_asset_loader):

#[derive(AssetCollection, Resource)]
struct VikGirl {
    #[asset(texture_atlas(tile_size_x = 20, tile_size_y = 20, columns = 1, rows = 1))]
    layout: Handle<TextureAtlasLayout>,
    #[asset(image(sampler = nearest))]
    #[asset(path = "Vik Girl.png")]    
    slice: Handle<Image>,
}

which has these impls:

impl StackedSprite for VikGirl {
    fn layout(&self) -> Handle<TextureAtlasLayout> {
        self.layout.clone()
    }

    fn slice(&self) -> Handle<Image> {
        self.slice.clone()
    }
}

There is also a generic stacked sprite struct which holds some info used for making the stack. It looks like this:

// Auxillary data for sprite stacks
struct SpriteStack<T: Resource + StackedSprite> {
    pos: Vec3,
    spacing: f32,
    scale: f32,
    slice_indexes: Vec<usize>,
    _marker: std::marker::PhantomData<T>,
}

which has the impls:

impl<T: Resource + StackedSprite> SpriteStack<T> {
    fn new(pos: Vec3,
           spacing: f32,
           scale: f32,
           slice_indexes: Vec<usize>
    ) -> Self {
        Self {
            pos,
            spacing,
            scale,
            slice_indexes,
            _marker: std::marker::PhantomData,
        }
    }
}
#

Where Im bashing my head against the borrow checker is that for the command, I need to get the loaded AssetCollection from the world, then spawn a stack.
This is what I've come up with, but it does not work.

impl<T: Resource + StackedSprite> Command for SpriteStack<T> {
    fn apply(self, world: &mut World) {
        let res: T = world.resource_scope(|world, _| {
            world.get_resource::<T>().unwrap().clone()
        });

        world.spawn((
            SpatialBundle {
                transform: Transform{
                    translation: self.pos,
                    scale: Vec3::splat(self.scale),
                    ..Default::default()
                },
                ..Default::default()
            },
        )).with_children(|c| {
            let mut counter: f32 = 0.;

            for &slice in self.slice_indexes.iter() {
                c.spawn((
                    SpriteBundle {
                        texture: res.slice(),
                        transform: Transform {
                            translation: self.pos + Vec3::new(0., counter, 0.),
                            scale: Vec3::splat(self.scale),
                            ..Default::default()
                        },
                        ..Default::default()
                    },
                    TextureAtlas {
                        layout: res.layout(),
                        index: slice,
                    },
                    Rotation { speed: 1. },
                ));
                counter += self.spacing;
            }
        });
    }
}

Any help is appreciated, cheers

echo kettle
# rugged gale Where Im bashing my head against the borrow checker is that for the command, I n...

That resource_scope is doing nothing (except maybe giving you errors). Consider doing all the spawning inside the resource_scope (where the second argument of the closure is your res, don't separately get it from the world! This is entire point of resource_scope).
Alternatively you could use get_resource initially and call .slice()/.layout() on it once, then clone the result of those calls

rugged gale
# echo kettle That `resource_scope` is doing nothing (except maybe giving you errors). Conside...

Thanks you much for the advice, your first suggestion didn't work as I was getting the error:

error: expected `::`, found `|`
  --> src/main.rs:97:46
   |
97 |         world.resource_scope(|world, res: <T>| {
   |                                              ^ expected `::`

error[E0308]: mismatched types
  --> src/main.rs:95:22
   |
93 | impl<T: Resource + StackedSprite> Command for SpriteStack<T> {
   |      - expected this type parameter
94 |     fn apply(self, world: &mut World) {
95 |         let res: T = world.get_resource::<T>().unwrap();
   |                  -   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected type parameter `T`, found `&T`
   |                  |
   |                  expected due to this
   |
   = note: expected type parameter `_`
                   found reference `&_`

But your second one worked perfectly! Cheers

echo kettle
#

Also, I don't think you really got my first advice correctly since from the error you're still using both resource_scope and get_resource. You should be using only one of them.

#

Finally, the second error you got is because you specified res: T but that's wrong, it should be &T. But since you already specified get_resource::<T> you shouldn't even need to specify the type of res, since it can be automatically determined.

rugged gale
#

Thanks for taking your time to answer again, you where right, I I didn't quite grasp your first advice. So I reread the doc (again) and still don't get it 😅

This is what i've done so far with your first suggestion

 world.resource_scope(|world, mut res: Mut<T>| {
            if let Some(res) = world.get_resource::<T>() {
// Blah 
echo kettle
#

Let me repeat: you only need either resource_scope or get_resource, not both

steady forge
#

An app I'm working on uses resource_scope like so:

world.resource_scope::<AssetServer, ()>(|world, asset_server| {
    // use asset_server ...
    world.commands().trigger(LoadMapEvent::from(self));
});