intuicio_framework_ecs/
universe.rs

1use crate::{
2    bundle::Bundle,
3    commands::CommandBuffer,
4    entity::Entity,
5    processor::WorldProcessor,
6    query::{Lookup, Query, TypedLookupFetch, TypedQueryFetch},
7    resources::Resources,
8    systems::{System, SystemContext, Systems},
9    world::World,
10    Component, ComponentRef, ComponentRefMut,
11};
12use intuicio_core::{context::Context, registry::Registry};
13use intuicio_data::type_hash::TypeHash;
14use intuicio_framework_serde::SerializationRegistry;
15use std::{
16    collections::{HashMap, HashSet},
17    error::Error,
18    marker::PhantomData,
19    sync::Mutex,
20};
21
22pub trait UniverseFetch<'a> {
23    type Value;
24
25    fn fetch(universe: &'a Universe, system: Entity) -> Result<Self::Value, Box<dyn Error>>;
26}
27
28impl<'a> UniverseFetch<'a> for &'a World {
29    type Value = &'a World;
30
31    fn fetch(universe: &'a Universe, _: Entity) -> Result<Self::Value, Box<dyn Error>> {
32        Ok(&universe.simulation)
33    }
34}
35
36pub struct Res<const LOCKING: bool, T>(PhantomData<fn() -> T>);
37
38impl<'a, const LOCKING: bool, T: Component> UniverseFetch<'a> for Res<LOCKING, &'a T> {
39    type Value = ComponentRef<'a, LOCKING, T>;
40
41    fn fetch(universe: &'a Universe, _: Entity) -> Result<Self::Value, Box<dyn Error>> {
42        universe.resources.get()
43    }
44}
45
46impl<'a, const LOCKING: bool, T: Component> UniverseFetch<'a> for Res<LOCKING, &'a mut T> {
47    type Value = ComponentRefMut<'a, LOCKING, T>;
48
49    fn fetch(universe: &'a Universe, _: Entity) -> Result<Self::Value, Box<dyn Error>> {
50        universe.resources.get_mut()
51    }
52}
53
54pub struct Local<const LOCKING: bool, T>(PhantomData<fn() -> T>);
55
56impl<'a, const LOCKING: bool, T: Component> UniverseFetch<'a> for Local<LOCKING, &'a T> {
57    type Value = ComponentRef<'a, LOCKING, T>;
58
59    fn fetch(universe: &'a Universe, system: Entity) -> Result<Self::Value, Box<dyn Error>> {
60        Ok(universe.systems.component(system)?)
61    }
62}
63
64impl<'a, const LOCKING: bool, T: Component> UniverseFetch<'a> for Local<LOCKING, &'a mut T> {
65    type Value = ComponentRefMut<'a, LOCKING, T>;
66
67    fn fetch(universe: &'a Universe, system: Entity) -> Result<Self::Value, Box<dyn Error>> {
68        Ok(universe.systems.component_mut(system)?)
69    }
70}
71
72impl<'a, const LOCKING: bool, Fetch: TypedQueryFetch<'a, LOCKING>> UniverseFetch<'a>
73    for Query<'a, LOCKING, Fetch>
74{
75    type Value = Query<'a, LOCKING, Fetch>;
76
77    fn fetch(_: &Universe, _: Entity) -> Result<Self::Value, Box<dyn Error>> {
78        Ok(Query::<LOCKING, Fetch>::default())
79    }
80}
81
82impl<'a, const LOCKING: bool, Fetch: TypedLookupFetch<'a, LOCKING>> UniverseFetch<'a>
83    for Lookup<'a, LOCKING, Fetch>
84{
85    type Value = Lookup<'a, LOCKING, Fetch>;
86
87    fn fetch(_: &Universe, _: Entity) -> Result<Self::Value, Box<dyn Error>> {
88        Ok(Lookup::<LOCKING, Fetch>::default())
89    }
90}
91
92macro_rules! impl_universe_fetch_tuple {
93    ($($type:ident),+) => {
94        impl<'a, $($type: UniverseFetch<'a>),+> UniverseFetch<'a> for ($($type,)+) {
95            type Value = ($($type::Value,)+);
96
97            fn fetch(universe: &'a Universe, entity: Entity) -> Result<Self::Value, Box<dyn Error>> {
98                Ok(($($type::fetch(universe, entity)?,)+))
99            }
100        }
101    };
102}
103
104impl_universe_fetch_tuple!(A);
105impl_universe_fetch_tuple!(A, B);
106impl_universe_fetch_tuple!(A, B, C);
107impl_universe_fetch_tuple!(A, B, C, D);
108impl_universe_fetch_tuple!(A, B, C, D, E);
109impl_universe_fetch_tuple!(A, B, C, D, E, F);
110impl_universe_fetch_tuple!(A, B, C, D, E, F, G);
111impl_universe_fetch_tuple!(A, B, C, D, E, F, G, H);
112impl_universe_fetch_tuple!(A, B, C, D, E, F, G, H, I);
113impl_universe_fetch_tuple!(A, B, C, D, E, F, G, H, I, J);
114impl_universe_fetch_tuple!(A, B, C, D, E, F, G, H, I, J, K);
115impl_universe_fetch_tuple!(A, B, C, D, E, F, G, H, I, J, K, L);
116impl_universe_fetch_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M);
117impl_universe_fetch_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
118impl_universe_fetch_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
119impl_universe_fetch_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
120
121pub trait UniverseCondition {
122    fn evaluate(context: SystemContext) -> bool;
123}
124
125pub struct ResourceDidChanged<T: Component>(PhantomData<fn() -> T>);
126
127impl<T: Component> UniverseCondition for ResourceDidChanged<T> {
128    fn evaluate(context: SystemContext) -> bool {
129        context.universe.resources.did_changed::<T>()
130    }
131}
132
133pub struct ResourceAdded<T: Component>(PhantomData<fn() -> T>);
134
135impl<T: Component> UniverseCondition for ResourceAdded<T> {
136    fn evaluate(context: SystemContext) -> bool {
137        context.universe.resources.added().has_component::<T>()
138    }
139}
140
141pub struct ResourceRemoved<T: Component>(PhantomData<fn() -> T>);
142
143impl<T: Component> UniverseCondition for ResourceRemoved<T> {
144    fn evaluate(context: SystemContext) -> bool {
145        context.universe.resources.removed().has_component::<T>()
146    }
147}
148
149pub struct ResourceUpdated<T: Component>(PhantomData<fn() -> T>);
150
151impl<T: Component> UniverseCondition for ResourceUpdated<T> {
152    fn evaluate(context: SystemContext) -> bool {
153        context
154            .universe
155            .resources
156            .updated()
157            .map(|changes| changes.has_component::<T>())
158            .unwrap_or_default()
159    }
160}
161
162pub struct ComponentDidChanged<T: Component>(PhantomData<fn() -> T>);
163
164impl<T: Component> UniverseCondition for ComponentDidChanged<T> {
165    fn evaluate(context: SystemContext) -> bool {
166        context.universe.simulation.component_did_changed::<T>()
167    }
168}
169
170pub struct ComponentAdded<T: Component>(PhantomData<fn() -> T>);
171
172impl<T: Component> UniverseCondition for ComponentAdded<T> {
173    fn evaluate(context: SystemContext) -> bool {
174        context.universe.simulation.added().has_component::<T>()
175    }
176}
177
178pub struct ComponentRemoved<T: Component>(PhantomData<fn() -> T>);
179
180impl<T: Component> UniverseCondition for ComponentRemoved<T> {
181    fn evaluate(context: SystemContext) -> bool {
182        context.universe.simulation.removed().has_component::<T>()
183    }
184}
185
186pub struct ComponentUpdated<T: Component>(PhantomData<fn() -> T>);
187
188impl<T: Component> UniverseCondition for ComponentUpdated<T> {
189    fn evaluate(context: SystemContext) -> bool {
190        context
191            .universe
192            .simulation
193            .updated()
194            .map(|changes| changes.has_component::<T>())
195            .unwrap_or_default()
196    }
197}
198
199pub struct SystemLocalDidChanged<T: Component>(PhantomData<fn() -> T>);
200
201impl<T: Component> UniverseCondition for SystemLocalDidChanged<T> {
202    fn evaluate(context: SystemContext) -> bool {
203        context
204            .universe
205            .systems
206            .entity_component_did_changed::<T>(context.entity())
207    }
208}
209
210pub struct SystemLocalAdded<T: Component>(PhantomData<fn() -> T>);
211
212impl<T: Component> UniverseCondition for SystemLocalAdded<T> {
213    fn evaluate(context: SystemContext) -> bool {
214        context
215            .universe
216            .systems
217            .added()
218            .has_entity_component::<T>(context.entity())
219    }
220}
221
222pub struct SystemLocalRemoved<T: Component>(PhantomData<fn() -> T>);
223
224impl<T: Component> UniverseCondition for SystemLocalRemoved<T> {
225    fn evaluate(context: SystemContext) -> bool {
226        context
227            .universe
228            .systems
229            .removed()
230            .has_entity_component::<T>(context.entity())
231    }
232}
233
234pub struct SystemLocalUpdated<T: Component>(PhantomData<fn() -> T>);
235
236impl<T: Component> UniverseCondition for SystemLocalUpdated<T> {
237    fn evaluate(context: SystemContext) -> bool {
238        context
239            .universe
240            .systems
241            .updated()
242            .map(|changes| changes.has_entity_component::<T>(context.entity()))
243            .unwrap_or_default()
244    }
245}
246
247macro_rules! impl_universe_condition_tuple {
248    ($($type:ident),+) => {
249        impl<$($type: UniverseCondition),+> UniverseCondition for ($($type,)+) {
250            fn evaluate(context: SystemContext) -> bool {
251                $($type::evaluate(context))&&+
252            }
253        }
254    };
255}
256
257impl_universe_condition_tuple!(A);
258impl_universe_condition_tuple!(A, B);
259impl_universe_condition_tuple!(A, B, C);
260impl_universe_condition_tuple!(A, B, C, D);
261impl_universe_condition_tuple!(A, B, C, D, E);
262impl_universe_condition_tuple!(A, B, C, D, E, F);
263impl_universe_condition_tuple!(A, B, C, D, E, F, G);
264impl_universe_condition_tuple!(A, B, C, D, E, F, G, H);
265impl_universe_condition_tuple!(A, B, C, D, E, F, G, H, I);
266impl_universe_condition_tuple!(A, B, C, D, E, F, G, H, I, J);
267impl_universe_condition_tuple!(A, B, C, D, E, F, G, H, I, J, K);
268impl_universe_condition_tuple!(A, B, C, D, E, F, G, H, I, J, K, L);
269impl_universe_condition_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M);
270impl_universe_condition_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
271impl_universe_condition_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
272impl_universe_condition_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
273
274#[derive(Default)]
275pub struct Universe {
276    pub simulation: World,
277    pub systems: Systems,
278    pub resources: Resources,
279    plugins: HashMap<TypeHash, Box<dyn Plugin>>,
280    plugins_to_register: Mutex<HashMap<TypeHash, Box<dyn Plugin>>>,
281    plugins_to_unregister: Mutex<HashSet<TypeHash>>,
282}
283
284impl Drop for Universe {
285    fn drop(&mut self) {
286        self.clear_plugins();
287    }
288}
289
290impl Universe {
291    pub fn new(simulation: World) -> Self {
292        Self {
293            simulation,
294            resources: Default::default(),
295            systems: Default::default(),
296            plugins: Default::default(),
297            plugins_to_register: Default::default(),
298            plugins_to_unregister: Default::default(),
299        }
300    }
301
302    pub fn with_basics(self, stack_capacity: usize, registers_capacity: usize) -> Self {
303        struct BasicsPlugin;
304        self.with_plugin(
305            QuickPlugin::<BasicsPlugin>::default()
306                .resource(CommandBuffer::default())
307                .resource(Registry::default())
308                .resource(Context::new(stack_capacity, registers_capacity))
309                .resource(WorldProcessor::default())
310                .resource(SerializationRegistry::default()),
311        )
312    }
313
314    pub fn clear_changes(&mut self) {
315        self.simulation.clear_changes();
316        self.resources.clear_changes();
317        self.systems.clear_changes();
318    }
319
320    pub fn execute_commands<const LOCKING: bool>(&mut self) {
321        for commands in self.resources.query::<LOCKING, &mut CommandBuffer>() {
322            commands.execute(&mut self.simulation);
323        }
324        for commands in self.systems.query::<LOCKING, &mut CommandBuffer>() {
325            commands.execute(&mut self.simulation);
326        }
327    }
328
329    pub fn with_plugin<T: Plugin + 'static>(mut self, plugin: T) -> Self {
330        self.add_plugin(plugin);
331        self.maintain_plugins();
332        self
333    }
334
335    pub fn add_plugin<T: Plugin + 'static>(&self, plugin: T) {
336        if let Ok(mut to_register) = self.plugins_to_register.lock() {
337            to_register.insert(TypeHash::of::<T>(), Box::new(plugin));
338        }
339    }
340
341    pub fn remove_plugin<T: Plugin + 'static>(&self) {
342        if let Ok(mut to_unregister) = self.plugins_to_unregister.lock() {
343            to_unregister.insert(TypeHash::of::<T>());
344        }
345    }
346
347    pub fn remove_plugin_raw(&self, type_hash: TypeHash) {
348        if let Ok(mut to_unregister) = self.plugins_to_unregister.lock() {
349            to_unregister.insert(type_hash);
350        }
351    }
352
353    pub fn clear_plugins(&mut self) {
354        for (_, mut plugin) in std::mem::take(&mut self.plugins) {
355            plugin.on_unregister(&mut self.simulation, &mut self.systems, &mut self.resources);
356        }
357    }
358
359    pub fn maintain_plugins(&mut self) {
360        if let Ok(mut to_unregister) = self.plugins_to_unregister.try_lock() {
361            if !to_unregister.is_empty() {
362                for type_hash in to_unregister.drain() {
363                    if let Some(mut plugin) = self.plugins.remove(&type_hash) {
364                        plugin.on_unregister(
365                            &mut self.simulation,
366                            &mut self.systems,
367                            &mut self.resources,
368                        );
369                    }
370                }
371            }
372        }
373        if let Ok(mut to_register) = self.plugins_to_register.try_lock() {
374            if !to_register.is_empty() {
375                for (type_hash, mut plugin) in std::mem::take(&mut *to_register).drain() {
376                    if let Some(mut plugin) = self.plugins.remove(&type_hash) {
377                        plugin.on_unregister(
378                            &mut self.simulation,
379                            &mut self.systems,
380                            &mut self.resources,
381                        );
382                    }
383                    if plugin
384                        .dependencies()
385                        .into_iter()
386                        .all(|type_hash| self.plugins.contains_key(&type_hash))
387                    {
388                        plugin.on_register(
389                            &mut self.simulation,
390                            &mut self.systems,
391                            &mut self.resources,
392                        );
393                        self.plugins.insert(type_hash, plugin);
394                    } else {
395                        to_register.insert(type_hash, plugin);
396                    }
397                }
398            }
399        }
400    }
401}
402
403pub trait Plugin: Send + Sync {
404    fn on_register(
405        &mut self,
406        simulation: &mut World,
407        systems: &mut Systems,
408        resources: &mut Resources,
409    );
410    fn on_unregister(
411        &mut self,
412        simulation: &mut World,
413        systems: &mut Systems,
414        resources: &mut Resources,
415    );
416
417    fn dependencies(&self) -> Vec<TypeHash> {
418        vec![]
419    }
420}
421
422#[derive(Default)]
423pub struct PluginsPackage {
424    plugins: Vec<Box<dyn Plugin>>,
425}
426
427impl PluginsPackage {
428    pub fn plugin(mut self, plugin: impl Plugin + 'static) -> Self {
429        self.plugins.push(Box::new(plugin));
430        self
431    }
432}
433
434impl Plugin for PluginsPackage {
435    fn on_register(
436        &mut self,
437        simulation: &mut World,
438        systems: &mut Systems,
439        resources: &mut Resources,
440    ) {
441        for plugin in &mut self.plugins {
442            plugin.on_register(simulation, systems, resources);
443        }
444    }
445
446    fn on_unregister(
447        &mut self,
448        simulation: &mut World,
449        systems: &mut Systems,
450        resources: &mut Resources,
451    ) {
452        for plugin in &mut self.plugins {
453            plugin.on_unregister(simulation, systems, resources);
454        }
455    }
456}
457
458pub struct QuickPlugin<Tag: Send + Sync> {
459    #[allow(clippy::type_complexity)]
460    simulation_register:
461        Vec<Box<dyn FnOnce(&mut World) -> Box<dyn FnOnce(&mut World) + Send + Sync> + Send + Sync>>,
462    #[allow(clippy::type_complexity)]
463    simulation_unregister: Vec<Box<dyn FnOnce(&mut World) + Send + Sync>>,
464    #[allow(clippy::type_complexity)]
465    systems_register: Vec<
466        Box<dyn FnOnce(&mut Systems) -> Box<dyn FnOnce(&mut Systems) + Send + Sync> + Send + Sync>,
467    >,
468    #[allow(clippy::type_complexity)]
469    systems_unregister: Vec<Box<dyn FnOnce(&mut Systems) + Send + Sync>>,
470    #[allow(clippy::type_complexity)]
471    resources_register: Vec<
472        Box<
473            dyn FnOnce(&mut Resources) -> Box<dyn FnOnce(&mut Resources) + Send + Sync>
474                + Send
475                + Sync,
476        >,
477    >,
478    #[allow(clippy::type_complexity)]
479    resources_unregister: Vec<Box<dyn FnOnce(&mut Resources) + Send + Sync>>,
480    _phantom: PhantomData<fn() -> Tag>,
481}
482
483impl<Tag: Send + Sync> Default for QuickPlugin<Tag> {
484    fn default() -> Self {
485        Self {
486            simulation_register: Default::default(),
487            simulation_unregister: Default::default(),
488            systems_register: Default::default(),
489            systems_unregister: Default::default(),
490            resources_register: Default::default(),
491            resources_unregister: Default::default(),
492            _phantom: Default::default(),
493        }
494    }
495}
496
497impl<Tag: Send + Sync> QuickPlugin<Tag> {
498    pub fn entity(mut self, bundle: impl Bundle + Send + Sync + 'static) -> Self {
499        self.simulation_register.push(Box::new(|world| {
500            let entity = world.spawn(bundle).unwrap();
501            Box::new(move |world| {
502                let _ = world.despawn(entity);
503            })
504        }));
505        self
506    }
507
508    pub fn entity_relation<const LOCKING: bool, R: Component, E: Component + PartialEq>(
509        mut self,
510        from: E,
511        payload: R,
512        to: E,
513    ) -> Self {
514        self.simulation_register.push(Box::new(move |world| {
515            let from = world.find_by::<LOCKING, E>(&from).unwrap();
516            let to = world.find_by::<LOCKING, E>(&to).unwrap();
517            world.relate::<LOCKING, R>(payload, from, to).unwrap();
518            Box::new(move |world| {
519                let _ = world.unrelate::<LOCKING, R>(from, to);
520            })
521        }));
522        self
523    }
524
525    pub fn system(
526        mut self,
527        system: impl System,
528        locals: impl Bundle + Send + Sync + 'static,
529    ) -> Self {
530        self.systems_register.push(Box::new(|systems| {
531            let entity = systems.add(system, locals).unwrap();
532            Box::new(move |systems| {
533                let _ = systems.despawn(entity);
534            })
535        }));
536        self
537    }
538
539    pub fn system_meta(mut self, locals: impl Bundle + Send + Sync + 'static) -> Self {
540        self.systems_register.push(Box::new(|systems| {
541            let entity = systems.spawn(locals).unwrap();
542            Box::new(move |systems| {
543                let _ = systems.despawn(entity);
544            })
545        }));
546        self
547    }
548
549    pub fn system_relation<const LOCKING: bool, R: Component, E: Component + PartialEq>(
550        mut self,
551        from: E,
552        payload: R,
553        to: E,
554    ) -> Self {
555        self.systems_register.push(Box::new(move |systems| {
556            let from = systems.find_by::<LOCKING, E>(&from).unwrap();
557            let to = systems.find_by::<LOCKING, E>(&to).unwrap();
558            systems.relate::<LOCKING, R>(payload, from, to).unwrap();
559            Box::new(move |systems| {
560                let _ = systems.unrelate::<LOCKING, R>(from, to);
561            })
562        }));
563        self
564    }
565
566    pub fn resource<T: Component>(mut self, resource: T) -> Self {
567        self.resources_register.push(Box::new(|resources| {
568            resources.add((resource,)).unwrap();
569            Box::new(|resources| {
570                let _ = resources.remove::<(T,)>();
571            })
572        }));
573        self
574    }
575}
576
577impl<T: Send + Sync> Plugin for QuickPlugin<T> {
578    fn on_register(
579        &mut self,
580        simulation: &mut World,
581        systems: &mut Systems,
582        resources: &mut Resources,
583    ) {
584        for execute in self.simulation_register.drain(..) {
585            self.simulation_unregister.push(execute(simulation));
586        }
587        for execute in self.systems_register.drain(..) {
588            self.systems_unregister.push(execute(systems));
589        }
590        for execute in self.resources_register.drain(..) {
591            self.resources_unregister.push(execute(resources));
592        }
593    }
594
595    fn on_unregister(
596        &mut self,
597        simulation: &mut World,
598        systems: &mut Systems,
599        resources: &mut Resources,
600    ) {
601        for execute in self.simulation_unregister.drain(..) {
602            execute(simulation);
603        }
604        for execute in self.systems_unregister.drain(..) {
605            execute(systems);
606        }
607        for execute in self.resources_unregister.drain(..) {
608            execute(resources);
609        }
610    }
611}