From bdffa8e3d8c36698891f25df9c17486e03b98956 Mon Sep 17 00:00:00 2001 From: makspll Date: Sun, 19 Jan 2025 12:34:31 +0000 Subject: [PATCH 01/14] add information on context sharing --- crates/bevy_mod_scripting_core/src/lib.rs | 10 ++++++++ docs/src/SUMMARY.md | 1 + .../sharing-contexts-between-scripts.md | 24 +++++++++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 docs/src/Summary/sharing-contexts-between-scripts.md diff --git a/crates/bevy_mod_scripting_core/src/lib.rs b/crates/bevy_mod_scripting_core/src/lib.rs index 64ec4ce5ad..fbec1d4b59 100644 --- a/crates/bevy_mod_scripting_core/src/lib.rs +++ b/crates/bevy_mod_scripting_core/src/lib.rs @@ -143,6 +143,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 +172,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/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index ced174f02a..27e751c066 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -8,6 +8,7 @@ - [Running Scripts](./Summary/running-scripts.md) - [Controlling Script Bindings](./Summary/controlling-script-bindings.md) - [Modifying Script Contexts](./Summary/customizing-script-contexts.md) +- [Shared Contexts](./Summary/sharing-contexts-between-scripts.md) # Scripting Reference diff --git a/docs/src/Summary/sharing-contexts-between-scripts.md b/docs/src/Summary/sharing-contexts-between-scripts.md new file mode 100644 index 0000000000..e0fb9951ba --- /dev/null +++ b/docs/src/Summary/sharing-contexts-between-scripts.md @@ -0,0 +1,24 @@ +# Shared Contexts + +By default BMS will create an individual script context, or sandbox, for each script that is run. This means that each script will have its own set of global variables and functions that are isolated from other scripts. However, sometimes this might not be desirable, if you aren't worried about scripts interfering with each other, or if you want to easilly share data between scripts. In these cases, you can use shared contexts. + +## Enabling Shared Contexts + +You can enable shared contexts by configuring the relevant scripting plugin like so: +```rust,ignore +let mut plugin = LuaScriptingPlugin::default().enable_context_sharing(); + +app.add_plugins(plugin); +``` + +## Context Loading Settings + +All context loading settings are stored in a separate resource per scripting plugin namely: `ContextLoadingSettings`. + +The settings are as follows: +- `loader` - the load and unload strategy for contexts. Each scripting plugin will have a load and unload function which is hooked up through here +- `assigner` - the strategy for assigning/unassigning contexts to scripts. This is used to determine how to assign a context to a script when it is run, and what to do with the context when the script is finished. +- `context_initializers` - stores all context initializers for the plugin +- `context_pre_handling_initializers` - stores all context pre-handling initializers for the plugin + +More advanced applications might want to customize these settings to suit their needs. \ No newline at end of file From 362e281b8a7b80a4640b32075ececc05668c9230 Mon Sep 17 00:00:00 2001 From: makspll Date: Sun, 19 Jan 2025 12:35:58 +0000 Subject: [PATCH 02/14] expand docs slightly --- docs/src/Summary/customizing-script-contexts.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/src/Summary/customizing-script-contexts.md b/docs/src/Summary/customizing-script-contexts.md index 2419989fbb..4aefd4bb07 100644 --- a/docs/src/Summary/customizing-script-contexts.md +++ b/docs/src/Summary/customizing-script-contexts.md @@ -4,6 +4,7 @@ You should be able to achieve what you need by registering script functions in m This is possible using `Context Initializers` and `Context Pre Handling Initializers` as well as `Runtime Initializers`. +It is however always reccomened to use the dynamic script function registry whenever possible, as it is more flexible and easier to use. It also allows you to introspect available functions easier. ## Context Initializers From f12a039aff00bea60899e565a0d6d88cefe5108a Mon Sep 17 00:00:00 2001 From: makspll Date: Sun, 19 Jan 2025 14:09:54 +0000 Subject: [PATCH 03/14] apply heavy clippy lints to remove all panics --- Cargo.toml | 5 +++++ clippy.toml | 3 +++ crates/bevy_api_gen/Cargo.toml | 2 -- crates/bevy_mod_scripting_core/Cargo.toml | 3 +++ crates/bevy_mod_scripting_functions/Cargo.toml | 3 +++ crates/languages/bevy_mod_scripting_lua/Cargo.toml | 3 +++ crates/languages/bevy_mod_scripting_rhai/Cargo.toml | 3 +++ crates/languages/bevy_mod_scripting_rune/Cargo.toml | 3 +++ 8 files changed, 23 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e5fb9614ab..d418ad5aa4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -117,3 +117,8 @@ debug = true name = "game_of_life" path = "examples/game_of_life.rs" required-features = ["lua54", "bevy/file_watcher", "bevy/multi_threaded"] + +[workspace.lints.clippy] +panic = "deny" +unwrap_used = "deny" +expect_used = "deny" diff --git a/clippy.toml b/clippy.toml index aca2b0afcd..de6e16533d 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,2 +1,5 @@ type-complexity-threshold = 1000 too-many-arguments-threshold = 999 +allow-panic-in-tests = true +allow-unwrap-in-tests = true +allow-expect-in-tests = true diff --git a/crates/bevy_api_gen/Cargo.toml b/crates/bevy_api_gen/Cargo.toml index fc2979fa75..5773d66271 100644 --- a/crates/bevy_api_gen/Cargo.toml +++ b/crates/bevy_api_gen/Cargo.toml @@ -58,5 +58,3 @@ chrono = "0.4" [build-dependencies] toml = "0.8" - -[workspace] diff --git a/crates/bevy_mod_scripting_core/Cargo.toml b/crates/bevy_mod_scripting_core/Cargo.toml index 60db85e454..0dc50f7624 100644 --- a/crates/bevy_mod_scripting_core/Cargo.toml +++ b/crates/bevy_mod_scripting_core/Cargo.toml @@ -39,3 +39,6 @@ itertools = "0.13" [dev-dependencies] test_utils = { workspace = true } + +[lints] +workspace = true 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/languages/bevy_mod_scripting_lua/Cargo.toml b/crates/languages/bevy_mod_scripting_lua/Cargo.toml index 95ec0bd776..bf3b72d071 100644 --- a/crates/languages/bevy_mod_scripting_lua/Cargo.toml +++ b/crates/languages/bevy_mod_scripting_lua/Cargo.toml @@ -54,3 +54,6 @@ regex = "1.11" [[test]] name = "lua_tests" harness = false + +[lints] +workspace = true diff --git a/crates/languages/bevy_mod_scripting_rhai/Cargo.toml b/crates/languages/bevy_mod_scripting_rhai/Cargo.toml index 14dfdbb75b..62e677fc0b 100644 --- a/crates/languages/bevy_mod_scripting_rhai/Cargo.toml +++ b/crates/languages/bevy_mod_scripting_rhai/Cargo.toml @@ -30,3 +30,6 @@ regex = "1.11" [[test]] name = "rhai_tests" harness = false + +[lints] +workspace = true diff --git a/crates/languages/bevy_mod_scripting_rune/Cargo.toml b/crates/languages/bevy_mod_scripting_rune/Cargo.toml index 7f6fecd361..9b8494803a 100644 --- a/crates/languages/bevy_mod_scripting_rune/Cargo.toml +++ b/crates/languages/bevy_mod_scripting_rune/Cargo.toml @@ -20,3 +20,6 @@ bevy = { workspace = true, default-features = false } rune = "0.13.1" rune-modules = "0.13.1" anyhow = "1.0.75" + +[lints] +workspace = true From e284705b4e54eda3a3369e4243f0a9cca8192566 Mon Sep 17 00:00:00 2001 From: makspll Date: Sun, 19 Jan 2025 15:06:52 +0000 Subject: [PATCH 04/14] remove event handler panics --- crates/bevy_mod_scripting_core/src/error.rs | 21 +++++ .../bevy_mod_scripting_core/src/extractors.rs | 66 ++++++++++++++ crates/bevy_mod_scripting_core/src/handler.rs | 88 ++++++++++--------- crates/bevy_mod_scripting_core/src/lib.rs | 1 + 4 files changed, 136 insertions(+), 40 deletions(-) create mode 100644 crates/bevy_mod_scripting_core/src/extractors.rs diff --git a/crates/bevy_mod_scripting_core/src/error.rs b/crates/bevy_mod_scripting_core/src/error.rs index bb14ac623d..d38159e1df 100644 --- a/crates/bevy_mod_scripting_core/src/error.rs +++ b/crates/bevy_mod_scripting_core/src/error.rs @@ -241,6 +241,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); 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..12d2d80d35 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,20 +138,43 @@ 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 }); diff --git a/crates/bevy_mod_scripting_core/src/lib.rs b/crates/bevy_mod_scripting_core/src/lib.rs index fbec1d4b59..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; From fea0442f8cdfabb89bc09183a517f7b7cdcdc734 Mon Sep 17 00:00:00 2001 From: makspll Date: Sun, 19 Jan 2025 16:52:30 +0000 Subject: [PATCH 05/14] remove panics from commands --- .../bevy_mod_scripting_core/src/commands.rs | 344 +++++++++--------- crates/bevy_mod_scripting_core/src/handler.rs | 1 - 2 files changed, 170 insertions(+), 175 deletions(-) diff --git a/crates/bevy_mod_scripting_core/src/commands.rs b/crates/bevy_mod_scripting_core/src/commands.rs index d9357353ca..a26b2df197 100644 --- a/crates/bevy_mod_scripting_core/src/commands.rs +++ b/crates/bevy_mod_scripting_core/src/commands.rs @@ -1,13 +1,12 @@ use crate::{ asset::ScriptAsset, - context::{ContextLoadingSettings, ScriptContexts}, event::{IntoCallbackLabel, OnScriptLoaded, OnScriptUnloaded}, - handler::{handle_script_errors, CallbackSettings, HandlerFn}, - runtime::RuntimeContainer, - script::{Script, ScriptId, Scripts}, + extractors::{extract_handler_context, yield_handler_context, HandlerContext}, + handler::handle_script_errors, + script::{Script, ScriptId}, IntoScriptPluginParams, }; -use bevy::{asset::Handle, ecs::world::Mut, log::debug, prelude::Command}; +use bevy::{asset::Handle, log::debug, prelude::Command}; use std::marker::PhantomData; pub struct DeleteScript { @@ -27,66 +26,74 @@ impl 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/handler.rs b/crates/bevy_mod_scripting_core/src/handler.rs index 12d2d80d35..c3061ef248 100644 --- a/crates/bevy_mod_scripting_core/src/handler.rs +++ b/crates/bevy_mod_scripting_core/src/handler.rs @@ -179,7 +179,6 @@ pub(crate) fn handle_script_errors + Clone>( 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)); From de854d57063a364463354449e0140cd15deb2067 Mon Sep 17 00:00:00 2001 From: makspll Date: Sun, 19 Jan 2025 20:21:39 +0000 Subject: [PATCH 06/14] DONT PANIC --- .../src/bindings/access_map.rs | 62 +++--- .../src/bindings/function/from.rs | 20 +- .../src/bindings/function/from_ref.rs | 11 +- .../src/bindings/function/into.rs | 6 +- .../src/bindings/function/into_ref.rs | 6 +- .../src/bindings/function/script_function.rs | 44 +++- .../src/bindings/pretty_print.rs | 53 ++++- .../src/bindings/query.rs | 116 +++++----- .../src/bindings/reference.rs | 79 ++++--- .../src/bindings/world.rs | 184 ++++++++++------ crates/bevy_mod_scripting_core/src/error.rs | 97 +++++--- .../src/reflection_extensions.rs | 37 ++-- .../bevy_mod_scripting_functions/src/core.rs | 208 ++++++++++-------- .../src/bindings/reference.rs | 66 +++--- .../src/bindings/script_value.rs | 4 +- .../bevy_mod_scripting_lua/src/lib.rs | 4 +- .../bevy_mod_scripting_lua/tests/lua_tests.rs | 3 +- .../src/test_functions.rs | 26 ++- 18 files changed, 638 insertions(+), 388 deletions(-) diff --git a/crates/bevy_mod_scripting_core/src/bindings/access_map.rs b/crates/bevy_mod_scripting_core/src/bindings/access_map.rs index 8dbbc39208..543712fda8 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/access_map.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/access_map.rs @@ -7,6 +7,8 @@ use bevy::{ use dashmap::{DashMap, Entry}; use smallvec::SmallVec; +use crate::error::InteropError; + use super::{ReflectAllocationId, ReflectBase}; #[derive(Debug, Clone, PartialEq, Eq)] @@ -80,8 +82,8 @@ pub enum ReflectAccessKind { /// for script owned values this is an allocationId, this is used to ensure we have permission to access the value. #[derive(PartialEq, Eq, Copy, Clone, Hash, Debug)] pub struct ReflectAccessId { - kind: ReflectAccessKind, - id: u64, + pub(crate) kind: ReflectAccessKind, + pub(crate) id: u64, } impl AccessMapKey for ReflectAccessId { @@ -111,19 +113,25 @@ impl AccessMapKey for ReflectAccessId { } impl ReflectAccessId { - pub fn for_resource(cell: &UnsafeWorldCell) -> Option { - Some(Self { + pub fn for_resource(cell: &UnsafeWorldCell) -> Result { + let resource_id = cell.components().resource_id::().ok_or_else(|| { + InteropError::unregistered_component_or_resource_type(std::any::type_name::()) + })?; + + Ok(Self { kind: ReflectAccessKind::ComponentOrResource, - id: cell.components().resource_id::()?.index() as u64, + id: resource_id.index() as u64, }) } pub fn for_component( cell: &UnsafeWorldCell, - ) -> Option { - let component_id = cell.components().component_id::()?; + ) -> Result { + let component_id = cell.components().component_id::().ok_or_else(|| { + InteropError::unregistered_component_or_resource_type(std::any::type_name::()) + })?; - Some(Self::for_component_id(component_id)) + Ok(Self::for_component_id(component_id)) } pub fn for_allocation(id: ReflectAllocationId) -> Self { @@ -140,11 +148,11 @@ impl ReflectAccessId { } } - pub fn for_reference(base: ReflectBase) -> Option { + pub fn for_reference(base: ReflectBase) -> Self { match base { - ReflectBase::Resource(id) => Some(Self::for_component_id(id)), - ReflectBase::Component(_, id) => Some(Self::for_component_id(id)), - ReflectBase::Owned(id) => Some(Self::for_allocation(id)), + ReflectBase::Resource(id) => Self::for_component_id(id), + ReflectBase::Component(_, id) => Self::for_component_id(id), + ReflectBase::Owned(id) => Self::for_allocation(id), } } } @@ -173,6 +181,12 @@ impl From for ComponentId { } } +impl From for ReflectAllocationId { + fn from(val: ReflectAccessId) -> Self { + ReflectAllocationId::new(val.id) + } +} + #[derive(Debug, Default)] pub struct AccessMap { individual_accesses: DashMap, @@ -323,17 +337,15 @@ impl DisplayCodeLocation for Option> { macro_rules! with_access_read { ($access_map:expr, $id:expr, $msg:expr, $body:block) => {{ if !$access_map.claim_read_access($id) { - panic!( - "{}. Aliasing access is held somewhere else: {}", + Err($crate::error::InteropError::cannot_claim_access( + $id, + $access_map.access_location($id), $msg, - $crate::bindings::access_map::DisplayCodeLocation::display_location( - $access_map.access_location($id) - ) - ); + )) } else { let result = $body; $access_map.release_access($id); - result + Ok(result) } }}; } @@ -342,17 +354,15 @@ macro_rules! with_access_read { macro_rules! with_access_write { ($access_map:expr, $id:expr, $msg:expr, $body:block) => { if !$access_map.claim_write_access($id) { - panic!( - "{}. Aliasing access is held somewhere else: {}", + Err($crate::error::InteropError::cannot_claim_access( + $id, + $access_map.access_location($id), $msg, - $crate::bindings::access_map::DisplayCodeLocation::display_location( - $access_map.access_location($id) - ) - ); + )) } else { let result = $body; $access_map.release_access($id); - result + Ok(result) } }; } diff --git a/crates/bevy_mod_scripting_core/src/bindings/function/from.rs b/crates/bevy_mod_scripting_core/src/bindings/function/from.rs index e97a37990a..2cf79ce2ad 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/function/from.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/function/from.rs @@ -114,7 +114,9 @@ impl FromScript for char { { match value { ScriptValue::Integer(i) => Ok(i as u8 as char), - ScriptValue::String(c) if c.len() == 1 => Ok(c.chars().next().expect("invariant")), + ScriptValue::String(c) => c.chars().next().ok_or_else(|| { + InteropError::value_mismatch(TypeId::of::(), ScriptValue::String(c)) + }), ScriptValue::Reference(r) => r.downcast::(world), _ => Err(InteropError::value_mismatch( std::any::TypeId::of::(), @@ -226,10 +228,7 @@ impl FromScript for Ref<'_, T> { ) -> Result, InteropError> { match value { ScriptValue::Reference(reflect_reference) => { - let raid = ReflectAccessId::for_reference(reflect_reference.base.base_id.clone()) - .ok_or_else(|| { - InteropError::unregistered_base(reflect_reference.base.clone()) - })?; + let raid = ReflectAccessId::for_reference(reflect_reference.base.base_id.clone()); if world.claim_read_access(raid) { // Safety: we just claimed access @@ -243,8 +242,9 @@ impl FromScript for Ref<'_, T> { Ok(Ref(cast)) } else { Err(InteropError::cannot_claim_access( - reflect_reference.base, + raid, world.get_access_location(raid), + format!("In conversion to type: Ref<{}>", std::any::type_name::()), )) } } @@ -300,10 +300,7 @@ impl FromScript for Mut<'_, T> { ) -> Result, InteropError> { match value { ScriptValue::Reference(reflect_reference) => { - let raid = ReflectAccessId::for_reference(reflect_reference.base.base_id.clone()) - .ok_or_else(|| { - InteropError::unregistered_base(reflect_reference.base.clone()) - })?; + let raid = ReflectAccessId::for_reference(reflect_reference.base.base_id.clone()); if world.claim_write_access(raid) { // Safety: we just claimed write access @@ -315,8 +312,9 @@ impl FromScript for Mut<'_, T> { Ok(Mut(cast)) } else { Err(InteropError::cannot_claim_access( - reflect_reference.base, + raid, world.get_access_location(raid), + format!("In conversion to type: Mut<{}>", std::any::type_name::()), )) } } diff --git a/crates/bevy_mod_scripting_core/src/bindings/function/from_ref.rs b/crates/bevy_mod_scripting_core/src/bindings/function/from_ref.rs index cdab793254..36c3bd1fd2 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/function/from_ref.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/function/from_ref.rs @@ -67,12 +67,11 @@ impl FromScriptRef for Box { ) })?; - if type_info.is_option() { - let inner_type = type_info.option_inner_type().expect("invariant"); + if let Some(inner_option_type) = type_info.option_inner_type() { let mut dynamic_enum = match value { ScriptValue::Unit => DynamicEnum::new("None", DynamicVariant::Unit), _ => { - let inner = Self::from_script_ref(inner_type, value, world)?; + let inner = Self::from_script_ref(inner_option_type, value, world)?; DynamicEnum::new( "Some", DynamicVariant::Tuple(DynamicTuple::from_iter(vec![inner])), @@ -84,13 +83,11 @@ impl FromScriptRef for Box { return Ok(Box::new(dynamic_enum)); } - if type_info.is_list() { - let inner_type = type_info.list_inner_type().expect("invariant"); - + if let Some(inner_list_type) = type_info.list_inner_type() { if let ScriptValue::List(vec) = value { let mut dynamic_list = DynamicList::default(); for item in vec { - let inner = Self::from_script_ref(inner_type, item, world.clone())?; + let inner = Self::from_script_ref(inner_list_type, item, world.clone())?; dynamic_list.push_box(inner); } diff --git a/crates/bevy_mod_scripting_core/src/bindings/function/into.rs b/crates/bevy_mod_scripting_core/src/bindings/function/into.rs index 9dc44b4ab5..4ec5852198 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/function/into.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/function/into.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, ffi::OsString, path::PathBuf}; -use bevy::reflect::PartialReflect; +use bevy::reflect::Reflect; use crate::{ bindings::{script_value::ScriptValue, ReflectReference, WorldGuard}, @@ -112,9 +112,9 @@ impl IntoScript for ReflectReference { } } -impl IntoScript for Val { +impl IntoScript for Val { fn into_script(self, world: WorldGuard) -> Result { - let boxed: Box = Box::new(self.0); + let boxed = Box::new(self.0); let allocator = world.allocator(); let mut allocator = allocator.write(); diff --git a/crates/bevy_mod_scripting_core/src/bindings/function/into_ref.rs b/crates/bevy_mod_scripting_core/src/bindings/function/into_ref.rs index f95bdb3b45..f95635a55a 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/function/into_ref.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/function/into_ref.rs @@ -1,6 +1,6 @@ use std::{ffi::OsString, path::PathBuf}; -use bevy::reflect::{ParsedPath, PartialReflect}; +use bevy::reflect::{Access, PartialReflect}; use crate::{ bindings::{function::into::IntoScript, ReflectReference, WorldGuard}, @@ -99,7 +99,7 @@ fn into_script_ref( // either return nil or ref into if let Ok(as_option) = r.as_option() { return if let Some(s) = as_option { - self_.index_path(ParsedPath::parse_static(".0").expect("invariant")); + self_.index_path(vec![FIRST_TUPLE_FIELD_ACCESS]); into_script_ref(self_, s, world) } else { Ok(ScriptValue::Unit) @@ -108,3 +108,5 @@ fn into_script_ref( Ok(ScriptValue::Reference(self_)) } + +const FIRST_TUPLE_FIELD_ACCESS: Access = Access::TupleIndex(0); diff --git a/crates/bevy_mod_scripting_core/src/bindings/function/script_function.rs b/crates/bevy_mod_scripting_core/src/bindings/function/script_function.rs index ff8b3d0def..2e1768c87f 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/function/script_function.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/function/script_function.rs @@ -398,6 +398,9 @@ impl ScriptFunctionRegistry { } /// Remove a function from the registry if it exists. Returns the removed function if it was found. + /// + /// Note if the function is overloaded, you will need to remove each overload individually. + /// Use [`ScriptFunctionRegistry::remove_all_overloads`] to remove all overloads at once. pub fn remove( &mut self, namespace: Namespace, @@ -407,6 +410,21 @@ impl ScriptFunctionRegistry { self.functions.remove(&FunctionKey { name, namespace }) } + pub fn remove_all_overloads( + &mut self, + namespace: Namespace, + name: impl Into>, + ) -> Result, Cow<'static, str>> { + let overloads: Vec<_> = self.iter_overloads(namespace, name)?.cloned().collect(); + for overload in overloads.iter() { + self.functions.remove(&FunctionKey { + name: overload.info.name.clone(), + namespace, + }); + } + Ok(overloads) + } + fn register_overload( &mut self, namespace: Namespace, @@ -428,17 +446,13 @@ impl ScriptFunctionRegistry { return; } - for i in 1..16 { + for i in 1.. { let overload = format!("{name}-{i}"); if !self.contains(namespace, overload.clone()) { self.register(namespace, overload, func); return; } } - - panic!( - "Could not register overload for function {name}. Maximum number of overloads reached" - ); } pub fn contains(&self, namespace: Namespace, name: impl Into>) -> bool { @@ -476,7 +490,7 @@ impl ScriptFunctionRegistry { Err(name) => return Err(name), }; - let overloads = (1..16) + let overloads = (1..) .map(move |i| { if i == 0 { self.get_function(namespace, name.clone()) @@ -486,7 +500,7 @@ impl ScriptFunctionRegistry { } }) .take_while(|o| o.is_ok()) - .map(|o| o.unwrap()); + .filter_map(|o| o.ok()); Ok(seed.chain(overloads)) } @@ -691,4 +705,20 @@ mod test { let removed = registry.remove(namespace, "test"); assert!(removed.is_none()); } + + #[test] + fn test_remove_all_overloads() { + let mut registry = ScriptFunctionRegistry::default(); + let fn_ = |a: usize, b: usize| a + b; + let namespace = Namespace::Global; + registry.register(namespace, "test", fn_); + let fn_2 = |a: usize, b: i32| a + (b as usize); + registry.register(namespace, "test", fn_2); + + let removed = registry + .remove_all_overloads(namespace, "test") + .expect("Failed to remove overloads"); + assert_eq!(removed.len(), 2); + assert!(registry.get_function(namespace, "test").is_err()); + } } diff --git a/crates/bevy_mod_scripting_core/src/bindings/pretty_print.rs b/crates/bevy_mod_scripting_core/src/bindings/pretty_print.rs index d32c741e3f..35e1090ed3 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/pretty_print.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/pretty_print.rs @@ -1,9 +1,11 @@ use crate::reflection_extensions::{FakeType, TypeIdExtensions}; use super::{ - script_value::ScriptValue, ReflectBase, ReflectBaseType, ReflectReference, WorldGuard, + access_map::ReflectAccessId, script_value::ScriptValue, ReflectAllocationId, ReflectBase, + ReflectBaseType, ReflectReference, WorldGuard, }; use bevy::{ + ecs::component::ComponentId, prelude::World, reflect::{PartialReflect, ReflectRef}, }; @@ -363,6 +365,55 @@ impl DisplayWithWorld for ReflectBaseType { } } +impl DisplayWithWorld for ComponentId { + fn display_without_world(&self) -> String { + format!("ComponentOrResource({})", self.index()) + } + + fn display_with_world(&self, world: WorldGuard) -> String { + let component_name = world + .as_unsafe_world_cell() + .components() + .get_info(*self) + .map(|info| info.name()); + + match component_name { + Some(n) => format!("ComponentOrResource({})", n), + None => "ComponentOrResource()".to_owned(), + } + } +} + +impl DisplayWithWorld for ReflectAccessId { + fn display_without_world(&self) -> String { + match self.kind { + super::access_map::ReflectAccessKind::ComponentOrResource => { + let component_id = ComponentId::from(*self); + component_id.display_without_world() + } + super::access_map::ReflectAccessKind::Allocation => { + format!("Allocation({})", self.id) + } + } + } + + fn display_with_world(&self, world: WorldGuard) -> String { + match self.kind { + super::access_map::ReflectAccessKind::ComponentOrResource => { + let component_id = ComponentId::from(*self); + component_id.display_with_world(world) + } + super::access_map::ReflectAccessKind::Allocation => { + let allocation_id = ReflectAllocationId::from(*self); + let allocator = world.allocator(); + let allocator = allocator.read(); + let type_id = allocator.get_type_id(&allocation_id).or_fake_id(); + format!("Allocation({})", type_id.display_with_world(world)) + } + } + } +} + impl DisplayWithWorld for TypeId { fn display_with_world(&self, world: WorldGuard) -> String { if *self == TypeId::of::() { diff --git a/crates/bevy_mod_scripting_core/src/bindings/query.rs b/crates/bevy_mod_scripting_core/src/bindings/query.rs index d6a438f819..2f7fa31a4e 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/query.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/query.rs @@ -14,21 +14,23 @@ use std::{any::TypeId, collections::VecDeque, sync::Arc}; #[reflect(opaque)] pub struct ScriptTypeRegistration { pub(crate) registration: Arc, - pub component_id: Option, - pub resource_id: Option, +} + +#[derive(Clone, Reflect, Debug)] +pub struct ScriptComponentRegistration { + pub(crate) registration: ScriptTypeRegistration, + pub(crate) component_id: ComponentId, +} + +#[derive(Clone, Reflect, Debug)] +pub struct ScriptResourceRegistration { + pub(crate) registration: ScriptTypeRegistration, + pub(crate) resource_id: ComponentId, } impl ScriptTypeRegistration { - pub fn new( - registration: Arc, - component_id: Option, - resource_id: Option, - ) -> Self { - Self { - registration, - component_id, - resource_id, - } + pub fn new(registration: Arc) -> Self { + Self { registration } } #[inline(always)] @@ -46,17 +48,48 @@ impl ScriptTypeRegistration { self.registration.type_info().type_id() } - /// Returns the [`ComponentId`] for this type, if it is a component. - #[inline(always)] - pub fn component_id(&self) -> Option { - self.component_id + pub fn type_registration(&self) -> &TypeRegistration { + &self.registration + } +} +impl ScriptResourceRegistration { + pub fn new(registration: ScriptTypeRegistration, resource_id: ComponentId) -> Self { + Self { + registration, + resource_id, + } } /// Returns the [`ComponentId`] for this type, if it is a resource. #[inline(always)] - pub fn resource_id(&self) -> Option { + pub fn resource_id(&self) -> ComponentId { self.resource_id } + + /// Returns the [`ScriptTypeRegistration`] for this type. + pub fn type_registration(&self) -> &ScriptTypeRegistration { + &self.registration + } +} + +impl ScriptComponentRegistration { + pub fn new(registration: ScriptTypeRegistration, component_id: ComponentId) -> Self { + Self { + registration, + component_id, + } + } + + /// Returns the [`ComponentId`] for this type, if it is a component. + #[inline(always)] + pub fn component_id(&self) -> ComponentId { + self.component_id + } + + /// Returns the [`ScriptTypeRegistration`] for this type. + pub fn type_registration(&self) -> &ScriptTypeRegistration { + &self.registration + } } impl std::fmt::Debug for ScriptTypeRegistration { @@ -76,37 +109,37 @@ impl std::fmt::Display for ScriptTypeRegistration { #[derive(Clone, Default, Reflect)] #[reflect(opaque)] pub struct ScriptQueryBuilder { - components: Vec, - with: Vec, - without: Vec, + components: Vec, + with: Vec, + without: Vec, } impl ScriptQueryBuilder { - pub fn components(&mut self, components: Vec) -> &mut Self { + pub fn components(&mut self, components: Vec) -> &mut Self { self.components.extend(components); self } - pub fn component(&mut self, component: ScriptTypeRegistration) -> &mut Self { + pub fn component(&mut self, component: ScriptComponentRegistration) -> &mut Self { self.components.push(component); self } - pub fn with_components(&mut self, with: Vec) -> &mut Self { + pub fn with_components(&mut self, with: Vec) -> &mut Self { self.with.extend(with); self } - pub fn with_component(&mut self, with: ScriptTypeRegistration) -> &mut Self { + pub fn with_component(&mut self, with: ScriptComponentRegistration) -> &mut Self { self.with.push(with); self } - pub fn without_components(&mut self, without: Vec) -> &mut Self { + pub fn without_components(&mut self, without: Vec) -> &mut Self { self.without.extend(without); self } - pub fn without_component(&mut self, without: ScriptTypeRegistration) -> &mut Self { + pub fn without_component(&mut self, without: ScriptComponentRegistration) -> &mut Self { self.without.push(without); self } @@ -142,33 +175,15 @@ impl WorldAccessGuard<'_> { // which entities match the query // so we might be being slightly overkill for c in &query.components { - dynamic_query.ref_id(c.component_id().ok_or_else(|| { - InteropError::unsupported_operation( - Some(c.type_id()), - None, - "query for component on non-component type".to_owned(), - ) - })?); + dynamic_query.ref_id(c.component_id()); } for w in query.with { - dynamic_query.with_id(w.component_id.ok_or_else(|| { - InteropError::unsupported_operation( - Some(w.type_id()), - None, - "query for entity with component which is non-component type".to_owned(), - ) - })?); + dynamic_query.with_id(w.component_id()); } for without_id in query.without { - dynamic_query.without_id(without_id.component_id.ok_or_else(|| { - InteropError::unsupported_operation( - Some(without_id.type_id()), - None, - "query for entity without component which is non-component type".to_owned(), - ) - })?); + dynamic_query.without_id(without_id.component_id()); } let mut built_query = dynamic_query.build(); @@ -181,11 +196,8 @@ impl WorldAccessGuard<'_> { .iter() .map(|c| ReflectReference { base: super::ReflectBaseType { - type_id: c.type_id(), - base_id: super::ReflectBase::Component( - r.id(), - c.component_id.unwrap(), - ), + type_id: c.type_registration().type_id(), + base_id: super::ReflectBase::Component(r.id(), c.component_id()), }, reflect_path: ParsedPath(vec![]), }) diff --git a/crates/bevy_mod_scripting_core/src/bindings/reference.rs b/crates/bevy_mod_scripting_core/src/bindings/reference.rs index d1709cf8e6..fe24ed6e96 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/reference.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/reference.rs @@ -85,19 +85,11 @@ impl ReflectReference { /// You can retrieve the allocator from the world using [`WorldGuard::allocator`]. /// Make sure to drop the allocator write guard before doing anything with the reference to prevent deadlocks. /// - pub fn new_allocated( + pub fn new_allocated( value: T, allocator: &mut ReflectAllocator, ) -> ReflectReference { - let type_id = value - .get_represented_type_info() - .map(|i| i.type_id()) - .unwrap_or_else(|| { - panic!( - "Type '{}' has no represented type information to allocate with.", - std::any::type_name::() - ) - }); + let type_id = std::any::TypeId::of::(); let id = allocator.allocate(value); ReflectReference { base: ReflectBaseType { @@ -108,20 +100,35 @@ impl ReflectReference { } } - pub fn new_allocated_boxed( + /// Create a new reference to a value by allocating it. + /// + /// Prefer using [`Self::new_allocated`] if you have a value that implements [`Reflect`]. + /// Will fail if the value does not have represented type info with a specific type id. + pub fn new_allocated_boxed_parial_reflect( value: Box, allocator: &mut ReflectAllocator, + ) -> Result { + match value.get_represented_type_info() { + Some(i) => { + let id = allocator.allocate_boxed(value); + Ok(ReflectReference { + base: ReflectBaseType { + type_id: i.type_id(), + base_id: ReflectBase::Owned(id), + }, + reflect_path: ParsedPath(Vec::default()), + }) + } + None => Err(InteropError::unsupported_operation(None, Some(value), "Tried to create a reference to a partial reflect value with no represented type info")), + } + } + + pub fn new_allocated_boxed( + value: Box, + allocator: &mut ReflectAllocator, ) -> ReflectReference { - let type_id = value - .get_represented_type_info() - .map(|i| i.type_id()) - .unwrap_or_else(|| { - panic!( - "Type '{}' has no represented type information to allocate with.", - std::any::type_name_of_val(value.as_ref()) - ) - }); - let id = allocator.allocate_boxed(value); + let type_id = value.type_id(); + let id = allocator.allocate_boxed(value.into_partial_reflect()); ReflectReference { base: ReflectBaseType { type_id, @@ -131,9 +138,9 @@ impl ReflectReference { } } - pub fn new_resource_ref(world: WorldGuard) -> Option { + pub fn new_resource_ref(world: WorldGuard) -> Result { let reflect_id = ReflectAccessId::for_resource::(&world.as_unsafe_world_cell())?; - Some(Self { + Ok(Self { base: ReflectBaseType { type_id: TypeId::of::(), base_id: ReflectBase::Resource(reflect_id.into()), @@ -142,9 +149,12 @@ impl ReflectReference { }) } - pub fn new_component_ref(entity: Entity, world: WorldGuard) -> Option { + pub fn new_component_ref( + entity: Entity, + world: WorldGuard, + ) -> Result { let reflect_id = ReflectAccessId::for_component::(&world.as_unsafe_world_cell())?; - Some(Self { + Ok(Self { base: ReflectBaseType { type_id: TypeId::of::(), base_id: ReflectBase::Component(entity, reflect_id.into()), @@ -220,13 +230,12 @@ impl ReflectReference { world: WorldGuard, f: F, ) -> Result { - let access_id = ReflectAccessId::for_reference(self.base.base_id.clone()) - .ok_or_else(|| InteropError::unregistered_base(self.base.clone()))?; + let access_id = ReflectAccessId::for_reference(self.base.base_id.clone()); with_access_read!( world.0.accesses, access_id, "could not access reflect reference", - { unsafe { self.reflect_unsafe(world.clone()) }.map(f) } + { unsafe { self.reflect_unsafe(world.clone()) }.map(f)? } ) } @@ -241,13 +250,12 @@ impl ReflectReference { world: WorldGuard, f: F, ) -> Result { - let access_id = ReflectAccessId::for_reference(self.base.base_id.clone()) - .ok_or_else(|| InteropError::unregistered_base(self.base.clone()))?; + let access_id = ReflectAccessId::for_reference(self.base.base_id.clone()); with_access_write!( world.0.accesses, access_id, "Could not access reflect reference mutably", - { unsafe { self.reflect_mut_unsafe(world.clone()) }.map(f) } + { unsafe { self.reflect_mut_unsafe(world.clone()) }.map(f)? } ) } @@ -522,8 +530,7 @@ impl ReflectRefIter { let next = match &mut self.index { IterationKey::Index(i) => { let mut next = self.base.clone(); - let parsed_path = - ParsedPath::parse(&format!("[{}]", *i)).expect("invariant violated"); + let parsed_path = ParsedPath::from(vec![list_index_access(*i)]); next.index_path(parsed_path); *i += 1; next @@ -533,6 +540,10 @@ impl ReflectRefIter { } } +const fn list_index_access(index: usize) -> bevy::reflect::Access<'static> { + bevy::reflect::Access::ListIndex(index) +} + impl Iterator for ReflectRefIter { type Item = Result; @@ -541,7 +552,7 @@ impl Iterator for ReflectRefIter { match &mut self.index { IterationKey::Index(i) => { let mut next = self.base.clone(); - let parsed_path = ParsedPath::parse(&i.to_string()).unwrap(); + let parsed_path = ParsedPath::from(vec![list_index_access(*i)]); next.index_path(parsed_path); *i += 1; Ok(next) diff --git a/crates/bevy_mod_scripting_core/src/bindings/world.rs b/crates/bevy_mod_scripting_core/src/bindings/world.rs index 8bc84ca52e..183bddc9ba 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/world.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/world.rs @@ -11,8 +11,10 @@ use super::{ namespace::Namespace, script_function::{AppScriptFunctionRegistry, CallerContext, DynamicScriptFunction}, }, + pretty_print::DisplayWithWorld, script_value::ScriptValue, - AppReflectAllocator, ReflectBase, ReflectBaseType, ReflectReference, ScriptTypeRegistration, + AppReflectAllocator, ReflectBase, ReflectBaseType, ReflectReference, + ScriptComponentRegistration, ScriptResourceRegistration, ScriptTypeRegistration, }; use crate::{error::InteropError, with_access_read, with_access_write, with_global_access}; use bevy::{ @@ -111,10 +113,26 @@ impl WorldCallbackAccess { Ok(world.get_type_by_name(type_name)) } + pub fn get_component_type( + &self, + registration: ScriptTypeRegistration, + ) -> Result, InteropError> { + let world = self.try_read()?; + Ok(world.get_component_type(registration)) + } + + pub fn get_resource_type( + &self, + registration: ScriptTypeRegistration, + ) -> Result, InteropError> { + let world = self.try_read()?; + Ok(world.get_resource_type(registration)) + } + pub fn add_default_component( &self, entity: Entity, - registration: ScriptTypeRegistration, + registration: ScriptComponentRegistration, ) -> Result<(), InteropError> { let world = self.try_read()?; world.add_default_component(entity, registration) @@ -141,7 +159,7 @@ impl WorldCallbackAccess { pub fn remove_component( &self, entity: Entity, - registration: ScriptTypeRegistration, + registration: ScriptComponentRegistration, ) -> Result<(), InteropError> { let world = self.try_read()?; world.remove_component(entity, registration) @@ -157,7 +175,7 @@ impl WorldCallbackAccess { pub fn remove_resource( &self, - registration: ScriptTypeRegistration, + registration: ScriptResourceRegistration, ) -> Result<(), InteropError> { let world = self.try_read()?; world.remove_resource(registration) @@ -251,7 +269,7 @@ impl WorldCallbackAccess { } } - Err(last_error.expect("invariant, iterator should always return at least one item, and if the call fails it should return an error")) + Err(last_error.ok_or_else(|| InteropError::invariant("invariant, iterator should always return at least one item, and if the call fails it should return an error"))?) } } @@ -274,22 +292,21 @@ pub(crate) struct WorldAccessGuardInner<'w> { } impl<'w> WorldAccessGuard<'w> { - /// Creates a new [`WorldAccessGuard`] for the given mutable borrow of the world + /// Creates a new [`WorldAccessGuard`] for the given mutable borrow of the world. + /// + /// Creating a guard requires that some resources exist in the world, namely: + /// - [`AppTypeRegistry`] + /// - [`AppReflectAllocator`] + /// - [`AppScriptFunctionRegistry`] + /// + /// If these resources do not exist, they will be initialized. pub fn new(world: &'w mut World) -> Self { - let type_registry = world - .get_resource::() - .expect("Type registry not present, cannot create world access guard") - .0 - .clone(); + let type_registry = world.get_resource_or_init::().0.clone(); - let allocator = world - .get_resource::() - .expect("Reflect allocator not present, cannot create world access guard") - .clone(); + let allocator = world.get_resource_or_init::().clone(); let function_registry = world - .get_resource::() - .expect("Function registry not present, cannot create world access guard") + .get_resource_or_init::() .clone(); Self(Arc::new(WorldAccessGuardInner { @@ -401,13 +418,12 @@ impl<'w> WorldAccessGuard<'w> { /// /// # Panics /// - if the resource does not exist - pub fn with_resource(&self, f: F) -> O + pub fn with_resource(&self, f: F) -> Result where R: Resource, F: FnOnce(&R) -> O, { - let access_id = ReflectAccessId::for_resource::(&self.0.cell) - .unwrap_or_else(|| panic!("Resource does not exist: {}", std::any::type_name::())); + let access_id = ReflectAccessId::for_resource::(&self.0.cell)?; with_access_read!( self.0.accesses, @@ -415,7 +431,13 @@ impl<'w> WorldAccessGuard<'w> { format!("Could not access resource: {}", std::any::type_name::()), { // Safety: we have acquired access for the duration of the closure - f(unsafe { self.0.cell.get_resource::().expect("invariant") }) + f(unsafe { + self.0.cell.get_resource::().ok_or_else(|| { + InteropError::unregistered_component_or_resource_type( + std::any::type_name::(), + ) + })? + }) } ) } @@ -424,21 +446,25 @@ impl<'w> WorldAccessGuard<'w> { /// /// # Panics /// - if the resource does not exist - pub fn with_resource_mut(&self, f: F) -> O + pub fn with_resource_mut(&self, f: F) -> Result where R: Resource, F: FnOnce(Mut) -> O, { - let access_id = ReflectAccessId::for_resource::(&self.0.cell) - .unwrap_or_else(|| panic!("Resource does not exist: {}", std::any::type_name::())); - + let access_id = ReflectAccessId::for_resource::(&self.0.cell)?; with_access_write!( self.0.accesses, access_id, format!("Could not access resource: {}", std::any::type_name::()), { // Safety: we have acquired access for the duration of the closure - f(unsafe { self.0.cell.get_resource_mut::().expect("invariant") }) + f(unsafe { + self.0.cell.get_resource_mut::().ok_or_else(|| { + InteropError::unregistered_component_or_resource_type( + std::any::type_name::(), + ) + })? + }) } ) } @@ -447,14 +473,12 @@ impl<'w> WorldAccessGuard<'w> { /// /// # Panics /// - if the component does not exist - pub fn with_component(&self, entity: Entity, f: F) -> O + pub fn with_component(&self, entity: Entity, f: F) -> Result where T: Component, F: FnOnce(Option<&T>) -> O, { - let access_id = ReflectAccessId::for_component::(&self.0.cell) - .unwrap_or_else(|| panic!("Component does not exist: {}", std::any::type_name::())); - + let access_id = ReflectAccessId::for_component::(&self.0.cell)?; with_access_read!( self.0.accesses, access_id, @@ -470,13 +494,12 @@ impl<'w> WorldAccessGuard<'w> { /// /// # Panics /// - if the component does not exist - pub fn with_component_mut(&self, entity: Entity, f: F) -> O + pub fn with_component_mut(&self, entity: Entity, f: F) -> Result where T: Component, F: FnOnce(Option>) -> O, { - let access_id = ReflectAccessId::for_component::(&self.0.cell) - .unwrap_or_else(|| panic!("Component does not exist: {}", std::any::type_name::())); + let access_id = ReflectAccessId::for_component::(&self.0.cell)?; with_access_write!( self.0.accesses, @@ -538,22 +561,37 @@ impl WorldAccessGuard<'_> { type_registry .get_with_short_type_path(&type_name) .or_else(|| type_registry.get_with_type_path(&type_name)) - .map(|registration| { - ScriptTypeRegistration::new( - Arc::new(registration.clone()), - self.get_component_id(registration.type_id()), - self.get_resource_id(registration.type_id()), - ) - }) + .map(|registration| ScriptTypeRegistration::new(Arc::new(registration.clone()))) + } + + pub fn get_component_type( + &self, + registration: ScriptTypeRegistration, + ) -> Result { + match self.get_component_id(registration.type_id()) { + Some(comp_id) => Ok(ScriptComponentRegistration::new(registration, comp_id)), + None => Err(registration), + } + } + + pub fn get_resource_type( + &self, + registration: ScriptTypeRegistration, + ) -> Result { + match self.get_resource_id(registration.type_id()) { + Some(resource_id) => Ok(ScriptComponentRegistration::new(registration, resource_id)), + None => Err(registration), + } } pub fn add_default_component( &self, entity: Entity, - registration: ScriptTypeRegistration, + registration: ScriptComponentRegistration, ) -> Result<(), InteropError> { let component_data = registration - .registration + .type_registration() + .type_registration() .data::() .ok_or_else(|| { InteropError::missing_type_data( @@ -563,10 +601,17 @@ impl WorldAccessGuard<'_> { })?; // we look for ReflectDefault or ReflectFromWorld data then a ReflectComponent data - let instance = if let Some(default_td) = registration.registration.data::() + let instance = if let Some(default_td) = registration + .type_registration() + .type_registration() + .data::() { default_td.default() - } else if let Some(from_world_td) = registration.registration.data::() { + } else if let Some(from_world_td) = registration + .type_registration() + .type_registration() + .data::() + { with_global_access!(self.0.accesses, "Could not add default component", { let world = unsafe { self.0.cell.world_mut() }; from_world_td.from_world(world) @@ -615,9 +660,16 @@ impl WorldAccessGuard<'_> { if entity.contains_id(component_id) { Ok(Some(ReflectReference { base: ReflectBaseType { - type_id: component_info - .type_id() - .expect("Component does not have type id"), + type_id: component_info.type_id().ok_or_else(|| { + InteropError::unsupported_operation( + None, + None, + format!( + "Component {} does not have a type id. Such components are not supported by BMS.", + component_id.display_without_world() + ), + ) + })?, base_id: ReflectBase::Component(entity.id(), component_id), }, reflect_path: ParsedPath(vec![]), @@ -644,10 +696,11 @@ impl WorldAccessGuard<'_> { pub fn remove_component( &self, entity: Entity, - registration: ScriptTypeRegistration, + registration: ScriptComponentRegistration, ) -> Result<(), InteropError> { let component_data = registration - .registration + .type_registration() + .type_registration() .data::() .ok_or_else(|| { InteropError::missing_type_data( @@ -680,7 +733,16 @@ impl WorldAccessGuard<'_> { base: ReflectBaseType { type_id: component_info .type_id() - .expect("Resource does not have type id"), + .ok_or_else(|| { + InteropError::unsupported_operation( + None, + None, + format!( + "Resource {} does not have a type id. Such resources are not supported by BMS.", + resource_id.display_without_world() + ), + ) + })?, base_id: ReflectBase::Resource(resource_id), }, reflect_path: ParsedPath(vec![]), @@ -689,10 +751,11 @@ impl WorldAccessGuard<'_> { pub fn remove_resource( &self, - registration: ScriptTypeRegistration, + registration: ScriptResourceRegistration, ) -> Result<(), InteropError> { let component_data = registration - .registration + .type_registration() + .type_registration() .data::() .ok_or_else(|| { InteropError::missing_type_data( @@ -725,7 +788,7 @@ impl WorldAccessGuard<'_> { } self.with_component(entity, |c: Option<&Children>| { - Ok(c.map(|c| c.to_vec()).unwrap_or_default()) + c.map(|c| c.to_vec()).unwrap_or_default() }) } @@ -734,7 +797,7 @@ impl WorldAccessGuard<'_> { return Err(InteropError::missing_entity(entity)); } - self.with_component(entity, |c: Option<&Parent>| Ok(c.map(|c| c.get()))) + self.with_component(entity, |c: Option<&Parent>| c.map(|c| c.get())) } pub fn push_children(&self, parent: Entity, children: &[Entity]) -> Result<(), InteropError> { @@ -871,19 +934,6 @@ pub trait WorldContainer { /// Sets the world to the given value fn set_world(&mut self, world: WorldCallbackAccess) -> Result<(), Self::Error>; - /// Gets the world, use [`WorldContainer::try_get_world`] if you want to handle errors with retrieving the world - /// # Panics - /// - if the world has not been set - /// - if the world has been dropped - fn get_world(&self) -> WorldGuard<'static> { - self.try_get_world().expect("World not set, or expired") - } - - fn get_callback_world(&self) -> WorldCallbackAccess { - self.try_get_callback_world() - .expect("World not set, or expired") - } - /// Tries to get the world fn try_get_world(&self) -> Result>, Self::Error>; diff --git a/crates/bevy_mod_scripting_core/src/error.rs b/crates/bevy_mod_scripting_core/src/error.rs index d38159e1df..64a752cc03 100644 --- a/crates/bevy_mod_scripting_core/src/error.rs +++ b/crates/bevy_mod_scripting_core/src/error.rs @@ -1,5 +1,5 @@ use crate::bindings::{ - access_map::DisplayCodeLocation, function::namespace::Namespace, + access_map::{DisplayCodeLocation, ReflectAccessId}, function::namespace::Namespace, pretty_print::DisplayWithWorld, script_value::ScriptValue, ReflectBaseType, ReflectReference, }; use bevy::{ @@ -8,11 +8,7 @@ use bevy::{ reflect::{func::FunctionError, PartialReflect, Reflect}, }; use std::{ - any::TypeId, - fmt::{Debug, Display}, - ops::Deref, - str::Utf8Error, - sync::Arc, + any::TypeId, borrow::Cow, fmt::{Debug, Display}, ops::Deref, str::Utf8Error, sync::Arc }; pub type ScriptResult = Result; @@ -310,6 +306,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 { @@ -349,12 +351,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(), })) } @@ -416,12 +420,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(), })) } @@ -475,10 +479,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(), })) } @@ -490,18 +494,18 @@ 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) } } @@ -522,7 +526,8 @@ pub enum InteropErrorInner { reason: String, }, CannotClaimAccess { - base: ReflectBaseType, + base: ReflectAccessId, + context: Cow<'static, str>, location: Option>, }, InvalidAccessCount { @@ -597,6 +602,12 @@ pub enum InteropErrorInner { OtherError { error: Box, }, + UnregisteredComponentOrResourceType { + type_name: Cow<'static, str>, + }, + Invariant { + message: String, + }, } impl PartialEq for InteropErrorInner { @@ -621,10 +632,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 ) }; } @@ -767,17 +778,33 @@ 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 { + InteropErrorInner::MissingFunctionError { on, function_name } => { missing_function_error!(function_name, on.display_with_world(world)) }, 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)) @@ -865,7 +892,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() }; @@ -887,6 +914,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) + }, } } @@ -899,8 +932,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()) @@ -988,7 +1021,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() }; @@ -1010,6 +1043,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/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/src/core.rs b/crates/bevy_mod_scripting_functions/src/core.rs index 749d3a58a3..114c27d3dd 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_component_type(registration) { + Ok(comp) => { + let mut allocator = allocator.write(); + return Ok(Some(ReflectReference::new_allocated( + comp, + &mut allocator, + ))); + } + Err(registration) => registration, + }; + + 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 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| { + world.remove_component(*e, r.clone()) }, ) .register( "get_resource", - |world: WorldCallbackAccess, registration: Val| { - match registration.resource_id() { - Some(id) => Ok(world.get_resource(id)?), - None => Ok(None), - } + |world: WorldCallbackAccess, registration: Val| { + world.get_resource(registration.resource_id()) }, ) .register( "has_resource", - |s: WorldCallbackAccess, registration: Val| match registration - .resource_id() - { - Some(id) => s.has_resource(id), - None => Ok(false), + |world: WorldCallbackAccess, registration: Val| { + world.has_resource(registration.resource_id()) }, ) .register( "remove_resource", - |s: WorldCallbackAccess, r: Val| s.remove_resource(r.clone()), + |s: WorldCallbackAccess, r: Val| { + s.remove_resource(r.into_inner()) + }, ) .register( "add_default_component", - |w: WorldCallbackAccess, e: Val, r: Val| { + |w: WorldCallbackAccess, e: Val, r: Val| { w.add_default_component(*e, r.clone()) }, ) @@ -140,21 +164,7 @@ pub fn register_world_functions(reg: &mut World) -> Result<(), FunctionRegistrat let query_builder = ScriptQueryBuilder::default(); Ok(Val(query_builder)) }) - .register("exit", |s: WorldCallbackAccess| s.exit()) - .register("log_all_allocations", |s: WorldCallbackAccess| { - let world = s.try_read().expect("stale world"); - let allocator = world.allocator(); - let allocator = allocator.read(); - for (id, _) in allocator.iter_allocations() { - let raid = ReflectAccessId::for_allocation(id.clone()); - if world.claim_read_access(raid) { - // Safety: ref released above - unsafe { world.release_access(raid) }; - } else { - panic!("Failed to claim read access for allocation id: {}", id.id()); - } - } - }); + .register("exit", |s: WorldCallbackAccess| s.exit()); Ok(()) } @@ -165,13 +175,13 @@ pub fn register_reflect_reference_functions( .register( "display_ref", |w: WorldCallbackAccess, s: ReflectReference| { - let world = w.try_read().expect("Stale world"); - s.display_with_world(world) + let world = w.try_read()?; + Ok(s.display_with_world(world)) }, ) .register("display_value", |w: WorldCallbackAccess, s: ReflectReference| { - let world = w.try_read().expect("Stale world"); - s.display_value_with_world(world) + let world = w.try_read()?; + Ok(s.display_value_with_world(world)) }) .register( "get", @@ -184,7 +194,7 @@ pub fn register_reflect_reference_functions( path.convert_to_0_indexed(); } self_.index_path(path); - let world = world.try_read().expect("Stale world"); + let world = world.try_read()?; ReflectReference::into_script_ref(self_, world) }, ) @@ -196,8 +206,8 @@ pub fn register_reflect_reference_functions( key: ScriptValue, value: ScriptValue| { if let ScriptValue::Reference(mut self_) = self_ { - let world = world.try_read().expect("stale world"); - let mut path: ParsedPath = key.try_into().unwrap(); + let world = world.try_read()?; + let mut path: ParsedPath = key.try_into()?; if caller_context.convert_to_0_indexed { path.convert_to_0_indexed(); } @@ -213,19 +223,19 @@ pub fn register_reflect_reference_functions( value, world.clone(), )?; - r.try_apply(other.as_partial_reflect()).unwrap(); + r.try_apply(other.as_partial_reflect()).map_err(|e| InteropError::external_error(Box::new(e)))?; Ok::<_, InteropError>(()) }) .into(); - return r; + return Ok(r); } - ScriptValue::Unit + Ok(ScriptValue::Unit) }, ) .register( "push", |w: WorldCallbackAccess, s: ReflectReference, v: ScriptValue| { - let world = w.try_read().expect("stale world"); + let world = w.try_read()?; let target_type_id = s.element_type_id(world.clone())?.ok_or_else(|| { InteropError::unsupported_operation( s.tail_type_id(world.clone()).unwrap_or_default(), @@ -238,18 +248,18 @@ pub fn register_reflect_reference_functions( }, ) .register("pop", |w: WorldCallbackAccess, s: ReflectReference| { - let world = w.try_read().expect("stale world"); + let world = w.try_read()?; let o = s.with_reflect_mut(world.clone(), |s| s.try_pop_boxed())??; let reference = { let allocator = world.allocator(); let mut allocator = allocator.write(); - ReflectReference::new_allocated_boxed(o, &mut allocator) + ReflectReference::new_allocated_boxed_parial_reflect(o, &mut allocator)? }; ReflectReference::into_script_ref(reference, world) }) .register("insert", |caller_context: CallerContext, w: WorldCallbackAccess, s: ReflectReference, k: ScriptValue, v: ScriptValue| { - let world = w.try_read().expect("stale world"); + let world = w.try_read()?; let key_type_id = s.key_type_id(world.clone())?.ok_or_else(|| { InteropError::unsupported_operation( s.tail_type_id(world.clone()).unwrap_or_default(), @@ -277,15 +287,15 @@ pub fn register_reflect_reference_functions( s.with_reflect_mut(world, |s| s.try_insert_boxed(key, value))? }) .register("clear", |w: WorldCallbackAccess, s: ReflectReference| { - let world = w.try_read().expect("stale world"); + let world = w.try_read()?; s.with_reflect_mut(world, |s| s.try_clear())? }) .register("len", |w: WorldCallbackAccess, s: ReflectReference| { - let world = w.try_read().expect("stale world"); + let world = w.try_read()?; s.len(world) }) .register("remove", |caller_context: CallerContext, w: WorldCallbackAccess, s: ReflectReference, k: ScriptValue| { - let world = w.try_read().expect("stale world"); + let world = w.try_read()?; let key_type_id = s.key_type_id(world.clone())?.ok_or_else(|| { InteropError::unsupported_operation( s.tail_type_id(world.clone()).unwrap_or_default(), @@ -301,18 +311,20 @@ pub fn register_reflect_reference_functions( } let removed = s.with_reflect_mut(world.clone(), |s| s.try_remove_boxed(key))??; - - removed.map(|some| { - let reference = { - let allocator = world.allocator(); - let mut allocator = allocator.write(); - ReflectReference::new_allocated_boxed(some, &mut allocator) - }; - ReflectReference::into_script_ref(reference, world) - }).transpose() + match removed { + Some(removed) => { + let reference = { + let allocator = world.allocator(); + let mut allocator = allocator.write(); + ReflectReference::new_allocated_boxed_parial_reflect(removed, &mut allocator)? + }; + ReflectReference::into_script_ref(reference, world) + } + None => Ok(ScriptValue::Unit), + } }) .register("iter", |w: WorldCallbackAccess, s: ReflectReference| { - let world = w.try_read().expect("stale world"); + let world = w.try_read()?; let mut len = s.len(world.clone())?.unwrap_or_default(); let mut infinite_iter = s.into_iter_infinite(); let iter_function = move || { @@ -342,13 +354,24 @@ pub fn register_script_type_registration_functions( .register("type_name", |s: Ref| s.type_name()) .register("short_name", |s: Ref| { s.short_name() + }); + + NamespaceBuilder::::new(registry) + .register("type_name", |s: Ref| { + s.type_registration().type_name() }) - .register("is_resource", |s: Ref| { - s.resource_id().is_some() + .register("short_name", |s: Ref| { + s.type_registration().short_name() + }); + + NamespaceBuilder::::new(registry) + .register("type_name", |s: Ref| { + s.type_registration().type_name() }) - .register("is_component", |s: Ref| { - s.component_id().is_some() + .register("short_name", |s: Ref| { + s.type_registration().short_name() }); + Ok(()) } @@ -358,7 +381,7 @@ pub fn register_script_query_builder_functions( NamespaceBuilder::::new(registry) .register( "component", - |s: Val, components: Val| { + |s: Val, components: Val| { let mut builder = s.into_inner(); builder.component(components.into_inner()); Val(builder) @@ -366,7 +389,7 @@ pub fn register_script_query_builder_functions( ) .register( "with", - |s: Val, with: Val| { + |s: Val, with: Val| { let mut builder = s.into_inner(); builder.with_component(with.into_inner()); Val(builder) @@ -374,7 +397,7 @@ pub fn register_script_query_builder_functions( ) .register( "without", - |s: Val, without: Val| { + |s: Val, without: Val| { let mut builder = s.into_inner(); builder.without_component(without.into_inner()); Val(builder) @@ -410,21 +433,30 @@ pub fn register_core_functions(app: &mut App) { // perhaps people might want to include some but not all of these #[cfg(feature = "core_functions")] - register_world_functions(world).expect("Failed to register world functions"); + if let Err(e) = register_world_functions(world) { + bevy::log::error!("Failed to register script world functions: {:?}", e); + } #[cfg(feature = "core_functions")] - register_reflect_reference_functions(world) - .expect("Failed to register reflect reference functions"); + if let Err(e) = register_reflect_reference_functions(world) { + bevy::log::error!("Failed to register reflect reference functions: {:?}", e); + } #[cfg(feature = "core_functions")] - register_script_type_registration_functions(world) - .expect("Failed to register script type registration functions"); + if let Err(e) = register_script_type_registration_functions(world) { + bevy::log::error!( + "Failed to register script type registration functions: {:?}", + e + ); + } #[cfg(feature = "core_functions")] - register_script_query_builder_functions(world) - .expect("Failed to register script query builder functions"); + if let Err(e) = register_script_query_builder_functions(world) { + bevy::log::error!("Failed to register script query builder functions: {:?}", e); + } #[cfg(feature = "core_functions")] - register_script_query_result_functions(world) - .expect("Failed to register script query result functions"); + if let Err(e) = register_script_query_result_functions(world) { + bevy::log::error!("Failed to register script query result functions: {:?}", e); + } } diff --git a/crates/languages/bevy_mod_scripting_lua/src/bindings/reference.rs b/crates/languages/bevy_mod_scripting_lua/src/bindings/reference.rs index 33f7640c7c..1b44ce03d7 100644 --- a/crates/languages/bevy_mod_scripting_lua/src/bindings/reference.rs +++ b/crates/languages/bevy_mod_scripting_lua/src/bindings/reference.rs @@ -38,7 +38,7 @@ impl UserData for LuaReflectReference { m.add_meta_function( MetaMethod::Index, |_, (self_, key): (LuaReflectReference, LuaScriptValue)| { - let world = ThreadWorldContainer.get_world(); + let world = ThreadWorldContainer.try_get_world()?; let self_: ReflectReference = self_.into(); let type_id = self_.tail_type_id(world.clone())?.or_fake_id(); @@ -58,7 +58,10 @@ impl UserData for LuaReflectReference { let func = world .lookup_function([TypeId::of::()], "get") - .expect("No 'get' function registered for a ReflectReference"); + .map_err(|f| { + InteropError::missing_function(TypeId::of::(), f) + })?; + // call the function with the key let out = func.call( vec![ScriptValue::Reference(self_), key], @@ -72,18 +75,20 @@ impl UserData for LuaReflectReference { m.add_meta_function( MetaMethod::NewIndex, |_, (self_, key, value): (LuaReflectReference, LuaScriptValue, LuaScriptValue)| { - let world = ThreadWorldContainer.get_world(); + let world = ThreadWorldContainer.try_get_world()?; let self_: ReflectReference = self_.into(); let key: ScriptValue = key.into(); let value: ScriptValue = value.into(); let func = world .lookup_function([TypeId::of::()], "set") - .expect("No 'set' function registered for a ReflectReference"); + .map_err(|f| { + InteropError::missing_function(TypeId::of::(), f) + })?; let out = func.call( vec![ScriptValue::Reference(self_), key, value], - ThreadWorldContainer.get_world(), + world, LUA_CALLER_CONTEXT, )?; @@ -94,8 +99,8 @@ impl UserData for LuaReflectReference { m.add_meta_function( MetaMethod::Sub, |_, (self_, other): (LuaReflectReference, LuaScriptValue)| { - let world = ThreadWorldContainer.get_callback_world(); - let guard = world.try_read().expect("World is not set or expired"); + let world = ThreadWorldContainer.try_get_callback_world()?; + let guard = world.try_read()?; let self_: ReflectReference = self_.into(); let other: ScriptValue = other.into(); let target_type_id = self_.tail_type_id(guard)?.or_fake_id(); @@ -109,8 +114,8 @@ impl UserData for LuaReflectReference { m.add_meta_function( MetaMethod::Add, |_, (self_, other): (LuaReflectReference, LuaScriptValue)| { - let world = ThreadWorldContainer.get_callback_world(); - let guard = world.try_read().expect("World is not set or expired"); + let world = ThreadWorldContainer.try_get_callback_world()?; + let guard = world.try_read()?; let self_: ReflectReference = self_.into(); let other: ScriptValue = other.into(); let target_type_id = self_.tail_type_id(guard)?.or_fake_id(); @@ -124,8 +129,8 @@ impl UserData for LuaReflectReference { m.add_meta_function( MetaMethod::Mul, |_, (self_, other): (LuaReflectReference, LuaScriptValue)| { - let world = ThreadWorldContainer.get_callback_world(); - let guard = world.try_read().expect("World is not set or expired"); + let world = ThreadWorldContainer.try_get_callback_world()?; + let guard = world.try_read()?; let self_: ReflectReference = self_.into(); let other: ScriptValue = other.into(); let target_type_id = self_.tail_type_id(guard)?.or_fake_id(); @@ -139,8 +144,8 @@ impl UserData for LuaReflectReference { m.add_meta_function( MetaMethod::Div, |_, (self_, other): (LuaReflectReference, LuaScriptValue)| { - let world = ThreadWorldContainer.get_callback_world(); - let guard = world.try_read().expect("World is not set or expired"); + let world = ThreadWorldContainer.try_get_callback_world()?; + let guard = world.try_read()?; let self_: ReflectReference = self_.into(); let other: ScriptValue = other.into(); let target_type_id = self_.tail_type_id(guard)?.or_fake_id(); @@ -154,8 +159,8 @@ impl UserData for LuaReflectReference { m.add_meta_function( MetaMethod::Mod, |_, (self_, other): (LuaReflectReference, LuaScriptValue)| { - let world = ThreadWorldContainer.get_callback_world(); - let guard = world.try_read().expect("World is not set or expired"); + let world = ThreadWorldContainer.try_get_callback_world()?; + let guard = world.try_read()?; let self_: ReflectReference = self_.into(); let other: ScriptValue = other.into(); let target_type_id = self_.tail_type_id(guard)?.or_fake_id(); @@ -167,8 +172,8 @@ impl UserData for LuaReflectReference { ); m.add_meta_function(MetaMethod::Unm, |_, self_: LuaReflectReference| { - let world = ThreadWorldContainer.get_callback_world(); - let guard = world.try_read().expect("World is not set or expired"); + let world = ThreadWorldContainer.try_get_callback_world()?; + let guard = world.try_read()?; let self_: ReflectReference = self_.into(); let target_type_id = self_.tail_type_id(guard)?.or_fake_id(); let args = vec![ScriptValue::Reference(self_)]; @@ -179,8 +184,8 @@ impl UserData for LuaReflectReference { m.add_meta_function( MetaMethod::Pow, |_, (self_, other): (LuaReflectReference, LuaScriptValue)| { - let world = ThreadWorldContainer.get_callback_world(); - let guard = world.try_read().expect("World is not set or expired"); + let world = ThreadWorldContainer.try_get_callback_world()?; + let guard = world.try_read()?; let self_: ReflectReference = self_.into(); let other: ScriptValue = other.into(); let target_type_id = self_.tail_type_id(guard)?.or_fake_id(); @@ -194,8 +199,8 @@ impl UserData for LuaReflectReference { m.add_meta_function( MetaMethod::Eq, |_, (self_, other): (LuaReflectReference, LuaScriptValue)| { - let world = ThreadWorldContainer.get_callback_world(); - let guard = world.try_read().expect("World is not set or expired"); + let world = ThreadWorldContainer.try_get_callback_world()?; + let guard = world.try_read()?; let self_: ReflectReference = self_.into(); let other: ScriptValue = other.into(); let target_type_id = self_.tail_type_id(guard)?.or_fake_id(); @@ -209,8 +214,8 @@ impl UserData for LuaReflectReference { m.add_meta_function( MetaMethod::Lt, |_, (self_, other): (LuaReflectReference, LuaScriptValue)| { - let world = ThreadWorldContainer.get_callback_world(); - let guard = world.try_read().expect("World is not set or expired"); + let world = ThreadWorldContainer.try_get_callback_world()?; + let guard = world.try_read()?; let self_: ReflectReference = self_.into(); let other: ScriptValue = other.into(); let target_type_id = self_.tail_type_id(guard)?.or_fake_id(); @@ -222,7 +227,7 @@ impl UserData for LuaReflectReference { ); m.add_meta_function(MetaMethod::Len, |_lua, self_: LuaScriptValue| { - let world = ThreadWorldContainer.get_world(); + let world = ThreadWorldContainer.try_get_world()?; let script_value: ScriptValue = self_.into(); Ok(match script_value { ScriptValue::Reference(r) => r.len(world)?, @@ -240,11 +245,11 @@ impl UserData for LuaReflectReference { m.add_meta_function(MetaMethod::Pairs, |_, s: LuaReflectReference| { // let mut iter_func = lookup_dynamic_function_typed::(l, "iter") // .expect("No iter function registered"); - let world = ThreadWorldContainer.get_world(); + let world = ThreadWorldContainer.try_get_world()?; let iter_func = world .lookup_function([TypeId::of::()], "iter") - .expect("No iter function registered"); + .map_err(|f| InteropError::missing_function(TypeId::of::(), f))?; Ok(LuaScriptValue::from(iter_func.call( vec![ScriptValue::Reference(s.into())], @@ -254,13 +259,12 @@ impl UserData for LuaReflectReference { }); m.add_meta_function(MetaMethod::ToString, |_, self_: LuaReflectReference| { - let world = ThreadWorldContainer.get_world(); + let world = ThreadWorldContainer.try_get_world()?; let reflect_reference: ReflectReference = self_.into(); let func = world .lookup_function([TypeId::of::()], "display_ref") - .expect("No 'display' function registered for a ReflectReference"); - + .map_err(|f| InteropError::missing_function(TypeId::of::(), f))?; let out = func.call( vec![ScriptValue::Reference(reflect_reference)], world, @@ -283,7 +287,7 @@ impl UserData for LuaStaticReflectReference { m.add_meta_function( MetaMethod::Index, |_, (self_, key): (LuaStaticReflectReference, LuaScriptValue)| { - let world = ThreadWorldContainer.get_world(); + let world = ThreadWorldContainer.try_get_world()?; let type_id = self_.0; let key: ScriptValue = key.into(); @@ -301,7 +305,7 @@ impl UserData for LuaStaticReflectReference { Err(key) => key, }; - let world = ThreadWorldContainer.get_world(); + let world = ThreadWorldContainer.try_get_world()?; Err( InteropError::missing_function(type_id, key.display_with_world(world.clone())) .into(), diff --git a/crates/languages/bevy_mod_scripting_lua/src/bindings/script_value.rs b/crates/languages/bevy_mod_scripting_lua/src/bindings/script_value.rs index 7666f6779d..6db50d1373 100644 --- a/crates/languages/bevy_mod_scripting_lua/src/bindings/script_value.rs +++ b/crates/languages/bevy_mod_scripting_lua/src/bindings/script_value.rs @@ -99,7 +99,7 @@ impl IntoLua for LuaScriptValue { ScriptValue::Error(script_error) => return Err(mlua::Error::external(script_error)), ScriptValue::Function(function) => lua .create_function(move |_lua, args: Variadic| { - let world = ThreadWorldContainer.get_world(); + let world = ThreadWorldContainer.try_get_world()?; let out = function.call( args.into_iter().map(Into::into), world, @@ -111,7 +111,7 @@ impl IntoLua for LuaScriptValue { .into_lua(lua)?, ScriptValue::FunctionMut(function) => lua .create_function(move |_lua, args: Variadic| { - let world = ThreadWorldContainer.get_world(); + let world = ThreadWorldContainer.try_get_world()?; let out = function.call( args.into_iter().map(Into::into), world, diff --git a/crates/languages/bevy_mod_scripting_lua/src/lib.rs b/crates/languages/bevy_mod_scripting_lua/src/lib.rs index 7cd05e2bc8..40f6700bc1 100644 --- a/crates/languages/bevy_mod_scripting_lua/src/lib.rs +++ b/crates/languages/bevy_mod_scripting_lua/src/lib.rs @@ -71,7 +71,7 @@ impl Default for LuaScriptingPlugin { }, |_script_id, context: &mut Lua| { // set static globals - let world = ThreadWorldContainer.get_world(); + let world = ThreadWorldContainer.try_get_world()?; let type_registry = world.type_registry(); let type_registry = type_registry.read(); @@ -114,7 +114,7 @@ impl Default for LuaScriptingPlugin { }, ], context_pre_handling_initializers: vec![|script_id, entity, context| { - let world = ThreadWorldContainer.get_world(); + let world = ThreadWorldContainer.try_get_world()?; context .globals() .set( diff --git a/crates/languages/bevy_mod_scripting_lua/tests/lua_tests.rs b/crates/languages/bevy_mod_scripting_lua/tests/lua_tests.rs index cfc38a3859..bc20b21230 100644 --- a/crates/languages/bevy_mod_scripting_lua/tests/lua_tests.rs +++ b/crates/languages/bevy_mod_scripting_lua/tests/lua_tests.rs @@ -1,3 +1,4 @@ +#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)] use bevy_mod_scripting_core::{ bindings::{pretty_print::DisplayWithWorld, ThreadWorldContainer, WorldContainer}, error::ScriptError, @@ -31,7 +32,7 @@ impl Test { globals.set( "assert_throws", ctxt.create_function(|_lua, (f, reg): (Function, String)| { - let world = ThreadWorldContainer.get_world(); + let world = ThreadWorldContainer.try_get_world()?; let result = f.call::<()>(MultiValue::new()); let err = match result { Ok(_) => { diff --git a/crates/script_integration_test_harness/src/test_functions.rs b/crates/script_integration_test_harness/src/test_functions.rs index 1cbcc093e1..03989bb41d 100644 --- a/crates/script_integration_test_harness/src/test_functions.rs +++ b/crates/script_integration_test_harness/src/test_functions.rs @@ -2,6 +2,7 @@ use std::sync::Arc; use bevy::{ app::App, + ecs::component::ComponentId, prelude::{Entity, World}, reflect::{Reflect, TypeRegistration}, }; @@ -12,7 +13,7 @@ use bevy_mod_scripting_core::{ script_function::{CallerContext, DynamicScriptFunctionMut}, }, pretty_print::DisplayWithWorld, - ReflectReference, ScriptTypeRegistration, WorldCallbackAccess, + ReflectReference, ScriptComponentRegistration, ScriptTypeRegistration, WorldCallbackAccess, }, error::InteropError, }; @@ -25,12 +26,31 @@ pub fn register_test_functions(world: &mut App) { let world = s.try_read().unwrap(); #[derive(Reflect)] struct Dummy; - let reg = - ScriptTypeRegistration::new(Arc::new(TypeRegistration::of::()), None, None); + let reg = ScriptTypeRegistration::new(Arc::new(TypeRegistration::of::())); let allocator = world.allocator(); let mut allocator = allocator.write(); ReflectReference::new_allocated(reg, &mut allocator) }) + .register("_get_mock_component_type", |s: WorldCallbackAccess| { + let world = s.try_read().unwrap(); + #[derive(Reflect)] + struct Dummy; + let reg = ScriptTypeRegistration::new(Arc::new(TypeRegistration::of::())); + let comp = ScriptComponentRegistration::new(reg, ComponentId::new(999999999999999)); + let allocator = world.allocator(); + let mut allocator = allocator.write(); + ReflectReference::new_allocated(comp, &mut allocator) + }) + .register("_get_mock_resource_type", |s: WorldCallbackAccess| { + let world = s.try_read().unwrap(); + #[derive(Reflect)] + struct Dummy; + let reg = ScriptTypeRegistration::new(Arc::new(TypeRegistration::of::())); + let comp = ScriptComponentRegistration::new(reg, ComponentId::new(999999999999999)); + let allocator = world.allocator(); + let mut allocator = allocator.write(); + ReflectReference::new_allocated(comp, &mut allocator) + }) .register( "_get_entity_with_test_component", |s: WorldCallbackAccess, name: String| { From 54d8721dcac4670afe34e5682e0c3054569329ab Mon Sep 17 00:00:00 2001 From: makspll Date: Sun, 19 Jan 2025 20:27:11 +0000 Subject: [PATCH 07/14] fix fmt --- crates/bevy_mod_scripting_core/src/error.rs | 30 ++++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/crates/bevy_mod_scripting_core/src/error.rs b/crates/bevy_mod_scripting_core/src/error.rs index 64a752cc03..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, ReflectAccessId}, 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, @@ -8,7 +11,12 @@ use bevy::{ reflect::{func::FunctionError, PartialReflect, Reflect}, }; use std::{ - any::TypeId, borrow::Cow, fmt::{Debug, Display}, ops::Deref, str::Utf8Error, sync::Arc + any::TypeId, + borrow::Cow, + fmt::{Debug, Display}, + ops::Deref, + str::Utf8Error, + sync::Arc, }; pub type ScriptResult = Result; @@ -494,8 +502,14 @@ 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 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 { @@ -789,14 +803,16 @@ macro_rules! invariant { macro_rules! unregistered_component_or_resource_type { ($type_name:expr) => { - format!("Expected registered component/resource but got unregistered type: {}", $type_name) + 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 { - InteropErrorInner::MissingFunctionError { on, function_name } => { missing_function_error!(function_name, on.display_with_world(world)) }, From ff4346f5ec9ba1f6033689f29bcd06d53dde4da0 Mon Sep 17 00:00:00 2001 From: makspll Date: Sun, 19 Jan 2025 21:06:08 +0000 Subject: [PATCH 08/14] fix failing tests and update docs --- Cargo.toml | 1 + crates/bevy_mod_scripting_core/src/asset.rs | 4 +- .../src/bindings/world.rs | 6 +-- crates/bevy_mod_scripting_core/src/context.rs | 4 +- crates/bevy_mod_scripting_core/src/handler.rs | 1 + .../bevy_mod_scripting_functions/src/core.rs | 12 +++--- .../src/bindings/script_value.rs | 1 - .../missing_resource_returns_nil.lua | 2 +- ...stered_component_returns_correct_type.lua} | 4 ++ ...gistered_resource_returns_correct_type.lua | 18 +++++++++ .../empty_entity_mock_component_is_false.lua | 2 +- ...issing_resource_mock_resource_is_false.lua | 2 +- .../no_resource_data_errors.lua | 2 +- .../src/test_functions.rs | 5 ++- crates/xtask/src/main.rs | 7 +++- docs/src/SUMMARY.md | 2 + .../script-component-registration.md | 39 +++++++++++++++++++ .../script-query-builder.md | 6 +-- .../script-resource-registration.md | 39 +++++++++++++++++++ docs/src/ScriptingReference/world.md | 4 +- 20 files changed, 133 insertions(+), 28 deletions(-) rename crates/languages/bevy_mod_scripting_lua/tests/data/get_type_by_name/{registered_type_returns_correct_type.lua => registered_component_returns_correct_type.lua} (72%) create mode 100644 crates/languages/bevy_mod_scripting_lua/tests/data/get_type_by_name/registered_resource_returns_correct_type.lua create mode 100644 docs/src/ScriptingReference/script-component-registration.md create mode 100644 docs/src/ScriptingReference/script-resource-registration.md diff --git a/Cargo.toml b/Cargo.toml index d418ad5aa4..b2b4fe9435 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -122,3 +122,4 @@ required-features = ["lua54", "bevy/file_watcher", "bevy/multi_threaded"] panic = "deny" unwrap_used = "deny" expect_used = "deny" +todo = "deny" diff --git a/crates/bevy_mod_scripting_core/src/asset.rs b/crates/bevy_mod_scripting_core/src/asset.rs index ff0e307d79..196e3b795b 100644 --- a/crates/bevy_mod_scripting_core/src/asset.rs +++ b/crates/bevy_mod_scripting_core/src/asset.rs @@ -544,9 +544,7 @@ mod tests { type C = (); 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/bindings/world.rs b/crates/bevy_mod_scripting_core/src/bindings/world.rs index 183bddc9ba..4a2377dc75 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/world.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/world.rs @@ -124,7 +124,7 @@ impl WorldCallbackAccess { pub fn get_resource_type( &self, registration: ScriptTypeRegistration, - ) -> Result, InteropError> { + ) -> Result, InteropError> { let world = self.try_read()?; Ok(world.get_resource_type(registration)) } @@ -577,9 +577,9 @@ impl WorldAccessGuard<'_> { pub fn get_resource_type( &self, registration: ScriptTypeRegistration, - ) -> Result { + ) -> Result { match self.get_resource_id(registration.type_id()) { - Some(resource_id) => Ok(ScriptComponentRegistration::new(registration, resource_id)), + Some(resource_id) => Ok(ScriptResourceRegistration::new(registration, resource_id)), None => Err(registration), } } 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/handler.rs b/crates/bevy_mod_scripting_core/src/handler.rs index c3061ef248..d637b15ff1 100644 --- a/crates/bevy_mod_scripting_core/src/handler.rs +++ b/crates/bevy_mod_scripting_core/src/handler.rs @@ -186,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_functions/src/core.rs b/crates/bevy_mod_scripting_functions/src/core.rs index 114c27d3dd..d988bbcecc 100644 --- a/crates/bevy_mod_scripting_functions/src/core.rs +++ b/crates/bevy_mod_scripting_functions/src/core.rs @@ -38,22 +38,22 @@ pub fn register_world_functions(reg: &mut World) -> Result<(), FunctionRegistrat Some(registration) => { let allocator = world.allocator(); - let registration = match world.get_component_type(registration) { - Ok(comp) => { + let registration = match world.get_resource_type(registration) { + Ok(res) => { let mut allocator = allocator.write(); return Ok(Some(ReflectReference::new_allocated( - comp, + res, &mut allocator, ))); } Err(registration) => registration, }; - let registration = match world.get_resource_type(registration) { - Ok(res) => { + let registration = match world.get_component_type(registration) { + Ok(comp) => { let mut allocator = allocator.write(); return Ok(Some(ReflectReference::new_allocated( - res, + comp, &mut allocator, ))); } diff --git a/crates/languages/bevy_mod_scripting_lua/src/bindings/script_value.rs b/crates/languages/bevy_mod_scripting_lua/src/bindings/script_value.rs index 6db50d1373..6c08150810 100644 --- a/crates/languages/bevy_mod_scripting_lua/src/bindings/script_value.rs +++ b/crates/languages/bevy_mod_scripting_lua/src/bindings/script_value.rs @@ -55,7 +55,6 @@ impl FromLua for LuaScriptValue { } ScriptValue::List(vec) } - Value::Function(_) => todo!("Function FromLua is not implemented yet"), // Value::Thread(thread) => todo!(), Value::UserData(ud) => { let ud = ud.borrow::().map_err(|e| { diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/get_resource/missing_resource_returns_nil.lua b/crates/languages/bevy_mod_scripting_lua/tests/data/get_resource/missing_resource_returns_nil.lua index d2bc48168b..1ca8b19050 100644 --- a/crates/languages/bevy_mod_scripting_lua/tests/data/get_resource/missing_resource_returns_nil.lua +++ b/crates/languages/bevy_mod_scripting_lua/tests/data/get_resource/missing_resource_returns_nil.lua @@ -1,2 +1,2 @@ -local type = world._get_mock_type() +local type = world._get_mock_resource_type() assert(world.get_resource(type) == nil, "Resource should not exist") \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/get_type_by_name/registered_type_returns_correct_type.lua b/crates/languages/bevy_mod_scripting_lua/tests/data/get_type_by_name/registered_component_returns_correct_type.lua similarity index 72% rename from crates/languages/bevy_mod_scripting_lua/tests/data/get_type_by_name/registered_type_returns_correct_type.lua rename to crates/languages/bevy_mod_scripting_lua/tests/data/get_type_by_name/registered_component_returns_correct_type.lua index 8b12faa6e6..fe71b07b83 100644 --- a/crates/languages/bevy_mod_scripting_lua/tests/data/get_type_by_name/registered_type_returns_correct_type.lua +++ b/crates/languages/bevy_mod_scripting_lua/tests/data/get_type_by_name/registered_component_returns_correct_type.lua @@ -13,3 +13,7 @@ local received = { assert(type ~= nil, 'Type not found') assert(received.type_name == expected.type_name, 'type_name mismatch, expected: ' .. expected.type_name .. ', got: ' .. received.type_name) assert(received.short_name == expected.short_name, 'short_name mismatch, expected: ' .. expected.short_name .. ', got: ' .. received.short_name) + +local type_ref = type:display_ref() +-- check contains ScriptComponentRegistration +assert(string.find(type_ref, "ScriptComponentRegistration") ~= nil, "ScriptComponentRegistration not found in type_ref. got: " .. type_ref) \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/get_type_by_name/registered_resource_returns_correct_type.lua b/crates/languages/bevy_mod_scripting_lua/tests/data/get_type_by_name/registered_resource_returns_correct_type.lua new file mode 100644 index 0000000000..d3bcf46d36 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_lua/tests/data/get_type_by_name/registered_resource_returns_correct_type.lua @@ -0,0 +1,18 @@ +local type = world.get_type_by_name('TestResource') + +local expected = { + type_name = 'test_utils::test_data::TestResource', + short_name = 'TestResource', +} + +local received = { + type_name = type:type_name(), + short_name = type:short_name(), +} + +assert(type ~= nil, 'Type not found') +assert(received.type_name == expected.type_name, 'type_name mismatch, expected: ' .. expected.type_name .. ', got: ' .. received.type_name) +assert(received.short_name == expected.short_name, 'short_name mismatch, expected: ' .. expected.short_name .. ', got: ' .. received.short_name) +local type_ref = type:display_ref() +-- check contains ScriptResourceRegistration +assert(string.find(type_ref, "ScriptResourceRegistration") ~= nil, "ScriptResourceRegistration not found in type_ref. got: " .. type_ref) \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/has_component/empty_entity_mock_component_is_false.lua b/crates/languages/bevy_mod_scripting_lua/tests/data/has_component/empty_entity_mock_component_is_false.lua index 226f87e549..853acc499b 100644 --- a/crates/languages/bevy_mod_scripting_lua/tests/data/has_component/empty_entity_mock_component_is_false.lua +++ b/crates/languages/bevy_mod_scripting_lua/tests/data/has_component/empty_entity_mock_component_is_false.lua @@ -1,4 +1,4 @@ local entity = world.spawn() -local type = world._get_mock_type() +local type = world._get_mock_component_type() assert(world.has_component(entity, type) == false, "Entity should not have component") \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/has_resource/missing_resource_mock_resource_is_false.lua b/crates/languages/bevy_mod_scripting_lua/tests/data/has_resource/missing_resource_mock_resource_is_false.lua index c15064fa39..9c86c67813 100644 --- a/crates/languages/bevy_mod_scripting_lua/tests/data/has_resource/missing_resource_mock_resource_is_false.lua +++ b/crates/languages/bevy_mod_scripting_lua/tests/data/has_resource/missing_resource_mock_resource_is_false.lua @@ -1,2 +1,2 @@ -local type = world._get_mock_type() +local type = world._get_mock_resource_type() assert(world.has_resource(type) == false, "Resource should not exist") \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/remove_resource/no_resource_data_errors.lua b/crates/languages/bevy_mod_scripting_lua/tests/data/remove_resource/no_resource_data_errors.lua index b9b7a766f1..0b5a4a3923 100644 --- a/crates/languages/bevy_mod_scripting_lua/tests/data/remove_resource/no_resource_data_errors.lua +++ b/crates/languages/bevy_mod_scripting_lua/tests/data/remove_resource/no_resource_data_errors.lua @@ -1,5 +1,5 @@ -local type = world._get_mock_type() +local type = world._get_mock_resource_type() assert_throws(function () world.remove_resource(type) diff --git a/crates/script_integration_test_harness/src/test_functions.rs b/crates/script_integration_test_harness/src/test_functions.rs index 03989bb41d..7e6ead81dc 100644 --- a/crates/script_integration_test_harness/src/test_functions.rs +++ b/crates/script_integration_test_harness/src/test_functions.rs @@ -13,7 +13,8 @@ use bevy_mod_scripting_core::{ script_function::{CallerContext, DynamicScriptFunctionMut}, }, pretty_print::DisplayWithWorld, - ReflectReference, ScriptComponentRegistration, ScriptTypeRegistration, WorldCallbackAccess, + ReflectReference, ScriptComponentRegistration, ScriptResourceRegistration, + ScriptTypeRegistration, WorldCallbackAccess, }, error::InteropError, }; @@ -46,7 +47,7 @@ pub fn register_test_functions(world: &mut App) { #[derive(Reflect)] struct Dummy; let reg = ScriptTypeRegistration::new(Arc::new(TypeRegistration::of::())); - let comp = ScriptComponentRegistration::new(reg, ComponentId::new(999999999999999)); + let comp = ScriptResourceRegistration::new(reg, ComponentId::new(999999999999999)); let allocator = world.allocator(); let mut allocator = allocator.write(); ReflectReference::new_allocated(comp, &mut allocator) diff --git a/crates/xtask/src/main.rs b/crates/xtask/src/main.rs index 56904acfd1..3980e77589 100644 --- a/crates/xtask/src/main.rs +++ b/crates/xtask/src/main.rs @@ -586,7 +586,7 @@ impl Xtasks { Xtasks::Build => Self::build(app_settings), Xtasks::Check { ide_mode, kind } => Self::check(app_settings, ide_mode, kind), Xtasks::Docs { open, no_rust_docs } => Self::docs(app_settings, open, no_rust_docs), - Xtasks::Test { name, package } => Self::test(app_settings, name, package), + Xtasks::Test { name, package } => Self::test(app_settings, package, name), Xtasks::CiCheck => Self::cicd(app_settings), Xtasks::Init => Self::init(app_settings), Xtasks::Macros { macro_name } => match macro_name { @@ -1135,11 +1135,14 @@ impl Xtasks { test_args.push(name); } + test_args.push("--exclude".to_owned()); + test_args.push("xtask".to_owned()); + Self::run_workspace_command( &app_settings, "test", "Failed to run tests", - vec!["--exclude", "xtask"], + test_args, None, )?; diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 27e751c066..7485aacd46 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -17,6 +17,8 @@ - [World](./ScriptingReference/world.md) - [ReflectReference](./ScriptingReference/reflect-reference.md) - [ScriptTypeRegistration](./ScriptingReference/script-type-registration.md) + - [ScriptComponentRegistration](./ScriptingReference/script-component-registration.md) + - [ScriptResourceRegistration](./ScriptingReference/script-resource-registration.md) - [ScriptQueryBuilder](./ScriptingReference/script-query-builder.md) - [ScriptQueryResult](./ScriptingReference/script-query-result.md) - [Core Callbacks](./ScriptingReference/core-callbacks.md) diff --git a/docs/src/ScriptingReference/script-component-registration.md b/docs/src/ScriptingReference/script-component-registration.md new file mode 100644 index 0000000000..c83fe416c9 --- /dev/null +++ b/docs/src/ScriptingReference/script-component-registration.md @@ -0,0 +1,39 @@ +# ScriptComponentRegistration + +A reference to a component type's registration, in general think of this as a handle to a type. + +## type_name + +Arguments: + +| Argument | Type | Description | +| --- | --- | --- | +| `s` | `ScriptComponentRegistration` | The type registration as returned by `get_type_by_name` | + +Returns: + +| Return | Description | +| --- | --- | +| `String` | The type name | + +```lua +local name = MyType:type_name() +``` + +## short_name + +Arguments: + +| Argument | Type | Description | +| --- | --- | --- | +| `s` | `ScriptComponentRegistration` | The type registration as returned by `get_type_by_name` | + +Returns: + +| Return | Description | +| --- | --- | +| `String` | The short name | + +```lua +local name = MyType:short_name() +``` \ No newline at end of file diff --git a/docs/src/ScriptingReference/script-query-builder.md b/docs/src/ScriptingReference/script-query-builder.md index 3f0654b14d..7d4fcbd93e 100644 --- a/docs/src/ScriptingReference/script-query-builder.md +++ b/docs/src/ScriptingReference/script-query-builder.md @@ -11,7 +11,7 @@ Arguments: | Argument | Type | Description | | --- | --- | --- | | `s` | `ScriptQueryBuilder` | The query builder | -| `component` | `ScriptTypeRegistration` | The component to query for | +| `component` | `ScriptComponentRegistration` | The component to query for | Returns: @@ -30,7 +30,7 @@ Arguments: | Argument | Type | Description | | --- | --- | --- | | `s` | `ScriptQueryBuilder` | The query builder | -| `with` | `ScriptTypeRegistration` | The component to include in the query | +| `with` | `ScriptComponentRegistration` | The component to include in the query | Returns: @@ -49,7 +49,7 @@ Arguments: | Argument | Type | Description | | --- | --- | --- | | `s` | `ScriptQueryBuilder` | The query builder | -| `without` | `ScriptTypeRegistration` | The component to exclude from the query | +| `without` | `ScriptComponentRegistration` | The component to exclude from the query | Returns: diff --git a/docs/src/ScriptingReference/script-resource-registration.md b/docs/src/ScriptingReference/script-resource-registration.md new file mode 100644 index 0000000000..13e500f88b --- /dev/null +++ b/docs/src/ScriptingReference/script-resource-registration.md @@ -0,0 +1,39 @@ +# ScriptResourceRegistration + +A reference to a resource type's registration, in general think of this as a handle to a type. + +## type_name + +Arguments: + +| Argument | Type | Description | +| --- | --- | --- | +| `s` | `ScriptResourceRegistration` | The type registration as returned by `get_type_by_name` | + +Returns: + +| Return | Description | +| --- | --- | +| `String` | The type name | + +```lua +local name = MyType:type_name() +``` + +## short_name + +Arguments: + +| Argument | Type | Description | +| --- | --- | --- | +| `s` | `ScriptResourceRegistration` | The type registration as returned by `get_type_by_name` | + +Returns: + +| Return | Description | +| --- | --- | +| `String` | The short name | + +```lua +local name = MyType:short_name() +``` \ No newline at end of file diff --git a/docs/src/ScriptingReference/world.md b/docs/src/ScriptingReference/world.md index 387b40232f..5fc9ff52dd 100644 --- a/docs/src/ScriptingReference/world.md +++ b/docs/src/ScriptingReference/world.md @@ -4,6 +4,8 @@ The `World` is the entry point for interacting with `Bevy`. It is provided to sc ### get_type_by_name +Returns either a `ScriptComponentRegistration` or `ScriptResourceRegistration` depending on the type of the type requested. If the type is neither returns a `ScriptTypeRegistration`. + Arguments: | Argument | Type | Description | @@ -14,7 +16,7 @@ Returns: | Return | Description | | --- | --- | -| `Option` | The type if it exists, otherwise `None` | +| `Option` | The registration for the type if it exists, otherwise `None` | ```lua MyType = world.get_type_by_name("MyType") From db448aa5ed8bf57dfb7af0635c711bf773a7f9ba Mon Sep 17 00:00:00 2001 From: makspll Date: Sun, 19 Jan 2025 21:09:16 +0000 Subject: [PATCH 09/14] remove unnecessary thing --- assets/scripts/game_of_life.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/assets/scripts/game_of_life.lua b/assets/scripts/game_of_life.lua index b0e3a72b40..8b4e59a826 100644 --- a/assets/scripts/game_of_life.lua +++ b/assets/scripts/game_of_life.lua @@ -75,7 +75,6 @@ end function on_update() local cells = fetch_life_state().cells - world.log_all_allocations() local settings = world.get_resource(Settings) local dimensions = settings.physical_grid_dimensions local dimension_x = dimensions._1 From e77a4d8076bb65dd068de60cb2d4bfc52cbedd7a Mon Sep 17 00:00:00 2001 From: makspll Date: Sun, 19 Jan 2025 21:12:37 +0000 Subject: [PATCH 10/14] add badges --- badges/panic-free.svg | 13 +++++++++++++ readme.md | 9 ++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 badges/panic-free.svg diff --git a/badges/panic-free.svg b/badges/panic-free.svg new file mode 100644 index 0000000000..05d0b0e2a1 --- /dev/null +++ b/badges/panic-free.svg @@ -0,0 +1,13 @@ + + PANIC FREE GUARANTEE + + + + + + COVERAGE + 53% + + + \ No newline at end of file diff --git a/readme.md b/readme.md index dae13e5b99..85fc8ba6b2 100644 --- a/readme.md +++ b/readme.md @@ -1,5 +1,4 @@ - - + [^2] [^3] ---

