Skip to main content

goud_engine/ecs/schedule/
system_set.rs

1//! System set, configuration, chained systems, and label-based ordering.
2
3use std::collections::HashSet;
4use std::fmt;
5
6use crate::ecs::system::SystemId;
7
8use super::system_label::{SystemLabel, SystemLabelId};
9use super::system_ordering::SystemOrdering;
10
11/// A named set of systems for grouping and batch configuration.
12///
13/// System sets allow applying ordering constraints and run conditions
14/// to multiple systems at once.
15#[derive(Debug, Clone)]
16pub struct SystemSet {
17    /// Human-readable name for the set.
18    name: String,
19    /// Systems in this set.
20    systems: Vec<SystemId>,
21    /// Unique set for fast contains checks.
22    system_set: HashSet<SystemId>,
23}
24
25impl SystemSet {
26    /// Creates a new empty system set.
27    #[inline]
28    pub fn new(name: impl Into<String>) -> Self {
29        Self {
30            name: name.into(),
31            systems: Vec::new(),
32            system_set: HashSet::new(),
33        }
34    }
35
36    /// Creates a new system set with pre-allocated capacity.
37    #[inline]
38    pub fn with_capacity(name: impl Into<String>, capacity: usize) -> Self {
39        Self {
40            name: name.into(),
41            systems: Vec::with_capacity(capacity),
42            system_set: HashSet::with_capacity(capacity),
43        }
44    }
45
46    /// Returns the name of this set.
47    #[inline]
48    pub fn name(&self) -> &str {
49        &self.name
50    }
51
52    /// Adds a system to this set.
53    ///
54    /// Returns `true` if the system was added, `false` if already present.
55    pub fn add(&mut self, system: SystemId) -> bool {
56        if self.system_set.insert(system) {
57            self.systems.push(system);
58            true
59        } else {
60            false
61        }
62    }
63
64    /// Removes a system from this set.
65    ///
66    /// Returns `true` if the system was removed, `false` if not present.
67    pub fn remove(&mut self, system: SystemId) -> bool {
68        if self.system_set.remove(&system) {
69            self.systems.retain(|&id| id != system);
70            true
71        } else {
72            false
73        }
74    }
75
76    /// Returns `true` if this set contains the given system.
77    #[inline]
78    pub fn contains(&self, system: SystemId) -> bool {
79        self.system_set.contains(&system)
80    }
81
82    /// Returns the number of systems in this set.
83    #[inline]
84    pub fn len(&self) -> usize {
85        self.systems.len()
86    }
87
88    /// Returns `true` if this set is empty.
89    #[inline]
90    pub fn is_empty(&self) -> bool {
91        self.systems.is_empty()
92    }
93
94    /// Returns an iterator over systems in this set.
95    #[inline]
96    pub fn iter(&self) -> impl Iterator<Item = SystemId> + '_ {
97        self.systems.iter().copied()
98    }
99
100    /// Clears all systems from this set.
101    pub fn clear(&mut self) {
102        self.systems.clear();
103        self.system_set.clear();
104    }
105}
106
107impl Default for SystemSet {
108    fn default() -> Self {
109        Self::new("DefaultSet")
110    }
111}
112
113// ============================================================================
114// SystemSetConfig - Configuration for System Sets
115// ============================================================================
116
117/// Configuration for a system set's execution behavior.
118///
119/// This allows setting ordering constraints and run conditions
120/// that apply to all systems in the set.
121#[derive(Debug, Clone)]
122pub struct SystemSetConfig {
123    /// Labels that this set should run before.
124    pub before_labels: Vec<SystemLabelId>,
125    /// Labels that this set should run after.
126    pub after_labels: Vec<SystemLabelId>,
127    /// Whether the set is enabled.
128    pub enabled: bool,
129}
130
131impl Default for SystemSetConfig {
132    fn default() -> Self {
133        Self::new()
134    }
135}
136
137impl SystemSetConfig {
138    /// Creates a new default configuration.
139    pub fn new() -> Self {
140        Self {
141            before_labels: Vec::new(),
142            after_labels: Vec::new(),
143            enabled: true,
144        }
145    }
146
147    /// Adds a "run before" constraint.
148    pub fn before<L: SystemLabel + Clone>(mut self, label: L) -> Self {
149        self.before_labels.push(SystemLabelId::of(label));
150        self
151    }
152
153    /// Adds a "run after" constraint.
154    pub fn after<L: SystemLabel + Clone>(mut self, label: L) -> Self {
155        self.after_labels.push(SystemLabelId::of(label));
156        self
157    }
158
159    /// Sets whether the set is enabled.
160    pub fn enabled(mut self, enabled: bool) -> Self {
161        self.enabled = enabled;
162        self
163    }
164}
165
166// ============================================================================
167// ChainedSystems - Strict Sequential Ordering
168// ============================================================================
169
170/// A chain of systems that must run in strict sequential order.
171///
172/// Chained systems are guaranteed to run one after another with no
173/// interleaving. This is useful when systems have implicit dependencies
174/// that cannot be expressed through component access patterns.
175#[derive(Debug, Clone)]
176pub struct ChainedSystems {
177    /// Name of the chain for debugging.
178    name: String,
179    /// Systems in chain order.
180    systems: Vec<SystemId>,
181}
182
183impl ChainedSystems {
184    /// Creates a new empty chain.
185    #[inline]
186    pub fn new(name: impl Into<String>) -> Self {
187        Self {
188            name: name.into(),
189            systems: Vec::new(),
190        }
191    }
192
193    /// Creates a chain with pre-allocated capacity.
194    #[inline]
195    pub fn with_capacity(name: impl Into<String>, capacity: usize) -> Self {
196        Self {
197            name: name.into(),
198            systems: Vec::with_capacity(capacity),
199        }
200    }
201
202    /// Returns the name of this chain.
203    #[inline]
204    pub fn name(&self) -> &str {
205        &self.name
206    }
207
208    /// Adds a system to the end of the chain.
209    pub fn add(&mut self, system: SystemId) {
210        self.systems.push(system);
211    }
212
213    /// Adds a system that must run immediately after another.
214    ///
215    /// If `after` is not in the chain, the system is added at the end.
216    pub fn add_after(&mut self, system: SystemId, after: SystemId) {
217        if let Some(pos) = self.systems.iter().position(|&id| id == after) {
218            self.systems.insert(pos + 1, system);
219        } else {
220            self.systems.push(system);
221        }
222    }
223
224    /// Returns the number of systems in the chain.
225    #[inline]
226    pub fn len(&self) -> usize {
227        self.systems.len()
228    }
229
230    /// Returns `true` if the chain is empty.
231    #[inline]
232    pub fn is_empty(&self) -> bool {
233        self.systems.is_empty()
234    }
235
236    /// Returns an iterator over systems in chain order.
237    #[inline]
238    pub fn iter(&self) -> impl Iterator<Item = SystemId> + '_ {
239        self.systems.iter().copied()
240    }
241
242    /// Converts this chain to a vector of ordering constraints.
243    ///
244    /// Each consecutive pair becomes a "before" constraint.
245    pub fn to_orderings(&self) -> Vec<SystemOrdering> {
246        self.systems
247            .windows(2)
248            .map(|pair| SystemOrdering::before(pair[0], pair[1]))
249            .collect()
250    }
251
252    /// Clears the chain.
253    pub fn clear(&mut self) {
254        self.systems.clear();
255    }
256}
257
258impl Default for ChainedSystems {
259    fn default() -> Self {
260        Self::new("Chain")
261    }
262}
263
264/// Creates ordering constraints from a sequence of system IDs.
265///
266/// Each consecutive pair becomes a "before" constraint.
267pub fn chain<I>(systems: I) -> Vec<SystemOrdering>
268where
269    I: IntoIterator<Item = SystemId>,
270{
271    let systems: Vec<_> = systems.into_iter().collect();
272    systems
273        .windows(2)
274        .map(|pair| SystemOrdering::before(pair[0], pair[1]))
275        .collect()
276}
277
278// ============================================================================
279// LabeledOrderingConstraint - Label-based Ordering
280// ============================================================================
281
282/// An ordering constraint that uses labels instead of system IDs.
283///
284/// This allows defining ordering relationships before systems are
285/// registered, using labels as placeholders.
286#[derive(Debug, Clone)]
287pub enum LabeledOrderingConstraint {
288    /// Run before systems with this label.
289    BeforeLabel(SystemLabelId),
290    /// Run after systems with this label.
291    AfterLabel(SystemLabelId),
292    /// Run before a specific system.
293    BeforeSystem(SystemId),
294    /// Run after a specific system.
295    AfterSystem(SystemId),
296}
297
298impl LabeledOrderingConstraint {
299    /// Creates a "before label" constraint.
300    pub fn before_label<L: SystemLabel + Clone>(label: L) -> Self {
301        LabeledOrderingConstraint::BeforeLabel(SystemLabelId::of(label))
302    }
303
304    /// Creates an "after label" constraint.
305    pub fn after_label<L: SystemLabel + Clone>(label: L) -> Self {
306        LabeledOrderingConstraint::AfterLabel(SystemLabelId::of(label))
307    }
308
309    /// Creates a "before system" constraint.
310    #[inline]
311    pub fn before_system(id: SystemId) -> Self {
312        LabeledOrderingConstraint::BeforeSystem(id)
313    }
314
315    /// Creates an "after system" constraint.
316    #[inline]
317    pub fn after_system(id: SystemId) -> Self {
318        LabeledOrderingConstraint::AfterSystem(id)
319    }
320
321    /// Returns `true` if this is a label-based constraint.
322    #[inline]
323    pub fn is_label_based(&self) -> bool {
324        matches!(
325            self,
326            LabeledOrderingConstraint::BeforeLabel(_) | LabeledOrderingConstraint::AfterLabel(_)
327        )
328    }
329
330    /// Returns `true` if this is a system-based constraint.
331    #[inline]
332    pub fn is_system_based(&self) -> bool {
333        matches!(
334            self,
335            LabeledOrderingConstraint::BeforeSystem(_) | LabeledOrderingConstraint::AfterSystem(_)
336        )
337    }
338}
339
340impl fmt::Display for LabeledOrderingConstraint {
341    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
342        match self {
343            LabeledOrderingConstraint::BeforeLabel(label) => {
344                write!(f, "before label '{}'", label.name())
345            }
346            LabeledOrderingConstraint::AfterLabel(label) => {
347                write!(f, "after label '{}'", label.name())
348            }
349            LabeledOrderingConstraint::BeforeSystem(id) => {
350                write!(f, "before system {}", id.raw())
351            }
352            LabeledOrderingConstraint::AfterSystem(id) => {
353                write!(f, "after system {}", id.raw())
354            }
355        }
356    }
357}
358
359#[cfg(test)]
360#[path = "tests/system_set_tests.rs"]
361mod tests;