Skip to main content

anput/
systems.rs

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