1use crate::{
4 bindings::{ThreadWorldContainer, WorldContainer, WorldGuard},
5 error::{InteropError, ScriptError},
6 script::{Script, ScriptId},
7 IntoScriptPluginParams,
8};
9use bevy::ecs::{entity::Entity, system::Resource};
10use std::{collections::HashMap, sync::atomic::AtomicU32};
11
12pub trait Context: 'static + Send + Sync {}
14impl<T: 'static + Send + Sync> Context for T {}
15
16pub type ContextId = u32;
18
19#[derive(Resource)]
21pub struct ScriptContexts<P: IntoScriptPluginParams> {
22 pub contexts: HashMap<ContextId, P::C>,
24}
25
26impl<P: IntoScriptPluginParams> Default for ScriptContexts<P> {
27 fn default() -> Self {
28 Self {
29 contexts: Default::default(),
30 }
31 }
32}
33
34static CONTEXT_ID_COUNTER: AtomicU32 = AtomicU32::new(0);
35impl<P: IntoScriptPluginParams> ScriptContexts<P> {
36 pub fn insert(&mut self, ctxt: P::C) -> ContextId {
38 let id = CONTEXT_ID_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
39 self.contexts.insert(id, ctxt);
40 id
41 }
42
43 pub fn insert_with_id(&mut self, id: ContextId, ctxt: P::C) -> Option<P::C> {
45 self.contexts.insert(id, ctxt)
46 }
47
48 pub fn allocate_id(&self) -> ContextId {
50 CONTEXT_ID_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
51 }
52
53 pub fn remove(&mut self, id: ContextId) -> Option<P::C> {
55 self.contexts.remove(&id)
56 }
57
58 pub fn get(&self, id: ContextId) -> Option<&P::C> {
60 self.contexts.get(&id)
61 }
62
63 pub fn get_mut(&mut self, id: ContextId) -> Option<&mut P::C> {
65 self.contexts.get_mut(&id)
66 }
67
68 pub fn contains(&self, id: ContextId) -> bool {
70 self.contexts.contains_key(&id)
71 }
72}
73
74pub type ContextInitializer<P> =
76 fn(&str, &mut <P as IntoScriptPluginParams>::C) -> Result<(), ScriptError>;
77
78pub type ContextPreHandlingInitializer<P> =
80 fn(&str, Entity, &mut <P as IntoScriptPluginParams>::C) -> Result<(), ScriptError>;
81
82#[derive(Resource)]
84pub struct ContextLoadingSettings<P: IntoScriptPluginParams> {
85 pub loader: ContextBuilder<P>,
87 pub assigner: ContextAssigner<P>,
89 pub context_initializers: Vec<ContextInitializer<P>>,
91 pub context_pre_handling_initializers: Vec<ContextPreHandlingInitializer<P>>,
93}
94
95impl<P: IntoScriptPluginParams> Default for ContextLoadingSettings<P> {
96 fn default() -> Self {
97 Self {
98 loader: ContextBuilder::default(),
99 assigner: ContextAssigner::default(),
100 context_initializers: Default::default(),
101 context_pre_handling_initializers: Default::default(),
102 }
103 }
104}
105
106impl<T: IntoScriptPluginParams> Clone for ContextLoadingSettings<T> {
107 fn clone(&self) -> Self {
108 Self {
109 loader: self.loader.clone(),
110 assigner: self.assigner.clone(),
111 context_initializers: self.context_initializers.clone(),
112 context_pre_handling_initializers: self.context_pre_handling_initializers.clone(),
113 }
114 }
115}
116pub type ContextLoadFn<P> = fn(
118 script_id: &ScriptId,
119 content: &[u8],
120 context_initializers: &[ContextInitializer<P>],
121 pre_handling_initializers: &[ContextPreHandlingInitializer<P>],
122 runtime: &mut <P as IntoScriptPluginParams>::R,
123) -> Result<<P as IntoScriptPluginParams>::C, ScriptError>;
124
125pub type ContextReloadFn<P> = fn(
127 script_id: &ScriptId,
128 content: &[u8],
129 previous_context: &mut <P as IntoScriptPluginParams>::C,
130 context_initializers: &[ContextInitializer<P>],
131 pre_handling_initializers: &[ContextPreHandlingInitializer<P>],
132 runtime: &mut <P as IntoScriptPluginParams>::R,
133) -> Result<(), ScriptError>;
134
135pub struct ContextBuilder<P: IntoScriptPluginParams> {
137 pub load: ContextLoadFn<P>,
139 pub reload: ContextReloadFn<P>,
141}
142
143impl<P: IntoScriptPluginParams> Default for ContextBuilder<P> {
144 fn default() -> Self {
145 Self {
146 load: |_, _, _, _, _| Err(InteropError::invariant("no context loader set").into()),
147 reload: |_, _, _, _, _, _| {
148 Err(InteropError::invariant("no context reloader set").into())
149 },
150 }
151 }
152}
153
154impl<P: IntoScriptPluginParams> ContextBuilder<P> {
155 pub fn load(
157 loader: ContextLoadFn<P>,
158 script: &ScriptId,
159 content: &[u8],
160 context_initializers: &[ContextInitializer<P>],
161 pre_handling_initializers: &[ContextPreHandlingInitializer<P>],
162 world: WorldGuard,
163 runtime: &mut P::R,
164 ) -> Result<P::C, ScriptError> {
165 WorldGuard::with_existing_static_guard(world.clone(), |world| {
166 ThreadWorldContainer.set_world(world)?;
167 (loader)(
168 script,
169 content,
170 context_initializers,
171 pre_handling_initializers,
172 runtime,
173 )
174 })
175 }
176
177 pub fn reload(
179 reloader: ContextReloadFn<P>,
180 script: &ScriptId,
181 content: &[u8],
182 previous_context: &mut P::C,
183 context_initializers: &[ContextInitializer<P>],
184 pre_handling_initializers: &[ContextPreHandlingInitializer<P>],
185 world: WorldGuard,
186 runtime: &mut P::R,
187 ) -> Result<(), ScriptError> {
188 WorldGuard::with_existing_static_guard(world, |world| {
189 ThreadWorldContainer.set_world(world)?;
190 (reloader)(
191 script,
192 content,
193 previous_context,
194 context_initializers,
195 pre_handling_initializers,
196 runtime,
197 )
198 })
199 }
200}
201
202impl<P: IntoScriptPluginParams> Clone for ContextBuilder<P> {
203 fn clone(&self) -> Self {
204 Self {
205 load: self.load,
206 reload: self.reload,
207 }
208 }
209}
210
211pub struct ContextAssigner<P: IntoScriptPluginParams> {
213 pub assign: fn(
223 script_id: &ScriptId,
224 new_content: &[u8],
225 contexts: &ScriptContexts<P>,
226 ) -> Option<ContextId>,
227
228 pub remove: fn(context_id: ContextId, script: &Script, contexts: &mut ScriptContexts<P>),
232}
233
234impl<P: IntoScriptPluginParams> ContextAssigner<P> {
235 pub fn new_global_context_assigner() -> Self {
238 Self {
239 assign: |_, _, _| Some(0), remove: |_, _, _| {}, }
242 }
243
244 pub fn new_individual_context_assigner() -> Self {
246 Self {
247 assign: |_, _, _| None,
248 remove: |id, _, c| _ = c.remove(id),
249 }
250 }
251}
252
253impl<P: IntoScriptPluginParams> Default for ContextAssigner<P> {
254 fn default() -> Self {
255 Self::new_individual_context_assigner()
256 }
257}
258
259impl<P: IntoScriptPluginParams> Clone for ContextAssigner<P> {
260 fn clone(&self) -> Self {
261 Self {
262 assign: self.assign,
263 remove: self.remove,
264 }
265 }
266}
267
268#[cfg(test)]
269mod tests {
270 use crate::asset::Language;
271
272 use super::*;
273
274 struct DummyParams;
275 impl IntoScriptPluginParams for DummyParams {
276 type C = String;
277 type R = ();
278
279 const LANGUAGE: Language = Language::Lua;
280
281 fn build_runtime() -> Self::R {}
282 }
283
284 #[test]
285 fn test_script_contexts_insert_get() {
286 let mut contexts: ScriptContexts<DummyParams> = ScriptContexts::default();
287 let id = contexts.insert("context1".to_string());
288 assert_eq!(contexts.contexts.get(&id), Some(&"context1".to_string()));
289 assert_eq!(
290 contexts.contexts.get_mut(&id),
291 Some(&mut "context1".to_string())
292 );
293 }
294
295 #[test]
296 fn test_script_contexts_allocate_id() {
297 let contexts: ScriptContexts<DummyParams> = ScriptContexts::default();
298 let id = contexts.allocate_id();
299 let next_id = contexts.allocate_id();
300 assert_eq!(next_id, id + 1);
301 }
302
303 #[test]
304 fn test_script_contexts_remove() {
305 let mut contexts: ScriptContexts<DummyParams> = ScriptContexts::default();
306 let id = contexts.insert("context1".to_string());
307 let removed = contexts.remove(id);
308 assert_eq!(removed, Some("context1".to_string()));
309 assert!(!contexts.contexts.contains_key(&id));
310
311 let next_id = contexts.allocate_id();
313 assert_eq!(next_id, id + 1);
314 }
315}