From e6d8c1246fdfbee7d784766be7f27ebeceab92fe Mon Sep 17 00:00:00 2001 From: CrispyPin Date: Sat, 7 Dec 2024 20:22:52 +0100 Subject: [PATCH 1/4] remove a vec allocation from simulation step --- src/marble_engine.rs | 182 ++++++++++++++++++++++--------------------- 1 file changed, 92 insertions(+), 90 deletions(-) diff --git a/src/marble_engine.rs b/src/marble_engine.rs index 6b521b9..e4c997c 100644 --- a/src/marble_engine.rs +++ b/src/marble_engine.rs @@ -16,6 +16,7 @@ pub struct Machine { board: Board, marbles: Vec, powered: Vec, + events: Vec, input: Vec, input_index: usize, @@ -23,12 +24,27 @@ pub struct Machine { steps: usize, } +#[derive(Debug, Clone, Copy)] +enum Event { + Stay, + /// (new_pos, new_dir) + MoveTo(Pos, Direction), + /// (new_pos, new_dir, trigger_pos) + Trigger(Pos, Direction, Pos), + /// (other, new_dir) + /// other marble should be set to reverse of new_dir + /// and should be cancelled if it had a movement event planned + Bounce(usize, Direction), + Remove, +} + impl Machine { pub fn new_empty(input: Vec, width: usize) -> Self { Self { board: Board::new_empty(width, width), marbles: Vec::new(), powered: Vec::new(), + events: Vec::new(), input, input_index: 0, output: Vec::new(), @@ -113,116 +129,102 @@ impl Machine { return; } - #[derive(Debug, Clone, Copy)] - enum Event { - Stay, - /// (new_pos, new_dir) - MoveTo(Pos, Direction), - /// (new_pos, new_dir, trigger_pos) - Trigger(Pos, Direction, Pos), - /// (other, new_dir) - /// other marble should be set to reverse of new_dir - /// and should be cancelled if it had a movement event planned - Bounce(usize, Direction), - Remove, - } + self.events.clear(); + for &pos in &self.marbles { + let marble = self.board.get(pos).unwrap(); + let Tile::Marble { value, dir } = marble else { + panic!("broken marble"); + }; + let front_pos = dir.step(pos); + let Some(front_tile) = self.board.get(front_pos) else { + self.events.push(Event::Stay); + continue; + }; - let mut marble_events: Vec = self - .marbles - .iter() - .map(|&pos| { - let marble = self.board.get(pos).unwrap(); - let Tile::Marble { value, dir } = marble else { - panic!("broken marble"); - }; - let front_pos = dir.step(pos); - let Some(front_tile) = self.board.get(front_pos) else { - return Event::Stay; - }; + if let Tile::Powerable(PTile::Bag, _) = front_tile { + self.events.push(Event::Remove); + continue; + } + if let Tile::Powerable(PTile::IO, _) = front_tile { + self.output.push(value as u8); + self.events.push(Event::Remove); + continue; + } - if let Tile::Powerable(PTile::Bag, _) = front_tile { - return Event::Remove; - } - if let Tile::Powerable(PTile::IO, _) = front_tile { - self.output.push(value as u8); - return Event::Remove; - } + let can_move_to = |tile| matches!(tile, Some(Tile::Blank | Tile::Digit(_))); - let can_move_to = |tile| matches!(tile, Some(Tile::Blank | Tile::Digit(_))); - - let can_move_over = |tile| match tile { - Tile::Mirror(mirror) => { - let new_dir = mirror.new_dir(dir); - let target_pos = new_dir.step(front_pos); - let target = self.board.get(target_pos); - if can_move_to(target) { - Some((target_pos, new_dir)) - } else { - None - } - } - Tile::Arrow(new_dir) => { - let target_pos = new_dir.step(front_pos); - let target = self.board.get(target_pos); - if target_pos == pos || can_move_to(target) { - Some((target_pos, new_dir)) - } else { - None - } - } - _ => None, - }; - - if can_move_to(Some(front_tile)) { - Event::MoveTo(front_pos, dir) - } else if let Tile::Powerable(PTile::Trigger, _) = front_tile { - let target_pos = dir.step(front_pos); + let can_move_over = |tile| match tile { + Tile::Mirror(mirror) => { + let new_dir = mirror.new_dir(dir); + let target_pos = new_dir.step(front_pos); let target = self.board.get(target_pos); if can_move_to(target) { - Event::Trigger(target_pos, dir, front_pos) + Some((target_pos, new_dir)) } else { - Event::Stay + None } - } else if let Some((new_pos, new_dir)) = can_move_over(front_tile) { - Event::MoveTo(new_pos, new_dir) - } else if let Tile::Marble { - value: _, - dir: other_dir, - } = front_tile - { - if other_dir != dir { - Event::Bounce( - self.marbles.iter().position(|m| m == &front_pos).unwrap(), - dir.opposite(), - ) + } + Tile::Arrow(new_dir) => { + let target_pos = new_dir.step(front_pos); + let target = self.board.get(target_pos); + if target_pos == pos || can_move_to(target) { + Some((target_pos, new_dir)) } else { - Event::Stay + None } + } + _ => None, + }; + let e = if can_move_to(Some(front_tile)) { + Event::MoveTo(front_pos, dir) + } else if let Tile::Powerable(PTile::Trigger, _) = front_tile { + let target_pos = dir.step(front_pos); + let target = self.board.get(target_pos); + if can_move_to(target) { + Event::Trigger(target_pos, dir, front_pos) } else { Event::Stay } - }) - .collect(); + } else if let Some((new_pos, new_dir)) = can_move_over(front_tile) { + Event::MoveTo(new_pos, new_dir) + } else if let Tile::Marble { + value: _, + dir: other_dir, + } = front_tile + { + if other_dir != dir { + Event::Bounce( + self.marbles.iter().position(|m| m == &front_pos).unwrap(), + dir.opposite(), + ) + } else { + Event::Stay + } + } else { + Event::Stay + }; + self.events.push(e); + } // resolve bounces - for i in 0..marble_events.len() { - let event = marble_events[i]; + for i in 0..self.events.len() { + let event = self.events[i]; if let Event::Bounce(other_index, dir) = event { - match marble_events[other_index] { + match self.events[other_index] { // cancel bounces on marble that are about to disappear Event::Remove => { - marble_events[i] = Event::MoveTo(self.marbles[other_index], dir.opposite()) + self.events[i] = Event::MoveTo(self.marbles[other_index], dir.opposite()) } // let already bouncing marbles continue Event::Bounce(_, _) => (), // interrupt any other movement/staying to bounce - _ => marble_events[other_index] = Event::Bounce(i, dir.opposite()), + _ => self.events[other_index] = Event::Bounce(i, dir.opposite()), } } } // resolve deletions of tiles - for (i, event) in marble_events.iter().enumerate() { + for (i, event) in self.events.iter().enumerate() { if let Event::Remove = event { self.board.set(self.marbles[i], Tile::Blank); } @@ -230,7 +232,7 @@ impl Machine { // resolve triggers let mut triggers_activated = Vec::new(); - for event in &mut marble_events { + for event in &mut self.events { if let Event::Trigger(new_pos, dir, trigger_pos) = event { triggers_activated.push(*trigger_pos); *event = Event::MoveTo(*new_pos, *dir); @@ -238,10 +240,10 @@ impl Machine { } // resolve collisions (multiple marbles entering the same space) - for i in 0..(marble_events.len() - 1) { - let event = marble_events[i]; + for i in 0..(self.events.len() - 1) { + let event = self.events[i]; if let Event::MoveTo(new_pos, _dir) = event { - for other_event in &mut marble_events[(i + 1)..] { + for other_event in &mut self.events[(i + 1)..] { if let Event::MoveTo(other_pos, _other_dir) = other_event { // todo: maybe sort by direction so the sucessful direction is consistent if other_pos == &new_pos { @@ -253,7 +255,7 @@ impl Machine { } // resolve movement - for (i, &event) in marble_events.iter().enumerate() { + for (i, &event) in self.events.iter().enumerate() { if let Event::Remove = event { continue; } @@ -281,7 +283,7 @@ impl Machine { } // resolve deletions of marbles - for (i, event) in marble_events.iter().enumerate().rev() { + for (i, event) in self.events.iter().enumerate().rev() { if let Event::Remove = event { self.marbles.remove(i); } From 1d001be40300aee0c352e72273193731d21aa0af Mon Sep 17 00:00:00 2001 From: CrispyPin Date: Sat, 7 Dec 2024 20:37:33 +0100 Subject: [PATCH 2/4] measure step time over an entire frame --- src/editor.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/editor.rs b/src/editor.rs index 0cb0d7c..ef162d5 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -31,7 +31,7 @@ const MAX_ZOOM_IN: i32 = 3; const BOARD_MARGIN: isize = 3; const MAX_SPEED_POWER: u8 = 16; const SPEED_DIGITS: u8 = 5; -const MAX_FRAME_TIME_MICROS: u128 = 1000_000 / 30; +const MAX_FRAME_TIME_MICROS: u128 = 1_000_000 / 30; #[derive(Debug)] pub struct Editor { @@ -180,9 +180,7 @@ impl Editor { } fn step(&mut self) { - let start_time = Instant::now(); self.machine.step(); - self.step_time = start_time.elapsed().as_micros(); if self.complete_popup == Popup::Visible { self.complete_popup = Popup::Dismissed; @@ -334,9 +332,10 @@ impl Editor { if self.sim_state == SimState::Running { self.time_since_step += rl.get_frame_time(); let step_size = 1. / (1 << self.sim_speed) as f32; + let mut steps_taken = 0; + let start_time = Instant::now(); if self.time_since_step > step_size { let step_count = (self.time_since_step / step_size) as u32; - let start_time = Instant::now(); for _ in 0..step_count { if self.sim_state != SimState::Running { // pause on level completion @@ -346,9 +345,16 @@ impl Editor { break; } self.step(); + steps_taken += 1; } self.time_since_step -= step_count as f32 * step_size; } + let avg_step_time = start_time + .elapsed() + .as_micros() + .checked_div(steps_taken) + .unwrap_or_default(); + self.step_time = avg_step_time; } if rl.is_key_pressed(KeyboardKey::KEY_SPACE) { self.step_pressed() From 28213da9f372b76771fc54a418bbabc1b3953232 Mon Sep 17 00:00:00 2001 From: CrispyPin Date: Sat, 7 Dec 2024 20:46:06 +0100 Subject: [PATCH 3/4] keep track of maximum average step time --- src/editor.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/editor.rs b/src/editor.rs index ef162d5..457170b 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -63,6 +63,7 @@ pub struct Editor { selected_blueprint: usize, blueprint_scroll: usize, step_time: u128, + max_step_time:u128, } #[derive(Debug, PartialEq)] @@ -139,6 +140,7 @@ impl Editor { selected_blueprint: usize::MAX, blueprint_scroll: 0, step_time: 0, + max_step_time: 0, } } @@ -163,6 +165,7 @@ impl Editor { } fn start_sim(&mut self) { + self.max_step_time = 0; self.machine.reset(); self.machine.set_board(self.source_board.clone()); } @@ -355,6 +358,7 @@ impl Editor { .checked_div(steps_taken) .unwrap_or_default(); self.step_time = avg_step_time; + self.max_step_time = avg_step_time.max(self.max_step_time); } if rl.is_key_pressed(KeyboardKey::KEY_SPACE) { self.step_pressed() @@ -597,6 +601,7 @@ impl Editor { draw_usize(d, textures, self.machine.step_count(), 420, 4, 9, 2); draw_usize(d, textures, self.step_time as usize, 540, 42, 9, 1); + draw_usize(d, textures, self.max_step_time as usize, 540, 58, 9, 1); d.draw_text("input:", 603, 8, 10, Color::WHITE); if simple_button(d, 600, 20, 35, 15) { From 42a355d387f9a7b46f6b5890e3f38b3f43606b46 Mon Sep 17 00:00:00 2001 From: CrispyPin Date: Sat, 7 Dec 2024 20:49:23 +0100 Subject: [PATCH 4/4] use i16 for coordinates instead of isize, yielding better cache locality etc --- src/editor.rs | 4 ++-- src/marble_engine/board.rs | 11 ++++++----- src/marble_engine/pos.rs | 18 ++++++++++-------- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/editor.rs b/src/editor.rs index 457170b..90f2b29 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -13,7 +13,7 @@ use crate::{ level::Level, marble_engine::{ board::Board, - pos::Pos, + pos::{Pos, PosInt}, tile::{Direction, GateType, MathOp, MirrorType, PTile, Tile, WireType}, Machine, }, @@ -28,7 +28,7 @@ const HEADER_HEIGHT: i32 = 40; const FOOTER_HEIGHT: i32 = 95; const SIDEBAR_WIDTH: i32 = 200 + 32 * 2 + 5 * 4; const MAX_ZOOM_IN: i32 = 3; -const BOARD_MARGIN: isize = 3; +const BOARD_MARGIN: PosInt = 3; const MAX_SPEED_POWER: u8 = 16; const SPEED_DIGITS: u8 = 5; const MAX_FRAME_TIME_MICROS: u128 = 1_000_000 / 30; diff --git a/src/marble_engine/board.rs b/src/marble_engine/board.rs index 9f69ceb..7a401d3 100644 --- a/src/marble_engine/board.rs +++ b/src/marble_engine/board.rs @@ -2,6 +2,7 @@ use crate::{draw_scaled_texture, Textures}; use super::tile::*; use super::Pos; +use super::PosInt; use raylib::prelude::*; #[derive(Debug, Clone)] @@ -73,7 +74,7 @@ impl Board { } fn in_bounds(&self, p: Pos) -> bool { - p.x >= 0 && p.y >= 0 && p.x < self.width as isize && p.y < self.height as isize + p.x >= 0 && p.y >= 0 && p.x < self.width as PosInt && p.y < self.height as PosInt } pub fn get(&self, p: Pos) -> Option { @@ -116,11 +117,11 @@ impl Board { } } - pub fn grow_to_include(&mut self, p: Pos) -> (isize, isize) { + pub fn grow_to_include(&mut self, p: Pos) -> (PosInt,PosInt) { let mut offset_x = 0; let mut offset_y = 0; if p.x < 0 { - let len = p.x.unsigned_abs(); + let len = p.x.unsigned_abs() as usize; for row in &mut self.rows { let mut new_row = vec![Tile::Blank; len]; new_row.append(row); @@ -137,7 +138,7 @@ impl Board { } if p.y < 0 { - let len = p.y.unsigned_abs(); + let len = p.y.unsigned_abs() as usize; let mut new_rows = vec![vec![Tile::Blank; self.width]; len]; new_rows.append(&mut self.rows); self.rows = new_rows; @@ -148,7 +149,7 @@ impl Board { self.rows.resize(new_height, vec![Tile::Blank; self.width]); self.height = new_height; } - (offset_x as isize, offset_y as isize) + (offset_x as PosInt, offset_y as PosInt) } pub fn trim_size(&mut self, margin: usize) -> (usize, usize) { diff --git a/src/marble_engine/pos.rs b/src/marble_engine/pos.rs index 3979b3a..9eea35c 100644 --- a/src/marble_engine/pos.rs +++ b/src/marble_engine/pos.rs @@ -2,10 +2,12 @@ use std::ops::Add; use raylib::prelude::*; +pub type PosInt = i16; + #[derive(Debug, Default, Clone, Copy, PartialEq)] pub struct Pos { - pub x: isize, - pub y: isize, + pub x: PosInt, + pub y: PosInt, } impl Pos { @@ -34,8 +36,8 @@ impl Pos { impl From<(usize, usize)> for Pos { fn from(value: (usize, usize)) -> Self { Self { - x: value.0 as isize, - y: value.1 as isize, + x: value.0 as PosInt, + y: value.1 as PosInt, } } } @@ -43,8 +45,8 @@ impl From<(usize, usize)> for Pos { impl From<(i32, i32)> for Pos { fn from(value: (i32, i32)) -> Self { Self { - x: value.0 as isize, - y: value.1 as isize, + x: value.0 as PosInt, + y: value.1 as PosInt, } } } @@ -52,8 +54,8 @@ impl From<(i32, i32)> for Pos { impl From for Pos { fn from(vec: Vector2) -> Self { Self { - x: vec.x as isize, - y: vec.y as isize, + x: vec.x as PosInt, + y: vec.y as PosInt, } } }