lumen_core/tick.rs
1//! Main-world tick stages and the [`Tick`] resource.
2//!
3//! - Each stage is a `bevy_ecs` [`SystemSet`].
4//! - Ordering is enforced by `.chain()` in [`crate::app::App::new`].
5//! - The render schedule runs after the main schedule and the extract step; see [`crate::render_world`].
6
7use bevy_ecs::prelude::*;
8use std::time::{Duration, Instant};
9
10/// The five ordered main-world stages of a Lumen tick.
11#[derive(SystemSet, Clone, Copy, Debug, Hash, PartialEq, Eq)]
12pub enum TickStage {
13 /// Ingests OS events (keyboard, mouse, IME, window). Window backend writes here.
14 Input,
15 /// Drains the bounded [`crate::command::CommandQueue`] and applies deferred mutations.
16 CommandDrain,
17 /// Runs application systems: state mutation, animations, scripts.
18 Systems,
19 /// Runs the layout engine: dirty flush, taffy round-trip, absolute-coord write-back.
20 LayoutSync,
21 /// Computes the accessibility-tree diff and pushes it to the OS.
22 A11ySync,
23}
24
25/// Per-tick frame clock resource.
26///
27/// - [`Self::now`] is captured at the start of each [`crate::app::App::tick`] before the [`TickStage::Input`] systems run.
28/// - [`Self::dt`] is `now - previous_now` (zero on the first tick).
29/// - [`Self::frame`] is a monotonic counter incremented once per tick (starts at 0; reaches 1 on the first tick).
30///
31/// Wave 1 migrates the animation primitives off `std::time::Instant::now()` to read this resource so headless tests can
32/// drive deterministic frame clocks; foundation only installs and updates the resource.
33#[derive(Resource, Clone, Copy, Debug)]
34pub struct Tick {
35 /// Wall-clock instant captured at the start of the current tick.
36 pub now: Instant,
37 /// Elapsed time since the previous tick's [`Self::now`]. Zero on the first tick.
38 pub dt: Duration,
39 /// Monotonic tick counter; 0 before the first tick, 1 after, and so on.
40 pub frame: u64,
41}
42
43impl Default for Tick {
44 fn default() -> Self {
45 Self {
46 now: Instant::now(),
47 dt: Duration::ZERO,
48 frame: 0,
49 }
50 }
51}
52
53impl Tick {
54 /// Advances the clock by capturing a fresh `Instant::now()` and bumping [`Self::frame`].
55 /// Called by [`crate::app::App::tick`] at the top of each tick, before the main schedule runs.
56 pub fn advance(&mut self) {
57 let now = Instant::now();
58 self.dt = now.saturating_duration_since(self.now);
59 self.now = now;
60 self.frame = self.frame.wrapping_add(1);
61 }
62}