diff --git a/Cargo.toml b/Cargo.toml index a556918..973d649 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,4 +10,7 @@ include = ["/src", "LICENSE", "/examples"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[dependencies] \ No newline at end of file +[dependencies] + +[dev-dependencies] +winit = "0.25.0" \ No newline at end of file diff --git a/README.md b/README.md index b2229ba..477788f 100644 --- a/README.md +++ b/README.md @@ -18,21 +18,26 @@ fn main() { let mut x: f32 = 0.0; // create a closure containing your update logic - let update_logic = |state: &mut State| { + let mut update_logic = move |state: &mut State| { // access loop metadata via the State object x += state.get_timescale(); print!("x: {} | ", x); // print information about the current tick's timings - state.debug_tick(); + state.debug_time(); }; - + // create a closure containing your display logic - let display_logic = |state: &State| { + let display_logic = move |state: &State| { // }; // run the simulation with your user-defined update and display logic - sim.run(update_logic, display_logic); + // initialize the sim (cleans internal clocks, etc.) + sim.init(); + loop { + // "step" the sim forward + sim.step(&mut update_logic, &display_logic); + } } ``` diff --git a/examples/basics.rs b/examples/basics.rs index e3d8d08..2166293 100644 --- a/examples/basics.rs +++ b/examples/basics.rs @@ -8,20 +8,25 @@ fn main() { let mut x: f32 = 0.0; // create a closure containing your update logic - let update_logic = |state: &mut State| { + let mut update_logic = move |state: &mut State| { // access loop metadata via the State object x += state.get_timescale(); print!("x: {} | ", x); // print information about the current tick's timings - state.debug_tick(); + state.debug_time(); }; - + // create a closure containing your display logic - let display_logic = |state: &State| { + let display_logic = move |state: &State| { // }; // run the simulation with your user-defined update and display logic - sim.run(update_logic, display_logic); + // initialize the sim (cleans internal clocks, etc.) + sim.init(); + loop { + // "step" the sim forward + sim.step(&mut update_logic, &display_logic); + } } \ No newline at end of file diff --git a/examples/windowing.rs b/examples/windowing.rs new file mode 100644 index 0000000..36b98b9 --- /dev/null +++ b/examples/windowing.rs @@ -0,0 +1,46 @@ +use hypoloop::core::{State, Loop}; +use winit::{ + event::{ElementState, Event, KeyboardInput, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::{CursorIcon, WindowBuilder}, +}; + +fn main() { + // create sim with default configuration + let mut sim = Loop::new(); + + // test variable + let mut x: f32 = 0.0; + + // create a winit event loop + let event_loop = EventLoop::new(); + + // create a winit window + let window = WindowBuilder::new().build(&event_loop).unwrap(); + window.set_title("Windowing test with hypoloop"); + + // create a closure containing your update logic + let mut update_logic = move |state: &mut State| { + // access loop metadata via the State object + x += state.get_timescale(); + print!("x: {} | ", x); + + // print information about the current tick's timings + state.debug_time(); + }; + + // create a closure containing your display logic + let display_logic = move |state: &State| { + // redraw the winit window + window.request_redraw(); + }; + + // initialize the sim (cleans internal clocks, etc.) + sim.init(); + + // run the winit event loop with embedded hypoloop sim + event_loop.run(move |event, _, control_flow| { + // "step" the sim forward + sim.step(&mut update_logic, &display_logic); + }); +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 3051e46..0a360d1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,10 +7,11 @@ pub mod core { update_interval: u32, timescale: f32, simulate: bool, + clock_start: Instant, + last_tick: Instant, delta_time: u32, irl_time: Duration, - sim_time: Duration, - last_tick: Instant + sim_time: Duration } impl State { @@ -21,10 +22,11 @@ pub mod core { update_interval: 40, timescale: 1.0, simulate: true, + clock_start: Instant::now(), + last_tick: Instant::now(), delta_time: 0, irl_time: Duration::new(0,0), - sim_time: Duration::new(0,0), - last_tick: Instant::now() + sim_time: Duration::new(0,0) }; // Make sure that delta_time always starts the same as update_interval @@ -59,12 +61,12 @@ pub mod core { self.last_tick } - /// Pauses the simulation + /// Pauses the simulation from within update logic pub fn pause(&mut self) { self.simulate = false; } - /// Resumes the simulation + /// Resumes the simulation from within update logic pub fn resume(&mut self) { self.simulate = true; } @@ -74,12 +76,15 @@ pub mod core { self.timescale = timescale; } - /// Prints a string of information about the current tick - pub fn debug_tick(self) { + /// Prints a string of information about the current time (IRL time, Sim time, Delta time (tick), Delta time (step)) + /// IRL time: Real time (in ms) elapsed since the start of the loop + /// Sim time: Virtual time (in ms) elapsed since the start of the loop + /// Delta time (tick): Real time (in ms) elapsed between the last tick and the previous tick + /// Delta time (step): Real time (in ms with nanosecond accuracy) elapsed since the last update tick + pub fn debug_time(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); + println!("IRL time: {}ms | Sim time: {}ms | Delta time (tick): {}ms | Delta time (step): {}ms", self.irl_time.as_millis(), self.sim_time.as_millis(), self.delta_time, loop_delay_ms); } } @@ -99,15 +104,21 @@ pub mod core { } } - /// 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), mut display_callback: impl FnMut(&State)) { + /// Initializes or re-initializes the simulation + pub fn init(&mut self) { // Make sure the simulation will run self.state.simulate = true; - // start the clock to keep track of real time - let clock_start = Instant::now(); - - while self.state.simulate { + // reset the internal clocks + self.state.clock_start = Instant::now(); + self.state.irl_time = Duration::new(0,0); + self.state.sim_time = Duration::new(0,0); + } + + /// Executes the per-loop logic (can be triggered manually so that hypoloop can be tied into external event loops) + pub fn step(&mut self, mut update_callback: impl FnMut(&mut State), display_callback: impl Fn(&State)) { + // don't run if the simulation is paused + if 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 @@ -121,7 +132,7 @@ pub mod core { } 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); + self.state.irl_time = Instant::now().duration_since(self.state.clock_start); } // update @@ -144,34 +155,8 @@ pub mod core { } } - - // 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(); - } - - // 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() { - } } \ No newline at end of file