Skip to content

Commit 8f663e9

Browse files
authored
refactor!: Merge ScriptContexts<T> into Scripts<T> + Remove Sync bound from Contexts (#350)
* feat!: Merge `ScriptContexts<T>` into `Scripts` and store type erased context pointers * parameterize scripts by the plugin type * improve logs
1 parent e40db41 commit 8f663e9

File tree

15 files changed

+363
-614
lines changed

15 files changed

+363
-614
lines changed

crates/bevy_mod_scripting_core/src/bindings/schedule.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ impl ScriptSystemBuilder {
334334
bevy::log::debug_once!("First call to script system {}", name);
335335
match handler_ctxt.call_dynamic_label(
336336
&name,
337-
self.script_id.clone(),
337+
&self.script_id,
338338
Entity::from_raw(0),
339339
vec![],
340340
guard.clone(),

crates/bevy_mod_scripting_core/src/commands.rs

Lines changed: 184 additions & 214 deletions
Large diffs are not rendered by default.

crates/bevy_mod_scripting_core/src/context.rs

Lines changed: 21 additions & 173 deletions
Original file line numberDiff line numberDiff line change
@@ -3,73 +3,17 @@
33
use crate::{
44
bindings::{ThreadWorldContainer, WorldContainer, WorldGuard},
55
error::{InteropError, ScriptError},
6-
script::{Script, ScriptId},
6+
script::ScriptId,
77
IntoScriptPluginParams,
88
};
99
use 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
7519
pub 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
}

crates/bevy_mod_scripting_core/src/error.rs

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ use crate::{
88
script_value::ScriptValue,
99
ReflectBaseType, ReflectReference,
1010
},
11-
context::ContextId,
1211
script::ScriptId,
1312
};
1413
use bevy::{
@@ -599,9 +598,8 @@ impl InteropError {
599598
}
600599

601600
/// Thrown if the required context for an operation is missing.
602-
pub fn missing_context(context_id: ContextId, script_id: impl Into<ScriptId>) -> Self {
601+
pub fn missing_context(script_id: impl Into<ScriptId>) -> Self {
603602
Self(Arc::new(InteropErrorInner::MissingContext {
604-
context_id,
605603
script_id: script_id.into(),
606604
}))
607605
}
@@ -812,8 +810,6 @@ pub enum InteropErrorInner {
812810
},
813811
/// Thrown if the required context for an operation is missing.
814812
MissingContext {
815-
/// The context that was missing
816-
context_id: ContextId,
817813
/// The script that was attempting to access the context
818814
script_id: ScriptId,
819815
},
@@ -1053,15 +1049,9 @@ impl PartialEq for InteropErrorInner {
10531049
},
10541050
) => a == c && b == d,
10551051
(
1056-
InteropErrorInner::MissingContext {
1057-
context_id: a,
1058-
script_id: b,
1059-
},
1060-
InteropErrorInner::MissingContext {
1061-
context_id: c,
1062-
script_id: d,
1063-
},
1064-
) => a == c && b == d,
1052+
InteropErrorInner::MissingContext { script_id: b },
1053+
InteropErrorInner::MissingContext { script_id: d },
1054+
) => b == d,
10651055
(
10661056
InteropErrorInner::MissingSchedule { schedule_name: a },
10671057
InteropErrorInner::MissingSchedule { schedule_name: b },
@@ -1284,10 +1274,10 @@ macro_rules! argument_count_mismatch_msg {
12841274
}
12851275

12861276
macro_rules! missing_context_for_callback {
1287-
($context_id:expr, $script_id:expr) => {
1277+
($script_id:expr) => {
12881278
format!(
1289-
"Missing context with id: {} for script with id: {}. Was the script loaded?.",
1290-
$context_id, $script_id
1279+
"Missing context for script with id: {}. Was the script loaded?.",
1280+
$script_id
12911281
)
12921282
};
12931283
}
@@ -1433,9 +1423,8 @@ impl DisplayWithWorld for InteropErrorInner {
14331423
InteropErrorInner::MissingScript { script_id } => {
14341424
missing_script_for_callback!(script_id)
14351425
},
1436-
InteropErrorInner::MissingContext { context_id, script_id } => {
1426+
InteropErrorInner::MissingContext { script_id } => {
14371427
missing_context_for_callback!(
1438-
context_id,
14391428
script_id
14401429
)
14411430
},
@@ -1580,9 +1569,8 @@ impl DisplayWithWorld for InteropErrorInner {
15801569
InteropErrorInner::MissingScript { script_id } => {
15811570
missing_script_for_callback!(script_id)
15821571
},
1583-
InteropErrorInner::MissingContext { context_id, script_id } => {
1572+
InteropErrorInner::MissingContext { script_id } => {
15841573
missing_context_for_callback!(
1585-
context_id,
15861574
script_id
15871575
)
15881576
},

0 commit comments

Comments
 (0)