33use crate :: {
44 bindings:: { ThreadWorldContainer , WorldContainer , WorldGuard } ,
55 error:: { InteropError , ScriptError } ,
6- script:: { Script , ScriptId } ,
6+ script:: ScriptId ,
77 IntoScriptPluginParams ,
88} ;
99use bevy:: ecs:: { entity:: Entity , system:: Resource } ;
10- use std:: { collections:: HashMap , sync:: atomic:: AtomicU32 } ;
1110
1211/// A trait that all script contexts must implement.
13- pub trait Context : ' static + Send + Sync { }
14- impl < T : ' static + Send + Sync > Context for T { }
15-
16- /// The type of a context id
17- pub type ContextId = u32 ;
18-
19- /// Stores script state for a scripting plugin. Scripts are identified by their `ScriptId`, while contexts are identified by their `ContextId`.
20- #[ derive( Resource ) ]
21- pub struct ScriptContexts < P : IntoScriptPluginParams > {
22- /// The contexts of the scripts
23- pub contexts : HashMap < ContextId , P :: C > ,
24- }
25-
26- impl < P : IntoScriptPluginParams > Default for ScriptContexts < P > {
27- fn default ( ) -> Self {
28- Self {
29- contexts : Default :: default ( ) ,
30- }
31- }
32- }
33-
34- static CONTEXT_ID_COUNTER : AtomicU32 = AtomicU32 :: new ( 0 ) ;
35- impl < P : IntoScriptPluginParams > ScriptContexts < P > {
36- /// Allocates a new ContextId and inserts the context into the map
37- 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- /// Inserts a context with a specific id
44- pub fn insert_with_id ( & mut self , id : ContextId , ctxt : P :: C ) -> Option < P :: C > {
45- self . contexts . insert ( id, ctxt)
46- }
47-
48- /// Allocate new context id without inserting a context
49- pub fn allocate_id ( & self ) -> ContextId {
50- CONTEXT_ID_COUNTER . fetch_add ( 1 , std:: sync:: atomic:: Ordering :: Relaxed )
51- }
52-
53- /// Removes a context from the map
54- pub fn remove ( & mut self , id : ContextId ) -> Option < P :: C > {
55- self . contexts . remove ( & id)
56- }
57-
58- /// Get a reference to a context
59- pub fn get ( & self , id : ContextId ) -> Option < & P :: C > {
60- self . contexts . get ( & id)
61- }
62-
63- /// Get a mutable reference to a context
64- pub fn get_mut ( & mut self , id : ContextId ) -> Option < & mut P :: C > {
65- self . contexts . get_mut ( & id)
66- }
67-
68- /// Check if a context exists
69- pub fn contains ( & self , id : ContextId ) -> bool {
70- self . contexts . contains_key ( & id)
71- }
72- }
12+ ///
13+ /// Contexts are not required to be `Sync` as they are internally stored behind a `Mutex` but they must satisfy `Send` so they can be
14+ /// freely sent between threads.
15+ pub trait Context : ' static + Send { }
16+ impl < T : ' static + Send > Context for T { }
7317
7418/// Initializer run once after creating a context but before executing it for the first time as well as after re-loading the script
7519pub type ContextInitializer < P > =
@@ -85,7 +29,7 @@ pub struct ContextLoadingSettings<P: IntoScriptPluginParams> {
8529 /// Defines the strategy used to load and reload contexts
8630 pub loader : ContextBuilder < P > ,
8731 /// Defines the strategy used to assign contexts to scripts
88- pub assigner : ContextAssigner < P > ,
32+ pub assignment_strategy : ContextAssignmentStrategy ,
8933 /// Initializers run once after creating a context but before executing it for the first time
9034 pub context_initializers : Vec < ContextInitializer < P > > ,
9135 /// Initializers run every time before executing or loading a script
@@ -96,7 +40,7 @@ impl<P: IntoScriptPluginParams> Default for ContextLoadingSettings<P> {
9640 fn default ( ) -> Self {
9741 Self {
9842 loader : ContextBuilder :: default ( ) ,
99- assigner : ContextAssigner :: default ( ) ,
43+ assignment_strategy : Default :: default ( ) ,
10044 context_initializers : Default :: default ( ) ,
10145 context_pre_handling_initializers : Default :: default ( ) ,
10246 }
@@ -107,7 +51,7 @@ impl<T: IntoScriptPluginParams> Clone for ContextLoadingSettings<T> {
10751 fn clone ( & self ) -> Self {
10852 Self {
10953 loader : self . loader . clone ( ) ,
110- assigner : self . assigner . clone ( ) ,
54+ assignment_strategy : self . assignment_strategy ,
11155 context_initializers : self . context_initializers . clone ( ) ,
11256 context_pre_handling_initializers : self . context_pre_handling_initializers . clone ( ) ,
11357 }
@@ -119,7 +63,7 @@ pub type ContextLoadFn<P> = fn(
11963 content : & [ u8 ] ,
12064 context_initializers : & [ ContextInitializer < P > ] ,
12165 pre_handling_initializers : & [ ContextPreHandlingInitializer < P > ] ,
122- runtime : & mut <P as IntoScriptPluginParams >:: R ,
66+ runtime : & <P as IntoScriptPluginParams >:: R ,
12367) -> Result < <P as IntoScriptPluginParams >:: C , ScriptError > ;
12468
12569/// A strategy for reloading contexts
@@ -129,7 +73,7 @@ pub type ContextReloadFn<P> = fn(
12973 previous_context : & mut <P as IntoScriptPluginParams >:: C ,
13074 context_initializers : & [ ContextInitializer < P > ] ,
13175 pre_handling_initializers : & [ ContextPreHandlingInitializer < P > ] ,
132- runtime : & mut <P as IntoScriptPluginParams >:: R ,
76+ runtime : & <P as IntoScriptPluginParams >:: R ,
13377) -> Result < ( ) , ScriptError > ;
13478
13579/// A strategy for loading and reloading contexts
@@ -160,7 +104,7 @@ impl<P: IntoScriptPluginParams> ContextBuilder<P> {
160104 context_initializers : & [ ContextInitializer < P > ] ,
161105 pre_handling_initializers : & [ ContextPreHandlingInitializer < P > ] ,
162106 world : WorldGuard ,
163- runtime : & mut P :: R ,
107+ runtime : & P :: R ,
164108 ) -> Result < P :: C , ScriptError > {
165109 WorldGuard :: with_existing_static_guard ( world. clone ( ) , |world| {
166110 ThreadWorldContainer . set_world ( world) ?;
@@ -183,7 +127,7 @@ impl<P: IntoScriptPluginParams> ContextBuilder<P> {
183127 context_initializers : & [ ContextInitializer < P > ] ,
184128 pre_handling_initializers : & [ ContextPreHandlingInitializer < P > ] ,
185129 world : WorldGuard ,
186- runtime : & mut P :: R ,
130+ runtime : & P :: R ,
187131 ) -> Result < ( ) , ScriptError > {
188132 WorldGuard :: with_existing_static_guard ( world, |world| {
189133 ThreadWorldContainer . set_world ( world) ?;
@@ -208,108 +152,12 @@ impl<P: IntoScriptPluginParams> Clone for ContextBuilder<P> {
208152 }
209153}
210154
211- /// A strategy for assigning contexts to new and existing but re-loaded scripts as well as for managing old contexts
212- pub struct ContextAssigner < P : IntoScriptPluginParams > {
213- /// Assign a context to the script.
214- /// The assigner can either return `Some(id)` or `None`.
215- /// Returning None will request the processor to assign a new context id to assign to this script.
216- ///
217- /// Regardless, whether a script gets a new context id or not, the processor will check if the given context exists.
218- /// If it does not exist, it will create a new context and assign it to the script.
219- /// If it does exist, it will NOT create a new context, but assign the existing one to the script, and re-load the context.
220- ///
221- /// This function is only called once for each script, when it is loaded for the first time.
222- pub assign : fn (
223- script_id : & ScriptId ,
224- new_content : & [ u8 ] ,
225- contexts : & ScriptContexts < P > ,
226- ) -> Option < ContextId > ,
227-
228- /// Handle the removal of the script, if any clean up in contexts is necessary perform it here.
229- ///
230- /// If you do not clean up the context here, it will stay in the context map!
231- pub remove : fn ( context_id : ContextId , script : & Script , contexts : & mut ScriptContexts < P > ) ,
232- }
233-
234- impl < P : IntoScriptPluginParams > ContextAssigner < P > {
235- /// Create an assigner which re-uses a single global context for all scripts, only use if you know what you're doing.
236- /// Will not perform any clean up on removal.
237- pub fn new_global_context_assigner ( ) -> Self {
238- Self {
239- assign : |_, _, _| Some ( 0 ) , // always use the same id in rotation
240- remove : |_, _, _| { } , // do nothing
241- }
242- }
243-
244- /// Create an assigner which assigns a new context to each script. This is the default strategy.
245- pub fn new_individual_context_assigner ( ) -> Self {
246- Self {
247- assign : |_, _, _| None ,
248- remove : |id, _, c| _ = c. remove ( id) ,
249- }
250- }
251- }
252-
253- impl < P : IntoScriptPluginParams > Default for ContextAssigner < P > {
254- fn default ( ) -> Self {
255- Self :: new_individual_context_assigner ( )
256- }
257- }
258-
259- impl < 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) ]
269- mod 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- // assert next id is still incremented
312- let next_id = contexts. allocate_id ( ) ;
313- assert_eq ! ( next_id, id + 1 ) ;
314- }
155+ /// The strategy used in assigning contexts to scripts
156+ #[ derive( Default , Clone , Copy ) ]
157+ pub enum ContextAssignmentStrategy {
158+ /// Assign a new context to each script
159+ #[ default]
160+ Individual ,
161+ /// Share contexts with all other scripts
162+ Global ,
315163}
0 commit comments