3D Visualization Design and Implementation - Cannot modify data within Arc to change entity position

Hi all,

I've been having some issues with a program I've been writing on-and-off over the last year or so, and was hoping to get some input. I keep running into major hurdles in development that I think are caused largely by my unfamiliarity with graphics/game engine design. It's a little convoluted, so I'll give some context:

The basic concept is a generalized 3D visualization program for the results of a simulator program that I've developed earlier. I'm using a mix of nalgebra and glium (yes I know it's deprecated) to make it, but I'm constantly running into issues with the design, which has involved a few refactors. What it should do is take position data, either generated by the program itself (for a demo) or by another computer providing data (eventually), and render it on the local computer. I've got the rendering and some basic orbiting camera behavior, but my issue primarily arises when trying to actually _move_the objects within the world.

My basic structure is as follows:

  • The 'world' is comprised of a Scene struct, which is basically just a struct with a vector of entities.
    • Each Entity has an id, position, rotation, models, et.c., like you'd expect. They also have a placeholder Behavior list because I'd like to be able to make them more dynamic but I'm not there yet.
  • The world is rendered via Viewports, a box on the screen that renders as a camera. Show above are two small viewports and one large one. Each contains an Arc reference to the Scene to read the data and render it. Each needs only a read-only reference.
    • Eventually, there should be multiple viewports displaying different views: one could be static in reference to a map, with another window locked on to a moving entity, with another showing some telemetry data. Hence the multiple views rather than a single large window.
  • The primary event loop does event handling (mostly camera control), creates a blank frame, updates the graphical elements, and then iterates over every viewport and draws each entity.

Now that I have a working camera, I've tried to start moving some of the objects around. Nothing big yet, just moving one in a circle. However, even though everything's been declared mutable, I can't modify any of the entities within the Scene anymore. I can't modify the original struct because it's been moved into the Arc, and I can't modify through an Arc de-reference because I can't seem to implement the trait DerefMut.

Any ideas? I'm open to alternative structures for the program. Is there a way to allow the struct to be changed in its current form? Is there another type of reference I can use that will allow the Scene to be read by multiple viewports and still be written to?

The short version is use Mutex or RwLock

What you're looking for is interior mutability, covered in The Book here: RefCell<T> and the Interior Mutability Pattern - The Rust Programming Language

The longer answer is much more complicated and never really goes away. You ideally don't want to be here in the first place, so if you can figure out how to get rid of the Arc you will probably have a better time. The tools mentioned above do exist for a reason, and you shouldn't feel dirty for using them if you do have a reason, but a design that lets you avoid them will generally be much better.

3 Likes

ezgif-3-1eaeaa07d5
Thanks! That worked, I ended up using RwLock. I have one thread creating new points and updating entity position, and another reading it and drawing to frame. Ideally yes, it'd be cleaner to do it all on one loop to prevent multiple threads trying to use the data but this hopefully should be better when there's a few dozen objects going around.

Write:

let (tx_gui, rx_gui) = mpsc::sync_channel(1);
    // Thread for calculations
    thread::spawn(move || {
        loop {
            // Clock update
            t = (std::time::SystemTime::now().duration_since(start_time).unwrap().as_micros() as f32) / (2.0*1E6*std::f32::consts::PI);
            tx_gui.send(t).unwrap();

            // Log new position
            test_scene_ref_2.write().unwrap().change_entity_position(1, na::Point3::<f64>::new(5.0*t.sin() as f64, 5.0*t.cos() as f64, 0.0));
            
        }
    });

Read is just called in the draw command.

A nitpick is that you should be using Instant for measuring wall clock elapsed time.

One generalized option for this "compute thread/render thread" approach is to use another "update" channel that pushes a bunch of changes (eg. SetPosition { id, pos }) from the compute thread, and reading all the updates at the start of the draw thread (with try_recv()) to apply them to the draw version of that state. Depending on what you're doing this can be fairly simple or a nightmare, you need to consider the compute thread getting too far ahead of the render thread, handing making updates to keep them in sync etc...

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.