khora_data/ecs/system.rs
1// Copyright 2025 eraflo
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! `DataSystem` — invariant systems of the Data layer.
16//!
17//! A `DataSystem` is a pure function over `&mut World` that runs every tick
18//! at a declared [`TickPhase`]. They are the home of the Data layer's
19//! self-maintenance work — hierarchy fix-ups (e.g. `transform_propagation`),
20//! storage compaction, deferred cleanup — i.e. invariants that *must* hold
21//! before the CLAD descent runs.
22//!
23//! Systems are auto-discovered at link time via [`inventory`]. Adding a new
24//! invariant is a matter of one file plus an [`inventory::submit!`]: zero
25//! changes are required in the engine, scheduler, or any agent.
26//!
27//! # Example
28//!
29//! ```rust,ignore
30//! use khora_data::ecs::{DataSystemRegistration, TickPhase, World};
31//!
32//! pub fn my_system(world: &mut World) {
33//! // ...
34//! }
35//!
36//! inventory::submit! {
37//! DataSystemRegistration {
38//! name: "my_system",
39//! phase: TickPhase::PostSimulation,
40//! run: my_system,
41//! order_hint: 0,
42//! runs_after: &[],
43//! }
44//! }
45//! ```
46//!
47//! # Phases
48//!
49//! See [`TickPhase`] for the available phases and their intended use.
50
51use crate::ecs::World;
52use khora_core::lane::OutputDeck;
53use khora_core::Runtime;
54
55/// Tick phases at which [`DataSystem`]s are dispatched.
56///
57/// The order below reflects their position in the engine tick:
58/// `PreSimulation` runs first (before any agent simulates), `Maintenance`
59/// runs last (after all agent work is finished).
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
61pub enum TickPhase {
62 /// Runs before any simulation. Input-driven mutations, scene events,
63 /// anything that should be visible to agents at the start of their
64 /// frame.
65 PreSimulation,
66
67 /// Runs after simulation, before extraction. Hierarchy fix-ups
68 /// (`transform_propagation`), invariant restoration that the agents
69 /// might have broken.
70 PostSimulation,
71
72 /// Runs right before extraction. Last chance to mutate the world before
73 /// `Flow`s project it to lanes — material syncs, GPU resource sync, etc.
74 PreExtract,
75
76 /// Runs at the end of the tick. Storage compaction, deferred cleanup,
77 /// best-effort idempotent maintenance.
78 Maintenance,
79}
80
81/// Registration entry for a [`DataSystem`].
82///
83/// One entry per system, submitted via [`inventory::submit!`] at link time.
84/// The dispatcher in `khora-control` discovers all entries, groups them by
85/// [`TickPhase`], topologically sorts within a phase using `runs_after`
86/// (with `order_hint` as tie-breaker), and invokes them sequentially.
87pub struct DataSystemRegistration {
88 /// Stable identifier — used for `runs_after` references and telemetry.
89 pub name: &'static str,
90 /// Phase this system belongs to.
91 pub phase: TickPhase,
92 /// The function to invoke on `(&mut World, &Runtime, &mut OutputDeck)`.
93 ///
94 /// - The `Runtime` bundle lets a system fetch typed services / backends /
95 /// resources it needs (`EcsMaintenance`, `GraphicsDevice`, …).
96 /// - The `OutputDeck` is the per-tick typed deck the lanes wrote into
97 /// during the CLAD descent. `Maintenance`-phase systems use
98 /// `deck.take::<MyWriteback>()` to drain typed writeback slots and
99 /// apply them to ECS components. Pre-scheduler phases receive an
100 /// empty (transient) deck — systems that don't need it just ignore
101 /// the third arg.
102 pub run: fn(&mut World, &Runtime, &mut OutputDeck),
103 /// Tie-breaker used to order systems within a phase when no explicit
104 /// `runs_after` ordering applies. Lower runs first. Default `0`.
105 pub order_hint: i32,
106 /// Names of systems within the same phase that must run before this
107 /// one. Used to topologically sort the phase. Empty by default.
108 pub runs_after: &'static [&'static str],
109}
110
111inventory::collect!(DataSystemRegistration);