DeleteScript {
impl Command for DeleteScript {
fn apply(self, world: &mut bevy::prelude::World) {
- let settings = world
- .get_resource::>()
- .expect("No ScriptLoadingSettings resource found")
- .clone();
-
- let runner = world
- .get_resource::>()
- .expect("No CallbackSettings resource found")
- .callback_handler;
-
- let mut ctxts = world
- .remove_non_send_resource::>()
- .unwrap();
+ let mut res_ctxt = match extract_handler_context::(world) {
+ Ok(res_ctxt) => res_ctxt,
+ Err(e) => {
+ bevy::log::error_once!(
+ "Could not delete script: {}, as some plugin resources are missing: {}",
+ self.id,
+ e
+ );
+ return;
+ }
+ };
- let mut runtime_container = world
- .remove_non_send_resource::>()
- .unwrap();
+ if let Some(script) = res_ctxt.scripts.scripts.remove(&self.id) {
+ debug!("Deleting script with id: {}", self.id);
- world.resource_scope(|world, mut scripts: Mut| {
- if let Some(script) = scripts.scripts.remove(&self.id) {
- debug!("Deleting script with id: {}", self.id);
+ match res_ctxt.script_contexts.get_mut(script.context_id) {
+ Some(context) => {
+ // first let the script uninstall itself
+ match (res_ctxt.callback_settings.callback_handler)(
+ vec![],
+ bevy::ecs::entity::Entity::from_raw(0),
+ &self.id,
+ &OnScriptUnloaded::into_callback_label(),
+ context,
+ &res_ctxt
+ .context_loading_settings
+ .context_pre_handling_initializers,
+ &mut res_ctxt.runtime_container.runtime,
+ world,
+ ) {
+ Ok(_) => {}
+ Err(e) => {
+ handle_script_errors(
+ world,
+ [e.with_context(format!(
+ "Running unload hook for script with id: {}. Language: {}",
+ self.id,
+ P::LANGUAGE
+ ))]
+ .into_iter(),
+ );
+ }
+ }
- // first let the script uninstall itself
- match (runner)(
- vec![],
- bevy::ecs::entity::Entity::from_raw(0),
- &self.id,
- &OnScriptUnloaded::into_callback_label(),
- ctxts.get_mut(script.context_id).unwrap(),
- &settings.context_pre_handling_initializers,
- &mut runtime_container.runtime,
- world,
- ) {
- Ok(_) => {}
- Err(e) => {
- handle_script_errors(
- world,
- [e.with_context(format!(
- "Running unload hook for script with id: {}. Language: {}",
- self.id,
- P::LANGUAGE
- ))]
- .into_iter(),
+ debug!("Removing script with id: {}", self.id);
+ (res_ctxt.context_loading_settings.assigner.remove)(
+ script.context_id,
+ &script,
+ &mut res_ctxt.script_contexts,
+ )
+ }
+ None => {
+ bevy::log::error!(
+ "Could not find context with id: {} corresponding to script with id: {}. Removing script without running callbacks.",
+ script.context_id,
+ self.id
);
- }
+ return;
}
+ };
+ } else {
+ bevy::log::error!(
+ "Attempted to delete script with id: {} but it does not exist, doing nothing!",
+ self.id
+ );
+ }
- debug!("Removing script with id: {}", self.id);
- (settings.assigner.remove)(script.context_id, &script, &mut ctxts)
- } else {
- bevy::log::error!(
- "Attempted to delete script with id: {} but it does not exist, doing nothing!",
- self.id
- );
- }
- });
-
- world.insert_resource(settings);
- world.insert_non_send_resource(ctxts);
- world.insert_non_send_resource(runtime_container);
+ yield_handler_context(world, res_ctxt);
}
}
@@ -113,20 +120,26 @@ impl CreateOrUpdateScript {
fn run_on_load_callback(
&self,
- settings: &ContextLoadingSettings
,
- runtime: &mut RuntimeContainer
,
- runner: HandlerFn
,
+ res_ctxt: &mut HandlerContext
,
world: &mut bevy::prelude::World,
ctxt: &mut
::C,
) {
- match (runner)(
+ bevy::log::debug!(
+ "{}: Running on load callback for script with id: {}",
+ P::LANGUAGE,
+ self.id
+ );
+
+ match (res_ctxt.callback_settings.callback_handler)(
vec![],
bevy::ecs::entity::Entity::from_raw(0),
&self.id,
&OnScriptLoaded::into_callback_label(),
ctxt,
- &settings.context_pre_handling_initializers,
- &mut runtime.runtime,
+ &res_ctxt
+ .context_loading_settings
+ .context_pre_handling_initializers,
+ &mut res_ctxt.runtime_container.runtime,
world,
) {
Ok(_) => {}
@@ -148,92 +161,85 @@ impl CreateOrUpdateScript {
fn reload_context(
&self,
world: &mut bevy::prelude::World,
- settings: &ContextLoadingSettings
,
- runtime: &mut RuntimeContainer
,
- builder: &crate::context::ContextBuilder
,
- log_context: String,
- previous_context: &mut
::C,
- ) -> bool {
- match (builder.reload)(
- &self.id,
- &self.content,
- previous_context,
- &settings.context_initializers,
- &settings.context_pre_handling_initializers,
- world,
- &mut runtime.runtime,
- ) {
- Ok(_) => {}
- Err(e) => {
- handle_script_errors(world, [e.with_context(log_context)].into_iter());
- return false;
- }
- };
- true
+ res_ctxt: &mut HandlerContext
,
+ previous_context_id: u32,
+ ) {
+ if let Some(mut previous_context) = res_ctxt.script_contexts.remove(previous_context_id) {
+ match (res_ctxt.context_loading_settings.loader.reload)(
+ &self.id,
+ &self.content,
+ &mut previous_context,
+ &res_ctxt.context_loading_settings.context_initializers,
+ &res_ctxt
+ .context_loading_settings
+ .context_pre_handling_initializers,
+ world,
+ &mut res_ctxt.runtime_container.runtime,
+ ) {
+ Ok(_) => {}
+ Err(e) => {
+ handle_script_errors(
+ world,
+ [e.with_context(format!("reloading script with id: {}", self.id))]
+ .into_iter(),
+ );
+ }
+ };
+
+ self.run_on_load_callback(res_ctxt, world, &mut previous_context);
+
+ res_ctxt
+ .script_contexts
+ .insert_with_id(previous_context_id, previous_context);
+ } else {
+ bevy::log::error!(
+ "{}: Could not reload script with id: {}, as the context with id: {} does not exist.",
+ P::LANGUAGE,
+ self.id,
+ previous_context_id
+ );
+ }
}
#[inline(always)]
fn execute(
self,
world: &mut bevy::prelude::World,
- settings: &ContextLoadingSettings
,
- contexts: &mut ScriptContexts
,
- runtime: &mut RuntimeContainer
,
- scripts: &mut Scripts,
- assigner: crate::context::ContextAssigner
,
- builder: crate::context::ContextBuilder
,
- runner: HandlerFn
,
+ res_ctxt: &mut HandlerContext
,
previous_context_id: Option,
) {
match previous_context_id {
Some(previous_context_id) => {
- if let Some(previous_context) = contexts.get_mut(previous_context_id) {
- let log_context = format!("{}: Reloading script: {}.", P::LANGUAGE, self.id);
- bevy::log::debug!("{}", log_context);
- if !self.reload_context(
- world,
- settings,
- runtime,
- &builder,
- log_context,
- previous_context,
- ) {
- return;
- }
- self.run_on_load_callback(settings, runtime, runner, world, previous_context);
- } else {
- bevy::log::error!("{}: Could not find previous context with id: {}. Could not reload script: {}. Someone deleted the context.", P::LANGUAGE, previous_context_id, self.id);
- }
+ bevy::log::debug!(
+ "{}: script with id already has a context: {}",
+ P::LANGUAGE,
+ self.id
+ );
+ self.reload_context(world, res_ctxt, previous_context_id);
}
None => {
let log_context = format!("{}: Loading script: {}", P::LANGUAGE, self.id);
- let new_context_id = (assigner.assign)(&self.id, &self.content, contexts)
- .unwrap_or_else(|| contexts.allocate_id());
- if let Some(existing_context) = contexts.get_mut(new_context_id) {
- // this can happen if we're sharing contexts between scripts
- if !self.reload_context(
- world,
- settings,
- runtime,
- &builder,
- log_context,
- existing_context,
- ) {
- return;
- }
-
- self.run_on_load_callback(settings, runtime, runner, world, existing_context);
+ let new_context_id = (res_ctxt.context_loading_settings.assigner.assign)(
+ &self.id,
+ &self.content,
+ &res_ctxt.script_contexts,
+ )
+ .unwrap_or_else(|| res_ctxt.script_contexts.allocate_id());
+ if res_ctxt.script_contexts.contains(new_context_id) {
+ self.reload_context(world, res_ctxt, new_context_id);
} else {
// load new context
bevy::log::debug!("{}", log_context);
- let ctxt = (builder.load)(
+ let ctxt = (res_ctxt.context_loading_settings.loader.load)(
&self.id,
&self.content,
- &settings.context_initializers,
- &settings.context_pre_handling_initializers,
+ &res_ctxt.context_loading_settings.context_initializers,
+ &res_ctxt
+ .context_loading_settings
+ .context_pre_handling_initializers,
world,
- &mut runtime.runtime,
+ &mut res_ctxt.runtime_container.runtime,
);
let mut ctxt = match ctxt {
Ok(ctxt) => ctxt,
@@ -243,14 +249,18 @@ impl CreateOrUpdateScript {
}
};
- self.run_on_load_callback(settings, runtime, runner, world, &mut ctxt);
+ self.run_on_load_callback(res_ctxt, world, &mut ctxt);
- if contexts.insert_with_id(new_context_id, ctxt).is_some() {
+ if res_ctxt
+ .script_contexts
+ .insert_with_id(new_context_id, ctxt)
+ .is_some()
+ {
bevy::log::warn!("{}: Context with id {} was not expected to exist. Overwriting it with a new context. This might happen if a script is not completely removed.", P::LANGUAGE, new_context_id);
}
}
- scripts.scripts.insert(
+ res_ctxt.scripts.scripts.insert(
self.id.clone(),
Script {
id: self.id,
@@ -265,29 +275,19 @@ impl CreateOrUpdateScript {
impl Command for CreateOrUpdateScript {
fn apply(self, world: &mut bevy::prelude::World) {
- let settings = world
- .get_resource::>()
- .expect(
- "Missing ContextLoadingSettings resource. Was the plugin initialized correctly?",
- )
- .clone();
- let mut contexts = world
- .remove_non_send_resource::>()
- .expect("No ScriptContexts resource found. Was the plugin initialized correctly?");
- let mut runtime = world
- .remove_non_send_resource::>()
- .expect("No RuntimeContainer resource found. Was the plugin initialized correctly?");
- let mut scripts = world
- .remove_resource::()
- .expect("No Scripts resource found. Was the plugin initialized correctly?");
-
- let runner = world.get_resource::>().unwrap();
- // assign context
- let assigner = settings.assigner.clone();
- let builder = settings.loader.clone();
- let runner = runner.callback_handler;
-
- let script = scripts.scripts.get(&self.id);
+ let mut res_ctxt = match extract_handler_context::(world) {
+ Ok(res_ctxt) => res_ctxt,
+ Err(e) => {
+ bevy::log::error_once!(
+ "Could not create or update script: {}, as some plugin resources are missing: {}",
+ self.id,
+ e
+ );
+ return;
+ }
+ };
+
+ let script = res_ctxt.scripts.scripts.get(&self.id);
let previous_context_id = script.as_ref().map(|s| s.context_id);
debug!(
"{}: CreateOrUpdateScript command applying (script_id: {}, previous_context_id: {:?})",
@@ -296,23 +296,10 @@ impl Command for CreateOrUpdateScript {
previous_context_id
);
- // closure to prevent returns from re-inserting resources
- self.execute(
- world,
- &settings,
- &mut contexts,
- &mut runtime,
- &mut scripts,
- assigner,
- builder,
- runner,
- previous_context_id,
- );
+ // closure to prevent early returns from yielding the context
+ self.execute(world, &mut res_ctxt, previous_context_id);
- world.insert_resource(scripts);
- world.insert_resource(settings);
- world.insert_non_send_resource(runtime);
- world.insert_non_send_resource(contexts);
+ yield_handler_context(world, res_ctxt);
}
}
@@ -320,13 +307,17 @@ impl Command for CreateOrUpdateScript {
mod test {
use bevy::{
app::App,
+ log::{Level, LogPlugin},
prelude::{Entity, World},
};
use crate::{
asset::Language,
bindings::script_value::ScriptValue,
- context::{ContextAssigner, ContextBuilder},
+ context::{ContextAssigner, ContextBuilder, ContextLoadingSettings, ScriptContexts},
+ handler::CallbackSettings,
+ runtime::RuntimeContainer,
+ script::Scripts,
};
use super::*;
@@ -334,6 +325,11 @@ mod test {
fn setup_app() -> App {
// setup all the resources necessary
let mut app = App::new();
+ app.add_plugins(LogPlugin {
+ filter: "bevy_mod_scripting_core=debug,info".to_owned(),
+ level: Level::TRACE,
+ ..Default::default()
+ });
app.insert_resource(ContextLoadingSettings:: {
loader: ContextBuilder {
diff --git a/crates/bevy_mod_scripting_core/src/context.rs b/crates/bevy_mod_scripting_core/src/context.rs
index 8c338859fa..a71ff98c18 100644
--- a/crates/bevy_mod_scripting_core/src/context.rs
+++ b/crates/bevy_mod_scripting_core/src/context.rs
@@ -192,9 +192,7 @@ mod tests {
const LANGUAGE: Language = Language::Lua;
- fn build_runtime() -> Self::R {
- todo!()
- }
+ fn build_runtime() -> Self::R {}
}
#[test]
diff --git a/crates/bevy_mod_scripting_core/src/error.rs b/crates/bevy_mod_scripting_core/src/error.rs
index bb14ac623d..8d4a6887bd 100644
--- a/crates/bevy_mod_scripting_core/src/error.rs
+++ b/crates/bevy_mod_scripting_core/src/error.rs
@@ -1,6 +1,9 @@
use crate::bindings::{
- access_map::DisplayCodeLocation, function::namespace::Namespace,
- pretty_print::DisplayWithWorld, script_value::ScriptValue, ReflectBaseType, ReflectReference,
+ access_map::{DisplayCodeLocation, ReflectAccessId},
+ function::namespace::Namespace,
+ pretty_print::DisplayWithWorld,
+ script_value::ScriptValue,
+ ReflectBaseType, ReflectReference,
};
use bevy::{
ecs::component::ComponentId,
@@ -9,6 +12,7 @@ use bevy::{
};
use std::{
any::TypeId,
+ borrow::Cow,
fmt::{Debug, Display},
ops::Deref,
str::Utf8Error,
@@ -241,6 +245,27 @@ impl From for ScriptError {
// }
// }
+#[derive(Clone, Debug, PartialEq)]
+pub struct MissingResourceError(&'static str);
+
+impl MissingResourceError {
+ pub fn new() -> Self {
+ Self(std::any::type_name::())
+ }
+}
+
+impl Display for MissingResourceError {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "Missing resource: {}. Was the plugin initialized correctly?",
+ self.0
+ )
+ }
+}
+
+impl std::error::Error for MissingResourceError {}
+
#[derive(Debug, Clone, PartialEq, Reflect)]
pub struct InteropError(#[reflect(ignore)] Arc);
@@ -289,6 +314,12 @@ impl FlattenError for Result, Intero
}
impl InteropError {
+ pub fn invariant(message: impl Display) -> Self {
+ Self(Arc::new(InteropErrorInner::Invariant {
+ message: message.to_string(),
+ }))
+ }
+
/// Thrown if a callback requires world access, but is unable to do so due
/// to the world not being reachable at all via any mechanism.
pub fn missing_world() -> Self {
@@ -328,12 +359,14 @@ impl InteropError {
/// Thrown if access to the given reflection base is required but cannot be claimed.
/// This is likely due to some other script already claiming access to the base.
pub fn cannot_claim_access(
- base: ReflectBaseType,
+ base: ReflectAccessId,
location: Option>,
+ context: impl Into>,
) -> Self {
Self(Arc::new(InteropErrorInner::CannotClaimAccess {
base,
location,
+ context: context.into(),
}))
}
@@ -395,12 +428,12 @@ impl InteropError {
pub fn unsupported_operation(
base: Option,
value: Option>,
- operation: String,
+ operation: impl Display,
) -> Self {
Self(Arc::new(InteropErrorInner::UnsupportedOperation {
base,
value,
- operation,
+ operation: operation.to_string(),
}))
}
@@ -454,10 +487,10 @@ impl InteropError {
Self(Arc::new(InteropErrorInner::OtherError { error }))
}
- pub fn missing_function(on: TypeId, function_name: String) -> Self {
+ pub fn missing_function(on: TypeId, function_name: impl Display) -> Self {
Self(Arc::new(InteropErrorInner::MissingFunctionError {
on,
- function_name,
+ function_name: function_name.to_string(),
}))
}
@@ -469,18 +502,24 @@ impl InteropError {
}))
}
+ pub fn unregistered_component_or_resource_type(
+ type_name: impl Into>,
+ ) -> Self {
+ Self(Arc::new(
+ InteropErrorInner::UnregisteredComponentOrResourceType {
+ type_name: type_name.into(),
+ },
+ ))
+ }
+
pub fn inner(&self) -> &InteropErrorInner {
&self.0
}
- /// Unwraps the inner error
- ///
- /// # Panics
- /// - if there are multiple references to the inner error
- pub fn into_inner(self) -> InteropErrorInner {
- Arc::try_unwrap(self.0).unwrap_or_else(|a| {
- Arc::try_unwrap(a).expect("tried to unwrap interop error while a copy exists")
- })
+ /// Unwraps the inner error if there is only one reference to it.
+ /// Otherwise returns Self.
+ pub fn into_inner(self) -> Result {
+ Arc::try_unwrap(self.0).map_err(Self)
}
}
@@ -501,7 +540,8 @@ pub enum InteropErrorInner {
reason: String,
},
CannotClaimAccess {
- base: ReflectBaseType,
+ base: ReflectAccessId,
+ context: Cow<'static, str>,
location: Option>,
},
InvalidAccessCount {
@@ -576,6 +616,12 @@ pub enum InteropErrorInner {
OtherError {
error: Box,
},
+ UnregisteredComponentOrResourceType {
+ type_name: Cow<'static, str>,
+ },
+ Invariant {
+ message: String,
+ },
}
impl PartialEq for InteropErrorInner {
@@ -600,10 +646,10 @@ macro_rules! unregistered_base {
}
macro_rules! cannot_claim_access {
- ($base:expr, $location:expr) => {
+ ($base:expr, $location:expr, $ctxt:expr) => {
format!(
- "Cannot claim access to base type: {}. The base is already claimed by something else in a way which prevents safe access. Location: {}",
- $base, $location
+ "Cannot claim access to base type: {}. The base is already claimed by something else in a way which prevents safe access. Location: {}. Context: {}",
+ $base, $location, $ctxt
)
};
}
@@ -746,6 +792,24 @@ macro_rules! invalid_access_count {
};
}
+macro_rules! invariant {
+ ($message:expr) => {
+ format!(
+ "An invariant has been broken. This is a bug in BMS, please report me! : {}",
+ $message
+ )
+ };
+}
+
+macro_rules! unregistered_component_or_resource_type {
+ ($type_name:expr) => {
+ format!(
+ "Expected registered component/resource but got unregistered type: {}",
+ $type_name
+ )
+ };
+}
+
impl DisplayWithWorld for InteropErrorInner {
fn display_with_world(&self, world: crate::bindings::WorldGuard) -> String {
match self {
@@ -755,8 +819,8 @@ impl DisplayWithWorld for InteropErrorInner {
InteropErrorInner::UnregisteredBase { base } => {
unregistered_base!(base.display_with_world(world))
}
- InteropErrorInner::CannotClaimAccess { base, location } => {
- cannot_claim_access!(base.display_with_world(world), location.display_location())
+ InteropErrorInner::CannotClaimAccess { base, location, context } => {
+ cannot_claim_access!(base.display_with_world(world), location.display_location(), context)
}
InteropErrorInner::ImpossibleConversion { into } => {
impossible_conversion!(into.display_with_world(world))
@@ -844,7 +908,7 @@ impl DisplayWithWorld for InteropErrorInner {
Namespace::OnType(type_id) => format!("on type: {}", type_id.display_with_world(world.clone())),
};
let display_name = if function_name.starts_with("TypeId") {
- function_name.split("::").last().unwrap()
+ function_name.split("::").last().unwrap_or_default()
} else {
function_name.as_str()
};
@@ -866,6 +930,12 @@ impl DisplayWithWorld for InteropErrorInner {
InteropErrorInner::InvalidAccessCount { count, expected, context } => {
invalid_access_count!(expected, count, context)
},
+ InteropErrorInner::Invariant { message } => {
+ invariant!(message)
+ },
+ InteropErrorInner::UnregisteredComponentOrResourceType { type_name } => {
+ unregistered_component_or_resource_type!(type_name)
+ },
}
}
@@ -878,8 +948,8 @@ impl DisplayWithWorld for InteropErrorInner {
InteropErrorInner::UnregisteredBase { base } => {
unregistered_base!(base.display_without_world())
}
- InteropErrorInner::CannotClaimAccess { base, location } => {
- cannot_claim_access!(base.display_without_world(), location.display_location())
+ InteropErrorInner::CannotClaimAccess { base, location, context } => {
+ cannot_claim_access!(base.display_without_world(), location.display_location(), context)
}
InteropErrorInner::ImpossibleConversion { into } => {
impossible_conversion!(into.display_without_world())
@@ -967,7 +1037,7 @@ impl DisplayWithWorld for InteropErrorInner {
Namespace::OnType(type_id) => format!("on type: {}", type_id.display_without_world()),
};
let display_name = if function_name.starts_with("TypeId") {
- function_name.split("::").last().unwrap()
+ function_name.split("::").last().unwrap_or_default()
} else {
function_name.as_str()
};
@@ -989,6 +1059,12 @@ impl DisplayWithWorld for InteropErrorInner {
InteropErrorInner::InvalidAccessCount { count, expected, context } => {
invalid_access_count!(expected, count, context)
},
+ InteropErrorInner::Invariant { message } => {
+ invariant!(message)
+ },
+ InteropErrorInner::UnregisteredComponentOrResourceType { type_name } => {
+ unregistered_component_or_resource_type!(type_name)
+ },
}
}
}
diff --git a/crates/bevy_mod_scripting_core/src/extractors.rs b/crates/bevy_mod_scripting_core/src/extractors.rs
new file mode 100644
index 0000000000..035766dba5
--- /dev/null
+++ b/crates/bevy_mod_scripting_core/src/extractors.rs
@@ -0,0 +1,66 @@
+//! Systems which are used to extract the various resources and components used by BMS.
+//!
+//! These are designed to be used to pipe inputs into other systems which require them, while handling any configuration erorrs nicely.
+
+use bevy::prelude::World;
+
+use crate::{
+ context::{ContextLoadingSettings, ScriptContexts},
+ error::MissingResourceError,
+ handler::CallbackSettings,
+ runtime::RuntimeContainer,
+ script::Scripts,
+ IntoScriptPluginParams,
+};
+
+/// Context for systems which handle events for scripts
+pub(crate) struct HandlerContext {
+ pub callback_settings: CallbackSettings,
+ pub context_loading_settings: ContextLoadingSettings
,
+ pub scripts: Scripts,
+ pub runtime_container: RuntimeContainer
,
+ pub script_contexts: ScriptContexts
,
+}
+
+pub(crate) fn extract_handler_context(
+ world: &mut World,
+) -> Result, MissingResourceError> {
+ // we don't worry about re-inserting these resources if we fail to extract them, as the plugin is misconfigured anyway,
+ // so the only solution is to stop the program and fix the configuration
+ // the config is either all in or nothing
+
+ let callback_settings = world
+ .remove_resource::>()
+ .ok_or_else(MissingResourceError::new::>)?;
+ let context_loading_settings = world
+ .remove_resource::>()
+ .ok_or_else(MissingResourceError::new::>)?;
+ let scripts = world
+ .remove_resource::()
+ .ok_or_else(MissingResourceError::new::)?;
+ let runtime_container = world
+ .remove_non_send_resource::>()
+ .ok_or_else(MissingResourceError::new::>)?;
+ let script_contexts = world
+ .remove_non_send_resource::>()
+ .ok_or_else(MissingResourceError::new::>)?;
+
+ Ok(HandlerContext {
+ callback_settings,
+ context_loading_settings,
+ scripts,
+ runtime_container,
+ script_contexts,
+ })
+}
+
+pub(crate) fn yield_handler_context(
+ world: &mut World,
+ context: HandlerContext,
+) {
+ world.insert_resource(context.callback_settings);
+ world.insert_resource(context.context_loading_settings);
+ world.insert_resource(context.scripts);
+ world.insert_non_send_resource(context.runtime_container);
+ world.insert_non_send_resource(context.script_contexts);
+}
diff --git a/crates/bevy_mod_scripting_core/src/handler.rs b/crates/bevy_mod_scripting_core/src/handler.rs
index 6145d4345b..d637b15ff1 100644
--- a/crates/bevy_mod_scripting_core/src/handler.rs
+++ b/crates/bevy_mod_scripting_core/src/handler.rs
@@ -1,14 +1,12 @@
-use std::any::type_name;
-
use crate::{
bindings::{
pretty_print::DisplayWithWorld, script_value::ScriptValue, WorldAccessGuard, WorldGuard,
},
- context::{ContextLoadingSettings, ContextPreHandlingInitializer, ScriptContexts},
+ context::ContextPreHandlingInitializer,
error::ScriptError,
event::{CallbackLabel, IntoCallbackLabel, ScriptCallbackEvent, ScriptErrorEvent},
- runtime::RuntimeContainer,
- script::{ScriptComponent, ScriptId, Scripts},
+ extractors::{extract_handler_context, yield_handler_context, HandlerContext},
+ script::{ScriptComponent, ScriptId},
IntoScriptPluginParams,
};
use bevy::{
@@ -18,7 +16,7 @@ use bevy::{
world::World,
},
log::{debug, trace},
- prelude::{EventReader, Events, Query, Ref, Res},
+ prelude::{EventReader, Events, Query, Ref},
};
pub trait Args: Clone + Send + Sync + 'static {}
@@ -53,36 +51,17 @@ macro_rules! push_err_and_continue {
};
}
-/// Passes events with the specified label to the script callback with the same name and runs the callback
-pub fn event_handler(
+/// A utility to separate the event handling logic from the retrieval of the handler context
+pub(crate) fn event_handler_internal(
world: &mut World,
+ res_ctxt: &mut HandlerContext,
params: &mut SystemState<(
EventReader,
- Res>,
- Res>,
- Res,
Query<(Entity, Ref)>,
)>,
) {
- let mut runtime_container = world
- .remove_non_send_resource::>()
- .unwrap_or_else(|| {
- panic!(
- "No runtime container for runtime {} found. Was the scripting plugin initialized correctly?",
- type_name::()
- )
- });
- let runtime = &mut runtime_container.runtime;
- let mut script_contexts = world
- .remove_non_send_resource::>()
- .unwrap_or_else(|| panic!("No script contexts found for context {}", type_name::()));
+ let (mut script_events, entities) = params.get_mut(world);
- let (mut script_events, callback_settings, context_settings, scripts, entities) =
- params.get_mut(world);
-
- let handler = callback_settings.callback_handler;
- let pre_handling_initializers = context_settings.context_pre_handling_initializers.clone();
- let scripts = scripts.clone();
let mut errors = Vec::default();
let events = script_events.read().cloned().collect::>();
@@ -112,7 +91,7 @@ pub fn event_handler(
"Handling event for script {} on entity {:?}",
script_id, entity
);
- let script = match scripts.scripts.get(script_id) {
+ let script = match res_ctxt.scripts.scripts.get(script_id) {
Some(s) => s,
None => {
trace!(
@@ -123,7 +102,11 @@ pub fn event_handler(
}
};
- let ctxt = match script_contexts.contexts.get_mut(&script.context_id) {
+ let ctxt = match res_ctxt
+ .script_contexts
+ .contexts
+ .get_mut(&script.context_id)
+ {
Some(ctxt) => ctxt,
None => {
// if we don't have a context for the script, it's either:
@@ -133,14 +116,16 @@ pub fn event_handler(
}
};
- let handler_result = (handler)(
+ let handler_result = (res_ctxt.callback_settings.callback_handler)(
event.args.clone(),
*entity,
&script.id,
&L::into_callback_label(),
ctxt,
- &pre_handling_initializers,
- runtime,
+ &res_ctxt
+ .context_loading_settings
+ .context_pre_handling_initializers,
+ &mut res_ctxt.runtime_container.runtime,
world,
)
.map_err(|e| {
@@ -153,25 +138,47 @@ pub fn event_handler(
}
}
- world.insert_non_send_resource(runtime_container);
- world.insert_non_send_resource(script_contexts);
-
handle_script_errors(world, errors.into_iter());
}
+/// Passes events with the specified label to the script callback with the same name and runs the callback.
+///
+/// If any of the resources required for the handler are missing, the system will log this issue and do nothing.
+pub fn event_handler(
+ world: &mut World,
+ params: &mut SystemState<(
+ EventReader,
+ Query<(Entity, Ref)>,
+ )>,
+) {
+ let mut res_ctxt = match extract_handler_context::(world) {
+ Ok(handler_context) => handler_context,
+ Err(e) => {
+ bevy::log::error_once!(
+ "Event handler for language `{}` will not run due to missing resource: {}",
+ P::LANGUAGE,
+ e
+ );
+ return;
+ }
+ };
+
+ // this ensures the internal handler cannot early return without yielding the context
+ event_handler_internal::(world, &mut res_ctxt, params);
+
+ yield_handler_context(world, res_ctxt);
+}
+
/// Handles errors caused by script execution and sends them to the error event channel
pub(crate) fn handle_script_errors + Clone>(
world: &mut World,
errors: I,
) {
- let mut error_events = world
- .get_resource_mut::>()
- .expect("Missing events resource");
+ let mut error_events = world.get_resource_or_init::>();
for error in errors.clone() {
error_events.send(ScriptErrorEvent { error });
}
-
for error in errors {
let arc_world = WorldGuard::new(WorldAccessGuard::new(world));
bevy::log::error!("{}", error.display_with_world(arc_world));
@@ -179,6 +186,7 @@ pub(crate) fn handle_script_errors + Clone>(
}
#[cfg(test)]
+#[allow(clippy::todo)]
mod test {
use std::{borrow::Cow, collections::HashMap};
diff --git a/crates/bevy_mod_scripting_core/src/lib.rs b/crates/bevy_mod_scripting_core/src/lib.rs
index 64ec4ce5ad..7a36abe00f 100644
--- a/crates/bevy_mod_scripting_core/src/lib.rs
+++ b/crates/bevy_mod_scripting_core/src/lib.rs
@@ -24,6 +24,7 @@ pub mod commands;
pub mod context;
pub mod error;
pub mod event;
+pub mod extractors;
pub mod handler;
pub mod reflection_extensions;
pub mod runtime;
@@ -143,6 +144,12 @@ pub trait ConfigureScriptPlugin {
initializer: ContextPreHandlingInitializer,
) -> Self;
fn add_runtime_initializer(self, initializer: RuntimeInitializer) -> Self;
+
+ /// Switch the context assigning strategy to a global context assigner.
+ ///
+ /// This means that all scripts will share the same context. This is useful for when you want to share data between scripts easilly.
+ /// Be careful however as this also means that scripts can interfere with each other in unexpected ways!.
+ fn enable_context_sharing(self);
}
impl>> ConfigureScriptPlugin for P {
@@ -166,6 +173,10 @@ impl>> ConfigureScriptPlugi
self.as_mut().add_runtime_initializer(initializer);
self
}
+
+ fn enable_context_sharing(mut self) {
+ self.as_mut().context_assigner = ContextAssigner::new_global_context_assigner();
+ }
}
// One of registration of things that need to be done only once per app
diff --git a/crates/bevy_mod_scripting_core/src/reflection_extensions.rs b/crates/bevy_mod_scripting_core/src/reflection_extensions.rs
index 29d8dfa3a8..d009acd333 100644
--- a/crates/bevy_mod_scripting_core/src/reflection_extensions.rs
+++ b/crates/bevy_mod_scripting_core/src/reflection_extensions.rs
@@ -22,8 +22,8 @@ pub trait PartialReflectExt {
reflect: &dyn PartialReflect,
world: WorldGuard,
) -> Box;
- fn allocate_cloned(&self, world: WorldGuard) -> ReflectReference;
- fn allocate(boxed: Box, world: WorldGuard) -> ReflectReference;
+
+ fn allocate(boxed: Box, world: WorldGuard) -> ReflectReference;
/// Check if the represented type is from the given crate and has the given type identifier,
/// returns false if not representing any type or if the type is not from the given crate or does not have the given type identifier.
@@ -186,7 +186,7 @@ impl PartialReflectExt for T {
// pop then insert in reverse order of popping (last elem -> first elem to insert)
let to_insert = (0..to_be_inserted_elems)
.rev()
- .map(|_| r.pop().expect("invariant"))
+ .map_while(|_| r.pop())
.collect::>();
to_insert.into_iter().rev().for_each(|e| {
@@ -197,10 +197,12 @@ impl PartialReflectExt for T {
// apply to existing elements in the list
for i in apply_range {
- apply(
- l.get_mut(i).expect("invariant"),
- r.get(i).expect("invariant"),
- )?;
+ let left = l.get_mut(i);
+ let right = r.get(i);
+ match (left, right) {
+ (Some(left), Some(right)) => apply(left, right)?,
+ _ => return Err(InteropError::invariant("set_as_list failed")),
+ };
}
Ok(())
@@ -273,15 +275,11 @@ impl PartialReflectExt for T {
}
fn convert_to_0_indexed_key(&mut self) {
- if let Some(type_id) = self.get_represented_type_info().map(|ti| ti.type_id()) {
- if type_id == TypeId::of::() {
- let self_ = self
- .as_partial_reflect_mut()
- .try_downcast_mut::()
- .expect("invariant");
-
- *self_ = self_.saturating_sub(1);
- }
+ if let Some(usize) = self
+ .try_as_reflect_mut()
+ .and_then(|r| r.downcast_mut::())
+ {
+ *usize = usize.saturating_sub(1);
}
}
@@ -405,16 +403,11 @@ impl PartialReflectExt for T {
}
}
- fn allocate(boxed: Box, world: WorldGuard) -> ReflectReference {
+ fn allocate(boxed: Box, world: WorldGuard) -> ReflectReference {
let allocator = world.allocator();
let mut allocator = allocator.write();
ReflectReference::new_allocated_boxed(boxed, &mut allocator)
}
-
- fn allocate_cloned(&self, world: WorldGuard) -> ReflectReference {
- let boxed = self.clone_value();
- Self::allocate(boxed, world)
- }
}
pub trait TypeInfoExtensions {
diff --git a/crates/bevy_mod_scripting_functions/Cargo.toml b/crates/bevy_mod_scripting_functions/Cargo.toml
index 74d3995b76..5630361b61 100644
--- a/crates/bevy_mod_scripting_functions/Cargo.toml
+++ b/crates/bevy_mod_scripting_functions/Cargo.toml
@@ -33,3 +33,6 @@ bevy = { workspace = true, features = [
uuid = "1.11"
smol_str = "0.2.2"
bevy_mod_scripting_core = { workspace = true }
+
+[lints]
+workspace = true
diff --git a/crates/bevy_mod_scripting_functions/src/core.rs b/crates/bevy_mod_scripting_functions/src/core.rs
index 749d3a58a3..d988bbcecc 100644
--- a/crates/bevy_mod_scripting_functions/src/core.rs
+++ b/crates/bevy_mod_scripting_functions/src/core.rs
@@ -6,7 +6,6 @@ use bevy::{
};
use bevy_mod_scripting_core::*;
use bindings::{
- access_map::ReflectAccessId,
function::{
from::{Ref, Val},
from_ref::FromScriptRef,
@@ -16,8 +15,8 @@ use bindings::{
},
pretty_print::DisplayWithWorld,
script_value::ScriptValue,
- ReflectReference, ReflectionPathExt, ScriptQueryBuilder, ScriptQueryResult,
- ScriptTypeRegistration, WorldCallbackAccess,
+ ReflectReference, ReflectionPathExt, ScriptComponentRegistration, ScriptQueryBuilder,
+ ScriptQueryResult, ScriptResourceRegistration, ScriptTypeRegistration, WorldCallbackAccess,
};
use error::InteropError;
use reflection_extensions::{PartialReflectExt, TypeIdExtensions};
@@ -32,63 +31,88 @@ pub fn register_world_functions(reg: &mut World) -> Result<(), FunctionRegistrat
.register(
"get_type_by_name",
|world: WorldCallbackAccess, type_name: String| {
- let val = world.get_type_by_name(type_name)?;
- Ok(val.map(Val))
+ let world = world.try_read()?;
+ let val = world.get_type_by_name(type_name);
+
+ Ok(match val {
+ Some(registration) => {
+ let allocator = world.allocator();
+
+ let registration = match world.get_resource_type(registration) {
+ Ok(res) => {
+ let mut allocator = allocator.write();
+ return Ok(Some(ReflectReference::new_allocated(
+ res,
+ &mut allocator,
+ )));
+ }
+ Err(registration) => registration,
+ };
+
+ let registration = match world.get_component_type(registration) {
+ Ok(comp) => {
+ let mut allocator = allocator.write();
+ return Ok(Some(ReflectReference::new_allocated(
+ comp,
+ &mut allocator,
+ )));
+ }
+ Err(registration) => registration,
+ };
+
+ let mut allocator = allocator.write();
+ Some(ReflectReference::new_allocated(
+ registration,
+ &mut allocator,
+ ))
+ }
+ None => None,
+ })
},
)
.register(
"get_component",
|world: WorldCallbackAccess,
entity: Val,
- registration: Val| {
- registration
- .component_id()
- .and_then(|id| world.get_component(*entity, id).transpose())
- .transpose()
+ registration: Val| {
+ world.get_component(*entity, registration.component_id())
},
)
.register(
"has_component",
- |s: WorldCallbackAccess,
+ |world: WorldCallbackAccess,
entity: Val,
- registration: Val| {
- match registration.component_id() {
- Some(id) => s.has_component(*entity, id),
- None => Ok(false),
- }
+ registration: Val| {
+ world.has_component(*entity, registration.component_id())
},
)
.register(
"remove_component",
- |s: WorldCallbackAccess, e: Val, r: Val| {
- s.remove_component(*e, r.clone())
+ |world: WorldCallbackAccess, e: Val, r: Val