Skip to main content

gizmo_core/
system.rs

1use crate::world::World;
2use std::any::TypeId;
3use std::collections::HashSet;
4
5// ==============================================================
6// ACCESS INFO (DAG DEPENDENCY GRAPH)
7// ==============================================================
8
9#[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// ==============================================================
55// PHASE (SYSTEM SET GROUPING)
56// ==============================================================
57
58/// Fizik motoru tarzı faz sıralaması.
59/// Sistemler bir faza atanır ve fazlar sabit sırada çalışır:
60/// `PreUpdate → Update → Physics → PostUpdate → Render`
61///
62/// Aynı faz içindeki sistemler DAG batching ile paralel çalıştırılır.
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
64pub enum Phase {
65    /// Input polling, zaman güncellemesi, olay temizliği
66    PreUpdate = 0,
67    /// Oyun mantığı, AI, scripting
68    #[default]
69    Update = 1,
70    /// Fizik simülasyonu (fixed timestep ile)
71    Physics = 2,
72    /// Transform propagation, cleanup
73    PostUpdate = 3,
74    /// Rendering hazırlığı
75    Render = 4,
76}
77
78impl Phase {
79    /// Tüm fazları sıralı olarak döndürür.
80    pub const ALL: [Phase; 5] = [
81        Phase::PreUpdate,
82        Phase::Update,
83        Phase::Physics,
84        Phase::PostUpdate,
85        Phase::Render,
86    ];
87
88    /// Faz adını döndürür (tracing span'ları için).
89    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
100// ==============================================================
101// SYSTEM TRAIT
102// ==============================================================
103
104/// Bir sistem: her frame'de çalıştırılabilir mantık birimi.
105pub trait System: Send + Sync {
106    fn run(&mut self, world: &World, dt: f32);
107    fn access_info(&self) -> AccessInfo;
108}
109
110// ==============================================================
111// DEPENDENCY INJECTION SİSTEMİ
112// ==============================================================
113
114use 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
214// ==============================================================
215// INTO SYSTEM — FONKSİYONLARDAN SİSTEME DÖNÜŞÜM (MAKRO İLE)
216// ==============================================================
217
218pub trait IntoSystem<Params> {
219    fn into_system(self) -> Box<dyn System>;
220}
221
222// 0 Parametre
223impl<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
241// Box<dyn System> dönüşümü
242impl 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
293/// 1-8 parametreli IntoSystem implementasyonlarını üretir.
294macro_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
357// Func returning &World and using f32 but acts as an Exclusive Barrier!
358impl<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    // Opaque functions act as a full barrier to prevent unsafe overlaps
366    fn access_info(&self) -> AccessInfo {
367        let mut info = AccessInfo::new();
368        info.is_exclusive = true;
369        info
370    }
371}
372
373// ==============================================================
374// RUN CONDITIONS
375// ==============================================================
376
377pub 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
497// ==============================================================
498// SYSTEM CONFIG — LABEL / BEFORE / AFTER / READS / WRITES
499// ==============================================================
500
501pub 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
700// ==============================================================
701// SCHEDULE — DAG BATCHING & MULTITHREADING
702// ==============================================================
703
704pub 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    /// Her faz için ayrı batch listesi. Fazlar sıralı çalışır, faz içi batch'ler paralel.
804    phase_batches: Vec<(Phase, Vec<SystemBatch>)>,
805    /// Geriye dönük uyumluluk: faz kullanılmadığında eski düz batch listesi.
806    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    /// Tek bir faz grubuna ait config'leri DAG-batch'le.
860    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        // Reverse adjacency
945        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        // DAG Batching (optimal greedy)
953        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        // Apply SetConfigs to systems
998        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        // Herhangi bir config varsayılan olmayan Phase kullanıyor mu?
1011        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            // Fazlara göre grupla
1016            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            // Her faz grubu için bağımsız DAG batch oluştur
1022            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            // Geriye uyumlu: tek düz batch listesi
1030            self.legacy_batches = Self::build_batches_for(configs);
1031        }
1032    }
1033
1034    /// Batch listesini çalıştırır (faz-içi veya legacy).
1035    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            // Flush deferred entity mutations between batches.
1044            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            // Fazları sırasıyla çalıştır: PreUpdate → Update → Physics → PostUpdate → Render
1062            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            // Legacy mod: düz batch listesi
1068            Self::run_batches(&mut self.legacy_batches, world, dt);
1069        }
1070
1071        // Frame profiling verisini kaydet (ring buffer'a yaz)
1072        if let Some(mut profiler) = world.get_resource_mut::<crate::profiler::FrameProfiler>() {
1073            profiler.end_frame();
1074        }
1075    }
1076
1077    /// Toplam batch sayısı (debug / test amaçlı)
1078    #[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    // --- Mock Bileşen ve Kaynaklar ---
1136    struct CompA;
1137    struct CompB;
1138
1139    // Testlerin çalışma sırasını takip etmek için kullanacağımız log
1140    #[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    // Basit bir test sistemi oluşturucu
1160    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        // İki sistem de sadece OKUYOR, birbiriyle uyumlu (parallel çalışabilir)
1175        assert!(info1.is_compatible_with(&info2));
1176
1177        let mut info3 = AccessInfo::new();
1178        info3.component_writes.push(TypeId::of::<CompA>());
1179
1180        // Biri okuyor diğeri YAZIYOR, uyumsuz (farklı batch'lerde olmalı)
1181        assert!(!info1.is_compatible_with(&info3));
1182
1183        // İkisi de YAZIYOR, uyumsuz
1184        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        // 3 bağımsız sistem, read/write çakışması yok. Tek bir batch içinde çalışmalı.
1195        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        // Hepsi aynı anda paralel çalışabileceği için 1 adet batch oluşmalı
1202        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        // sys1: CompA yazıyor
1234        schedule.add_di_system(create_system("sys1", log.clone()).writes::<CompA>());
1235        // sys2: CompA okuyor (sys1 ile çakışır, ayrı batch'e gitmeli)
1236        schedule.add_di_system(create_system("sys2", log.clone()).reads::<CompA>());
1237        // sys3: CompB yazıyor (hiçbiriyle çakışmaz, sys1 ile aynı batch'e girebilir)
1238        schedule.add_di_system(create_system("sys3", log.clone()).writes::<CompB>());
1239        // sys4: CompA yazıyor (sys1 ve sys2 ile çakışır, en sona kalmalı)
1240        schedule.add_di_system(create_system("sys4", log.clone()).writes::<CompA>());
1241
1242        schedule.build();
1243
1244        // Beklenen Batch'ler (Greedy Backward Scan):
1245        // Batch 0: sys1 (writes CompA)
1246        // Batch 1: sys2 (reads CompA), sys3 (writes CompB)
1247        // Batch 2: sys4 (writes CompA)
1248        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        // sys1 "after" sys2 olarak işaretlendi
1260        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        // sys3 "before" sys2 olarak işaretlendi
1269        schedule.add_di_system(
1270            create_system("sys3", log.clone())
1271                .label("System3")
1272                .before("System2"),
1273        );
1274
1275        schedule.build();
1276
1277        // Bağımsız olsalar bile (okuma/yazma çakışması olmasa dahi) explicit order yüzünden:
1278        // Sıralama: sys3 -> sys2 -> sys1 olmalı ve farklı batch'lerde olmalılar
1279        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"), // Cycle: A -> B -> C -> A
1300        );
1301
1302        // Bu çağrı panic atmalı
1303        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        // 3 sistem farklı fazlara atanmış — veri çakışması yok ama
1312        // faz sıralaması garanti edilmeli: PreUpdate → Physics → Render
1313        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        // Phase modunda olmalı
1321        assert!(schedule.uses_phases);
1322        // 3 faz grubu oluşmalı
1323        assert_eq!(schedule.phase_batches.len(), 3);
1324        // Sıralama: PreUpdate(0) < Physics(2) < Render(4)
1325        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        // Çalışma sırası deterministik olmalı
1333        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        // Physics fazında 2 çakışan sistem + 1 bağımsız sistem
1343        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        // Update fazında 1 bağımsız sistem
1354        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        // 2 faz grubu: Update ve Physics
1360        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        // Physics fazı 2 batch'e ayrılmalı (writes/reads çakışması)
1365        assert_eq!(schedule.phase_batches[1].1.len(), 2);
1366
1367        // Toplam batch sayısı: Update(1) + Physics(2) = 3
1368        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}