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, ®istry);
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, ®istry);
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}