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
241macro_rules! impl_into_system {
243 ($($P:ident),+) => {
244 #[allow(non_snake_case)]
245 impl<F, $($P),+> IntoSystem<($($P,)+)> for F
246 where
247 F: FnMut($($P::Item<'_>),+) + FnMut($($P),+) + Send + Sync + 'static,
248 $($P: SystemParam + 'static,)+
249 {
250 fn into_system(self) -> Box<dyn System> {
251 struct MultiParamSystem<F, $($P),+> {
252 func: F,
253 _marker: std::marker::PhantomData<fn() -> ($($P,)+)>,
254 }
255
256 impl<F, $($P),+> System for MultiParamSystem<F, $($P),+>
257 where
258 F: FnMut($($P::Item<'_>),+) + FnMut($($P),+) + Send + Sync + 'static,
259 $($P: SystemParam + 'static,)+
260 {
261 fn run(&mut self, world: &World, dt: f32) {
262 $(
263 let $P = match $P::fetch(world, dt) {
264 Ok(v) => v,
265 Err(e) => {
266 panic!(
267 "❌ 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",
268 param_type = std::any::type_name::<$P>(),
269 e = e
270 );
271 }
272 };
273 )+
274 (self.func)($($P),+);
275 }
276 fn access_info(&self) -> AccessInfo {
277 let mut info = AccessInfo::new();
278 $($P::get_access_info(&mut info);)+
279 info
280 }
281 }
282
283 Box::new(MultiParamSystem {
284 func: self,
285 _marker: std::marker::PhantomData,
286 })
287 }
288 }
289 };
290}
291
292impl_into_system!(P1);
293impl_into_system!(P1, P2);
294impl_into_system!(P1, P2, P3);
295impl_into_system!(P1, P2, P3, P4);
296impl_into_system!(P1, P2, P3, P4, P5);
297impl_into_system!(P1, P2, P3, P4, P5, P6);
298impl_into_system!(P1, P2, P3, P4, P5, P6, P7);
299impl_into_system!(P1, P2, P3, P4, P5, P6, P7, P8);
300impl_into_system!(P1, P2, P3, P4, P5, P6, P7, P8, P9);
301impl_into_system!(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10);
302impl_into_system!(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11);
303impl_into_system!(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12);
304
305impl<F> System for F
307where
308 F: FnMut(&World, f32) + Send + Sync + 'static,
309{
310 fn run(&mut self, world: &World, dt: f32) {
311 (self)(world, dt);
312 }
313 fn access_info(&self) -> AccessInfo {
315 let mut info = AccessInfo::new();
316 info.is_exclusive = true;
317 info
318 }
319}
320
321pub struct ConditionalSystem {
326 inner: Box<dyn System>,
327 condition: Box<dyn FnMut(&World) -> bool + Send + Sync>,
328}
329
330impl System for ConditionalSystem {
331 fn run(&mut self, world: &World, dt: f32) {
332 if (self.condition)(world) {
333 self.inner.run(world, dt);
334 }
335 }
336 fn access_info(&self) -> AccessInfo {
337 self.inner.access_info()
338 }
339}
340
341pub struct SystemConfig {
346 pub(crate) system: Box<dyn System>,
347 pub(crate) labels: Vec<&'static str>,
348 pub(crate) before: Vec<&'static str>,
349 pub(crate) after: Vec<&'static str>,
350 pub(crate) added_info: AccessInfo,
351 pub(crate) phase: Phase,
352}
353
354impl SystemConfig {
355 pub fn new(system: Box<dyn System>) -> Self {
356 Self {
357 system,
358 labels: Vec::new(),
359 before: Vec::new(),
360 after: Vec::new(),
361 added_info: AccessInfo::new(),
362 phase: Phase::default(),
363 }
364 }
365
366 pub fn label(mut self, label: &'static str) -> Self {
367 self.labels.push(label);
368 self
369 }
370
371 pub fn before(mut self, target: &'static str) -> Self {
372 self.before.push(target);
373 self
374 }
375
376 pub fn after(mut self, target: &'static str) -> Self {
377 self.after.push(target);
378 self
379 }
380
381 pub fn reads<T: 'static>(mut self) -> Self {
382 self.added_info.component_reads.push(TypeId::of::<T>());
383 self
384 }
385 pub fn writes<T: 'static>(mut self) -> Self {
386 self.added_info.component_writes.push(TypeId::of::<T>());
387 self
388 }
389 pub fn reads_res<T: 'static>(mut self) -> Self {
390 self.added_info.resource_reads.push(TypeId::of::<T>());
391 self
392 }
393 pub fn writes_res<T: 'static>(mut self) -> Self {
394 self.added_info.resource_writes.push(TypeId::of::<T>());
395 self
396 }
397 pub fn exclusive(mut self) -> Self {
398 self.added_info.is_exclusive = true;
399 self
400 }
401 pub fn in_phase(mut self, phase: Phase) -> Self {
402 self.phase = phase;
403 self
404 }
405
406 pub fn run_if<F>(mut self, condition: F) -> Self
407 where
408 F: FnMut(&World) -> bool + Send + Sync + 'static,
409 {
410 self.system = Box::new(ConditionalSystem {
411 inner: self.system,
412 condition: Box::new(condition),
413 });
414 self
415 }
416}
417
418pub trait IntoSystemConfig<Params> {
419 fn into_config(self) -> SystemConfig;
420
421 fn label(self, l: &'static str) -> SystemConfig
422 where
423 Self: Sized,
424 {
425 self.into_config().label(l)
426 }
427 fn before(self, target: &'static str) -> SystemConfig
428 where
429 Self: Sized,
430 {
431 self.into_config().before(target)
432 }
433 fn after(self, target: &'static str) -> SystemConfig
434 where
435 Self: Sized,
436 {
437 self.into_config().after(target)
438 }
439
440 fn reads<C: 'static>(self) -> SystemConfig
441 where
442 Self: Sized,
443 {
444 self.into_config().reads::<C>()
445 }
446 fn writes<C: 'static>(self) -> SystemConfig
447 where
448 Self: Sized,
449 {
450 self.into_config().writes::<C>()
451 }
452 fn reads_res<C: 'static>(self) -> SystemConfig
453 where
454 Self: Sized,
455 {
456 self.into_config().reads_res::<C>()
457 }
458 fn writes_res<C: 'static>(self) -> SystemConfig
459 where
460 Self: Sized,
461 {
462 self.into_config().writes_res::<C>()
463 }
464 fn exclusive(self) -> SystemConfig
465 where
466 Self: Sized,
467 {
468 self.into_config().exclusive()
469 }
470 fn in_phase(self, phase: Phase) -> SystemConfig
471 where
472 Self: Sized,
473 {
474 self.into_config().in_phase(phase)
475 }
476 fn run_if<F>(self, condition: F) -> SystemConfig
477 where
478 F: FnMut(&World) -> bool + Send + Sync + 'static,
479 Self: Sized,
480 {
481 self.into_config().run_if(condition)
482 }
483}
484
485impl<Params, T: IntoSystem<Params>> IntoSystemConfig<Params> for T {
486 fn into_config(self) -> SystemConfig {
487 SystemConfig::new(self.into_system())
488 }
489}
490
491impl IntoSystemConfig<()> for SystemConfig {
492 fn into_config(self) -> SystemConfig {
493 self
494 }
495}
496
497pub struct SystemBatch {
502 systems: Vec<Box<dyn System>>,
503 pub access_info: AccessInfo,
504}
505
506impl Default for SystemBatch {
507 fn default() -> Self {
508 Self::new()
509 }
510}
511
512impl SystemBatch {
513 pub fn new() -> Self {
514 Self {
515 systems: Vec::new(),
516 access_info: AccessInfo::new(),
517 }
518 }
519
520 pub fn add_system(&mut self, system: Box<dyn System>, config_info: AccessInfo) {
521 let mut sys_info = system.access_info();
522 sys_info.component_reads.extend(config_info.component_reads);
523 sys_info
524 .component_writes
525 .extend(config_info.component_writes);
526 sys_info.resource_reads.extend(config_info.resource_reads);
527 sys_info.resource_writes.extend(config_info.resource_writes);
528 sys_info.is_exclusive = sys_info.is_exclusive || config_info.is_exclusive;
529
530 self.access_info
531 .component_reads
532 .extend(sys_info.component_reads);
533 self.access_info
534 .component_writes
535 .extend(sys_info.component_writes);
536 self.access_info
537 .resource_reads
538 .extend(sys_info.resource_reads);
539 self.access_info
540 .resource_writes
541 .extend(sys_info.resource_writes);
542 self.access_info.is_exclusive = self.access_info.is_exclusive || sys_info.is_exclusive;
543
544 self.systems.push(system);
545 }
546
547 pub fn is_compatible(&self, system: &dyn System, config_info: &AccessInfo) -> bool {
548 let mut sys_info = system.access_info();
549 sys_info
550 .component_reads
551 .extend(config_info.component_reads.iter().cloned());
552 sys_info
553 .component_writes
554 .extend(config_info.component_writes.iter().cloned());
555 sys_info
556 .resource_reads
557 .extend(config_info.resource_reads.iter().cloned());
558 sys_info
559 .resource_writes
560 .extend(config_info.resource_writes.iter().cloned());
561 sys_info.is_exclusive = sys_info.is_exclusive || config_info.is_exclusive;
562
563 self.access_info.is_compatible_with(&sys_info)
564 }
565}
566
567pub struct Schedule {
568 unbuilt_configs: Vec<SystemConfig>,
569 phase_batches: Vec<(Phase, Vec<SystemBatch>)>,
571 legacy_batches: Vec<SystemBatch>,
573 uses_phases: bool,
574}
575
576impl Schedule {
577 pub fn new() -> Self {
578 Self {
579 unbuilt_configs: Vec::new(),
580 phase_batches: Vec::new(),
581 legacy_batches: Vec::new(),
582 uses_phases: false,
583 }
584 }
585
586 pub fn add_di_system<Params, S: IntoSystemConfig<Params>>(&mut self, system: S) {
587 self.unbuilt_configs.push(system.into_config());
588 self.invalidate();
589 }
590
591 pub fn add_system<S: System + 'static>(&mut self, system: S) {
592 self.unbuilt_configs
593 .push(SystemConfig::new(Box::new(system)));
594 self.invalidate();
595 }
596
597 pub fn add_system_boxed(&mut self, system: Box<dyn System>) {
598 self.unbuilt_configs.push(SystemConfig::new(system));
599 self.invalidate();
600 }
601
602 fn invalidate(&mut self) {
603 self.phase_batches.clear();
604 self.legacy_batches.clear();
605 }
606
607 fn is_built(&self) -> bool {
608 !self.phase_batches.is_empty() || !self.legacy_batches.is_empty()
609 }
610
611 pub fn validate(&mut self) {
612 self.build();
613 }
614
615 fn build_batches_for(configs: Vec<SystemConfig>) -> Vec<SystemBatch> {
617 let count = configs.len();
618 if count == 0 {
619 return Vec::new();
620 }
621
622 let mut edge_set: HashSet<(usize, usize)> = HashSet::new();
623 let mut adj = vec![Vec::new(); count];
624 let mut in_degree = vec![0usize; count];
625
626 let add_edge = |from: usize,
627 to: usize,
628 edge_set: &mut HashSet<(usize, usize)>,
629 adj: &mut Vec<Vec<usize>>,
630 in_degree: &mut Vec<usize>| {
631 if edge_set.insert((from, to)) {
632 adj[from].push(to);
633 in_degree[to] += 1;
634 }
635 };
636
637 for i in 0..count {
638 for before_label in &configs[i].before {
639 let mut found = false;
640 for (j, config_j) in configs.iter().enumerate() {
641 if i != j && config_j.labels.contains(before_label) {
642 add_edge(i, j, &mut edge_set, &mut adj, &mut in_degree);
643 found = true;
644 }
645 }
646 if !found {
647 crate::gizmo_log!(
648 Warning,
649 "[Schedule] Sistem {}'in before('{}') label'ı eşleşmiyor!",
650 i,
651 before_label
652 );
653 }
654 }
655 for after_label in &configs[i].after {
656 let mut found = false;
657 for (j, config_j) in configs.iter().enumerate() {
658 if i != j && config_j.labels.contains(after_label) {
659 add_edge(j, i, &mut edge_set, &mut adj, &mut in_degree);
660 found = true;
661 }
662 }
663 if !found {
664 crate::gizmo_log!(
665 Warning,
666 "[Schedule] Sistem {}'in after('{}') label'ı eşleşmiyor!",
667 i,
668 after_label
669 );
670 }
671 }
672 }
673
674 let mut queue = std::collections::VecDeque::new();
675 for (i, deg) in in_degree.iter().enumerate() {
676 if *deg == 0 {
677 queue.push_back(i);
678 }
679 }
680
681 let mut sorted_indices = Vec::with_capacity(count);
682 while let Some(node) = queue.pop_front() {
683 sorted_indices.push(node);
684 for &neighbor in &adj[node] {
685 in_degree[neighbor] -= 1;
686 if in_degree[neighbor] == 0 {
687 queue.push_back(neighbor);
688 }
689 }
690 }
691
692 if sorted_indices.len() != count {
693 panic!(
694 "Cyclic dependency detected! {} sistemin {} tanesi sıralanabildi.",
695 count,
696 sorted_indices.len()
697 );
698 }
699
700 let mut predecessors = vec![Vec::<usize>::new(); count];
702 for (from, neighbors) in adj.iter().enumerate() {
703 for &to in neighbors {
704 predecessors[to].push(from);
705 }
706 }
707
708 let mut dummy_configs: Vec<Option<SystemConfig>> = configs.into_iter().map(Some).collect();
710 let mut batches: Vec<SystemBatch> = Vec::new();
711 let mut system_batch = vec![0usize; count];
712
713 for &idx in &sorted_indices {
714 let config = dummy_configs[idx].take().unwrap();
715
716 let earliest = predecessors[idx]
717 .iter()
718 .map(|&pred| system_batch[pred] + 1)
719 .max()
720 .unwrap_or(0);
721
722 let placed = (earliest..batches.len())
723 .rev()
724 .find(|&bidx| batches[bidx].is_compatible(&*config.system, &config.added_info));
725
726 let batch_idx = if let Some(bidx) = placed {
727 batches[bidx].add_system(config.system, config.added_info);
728 bidx
729 } else {
730 let new_idx = batches.len();
731 let mut new_batch = SystemBatch::new();
732 new_batch.add_system(config.system, config.added_info);
733 batches.push(new_batch);
734 new_idx
735 };
736
737 system_batch[idx] = batch_idx;
738 }
739
740 batches
741 }
742
743 fn build(&mut self) {
744 if self.is_built() {
745 return;
746 }
747
748 let configs = std::mem::take(&mut self.unbuilt_configs);
749 if configs.is_empty() {
750 return;
751 }
752
753 let has_explicit_phase = configs.iter().any(|c| c.phase != Phase::Update);
755 self.uses_phases = has_explicit_phase;
756
757 if has_explicit_phase {
758 let mut phase_groups: std::collections::BTreeMap<Phase, Vec<SystemConfig>> =
760 std::collections::BTreeMap::new();
761 for config in configs {
762 phase_groups.entry(config.phase).or_default().push(config);
763 }
764 for (phase, group) in phase_groups {
766 let batches = Self::build_batches_for(group);
767 if !batches.is_empty() {
768 self.phase_batches.push((phase, batches));
769 }
770 }
771 } else {
772 self.legacy_batches = Self::build_batches_for(configs);
774 }
775 }
776
777 fn run_batches(batches: &mut [SystemBatch], world: &mut World, dt: f32) {
779 use rayon::prelude::*;
780
781 for batch in batches.iter_mut() {
782 batch.systems.par_iter_mut().for_each(|system| {
783 system.run(world, dt);
784 });
785
786 let queue_clone = world
788 .get_resource::<crate::commands::CommandQueue>()
789 .filter(|q| !q.is_empty())
790 .map(|q| (*q).clone());
791 if let Some(queue) = queue_clone {
792 queue.apply(world);
793 }
794 }
795 }
796
797 #[tracing::instrument(skip_all, name = "ecs_update")]
798 pub fn run(&mut self, world: &mut World, dt: f32) {
799 if !self.is_built() && !self.unbuilt_configs.is_empty() {
800 self.build();
801 }
802
803 if self.uses_phases {
804 for (_phase, batches) in &mut self.phase_batches {
806 let _span = tracing::info_span!("phase", name = _phase.name()).entered();
807 Self::run_batches(batches, world, dt);
808 }
809 } else {
810 Self::run_batches(&mut self.legacy_batches, world, dt);
812 }
813
814 if let Some(mut profiler) = world.get_resource_mut::<crate::profiler::FrameProfiler>() {
816 profiler.end_frame();
817 }
818 }
819
820 #[cfg(test)]
822 fn total_batch_count(&self) -> usize {
823 if self.uses_phases {
824 self.phase_batches.iter().map(|(_, b)| b.len()).sum()
825 } else {
826 self.legacy_batches.len()
827 }
828 }
829}
830
831impl Default for Schedule {
832 fn default() -> Self {
833 Self::new()
834 }
835}
836
837#[cfg(test)]
838mod tests {
839 use super::*;
840 use std::sync::{Arc, Mutex};
841
842 struct CompA;
844 struct CompB;
845
846 #[derive(Clone)]
848 struct RunLog {
849 log: Arc<Mutex<Vec<&'static str>>>,
850 }
851
852 impl RunLog {
853 fn new() -> Self {
854 Self {
855 log: Arc::new(Mutex::new(Vec::new())),
856 }
857 }
858 fn push(&self, msg: &'static str) {
859 self.log.lock().unwrap().push(msg);
860 }
861 fn get(&self) -> Vec<&'static str> {
862 self.log.lock().unwrap().clone()
863 }
864 }
865
866 fn create_system(name: &'static str, log: RunLog) -> impl FnMut() + Send + Sync + 'static {
868 move || {
869 log.push(name);
870 }
871 }
872
873 #[test]
874 fn test_schedule_access_info_compatibility() {
875 let mut info1 = AccessInfo::new();
876 info1.component_reads.push(TypeId::of::<CompA>());
877
878 let mut info2 = AccessInfo::new();
879 info2.component_reads.push(TypeId::of::<CompA>());
880
881 assert!(info1.is_compatible_with(&info2));
883
884 let mut info3 = AccessInfo::new();
885 info3.component_writes.push(TypeId::of::<CompA>());
886
887 assert!(!info1.is_compatible_with(&info3));
889
890 let mut info4 = AccessInfo::new();
892 info4.component_writes.push(TypeId::of::<CompA>());
893 assert!(!info3.is_compatible_with(&info4));
894 }
895
896 #[test]
897 fn test_schedule_dag_batching_independent() {
898 let mut schedule = Schedule::new();
899 let log = RunLog::new();
900
901 schedule.add_di_system(create_system("sys1", log.clone()));
903 schedule.add_di_system(create_system("sys2", log.clone()));
904 schedule.add_di_system(create_system("sys3", log.clone()));
905
906 schedule.build();
907
908 assert_eq!(schedule.legacy_batches.len(), 1);
910 assert_eq!(schedule.legacy_batches[0].systems.len(), 3);
911 }
912
913 #[test]
914 fn test_schedule_dag_batching_with_conflicts() {
915 let mut schedule = Schedule::new();
916 let log = RunLog::new();
917
918 schedule.add_di_system(create_system("sys1", log.clone()).writes::<CompA>());
920 schedule.add_di_system(create_system("sys2", log.clone()).reads::<CompA>());
922 schedule.add_di_system(create_system("sys3", log.clone()).writes::<CompB>());
924 schedule.add_di_system(create_system("sys4", log.clone()).writes::<CompA>());
926
927 schedule.build();
928
929 assert_eq!(schedule.legacy_batches.len(), 3);
934 assert_eq!(schedule.legacy_batches[0].systems.len(), 1);
935 assert_eq!(schedule.legacy_batches[1].systems.len(), 2);
936 assert_eq!(schedule.legacy_batches[2].systems.len(), 1);
937 }
938
939 #[test]
940 fn test_schedule_explicit_ordering_before_after() {
941 let mut schedule = Schedule::new();
942 let log = RunLog::new();
943
944 schedule.add_di_system(
946 create_system("sys1", log.clone())
947 .label("System1")
948 .after("System2"),
949 );
950
951 schedule.add_di_system(create_system("sys2", log.clone()).label("System2"));
952
953 schedule.add_di_system(
955 create_system("sys3", log.clone())
956 .label("System3")
957 .before("System2"),
958 );
959
960 schedule.build();
961
962 assert_eq!(schedule.legacy_batches.len(), 3);
965
966 let mut world = World::new();
967 schedule.run(&mut world, 0.1);
968
969 let result = log.get();
970 assert_eq!(result, vec!["sys3", "sys2", "sys1"]);
971 }
972
973 #[test]
974 #[should_panic(expected = "Cyclic dependency detected!")]
975 fn test_schedule_cyclic_dependency_panics() {
976 let mut schedule = Schedule::new();
977 let log = RunLog::new();
978
979 schedule.add_di_system(create_system("sysA", log.clone()).label("A").before("B"));
980
981 schedule.add_di_system(create_system("sysB", log.clone()).label("B").before("C"));
982
983 schedule.add_di_system(
984 create_system("sysC", log.clone()).label("C").before("A"), );
986
987 schedule.build();
989 }
990
991 #[test]
992 fn test_schedule_phase_ordering() {
993 let mut schedule = Schedule::new();
994 let log = RunLog::new();
995
996 schedule.add_di_system(create_system("render_sys", log.clone()).in_phase(Phase::Render));
999 schedule.add_di_system(create_system("physics_sys", log.clone()).in_phase(Phase::Physics));
1000 schedule
1001 .add_di_system(create_system("pre_update_sys", log.clone()).in_phase(Phase::PreUpdate));
1002
1003 schedule.build();
1004
1005 assert!(schedule.uses_phases);
1007 assert_eq!(schedule.phase_batches.len(), 3);
1009 assert_eq!(schedule.phase_batches[0].0, Phase::PreUpdate);
1011 assert_eq!(schedule.phase_batches[1].0, Phase::Physics);
1012 assert_eq!(schedule.phase_batches[2].0, Phase::Render);
1013
1014 let mut world = World::new();
1015 schedule.run(&mut world, 0.016);
1016
1017 let result = log.get();
1019 assert_eq!(result, vec!["pre_update_sys", "physics_sys", "render_sys"]);
1020 }
1021
1022 #[test]
1023 fn test_schedule_phase_with_intra_phase_batching() {
1024 let mut schedule = Schedule::new();
1025 let log = RunLog::new();
1026
1027 schedule.add_di_system(
1029 create_system("phys1", log.clone())
1030 .in_phase(Phase::Physics)
1031 .writes::<CompA>(),
1032 );
1033 schedule.add_di_system(
1034 create_system("phys2", log.clone())
1035 .in_phase(Phase::Physics)
1036 .reads::<CompA>(),
1037 );
1038 schedule.add_di_system(create_system("update_sys", log.clone()).in_phase(Phase::Update));
1040
1041 schedule.build();
1042
1043 assert!(schedule.uses_phases);
1044 assert_eq!(schedule.phase_batches.len(), 2);
1046 assert_eq!(schedule.phase_batches[0].0, Phase::Update);
1047 assert_eq!(schedule.phase_batches[1].0, Phase::Physics);
1048
1049 assert_eq!(schedule.phase_batches[1].1.len(), 2);
1051
1052 assert_eq!(schedule.total_batch_count(), 3);
1054 }
1055}