intuicio_framework_ecs/
systems.rs

1use crate::{
2    bundle::Bundle,
3    entity::Entity,
4    prelude::Res,
5    universe::{Universe, UniverseCondition, UniverseFetch},
6    world::{World, WorldError},
7    Component,
8};
9use intuicio_core::{
10    context::Context,
11    function::{FunctionHandle, FunctionQuery},
12    registry::Registry,
13    types::TypeQuery,
14};
15use intuicio_data::managed::{DynamicManaged, DynamicManagedRef};
16use std::{
17    borrow::Cow,
18    error::Error,
19    ops::{Deref, DerefMut},
20    sync::RwLock,
21};
22
23pub struct SystemContext<'a> {
24    pub universe: &'a Universe,
25    entity: Entity,
26}
27
28impl<'a> SystemContext<'a> {
29    pub fn new(universe: &'a Universe, entity: Entity) -> Self {
30        Self { universe, entity }
31    }
32
33    pub fn new_unknown(universe: &'a Universe) -> Self {
34        Self {
35            universe,
36            entity: Default::default(),
37        }
38    }
39
40    pub fn entity(&self) -> Entity {
41        self.entity
42    }
43
44    pub fn fetch<Fetch: UniverseFetch<'a>>(&'a self) -> Result<Fetch::Value, Box<dyn Error>> {
45        Fetch::fetch(self.universe, self.entity)
46    }
47}
48
49impl Clone for SystemContext<'_> {
50    fn clone(&self) -> Self {
51        *self
52    }
53}
54
55impl Copy for SystemContext<'_> {}
56
57pub trait System: Component {
58    fn run(&self, context: SystemContext) -> Result<(), Box<dyn Error>>;
59
60    fn should_run(&self, context: SystemContext) -> bool {
61        context
62            .universe
63            .systems
64            .component::<true, SystemRunCondition>(context.entity)
65            .map(|condition| condition.evaluate(context))
66            .unwrap_or(true)
67    }
68
69    fn try_run(&self, context: SystemContext) -> Result<(), Box<dyn Error>> {
70        if self.should_run(context) {
71            self.run(context)
72        } else {
73            Ok(())
74        }
75    }
76}
77
78impl<T: Fn(SystemContext) -> Result<(), Box<dyn Error>> + Component> System for T {
79    fn run(&self, context: SystemContext) -> Result<(), Box<dyn Error>> {
80        (self)(context)
81    }
82}
83
84pub struct ScriptedFunctionSystem<const LOCKING: bool> {
85    run: FunctionHandle,
86}
87
88impl<const LOCKING: bool> ScriptedFunctionSystem<LOCKING> {
89    pub fn new(run: FunctionHandle) -> Self {
90        Self { run }
91    }
92}
93
94impl<const LOCKING: bool> System for ScriptedFunctionSystem<LOCKING> {
95    fn run(&self, context: SystemContext) -> Result<(), Box<dyn Error>> {
96        let (registry, mut ctx) =
97            context.fetch::<(Res<LOCKING, &Registry>, Res<LOCKING, &mut Context>)>()?;
98        let entity = DynamicManaged::new(context.entity()).map_err::<Box<dyn Error>, _>(|_| {
99            "Could not make managed object out of entity!".into()
100        })?;
101        let (universe, _) = DynamicManagedRef::make(context.universe);
102        ctx.stack().push(entity);
103        ctx.stack().push(universe);
104        self.run.invoke(&mut ctx, &registry);
105        Ok(())
106    }
107}
108
109enum ScriptedObjectFunction {
110    Name(Cow<'static, str>),
111    Handle(FunctionHandle),
112}
113
114pub struct ScriptedObjectSystem<const LOCKING: bool> {
115    object: DynamicManaged,
116    function: RwLock<ScriptedObjectFunction>,
117}
118
119impl<const LOCKING: bool> ScriptedObjectSystem<LOCKING> {
120    pub fn new(object: DynamicManaged) -> Self {
121        Self {
122            object,
123            function: RwLock::new(ScriptedObjectFunction::Name("run".into())),
124        }
125    }
126
127    pub fn new_custom(object: DynamicManaged, name: Cow<'static, str>) -> Self {
128        Self {
129            object,
130            function: RwLock::new(ScriptedObjectFunction::Name(name)),
131        }
132    }
133}
134
135impl<const LOCKING: bool> System for ScriptedObjectSystem<LOCKING> {
136    fn run(&self, context: SystemContext) -> Result<(), Box<dyn Error>> {
137        let (registry, mut ctx) =
138            context.fetch::<(Res<LOCKING, &Registry>, Res<LOCKING, &mut Context>)>()?;
139        let mut function = self.function.write().map_err::<Box<dyn Error>, _>(|_| {
140            "Could not get write access to scripted object function!".into()
141        })?;
142        if let ScriptedObjectFunction::Name(name) = &*function {
143            *function = ScriptedObjectFunction::Handle(
144                registry
145                    .find_function(FunctionQuery {
146                        name: Some(name.clone()),
147                        type_query: Some(TypeQuery {
148                            type_hash: Some(*self.object.type_hash()),
149                            ..Default::default()
150                        }),
151                        ..Default::default()
152                    })
153                    .ok_or_else::<Box<dyn Error>, _>(|| {
154                        "Could not find type of scripted object!".into()
155                    })?,
156            );
157        }
158        if let ScriptedObjectFunction::Handle(function) = &*function {
159            let entity =
160                DynamicManaged::new(context.entity()).map_err::<Box<dyn Error>, _>(|_| {
161                    "Could not make managed object out of entity!".into()
162                })?;
163            let (universe, _) = DynamicManagedRef::make(context.universe);
164            let this = self
165                .object
166                .borrow()
167                .ok_or_else::<Box<dyn Error>, _>(|| "Could not borrow scripted object!".into())?;
168            ctx.stack().push(entity);
169            ctx.stack().push(universe);
170            ctx.stack().push(this);
171            function.invoke(&mut ctx, &registry);
172            Ok(())
173        } else {
174            Err("Scripted object function is not resolved into a handle!".into())
175        }
176    }
177}
178
179pub struct SystemObject(Box<dyn System>);
180
181impl SystemObject {
182    pub fn new(system: impl System) -> Self {
183        Self(Box::new(system))
184    }
185
186    pub fn should_run(&self, context: SystemContext) -> bool {
187        self.0.should_run(context)
188    }
189
190    pub fn run(&self, context: SystemContext) -> Result<(), Box<dyn Error>> {
191        self.0.run(context)
192    }
193
194    pub fn try_run(&self, context: SystemContext) -> Result<(), Box<dyn Error>> {
195        self.0.try_run(context)
196    }
197}
198
199pub struct SystemRunCondition(Box<dyn Fn(SystemContext) -> bool + Send + Sync>);
200
201impl SystemRunCondition {
202    pub fn new<T: UniverseCondition>() -> Self {
203        Self(Box::new(|context| T::evaluate(context)))
204    }
205
206    pub fn evaluate(&self, context: SystemContext) -> bool {
207        (self.0)(context)
208    }
209}
210
211#[derive(Default)]
212pub struct Systems {
213    world: World,
214}
215
216impl Deref for Systems {
217    type Target = World;
218
219    fn deref(&self) -> &Self::Target {
220        &self.world
221    }
222}
223
224impl DerefMut for Systems {
225    fn deref_mut(&mut self) -> &mut Self::Target {
226        &mut self.world
227    }
228}
229
230impl Systems {
231    pub fn add(
232        &mut self,
233        system: impl System,
234        locals: impl Bundle,
235    ) -> Result<Entity, Box<dyn Error>> {
236        let result = self.world.spawn((SystemObject::new(system),))?;
237        WorldError::allow(
238            self.world.insert(result, locals),
239            [WorldError::EmptyColumnSet],
240            (),
241        )?;
242        Ok(result)
243    }
244
245    pub fn add_locals(
246        &mut self,
247        entity: Entity,
248        bundle: impl Bundle,
249    ) -> Result<(), Box<dyn Error>> {
250        WorldError::allow(
251            self.world.insert(entity, bundle),
252            [WorldError::EmptyColumnSet],
253            (),
254        )?;
255        Ok(())
256    }
257
258    pub fn run<const LOCKING: bool>(
259        &self,
260        universe: &Universe,
261        entity: Entity,
262    ) -> Result<(), Box<dyn Error>> {
263        self.world
264            .component::<LOCKING, SystemObject>(entity)?
265            .run(SystemContext::new(universe, entity))
266    }
267
268    pub fn try_run<const LOCKING: bool>(
269        &self,
270        universe: &Universe,
271        entity: Entity,
272    ) -> Result<(), Box<dyn Error>> {
273        self.world
274            .component::<LOCKING, SystemObject>(entity)?
275            .try_run(SystemContext::new(universe, entity))
276    }
277
278    pub fn run_one_shot<const LOCKING: bool>(
279        universe: &Universe,
280        system: impl System,
281    ) -> Result<(), Box<dyn Error>> {
282        system.run(SystemContext::new(universe, Default::default()))
283    }
284
285    pub fn try_run_one_shot<const LOCKING: bool>(
286        universe: &Universe,
287        system: impl System,
288    ) -> Result<(), Box<dyn Error>> {
289        system.try_run(SystemContext::new(universe, Default::default()))
290    }
291}