I'm trying to write a simple game engine, but the normal patterns I would use to do this don't work here due to the strict borrowing rules.
There is a World
struct which owns a collection of Object
trait objects. We can think of these as moving physical objects in the game engine. World
is responsible for calling update
and draw
on each of these during each game tick.
The World
struct also owns a collection of Event
trait objects. Each of these events encapsulates an arbitrary piece of code that modifies the objects
during the game ticks.
Ideally, the Event
trait object has a single method do_event
that takes no arguments that can be called by World
during each game tick. The problem is that a particular Event
needs to have mutable references to the objects that it modifies but these objects are owned by World
. We could have World
pass mutable references to the necessary objects when it calls do_event
for each event, but how does it know which objects to pass to each event object?
If this were C++ or Python, when constructing a particular Event
, I would pass the target of the event to the constructor where it would be saved and then used during do_event
. Obviously, this doesn't work in Rust because the targets are all owned by World
so we cannot store mutable references elsewhere.
I could add some ID system and a lookup table so World
knows which objects to pass to which Event
s but this is messy and costly. I suspect my entire way of approaching this class of problem needs to change, however, I am stumped.
A sketch of my code:
trait Object {
fn update(&mut self);
fn draw(&self);
}
trait Event {
fn do_event(&mut self);
}
struct World {
objects: Vec<Box<dyn Object + 'a>>,
events: Vec<Box<dyn Event + 'a>>,
}
impl World {
fn update(&mut self) {
for obj in self.objects.iter_mut() {
obj.update();
}
for evt in self.events.iter_mut() {
evt.do_event();
}
}
fn draw(&mut self) { /*...*/ }
}
struct SomeEvent<'a, T: Object> {
target: &'a mut T,
}
impl<'a, T: Object> Event for SomeEvent<'a, T> {
fn update(&self) {
self.target.do_shrink(); // need mutable reference here
}
}
I know that RefCell
enables multiple objects to obtain mutable references. Perhaps that is the direction I should go. However, based on what I learned from the Rust book, I got the sense that RefCell
s break some of Rust's main ideas by introducing unsafe code and circular references. I guess I was just wondering if there was some other obvious design pattern that adhered more to the idiomatic way of doing things.