Hello fellow rust users. The very same as last time I maneuvered myself into a corner and can't escape. I know the "borrow as mutable"-problem comes up many times. Please bear with me.
I get two error messages. I understand them, but I'm unsure how to remedy them. I've prepared an example below.
I would be happy if someone could help me fix the errors. And also I'd like to hear further code suggestions.
Thanks! I've prepped a gist here
The errors:
error[E0499]: cannot borrow `game_runtime.objects` as mutable more than once at a time
--> src/main.rs:106:22
|
103 | let mut objects: Vec<&mut Box<dyn Machine + Send + Sync>> = gather(requested_ids, &mut game_runtime.objects);
| -------------------- first mutable borrow occurs here
...
106 | let my_object = game_runtime.objects.get_mut(&id).unwrap();
| ^^^^^^^^^^^^^^^^^^^^ second mutable borrow occurs here
...
126 | }
| - first borrow ends here
error[E0499]: cannot borrow `*objects` as mutable more than once at a time
--> src/main.rs:179:9
|
179 | match objects.get_mut(&id) {
| ^^^^^^^ mutable borrow starts here in previous iteration of loop
...
187 | }
| - mutable borrow ends here
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0499`.
The code:
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::thread;
use std::time::{Duration};
pub type TICK= u64;
fn main() {
let mut game_runtime = GameRuntime { objects: HashMap::new(), chunks: HashMap::new() };
//create a player
//in reality load data from database
let player_id = 1;
let o = Player::new(player_id);
let chunk_position = Position(o.position.0 / 100, o.position.1 / 100, o.position.2 / 100);//dummy space partition
//insert positions into game_runtime.chunks
// which is: HashMap<space partitioned position, HashMap<ID, real-Position>>
match game_runtime.chunks.entry(chunk_position) {
Entry::Occupied(position_map) => {
position_map.into_mut().insert(player_id, chunk_position);
}
Entry::Vacant(position_map) => {
let mut map = HashMap::new();
map.insert(player_id, o.position);
position_map.insert(map);
}
}
//insert player state machine
game_runtime.objects.insert(player_id, GameObject {
state_machine: Box::new(o),
});
//create player END
//create a enemy
//in reality load data from database
let enemy_id = 2;
let o = Enemy::new(enemy_id);
let chunk_position = Position(o.position.0 / 100, o.position.1 / 100, o.position.2 / 100);//dummy space partition
//insert positions into: HashMap<space partitioned position, HashMap<ID,real-Position>>
match game_runtime.chunks.entry(chunk_position) {
Entry::Occupied(position_map) => {
position_map.into_mut().insert(enemy_id, chunk_position);
}
Entry::Vacant(position_map) => {
let mut map = HashMap::new();
map.insert(enemy_id, o.position);
position_map.insert(map);
}
}
//insert enemy state machine
game_runtime.objects.insert(enemy_id, GameObject {
state_machine: Box::new(o),
});
//create enemy END
let mut tick_counter: u64=0;
//each tick advance state machine with ID
let mut tick_scheduling_map: HashMap<TICK, Vec<ID>> = HashMap::new();
//test data: on tick 5 wake up object with id 1
tick_scheduling_map.insert(5, vec![1]);
thread::spawn(move || loop {
//for next loop iteration
let mut next: HashMap<TICK, Vec<ID>> = HashMap::new();
//this entire part is soo deeply nested. Is it possible to extract the mini-jobs into separate functions and let it still compile? Currently it doesn't.
match tick_scheduling_map.entry(tick_counter) {
Entry::Occupied(vec) => {
for id in vec.get().iter() {
let requested_ids;
//get current state machine
match game_runtime.objects.get_mut(&id) {
Some(ref mut object_by_id) => {
match object_by_id.state_machine {
ref mut my_object => {
println!("on tick {} wake up {}", tick_counter, id);
//on tick returns a list of enemies (ids) to battle against
match my_object.schedule(tick_counter, &game_runtime.chunks) {
Some(_requested_ids) => {
//save list of enemies
requested_ids = _requested_ids;
}
None => { requested_ids = vec![]; }
}
}
}
}
None => { requested_ids = vec![]; }
}
//now gather all requested enemies
let mut objects: Vec<&mut Box<dyn Machine + Send + Sync>> = gather(requested_ids, &mut game_runtime.objects);
//but get current state machine again in order to call trigger_event on it
let my_object = game_runtime.objects.get_mut(&id).unwrap();
//combine both
match my_object.state_machine.trigger_event(tick_counter, &mut objects) {
//trigger_event returns integer >=1 specifying when to reschedule
Some(rescheduled_at_tick) => {
if rescheduled_at_tick >= 1 {
let next_tick = tick_counter + rescheduled_at_tick;
match next.entry(next_tick) {
Entry::Vacant(id_vec) => {
id_vec.insert(vec![*id]);
}
Entry::Occupied(mut id_vec) => {
id_vec.get_mut().push(*id);
}
};
}
}
None => {}
}
}
vec.remove_entry();
}
Entry::Vacant(_v) => {}
}
//update map for next schedules
for (tick, id_vec) in &next {
match tick_scheduling_map.entry(*tick) {
Entry::Vacant(vec) => {
vec.insert(id_vec.to_vec());
}
Entry::Occupied(mut vec) => {
vec.get_mut().extend(id_vec.to_vec());
}
};
}
//remove current tick schedules
tick_scheduling_map.remove(&tick_counter);
tick_counter+=1;
thread::sleep(Duration::from_millis(1000));
});
}
pub trait Machine {
fn schedule(&mut self, current_tick: u64, chunks: &HashMap<Position, HashMap<ID, Position>>) -> Option<Vec<u64>>;
fn trigger_event(&mut self, tick_counter: u64, objects: &mut std::vec::Vec<&mut std::boxed::Box<(dyn Machine + std::marker::Send + std::marker::Sync + 'static)>>) -> Option<u64>;
fn get_position(&self) -> Position;
fn set_position(&mut self, position: Position);
fn get_id(&self) -> ID;
}
impl std::fmt::Debug for 'static + Machine + std::marker::Send + std::marker::Sync {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Machine at {:?} with id: {}",self.get_position(),self.get_id())
}
}
pub struct GameRuntime {
pub objects: HashMap<ID, GameObject>,
pub chunks: HashMap<Position, HashMap<ID, Position>>,
}
pub struct GameObject {
pub state_machine: Box<dyn Machine + Send + Sync>,
}
fn gather(requested_ids: Vec<u64>, objects: &mut HashMap<ID, GameObject>) -> Vec<&mut Box<dyn Machine + Send + Sync>> {
let mut out = vec![];
for id in requested_ids.iter() {
match objects.get_mut(&id) {
Some(mut object_by_id) => {
out.push(&mut object_by_id.state_machine);
}
None => {}
}
}
out
}
#[derive(Debug, Clone)]
#[derive(PartialEq, Eq, Hash)]
#[derive(Default)]
#[derive(Copy)]
pub struct Position(pub i64, pub i64, pub i64);
pub type ID = u64;
pub struct Player {
pub id: u64,
pub position: Position,
}
impl Player {
pub fn new(id: u64) -> Self {
Player {
position: Position(5, 1, 0),
id,
}
}
}
impl Machine for Player {
fn schedule(&mut self, _current_tick: u64, _chunks: &HashMap<Position, HashMap<ID, Position>>) -> Option<Vec<u64>>{
Some(vec![1])//finds nearest neighbors via space partition and distance and returns id, in this case battle against object with id 1
}
fn trigger_event(&mut self, _tick_counter: u64, _objects: &mut std::vec::Vec<&mut std::boxed::Box<(dyn Machine + std::marker::Send + std::marker::Sync + 'static)>>) -> Option<u64>
{
//... handle internal event here
//for example
for mut member in _objects.iter_mut() {//do something -- anything -- with the objects and self
let current_position = member.get_position();
member.set_position(Position(current_position.0-1,current_position.1+1,current_position.2));
}
None//then do not reschedule event
}
//must be repeated for every object impl, sadly -- is there a way out?
fn get_position(&self) -> Position{
self.position
}
//must be repeated for every object impl, sadly -- is there a way out?
fn set_position(&mut self, position: Position){
self.position=position;
}
//must be repeated for every object impl, sadly -- is there a way out?
fn get_id(&self) -> ID{
self.id
}
}
pub struct Enemy {
pub id: u64,
pub position: Position,
}
impl Enemy {
pub fn new(id: u64) -> Self {
Enemy {
position: Position(0, 0, 0),
id,
}
}
}
impl Machine for Enemy {
fn schedule(&mut self, _current_tick: u64, _chunks: &HashMap<Position, HashMap<ID, Position>>) -> Option<Vec<u64>>{
None
}
fn trigger_event(&mut self, _tick_counter: u64, _objects: &mut std::vec::Vec<&mut std::boxed::Box<(dyn Machine + std::marker::Send + std::marker::Sync + 'static)>>) -> Option<u64>
{
//... handle internal event here
None//then do not reschedule event
}
//must be repeated for every object impl, sadly -- is there a way out?
fn get_position(&self) -> Position{
self.position
}
//must be repeated for every object impl, sadly -- is there a way out?
fn set_position(&mut self, position: Position){
self.position=position;
}
//must be repeated for every object impl, sadly -- is there a way out?
fn get_id(&self) -> ID{
self.id
}
}
!!! Thanks