rustrwlock

Calling a function inside an rwlock without locking in rust


I'm trying to write a sort of "game engine". In a few words the problem is that I'm calling a function through a rwlock and that function call another function that can lock that same rwlock.

Here's in detail :

I have an event_loop, each frame I'm calling a draw function inside a scene struct. The scene struct is stored inside a rc<rwlock<Scene>>

fn main () {
    current_scene.draw(&mut target, &draw_params);
}

pub struct Scene{
    objects: Vec<Rc<RwLock<dyn ObjectTrait>>>,
}
impl Scene{

    pub fn draw(&  self, target: &mut Frame, draw_params: &glium::DrawParameters) -> (){
        for object in & self.objects{
            let mut object = object.write().unwrap();
            object.update_script();
            object.draw(target, &perspective, &view, &vec![], draw_params);
        } 

    }

}

The update_script function update all the script. A script is define by a struct that implement the script trait.

pub struct CameraMovement {
    script: Script
}

impl ScriptTrait for CameraMovement {
    fn update(&self)->() {
        //do some stuff every frame
    }
}

But it can happen that I want to interact with others object or with the scene inside the update function. So in the script struct I have a scene rc and methods like : get_object_by_name() get_camera() get_scene()

But I can't access the Rwlock because it's already locked by the first function call.

So what do I need to do ?

Is there a way to call the function without locking the rwlock ?

Do I have to refactor my code ?

Do I have to pass the reference to the lock ?

Here's a minimal reproducible example :

use std::rc::Rc;
use std::sync::RwLock;

struct Script {
    pub scene: Rc<RwLock<Scene>>,
}

trait ScriptTrait {
    fn update(&self) -> ();
}

struct MyCustomizedScript {
    script : Script
}

impl ScriptTrait for MyCustomizedScript {
    fn update(&self) -> () {
        match self.script.scene.try_read() {
            Ok(n) => return (),
            Err(e) => panic!("err try write scene")
        }
    }
}

struct Object {
    scripts: Vec<Box<(dyn ScriptTrait)>>
}

impl Object {
    fn update_script(& mut self) -> () {
        for i in 0..self.scripts.len() {
            self.scripts[i].update();
        }
    }
}

struct Scene {
    objects: Vec<Rc<RwLock<Object>>>
}

impl Scene {
    pub fn draw (&self) {
        for object in & self.objects{
            let mut object = object.write().unwrap();
            object.update_script();
            //object.draw();
        }
    }
    
    pub fn add_object(&mut self, object: Rc<RwLock<Object>>) -> (){
        self.objects.push(object); 
    }
}


fn main() {
    let scene = Rc::new(RwLock::new(Scene {
        objects: vec![]
    }));
    let customScript = Box::new(MyCustomizedScript {
        script: Script {
            scene : scene.clone()
        }
    });
    let object = Rc::new(RwLock::new(Object {
        scripts: vec![customScript]
    }));
    let mut scene = scene.write().unwrap();
    scene.add_object(object);
    scene.draw();
}

Solution

  • So you have multiple Object which each have multiple Script.

    Scene
        - Object
            - impl ScriptTrait <--- need to have read access to the whole Scene
            - impl ScriptTrait
        - Object
            - impl ScriptTrait
    

    The problem is that when you iterate througth all the object to update them, you need to write lock the Scene, but then you cannot read lock it any more.

    Pass &self

    one of the way is to pass &self down to Object then pass it to ScriptTrait.

    rust playground

    // . . .
    trait ScriptTrait {
        fn update(&mut self, scene: &Scene) -> ();
    }
    // . . .
    impl Object {
        fn update_script(&mut self, scene: &Scene) -> () {
            for script in self.scripts.iter_mut() {
                script.update(scene);
            }
        }
    }
    // . . .
    pub fn draw(&mut self) {
        for object in self.objects.clone() {
                                //^^^^^^^^
                                // you will need to clone the Vec 
                                // to avoid making mutable borrow of self
                                // it only clone the `Arc` pointer for each object,
                                // but might still be expensive for large amount
            let mut object = object.write().unwrap();
            object.update_script(self);
        }
    }
    

    Unlock

    if you can ensure that the Vec of Object will not be change, i.e. element append, deletion, swap... you can get the write guard of the object then unlock the scene for the object

    rust playground

    // . . .
    fn draw_scene(scene: Arc<RwLock<Scene>>) {
        let len = scene.read().unwrap().objects.len();
    
        for i in 0..len {
            scene.write().unwrap().objects[i]
                .write()
                .unwrap()
                .update_script();
        }
    }
    // . . .
    

    Update on Object Pair

    instead of taking the whole scene to update script, just take one object at time.

    rust playground

    // . . .
    pub fn draw(&self) {
        for (i, a) in self.objects.iter().enumerate() {
            for (j, b) in self.objects.iter().enumerate() {
                if i == j {
                    continue;
                }
    
                a.write().unwrap().update_script(&b.read().unwrap());
            }
        }
    }
    // . . .