@@ -46,4 +45,8 @@ The languages currently supported are as follows: For examples, installation and usage instructions see our shiny new [book](https://makspll.github.io/bevy_mod_scripting) -[^1]: Due to the recent re-write of the crate, documentation generation as well as rhai and rune support are temporarilly on hold. They will likely be re-implemented in the future. `Rhai` in particualar is difficult to re-implement due to a lack of support for first-class-functions. \ No newline at end of file +[^1]: Due to the recent re-write of the crate, documentation generation as well as rhai and rune support are temporarilly on hold. They will likely be re-implemented in the future. `Rhai` in particualar is difficult to re-implement due to a lack of support for first-class-functions. + +[^2]: the coverage does not include generated bindings. + +[^3]: The crate strictly enforces no `unwrap`, `expect`, `panic` or `todo`'s via clippy lints. \ No newline at end of file From e1253d09f284b5c7f5eea6c286a79cb815647d60 Mon Sep 17 00:00:00 2001 From: makspll Date: Sun, 19 Jan 2025 21:13:52 +0000 Subject: [PATCH 11/14] update badge --- badges/panic-free.svg | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/badges/panic-free.svg b/badges/panic-free.svg index 05d0b0e2a1..92e295c070 100644 --- a/badges/panic-free.svg +++ b/badges/panic-free.svg @@ -1,13 +1,13 @@ - PANIC FREE GUARANTEE + PANIC FREE - COVERAGE - 53% + PANIC FREE + GURANTEE \ No newline at end of file From a4877ce35409e02ba44c1c48e12c14898784d8a4 Mon Sep 17 00:00:00 2001 From: Maksymilian Mozolewski Date: Sun, 19 Jan 2025 21:16:28 +0000 Subject: [PATCH 12/14] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 85fc8ba6b2..05613d2d3e 100644 --- a/readme.md +++ b/readme.md @@ -49,4 +49,4 @@ For examples, installation and usage instructions see our shiny new [book](https [^2]: the coverage does not include generated bindings. -[^3]: The crate strictly enforces no `unwrap`, `expect`, `panic` or `todo`'s via clippy lints. \ No newline at end of file +[^3]: The crate strictly enforces no `unwrap`, `expect`, `panic` or `todo`'s via clippy lints. From 99b845d17da7f253bbb3e96072cb0e076a7be88a Mon Sep 17 00:00:00 2001 From: makspll Date: Sun, 19 Jan 2025 21:17:05 +0000 Subject: [PATCH 13/14] change badge --- badges/{panic-free.svg => no-panic.svg} | 0 readme.md | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename badges/{panic-free.svg => no-panic.svg} (100%) diff --git a/badges/panic-free.svg b/badges/no-panic.svg similarity index 100% rename from badges/panic-free.svg rename to badges/no-panic.svg diff --git a/readme.md b/readme.md index 85fc8ba6b2..7229dfb6ba 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,4 @@ - [^2] [^3] + [^2] [^3] ---

From 1429135684064543059173e06b4ae163441d2a83 Mon Sep 17 00:00:00 2001 From: makspll Date: Sun, 19 Jan 2025 21:18:00 +0000 Subject: [PATCH 14/14] change badge again --- badges/{no-panic.svg => no-panics.svg} | 6 +++--- readme.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) rename badges/{no-panic.svg => no-panics.svg} (81%) diff --git a/badges/no-panic.svg b/badges/no-panics.svg similarity index 81% rename from badges/no-panic.svg rename to badges/no-panics.svg index 92e295c070..6d6809266f 100644 --- a/badges/no-panic.svg +++ b/badges/no-panics.svg @@ -1,13 +1,13 @@ + xmlns:xlink="http://www.w3.org/1999/xlink" width="142.5" height="28" role="img" aria-label="PANIC FREE!"> PANIC FREE - PANIC FREE - GURANTEE + PANIC + FREE! \ No newline at end of file diff --git a/readme.md b/readme.md index e4f5d98a91..a9edc8e66b 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,4 @@ - [^2] [^3] + [^2] [^3] ---