1use std::any::{Any, TypeId};
8use std::collections::HashSet;
9use std::time::Duration;
10
11pub use crate::budget::TickBudget;
12pub use crate::components::{EntityId, Phase};
13
14pub trait SystemContext: crate::world::handle::WorldHandle {
26 fn spawn(&mut self) -> EntityId;
28
29 fn despawn(&mut self, entity: EntityId);
31
32 fn set_component(
34 &mut self,
35 entity: EntityId,
36 type_id: TypeId,
37 component: Box<dyn Any + Send + Sync>,
38 );
39
40 fn entities_with(&self, type_id: TypeId) -> Vec<EntityId>;
42
43 fn get_component(&self, entity: EntityId, type_id: TypeId) -> Option<&dyn Any>;
45
46 fn get_component_mut(&mut self, entity: EntityId, type_id: TypeId) -> Option<&mut dyn Any>;
48
49 fn budget(&self) -> &TickBudget;
54}
55
56pub trait SystemContextExt {
61 fn get<T: crate::components::Component>(&self, entity: EntityId) -> Option<&T>;
63
64 fn get_mut<T: crate::components::Component>(&mut self, entity: EntityId) -> Option<&mut T>;
66
67 fn set<T: crate::components::Component>(&mut self, entity: EntityId, component: T);
69
70 fn query<T: crate::components::Component>(&self) -> Vec<EntityId>;
72}
73
74impl<S: SystemContext + ?Sized> SystemContextExt for S {
75 fn get<T: crate::components::Component>(&self, entity: EntityId) -> Option<&T> {
76 self.get_component(entity, TypeId::of::<T>())
77 .and_then(|any| any.downcast_ref::<T>())
78 }
79
80 fn get_mut<T: crate::components::Component>(&mut self, entity: EntityId) -> Option<&mut T> {
81 self.get_component_mut(entity, TypeId::of::<T>())
82 .and_then(|any| any.downcast_mut::<T>())
83 }
84
85 fn set<T: crate::components::Component>(&mut self, entity: EntityId, component: T) {
86 self.set_component(entity, TypeId::of::<T>(), Box::new(component));
87 }
88
89 fn query<T: crate::components::Component>(&self) -> Vec<EntityId> {
90 self.entities_with(TypeId::of::<T>())
91 }
92}
93
94pub struct SystemDescriptor {
96 pub name: String,
98 pub phase: Phase,
100 pub every: u64,
102 pub access: SystemAccess,
104 pub budget: Option<Duration>,
106 pub runner: Box<dyn SystemRunner>,
108}
109
110pub trait SystemRunner: Send {
114 fn run(&mut self, ctx: &mut dyn SystemContext);
116}
117
118impl<F: FnMut(&mut dyn SystemContext) + Send> SystemRunner for F {
120 fn run(&mut self, ctx: &mut dyn SystemContext) {
121 self(ctx);
122 }
123}
124
125#[derive(Debug, Clone)]
131pub struct SystemAccess {
132 pub reads: HashSet<TypeId>,
134 pub writes: HashSet<TypeId>,
136}
137
138impl SystemAccess {
139 pub fn new() -> Self {
141 Self {
142 reads: HashSet::new(),
143 writes: HashSet::new(),
144 }
145 }
146
147 pub fn conflicts_with(&self, other: &SystemAccess) -> bool {
152 for w in &self.writes {
153 if other.reads.contains(w) || other.writes.contains(w) {
154 return true;
155 }
156 }
157 for w in &other.writes {
158 if self.reads.contains(w) {
159 return true;
160 }
161 }
162 false
163 }
164}
165
166impl Default for SystemAccess {
167 fn default() -> Self {
168 Self::new()
169 }
170}
171
172pub struct SystemBuilder {
174 name: String,
175 phase: Phase,
176 every: u64,
177 access: SystemAccess,
178 budget: Option<Duration>,
179}
180
181impl SystemBuilder {
182 pub fn new(name: &str) -> Self {
184 Self {
185 name: name.to_string(),
186 phase: Phase::Simulate,
187 every: 1,
188 access: SystemAccess::new(),
189 budget: None,
190 }
191 }
192
193 pub fn phase(mut self, phase: Phase) -> Self {
195 self.phase = phase;
196 self
197 }
198
199 pub fn every(mut self, every: u64) -> Self {
204 self.every = every;
205 self
206 }
207
208 pub fn reads<T: crate::components::Component>(mut self) -> Self {
210 self.access.reads.insert(TypeId::of::<T>());
211 self
212 }
213
214 pub fn writes<T: crate::components::Component>(mut self) -> Self {
216 self.access.writes.insert(TypeId::of::<T>());
217 self
218 }
219
220 pub fn budget_ms(mut self, ms: u64) -> Self {
225 self.budget = Some(Duration::from_millis(ms));
226 self
227 }
228
229 pub fn run<F: FnMut(&mut dyn SystemContext) + Send + 'static>(
231 self,
232 runner: F,
233 ) -> SystemDescriptor {
234 SystemDescriptor {
235 name: self.name,
236 phase: self.phase,
237 every: self.every,
238 access: self.access,
239 budget: self.budget,
240 runner: Box::new(runner),
241 }
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248 use crate::components::{Position, Velocity};
249
250 #[test]
251 fn system_builder_defaults() {
252 let desc = SystemBuilder::new("test").run(|_ctx| {});
253 assert_eq!(desc.name, "test");
254 assert_eq!(desc.phase, Phase::Simulate);
255 assert_eq!(desc.every, 1);
256 }
257
258 #[test]
259 fn system_builder_with_access() {
260 let desc = SystemBuilder::new("physics")
261 .phase(Phase::Simulate)
262 .every(1)
263 .reads::<Position>()
264 .writes::<Position>()
265 .writes::<Velocity>()
266 .run(|_ctx| {});
267 assert!(desc.access.reads.contains(&TypeId::of::<Position>()));
268 assert!(desc.access.writes.contains(&TypeId::of::<Position>()));
269 assert!(desc.access.writes.contains(&TypeId::of::<Velocity>()));
270 }
271
272 #[test]
273 fn access_conflict_detection() {
274 let mut a = SystemAccess::new();
275 a.writes.insert(TypeId::of::<Position>());
276
277 let mut b = SystemAccess::new();
278 b.reads.insert(TypeId::of::<Position>());
279
280 assert!(a.conflicts_with(&b));
281 assert!(b.conflicts_with(&a));
282 }
283
284 #[test]
285 fn no_conflict_for_disjoint_access() {
286 let mut a = SystemAccess::new();
287 a.reads.insert(TypeId::of::<Position>());
288
289 let mut b = SystemAccess::new();
290 b.reads.insert(TypeId::of::<Velocity>());
291
292 assert!(!a.conflicts_with(&b));
293 }
294
295 #[test]
296 fn read_read_no_conflict() {
297 let mut a = SystemAccess::new();
298 a.reads.insert(TypeId::of::<Position>());
299
300 let mut b = SystemAccess::new();
301 b.reads.insert(TypeId::of::<Position>());
302
303 assert!(!a.conflicts_with(&b));
304 }
305}