Mutate in double loop ( SPECS )

Hello,

I am experimenting in a SPECS game. And I am faced with a problem.

I go through a list of components (simplifying) speed and velocity. If 2 objects if collides, I have to update the velocity of one with that of the other.

In the example below this is not possible because of the borrow check rules.

Is there a possibility for going beyond this rule with the pointer or others (Rc, Box, RefCell, etc..) . Or should I have found another logic.

Thank you.

use specs::{Component, VecStorage, World, WorldExt};
use specs_derive::Component;
use specs::prelude::*;
use rand::prelude::*;

#[derive(Component, Debug)]
#[storage(VecStorage)]
pub struct Position{
    pub x : f64,
    pub y : f64,
}

#[derive(Component, Debug)]
#[storage(VecStorage)]
pub struct Velocity{
   pub x : f64,
   pub y : f64,
}

// in a real case, we add ´Entities<'a>´ so as not to compare the same ´Entities<'a>´
pub struct SysCollides;
impl<'a> System<'a> for SysCollides {
    type SystemData = (ReadStorage<'a, Position>,
                       WriteStorage<'a, Velocity>,
                       );

    fn run(&mut self,  data: Self::SystemData) {
        let ( positions, mut velocities) = data ;
        for (pos_a, vel_a) in (& positions, & velocities ).join() {
            for (pos_b, vel_b )in (& positions,& velocities).join() {
                // condition to have same position
                if pos_a.x == pos_b.x && pos_a.y == pos_b.y  {
                    // if same position, else modify vel_b to be equal vel_a
                    // vel_b.x = vel_a.x;
                    // vel_b.y = vel_a.y;
                }
            }
        }
    }
}

fn initialize_entity(world: &mut World){
    let mut rng = rand::thread_rng();
    let pos_x : f64 = rng.gen_range(50.0, 700.0);
    let pos_y : f64 = rng.gen_range(50.0, 500.0);
    let vel_y : f64 = rng.gen_range(-1.0, 1.0);
    let vel_x : f64 = rng.gen_range(-1.0, 1.0);
    
    world.create_entity()
        .with(Position{ x : pos_x, y : pos_y})
        .with(Velocity{ x : vel_x, y : vel_y })
        .build();
}

fn main() {
    println!("Hello, world!");
    let mut world = World::new();
    // -- compoments --
    world.register::<Position>();
    world.register::<Velocity>();
    for _ in 0..1000 {
        initialize_entity(&mut world);
    }
    
    let mut dispatcher = DispatcherBuilder::new()
        .with(SysCollides, "sys_collides", &[])
        .build();
    dispatcher.setup(&mut world);
    let mut tps = 0;
    // just for exemple run
    'running : loop {
        dispatcher.dispatch(&mut world);
        world.maintain();
        if tps >= 10_000 { break 'running; }
        tps += 1;   
    }
}

You should probably just loop over your positions, check the condition and then update your velocities using index access. Playground.

If I'm not mistaken that's how ECS is supposed to work.

See this SO question for some pointers on better algorithms than the one in the playground.

Thank to your reply.

The problem is that I have to iterate on the velocity.

playground

why do you need to iterate on velocity? Are your indexes not consistent between both vectors?

I'm no expert on ECS, but from what I understand, the point is to use indexes to avoid this problem in the first place.

Ok, I just understood the logic of your explanation, I stayed on my problem.

In fact, what links positions and velocities is the entity. I will work on this solution.

And I will post the solution under SPECS, it can help other people.
Thank you.

2 Likes

it does not work, in the current state of my knowledge. I don't know the procedure to reach a particular index in the "Vel" vector. For information, the structure of the vector should look like this.

"Vec<velocity {x, y}, Entity.id>"

I will try to ask the SPECS team the question

The solution was found via discord. Thanks to @Uriopass

Make a Vec containing collisions then iterate on this vec later. That way, you can have a nested immutable loop on the same structure without borrowing being a problem.

use specs::{Component, VecStorage, World, WorldExt};

use specs_derive::Component;

use specs::prelude::*;

use rand::prelude::*;

#[derive(Component, Debug)]

#[storage(VecStorage)]

pub struct Position{

    pub x : f64,

    pub y : f64,

 }

#[derive(Component, Debug, Clone, Copy)]

#[storage(VecStorage)]

pub struct Velocity{

   pub x : f64,

   pub y : f64,

}

// in a real case, we add ´Entities<'a>´ so as not to compare the same ´Entities<'a>´

pub struct SysCollides;

impl<'a> System<'a> for SysCollides {

    type SystemData = (ReadStorage<'a, Position>,

                       WriteStorage<'a, Velocity>,

                       Entities<'a>

                       );

    fn run(&mut self,  data: Self::SystemData) {

        let ( positions, mut velocities, entities) = data ;

        

        let mut collides : Vec< (Velocity, Velocity, Entity, Entity)> = vec![];

        for (pos_a, vel_a, ent_a) in (& positions, & velocities, &*entities ).join() {

            for (pos_b, vel_b, ent_b )in (& positions,&  velocities, &*entities).join() {

                // condition to have same position

                if pos_a.x == pos_b.x && pos_a.y == pos_b.y  {

                    // if same position, else modify vel_b to be equal vel_a

                    //vel_b.x = vel_a.x;

                    //vel_b.y = vel_a.y;

                    collides.push((*vel_a, *vel_b, ent_a, ent_b));

                }

            }

        }

        for collide in collides.iter(){

            println!("{:?} - {:?}", collide.0 , collide.2.id());

        }

    }

}

fn initialize_entity(world: &mut World){

    let mut rng = rand::thread_rng();

    let pos_x : f64 = rng.gen_range(50.0, 700.0);

    let pos_y : f64 = rng.gen_range(50.0, 500.0);

    let vel_y : f64 = rng.gen_range(-1.0, 1.0);

    let vel_x : f64 = rng.gen_range(-1.0, 1.0);

    

    world.create_entity()

        .with(Position{ x : pos_x, y : pos_y})

        .with(Velocity{ x : vel_x, y : vel_y })

        .build();

}

fn main() {

    println!("Hello, world!");

    let mut world = World::new();

    // -- compoments --

    world.register::<Position>();

    world.register::<Velocity>();

    for _ in 0..1000 {

        initialize_entity(&mut world);

    }

    

    let mut dispatcher = DispatcherBuilder::new()

        .with(SysCollides, "sys_collides", &[])

        .build();

    dispatcher.setup(&mut world);

    let mut tps = 0;

    // just for exemple run

    'running : loop {

        dispatcher.dispatch(&mut world);

        world.maintain();

        if tps >= 10_000 { break 'running; }

        tps += 1;   

    }

}

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.