Consider the following simple structs:
struct Monster {
// ...
}
struct Player {
// ...
}
struct Missile {
// ...
//target: ???,
}
When writing game logic, it is very common to have objects refer to each other. In the example above, we have the structs Monster
, Player
and Missile
to illustrate the kinds of interactions needed.
Imagine we have the following traits: Position
and Think
. All the above structs implements Position
, and all except Missile
implements Think
.
The first thing to note is that Missile
is a homing missile: It stores a target and whenever the game updates Missile
objects, it will move it toward it's target.
As far as I can tell, it is impossible to sensibly store the target of this Missile
in Rust.
Obviously, the missile doesn't own its target. It just wants to access its Position
trait. The game objects must be shared, through Rc
or Gc
. But it's not like the Missile
can just store a Weak<???>
reference to something with a Position
trait. A Box<Position>
means consuming whatever object had that trait. A Box<Any>
does not allow downcasting to traits.
Making Missile<T>
and storing the target as Weak<T>
doesn't help. How would those Missile<T>
be stored? In one Collection<T>
for each kind of object the missile targets? Game objects will need to be more generic, and the dreadful Box<Any>
seems inevitable.
I'm rather new to Rust. It baffles my mind that this is straight out impossible. Surely, I must be missing something?
I'm also pretty new to Rust, but here's what I've found. You can use the std::rc::Rc<T>
struct in combination with Box. To have a mutable shared references, you need to wrap boxed item in a std::cell::RefCell
.
So, your player, monster, missile example would go something like this:
use std::cell::RefCell;
use std::rc::Rc;
trait Position {
fn position(&mut self);
}
struct Monster;
impl Position for Monster {
fn position(&mut self) {
println!("Rawr I am getting the monster's position");
}
}
struct Player {
x: i32,
}
impl Position for Player {
fn position(&mut self) {
println!("Getting player's position {}", self.x);
self.x += 1;
}
}
struct Missile {
target: Rc<RefCell<Box<Position>>>,
}
fn main() {
// Create some stuff
let player = Rc::new(RefCell::new(Box::new(Player{x: 42}) as Box<Position>));
let monster = Rc::new(RefCell::new(Box::new(Monster) as Box<Position>));
// Our missile: initial target - monster
let mut missile = Missile{target: monster};
// Should be a monster
missile.target.borrow_mut().position();
// Redirect missile to player
missile.target = player.clone();
// Should be a player
missile.target.borrow_mut().position();
// Show that it is in fact a reference to the original player
player.borrow_mut().position();
}
However, it is usually possible to design entity systems that don't require shared references, and this is considered to be more idiomatic Rust. If your entity system is really complex, I'd recommend using an Entity Component System.
EDIT: Improved code and information, and removed some inaccurate information.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With