1use crate::world::World;
2use std::any::TypeId;
3use std::collections::HashSet;
4
5#[derive(Default, Clone)]
10pub struct AccessInfo {
11 pub component_reads: Vec<TypeId>,
12 pub component_writes: Vec<TypeId>,
13 pub resource_reads: Vec<TypeId>,
14 pub resource_writes: Vec<TypeId>,
15 pub is_exclusive: bool,
16}
17
18impl AccessInfo {
19 pub fn new() -> Self {
20 Self::default()
21 }
22
23 pub fn is_compatible_with(&self, other: &AccessInfo) -> bool {
24 if self.is_exclusive || other.is_exclusive {
25 return false;
26 }
27
28 for w in &self.component_writes {
29 if other.component_writes.contains(w) || other.component_reads.contains(w) {
30 return false;
31 }
32 }
33 for r in &self.component_reads {
34 if other.component_writes.contains(r) {
35 return false;
36 }
37 }
38
39 for w in &self.resource_writes {
40 if other.resource_writes.contains(w) || other.resource_reads.contains(w) {
41 return false;
42 }
43 }
44 for r in &self.resource_reads {
45 if other.resource_writes.contains(r) {
46 return false;
47 }
48 }
49
50 true
51 }
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
64pub enum Phase {
65 PreUpdate = 0,
67 #[default]
69 Update = 1,
70 Physics = 2,
72 PostUpdate = 3,
74 Render = 4,
76}
77
78impl Phase {
79 pub const ALL: [Phase; 5] = [
81 Phase::PreUpdate,
82 Phase::Update,
83 Phase::Physics,
84 Phase::PostUpdate,
85 Phase::Render,
86 ];
87
88 pub const fn name(&self) -> &'static str {
90 match self {
91 Phase::PreUpdate => "pre_update",
92 Phase::Update => "update",
93 Phase::Physics => "physics",
94 Phase::PostUpdate => "post_update",
95 Phase::Render => "render",
96 }
97 }
98}
99
100pub trait System: Send + Sync {
106 fn run(&mut self, world: &World, dt: f32);
107 fn access_info(&self) -> AccessInfo;
108}
109
110use crate::world::{ResourceReadGuard, ResourceWriteGuard};
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117pub enum SystemParamFetchError {
118 Resource(crate::world::ResourceFetchError),
119 QueryError,
120}
121
122impl From<crate::world::ResourceFetchError> for SystemParamFetchError {
123 fn from(value: crate::world::ResourceFetchError) -> Self {
124 Self::Resource(value)
125 }
126}
127
128pub trait SystemParam {
129 type Item<'w>;
130 fn fetch<'w>(world: &'w World, dt: f32) -> Result<Self::Item<'w>, SystemParamFetchError>;
131 fn get_access_info(info: &mut AccessInfo);
132}
133
134pub struct Res<'w, T: 'static> {
135 value: ResourceReadGuard<'w, T>,
136}
137
138impl<'w, T: 'static> std::ops::Deref for Res<'w, T> {
139 type Target = T;
140 fn deref(&self) -> &Self::Target {
141 &self.value
142 }
143}
144
145impl<T: 'static> SystemParam for Res<'static, T> {
146 type Item<'w> = Res<'w, T>;
147 fn fetch<'w>(world: &'w World, _dt: f32) -> Result<Self::Item<'w>, SystemParamFetchError> {
148 let value = world.try_get_resource::<T>()?;
149 Ok(Res::<T> { value })
150 }
151 fn get_access_info(info: &mut AccessInfo) {
152 info.resource_reads.push(TypeId::of::<T>());
153 }
154}
155
156pub struct ResMut<'w, T: 'static> {
157 value: ResourceWriteGuard<'w, T>,
158}
159
160impl<'w, T: 'static> std::ops::Deref for ResMut<'w, T> {
161 type Target = T;
162 fn deref(&self) -> &Self::Target {
163 &self.value
164 }
165}
166
167impl<'w, T: 'static> std::ops::DerefMut for ResMut<'w, T> {
168 fn deref_mut(&mut self) -> &mut Self::Target {
169 &mut self.value
170 }
171}
172
173impl<T: 'static> SystemParam for ResMut<'static, T> {
174 type Item<'w> = ResMut<'w, T>;
175 fn fetch<'w>(world: &'w World, _dt: f32) -> Result<Self::Item<'w>, SystemParamFetchError> {
176 let value = world.try_get_resource_mut::<T>()?;
177 Ok(ResMut::<T> { value })
178 }
179 fn get_access_info(info: &mut AccessInfo) {
180 info.resource_writes.push(TypeId::of::<T>());
181 }
182}
183
184impl SystemParam for f32 {
185 type Item<'w> = f32;
186 fn fetch<'w>(_world: &'w World, dt: f32) -> Result<Self::Item<'w>, SystemParamFetchError> {
187 Ok(dt)
188 }
189 fn get_access_info(_info: &mut AccessInfo) {}
190}
191
192impl<Q: crate::query::WorldQuery + 'static> SystemParam for crate::query::Query<'static, Q> {
193 type Item<'w> = crate::query::Query<'w, Q>;
194 fn fetch<'w>(world: &'w World, _dt: f32) -> Result<Self::Item<'w>, SystemParamFetchError> {
195 if let Some(query) = world.query::<Q>() {
196 Ok(query)
197 } else {
198 Err(SystemParamFetchError::QueryError)
199 }
200 }
201 fn get_access_info(info: &mut AccessInfo) {
202 let mut types = Vec::new();
203 Q::check_aliasing(&mut types);
204 for (tid, is_mut) in types {
205 if is_mut {
206 info.component_writes.push(tid);
207 } else {
208 info.component_reads.push(tid);
209 }
210 }
211 }
212}
213
214pub trait IntoSystem<Params> {
219 fn into_system(self) -> Box<dyn System>;
220}
221
222impl<F> IntoSystem<()> for F
224where
225 F: FnMut() + Send + Sync + 'static,
226{
227 fn into_system(self) -> Box<dyn System> {
228 struct ZeroParamSystem<F>(F);
229 impl<F: FnMut() + Send + Sync + 'static> System for ZeroParamSystem<F> {
230 fn run(&mut self, _world: &World, _dt: f32) {
231 (self.0)();
232 }
233 fn access_info(&self) -> AccessInfo {
234 AccessInfo::new()
235 }
236 }
237 Box::new(ZeroParamSystem(self))
238 }
239}
240
241impl IntoSystem<()> for Box<dyn System> {
243 fn into_system(self) -> Box<dyn System> {
244 self
245 }
246}
247
248impl System for Box<dyn System> {
249 fn run(&mut self, world: &World, dt: f32) {
250 (**self).run(world, dt);
251 }
252 fn access_info(&self) -> AccessInfo {
253 (**self).access_info()
254 }
255}
256
257pub struct PipeSystem {
258 a: Box<dyn System>,
259 b: Box<dyn System>,
260}
261
262impl System for PipeSystem {
263 fn run(&mut self, world: &World, dt: f32) {
264 self.a.run(world, dt);
265 self.b.run(world, dt);
266 }
267
268 fn access_info(&self) -> AccessInfo {
269 let mut info = self.a.access_info();
270 let mut b_info = self.b.access_info();
271 info.component_reads.append(&mut b_info.component_reads);
272 info.component_writes.append(&mut b_info.component_writes);
273 info.resource_reads.append(&mut b_info.resource_reads);
274 info.resource_writes.append(&mut b_info.resource_writes);
275 info.is_exclusive = info.is_exclusive || b_info.is_exclusive;
276 info
277 }
278}
279
280pub trait SystemExt<ParamA> {
281 fn pipe<ParamB, SystemB: IntoSystem<ParamB>>(self, other: SystemB) -> Box<dyn System>;
282}
283
284impl<ParamA, SystemA: IntoSystem<ParamA>> SystemExt<ParamA> for SystemA {
285 fn pipe<ParamB, SystemB: IntoSystem<ParamB>>(self, other: SystemB) -> Box<dyn System> {
286 Box::new(PipeSystem {
287 a: self.into_system(),
288 b: other.into_system(),
289 })
290 }
291}
292
293macro_rules! impl_into_system {
295 ($($P:ident),+) => {
296 #[allow(non_snake_case)]
297 impl<F, $($P),+> IntoSystem<($($P,)+)> for F
298 where
299 F: FnMut($($P::Item<'_>),+) + FnMut($($P),+) + Send + Sync + 'static,
300 $($P: SystemParam + 'static,)+
301 {
302 fn into_system(self) -> Box<dyn System> {
303 struct MultiParamSystem<F, $($P),+> {
304 func: F,
305 _marker: std::marker::PhantomData<fn() -> ($($P,)+)>,
306 }
307
308 impl<F, $($P),+> System for MultiParamSystem<F, $($P),+>
309 where
310 F: FnMut($($P::Item<'_>),+) + FnMut($($P),+) + Send + Sync + 'static,
311 $($P: SystemParam + 'static,)+
312 {
313 fn run(&mut self, world: &World, dt: f32) {
314 $(
315 let $P = match $P::fetch(world, dt) {
316 Ok(v) => v,
317 Err(e) => {
318 panic!(
319 "❌ FATAL ECS ERROR ❌\n\nSistem parametresi '{param_type}' Dünya'da (World) bulunamadı!\n\nHata Detayı: {e:?}\n\nÇözüm: `app.world.insert_resource()` veya `app.add_plugin()` kullanarak eksik kaynağı başlangıçta Dünya'ya eklediğinizden emin olun. Gizmo Engine, hataların sessizce yok sayılmasını önlemek için sistemi durdurdu.\n",
320 param_type = std::any::type_name::<$P>(),
321 e = e
322 );
323 }
324 };
325 )+
326 (self.func)($($P),+);
327 }
328 fn access_info(&self) -> AccessInfo {
329 let mut info = AccessInfo::new();
330 $($P::get_access_info(&mut info);)+
331 info
332 }
333 }
334
335 Box::new(MultiParamSystem {
336 func: self,
337 _marker: std::marker::PhantomData,
338 })
339 }
340 }
341 };
342}
343
344impl_into_system!(P1);
345impl_into_system!(P1, P2);
346impl_into_system!(P1, P2, P3);
347impl_into_system!(P1, P2, P3, P4);
348impl_into_system!(P1, P2, P3, P4, P5);
349impl_into_system!(P1, P2, P3, P4, P5, P6);
350impl_into_system!(P1, P2, P3, P4, P5, P6, P7);
351impl_into_system!(P1, P2, P3, P4, P5, P6, P7, P8);
352impl_into_system!(P1, P2, P3, P4, P5, P6, P7, P8, P9);
353impl_into_system!(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10);
354impl_into_system!(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11);
355impl_into_system!(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12);
356
357impl<F> System for F
359where
360 F: FnMut(&World, f32) + Send + Sync + 'static,
361{
362 fn run(&mut self, world: &World, dt: f32) {
363 (self)(world, dt);
364 }
365 fn access_info(&self) -> AccessInfo {
367 let mut info = AccessInfo::new();
368 info.is_exclusive = true;
369 info
370 }
371}
372
373pub trait IntoCondition<Params> {
378 fn into_condition(self) -> Box<dyn FnMut(&World) -> bool + Send + Sync>;
379}
380
381impl<F> IntoCondition<()> for F
382where
383 F: FnMut() -> bool + Send + Sync + 'static,
384{
385 fn into_condition(mut self) -> Box<dyn FnMut(&World) -> bool + Send + Sync> {
386 Box::new(move |_world| self())
387 }
388}
389
390macro_rules! impl_into_condition {
391 ($($P:ident),+) => {
392 #[allow(non_snake_case)]
393 impl<F, $($P),+> IntoCondition<($($P,)+)> for F
394 where
395 F: FnMut($($P::Item<'_>),+) -> bool + Send + Sync + 'static,
396 $($P: SystemParam + 'static,)+
397 {
398 fn into_condition(mut self) -> Box<dyn FnMut(&World) -> bool + Send + Sync> {
399 Box::new(move |world| {
400 $(let $P = $P::fetch(world, 0.0).unwrap();)+
401 (self)($($P),+)
402 })
403 }
404 }
405 };
406}
407
408impl_into_condition!(P1);
409impl_into_condition!(P1, P2);
410impl_into_condition!(P1, P2, P3);
411impl_into_condition!(P1, P2, P3, P4);
412impl_into_condition!(P1, P2, P3, P4, P5);
413impl_into_condition!(P1, P2, P3, P4, P5, P6);
414
415pub trait SystemExtRunIf {
416 fn run_if_sys<ParamC, Cond: IntoCondition<ParamC>>(self, cond: Cond) -> Box<dyn System>;
417}
418
419impl SystemExtRunIf for Box<dyn System> {
420 fn run_if_sys<ParamC, Cond: IntoCondition<ParamC>>(self, cond: Cond) -> Box<dyn System> {
421 Box::new(ConditionalSystem {
422 inner: self,
423 condition: cond.into_condition(),
424 })
425 }
426}
427
428pub struct ConditionalSystem {
429 inner: Box<dyn System>,
430 condition: Box<dyn FnMut(&World) -> bool + Send + Sync>,
431}
432
433impl System for ConditionalSystem {
434 fn run(&mut self, world: &World, dt: f32) {
435 if (self.condition)(world) {
436 self.inner.run(world, dt);
437 }
438 }
439 fn access_info(&self) -> AccessInfo {
440 self.inner.access_info()
441 }
442}
443
444pub trait DistributiveRunIfExt<Params> {
445 fn distributive_run_if<ParamC, Cond: IntoCondition<ParamC> + Clone + Send + Sync + 'static>(self, cond: Cond) -> Box<dyn System>;
446}
447
448macro_rules! impl_distributive_run_if {
449 ($($P:ident $S:ident $idx:tt),+) => {
450 impl<$($P, $S),+> DistributiveRunIfExt<($($P,)+)> for ($($S,)+)
451 where
452 $($S: IntoSystem<$P> + 'static,)+
453 {
454 fn distributive_run_if<ParamC, Cond: IntoCondition<ParamC> + Clone + Send + Sync + 'static>(self, cond: Cond) -> Box<dyn System> {
455 let mut systems: Vec<Box<dyn System>> = Vec::new();
456 $(
457 systems.push(self.$idx.into_system().run_if_sys(cond.clone()));
458 )+
459
460 struct MacroSystem {
461 systems: Vec<Box<dyn System>>,
462 }
463 impl System for MacroSystem {
464 fn run(&mut self, world: &World, dt: f32) {
465 for s in &mut self.systems {
466 s.run(world, dt);
467 }
468 }
469 fn access_info(&self) -> AccessInfo {
470 let mut info = AccessInfo::new();
471 for s in &self.systems {
472 let s_info = s.access_info();
473 info.component_reads.extend(s_info.component_reads);
474 info.component_writes.extend(s_info.component_writes);
475 info.resource_reads.extend(s_info.resource_reads);
476 info.resource_writes.extend(s_info.resource_writes);
477 }
478 info
479 }
480 }
481
482 Box::new(MacroSystem { systems })
483 }
484 }
485 };
486}
487
488impl_distributive_run_if!(P1 S1 0);
489impl_distributive_run_if!(P1 S1 0, P2 S2 1);
490impl_distributive_run_if!(P1 S1 0, P2 S2 1, P3 S3 2);
491impl_distributive_run_if!(P1 S1 0, P2 S2 1, P3 S3 2, P4 S4 3);
492impl_distributive_run_if!(P1 S1 0, P2 S2 1, P3 S3 2, P4 S4 3, P5 S5 4);
493impl_distributive_run_if!(P1 S1 0, P2 S2 1, P3 S3 2, P4 S4 3, P5 S5 4, P6 S6 5);
494impl_distributive_run_if!(P1 S1 0, P2 S2 1, P3 S3 2, P4 S4 3, P5 S5 4, P6 S6 5, P7 S7 6);
495impl_distributive_run_if!(P1 S1 0, P2 S2 1, P3 S3 2, P4 S4 3, P5 S5 4, P6 S6 5, P7 S7 6, P8 S8 7);
496
497pub trait SystemSet: 'static {
502 fn set_name() -> &'static str {
503 std::any::type_name::<Self>()
504 }
505}
506
507pub struct SystemConfig {
508 pub(crate) system: Box<dyn System>,
509 pub(crate) labels: Vec<&'static str>,
510 pub(crate) before: Vec<&'static str>,
511 pub(crate) after: Vec<&'static str>,
512 pub(crate) in_sets: Vec<&'static str>,
513 pub(crate) added_info: AccessInfo,
514 pub(crate) phase: Phase,
515}
516
517impl SystemConfig {
518 pub fn new(system: Box<dyn System>) -> Self {
519 Self {
520 system,
521 labels: Vec::new(),
522 before: Vec::new(),
523 after: Vec::new(),
524 in_sets: Vec::new(),
525 added_info: AccessInfo::new(),
526 phase: Phase::default(),
527 }
528 }
529
530 pub fn in_set<S: SystemSet>(mut self) -> Self {
531 self.in_sets.push(S::set_name());
532 self.labels.push(S::set_name());
533 self
534 }
535
536 pub fn label(mut self, label: &'static str) -> Self {
537 self.labels.push(label);
538 self
539 }
540
541 pub fn before(mut self, target: &'static str) -> Self {
542 self.before.push(target);
543 self
544 }
545
546 pub fn before_set<S: SystemSet>(mut self) -> Self {
547 self.before.push(S::set_name());
548 self
549 }
550
551 pub fn after(mut self, target: &'static str) -> Self {
552 self.after.push(target);
553 self
554 }
555
556 pub fn after_set<S: SystemSet>(mut self) -> Self {
557 self.after.push(S::set_name());
558 self
559 }
560
561 pub fn reads<T: 'static>(mut self) -> Self {
562 self.added_info.component_reads.push(TypeId::of::<T>());
563 self
564 }
565 pub fn writes<T: 'static>(mut self) -> Self {
566 self.added_info.component_writes.push(TypeId::of::<T>());
567 self
568 }
569 pub fn reads_res<T: 'static>(mut self) -> Self {
570 self.added_info.resource_reads.push(TypeId::of::<T>());
571 self
572 }
573 pub fn writes_res<T: 'static>(mut self) -> Self {
574 self.added_info.resource_writes.push(TypeId::of::<T>());
575 self
576 }
577 pub fn exclusive(mut self) -> Self {
578 self.added_info.is_exclusive = true;
579 self
580 }
581 pub fn in_phase(mut self, phase: Phase) -> Self {
582 self.phase = phase;
583 self
584 }
585
586 pub fn run_if<F>(mut self, condition: F) -> Self
587 where
588 F: FnMut(&World) -> bool + Send + Sync + 'static,
589 {
590 self.system = Box::new(ConditionalSystem {
591 inner: self.system,
592 condition: Box::new(condition),
593 });
594 self
595 }
596}
597
598pub trait IntoSystemConfig<Params> {
599 fn into_config(self) -> SystemConfig;
600
601 fn label(self, l: &'static str) -> SystemConfig
602 where
603 Self: Sized,
604 {
605 self.into_config().label(l)
606 }
607
608 fn in_set<S: SystemSet>(self) -> SystemConfig
609 where
610 Self: Sized,
611 {
612 self.into_config().in_set::<S>()
613 }
614
615 fn before(self, target: &'static str) -> SystemConfig
616 where
617 Self: Sized,
618 {
619 self.into_config().before(target)
620 }
621
622 fn before_set<S: SystemSet>(self) -> SystemConfig
623 where
624 Self: Sized,
625 {
626 self.into_config().before_set::<S>()
627 }
628
629 fn after(self, target: &'static str) -> SystemConfig
630 where
631 Self: Sized,
632 {
633 self.into_config().after(target)
634 }
635
636 fn after_set<S: SystemSet>(self) -> SystemConfig
637 where
638 Self: Sized,
639 {
640 self.into_config().after_set::<S>()
641 }
642
643 fn reads<C: 'static>(self) -> SystemConfig
644 where
645 Self: Sized,
646 {
647 self.into_config().reads::<C>()
648 }
649 fn writes<C: 'static>(self) -> SystemConfig
650 where
651 Self: Sized,
652 {
653 self.into_config().writes::<C>()
654 }
655 fn reads_res<C: 'static>(self) -> SystemConfig
656 where
657 Self: Sized,
658 {
659 self.into_config().reads_res::<C>()
660 }
661 fn writes_res<C: 'static>(self) -> SystemConfig
662 where
663 Self: Sized,
664 {
665 self.into_config().writes_res::<C>()
666 }
667 fn exclusive(self) -> SystemConfig
668 where
669 Self: Sized,
670 {
671 self.into_config().exclusive()
672 }
673 fn in_phase(self, phase: Phase) -> SystemConfig
674 where
675 Self: Sized,
676 {
677 self.into_config().in_phase(phase)
678 }
679 fn run_if<F>(self, condition: F) -> SystemConfig
680 where
681 F: FnMut(&World) -> bool + Send + Sync + 'static,
682 Self: Sized,
683 {
684 self.into_config().run_if(condition)
685 }
686}
687
688impl<Params, T: IntoSystem<Params>> IntoSystemConfig<Params> for T {
689 fn into_config(self) -> SystemConfig {
690 SystemConfig::new(self.into_system())
691 }
692}
693
694impl IntoSystemConfig<()> for SystemConfig {
695 fn into_config(self) -> SystemConfig {
696 self
697 }
698}
699
700pub struct SystemBatch {
705 systems: Vec<Box<dyn System>>,
706 pub access_info: AccessInfo,
707}
708
709impl Default for SystemBatch {
710 fn default() -> Self {
711 Self::new()
712 }
713}
714
715impl SystemBatch {
716 pub fn new() -> Self {
717 Self {
718 systems: Vec::new(),
719 access_info: AccessInfo::new(),
720 }
721 }
722
723 pub fn add_system(&mut self, system: Box<dyn System>, config_info: AccessInfo) {
724 let mut sys_info = system.access_info();
725 sys_info.component_reads.extend(config_info.component_reads);
726 sys_info
727 .component_writes
728 .extend(config_info.component_writes);
729 sys_info.resource_reads.extend(config_info.resource_reads);
730 sys_info.resource_writes.extend(config_info.resource_writes);
731 sys_info.is_exclusive = sys_info.is_exclusive || config_info.is_exclusive;
732
733 self.access_info
734 .component_reads
735 .extend(sys_info.component_reads);
736 self.access_info
737 .component_writes
738 .extend(sys_info.component_writes);
739 self.access_info
740 .resource_reads
741 .extend(sys_info.resource_reads);
742 self.access_info
743 .resource_writes
744 .extend(sys_info.resource_writes);
745 self.access_info.is_exclusive = self.access_info.is_exclusive || sys_info.is_exclusive;
746
747 self.systems.push(system);
748 }
749
750 pub fn is_compatible(&self, system: &dyn System, config_info: &AccessInfo) -> bool {
751 let mut sys_info = system.access_info();
752 sys_info
753 .component_reads
754 .extend(config_info.component_reads.iter().cloned());
755 sys_info
756 .component_writes
757 .extend(config_info.component_writes.iter().cloned());
758 sys_info
759 .resource_reads
760 .extend(config_info.resource_reads.iter().cloned());
761 sys_info
762 .resource_writes
763 .extend(config_info.resource_writes.iter().cloned());
764 sys_info.is_exclusive = sys_info.is_exclusive || config_info.is_exclusive;
765
766 self.access_info.is_compatible_with(&sys_info)
767 }
768}
769
770pub struct SetConfig {
771 pub name: &'static str,
772 pub before: Vec<&'static str>,
773 pub after: Vec<&'static str>,
774 pub phase: Option<Phase>,
775}
776
777impl SetConfig {
778 pub fn new<S: SystemSet>() -> Self {
779 Self {
780 name: S::set_name(),
781 before: Vec::new(),
782 after: Vec::new(),
783 phase: None,
784 }
785 }
786 pub fn before<S: SystemSet>(mut self) -> Self {
787 self.before.push(S::set_name());
788 self
789 }
790 pub fn after<S: SystemSet>(mut self) -> Self {
791 self.after.push(S::set_name());
792 self
793 }
794 pub fn in_phase(mut self, phase: Phase) -> Self {
795 self.phase = Some(phase);
796 self
797 }
798}
799
800pub struct Schedule {
801 unbuilt_configs: Vec<SystemConfig>,
802 set_configs: std::collections::HashMap<&'static str, SetConfig>,
803 phase_batches: Vec<(Phase, Vec<SystemBatch>)>,
805 legacy_batches: Vec<SystemBatch>,
807 uses_phases: bool,
808}
809
810impl Schedule {
811 pub fn new() -> Self {
812 Self {
813 unbuilt_configs: Vec::new(),
814 set_configs: std::collections::HashMap::new(),
815 phase_batches: Vec::new(),
816 legacy_batches: Vec::new(),
817 uses_phases: false,
818 }
819 }
820
821 pub fn configure_set(&mut self, config: SetConfig) {
822 self.set_configs.insert(config.name, config);
823 self.invalidate();
824 }
825
826 pub fn add_di_system<Params, S: IntoSystemConfig<Params>>(&mut self, system: S) {
827 self.unbuilt_configs.push(system.into_config());
828 self.invalidate();
829 }
830
831 pub fn add_system<S: System + 'static>(&mut self, system: S) {
832 self.unbuilt_configs
833 .push(SystemConfig::new(Box::new(system)));
834 self.invalidate();
835 }
836
837 pub fn add_systems<T, Configs: IntoSystemConfigs<T>>(&mut self, configs: Configs) {
838 configs.into_configs(self);
839 }
840
841 pub fn add_system_boxed(&mut self, system: Box<dyn System>) {
842 self.unbuilt_configs.push(SystemConfig::new(system));
843 self.invalidate();
844 }
845
846 fn invalidate(&mut self) {
847 self.phase_batches.clear();
848 self.legacy_batches.clear();
849 }
850
851 fn is_built(&self) -> bool {
852 !self.phase_batches.is_empty() || !self.legacy_batches.is_empty()
853 }
854
855 pub fn validate(&mut self) {
856 self.build();
857 }
858
859 fn build_batches_for(configs: Vec<SystemConfig>) -> Vec<SystemBatch> {
861 let count = configs.len();
862 if count == 0 {
863 return Vec::new();
864 }
865
866 let mut edge_set: HashSet<(usize, usize)> = HashSet::new();
867 let mut adj = vec![Vec::new(); count];
868 let mut in_degree = vec![0usize; count];
869
870 let add_edge = |from: usize,
871 to: usize,
872 edge_set: &mut HashSet<(usize, usize)>,
873 adj: &mut Vec<Vec<usize>>,
874 in_degree: &mut Vec<usize>| {
875 if edge_set.insert((from, to)) {
876 adj[from].push(to);
877 in_degree[to] += 1;
878 }
879 };
880
881 for i in 0..count {
882 for before_label in &configs[i].before {
883 let mut found = false;
884 for (j, config_j) in configs.iter().enumerate() {
885 if i != j && config_j.labels.contains(before_label) {
886 add_edge(i, j, &mut edge_set, &mut adj, &mut in_degree);
887 found = true;
888 }
889 }
890 if !found {
891 crate::gizmo_log!(
892 Warning,
893 "[Schedule] Sistem {}'in before('{}') label'ı eşleşmiyor!",
894 i,
895 before_label
896 );
897 }
898 }
899 for after_label in &configs[i].after {
900 let mut found = false;
901 for (j, config_j) in configs.iter().enumerate() {
902 if i != j && config_j.labels.contains(after_label) {
903 add_edge(j, i, &mut edge_set, &mut adj, &mut in_degree);
904 found = true;
905 }
906 }
907 if !found {
908 crate::gizmo_log!(
909 Warning,
910 "[Schedule] Sistem {}'in after('{}') label'ı eşleşmiyor!",
911 i,
912 after_label
913 );
914 }
915 }
916 }
917
918 let mut queue = std::collections::VecDeque::new();
919 for (i, deg) in in_degree.iter().enumerate() {
920 if *deg == 0 {
921 queue.push_back(i);
922 }
923 }
924
925 let mut sorted_indices = Vec::with_capacity(count);
926 while let Some(node) = queue.pop_front() {
927 sorted_indices.push(node);
928 for &neighbor in &adj[node] {
929 in_degree[neighbor] -= 1;
930 if in_degree[neighbor] == 0 {
931 queue.push_back(neighbor);
932 }
933 }
934 }
935
936 if sorted_indices.len() != count {
937 panic!(
938 "Cyclic dependency detected! {} sistemin {} tanesi sıralanabildi.",
939 count,
940 sorted_indices.len()
941 );
942 }
943
944 let mut predecessors = vec![Vec::<usize>::new(); count];
946 for (from, neighbors) in adj.iter().enumerate() {
947 for &to in neighbors {
948 predecessors[to].push(from);
949 }
950 }
951
952 let mut dummy_configs: Vec<Option<SystemConfig>> = configs.into_iter().map(Some).collect();
954 let mut batches: Vec<SystemBatch> = Vec::new();
955 let mut system_batch = vec![0usize; count];
956
957 for &idx in &sorted_indices {
958 let config = dummy_configs[idx].take().unwrap();
959
960 let earliest = predecessors[idx]
961 .iter()
962 .map(|&pred| system_batch[pred] + 1)
963 .max()
964 .unwrap_or(0);
965
966 let placed = (earliest..batches.len())
967 .rev()
968 .find(|&bidx| batches[bidx].is_compatible(&*config.system, &config.added_info));
969
970 let batch_idx = if let Some(bidx) = placed {
971 batches[bidx].add_system(config.system, config.added_info);
972 bidx
973 } else {
974 let new_idx = batches.len();
975 let mut new_batch = SystemBatch::new();
976 new_batch.add_system(config.system, config.added_info);
977 batches.push(new_batch);
978 new_idx
979 };
980
981 system_batch[idx] = batch_idx;
982 }
983
984 batches
985 }
986
987 pub fn build(&mut self) {
988 if self.is_built() {
989 return;
990 }
991
992 let mut configs = std::mem::take(&mut self.unbuilt_configs);
993 if configs.is_empty() {
994 return;
995 }
996
997 for config in &mut configs {
999 for set_name in &config.in_sets {
1000 if let Some(set_cfg) = self.set_configs.get(set_name) {
1001 config.before.extend(set_cfg.before.iter().copied());
1002 config.after.extend(set_cfg.after.iter().copied());
1003 if let Some(phase) = set_cfg.phase {
1004 config.phase = phase;
1005 }
1006 }
1007 }
1008 }
1009
1010 let has_explicit_phase = configs.iter().any(|c| c.phase != Phase::Update);
1012 self.uses_phases = has_explicit_phase;
1013
1014 if has_explicit_phase {
1015 let mut phase_groups: std::collections::BTreeMap<Phase, Vec<SystemConfig>> =
1017 std::collections::BTreeMap::new();
1018 for config in configs {
1019 phase_groups.entry(config.phase).or_default().push(config);
1020 }
1021 for (phase, group) in phase_groups {
1023 let batches = Self::build_batches_for(group);
1024 if !batches.is_empty() {
1025 self.phase_batches.push((phase, batches));
1026 }
1027 }
1028 } else {
1029 self.legacy_batches = Self::build_batches_for(configs);
1031 }
1032 }
1033
1034 fn run_batches(batches: &mut [SystemBatch], world: &mut World, dt: f32) {
1036 use rayon::prelude::*;
1037
1038 for batch in batches.iter_mut() {
1039 batch.systems.par_iter_mut().for_each(|system| {
1040 system.run(world, dt);
1041 });
1042
1043 let queue_clone = world
1045 .get_resource::<crate::commands::CommandQueue>()
1046 .filter(|q| !q.is_empty())
1047 .map(|q| (*q).clone());
1048 if let Some(queue) = queue_clone {
1049 queue.apply(world);
1050 }
1051 }
1052 }
1053
1054 #[tracing::instrument(skip_all, name = "ecs_update")]
1055 pub fn run(&mut self, world: &mut World, dt: f32) {
1056 if !self.is_built() && !self.unbuilt_configs.is_empty() {
1057 self.build();
1058 }
1059
1060 if self.uses_phases {
1061 for (_phase, batches) in &mut self.phase_batches {
1063 let _span = tracing::info_span!("phase", name = _phase.name()).entered();
1064 Self::run_batches(batches, world, dt);
1065 }
1066 } else {
1067 Self::run_batches(&mut self.legacy_batches, world, dt);
1069 }
1070
1071 if let Some(mut profiler) = world.get_resource_mut::<crate::profiler::FrameProfiler>() {
1073 profiler.end_frame();
1074 }
1075 }
1076
1077 #[cfg(test)]
1079 fn total_batch_count(&self) -> usize {
1080 if self.uses_phases {
1081 self.phase_batches.iter().map(|(_, b)| b.len()).sum()
1082 } else {
1083 self.legacy_batches.len()
1084 }
1085 }
1086}
1087
1088impl Default for Schedule {
1089 fn default() -> Self {
1090 Self::new()
1091 }
1092}
1093
1094pub trait IntoSystemConfigs<T> {
1095 fn into_configs(self, schedule: &mut Schedule);
1096}
1097
1098impl<P1, S1> IntoSystemConfigs<(P1,)> for S1
1099where
1100 S1: IntoSystem<P1> + 'static,
1101{
1102 fn into_configs(self, schedule: &mut Schedule) {
1103 schedule.add_system(self.into_system());
1104 }
1105}
1106
1107macro_rules! impl_into_system_configs {
1108 ($($P:ident $S:ident),+) => {
1109 impl<$($P, $S),+> IntoSystemConfigs<($($P,)+)> for ($($S,)+)
1110 where
1111 $($S: IntoSystem<$P> + 'static,)+
1112 {
1113 fn into_configs(self, schedule: &mut Schedule) {
1114 #[allow(non_snake_case)]
1115 let ($($S,)+) = self;
1116 $(schedule.add_system($S.into_system());)+
1117 }
1118 }
1119 };
1120}
1121
1122impl_into_system_configs!(P1 S1, P2 S2);
1123impl_into_system_configs!(P1 S1, P2 S2, P3 S3);
1124impl_into_system_configs!(P1 S1, P2 S2, P3 S3, P4 S4);
1125impl_into_system_configs!(P1 S1, P2 S2, P3 S3, P4 S4, P5 S5);
1126impl_into_system_configs!(P1 S1, P2 S2, P3 S3, P4 S4, P5 S5, P6 S6);
1127impl_into_system_configs!(P1 S1, P2 S2, P3 S3, P4 S4, P5 S5, P6 S6, P7 S7);
1128impl_into_system_configs!(P1 S1, P2 S2, P3 S3, P4 S4, P5 S5, P6 S6, P7 S7, P8 S8);
1129
1130#[cfg(test)]
1131mod tests {
1132 use super::*;
1133 use std::sync::{Arc, Mutex};
1134
1135 struct CompA;
1137 struct CompB;
1138
1139 #[derive(Clone)]
1141 struct RunLog {
1142 log: Arc<Mutex<Vec<&'static str>>>,
1143 }
1144
1145 impl RunLog {
1146 fn new() -> Self {
1147 Self {
1148 log: Arc::new(Mutex::new(Vec::new())),
1149 }
1150 }
1151 fn push(&self, msg: &'static str) {
1152 self.log.lock().unwrap().push(msg);
1153 }
1154 fn get(&self) -> Vec<&'static str> {
1155 self.log.lock().unwrap().clone()
1156 }
1157 }
1158
1159 fn create_system(name: &'static str, log: RunLog) -> impl FnMut() + Send + Sync + 'static {
1161 move || {
1162 log.push(name);
1163 }
1164 }
1165
1166 #[test]
1167 fn test_schedule_access_info_compatibility() {
1168 let mut info1 = AccessInfo::new();
1169 info1.component_reads.push(TypeId::of::<CompA>());
1170
1171 let mut info2 = AccessInfo::new();
1172 info2.component_reads.push(TypeId::of::<CompA>());
1173
1174 assert!(info1.is_compatible_with(&info2));
1176
1177 let mut info3 = AccessInfo::new();
1178 info3.component_writes.push(TypeId::of::<CompA>());
1179
1180 assert!(!info1.is_compatible_with(&info3));
1182
1183 let mut info4 = AccessInfo::new();
1185 info4.component_writes.push(TypeId::of::<CompA>());
1186 assert!(!info3.is_compatible_with(&info4));
1187 }
1188
1189 #[test]
1190 fn test_schedule_dag_batching_independent() {
1191 let mut schedule = Schedule::new();
1192 let log = RunLog::new();
1193
1194 schedule.add_di_system(create_system("sys1", log.clone()));
1196 schedule.add_di_system(create_system("sys2", log.clone()));
1197 schedule.add_di_system(create_system("sys3", log.clone()));
1198
1199 schedule.build();
1200
1201 assert_eq!(schedule.legacy_batches.len(), 1);
1203 assert_eq!(schedule.legacy_batches[0].systems.len(), 3);
1204 }
1205
1206 struct PhysicsSet;
1207 impl SystemSet for PhysicsSet {}
1208
1209 #[test]
1210 fn test_system_set_configuration() {
1211 let mut schedule = Schedule::new();
1212 let log = RunLog::new();
1213
1214 schedule.add_di_system(
1215 create_system("sys_a", log.clone()).in_set::<PhysicsSet>()
1216 );
1217 schedule.add_di_system(
1218 create_system("sys_b", log.clone()).after_set::<PhysicsSet>()
1219 );
1220
1221 schedule.configure_set(SetConfig::new::<PhysicsSet>());
1222
1223 schedule.build();
1224
1225 assert_eq!(schedule.legacy_batches.len(), 2);
1226 }
1227
1228 #[test]
1229 fn test_schedule_dag_batching_with_conflicts() {
1230 let mut schedule = Schedule::new();
1231 let log = RunLog::new();
1232
1233 schedule.add_di_system(create_system("sys1", log.clone()).writes::<CompA>());
1235 schedule.add_di_system(create_system("sys2", log.clone()).reads::<CompA>());
1237 schedule.add_di_system(create_system("sys3", log.clone()).writes::<CompB>());
1239 schedule.add_di_system(create_system("sys4", log.clone()).writes::<CompA>());
1241
1242 schedule.build();
1243
1244 assert_eq!(schedule.legacy_batches.len(), 3);
1249 assert_eq!(schedule.legacy_batches[0].systems.len(), 1);
1250 assert_eq!(schedule.legacy_batches[1].systems.len(), 2);
1251 assert_eq!(schedule.legacy_batches[2].systems.len(), 1);
1252 }
1253
1254 #[test]
1255 fn test_schedule_explicit_ordering_before_after() {
1256 let mut schedule = Schedule::new();
1257 let log = RunLog::new();
1258
1259 schedule.add_di_system(
1261 create_system("sys1", log.clone())
1262 .label("System1")
1263 .after("System2"),
1264 );
1265
1266 schedule.add_di_system(create_system("sys2", log.clone()).label("System2"));
1267
1268 schedule.add_di_system(
1270 create_system("sys3", log.clone())
1271 .label("System3")
1272 .before("System2"),
1273 );
1274
1275 schedule.build();
1276
1277 assert_eq!(schedule.legacy_batches.len(), 3);
1280
1281 let mut world = World::new();
1282 schedule.run(&mut world, 0.1);
1283
1284 let result = log.get();
1285 assert_eq!(result, vec!["sys3", "sys2", "sys1"]);
1286 }
1287
1288 #[test]
1289 #[should_panic(expected = "Cyclic dependency detected!")]
1290 fn test_schedule_cyclic_dependency_panics() {
1291 let mut schedule = Schedule::new();
1292 let log = RunLog::new();
1293
1294 schedule.add_di_system(create_system("sysA", log.clone()).label("A").before("B"));
1295
1296 schedule.add_di_system(create_system("sysB", log.clone()).label("B").before("C"));
1297
1298 schedule.add_di_system(
1299 create_system("sysC", log.clone()).label("C").before("A"), );
1301
1302 schedule.build();
1304 }
1305
1306 #[test]
1307 fn test_schedule_phase_ordering() {
1308 let mut schedule = Schedule::new();
1309 let log = RunLog::new();
1310
1311 schedule.add_di_system(create_system("render_sys", log.clone()).in_phase(Phase::Render));
1314 schedule.add_di_system(create_system("physics_sys", log.clone()).in_phase(Phase::Physics));
1315 schedule
1316 .add_di_system(create_system("pre_update_sys", log.clone()).in_phase(Phase::PreUpdate));
1317
1318 schedule.build();
1319
1320 assert!(schedule.uses_phases);
1322 assert_eq!(schedule.phase_batches.len(), 3);
1324 assert_eq!(schedule.phase_batches[0].0, Phase::PreUpdate);
1326 assert_eq!(schedule.phase_batches[1].0, Phase::Physics);
1327 assert_eq!(schedule.phase_batches[2].0, Phase::Render);
1328
1329 let mut world = World::new();
1330 schedule.run(&mut world, 0.016);
1331
1332 let result = log.get();
1334 assert_eq!(result, vec!["pre_update_sys", "physics_sys", "render_sys"]);
1335 }
1336
1337 #[test]
1338 fn test_schedule_phase_with_intra_phase_batching() {
1339 let mut schedule = Schedule::new();
1340 let log = RunLog::new();
1341
1342 schedule.add_di_system(
1344 create_system("phys1", log.clone())
1345 .in_phase(Phase::Physics)
1346 .writes::<CompA>(),
1347 );
1348 schedule.add_di_system(
1349 create_system("phys2", log.clone())
1350 .in_phase(Phase::Physics)
1351 .reads::<CompA>(),
1352 );
1353 schedule.add_di_system(create_system("update_sys", log.clone()).in_phase(Phase::Update));
1355
1356 schedule.build();
1357
1358 assert!(schedule.uses_phases);
1359 assert_eq!(schedule.phase_batches.len(), 2);
1361 assert_eq!(schedule.phase_batches[0].0, Phase::Update);
1362 assert_eq!(schedule.phase_batches[1].0, Phase::Physics);
1363
1364 assert_eq!(schedule.phase_batches[1].1.len(), 2);
1366
1367 assert_eq!(schedule.total_batch_count(), 3);
1369 }
1370
1371 #[test]
1372 fn write_write_conflict() {
1373 let mut a = AccessInfo::new();
1374 a.component_writes.push(TypeId::of::<CompA>());
1375 let mut b = AccessInfo::new();
1376 b.component_writes.push(TypeId::of::<CompA>());
1377 assert!(!a.is_compatible_with(&b));
1378 }
1379
1380 #[test]
1381 fn read_write_conflict() {
1382 let mut a = AccessInfo::new();
1383 a.component_reads.push(TypeId::of::<CompA>());
1384 let mut b = AccessInfo::new();
1385 b.component_writes.push(TypeId::of::<CompA>());
1386 assert!(!a.is_compatible_with(&b));
1387 }
1388
1389 #[test]
1390 fn read_read_no_conflict() {
1391 let mut a = AccessInfo::new();
1392 a.component_reads.push(TypeId::of::<CompA>());
1393 let mut b = AccessInfo::new();
1394 b.component_reads.push(TypeId::of::<CompA>());
1395 assert!(a.is_compatible_with(&b));
1396 }
1397
1398 #[test]
1399 fn different_types_no_conflict() {
1400 let mut a = AccessInfo::new();
1401 a.component_writes.push(TypeId::of::<CompA>());
1402 let mut b = AccessInfo::new();
1403 b.component_writes.push(TypeId::of::<CompB>());
1404 assert!(a.is_compatible_with(&b));
1405 }
1406}