Skip to content

Commit 86f2be8

Browse files
committed
feat!: Merge ScriptContexts<T> into Scripts and store type erased context pointers
1 parent 2255a41 commit 86f2be8

File tree

15 files changed

+370
-588
lines changed

15 files changed

+370
-588
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: 192 additions & 211 deletions
Large diffs are not rendered by default.
Lines changed: 44 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,50 @@
11
//! Traits and types for managing script contexts.
22
3+
use std::any::Any;
4+
35
use crate::{
46
bindings::{ThreadWorldContainer, WorldContainer, WorldGuard},
57
error::{InteropError, ScriptError},
6-
script::{Script, ScriptId},
8+
script::ScriptId,
79
IntoScriptPluginParams,
810
};
911
use bevy::ecs::{entity::Entity, system::Resource};
10-
use std::{collections::HashMap, sync::atomic::AtomicU32};
1112

1213
/// 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>,
14+
///
15+
/// Contexts are not required to be `Sync` as they are internally stored behind a `Mutex` but they must satisfy `Send` so they can be
16+
/// freely sent between threads.
17+
pub trait Context: 'static + Send {
18+
/// Downcast the context to a reference of the given type if it is of that type.
19+
fn as_any(&self) -> &dyn Any;
20+
/// Downcast the context to a mutable reference of the given type if it is of that type.
21+
fn as_any_mut(&mut self) -> &mut dyn Any;
2422
}
2523

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)
24+
impl<T: 'static + Send> Context for T {
25+
fn as_any(&self) -> &dyn Any {
26+
self
5627
}
5728

58-
/// Get a reference to a context
59-
pub fn get(&self, id: ContextId) -> Option<&P::C> {
60-
self.contexts.get(&id)
29+
fn as_any_mut(&mut self) -> &mut dyn Any {
30+
self
6131
}
32+
}
33+
/// A helper trait for downcasting a context to a reference of a specific type.
34+
pub trait DowncastContext {
35+
/// Downcast the context to a reference of the given type if it is of that type.
36+
fn downcast_ref<T: 'static>(&self) -> Option<&T>;
37+
/// Downcast the context to a mutable reference of the given type if it is of that type.
38+
fn downcast_mut<T: 'static>(&mut self) -> Option<&mut T>;
39+
}
6240

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)
41+
impl DowncastContext for dyn Context {
42+
fn downcast_ref<T: 'static>(&self) -> Option<&T> {
43+
self.as_any().downcast_ref()
6644
}
6745

68-
/// Check if a context exists
69-
pub fn contains(&self, id: ContextId) -> bool {
70-
self.contexts.contains_key(&id)
46+
fn downcast_mut<T: 'static>(&mut self) -> Option<&mut T> {
47+
self.as_any_mut().downcast_mut()
7148
}
7249
}
7350

@@ -85,7 +62,7 @@ pub struct ContextLoadingSettings<P: IntoScriptPluginParams> {
8562
/// Defines the strategy used to load and reload contexts
8663
pub loader: ContextBuilder<P>,
8764
/// Defines the strategy used to assign contexts to scripts
88-
pub assigner: ContextAssigner<P>,
65+
pub assignment_strategy: ContextAssignmentStrategy,
8966
/// Initializers run once after creating a context but before executing it for the first time
9067
pub context_initializers: Vec<ContextInitializer<P>>,
9168
/// Initializers run every time before executing or loading a script
@@ -96,7 +73,7 @@ impl<P: IntoScriptPluginParams> Default for ContextLoadingSettings<P> {
9673
fn default() -> Self {
9774
Self {
9875
loader: ContextBuilder::default(),
99-
assigner: ContextAssigner::default(),
76+
assignment_strategy: Default::default(),
10077
context_initializers: Default::default(),
10178
context_pre_handling_initializers: Default::default(),
10279
}
@@ -107,7 +84,7 @@ impl<T: IntoScriptPluginParams> Clone for ContextLoadingSettings<T> {
10784
fn clone(&self) -> Self {
10885
Self {
10986
loader: self.loader.clone(),
110-
assigner: self.assigner.clone(),
87+
assignment_strategy: self.assignment_strategy,
11188
context_initializers: self.context_initializers.clone(),
11289
context_pre_handling_initializers: self.context_pre_handling_initializers.clone(),
11390
}
@@ -119,7 +96,7 @@ pub type ContextLoadFn<P> = fn(
11996
content: &[u8],
12097
context_initializers: &[ContextInitializer<P>],
12198
pre_handling_initializers: &[ContextPreHandlingInitializer<P>],
122-
runtime: &mut <P as IntoScriptPluginParams>::R,
99+
runtime: &<P as IntoScriptPluginParams>::R,
123100
) -> Result<<P as IntoScriptPluginParams>::C, ScriptError>;
124101

125102
/// A strategy for reloading contexts
@@ -129,7 +106,7 @@ pub type ContextReloadFn<P> = fn(
129106
previous_context: &mut <P as IntoScriptPluginParams>::C,
130107
context_initializers: &[ContextInitializer<P>],
131108
pre_handling_initializers: &[ContextPreHandlingInitializer<P>],
132-
runtime: &mut <P as IntoScriptPluginParams>::R,
109+
runtime: &<P as IntoScriptPluginParams>::R,
133110
) -> Result<(), ScriptError>;
134111

135112
/// A strategy for loading and reloading contexts
@@ -160,7 +137,7 @@ impl<P: IntoScriptPluginParams> ContextBuilder<P> {
160137
context_initializers: &[ContextInitializer<P>],
161138
pre_handling_initializers: &[ContextPreHandlingInitializer<P>],
162139
world: WorldGuard,
163-
runtime: &mut P::R,
140+
runtime: &P::R,
164141
) -> Result<P::C, ScriptError> {
165142
WorldGuard::with_existing_static_guard(world.clone(), |world| {
166143
ThreadWorldContainer.set_world(world)?;
@@ -183,7 +160,7 @@ impl<P: IntoScriptPluginParams> ContextBuilder<P> {
183160
context_initializers: &[ContextInitializer<P>],
184161
pre_handling_initializers: &[ContextPreHandlingInitializer<P>],
185162
world: WorldGuard,
186-
runtime: &mut P::R,
163+
runtime: &P::R,
187164
) -> Result<(), ScriptError> {
188165
WorldGuard::with_existing_static_guard(world, |world| {
189166
ThreadWorldContainer.set_world(world)?;
@@ -208,108 +185,12 @@ impl<P: IntoScriptPluginParams> Clone for ContextBuilder<P> {
208185
}
209186
}
210187

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-
}
188+
/// The strategy used in assigning contexts to scripts
189+
#[derive(Default, Clone, Copy)]
190+
pub enum ContextAssignmentStrategy {
191+
/// Assign a new context to each script
192+
#[default]
193+
Individual,
194+
/// Share contexts with all other scripts
195+
Global,
315196
}

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)