tick_system.rs
1 use std::{ 2 sync::Arc, 3 time::{Duration, Instant}, 4 }; 5 6 use crossbeam::atomic::AtomicCell; 7 use derive_builder::Builder; 8 use let_engine_core::{CustomError, backend::Backends}; 9 use parking_lot::Mutex; 10 11 use crate::{Game, GameWrapper}; 12 13 pub(crate) struct TickSystem { 14 settings: Mutex<TickSettings>, 15 report: AtomicCell<Tick>, 16 tick_pause_lock: (parking_lot::Mutex<bool>, parking_lot::Condvar), 17 } 18 19 impl TickSystem { 20 pub(crate) fn new(settings: TickSettings) -> Self { 21 Self { 22 settings: Mutex::new(settings), 23 report: AtomicCell::new(Tick::default()), 24 tick_pause_lock: (parking_lot::Mutex::new(false), parking_lot::Condvar::new()), 25 } 26 } 27 } 28 29 /// Runs the games `tick` function after every iteration. 30 pub(super) fn run<G: Game<B, E>, E: CustomError, B: Backends>(game: Arc<GameWrapper<G, E, B>>) { 31 let mut index: usize = 0; 32 33 loop { 34 #[allow(unused_mut)] 35 let mut settings: TickSettings = { 36 let interface = &game.backends.tick_system; 37 38 // wait if paused 39 interface 40 .tick_pause_lock 41 .1 42 .wait_while(&mut interface.tick_pause_lock.0.lock(), |x| *x); 43 interface.settings.lock().clone() 44 }; 45 46 // capture tick start time. 47 let start_time = Instant::now(); 48 49 // Run the logic 50 game.tick(); 51 52 // update the physics in case they are active in the tick settings. 53 #[cfg(feature = "physics")] 54 if settings.update_physics { 55 game.scene 56 .lock() 57 .physics_iteration() 58 .expect("Expected physics iteration to succeed") // TODO: Handle problems 59 } 60 61 // record the elapsed time. 62 let elapsed_time = start_time.elapsed(); 63 64 // Lock the thread in case the time scale is 0. 65 if game.time.scale() == 0.0 { 66 let mut guard = game.time.zero_cvar.0.lock(); 67 game.time.zero_cvar.1.wait(&mut guard); 68 } 69 70 let tick_wait = if settings.time_scale_influence { 71 // Multiply the waiting duration with the inverse time scale. 72 settings.tick_wait.mul_f64(1.0 / game.time.scale()) 73 } else { 74 settings.tick_wait 75 }; 76 77 // calculate waiting time 78 // ((1.0 / time_scale) * tick_wait) - elapsed_time 79 let waiting_time = if let TimeStep::Variable = settings.timestep_mode { 80 // Subtract the tick logic execution time from the waiting time to make the waiting time between ticks more consistent. 81 tick_wait.saturating_sub(elapsed_time) 82 } else { 83 tick_wait 84 }; 85 86 // Spin sleep so windows users with their lower quality sleep functions get the same sleep duration 87 spin_sleep::sleep(waiting_time); 88 89 // report tick in case a reporter is active 90 game.backends.tick_system.report.store(Tick { 91 duration: elapsed_time, 92 waiting_time, 93 index, 94 }); 95 96 index += 1; 97 98 if game.exit.get().is_some() { 99 break; 100 } 101 } 102 } 103 104 /// The settings for the tick system of the game engine. 105 #[derive(Clone, Debug, Builder)] 106 pub struct TickSettings { 107 /// The target duration to wait after every tick. 108 /// 109 /// ## Default 110 /// 111 /// - 1 / 62 seconds 112 /// 113 /// 62 ticks per second. 114 #[builder(setter(into), default = "Duration::from_secs_f64(1.0/62.0)")] 115 pub tick_wait: Duration, 116 /// The waiting behaviour of this tick system. 117 /// 118 /// ## Default 119 /// 120 /// `TimeStep::Variable` 121 /// 122 /// Prevents the game from slowing down in case ticks become more time expensive. 123 #[builder(default)] 124 pub timestep_mode: TimeStep, 125 /// If true this tick system will also iterate all the physics systems in the scene and update them. 126 /// 127 /// ## Default 128 /// 129 /// `true` 130 #[builder(default = "true")] 131 #[cfg(feature = "physics")] 132 pub update_physics: bool, 133 /// If this is true the tick system will be paused. 134 /// 135 /// ## Default 136 /// 137 /// `false` 138 #[builder(default)] 139 pub paused: bool, 140 /// If this is true the tick systems tick rate will be influenced by the time scale. 141 /// 142 /// ## Default 143 /// 144 /// `true` 145 #[builder(default = "true")] 146 pub time_scale_influence: bool, 147 } 148 149 impl Default for TickSettings { 150 fn default() -> Self { 151 Self { 152 tick_wait: Duration::from_secs_f64(1.0 / 62.0), 153 #[cfg(feature = "physics")] 154 update_physics: true, 155 timestep_mode: TimeStep::default(), 156 paused: false, 157 time_scale_influence: true, 158 } 159 } 160 } 161 impl TickSettings { 162 pub fn into_builder(self) -> TickSettingsBuilder { 163 self.into() 164 } 165 } 166 impl From<TickSettings> for TickSettingsBuilder { 167 fn from(value: TickSettings) -> Self { 168 Self { 169 tick_wait: Some(value.tick_wait), 170 timestep_mode: Some(value.timestep_mode), 171 #[cfg(feature = "physics")] 172 update_physics: Some(value.update_physics), 173 paused: Some(value.paused), 174 time_scale_influence: Some(value.time_scale_influence), 175 } 176 } 177 } 178 179 /// A tick report. 180 #[derive(Clone, Default, Copy, PartialEq, Eq, PartialOrd, Ord)] 181 pub struct Tick { 182 /// Time it took to execute this tick. 183 pub duration: Duration, 184 /// Time the tick waited before running the next one. 185 pub waiting_time: Duration, 186 /// The index of this tick. 187 pub index: usize, 188 } 189 190 impl Tick { 191 pub fn duration(&self) -> &Duration { 192 &self.duration 193 } 194 pub fn waiting_time(&self) -> &Duration { 195 &self.waiting_time 196 } 197 pub fn index(&self) -> usize { 198 self.index 199 } 200 /// Returns true if the tick execution time takes longer than the expected waiting time. 201 /// 202 /// Because if the tick execution takes longer than the target waiting time the rate decreases making the logic behind the tick system slower. 203 pub fn has_slowdown(&self) -> bool { 204 self.waiting_time.is_zero() 205 } 206 } 207 208 impl std::fmt::Debug for Tick { 209 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 210 f.debug_struct("Tick") 211 .field("duration", &self.duration) 212 .field("waiting time", &self.waiting_time) 213 .field("index", &self.index) 214 .field("has slowdown", &self.has_slowdown()) 215 .finish() 216 } 217 } 218 219 /// The waiting behaviour of the tick system. 220 /// 221 /// Set to variable by default. 222 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default)] 223 pub enum TimeStep { 224 /// Wait a fixed time after every tick, not caring about the duration the tick actually lasted for. 225 Fixed, 226 227 /// Wait a variable time using the tick_wait field as a target duration. 228 /// 229 /// That means the tick system waits less the longer the tick took to execute. 230 /// This for example prevents the physics system from slowing down in case the iterations get more expensive. 231 #[default] 232 Variable, 233 }