Static and dynamic dispatch for same object?

Here's the situation:

  • I'm making a game with buildings.
  • Some buildings store references to other buildings, e.g. conveyor belts will store references of conveyor belts they are connected to.
    • These references need to be able to access information specific to that type of building.
  • A global list of all buildings will also be stored where type of building does not matter
    • e.g. for destructing buildings when the player requests so.

For point 2, I would need something like Rc<RefCell<ConveyorBelt>>, but for point 3, I would need something like Rc<RefCell<dyn Building>>. My thought would be to do something like this:

struct ConveyorBelt;
trait Building { /* ... */ }
impl Building for ConveyorBelt { /* ... */ }

trait BuildingPtr { /* wrapper methods */ }
impl<T: Building> BuildingPtr for Rc<RefCell<T>> {
    /* wrapper implementations */
}

let conveyor_ptr: Rc<RefCell<ConveyorBelt>> = Default::default();
let another_ptr = Rc::clone(&conveyor_ptr);
let dynamic_ptr: Box<dyn BuildingPtr> = Box::new(Rc::clone(&another_ptr));

println!("{}", conveyor_ptr.conveyor_specific_data);
println!("{}", dynamic_ptr.on_destroy_wrapper());

My question is if there is any better way to do this or should I go with what I have? Or is there an entirely different way to structure the code that supports the same features?

Disclaimer: I have no idea how to structure games.

I don’t appreciate the double indirection of Box<dyn BuildingPtr> though.

If Building is object safe then Rc<RefCell<dyn Building>> is a thing, and you can coerce Rc<RefCell<ConveyorBelt>> into that type. If this works for you it would also avoid restating and wrapping the Building interface.

I'd say the usual way to structure games in Rust is using an ECS.

Without going full ECS you could have multiple Vec or Vec-like data structure to store all your "game objects".

Both those methods would use an index to refer to other buildings.

That's really helpful, I didn't realize you could do that. I originally thought that because dyn Building requires extra data for a vtable that I had to choose between having only static dispatch pointers to some data or having only dynamic dispatch pointers. I should read more about what makes that possible.

I'm programming this with Amethyst which uses an ECS. The reason I'm looking into using smart pointers instead of entities is primarily so that I can avoid having to check if a particular component exists every time I need its data. E.G. instead of having to do something like:

if let Some(conveyor) = get_component::<Conveyor>(entity) {
    // ...
}

I can simply do:

conveyor_data.borrow().do_something();

Would there be a way to still use ECS while avoiding that problem? I would want a conveyor belt to be able to check and modify whichever item is on the belt behind it without having to first find the component that stores that data. With using pointers, I can just directly store a reference to that data.

Specs doesn't seem to allow indexing directly, which would get you:

let conveyor = conveyors[entity];

You could use unwrap. Both of these would require to clean up when a conveyor is removed.
I think if let is more robust. If you iterate over all conveyors you could avoid if let and skip during iteration.

For the items on the belt you shouldn't need a "thing" but just parts of it.
Like a Transform and maybe Weight.

Sure pointers let you directly access things but you loose a lot by going this way.
In Factorio or Satisfactory when the player is on a belt it gets moved.
To make all items and the player share a common trait you're gonna have to take a very tortuous path.

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.