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 Events 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 RefCells 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.
After reading the question comments and watching kyren's RustConf 2018 talk (thanks trentcl), I've realized the OO approach to my game engine is fundamentally incompatible with Rust (or at least quite difficult).
After working on this for a day or so, I would highly recommend watching the posted video and then using the specs library (an implementation of the ECS system mentioned by user2722968).
Hope this helps someone else in the future.
Related
I'm considering making Rust my primary development language instead of Go, so I've been reading the docs.
One thing that my work requires a lot of is reading and writing from multiple threads into a custom database that is stored in memory as a single massive array and could be 32GB in size. The database functions are designed to avoid race conditions, and mutexes or atomic primitives are used where necessary.
The Rust doc implies that an array can only be either mutable (writeable) on a single thread, or non-mutable (read only) by many threads, and cannot be writable on one and readable on another simultaneously. How then can an in-memory database be used...? It doesn't make sense!
Do I have this wrong?
Forgive me that I can't give any specific Rust example, because I'm still learning the Rust syntax, and to be honest I need to know the answer to this question before using all my time learning a language I will be unable to use.
There is an unsafe way to do it, namely using the UnsafeCell, which returns mutable raw pointers to its interior data. These are not tracked by the Borrow-Checker and so you have to make sure the invariants are upheld
pub struct UnsafeVec<T> {
data: UnsafeCell<Vec<T>>
}
impl<T> UnsafeVec<T> {
pub fn new() -> Self {
UnsafeVec { data: UnsafeCell::new(Vec::new()) }
}
pub fn push(&mut self, arg: T) {
self.data.get_mut().push(arg)
}
pub unsafe fn index_mut(&self, index: usize) -> &mut T {
&mut (*self.data.get())[index]
}
}
unsafe impl<T> Sync for UnsafeVec<T> {}
unsafe impl<T> Send for UnsafeVec<T> {}
which allows you to write
fn main() {
let mut unsafe_vec = UnsafeVec::<i32>::new();
unsafe_vec.push(15);
unsafe {
*unsafe_vec.index_mut(0) += 1;
}
}
The method index_mut allows to modify the interior vector with an immutable reference.
The Sync and Send traits signal the compiler that the type can be safely shared across threads, which is only true if you prevent possible data races manually!.
Again, this is an unsafe option that requires you to uphold the invariants yourself.
Context
As an exercise, I'm attempting to re-implement https://github.com/urbanairship/sarlacc-pit in Rust.
Sarlacc-pit mirrors an external source into an in memory data structure (Set/Map/Etc). Clients of the library operate purely in terms of the collection with no knowledge that its contents are changing under the hood.
Problem
Clients need to keep an immutable reference to the collection, while a single update thread keeps a mutable reference in order to update its contents. This directly violates rust's guarantees but should be safe in this case with the following rough structure:
pub struct Map<K, V> {
delegate: SomeReferenceType<Arc<HashMap<K, V>>>
}
impl<K, V> Map<K, V> {
pub fn get(&self, k: &K) -> Option<&V> {
self.delegate.borrow().get(k)
}
fn update(&mut self, new_delegate: HashMap<K, V>) {
self.delegate.set(Arc::new(new_delegate));
}
}
pub struct UpdateService<K, V> {
collection: Arc<Map<K, V>>
}
impl<K, V> UpdateService<K ,V> {
pub fn get_collection(&self) -> Arc<Map<K, V>> {
collection.clone()
}
// Called from a thread run on a cadence
fn update_collection(&mut self) {
let new_value = /* fetch and process value from backing store */
self.collection.borrow_mut().update(new_value);
}
}
This doesn't compile for a number of reasons, I realize.
The core of the question is: What should the type of SomeReferenceType be to allow to allow these mutable and immutable references to co-exist without something like an ReadWriteLock? Am I missing something?
If update_collection is called from another thread, what guarantees do you have that the main thread isn't in the middle of reading from the collection at the same time? With the information you have provided, you need something like a RwLock or Mutex to make this safe.
You have asserted that you believe this to be safe. If there is an undisclosed constraint on your system that allows you to guarantee that a simultaneous read and write cannot happen then there may be a way to incorporate that into the types. But a better answer can't be given otherwise.
For example, if updates are infrequent, it might satisfy your use case to use three copies of your collection, and swap them over after each modification:
One for reading,
One for writing,
One for transitioning clients while swapping the collections
This would not be a "beginner" level Rust project though.
I'm fairly new to Rust and I've been trying to get a simple game working with ggez.
Now, normally in C++ or C# I would structure my game around having an instance of a "master" class that takes care of settings, frame limiting, window creation, event handling, etc., and "state" classes that are held by the master class. I would pass a pointer to the master class when creating the state class (something like game_master*) so that the state class can access the resources of the master class.
In Rust I can't pass a &mut self because the state could potentially outlive the reference.
pub fn new(_ctx: &mut Context) -> GameMaster {
let game = GameMaster {
state: None,
pending_state: None
};
game.state = Some(Box::new(TestState::new_with_master(&mut game))) <----- NOPE
game
}
I think this could be solved with lifetimes but I haven't found a way to get lifetimes working with traits. Passing the master in as a reference on every function call doesn't work either because only one mutable reference can be held at a time.
fn update(&mut self, ctx: &mut Context) -> GameResult<()> {
if self.state.is_some() {
(*self.state.as_mut().unwrap()).update(ctx, &mut self) <----- NOPE
}
else {
Ok(())
}
}
Is there a good way to do this in Rust?
I have a similar problem a while ago but I think the only choices are:
use Rc<RefCell<>> everywhere. Remember to also use Weak or add a special destroy method if you ever need to garbage collect these objects since they hold reference to each other.
use raw pointers and unsafe code. The problem is that with unsafe deference, you also lose the mutability check (RefCell ensures only one mutable reference exists at a time) in addition to the lifetime check.
There is a new Pin type in unstable Rust and the RFC is already merged. It is said to be kind of a game changer when it comes to passing references, but I am not sure how and when one should use it.
Can anyone explain it in layman's terms?
What is pinning ?
In programming, pinning X means instructing X not to move.
For example:
Pinning a thread to a CPU core, to ensure it always executes on the same CPU,
Pinning an object in memory, to prevent a Garbage Collector to move it (in C# for example).
What is the Pin type about?
The Pin type's purpose is to pin an object in memory.
It enables taking the address of an object and having a guarantee that this address will remain valid for as long as the instance of Pin is alive.
What are the usecases?
The primary usecase, for which it was developed, is supporting Generators.
The idea of generators is to write a simple function, with yield, and have the compiler automatically translate this function into a state machine. The state that the generator carries around is the "stack" variables that need to be preserved from one invocation to another.
The key difficulty of Generators that Pin is designed to fix is that Generators may end up storing a reference to one of their own data members (after all, you can create references to stack values) or a reference to an object ultimately owned by their own data members (for example, a &T obtained from a Box<T>).
This is a subcase of self-referential structs, which until now required custom libraries (and lots of unsafe). The problem of self-referential structs is that if the struct move, the reference it contains still points to the old memory.
Pin apparently solves this years-old issue of Rust. As a library type. It creates the extra guarantee that as long as Pin exist the pinned value cannot be moved.
The usage, therefore, is to first create the struct you need, return it/move it at will, and then when you are satisfied with its place in memory initialize the pinned references.
One of the possible uses of the Pin type is self-referencing objects; an article by ralfj provides an example of a SelfReferential struct which would be very complicated without it:
use std::ptr;
use std::pin::Pin;
use std::marker::PhantomPinned;
struct SelfReferential {
data: i32,
self_ref: *const i32,
_pin: PhantomPinned,
}
impl SelfReferential {
fn new() -> SelfReferential {
SelfReferential { data: 42, self_ref: ptr::null(), _pin: PhantomPinned }
}
fn init(self: Pin<&mut Self>) {
let this : &mut Self = unsafe { self.get_unchecked_mut() };
// Set up self_ref to point to this.data.
this.self_ref = &mut this.data as *const i32;
}
fn read_ref(self: Pin<&Self>) -> Option<i32> {
let this : &Self= self.get_ref();
// Dereference self_ref if it is non-NULL.
if this.self_ref == ptr::null() {
None
} else {
Some(unsafe { *this.self_ref })
}
}
}
fn main() {
let mut data: Pin<Box<SelfReferential>> = Box::new(SelfReferential::new()).into();
data.as_mut().init();
println!("{:?}", data.as_ref().read_ref()); // prints Some(42)
}
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.