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