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/// 1-8 parametreli IntoSystem implementasyonlarını üretir.
242macro_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
305// Func returning &World and using f32 but acts as an Exclusive Barrier!
306impl<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    // Opaque functions act as a full barrier to prevent unsafe overlaps
314    fn access_info(&self) -> AccessInfo {
315        let mut info = AccessInfo::new();
316        info.is_exclusive = true;
317        info
318    }
319}
320
321// ==============================================================
322// RUN CONDITIONS
323// ==============================================================
324
325pub 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
341// ==============================================================
342// SYSTEM CONFIG — LABEL / BEFORE / AFTER / READS / WRITES
343// ==============================================================
344
345pub 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
497// ==============================================================
498// SCHEDULE — DAG BATCHING & MULTITHREADING
499// ==============================================================
500
501pub 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    /// Her faz için ayrı batch listesi. Fazlar sıralı çalışır, faz içi batch'ler paralel.
570    phase_batches: Vec<(Phase, Vec<SystemBatch>)>,
571    /// Geriye dönük uyumluluk: faz kullanılmadığında eski düz batch listesi.
572    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    /// Tek bir faz grubuna ait config'leri DAG-batch'le.
616    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        // Reverse adjacency
701        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        // DAG Batching (optimal greedy)
709        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        // Herhangi bir config varsayılan olmayan Phase kullanıyor mu?
754        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            // Fazlara göre grupla
759            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            // Her faz grubu için bağımsız DAG batch oluştur
765            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            // Geriye uyumlu: tek düz batch listesi
773            self.legacy_batches = Self::build_batches_for(configs);
774        }
775    }
776
777    /// Batch listesini çalıştırır (faz-içi veya legacy).
778    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            // Flush deferred entity mutations between batches.
787            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            // Fazları sırasıyla çalıştır: PreUpdate → Update → Physics → PostUpdate → Render
805            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            // Legacy mod: düz batch listesi
811            Self::run_batches(&mut self.legacy_batches, world, dt);
812        }
813
814        // Frame profiling verisini kaydet (ring buffer'a yaz)
815        if let Some(mut profiler) = world.get_resource_mut::<crate::profiler::FrameProfiler>() {
816            profiler.end_frame();
817        }
818    }
819
820    /// Toplam batch sayısı (debug / test amaçlı)
821    #[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    // --- Mock Bileşen ve Kaynaklar ---
843    struct CompA;
844    struct CompB;
845
846    // Testlerin çalışma sırasını takip etmek için kullanacağımız log
847    #[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    // Basit bir test sistemi oluşturucu
867    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        // İki sistem de sadece OKUYOR, birbiriyle uyumlu (parallel çalışabilir)
882        assert!(info1.is_compatible_with(&info2));
883
884        let mut info3 = AccessInfo::new();
885        info3.component_writes.push(TypeId::of::<CompA>());
886
887        // Biri okuyor diğeri YAZIYOR, uyumsuz (farklı batch'lerde olmalı)
888        assert!(!info1.is_compatible_with(&info3));
889
890        // İkisi de YAZIYOR, uyumsuz
891        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        // 3 bağımsız sistem, read/write çakışması yok. Tek bir batch içinde çalışmalı.
902        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        // Hepsi aynı anda paralel çalışabileceği için 1 adet batch oluşmalı
909        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        // sys1: CompA yazıyor
919        schedule.add_di_system(create_system("sys1", log.clone()).writes::<CompA>());
920        // sys2: CompA okuyor (sys1 ile çakışır, ayrı batch'e gitmeli)
921        schedule.add_di_system(create_system("sys2", log.clone()).reads::<CompA>());
922        // sys3: CompB yazıyor (hiçbiriyle çakışmaz, sys1 ile aynı batch'e girebilir)
923        schedule.add_di_system(create_system("sys3", log.clone()).writes::<CompB>());
924        // sys4: CompA yazıyor (sys1 ve sys2 ile çakışır, en sona kalmalı)
925        schedule.add_di_system(create_system("sys4", log.clone()).writes::<CompA>());
926
927        schedule.build();
928
929        // Beklenen Batch'ler (Greedy Backward Scan):
930        // Batch 0: sys1 (writes CompA)
931        // Batch 1: sys2 (reads CompA), sys3 (writes CompB)
932        // Batch 2: sys4 (writes CompA)
933        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        // sys1 "after" sys2 olarak işaretlendi
945        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        // sys3 "before" sys2 olarak işaretlendi
954        schedule.add_di_system(
955            create_system("sys3", log.clone())
956                .label("System3")
957                .before("System2"),
958        );
959
960        schedule.build();
961
962        // Bağımsız olsalar bile (okuma/yazma çakışması olmasa dahi) explicit order yüzünden:
963        // Sıralama: sys3 -> sys2 -> sys1 olmalı ve farklı batch'lerde olmalılar
964        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"), // Cycle: A -> B -> C -> A
985        );
986
987        // Bu çağrı panic atmalı
988        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        // 3 sistem farklı fazlara atanmış — veri çakışması yok ama
997        // faz sıralaması garanti edilmeli: PreUpdate → Physics → Render
998        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        // Phase modunda olmalı
1006        assert!(schedule.uses_phases);
1007        // 3 faz grubu oluşmalı
1008        assert_eq!(schedule.phase_batches.len(), 3);
1009        // Sıralama: PreUpdate(0) < Physics(2) < Render(4)
1010        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        // Çalışma sırası deterministik olmalı
1018        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        // Physics fazında 2 çakışan sistem + 1 bağımsız sistem
1028        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        // Update fazında 1 bağımsız sistem
1039        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        // 2 faz grubu: Update ve Physics
1045        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        // Physics fazı 2 batch'e ayrılmalı (writes/reads çakışması)
1050        assert_eq!(schedule.phase_batches[1].1.len(), 2);
1051
1052        // Toplam batch sayısı: Update(1) + Physics(2) = 3
1053        assert_eq!(schedule.total_batch_count(), 3);
1054    }
1055
1056    #[test]
1057    fn write_write_conflict() {
1058        let mut a = AccessInfo::new();
1059        a.component_writes.push(TypeId::of::<CompA>());
1060        let mut b = AccessInfo::new();
1061        b.component_writes.push(TypeId::of::<CompA>());
1062        assert!(!a.is_compatible_with(&b));
1063    }
1064
1065    #[test]
1066    fn read_write_conflict() {
1067        let mut a = AccessInfo::new();
1068        a.component_reads.push(TypeId::of::<CompA>());
1069        let mut b = AccessInfo::new();
1070        b.component_writes.push(TypeId::of::<CompA>());
1071        assert!(!a.is_compatible_with(&b));
1072    }
1073
1074    #[test]
1075    fn read_read_no_conflict() {
1076        let mut a = AccessInfo::new();
1077        a.component_reads.push(TypeId::of::<CompA>());
1078        let mut b = AccessInfo::new();
1079        b.component_reads.push(TypeId::of::<CompA>());
1080        assert!(a.is_compatible_with(&b));
1081    }
1082
1083    #[test]
1084    fn different_types_no_conflict() {
1085        let mut a = AccessInfo::new();
1086        a.component_writes.push(TypeId::of::<CompA>());
1087        let mut b = AccessInfo::new();
1088        b.component_writes.push(TypeId::of::<CompB>());
1089        assert!(a.is_compatible_with(&b));
1090    }
1091}