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