Skip to main content

gizmo_core/
time.rs

1/// Motor genelinde zaman yönetimi.
2///
3/// # Kullanım
4/// ```rust,ignore
5/// let mut time = Time::default();
6///
7/// // Her frame başında:
8/// time.update(raw_dt);
9///
10/// // Okuma:
11/// let dt = time.dt();           // Clamped delta (max 50ms)
12/// let elapsed = time.elapsed(); // Toplam geçen süre
13/// let frame = time.frame();     // Frame sayacı
14/// let raw = time.raw_dt();      // Ham, clamp edilmemiş dt
15///
16/// // Zaman ölçeği:
17/// time.set_time_scale(0.5); // Slow motion
18/// time.set_time_scale(0.0); // Pause
19/// ```
20#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
21pub struct Time {
22    /// Clamped delta time (saniye). `time_scale` uygulanmış.
23    dt: f32,
24    /// Ham (raw) delta time — clamp ve scale uygulanmamış.
25    raw_dt: f32,
26    /// Toplam geçen süre (saniye, f64 hassasiyetinde).
27    elapsed: f64,
28    /// Frame sayacı.
29    frame_count: u64,
30    /// Zaman ölçeği. 1.0 = normal, 0.5 = slow motion, 0.0 = pause.
31    time_scale: f32,
32    /// Maksimum dt cap (saniye). Varsayılan: 1/20 = 50ms.
33    max_dt: f32,
34}
35
36/// Varsayılan max dt: 50ms (20 FPS minimum).
37const DEFAULT_MAX_DT: f32 = 1.0 / 20.0;
38
39impl Time {
40    pub fn new() -> Self {
41        Self {
42            dt: 0.0,
43            raw_dt: 0.0,
44            elapsed: 0.0,
45            frame_count: 0,
46            time_scale: 1.0,
47            max_dt: DEFAULT_MAX_DT,
48        }
49    }
50
51    /// Ham dt'yi alır, clamp + scale uygular ve tüm zamansal değerleri günceller.
52    /// Her frame başında bir kez çağrılmalıdır.
53    pub fn update(&mut self, raw_dt: f32) {
54        self.raw_dt = raw_dt.max(0.0); // Negatif dt'yi engelle
55        self.dt = (self.raw_dt * self.time_scale).min(self.max_dt);
56        self.elapsed += self.dt as f64;
57        self.frame_count += 1;
58    }
59
60    // ──── Getter'lar ────
61
62    /// Clamped ve scaled delta time (saniye).
63    /// Fizik, hareket, animasyon gibi sistemler bunu kullanmalıdır.
64    #[inline]
65    pub fn dt(&self) -> f32 {
66        self.dt
67    }
68
69    /// Ham (raw) delta time — clamp ve scale uygulanmamış.
70    /// Gerçek wall-clock zamanına ihtiyaç duyan sistemler için (ör: FPS sayacı).
71    #[inline]
72    pub fn raw_dt(&self) -> f32 {
73        self.raw_dt
74    }
75
76    /// Toplam geçen süre (saniye, f64 hassasiyetinde).
77    /// Uzun oturumlarda bile hassasiyetini korur.
78    #[inline]
79    pub fn elapsed(&self) -> f64 {
80        self.elapsed
81    }
82
83    /// Toplam frame sayısı.
84    #[inline]
85    pub fn frame(&self) -> u64 {
86        self.frame_count
87    }
88
89    /// Mevcut zaman ölçeği.
90    #[inline]
91    pub fn time_scale(&self) -> f32 {
92        self.time_scale
93    }
94
95    /// Mevcut FPS (1/raw_dt). raw_dt = 0 ise 0.0 döner.
96    #[inline]
97    pub fn fps(&self) -> f32 {
98        if self.raw_dt > 0.0 {
99            1.0 / self.raw_dt
100        } else {
101            0.0
102        }
103    }
104
105    // ──── Setter'lar ────
106
107    /// Zaman ölçeğini ayarlar. 0.0 = durdur, 0.5 = ağır çekim, 1.0 = normal, 2.0 = hızlı.
108    pub fn set_time_scale(&mut self, scale: f32) {
109        self.time_scale = scale.max(0.0);
110    }
111
112    /// Maksimum dt cap'ini ayarlar (saniye).
113    pub fn set_max_dt(&mut self, max: f32) {
114        self.max_dt = max.max(0.001); // En az ~1ms
115    }
116}
117
118impl Default for Time {
119    fn default() -> Self {
120        Self::new()
121    }
122}
123
124// ═══════════════════════════════════════════════════════════════════════
125//  PhysicsTime — Sabit zaman adımlı fizik zamanlayıcı
126//
127//  Fizik motoru sabit dt'de çalışır (varsayılan 1/60s = 16.67ms).
128//  Render frame'leri değişken hızda çalışırken, fizik her zaman aynı
129//  dt ile güncellenir → determinizm + kararlılık.
130//
131//  Kullanım:
132//    Frame başında `accumulate(render_dt)` çağrılır.
133//    `should_step()` true döndüğü sürece fizik adımları çalıştırılır.
134//    `consume_step()` ile accumulator azaltılır.
135//    `alpha()` ile render interpolasyonu yapılır.
136// ═══════════════════════════════════════════════════════════════════════
137#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
138pub struct PhysicsTime {
139    /// Sabit fizik zaman adımı (saniye). Varsayılan: 1/60.
140    fixed_dt: f32,
141    /// Birikmiş süre — henüz fizik adımı olarak harcanmamış zaman.
142    accumulator: f32,
143    /// Maksimum birikim limiti (spiral of death koruması).
144    max_accumulator: f32,
145    /// Toplam fizik adım sayısı.
146    step_count: u64,
147    /// Toplam fizik süresi (f64 hassasiyetinde).
148    physics_elapsed: f64,
149    /// İnterpolasyon katsayısı: 0.0..1.0 arası.
150    /// Render sırasında `lerp(prev_state, curr_state, alpha)` için kullanılır.
151    alpha: f32,
152}
153
154impl PhysicsTime {
155    /// Yeni PhysicsTime oluşturur. `hz` = fizik güncellenme hızı (örn: 60, 120, 240).
156    pub fn new(hz: u32) -> Self {
157        let fixed_dt = 1.0 / hz as f32;
158        Self {
159            fixed_dt,
160            accumulator: 0.0,
161            max_accumulator: fixed_dt * 8.0, // En fazla 8 fizik adımı birikebilir
162            step_count: 0,
163            physics_elapsed: 0.0,
164            alpha: 0.0,
165        }
166    }
167
168    /// Render frame dt'sini biriktiriciye ekler.
169    /// Her frame başında bir kez çağrılır.
170    pub fn accumulate(&mut self, render_dt: f32) {
171        self.accumulator += render_dt;
172        // Spiral of death koruması
173        if self.accumulator > self.max_accumulator {
174            self.accumulator = self.max_accumulator;
175        }
176    }
177
178    /// Bir fizik adımı için yeterli süre birikmiş mi?
179    #[inline]
180    pub fn should_step(&self) -> bool {
181        self.accumulator >= self.fixed_dt
182    }
183
184    /// Bir fizik adımını "tüketir" — accumulator'dan fixed_dt düşer.
185    /// Her fizik step'inden sonra çağrılır.
186    pub fn consume_step(&mut self) {
187        self.accumulator -= self.fixed_dt;
188        self.step_count += 1;
189        self.physics_elapsed += self.fixed_dt as f64;
190    }
191
192    /// İnterpolasyon alpha'sını hesaplar.
193    /// Tüm fizik adımları bittikten sonra, render'dan önce çağrılır.
194    pub fn compute_alpha(&mut self) {
195        self.alpha = self.accumulator / self.fixed_dt;
196    }
197
198    // ──── Getter'lar ────
199
200    /// Sabit fizik dt'si (saniye).
201    #[inline]
202    pub fn fixed_dt(&self) -> f32 {
203        self.fixed_dt
204    }
205
206    /// İnterpolasyon katsayısı (0.0 .. 1.0).
207    /// `render_pos = lerp(prev_physics_pos, curr_physics_pos, alpha)`
208    #[inline]
209    pub fn alpha(&self) -> f32 {
210        self.alpha
211    }
212
213    /// Toplam fizik adım sayısı.
214    #[inline]
215    pub fn step_count(&self) -> u64 {
216        self.step_count
217    }
218
219    /// Toplam fizik süresi (f64 hassasiyetinde).
220    #[inline]
221    pub fn physics_elapsed(&self) -> f64 {
222        self.physics_elapsed
223    }
224
225    /// Birikmiş süre (debug amaçlı).
226    #[inline]
227    pub fn accumulator(&self) -> f32 {
228        self.accumulator
229    }
230
231    // ──── Setter'lar ────
232
233    /// Fizik hızını değiştirir (Hz). Dikkat: birikmiş süre sıfırlanmaz.
234    pub fn set_hz(&mut self, hz: u32) {
235        self.fixed_dt = 1.0 / hz.max(1) as f32;
236        self.max_accumulator = self.fixed_dt * 8.0;
237    }
238}
239
240impl Default for PhysicsTime {
241    fn default() -> Self {
242        Self::new(60) // 60 Hz fizik
243    }
244}
245
246#[cfg(test)]
247mod tests {
248    use super::*;
249
250    #[test]
251    fn test_basic_update() {
252        let mut time = Time::new();
253        time.update(0.016);
254
255        assert!((time.dt() - 0.016).abs() < 0.0001);
256        assert!((time.raw_dt() - 0.016).abs() < 0.0001);
257        assert!((time.elapsed() - 0.016).abs() < 0.001);
258        assert_eq!(time.frame(), 1);
259    }
260
261    #[test]
262    fn test_dt_clamp() {
263        let mut time = Time::new();
264        time.update(1.0); // 1 saniye spike
265
266        assert!(time.dt() <= DEFAULT_MAX_DT + 0.0001);
267        assert!((time.raw_dt() - 1.0).abs() < 0.0001);
268    }
269
270    #[test]
271    fn test_negative_dt_clamped_to_zero() {
272        let mut time = Time::new();
273        time.update(-0.5);
274
275        assert_eq!(time.dt(), 0.0);
276        assert_eq!(time.raw_dt(), 0.0);
277    }
278
279    #[test]
280    fn test_time_scale() {
281        let mut time = Time::new();
282        time.set_time_scale(0.5);
283        time.update(0.016);
284
285        assert!((time.dt() - 0.008).abs() < 0.0001); // 0.016 * 0.5
286        assert!((time.raw_dt() - 0.016).abs() < 0.0001);
287    }
288
289    #[test]
290    fn test_time_scale_zero_is_pause() {
291        let mut time = Time::new();
292        time.set_time_scale(0.0);
293        time.update(0.016);
294
295        assert_eq!(time.dt(), 0.0);
296        assert_eq!(time.elapsed(), 0.0);
297        assert_eq!(time.frame(), 1); // Frame hâlâ sayılır
298    }
299
300    #[test]
301    fn test_elapsed_accumulates() {
302        let mut time = Time::new();
303        for _ in 0..100 {
304            time.update(0.01);
305        }
306
307        assert!((time.elapsed() - 1.0).abs() < 0.01);
308        assert_eq!(time.frame(), 100);
309    }
310
311    #[test]
312    fn test_fps() {
313        let mut time = Time::new();
314        time.update(1.0 / 60.0);
315        assert!((time.fps() - 60.0).abs() < 1.0);
316
317        time.update(0.0);
318        assert_eq!(time.fps(), 0.0); // Division by zero koruması
319    }
320
321    #[test]
322    fn test_custom_max_dt() {
323        let mut time = Time::new();
324        time.set_max_dt(1.0 / 10.0); // 100ms
325        time.update(0.5);
326
327        assert!((time.dt() - 0.1).abs() < 0.0001); // 100ms cap
328    }
329
330    #[test]
331    fn test_serde_derive() {
332        // serde derive doğru çalışıyor — serialize/deserialize uygulanmış
333        let mut time = Time::new();
334        time.update(0.016);
335        time.update(0.016);
336
337        // Clone ile roundtrip kontrolü (serde_json bağımlılık gerektirmeden)
338        let cloned = time;
339        assert_eq!(cloned.frame(), time.frame());
340        assert!((cloned.elapsed() - time.elapsed()).abs() < 0.001);
341    }
342
343    // ─── PhysicsTime Testleri ───
344
345    #[test]
346    fn test_physics_time_basic_step() {
347        let mut pt = PhysicsTime::new(60);
348        assert!(!pt.should_step()); // Henüz birikim yok
349
350        pt.accumulate(1.0 / 60.0); // Tam bir fizik adımı
351        assert!(pt.should_step());
352
353        pt.consume_step();
354        assert!(!pt.should_step());
355        assert_eq!(pt.step_count(), 1);
356    }
357
358    #[test]
359    fn test_physics_time_multiple_steps() {
360        let mut pt = PhysicsTime::new(60);
361        let fixed_dt = pt.fixed_dt();
362        // 3.5 adıma yetecek birikim (FP hassasiyeti için margin)
363        pt.accumulate(fixed_dt * 3.5);
364
365        let mut steps = 0;
366        while pt.should_step() {
367            pt.consume_step();
368            steps += 1;
369        }
370        assert_eq!(steps, 3);
371        assert_eq!(pt.step_count(), 3);
372    }
373
374    #[test]
375    fn test_physics_time_spiral_of_death() {
376        let mut pt = PhysicsTime::new(60);
377        // 1 saniyelik spike — max 8 adım birikebilir
378        pt.accumulate(1.0);
379
380        let mut steps = 0;
381        while pt.should_step() {
382            pt.consume_step();
383            steps += 1;
384        }
385        assert!(
386            steps <= 8,
387            "Spiral koruması: max 8 adım, bulundu: {}",
388            steps
389        );
390    }
391
392    #[test]
393    fn test_physics_time_alpha() {
394        let mut pt = PhysicsTime::new(60);
395        let fixed_dt = 1.0 / 60.0;
396
397        // 1.5 fizik adımı birikim
398        pt.accumulate(fixed_dt * 1.5);
399        pt.consume_step(); // 1 adım tüket
400        pt.compute_alpha();
401
402        // Kalan 0.5 adım → alpha ≈ 0.5
403        assert!(
404            (pt.alpha() - 0.5).abs() < 0.01,
405            "Alpha ≈ 0.5: {}",
406            pt.alpha()
407        );
408    }
409
410    #[test]
411    fn test_physics_time_elapsed() {
412        let mut pt = PhysicsTime::new(60);
413        for _ in 0..60 {
414            pt.accumulate(1.0 / 60.0);
415            while pt.should_step() {
416                pt.consume_step();
417            }
418        }
419        // 60 adım × 1/60 = 1.0s
420        assert!((pt.physics_elapsed() - 1.0).abs() < 0.001);
421    }
422
423    #[test]
424    fn test_physics_time_set_hz() {
425        let mut pt = PhysicsTime::new(60);
426        pt.set_hz(120);
427        assert!((pt.fixed_dt() - 1.0 / 120.0).abs() < 1e-6);
428    }
429}