diff --git a/examples/particles/Cargo.toml b/examples/particles/Cargo.toml index 7c7caa8..ac474eb 100644 --- a/examples/particles/Cargo.toml +++ b/examples/particles/Cargo.toml @@ -6,4 +6,4 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -hypoloop = {version = "0.1.1", path = "D:/Code/hypoloop"} \ No newline at end of file +hypoloop = {version = "0.1.1", path = "D:/OneDrive/Code/hypoloop"} \ No newline at end of file diff --git a/examples/particles/src/main.rs b/examples/particles/src/main.rs index fad6ce3..08148eb 100644 --- a/examples/particles/src/main.rs +++ b/examples/particles/src/main.rs @@ -1,16 +1,20 @@ -use hypoloop::Simulation; +use hypoloop::core::Loop; // look into using closures for this fn main() { // create sim and configure it - let mut sim = Simulation::new(); - sim.set_update_function(test); + let mut sim = Loop::new(); //sim.set_realtime(false); - // run sim - sim.run(); -} + // test variable + let mut x: f32 = 0.0; -fn test() { - println!("Test"); + // run the simulation using custom update logic + sim.run(|state| { + state.debug_tick(); + + x += 2.0 * state.get_timescale(); + + //println!("Delta time: {} | Timescale: {} | Sim time: {} | x: {}", state.get_delta_time(), state.get_timescale(), state.get_sim_time().as_millis(), x); + }); } \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 6faaa63..5bcb58f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,145 +1,177 @@ -use std::time::{Duration, Instant}; +pub mod core { + use std::time::{Duration, Instant}; + + /// Contains mutable simulation state which can be changed via callback functions + #[derive(Copy, Clone)] + pub struct State { + update_interval: u32, + timescale: f32, + simulate: bool, + delta_time: u32, + irl_time: Duration, + sim_time: Duration, + last_tick: Instant + } -// debug constants -const DEBUG_LOOP: bool = false; -const DEBUG_TIME: bool = true; + impl State { + /// Creates a default State object + pub fn new() -> State { + // Create default state object + let mut new_state = State { + update_interval: 40, + timescale: 1.0, + simulate: true, + delta_time: 0, + irl_time: Duration::new(0,0), + sim_time: Duration::new(0,0), + last_tick: Instant::now() + }; -/// Contains all per-simulation logic and state -// update_interval is the minimum delay (in milliseconds) between update ticks -// timescale is the rate of the simulation proportional to real-time -// if realtime is false, the simulation runs as fast as possible and doesn't run the display function -pub struct Simulation { - update_interval: u32, - timescale: f32, - realtime: bool, - simulate: bool, - update_function: fn(), - display_function: fn() -} + // Make sure that delta_time always starts the same as update_interval + new_state.delta_time = new_state.update_interval; -impl Simulation { - /// Creates a new simulation with default values - pub fn new() -> Simulation { - Simulation { - update_interval: 40, - timescale: 1.0, - realtime: true, - simulate: true, - update_function: default_update, - display_function: default_display + // Return this default state + new_state + } + + /// Returns the current "delta time", the time elapsed since the last update tick in milliseconds + pub fn get_delta_time(self) -> u32 { + self.delta_time + } + + /// Returns the current IRL time elapsed since the start of the simulation + pub fn get_irl_time(self) -> Duration { + self.irl_time + } + + /// Returns the current simulation time elapsed since the start of the simulation + pub fn get_sim_time(self) -> Duration { + self.sim_time + } + + /// Returns the current "timescale", the speed of simulation time relative to real time + pub fn get_timescale(self) -> f32 { + self.timescale + } + + /// Returns the time of the last tick + pub fn get_last_tick(self) -> Instant { + self.last_tick + } + + /// Pauses the simulation + pub fn pause(&mut self) { + self.simulate = false; + } + + /// Resumes the simulation + pub fn resume(&mut self) { + self.simulate = true; + } + + /// Changes the simulation timescale + pub fn set_timescale(&mut self, timescale: f32) { + self.timescale = timescale; + } + + /// Prints a string of information about the current tick + pub fn debug_tick(self) { + let elapsed_time = Instant::now().duration_since(self.last_tick); + let loop_delay_ms = elapsed_time.as_nanos() as f32 / 1_000_000.0; + let loop_rate_hz = 1000.0 / loop_delay_ms; + println!("IRL time: {}ms | Sim time: {}ms | Tick delay/rate: {}ms/{}hz", self.irl_time.as_millis(), self.sim_time.as_millis(), loop_delay_ms, loop_rate_hz); } } - /// Allows the user to pass in a custom update function - pub fn set_update_function(&mut self, user_function: fn()) { - self.update_function = user_function; + /// The simulation loop itself + pub struct Loop { + state: State, + realtime: bool } - - /// Allows the user to pass in a custom display function - pub fn set_display_function(&mut self, user_function: fn()) { - self.display_function = user_function; - } - - /// Allows the user to turn realtime mode on/off - pub fn set_realtime(&mut self, realtime: bool) { - self.realtime = realtime; - } - - /// Initializes and runs the simulation - pub fn run(&self) { - // start the clock to keep track of real time - let clock_start = Instant::now(); - // keep track of the last tick time - let mut last_tick = Instant::now(); + impl Loop { + /// Creates a new simulation with default values + pub fn new() -> Loop { + // Return a Loop object with a default State + Loop { + state: State::new(), + realtime: true + } + } - // real-time and sim-time clocks - let mut irl_time = Duration::new(0, 0); - let mut sim_time = Duration::new(0, 0); - - while self.simulate { - // TODO - support frameskips - if !self.realtime || delta_time(last_tick) >= self.update_interval { - // mutable delta time and timescale for flexibility - let mut current_timescale: f32; - let mut current_delta_time: u32; - let elapsed_time = Instant::now().duration_since(last_tick); + /// Initializes and runs the simulation using a user-supplied callback as the update logic + pub fn run(&mut self, mut update_callback: impl FnMut(&mut State)) { + // Make sure the simulation will run + self.state.simulate = true; + + // start the clock to keep track of real time + let clock_start = Instant::now(); - // update clocks + while self.state.simulate { + // TODO - support frameskips + if !self.realtime || delta_time(self.state.last_tick) >= self.state.update_interval { + // mutable delta time and timescale for flexibility + let elapsed_time = Instant::now().duration_since(self.state.last_tick); + + // update clocks + if self.realtime { + self.state.delta_time = delta_time(self.state.last_tick); + self.state.sim_time += elapsed_time.mul_f32(self.state.timescale); + self.state.irl_time += elapsed_time; + } else { + self.state.delta_time = self.state.update_interval; + self.state.sim_time += Duration::from_millis(self.state.update_interval as u64); + self.state.irl_time = Instant::now().duration_since(clock_start); + } + + // update + update_callback(&mut self.state); + + // record last tick time + self.state.last_tick = Instant::now(); + } + + // display if self.realtime { - current_timescale = self.timescale; - current_delta_time = delta_time(last_tick); - sim_time += elapsed_time.mul_f32(self.timescale); - irl_time += elapsed_time; - } else { - current_timescale = 1.0; - current_delta_time = self.update_interval; - sim_time += Duration::from_millis(self.update_interval as u64); - irl_time = Instant::now().duration_since(clock_start); + display(delta_time(self.state.last_tick), self.state.timescale, self.state.update_interval); } - - // DEBUG - if DEBUG_TIME { - let loop_delay_ms = elapsed_time.as_nanos() as f32 / 1_000_000.0; - let loop_rate_hz = 1000.0 / loop_delay_ms; - println!("Realtime: {} | IRL time: {}ms | Sim time: {}ms | Tick delay/rate: {}ms/{}hz", self.realtime, irl_time.as_millis(), sim_time.as_millis(), loop_delay_ms, loop_rate_hz); - } - - // record last tick time - last_tick = Instant::now(); - - // update - update(self.update_function, current_delta_time, current_timescale); - } - - // display - if self.realtime { - display(self.display_function, delta_time(last_tick), self.timescale, self.update_interval); } } + + /// Turns real-time mode on/off + pub fn set_realtime(&mut self, realtime: bool) { + self.realtime = realtime; + } } -} - - -// update function -// this is where all your per-tick logic should go -fn update(user_function: fn(), delta_time: u32, timescale: f32) { - // DEBUG - if DEBUG_LOOP { - println!("Updating..."); + + + // update function + // this is where all your per-tick logic should go + fn update(user_function: fn(), delta_time: u32, timescale: f32) { + // use timestep to scale per-tick calculations appropriately + let timestep: f32 = delta_time as f32 / 1000.0 * timescale; + + // call user update function + user_function(); } - - // use timestep to scale per-tick calculations appropriately - let timestep: f32 = delta_time as f32 / 1000.0 * timescale; - - // call user update function - user_function(); -} - -// display function -// this is where you should call a render function -fn display(user_function: fn(), delta_time: u32, timescale: f32, update_interval: u32) { - // DEBUG - if DEBUG_LOOP { - println!("Displaying..."); + + // display function + // this is where you should call a render function + fn display(delta_time: u32, timescale: f32, update_interval: u32) { + // use interpolation to smooth display values between ticks + let interpolation: f32 = delta_time as f32 / update_interval as f32 * timescale; + } + + // gets the time in milliseconds that's elapsed since the earlier Instant + fn delta_time(earlier: Instant) -> u32 { + Instant::now().duration_since(earlier).as_millis() as u32 + } + + // default update function (does nothing) + fn default_update() { + } + + // default display function (does nothing) + fn default_display() { } - - // use interpolation to smooth display values between ticks - let interpolation: f32 = delta_time as f32 / update_interval as f32 * timescale; - - // call user display function - user_function(); -} - -// gets the time in milliseconds that's elapsed since the earlier Instant -fn delta_time(earlier: Instant) -> u32 { - Instant::now().duration_since(earlier).as_millis() as u32 -} - -// default update function (does nothing) -fn default_update() { -} - -// default display function (does nothing) -fn default_display() { } \ No newline at end of file