From 1691b3b3232c6e7f2740ac6a3f114f85d73f3acf Mon Sep 17 00:00:00 2001 From: makspll Date: Sun, 12 Oct 2025 14:07:18 +0100 Subject: [PATCH 1/5] feat: registered callbacks --- Cargo.toml | 3 + .../register_callback/dynamic_on_test.lua | 12 +++ assets/tests/register_callback/empty.lua | 0 assets/tests/register_callback/scenario.txt | 27 ++++++ crates/bevy_mod_scripting_bindings/Cargo.toml | 1 + .../src/function/script_function.rs | 13 ++- .../src/reference.rs | 9 +- .../bevy_mod_scripting_bindings/src/world.rs | 44 +++++----- crates/bevy_mod_scripting_core/Cargo.toml | 2 +- .../bevy_mod_scripting_core/src/callbacks.rs | 65 +++++++++++++++ .../bevy_mod_scripting_core/src/commands.rs | 20 +++-- crates/bevy_mod_scripting_core/src/context.rs | 23 +++-- crates/bevy_mod_scripting_core/src/event.rs | 5 +- .../bevy_mod_scripting_core/src/extractors.rs | 54 +----------- crates/bevy_mod_scripting_core/src/handler.rs | 42 +++++++--- crates/bevy_mod_scripting_core/src/lib.rs | 7 +- .../src/pipeline/hooks.rs | 13 ++- .../src/pipeline/machines.rs | 1 + .../src/pipeline/mod.rs | 3 +- .../src/script/context_key.rs | 76 +---------------- .../bevy_mod_scripting_core/src/script/mod.rs | 46 +--------- .../src/script/script_context.rs | 1 + .../src/script_system.rs | 15 ++-- .../bevy_mod_scripting_display/src/handle.rs | 44 ++++++++++ crates/bevy_mod_scripting_display/src/lib.rs | 3 +- .../bevy_mod_scripting_functions/Cargo.toml | 1 + .../bevy_mod_scripting_functions/src/core.rs | 9 +- crates/bevy_mod_scripting_script/Cargo.toml | 21 +++++ crates/bevy_mod_scripting_script/src/lib.rs | 83 +++++++++++++++++++ .../bevy_mod_scripting_lua/Cargo.toml | 1 + .../src/bindings/reference.rs | 78 ++++++++++------- .../bevy_mod_scripting_lua/src/lib.rs | 78 +++++++++++++---- .../bevy_mod_scripting_rhai/Cargo.toml | 1 + .../src/bindings/reference.rs | 73 +++++++++------- .../bevy_mod_scripting_rhai/src/lib.rs | 15 ++-- .../Cargo.toml | 2 + .../src/lib.rs | 13 ++- .../src/parse.rs | 3 +- .../src/scenario.rs | 3 +- .../test_utils/src/test_plugin.rs | 4 - examples/game_of_life.rs | 4 - src/lib.rs | 4 + src/prelude.rs | 4 + 43 files changed, 581 insertions(+), 345 deletions(-) create mode 100644 assets/tests/register_callback/dynamic_on_test.lua create mode 100644 assets/tests/register_callback/empty.lua create mode 100644 assets/tests/register_callback/scenario.txt create mode 100644 crates/bevy_mod_scripting_core/src/callbacks.rs create mode 100644 crates/bevy_mod_scripting_display/src/handle.rs create mode 100644 crates/bevy_mod_scripting_script/Cargo.toml create mode 100644 crates/bevy_mod_scripting_script/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 6a9499c167..8912ebd0b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -112,6 +112,7 @@ bevy_mod_scripting_derive = { workspace = true } bevy_mod_scripting_asset = { workspace = true } bevy_mod_scripting_bindings = { workspace = true } bevy_mod_scripting_display = { workspace = true } +bevy_mod_scripting_script = { workspace = true } [workspace.dependencies] # local crates @@ -127,6 +128,7 @@ bevy_mod_scripting_rhai = { path = "crates/languages/bevy_mod_scripting_rhai", v bevy_mod_scripting_asset = { path = "crates/bevy_mod_scripting_asset", version = "0.16.0", default-features = false } bevy_mod_scripting_bindings = { path = "crates/bevy_mod_scripting_bindings", version = "0.16.0", default-features = false } bevy_mod_scripting_display = { path = "crates/bevy_mod_scripting_display", version = "0.16.0", default-features = false } +bevy_mod_scripting_script = { path = "crates/bevy_mod_scripting_script", version = "0.16.0", default-features = false } # bevy bevy_mod_scripting_core = { path = "crates/bevy_mod_scripting_core", version = "0.16.0" } @@ -261,6 +263,7 @@ members = [ "crates/bevy_mod_scripting_asset", "crates/bevy_mod_scripting_bindings", "crates/bevy_mod_scripting_display", + "crates/bevy_mod_scripting_script", ] resolver = "2" exclude = ["codegen", "crates/macro_tests", "xtask"] diff --git a/assets/tests/register_callback/dynamic_on_test.lua b/assets/tests/register_callback/dynamic_on_test.lua new file mode 100644 index 0000000000..3dc09a778e --- /dev/null +++ b/assets/tests/register_callback/dynamic_on_test.lua @@ -0,0 +1,12 @@ +function on_script_loaded() + register_callback("on_test", dynamic_on_test) +end + +function dynamic_on_test() + register_callback("on_test_last", dynamic_on_test_last) + return "on test: I am dynamically registered from a normal callback!" +end + +function dynamic_on_test_last() + return "on test last: I am dynamically registered from another dynamic callback!" +end diff --git a/assets/tests/register_callback/empty.lua b/assets/tests/register_callback/empty.lua new file mode 100644 index 0000000000..e69de29bb2 diff --git a/assets/tests/register_callback/scenario.txt b/assets/tests/register_callback/scenario.txt new file mode 100644 index 0000000000..a0086eb45e --- /dev/null +++ b/assets/tests/register_callback/scenario.txt @@ -0,0 +1,27 @@ +// #main_script dynamic_on_test.lua +SetCurrentLanguage language="@this_script_language" +InstallPlugin miliseconds_budget=999999 +SetupHandler OnTest=null, Update=null +SetupHandler OnTestPostUpdate=null, PostUpdate=null +SetupHandler Last=null, OnTestLast=null +FinalizeApp + +LoadScriptAs as_name="@this_script", path="@this_script" +WaitForScriptLoaded name="@this_script" +SpawnEntityWithScript name="test_entity", script="@this_script" +RunUpdateOnce +EmitScriptCallbackEvent emit_response=true, entity="test_entity", label="OnTest", language=null, recipients="EntityScript", script="@this_script" +EmitScriptCallbackEvent emit_response=true, entity="test_entity", label="OnTestLast", language=null, recipients="EntityScript", script="@this_script" +RunUpdateOnce +AssertCallbackSuccess attachment="EntityScript", entity="test_entity", label="OnTest", script="@this_script", expect_string_value="on test: I am dynamically registered from a normal callback!" +AssertCallbackSuccess attachment="EntityScript", entity="test_entity", label="OnTestLast", script="@this_script", expect_string_value="on test last: I am dynamically registered from another dynamic callback!" + +// reload, deleting old callbacks, expect stored callbacks to still work +ReloadScriptFrom script="@this_script", path="empty.lua" +RunUpdateOnce +EmitScriptCallbackEvent emit_response=true, entity="test_entity", label="OnTest", language=null, recipients="EntityScript", script="@this_script" +EmitScriptCallbackEvent emit_response=true, entity="test_entity", label="OnTestLast", language=null, recipients="EntityScript", script="@this_script" +RunUpdateOnce +AssertCallbackSuccess attachment="EntityScript", entity="test_entity", label="OnTest", script="@this_script", expect_string_value="on test: I am dynamically registered from a normal callback!" +AssertCallbackSuccess attachment="EntityScript", entity="test_entity", label="OnTestLast", script="@this_script", expect_string_value="on test last: I am dynamically registered from another dynamic callback!" + diff --git a/crates/bevy_mod_scripting_bindings/Cargo.toml b/crates/bevy_mod_scripting_bindings/Cargo.toml index a1c0a3fbcb..a94123c391 100644 --- a/crates/bevy_mod_scripting_bindings/Cargo.toml +++ b/crates/bevy_mod_scripting_bindings/Cargo.toml @@ -16,6 +16,7 @@ readme.workspace = true bevy_mod_scripting_asset = { workspace = true } bevy_mod_scripting_derive = { workspace = true } bevy_mod_scripting_display = { workspace = true } +bevy_mod_scripting_script = { workspace = true } bevy_system_reflection = { workspace = true } bevy_diagnostic = { workspace = true } bevy_ecs = { workspace = true } diff --git a/crates/bevy_mod_scripting_bindings/src/function/script_function.rs b/crates/bevy_mod_scripting_bindings/src/function/script_function.rs index 75d57a04f1..d563aff1a1 100644 --- a/crates/bevy_mod_scripting_bindings/src/function/script_function.rs +++ b/crates/bevy_mod_scripting_bindings/src/function/script_function.rs @@ -4,7 +4,7 @@ use super::MagicFunctions; use super::{from::FromScript, into::IntoScript, namespace::Namespace}; use crate::docgen::info::{FunctionInfo, GetFunctionInfo}; use crate::function::arg_meta::ArgMeta; -use crate::{ScriptValue, ThreadWorldContainer, WorldContainer, WorldGuard, error::InteropError}; +use crate::{ScriptValue, ThreadWorldContainer, WorldGuard, error::InteropError}; use bevy_ecs::prelude::Resource; use bevy_mod_scripting_asset::Language; use bevy_mod_scripting_derive::DebugWithTypeInfo; @@ -55,7 +55,7 @@ impl FunctionCallContext { /// Tries to access the world, returning an error if the world is not available #[profiling::function] pub fn world<'l>(&self) -> Result, InteropError> { - ThreadWorldContainer.try_get_world() + ThreadWorldContainer.try_get_context().map(|c| c.world) } /// Whether the caller uses 1-indexing on all indexes and expects 0-indexing conversions to be performed. #[profiling::function] @@ -661,12 +661,19 @@ variadics_please::all_tuples!(impl_script_function, 0, 13, T); #[cfg(test)] mod test { use super::*; + use bevy_asset::{AssetId, Handle}; use bevy_ecs::{prelude::Component, world::World}; + use bevy_mod_scripting_script::ScriptAttachment; fn with_local_world(f: F) { let mut world = World::default(); WorldGuard::with_static_guard(&mut world, |world| { - ThreadWorldContainer.set_world(world).unwrap(); + ThreadWorldContainer + .set_context(crate::ThreadScriptContext { + world, + attachment: ScriptAttachment::StaticScript(Handle::Weak(AssetId::invalid())), + }) + .unwrap(); f() }); } diff --git a/crates/bevy_mod_scripting_bindings/src/reference.rs b/crates/bevy_mod_scripting_bindings/src/reference.rs index 664cabcd43..6a6259f798 100644 --- a/crates/bevy_mod_scripting_bindings/src/reference.rs +++ b/crates/bevy_mod_scripting_bindings/src/reference.rs @@ -6,9 +6,8 @@ //! we need wrapper types which have owned and ref variants. use super::{WorldGuard, access_map::ReflectAccessId}; use crate::{ - ReflectAllocationId, ReflectAllocator, ThreadWorldContainer, WorldContainer, - error::InteropError, reflection_extensions::PartialReflectExt, with_access_read, - with_access_write, + ReflectAllocationId, ReflectAllocator, ThreadWorldContainer, error::InteropError, + reflection_extensions::PartialReflectExt, with_access_read, with_access_write, }; use bevy_asset::{ReflectAsset, UntypedHandle}; use bevy_ecs::{component::Component, ptr::Ptr, resource::Resource}; @@ -68,7 +67,7 @@ impl DisplayWithTypeInfo for ReflectReference { let guard = any.downcast_ref::().cloned().or_else(|| { any.downcast_ref::() - .and_then(|t| t.try_get_world().ok()) + .and_then(|t| t.try_get_context().ok().map(|c| c.world)) }); if let Some(guard) = guard { @@ -767,7 +766,7 @@ impl DisplayWithTypeInfo for ReflectBase { let guard = any.downcast_ref::().cloned().or_else(|| { any.downcast_ref::() - .and_then(|t| t.try_get_world().ok()) + .and_then(|t| t.try_get_context().ok().map(|c| c.world)) }); if let Some(guard) = guard { diff --git a/crates/bevy_mod_scripting_bindings/src/world.rs b/crates/bevy_mod_scripting_bindings/src/world.rs index bb61e92046..2a76e1cfa3 100644 --- a/crates/bevy_mod_scripting_bindings/src/world.rs +++ b/crates/bevy_mod_scripting_bindings/src/world.rs @@ -51,6 +51,7 @@ use bevy_ecs::{ }; use bevy_mod_scripting_asset::ScriptAsset; use bevy_mod_scripting_display::GetTypeInfo; +use bevy_mod_scripting_script::ScriptAttachment; use bevy_platform::collections::HashMap; use bevy_reflect::{TypeInfo, VariantInfo}; use bevy_system_reflection::ReflectSchedule; @@ -1337,44 +1338,45 @@ impl WorldAccessGuard<'_> { } } -/// Utility type for accessing the world in a callback -pub trait WorldContainer { - /// The error type for the container - type Error: Debug; - /// Sets the world to the given value in the container - fn set_world(&mut self, world: WorldGuard<'static>) -> Result<(), Self::Error>; - - /// Tries to get the world from the container - fn try_get_world<'l>(&self) -> Result, Self::Error>; -} - /// A world container that stores the world in a thread local pub struct ThreadWorldContainer; +#[derive(Clone)] +/// Context passed down indirectly to script related functions, used to avoid prop drilling problems. +pub struct ThreadScriptContext<'l> { + /// The world pointer + pub world: WorldGuard<'l>, + /// The currently active script attachment + pub attachment: ScriptAttachment, +} + thread_local! { - static WORLD_CALLBACK_ACCESS: RefCell>> = const { RefCell::new(None) }; + static WORLD_CALLBACK_ACCESS: RefCell>> = const { RefCell::new(None) }; } #[profiling::all_functions] -impl WorldContainer for ThreadWorldContainer { - type Error = InteropError; - - fn set_world(&mut self, world: WorldGuard<'static>) -> Result<(), Self::Error> { +impl ThreadWorldContainer { + /// Tries to set the thread context to the given value + pub fn set_context(&mut self, world: ThreadScriptContext<'static>) -> Result<(), InteropError> { WORLD_CALLBACK_ACCESS.with(|w| { w.replace(Some(world)); }); Ok(()) } - fn try_get_world<'l>(&self) -> Result, Self::Error> { + /// Tries to get the world from the container + pub fn try_get_context<'l>(&self) -> Result, InteropError> { WORLD_CALLBACK_ACCESS .with(|w| w.borrow().clone().ok_or_else(InteropError::missing_world)) - .map(|v| v.shorten_lifetime()) + .map(|v| ThreadScriptContext { + world: v.world.shorten_lifetime(), + attachment: v.attachment, + }) } } impl GetTypeInfo for ThreadWorldContainer { fn get_type_info(&self, type_id: TypeId) -> Option<&TypeInfo> { - let world = self.try_get_world().ok()?; + let world = self.try_get_context().ok()?.world; let registry = world.type_registry(); let registry = registry.read(); registry.get(type_id).map(|r| r.type_info()) @@ -1385,7 +1387,7 @@ impl GetTypeInfo for ThreadWorldContainer { type_id: TypeId, type_data_id: TypeId, ) -> Option> { - let world = self.try_get_world().ok()?; + let world = self.try_get_context().ok()?.world; let registry = world.type_registry(); let registry = registry.read(); registry @@ -1397,7 +1399,7 @@ impl GetTypeInfo for ThreadWorldContainer { &self, component_id: ComponentId, ) -> Option<&bevy_ecs::component::ComponentInfo> { - let world = self.try_get_world().ok()?; + let world = self.try_get_context().ok()?.world; let cell = world.as_unsafe_world_cell().ok()?; cell.components().get_info(component_id) } diff --git a/crates/bevy_mod_scripting_core/Cargo.toml b/crates/bevy_mod_scripting_core/Cargo.toml index dd9de7dc6e..34d187e9d8 100644 --- a/crates/bevy_mod_scripting_core/Cargo.toml +++ b/crates/bevy_mod_scripting_core/Cargo.toml @@ -27,7 +27,7 @@ bevy_mod_scripting_asset = { workspace = true } bevy_mod_scripting_bindings = { workspace = true } bevy_system_reflection = { workspace = true } bevy_mod_scripting_display = { workspace = true } - +bevy_mod_scripting_script = { workspace = true } bevy_reflect = { workspace = true, default-features = false, features = [] } bevy_ecs = { workspace = true, default-features = false, features = [] } bevy_app = { workspace = true, default-features = false, features = [] } diff --git a/crates/bevy_mod_scripting_core/src/callbacks.rs b/crates/bevy_mod_scripting_core/src/callbacks.rs new file mode 100644 index 0000000000..d85b0da015 --- /dev/null +++ b/crates/bevy_mod_scripting_core/src/callbacks.rs @@ -0,0 +1,65 @@ +//! Module managing script registered callbacks +use std::{marker::PhantomData, sync::Arc}; + +use bevy_app::Plugin; +use bevy_ecs::{resource::Resource, world::WorldId}; +use bevy_mod_scripting_bindings::{InteropError, ScriptValue}; +use bevy_mod_scripting_script::ScriptAttachment; +use bevy_platform::collections::HashMap; +use parking_lot::RwLock; + +use crate::IntoScriptPluginParams; + +/// A callback function encapsulating the callback context +pub type CallbackFn

= dyn Fn( + Vec, + &mut

::C, + WorldId, + ) -> Result + + Send + + Sync + + 'static; + +/// A resource containing the callbacks for this script plugin. +/// +/// Scripts can opt to register callbacks which are then used to invoke event callbacks. +#[derive(Resource)] +pub struct ScriptCallbacks { + /// The callbacks registered per script + pub callbacks: Arc>>>>, +} + +impl Clone for ScriptCallbacks

{ + fn clone(&self) -> Self { + Self { + callbacks: self.callbacks.clone(), + } + } +} + +impl Default for ScriptCallbacks

{ + fn default() -> Self { + Self { + callbacks: Default::default(), + } + } +} + +/// Plugin adding necessary resources to handle callback storage for script plugins. +pub struct ScriptCallbacksPlugin { + _ph: PhantomData, +} + +impl Default for ScriptCallbacksPlugin

{ + fn default() -> Self { + Self { + _ph: Default::default(), + } + } +} + +impl Plugin for ScriptCallbacksPlugin

{ + fn build(&self, app: &mut bevy_app::App) { + app.init_resource::>(); + } +} diff --git a/crates/bevy_mod_scripting_core/src/commands.rs b/crates/bevy_mod_scripting_core/src/commands.rs index 88d5d0681b..ade22041f1 100644 --- a/crates/bevy_mod_scripting_core/src/commands.rs +++ b/crates/bevy_mod_scripting_core/src/commands.rs @@ -4,19 +4,20 @@ use std::{sync::Arc, time::Duration}; use crate::{ IntoScriptPluginParams, ScriptContext, + callbacks::ScriptCallbacks, error::ScriptError, event::{ CallbackLabel, ForPlugin, ScriptAttachedEvent, ScriptCallbackResponseEvent, ScriptDetachedEvent, }, - extractors::CallContext, - handler::{send_callback_response, send_script_errors}, + handler::{ScriptingHandler, send_callback_response, send_script_errors}, pipeline::RunProcessingPipelineOnce, - script::{DisplayProxy, ScriptAttachment}, }; use bevy_ecs::{system::Command, world::World}; use bevy_log::trace; use bevy_mod_scripting_bindings::{ScriptValue, WorldGuard}; +use bevy_mod_scripting_display::DisplayProxy; +use bevy_mod_scripting_script::ScriptAttachment; use parking_lot::Mutex; /// Runs a callback on the script with the given ID if it exists @@ -66,12 +67,15 @@ impl RunScriptCallback

{ self, guard: WorldGuard, ctxt: Arc>, + script_callbacks: ScriptCallbacks

, ) -> Result { let mut ctxt_guard = ctxt.lock(); - let result = ctxt_guard.call_context_dynamic( + let result = P::handle( + self.args, &self.attachment, &self.callback, - self.args, + &mut ctxt_guard, + script_callbacks, guard.clone(), ); let result = result.map_err(|e| { @@ -109,6 +113,7 @@ impl RunScriptCallback

{ self, guard: WorldGuard, script_contexts: ScriptContext

, + script_callbacks: ScriptCallbacks

, ) -> Result { let script_contexts = script_contexts.read(); let ctxt = script_contexts.get_context(&self.attachment); @@ -125,7 +130,7 @@ impl RunScriptCallback

{ } }; - self.run_with_context(guard, ctxt.clone()) + self.run_with_context(guard, ctxt.clone(), script_callbacks) } /// Equivalent to running the command, but also returns the result of the callback. @@ -133,8 +138,9 @@ impl RunScriptCallback

{ /// The returned errors will NOT be sent as events or printed pub fn run(self, world: &mut World) -> Result { let script_contexts = world.get_resource_or_init::>().clone(); + let script_callbacks = world.get_resource_or_init::>().clone(); let guard = WorldGuard::new_exclusive(world); - self.run_with_contexts(guard, script_contexts) + self.run_with_contexts(guard, script_contexts, script_callbacks) } } diff --git a/crates/bevy_mod_scripting_core/src/context.rs b/crates/bevy_mod_scripting_core/src/context.rs index 57e9d8fcaa..18bc6ccee2 100644 --- a/crates/bevy_mod_scripting_core/src/context.rs +++ b/crates/bevy_mod_scripting_core/src/context.rs @@ -1,16 +1,21 @@ //! Traits and types for managing script contexts. +use std::any::Any; + use bevy_ecs::world::WorldId; -use bevy_mod_scripting_bindings::{InteropError, ThreadWorldContainer, WorldContainer, WorldGuard}; +use bevy_mod_scripting_bindings::{ + InteropError, ThreadScriptContext, ThreadWorldContainer, WorldGuard, +}; +use bevy_mod_scripting_script::ScriptAttachment; -use crate::{IntoScriptPluginParams, extractors::GetPluginFor, script::ScriptAttachment}; +use crate::IntoScriptPluginParams; /// A trait that all script contexts must implement. /// /// Contexts are not required to be `Sync` as they are internally stored behind a `Mutex` but they must satisfy `Send` so they can be /// freely sent between threads. -pub trait Context: 'static + Send + GetPluginFor {} -impl Context for T {} +pub trait Context: 'static + Send + Any {} +impl Context for T {} /// Initializer run once after creating a context but before executing it for the first time as well as after re-loading the script pub type ContextInitializer

= @@ -63,7 +68,10 @@ impl ScriptingLoader

for P { ) -> Result { WorldGuard::with_existing_static_guard(world.clone(), |world| { let world_id = world.id(); - ThreadWorldContainer.set_world(world)?; + ThreadWorldContainer.set_context(ThreadScriptContext { + world, + attachment: attachment.clone(), + })?; Self::context_loader()(attachment, content, world_id) }) } @@ -76,7 +84,10 @@ impl ScriptingLoader

for P { ) -> Result<(), InteropError> { WorldGuard::with_existing_static_guard(world, |world| { let world_id = world.id(); - ThreadWorldContainer.set_world(world)?; + ThreadWorldContainer.set_context(ThreadScriptContext { + world, + attachment: attachment.clone(), + })?; Self::context_reloader()(attachment, content, previous_context, world_id) }) } diff --git a/crates/bevy_mod_scripting_core/src/event.rs b/crates/bevy_mod_scripting_core/src/event.rs index ad104da2bd..86667e589b 100644 --- a/crates/bevy_mod_scripting_core/src/event.rs +++ b/crates/bevy_mod_scripting_core/src/event.rs @@ -6,12 +6,13 @@ use ::{bevy_asset::Handle, bevy_ecs::entity::Entity, bevy_reflect::Reflect}; use bevy_ecs::event::Event; use bevy_mod_scripting_asset::Language; use bevy_mod_scripting_bindings::ScriptValue; +use bevy_mod_scripting_script::ScriptAttachment; use parking_lot::Mutex; use crate::{ IntoScriptPluginParams, error::ScriptError, - script::{ScriptAttachment, ScriptContext, ScriptId}, + script::{ScriptContext, ScriptId}, }; /// An error coming from a script @@ -428,7 +429,7 @@ mod test { use crate::{ config::{GetPluginThreadConfig, ScriptingPluginConfiguration}, event::Recipients, - script::{ContextPolicy, ScriptAttachment, ScriptContext}, + script::{ContextPolicy, ScriptContext}, }; #[test] diff --git a/crates/bevy_mod_scripting_core/src/extractors.rs b/crates/bevy_mod_scripting_core/src/extractors.rs index 785ef8fda2..4973e199c6 100644 --- a/crates/bevy_mod_scripting_core/src/extractors.rs +++ b/crates/bevy_mod_scripting_core/src/extractors.rs @@ -2,13 +2,6 @@ //! //! These are designed to be used to pipe inputs into other systems which require them, while handling any configuration erorrs nicely. -use crate::{ - IntoScriptPluginParams, - context::Context, - event::{CallbackLabel, IntoCallbackLabel}, - handler::ScriptingHandler, - script::ScriptAttachment, -}; use bevy_ecs::{ archetype::Archetype, component::{ComponentId, Tick}, @@ -17,54 +10,10 @@ use bevy_ecs::{ system::{SystemMeta, SystemParam, SystemParamValidationError}, world::{DeferredWorld, World, unsafe_world_cell::UnsafeWorldCell}, }; -use bevy_mod_scripting_bindings::{ - InteropError, WorldAccessGuard, WorldGuard, access_map::ReflectAccessId, - script_value::ScriptValue, -}; +use bevy_mod_scripting_bindings::{WorldAccessGuard, WorldGuard, access_map::ReflectAccessId}; use fixedbitset::FixedBitSet; -/// A reverse mapping from plugin context types to their plugin types. -/// Useful in implementing generic traits on context types. -pub trait GetPluginFor { - /// The plugin type associated with this context type - type P: IntoScriptPluginParams; -} - -/// A utility trait extending arbitrary script contexts with the ability to call callbacks on themselves using their -/// plugin handler function. -pub trait CallContext { - /// Invoke a callback on this context - fn call_context_dynamic( - &mut self, - context_key: &ScriptAttachment, - label: &CallbackLabel, - payload: Vec, - guard: WorldGuard<'_>, - ) -> Result; - - /// Invoke a callback on this context - fn call_context( - &mut self, - context_key: &ScriptAttachment, - payload: Vec, - guard: WorldGuard<'_>, - ) -> Result { - self.call_context_dynamic(context_key, &C::into_callback_label(), payload, guard) - } -} - -impl CallContext for C { - fn call_context_dynamic( - &mut self, - context_key: &ScriptAttachment, - label: &CallbackLabel, - payload: Vec, - guard: WorldGuard<'_>, - ) -> Result { - C::P::handle(payload, context_key, label, self, guard) - } -} /// A wrapper around a world which pre-populates access, to safely co-exist with other system params, /// acts exactly like `&mut World` so this should be your only top-level system param /// @@ -219,6 +168,7 @@ pub(crate) fn get_all_access_ids(access: &Access) -> Vec<(ReflectAc mod test { use crate::config::{GetPluginThreadConfig, ScriptingPluginConfiguration}; use bevy_ecs::resource::Resource; + use bevy_mod_scripting_bindings::ScriptValue; use test_utils::make_test_plugin; use { diff --git a/crates/bevy_mod_scripting_core/src/handler.rs b/crates/bevy_mod_scripting_core/src/handler.rs index 84f1fba314..631e3d15b7 100644 --- a/crates/bevy_mod_scripting_core/src/handler.rs +++ b/crates/bevy_mod_scripting_core/src/handler.rs @@ -1,21 +1,22 @@ //! Contains the logic for handling script callback events use bevy_ecs::world::WorldId; use bevy_mod_scripting_bindings::{ - InteropError, ScriptValue, ThreadWorldContainer, WorldAccessGuard, WorldContainer, WorldGuard, + InteropError, ScriptValue, ThreadScriptContext, ThreadWorldContainer, WorldAccessGuard, + WorldGuard, }; -use bevy_mod_scripting_display::WithTypeInfo; +use bevy_mod_scripting_display::{DisplayProxy, WithTypeInfo}; +use bevy_mod_scripting_script::ScriptAttachment; -use crate::extractors::CallContext; -use crate::script::DisplayProxy; use crate::{ IntoScriptPluginParams, + callbacks::ScriptCallbacks, error::ScriptError, event::{ CallbackLabel, IntoCallbackLabel, ScriptCallbackEvent, ScriptCallbackResponseEvent, ScriptErrorEvent, }, extractors::WithWorldGuard, - script::{ScriptAttachment, ScriptContext}, + script::ScriptContext, }; use { bevy_ecs::{ @@ -47,6 +48,7 @@ pub trait ScriptingHandler { context_key: &ScriptAttachment, callback: &CallbackLabel, script_ctxt: &mut P::C, + script_callbacks: ScriptCallbacks

, world: WorldGuard, ) -> Result; } @@ -55,15 +57,29 @@ impl ScriptingHandler

for P { /// Calls the handler function while providing the necessary thread local context fn handle( args: Vec, - context_key: &ScriptAttachment, + attachment: &ScriptAttachment, callback: &CallbackLabel, script_ctxt: &mut P::C, + script_callbacks: ScriptCallbacks

, world: WorldGuard, ) -> Result { WorldGuard::with_existing_static_guard(world.clone(), |world| { let world_id = world.id(); - ThreadWorldContainer.set_world(world)?; - Self::handler()(args, context_key, callback, script_ctxt, world_id) + ThreadWorldContainer.set_context(ThreadScriptContext { + world, + attachment: attachment.clone(), + })?; + let callbacks = script_callbacks.callbacks.read(); + if let Some(callback) = callbacks + .get(&(attachment.clone(), callback.to_string())) + .cloned() + { + drop(callbacks); + callback(args, script_ctxt, world_id) + } else { + drop(callbacks); + Self::handler()(args, attachment, callback, script_ctxt, world_id) + } }) } } @@ -79,12 +95,14 @@ pub fn event_handler( // we wrap the inner event handler, so that we can guarantee that the handler context is released statically { let script_context = world.get_resource_or_init::>().clone(); + let script_callbacks = world.get_resource_or_init::>().clone(); let (event_cursor, mut guard) = state.get_mut(world); let (guard, _) = guard.get_mut(); event_handler_inner::

( L::into_callback_label(), event_cursor, script_context, + script_callbacks, guard, ); } @@ -101,6 +119,7 @@ pub(crate) fn event_handler_inner( callback_label: CallbackLabel, mut event_cursor: Local>, script_context: ScriptContext

, + script_callbacks: ScriptCallbacks

, guard: WorldAccessGuard, ) { let mut errors = Vec::default(); @@ -130,10 +149,13 @@ pub(crate) fn event_handler_inner( for (attachment, ctxt) in recipients { let mut ctxt = ctxt.lock(); - let call_result = ctxt.call_context_dynamic( + + let call_result = P::handle( + event.args.clone(), &attachment, &callback_label, - event.args.clone(), + &mut ctxt, + script_callbacks.clone(), guard.clone(), ); let call_result = call_result.map_err(|e| { diff --git a/crates/bevy_mod_scripting_core/src/lib.rs b/crates/bevy_mod_scripting_core/src/lib.rs index 7fbfe3185b..0067463b84 100644 --- a/crates/bevy_mod_scripting_core/src/lib.rs +++ b/crates/bevy_mod_scripting_core/src/lib.rs @@ -3,6 +3,7 @@ //! Contains language agnostic systems and types for handling scripting in bevy. use crate::{ + callbacks::ScriptCallbacksPlugin, config::{GetPluginThreadConfig, ScriptingPluginConfiguration}, context::{ContextLoadFn, ContextReloadFn}, event::ScriptErrorEvent, @@ -30,6 +31,7 @@ use handler::HandlerFn; use runtime::{Runtime, RuntimeInitializer}; use script::{ContextPolicy, ScriptComponent, ScriptContext}; +pub mod callbacks; pub mod commands; pub mod config; pub mod context; @@ -173,7 +175,10 @@ impl Plugin for ScriptingPlugin

{ app.insert_resource(ScriptContext::

::new(self.context_policy.clone())); app.register_asset_loader(ScriptAssetLoader::new(config.language_extensions)); - app.add_plugins(self.processing_pipeline_plugin.clone()); + app.add_plugins(( + self.processing_pipeline_plugin.clone(), + ScriptCallbacksPlugin::

::default(), + )); register_types(app); } diff --git a/crates/bevy_mod_scripting_core/src/pipeline/hooks.rs b/crates/bevy_mod_scripting_core/src/pipeline/hooks.rs index 50ba89d223..7f1d846153 100644 --- a/crates/bevy_mod_scripting_core/src/pipeline/hooks.rs +++ b/crates/bevy_mod_scripting_core/src/pipeline/hooks.rs @@ -1,6 +1,7 @@ use bevy_mod_scripting_bindings::ScriptValue; use crate::{ + callbacks::ScriptCallbacks, commands::RunScriptCallback, event::{IntoCallbackLabel, OnScriptLoaded, OnScriptReloaded, OnScriptUnloaded}, }; @@ -20,6 +21,7 @@ impl TransitionListener> for OnLoa ) -> Result<(), ScriptError> { let world_id = world.id(); let emit_responses = P::readonly_configuration(world_id).emit_responses; + let callbacks = world.get_resource_or_init::>().clone(); let guard = WorldGuard::new_exclusive(world); RunScriptCallback::

::new( @@ -28,7 +30,7 @@ impl TransitionListener> for OnLoa vec![], emit_responses, ) - .run_with_context(guard.clone(), state.context.clone()) + .run_with_context(guard.clone(), state.context.clone(), callbacks) .map(|_| ()) } } @@ -45,6 +47,7 @@ impl TransitionListener> ) -> Result<(), ScriptError> { let world_id = world.id(); let emit_responses = P::readonly_configuration(world_id).emit_responses; + let callbacks = world.get_resource_or_init::>().clone(); let guard = WorldGuard::new_exclusive(world); let v = RunScriptCallback::

::new( @@ -53,7 +56,7 @@ impl TransitionListener> vec![], emit_responses, ) - .run_with_context(guard.clone(), state.existing_context.clone())?; + .run_with_context(guard.clone(), state.existing_context.clone(), callbacks)?; ctxt.insert(UNLOADED_SCRIPT_STATE_KEY, v); Ok(()) } @@ -71,6 +74,7 @@ impl TransitionListener> ) -> Result<(), ScriptError> { let world_id = world.id(); let emit_responses = P::readonly_configuration(world_id).emit_responses; + let callbacks = world.get_resource_or_init::>().clone(); let guard = WorldGuard::new_exclusive(world); let v = RunScriptCallback::

::new( @@ -79,7 +83,7 @@ impl TransitionListener> vec![], emit_responses, ) - .run_with_context(guard.clone(), state.existing_context.clone())?; + .run_with_context(guard.clone(), state.existing_context.clone(), callbacks)?; ctxt.insert(UNLOADED_SCRIPT_STATE_KEY, v); Ok(()) } @@ -95,6 +99,7 @@ impl TransitionListener> for OnRel ) -> Result<(), ScriptError> { let world_id = world.id(); let emit_responses = P::readonly_configuration(world_id).emit_responses; + let callbacks = world.get_resource_or_init::>().clone(); let guard = WorldGuard::new_exclusive(world); if state.is_new_context { @@ -110,7 +115,7 @@ impl TransitionListener> for OnRel vec![unload_state], emit_responses, ) - .run_with_context(guard.clone(), state.context.clone()) + .run_with_context(guard.clone(), state.context.clone(), callbacks) .map(|_| ()) } } diff --git a/crates/bevy_mod_scripting_core/src/pipeline/machines.rs b/crates/bevy_mod_scripting_core/src/pipeline/machines.rs index ed2709be3f..1849daaa0b 100644 --- a/crates/bevy_mod_scripting_core/src/pipeline/machines.rs +++ b/crates/bevy_mod_scripting_core/src/pipeline/machines.rs @@ -8,6 +8,7 @@ use std::{ use bevy_log::debug; use bevy_mod_scripting_bindings::InteropError; +use bevy_mod_scripting_script::ScriptAttachment; use bevy_platform::collections::HashMap; use super::*; diff --git a/crates/bevy_mod_scripting_core/src/pipeline/mod.rs b/crates/bevy_mod_scripting_core/src/pipeline/mod.rs index 821c485f9f..cf7dc2936e 100644 --- a/crates/bevy_mod_scripting_core/src/pipeline/mod.rs +++ b/crates/bevy_mod_scripting_core/src/pipeline/mod.rs @@ -29,7 +29,7 @@ use crate::{ OnLoadedListener, OnReloadedListener, OnUnloadedForReloadListener, OnUnloadedForUnloadListener, }, - script::{ScriptAttachment, ScriptContext, ScriptId}, + script::{ScriptContext, ScriptId}, }; mod hooks; @@ -351,6 +351,7 @@ mod test { use bevy_ecs::{entity::Entity, system::SystemState, world::FromWorld}; use bevy_mod_scripting_asset::Language; use bevy_mod_scripting_bindings::ScriptValue; + use bevy_mod_scripting_script::ScriptAttachment; use test_utils::make_test_plugin; use crate::config::{GetPluginThreadConfig, ScriptingPluginConfiguration}; diff --git a/crates/bevy_mod_scripting_core/src/script/context_key.rs b/crates/bevy_mod_scripting_core/src/script/context_key.rs index 026c42a1fd..640d32abfe 100644 --- a/crates/bevy_mod_scripting_core/src/script/context_key.rs +++ b/crates/bevy_mod_scripting_core/src/script/context_key.rs @@ -1,85 +1,11 @@ use std::fmt; use bevy_ecs::entity::Entity; +use bevy_mod_scripting_display::DisplayProxy; use super::*; use crate::ScriptAsset; -/// Specifies a unique attachment of a script. These attachments are mapped to [`ContextKey`]'s depending on the context policy used. -#[derive(Debug, Hash, Clone, PartialEq, Eq, Reflect)] -pub enum ScriptAttachment { - /// a script attached to an entity, with an optional domain. By default selecting a domain will put the context of this script on a per-domain basis. - EntityScript(Entity, Handle), - /// a static script, with an optional domain. By default selecting a domain will put the context of this script on a per-domain basis. - StaticScript(Handle), -} - -impl std::fmt::Display for ScriptAttachment { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - ScriptAttachment::EntityScript(entity, script) => { - write!( - f, - "EntityScript(entity: {}, script: {})", - entity, - script.display(), - ) - } - ScriptAttachment::StaticScript(script) => { - write!(f, "StaticScript(script: {})", script.display()) - } - } - } -} - -impl ScriptAttachment { - /// Returns the script handle. - pub fn script(&self) -> Handle { - match self { - ScriptAttachment::EntityScript(_, script) => script.clone(), - ScriptAttachment::StaticScript(script) => script.clone(), - } - } - - /// Returns a mutable reference to the underlying script handle. - pub fn script_mut(&mut self) -> &mut Handle { - match self { - ScriptAttachment::EntityScript(_, script) => script, - ScriptAttachment::StaticScript(script) => script, - } - } - - /// Returns the entity if it exists. - pub fn entity(&self) -> Option { - match self { - ScriptAttachment::EntityScript(entity, _) => Some(*entity), - ScriptAttachment::StaticScript(_) => None, - } - } - - /// Downcasts any script handles into weak handles. - pub fn into_weak(self) -> Self { - match self { - ScriptAttachment::EntityScript(entity, script) => { - ScriptAttachment::EntityScript(entity, script.clone_weak()) - } - ScriptAttachment::StaticScript(script) => { - ScriptAttachment::StaticScript(script.clone_weak()) - } - } - } - - /// Returns true if the attachment is a static script. - pub fn is_static(&self) -> bool { - matches!(self, ScriptAttachment::StaticScript(_)) - } - - /// Returns true if the attachment is an entity script. - pub fn is_entity_script(&self) -> bool { - matches!(self, ScriptAttachment::EntityScript(_, _)) - } -} - impl From for ContextKey { fn from(val: ScriptAttachment) -> Self { match val { diff --git a/crates/bevy_mod_scripting_core/src/script/mod.rs b/crates/bevy_mod_scripting_core/src/script/mod.rs index e7b42faa56..f8d9732da8 100644 --- a/crates/bevy_mod_scripting_core/src/script/mod.rs +++ b/crates/bevy_mod_scripting_core/src/script/mod.rs @@ -2,14 +2,13 @@ use std::{ collections::{HashMap, HashSet}, - fmt, ops::Deref, }; use crate::event::{ScriptAttachedEvent, ScriptDetachedEvent}; use ::{ - bevy_asset::{Asset, AssetId, Handle}, + bevy_asset::{AssetId, Handle}, bevy_ecs::{ component::HookContext, entity::Entity, prelude::ReflectComponent, resource::Resource, world::DeferredWorld, @@ -21,6 +20,7 @@ mod context_key; mod script_context; use bevy_ecs::component::Component; use bevy_mod_scripting_asset::ScriptAsset; +use bevy_mod_scripting_script::ScriptAttachment; pub use context_key::*; pub use script_context::*; @@ -29,48 +29,6 @@ pub use script_context::*; /// I.e. an asset with the path `path/to/asset.ext` will have the script id `path/to/asset.ext` pub type ScriptId = AssetId; -/// Display the path of a script or its asset ID. -#[doc(hidden)] -pub struct HandleDisplay<'a, T: Asset>(&'a Handle); - -impl<'a, A: Asset> fmt::Display for HandleDisplay<'a, A> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(path) = self.0.path() { - write!(f, "path {path}") - } else { - write!(f, "id {}", self.0.id()) - } - } -} - -impl<'a, A: Asset> fmt::Debug for HandleDisplay<'a, A> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(path) = self.0.path() { - write!(f, "path {path:?}") - } else { - write!(f, "id {:?}", self.0.id()) - } - } -} - -/// Make a type display-able. -pub trait DisplayProxy { - /// The type that does the displaying. - type D<'a>: fmt::Display + fmt::Debug - where - Self: 'a; - /// Return a display-able reference. - fn display<'a>(&'a self) -> Self::D<'a>; -} - -impl DisplayProxy for Handle { - type D<'a> = HandleDisplay<'a, A>; - - fn display<'a>(&'a self) -> Self::D<'a> { - HandleDisplay(self) - } -} - #[derive(Component, Reflect, Clone, Default, Debug)] #[reflect(Component)] #[component(on_remove=Self::on_remove, on_add=Self::on_add)] diff --git a/crates/bevy_mod_scripting_core/src/script/script_context.rs b/crates/bevy_mod_scripting_core/src/script/script_context.rs index 3a6e0d8faf..fa622a077a 100644 --- a/crates/bevy_mod_scripting_core/src/script/script_context.rs +++ b/crates/bevy_mod_scripting_core/src/script/script_context.rs @@ -1,5 +1,6 @@ use std::{hash::Hash, sync::Arc}; +use bevy_mod_scripting_script::ScriptAttachment; use parking_lot::{Mutex, RwLock}; use super::*; diff --git a/crates/bevy_mod_scripting_core/src/script_system.rs b/crates/bevy_mod_scripting_core/src/script_system.rs index 313ba43847..9ad6b54641 100644 --- a/crates/bevy_mod_scripting_core/src/script_system.rs +++ b/crates/bevy_mod_scripting_core/src/script_system.rs @@ -1,10 +1,8 @@ //! everything to do with dynamically added script systems use crate::{ - IntoScriptPluginParams, - event::CallbackLabel, - extractors::{CallContext, get_all_access_ids}, - script::{ScriptAttachment, ScriptContext}, + IntoScriptPluginParams, callbacks::ScriptCallbacks, event::CallbackLabel, + extractors::get_all_access_ids, handler::ScriptingHandler, script::ScriptContext, }; use ::{ @@ -33,6 +31,7 @@ use bevy_mod_scripting_bindings::{ ScriptQueryBuilder, ScriptQueryResult, ScriptResourceRegistration, Val, WorldAccessGuard, WorldGuard, }; +use bevy_mod_scripting_script::ScriptAttachment; use bevy_system_reflection::{ReflectSchedule, ReflectSystem}; use std::{ any::TypeId, borrow::Cow, collections::HashSet, hash::Hash, marker::PhantomData, ops::Deref, @@ -203,6 +202,7 @@ struct ScriptSystemState { callback_label: CallbackLabel, system_params: Vec, script_contexts: ScriptContext

, + script_callbacks: ScriptCallbacks

, } /// Equivalent of [`SystemParam`] but for dynamic systems, these are the kinds of things @@ -387,10 +387,12 @@ impl System for DynamicScriptSystem

{ if let Some(context) = script_context.get_context(&self.target_attachment) { let mut context = context.lock(); - let result = context.call_context_dynamic( + let result = P::handle( + payload, &self.target_attachment, &state.callback_label, - payload, + &mut context, + state.script_callbacks.clone(), guard.clone(), ); drop(context); @@ -493,6 +495,7 @@ impl System for DynamicScriptSystem

{ callback_label: self.name.to_string().into(), system_params, script_contexts: world.get_resource_or_init::>().clone(), + script_callbacks: world.get_resource_or_init::>().clone(), }) } diff --git a/crates/bevy_mod_scripting_display/src/handle.rs b/crates/bevy_mod_scripting_display/src/handle.rs new file mode 100644 index 0000000000..87fd1add50 --- /dev/null +++ b/crates/bevy_mod_scripting_display/src/handle.rs @@ -0,0 +1,44 @@ +use bevy_asset::{Asset, Handle}; +use std::fmt; + +/// Display the path of a script or its asset ID. +#[doc(hidden)] +pub struct HandleDisplay<'a, T: Asset>(&'a Handle); + +impl<'a, A: Asset> fmt::Display for HandleDisplay<'a, A> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(path) = self.0.path() { + write!(f, "path {path}") + } else { + write!(f, "id {}", self.0.id()) + } + } +} + +impl<'a, A: Asset> fmt::Debug for HandleDisplay<'a, A> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(path) = self.0.path() { + write!(f, "path {path:?}") + } else { + write!(f, "id {:?}", self.0.id()) + } + } +} + +/// Make a type display-able. +pub trait DisplayProxy { + /// The type that does the displaying. + type D<'a>: fmt::Display + fmt::Debug + where + Self: 'a; + /// Return a display-able reference. + fn display<'a>(&'a self) -> Self::D<'a>; +} + +impl DisplayProxy for Handle { + type D<'a> = HandleDisplay<'a, A>; + + fn display<'a>(&'a self) -> Self::D<'a> { + HandleDisplay(self) + } +} diff --git a/crates/bevy_mod_scripting_display/src/lib.rs b/crates/bevy_mod_scripting_display/src/lib.rs index 95e72b92ec..7a873dfa59 100644 --- a/crates/bevy_mod_scripting_display/src/lib.rs +++ b/crates/bevy_mod_scripting_display/src/lib.rs @@ -4,9 +4,10 @@ use std::{ any::{Any, TypeId}, ops::Deref, }; +mod handle; mod impls; mod printer; -pub use printer::*; +pub use {handle::*, printer::*}; use bevy_ecs::{ component::{ComponentId, ComponentInfo}, diff --git a/crates/bevy_mod_scripting_functions/Cargo.toml b/crates/bevy_mod_scripting_functions/Cargo.toml index 6127883653..fb2c1a13b7 100644 --- a/crates/bevy_mod_scripting_functions/Cargo.toml +++ b/crates/bevy_mod_scripting_functions/Cargo.toml @@ -46,6 +46,7 @@ bevy_mod_scripting_core = { workspace = true } bevy_mod_scripting_bindings = { workspace = true } bevy_mod_scripting_display = { workspace = true } bevy_mod_scripting_asset = { workspace = true } +bevy_mod_scripting_script = { workspace = true } bevy_mod_scripting_derive = { workspace = true } bevy_mod_scripting_lua = { path = "../languages/bevy_mod_scripting_lua", optional = true, version = "0.16.0" } bevy_mod_scripting_rhai = { path = "../languages/bevy_mod_scripting_rhai", optional = true, version = "0.16.0" } diff --git a/crates/bevy_mod_scripting_functions/src/core.rs b/crates/bevy_mod_scripting_functions/src/core.rs index c3ffd8584a..7c5c3227e2 100644 --- a/crates/bevy_mod_scripting_functions/src/core.rs +++ b/crates/bevy_mod_scripting_functions/src/core.rs @@ -1,6 +1,7 @@ //! Contains functions defined by the [`bevy_mod_scripting_core`] crate use bevy_mod_scripting_asset::ScriptAsset; +use bevy_mod_scripting_script::ScriptAttachment; use bevy_platform::collections::HashMap; use std::ops::Deref; @@ -11,7 +12,6 @@ use bevy_mod_scripting_bindings::{ DynamicScriptFunctionMut, FunctionInfo, GlobalNamespace, InteropError, PartialReflectExt, ReflectReference, ScriptComponentRegistration, ScriptQueryBuilder, ScriptQueryResult, ScriptResourceRegistration, ScriptTypeRegistration, ThreadWorldContainer, Union, - WorldContainer, function::{ from::{Ref, Val}, from_ref::FromScriptRef, @@ -20,10 +20,7 @@ use bevy_mod_scripting_bindings::{ }, script_value::ScriptValue, }; -use bevy_mod_scripting_core::{ - script::ScriptAttachment, - script_system::{ManageScriptSystems, ScriptSystemBuilder}, -}; +use bevy_mod_scripting_core::script_system::{ManageScriptSystems, ScriptSystemBuilder}; use bevy_mod_scripting_derive::script_bindings; use bevy_mod_scripting_display::{OrFakeId, WithTypeInfo}; use bevy_reflect::PartialReflect; @@ -832,7 +829,7 @@ impl ReflectReference { let iter_function = move || { // world is not thread safe, we can't capture it in the closure // or it will also be non-thread safe - let world = ThreadWorldContainer.try_get_world()?; + let world = ThreadWorldContainer.try_get_context()?.world; if len == 0 { return Ok(ScriptValue::Unit); } diff --git a/crates/bevy_mod_scripting_script/Cargo.toml b/crates/bevy_mod_scripting_script/Cargo.toml new file mode 100644 index 0000000000..f3a6599e37 --- /dev/null +++ b/crates/bevy_mod_scripting_script/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "bevy_mod_scripting_script" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +keywords.workspace = true +categories.workspace = true +readme.workspace = true + +[dependencies] +bevy_mod_scripting_asset = { workspace = true } +bevy_mod_scripting_display = { workspace = true } +bevy_ecs = { workspace = true } +bevy_reflect = { workspace = true } +bevy_asset = { workspace = true } + +[lints] +workspace = true diff --git a/crates/bevy_mod_scripting_script/src/lib.rs b/crates/bevy_mod_scripting_script/src/lib.rs new file mode 100644 index 0000000000..ecb62e5d76 --- /dev/null +++ b/crates/bevy_mod_scripting_script/src/lib.rs @@ -0,0 +1,83 @@ +//! Contains definitions for script related structures + +use bevy_asset::Handle; +use bevy_ecs::entity::Entity; +use bevy_mod_scripting_asset::ScriptAsset; +use bevy_mod_scripting_display::DisplayProxy; +use bevy_reflect::Reflect; +use std::fmt; + +/// Specifies a unique attachment of a script. These attachments are mapped to [`ContextKey`]'s depending on the context policy used. +#[derive(Debug, Hash, Clone, PartialEq, Eq, Reflect)] +pub enum ScriptAttachment { + /// a script attached to an entity, with an optional domain. By default selecting a domain will put the context of this script on a per-domain basis. + EntityScript(Entity, Handle), + /// a static script, with an optional domain. By default selecting a domain will put the context of this script on a per-domain basis. + StaticScript(Handle), +} + +impl std::fmt::Display for ScriptAttachment { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ScriptAttachment::EntityScript(entity, script) => { + write!( + f, + "EntityScript(entity: {}, script: {})", + entity, + script.display(), + ) + } + ScriptAttachment::StaticScript(script) => { + write!(f, "StaticScript(script: {})", script.display()) + } + } + } +} + +impl ScriptAttachment { + /// Returns the script handle. + pub fn script(&self) -> Handle { + match self { + ScriptAttachment::EntityScript(_, script) => script.clone(), + ScriptAttachment::StaticScript(script) => script.clone(), + } + } + + /// Returns a mutable reference to the underlying script handle. + pub fn script_mut(&mut self) -> &mut Handle { + match self { + ScriptAttachment::EntityScript(_, script) => script, + ScriptAttachment::StaticScript(script) => script, + } + } + + /// Returns the entity if it exists. + pub fn entity(&self) -> Option { + match self { + ScriptAttachment::EntityScript(entity, _) => Some(*entity), + ScriptAttachment::StaticScript(_) => None, + } + } + + /// Downcasts any script handles into weak handles. + pub fn into_weak(self) -> Self { + match self { + ScriptAttachment::EntityScript(entity, script) => { + ScriptAttachment::EntityScript(entity, script.clone_weak()) + } + ScriptAttachment::StaticScript(script) => { + ScriptAttachment::StaticScript(script.clone_weak()) + } + } + } + + /// Returns true if the attachment is a static script. + pub fn is_static(&self) -> bool { + matches!(self, ScriptAttachment::StaticScript(_)) + } + + /// Returns true if the attachment is an entity script. + pub fn is_entity_script(&self) -> bool { + matches!(self, ScriptAttachment::EntityScript(_, _)) + } +} diff --git a/crates/languages/bevy_mod_scripting_lua/Cargo.toml b/crates/languages/bevy_mod_scripting_lua/Cargo.toml index e9801676b2..7740b36ac4 100644 --- a/crates/languages/bevy_mod_scripting_lua/Cargo.toml +++ b/crates/languages/bevy_mod_scripting_lua/Cargo.toml @@ -46,6 +46,7 @@ bevy_mod_scripting_core = { workspace = true } bevy_mod_scripting_display = { workspace = true } bevy_mod_scripting_bindings = { workspace = true } bevy_mod_scripting_asset = { workspace = true } +bevy_mod_scripting_script = { workspace = true } mlua = { workspace = true, features = ["vendored", "send", "macros"] } profiling = { workspace = true } 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 23f1a85643..dc676b48ad 100644 --- a/crates/languages/bevy_mod_scripting_lua/src/bindings/reference.rs +++ b/crates/languages/bevy_mod_scripting_lua/src/bindings/reference.rs @@ -1,8 +1,7 @@ use std::any::TypeId; use bevy_mod_scripting_bindings::{ - ReflectReference, ThreadWorldContainer, WorldContainer, error::InteropError, - script_value::ScriptValue, + ReflectReference, ThreadWorldContainer, error::InteropError, script_value::ScriptValue, }; use bevy_mod_scripting_display::OrFakeId; use mlua::{ExternalError, MetaMethod, UserData, UserDataMethods}; @@ -41,8 +40,9 @@ impl UserData for LuaReflectReference { |_, (self_, key): (LuaReflectReference, LuaScriptValue)| { profiling::function_scope!("MetaMethod::Index"); let world = ThreadWorldContainer - .try_get_world() - .map_err(IntoMluaError::to_lua_error)?; + .try_get_context() + .map_err(IntoMluaError::to_lua_error)? + .world; let self_: ReflectReference = self_.into(); let type_id = self_ .tail_type_id(world.clone()) @@ -80,8 +80,9 @@ impl UserData for LuaReflectReference { |_, (self_, key, value): (LuaReflectReference, LuaScriptValue, LuaScriptValue)| { profiling::function_scope!("MetaMethod::NewIndex"); let world = ThreadWorldContainer - .try_get_world() - .map_err(IntoMluaError::to_lua_error)?; + .try_get_context() + .map_err(IntoMluaError::to_lua_error)? + .world; let self_: ReflectReference = self_.into(); let key: ScriptValue = key.into(); let value: ScriptValue = value.into(); @@ -103,8 +104,9 @@ impl UserData for LuaReflectReference { |_, (self_, other): (LuaReflectReference, LuaScriptValue)| { profiling::function_scope!("MetaMethod::Sub"); let world = ThreadWorldContainer - .try_get_world() - .map_err(IntoMluaError::to_lua_error)?; + .try_get_context() + .map_err(IntoMluaError::to_lua_error)? + .world; let self_: ReflectReference = self_.into(); let other: ScriptValue = other.into(); let target_type_id = self_ @@ -124,8 +126,9 @@ impl UserData for LuaReflectReference { |_, (self_, other): (LuaReflectReference, LuaScriptValue)| { profiling::function_scope!("MetaMethod::Add"); let world = ThreadWorldContainer - .try_get_world() - .map_err(IntoMluaError::to_lua_error)?; + .try_get_context() + .map_err(IntoMluaError::to_lua_error)? + .world; let self_: ReflectReference = self_.into(); let other: ScriptValue = other.into(); let target_type_id = self_ @@ -145,8 +148,9 @@ impl UserData for LuaReflectReference { |_, (self_, other): (LuaReflectReference, LuaScriptValue)| { profiling::function_scope!("MetaMethod::Mul"); let world = ThreadWorldContainer - .try_get_world() - .map_err(IntoMluaError::to_lua_error)?; + .try_get_context() + .map_err(IntoMluaError::to_lua_error)? + .world; let self_: ReflectReference = self_.into(); let other: ScriptValue = other.into(); let target_type_id = self_ @@ -166,8 +170,9 @@ impl UserData for LuaReflectReference { |_, (self_, other): (LuaReflectReference, LuaScriptValue)| { profiling::function_scope!("MetaMethod::Div"); let world = ThreadWorldContainer - .try_get_world() - .map_err(IntoMluaError::to_lua_error)?; + .try_get_context() + .map_err(IntoMluaError::to_lua_error)? + .world; let self_: ReflectReference = self_.into(); let other: ScriptValue = other.into(); let target_type_id = self_ @@ -187,8 +192,9 @@ impl UserData for LuaReflectReference { |_, (self_, other): (LuaReflectReference, LuaScriptValue)| { profiling::function_scope!("MetaMethod::Mod"); let world = ThreadWorldContainer - .try_get_world() - .map_err(IntoMluaError::to_lua_error)?; + .try_get_context() + .map_err(IntoMluaError::to_lua_error)? + .world; let self_: ReflectReference = self_.into(); let other: ScriptValue = other.into(); let target_type_id = self_ @@ -206,8 +212,9 @@ impl UserData for LuaReflectReference { m.add_meta_function(MetaMethod::Unm, |_, self_: LuaReflectReference| { profiling::function_scope!("MetaMethod::Unm"); let world = ThreadWorldContainer - .try_get_world() - .map_err(IntoMluaError::to_lua_error)?; + .try_get_context() + .map_err(IntoMluaError::to_lua_error)? + .world; let self_: ReflectReference = self_.into(); let target_type_id = self_ .tail_type_id(world.clone()) @@ -225,8 +232,9 @@ impl UserData for LuaReflectReference { |_, (self_, other): (LuaReflectReference, LuaScriptValue)| { profiling::function_scope!("MetaMethod::Pow"); let world = ThreadWorldContainer - .try_get_world() - .map_err(IntoMluaError::to_lua_error)?; + .try_get_context() + .map_err(IntoMluaError::to_lua_error)? + .world; let self_: ReflectReference = self_.into(); let other: ScriptValue = other.into(); let target_type_id = self_ @@ -246,8 +254,9 @@ impl UserData for LuaReflectReference { |_, (self_, other): (LuaReflectReference, LuaScriptValue)| { profiling::function_scope!("MetaMethod::Eq"); let world = ThreadWorldContainer - .try_get_world() - .map_err(IntoMluaError::to_lua_error)?; + .try_get_context() + .map_err(IntoMluaError::to_lua_error)? + .world; let self_: ReflectReference = self_.into(); let other: ScriptValue = other.into(); let target_type_id = self_ @@ -267,8 +276,9 @@ impl UserData for LuaReflectReference { |_, (self_, other): (LuaReflectReference, LuaScriptValue)| { profiling::function_scope!("MetaMethod::Lt"); let world = ThreadWorldContainer - .try_get_world() - .map_err(IntoMluaError::to_lua_error)?; + .try_get_context() + .map_err(IntoMluaError::to_lua_error)? + .world; let self_: ReflectReference = self_.into(); let other: ScriptValue = other.into(); let target_type_id = self_ @@ -286,8 +296,9 @@ impl UserData for LuaReflectReference { m.add_meta_function(MetaMethod::Len, |_lua, self_: LuaScriptValue| { profiling::function_scope!("MetaMethod::Len"); let world = ThreadWorldContainer - .try_get_world() - .map_err(IntoMluaError::to_lua_error)?; + .try_get_context() + .map_err(IntoMluaError::to_lua_error)? + .world; let script_value: ScriptValue = self_.into(); Ok(match script_value { ScriptValue::Reference(r) => r.len(world).map_err(IntoMluaError::to_lua_error)?, @@ -307,8 +318,9 @@ impl UserData for LuaReflectReference { // let mut iter_func = lookup_dynamic_function_typed::(l, "iter") // .expect("No iter function registered"); let world = ThreadWorldContainer - .try_get_world() - .map_err(IntoMluaError::to_lua_error)?; + .try_get_context() + .map_err(IntoMluaError::to_lua_error)? + .world; let iter_func = world .lookup_function([TypeId::of::()], "iter") @@ -327,8 +339,9 @@ impl UserData for LuaReflectReference { m.add_meta_function(MetaMethod::ToString, |_, self_: LuaReflectReference| { profiling::function_scope!("MetaMethod::ToString"); let world = ThreadWorldContainer - .try_get_world() - .map_err(IntoMluaError::to_lua_error)?; + .try_get_context() + .map_err(IntoMluaError::to_lua_error)? + .world; let reflect_reference: ReflectReference = self_.into(); let func = world @@ -362,8 +375,9 @@ impl UserData for LuaStaticReflectReference { |_, (self_, key): (LuaStaticReflectReference, LuaScriptValue)| { profiling::function_scope!("MetaMethod::Index"); let world = ThreadWorldContainer - .try_get_world() - .map_err(IntoMluaError::to_lua_error)?; + .try_get_context() + .map_err(IntoMluaError::to_lua_error)? + .world; let type_id = self_.0; let key: ScriptValue = key.into(); diff --git a/crates/languages/bevy_mod_scripting_lua/src/lib.rs b/crates/languages/bevy_mod_scripting_lua/src/lib.rs index f60c605684..39d771f9f4 100644 --- a/crates/languages/bevy_mod_scripting_lua/src/lib.rs +++ b/crates/languages/bevy_mod_scripting_lua/src/lib.rs @@ -1,5 +1,8 @@ //! Lua integration for the bevy_mod_scripting system. -use std::ops::{Deref, DerefMut}; +use std::{ + ops::{Deref, DerefMut}, + sync::Arc, +}; use ::{ bevy_app::Plugin, @@ -7,27 +10,28 @@ use ::{ bevy_ecs::{entity::Entity, world::World}, }; use bevy_app::App; -use bevy_ecs::world::WorldId; +use bevy_ecs::world::{Mut, WorldId}; use bevy_log::trace; use bevy_mod_scripting_asset::{Language, ScriptAsset}; use bevy_mod_scripting_bindings::{ - InteropError, PartialReflectExt, ThreadWorldContainer, WorldContainer, - function::namespace::Namespace, globals::AppScriptGlobalsRegistry, script_value::ScriptValue, + InteropError, PartialReflectExt, ThreadWorldContainer, function::namespace::Namespace, + globals::AppScriptGlobalsRegistry, script_value::ScriptValue, }; use bevy_mod_scripting_core::{ IntoScriptPluginParams, ScriptingPlugin, + callbacks::ScriptCallbacks, config::{GetPluginThreadConfig, ScriptingPluginConfiguration}, event::CallbackLabel, - extractors::GetPluginFor, make_plugin_config_static, - script::{ContextPolicy, ScriptAttachment}, + script::ContextPolicy, }; +use bevy_mod_scripting_script::ScriptAttachment; use bindings::{ reference::{LuaReflectReference, LuaStaticReflectReference}, script_value::LuaScriptValue, }; pub use mlua; -use mlua::{Function, IntoLua, Lua, MultiValue}; +use mlua::{Function, IntoLua, Lua, MultiValue, Variadic}; /// Bindings for lua. pub mod bindings; @@ -52,10 +56,6 @@ impl DerefMut for LuaContext { } } -impl GetPluginFor for LuaContext { - type P = LuaScriptingPlugin; -} - impl IntoScriptPluginParams for LuaScriptingPlugin { type C = LuaContext; type R = (); @@ -89,6 +89,51 @@ pub struct LuaScriptingPlugin { pub scripting_plugin: ScriptingPlugin, } +fn register_plugin_globals(lua: &mut Lua) -> Result<(), mlua::Error> { + lua.globals().set( + "register_callback", + lua.create_function(|_lua: &Lua, (callback, func): (String, Function)| { + let thread_ctxt = ThreadWorldContainer + .try_get_context() + .map_err(mlua::Error::external)?; + let world = thread_ctxt.world; + let attachment = thread_ctxt.attachment; + world + .with_resource_mut(|res: Mut>| { + let mut callbacks = res.callbacks.write(); + callbacks.insert( + (attachment.clone(), callback), + Arc::new( + move |args: Vec, + lua: &mut LuaContext, + world_id: WorldId| { + let pre_handling_callbacks = + LuaScriptingPlugin::readonly_configuration(world_id) + .pre_handling_callbacks; + + pre_handling_callbacks + .iter() + .try_for_each(|init| init(&attachment, lua))?; + + let args = args + .into_iter() + .map(LuaScriptValue) + .collect::>(); + + func.call::(args) + .map_err(IntoInteropError::to_bms_error) + .map(ScriptValue::from) + }, + ), + ) + }) + .map_err(mlua::Error::external)?; + Ok(()) + })?, + )?; + Ok(()) +} + impl Default for LuaScriptingPlugin { fn default() -> Self { LuaScriptingPlugin { @@ -98,19 +143,22 @@ impl Default for LuaScriptingPlugin { context_initializers: vec![ |_script_id, context| { // set the world global - context - .globals() + let globals = context.globals(); + + globals .set( "world", LuaStaticReflectReference(std::any::TypeId::of::()), ) .map_err(IntoInteropError::to_bms_error)?; + register_plugin_globals(context).map_err(IntoInteropError::to_bms_error)?; + Ok(()) }, |_script_id, context| { // set static globals - let world = ThreadWorldContainer.try_get_world()?; + let world = ThreadWorldContainer.try_get_context()?.world; let globals_registry = world.with_resource(|r: &AppScriptGlobalsRegistry| r.clone())?; let globals_registry = globals_registry.read(); @@ -157,7 +205,7 @@ impl Default for LuaScriptingPlugin { ], context_pre_handling_initializers: vec![|context_key, context| { // TODO: convert these to functions - let world = ThreadWorldContainer.try_get_world()?; + let world = ThreadWorldContainer.try_get_context()?.world; if let Some(entity) = context_key.entity() { context .globals() diff --git a/crates/languages/bevy_mod_scripting_rhai/Cargo.toml b/crates/languages/bevy_mod_scripting_rhai/Cargo.toml index 5b34fbac5a..d791c59c95 100644 --- a/crates/languages/bevy_mod_scripting_rhai/Cargo.toml +++ b/crates/languages/bevy_mod_scripting_rhai/Cargo.toml @@ -26,6 +26,7 @@ bevy_mod_scripting_core = { workspace = true } bevy_mod_scripting_display = { workspace = true } bevy_mod_scripting_bindings = { workspace = true } bevy_mod_scripting_asset = { workspace = true } +bevy_mod_scripting_script = { workspace = true } strum = { workspace = true, features = ["derive"] } parking_lot = { workspace = true } diff --git a/crates/languages/bevy_mod_scripting_rhai/src/bindings/reference.rs b/crates/languages/bevy_mod_scripting_rhai/src/bindings/reference.rs index a5799c3427..ecf80ce8f4 100644 --- a/crates/languages/bevy_mod_scripting_rhai/src/bindings/reference.rs +++ b/crates/languages/bevy_mod_scripting_rhai/src/bindings/reference.rs @@ -5,7 +5,7 @@ use std::{ use crate::IntoRhaiError; use bevy_mod_scripting_bindings::{ - ReflectReference, ScriptValue, ThreadWorldContainer, WorldContainer, error::InteropError, + ReflectReference, ScriptValue, ThreadWorldContainer, error::InteropError, function::script_function::DynamicScriptFunctionMut, }; use bevy_mod_scripting_display::OrFakeId; @@ -256,7 +256,7 @@ impl IntoIterator for RhaiReflectReference { fn into_iter(self) -> Self::IntoIter { let result = (|| { - let world = ThreadWorldContainer.try_get_world()?; + let world = ThreadWorldContainer.try_get_context()?.world; let iter_func = world .lookup_function([TypeId::of::()], "iter") @@ -295,8 +295,9 @@ impl CustomType for RhaiReflectReference { .with_name(std::any::type_name::()) .with_indexer_get(|self_: &mut Self, index: Dynamic| { let world = ThreadWorldContainer - .try_get_world() - .map_err(IntoRhaiError::into_rhai_error)?; + .try_get_context() + .map_err(IntoRhaiError::into_rhai_error)? + .world; let self_ = &self_.0; let type_id = self_ .tail_type_id(world.clone()) @@ -332,8 +333,9 @@ impl CustomType for RhaiReflectReference { }) .with_indexer_set(|self_: &mut Self, index: Dynamic, value: Dynamic| { let world = ThreadWorldContainer - .try_get_world() - .map_err(IntoRhaiError::into_rhai_error)?; + .try_get_context() + .map_err(IntoRhaiError::into_rhai_error)? + .world; let self_ = self_.0.clone(); let key = ScriptValue::from_dynamic(index)?; let value = ScriptValue::from_dynamic(value)?; @@ -351,8 +353,9 @@ impl CustomType for RhaiReflectReference { RhaiOperator::Sub.function_name(), |self_: Self, other: Dynamic| { let world = ThreadWorldContainer - .try_get_world() - .map_err(IntoRhaiError::into_rhai_error)?; + .try_get_context() + .map_err(IntoRhaiError::into_rhai_error)? + .world; let self_: ReflectReference = self_.0.clone(); let other: ScriptValue = ScriptValue::from_dynamic(other)?; let target_type_id = self_ @@ -370,8 +373,9 @@ impl CustomType for RhaiReflectReference { RhaiOperator::Add.function_name(), |self_: Self, other: Dynamic| { let world = ThreadWorldContainer - .try_get_world() - .map_err(IntoRhaiError::into_rhai_error)?; + .try_get_context() + .map_err(IntoRhaiError::into_rhai_error)? + .world; let self_: ReflectReference = self_.0.clone(); let other: ScriptValue = ScriptValue::from_dynamic(other)?; let target_type_id = self_ @@ -389,8 +393,9 @@ impl CustomType for RhaiReflectReference { RhaiOperator::Mul.function_name(), |self_: Self, other: Dynamic| { let world = ThreadWorldContainer - .try_get_world() - .map_err(IntoRhaiError::into_rhai_error)?; + .try_get_context() + .map_err(IntoRhaiError::into_rhai_error)? + .world; let self_: ReflectReference = self_.0.clone(); let other: ScriptValue = ScriptValue::from_dynamic(other)?; let target_type_id = self_ @@ -408,8 +413,9 @@ impl CustomType for RhaiReflectReference { RhaiOperator::Div.function_name(), |self_: Self, other: Dynamic| { let world = ThreadWorldContainer - .try_get_world() - .map_err(IntoRhaiError::into_rhai_error)?; + .try_get_context() + .map_err(IntoRhaiError::into_rhai_error)? + .world; let self_: ReflectReference = self_.0.clone(); let other: ScriptValue = ScriptValue::from_dynamic(other)?; let target_type_id = self_ @@ -427,8 +433,9 @@ impl CustomType for RhaiReflectReference { RhaiOperator::Mod.function_name(), |self_: Self, other: Dynamic| { let world = ThreadWorldContainer - .try_get_world() - .map_err(IntoRhaiError::into_rhai_error)?; + .try_get_context() + .map_err(IntoRhaiError::into_rhai_error)? + .world; let self_: ReflectReference = self_.0.clone(); let other: ScriptValue = ScriptValue::from_dynamic(other)?; let target_type_id = self_ @@ -444,8 +451,9 @@ impl CustomType for RhaiReflectReference { ) .with_fn(RhaiOperator::Unm.function_name(), |self_: Self| { let world = ThreadWorldContainer - .try_get_world() - .map_err(IntoRhaiError::into_rhai_error)?; + .try_get_context() + .map_err(IntoRhaiError::into_rhai_error)? + .world; let self_: ReflectReference = self_.0.clone(); let target_type_id = self_ .tail_type_id(world.clone()) @@ -461,8 +469,9 @@ impl CustomType for RhaiReflectReference { RhaiOperator::Pow.function_name(), |self_: Self, other: Dynamic| { let world = ThreadWorldContainer - .try_get_world() - .map_err(IntoRhaiError::into_rhai_error)?; + .try_get_context() + .map_err(IntoRhaiError::into_rhai_error)? + .world; let self_: ReflectReference = self_.0.clone(); let other: ScriptValue = ScriptValue::from_dynamic(other)?; let target_type_id = self_ @@ -480,8 +489,9 @@ impl CustomType for RhaiReflectReference { RhaiOperator::Eq.function_name(), |self_: Self, other: Dynamic| { let world = ThreadWorldContainer - .try_get_world() - .map_err(IntoRhaiError::into_rhai_error)?; + .try_get_context() + .map_err(IntoRhaiError::into_rhai_error)? + .world; let self_: ReflectReference = self_.0.clone(); let other: ScriptValue = ScriptValue::from_dynamic(other)?; let target_type_id = self_ @@ -499,8 +509,9 @@ impl CustomType for RhaiReflectReference { RhaiOperator::Ne.function_name(), |self_: Self, other: Dynamic| { let world = ThreadWorldContainer - .try_get_world() - .map_err(IntoRhaiError::into_rhai_error)?; + .try_get_context() + .map_err(IntoRhaiError::into_rhai_error)? + .world; let self_: ReflectReference = self_.0.clone(); let other: ScriptValue = ScriptValue::from_dynamic(other)?; let target_type_id = self_ @@ -524,8 +535,9 @@ impl CustomType for RhaiReflectReference { RhaiOperator::Lt.function_name(), |self_: Self, other: Dynamic| { let world = ThreadWorldContainer - .try_get_world() - .map_err(IntoRhaiError::into_rhai_error)?; + .try_get_context() + .map_err(IntoRhaiError::into_rhai_error)? + .world; let self_: ReflectReference = self_.0.clone(); let other: ScriptValue = ScriptValue::from_dynamic(other)?; let target_type_id = self_ @@ -541,7 +553,7 @@ impl CustomType for RhaiReflectReference { ) .on_debug(|self_| { let result: Result<_, InteropError> = (|| { - let world = ThreadWorldContainer.try_get_world()?; + let world = ThreadWorldContainer.try_get_context()?.world; let reflect_reference = self_.0.clone(); let func = world @@ -571,7 +583,7 @@ impl CustomType for RhaiReflectReference { }) .on_print(|self_| { let result: Result<_, InteropError> = (|| { - let world = ThreadWorldContainer.try_get_world()?; + let world = ThreadWorldContainer.try_get_context()?.world; let reflect_reference = self_.0.clone(); let func = world @@ -612,8 +624,9 @@ impl CustomType for RhaiStaticReflectReference { .with_name(std::any::type_name::()) .with_indexer_get(|self_: &mut Self, index: Dynamic| { let world = ThreadWorldContainer - .try_get_world() - .map_err(IntoRhaiError::into_rhai_error)?; + .try_get_context() + .map_err(IntoRhaiError::into_rhai_error)? + .world; let type_id = self_.0; let key: ScriptValue = ScriptValue::from_dynamic(index)?; diff --git a/crates/languages/bevy_mod_scripting_rhai/src/lib.rs b/crates/languages/bevy_mod_scripting_rhai/src/lib.rs index e6de10e1ff..0db90e9532 100644 --- a/crates/languages/bevy_mod_scripting_rhai/src/lib.rs +++ b/crates/languages/bevy_mod_scripting_rhai/src/lib.rs @@ -15,16 +15,17 @@ use bevy_log::trace; use bevy_mod_scripting_asset::{Language, ScriptAsset}; use bevy_mod_scripting_bindings::{ AppScriptGlobalsRegistry, InteropError, Namespace, PartialReflectExt, ScriptValue, - ThreadWorldContainer, WorldContainer, + ThreadWorldContainer, }; use bevy_mod_scripting_core::{ IntoScriptPluginParams, ScriptingPlugin, config::{GetPluginThreadConfig, ScriptingPluginConfiguration}, event::CallbackLabel, - extractors::GetPluginFor, make_plugin_config_static, - script::{ContextPolicy, DisplayProxy, ScriptAttachment}, + script::ContextPolicy, }; +use bevy_mod_scripting_display::DisplayProxy; +use bevy_mod_scripting_script::ScriptAttachment; use bindings::reference::{ReservedKeyword, RhaiReflectReference, RhaiStaticReflectReference}; use parking_lot::RwLock; pub use rhai; @@ -44,10 +45,6 @@ pub struct RhaiScriptContext { pub scope: Scope<'static>, } -impl GetPluginFor for RhaiScriptContext { - type P = RhaiScriptingPlugin; -} - make_plugin_config_static!(RhaiScriptingPlugin); impl IntoScriptPluginParams for RhaiScriptingPlugin { @@ -155,7 +152,7 @@ impl Default for RhaiScriptingPlugin { }, |_, context| { // initialize global functions - let world = ThreadWorldContainer.try_get_world()?; + let world = ThreadWorldContainer.try_get_context()?.world; let globals_registry = world.with_resource(|r: &AppScriptGlobalsRegistry| r.clone())?; let globals_registry = globals_registry.read(); @@ -216,7 +213,7 @@ impl Default for RhaiScriptingPlugin { }, ], context_pre_handling_initializers: vec![|context_key, context| { - let world = ThreadWorldContainer.try_get_world()?; + let world = ThreadWorldContainer.try_get_context()?.world; if let Some(entity) = context_key.entity() { context.scope.set_or_push( diff --git a/crates/testing_crates/script_integration_test_harness/Cargo.toml b/crates/testing_crates/script_integration_test_harness/Cargo.toml index 7a08a71516..2ccc2e43bc 100644 --- a/crates/testing_crates/script_integration_test_harness/Cargo.toml +++ b/crates/testing_crates/script_integration_test_harness/Cargo.toml @@ -26,6 +26,8 @@ bevy_mod_scripting_asset = { workspace = true } bevy_mod_scripting_functions = { workspace = true, features = [ "core_functions", ] } +bevy_mod_scripting_display = { workspace = true } +bevy_mod_scripting_script = { workspace = true } regex = { workspace = true } pretty_assertions = { workspace = true } bevy_mod_scripting_lua = { workspace = true, optional = true } diff --git a/crates/testing_crates/script_integration_test_harness/src/lib.rs b/crates/testing_crates/script_integration_test_harness/src/lib.rs index 90596f3ca7..03367008d5 100644 --- a/crates/testing_crates/script_integration_test_harness/src/lib.rs +++ b/crates/testing_crates/script_integration_test_harness/src/lib.rs @@ -23,17 +23,19 @@ use ::{ use bevy_asset::Assets; use bevy_mod_scripting_asset::ScriptAsset; use bevy_mod_scripting_bindings::{ - CoreScriptGlobalsPlugin, ReflectAccessId, ThreadWorldContainer, WorldAccessGuard, - WorldContainer, WorldGuard, + CoreScriptGlobalsPlugin, ReflectAccessId, ThreadScriptContext, ThreadWorldContainer, + WorldAccessGuard, WorldGuard, }; use bevy_mod_scripting_core::{ BMSScriptingInfrastructurePlugin, IntoScriptPluginParams, commands::AttachScript, error::ScriptError, pipeline::PipelineRun, - script::{DisplayProxy, ScriptAttachment, ScriptComponent, ScriptContext}, + script::{ScriptComponent, ScriptContext}, }; +use bevy_mod_scripting_display::DisplayProxy; use bevy_mod_scripting_functions::ScriptFunctionsPlugin; +use bevy_mod_scripting_script::ScriptAttachment; use criterion::{BatchSize, measurement::Measurement}; use rand::{Rng, SeedableRng}; use test_functions::{RNG, register_test_functions}; @@ -328,7 +330,10 @@ where let _ = WorldAccessGuard::with_existing_static_guard(guard, |guard| { // Ensure the world is available via ThreadWorldContainer ThreadWorldContainer - .set_world(guard.clone()) + .set_context(ThreadScriptContext { + world: guard.clone(), + attachment: ScriptAttachment::StaticScript(Handle::Weak(script_id)), + }) .map_err(|e| format!("{e:#?}"))?; // Pass the locked context to the closure for benchmarking its Lua (or generic) part bench_fn(&mut ctxt_locked, runtime, label, criterion) diff --git a/crates/testing_crates/script_integration_test_harness/src/parse.rs b/crates/testing_crates/script_integration_test_harness/src/parse.rs index 0e007d466c..0a4406f323 100644 --- a/crates/testing_crates/script_integration_test_harness/src/parse.rs +++ b/crates/testing_crates/script_integration_test_harness/src/parse.rs @@ -9,8 +9,9 @@ use bevy_mod_scripting_core::{ CallbackLabel, OnScriptLoaded, OnScriptReloaded, OnScriptUnloaded, Recipients, ScriptCallbackEvent, }, - script::{ContextPolicy, ScriptAttachment}, + script::ContextPolicy, }; +use bevy_mod_scripting_script::ScriptAttachment; use crate::scenario::{SCENARIO_SELF_LANGUAGE_NAME, ScenarioContext, ScenarioStep}; diff --git a/crates/testing_crates/script_integration_test_harness/src/scenario.rs b/crates/testing_crates/script_integration_test_harness/src/scenario.rs index 79976c2379..8700d6bcac 100644 --- a/crates/testing_crates/script_integration_test_harness/src/scenario.rs +++ b/crates/testing_crates/script_integration_test_harness/src/scenario.rs @@ -30,8 +30,9 @@ use bevy_mod_scripting_core::{ ScriptCallbackResponseEvent, ScriptDetachedEvent, }, handler::event_handler, - script::{ContextPolicy, ScriptAttachment, ScriptComponent, ScriptContext}, + script::{ContextPolicy, ScriptComponent, ScriptContext}, }; +use bevy_mod_scripting_script::ScriptAttachment; use test_utils::test_data::setup_integration_test; use crate::{install_test_plugin, parse::*}; diff --git a/crates/testing_crates/test_utils/src/test_plugin.rs b/crates/testing_crates/test_utils/src/test_plugin.rs index d7308a303c..f0c36ea096 100644 --- a/crates/testing_crates/test_utils/src/test_plugin.rs +++ b/crates/testing_crates/test_utils/src/test_plugin.rs @@ -20,10 +20,6 @@ macro_rules! make_test_plugin { $ident::make_plugin_config_static!(TestPlugin); - impl $ident::extractors::GetPluginFor for TestContext { - type P = TestPlugin; - } - impl $ident::IntoScriptPluginParams for TestPlugin { type C = TestContext; type R = TestRuntime; diff --git a/examples/game_of_life.rs b/examples/game_of_life.rs index 7589224ccd..14d82ef684 100644 --- a/examples/game_of_life.rs +++ b/examples/game_of_life.rs @@ -17,10 +17,6 @@ use bevy::{ use bevy_console::{AddConsoleCommand, ConsoleCommand, ConsoleOpen, ConsolePlugin, make_layer}; use bevy_mod_scripting::prelude::*; use bevy_mod_scripting_bindings::AllocatorDiagnosticPlugin; -use bevy_mod_scripting_core::{ - commands::{AttachScript, DetachScript}, - script::ScriptAttachment, -}; use clap::Parser; // CONSOLE SETUP diff --git a/src/lib.rs b/src/lib.rs index dd724a1282..51abdb5713 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,10 @@ pub mod asset { pub use bevy_mod_scripting_asset::*; } +pub mod script { + pub use bevy_mod_scripting_script::*; +} + pub mod prelude; #[cfg(feature = "lua")] diff --git a/src/prelude.rs b/src/prelude.rs index 697888ed01..c3c0e6d7b6 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -11,8 +11,12 @@ pub use bevy_mod_scripting_bindings::{ script_value::ScriptValue, }; +pub use bevy_mod_scripting_script::*; + pub use bevy_mod_scripting_asset::*; +pub use bevy_mod_scripting_core::commands::*; + #[cfg(feature = "lua")] pub use bevy_mod_scripting_lua::LuaScriptingPlugin; #[cfg(feature = "rhai")] From 4e6e162fb25cdc7443f255616f3b7462f78695a3 Mon Sep 17 00:00:00 2001 From: makspll Date: Sun, 12 Oct 2025 15:19:14 +0100 Subject: [PATCH 2/5] implement in rhai too --- .../{ => lua}/dynamic_on_test.lua | 0 .../register_callback/{ => lua}/empty.lua | 0 .../register_callback/{ => lua}/scenario.txt | 0 .../rhai/dynamic_on_test.rhai | 12 +++++ .../tests/register_callback/rhai/empty.rhai | 0 .../tests/register_callback/rhai/scenario.txt | 27 +++++++++++ .../bevy_mod_scripting_rhai/src/lib.rs | 47 +++++++++++++++++-- 7 files changed, 83 insertions(+), 3 deletions(-) rename assets/tests/register_callback/{ => lua}/dynamic_on_test.lua (100%) rename assets/tests/register_callback/{ => lua}/empty.lua (100%) rename assets/tests/register_callback/{ => lua}/scenario.txt (100%) create mode 100644 assets/tests/register_callback/rhai/dynamic_on_test.rhai create mode 100644 assets/tests/register_callback/rhai/empty.rhai create mode 100644 assets/tests/register_callback/rhai/scenario.txt diff --git a/assets/tests/register_callback/dynamic_on_test.lua b/assets/tests/register_callback/lua/dynamic_on_test.lua similarity index 100% rename from assets/tests/register_callback/dynamic_on_test.lua rename to assets/tests/register_callback/lua/dynamic_on_test.lua diff --git a/assets/tests/register_callback/empty.lua b/assets/tests/register_callback/lua/empty.lua similarity index 100% rename from assets/tests/register_callback/empty.lua rename to assets/tests/register_callback/lua/empty.lua diff --git a/assets/tests/register_callback/scenario.txt b/assets/tests/register_callback/lua/scenario.txt similarity index 100% rename from assets/tests/register_callback/scenario.txt rename to assets/tests/register_callback/lua/scenario.txt diff --git a/assets/tests/register_callback/rhai/dynamic_on_test.rhai b/assets/tests/register_callback/rhai/dynamic_on_test.rhai new file mode 100644 index 0000000000..9a2eac1479 --- /dev/null +++ b/assets/tests/register_callback/rhai/dynamic_on_test.rhai @@ -0,0 +1,12 @@ +fn on_script_loaded(){ + register_callback("on_test", dynamic_on_test); +} + +fn dynamic_on_test(){ + register_callback("on_test_last", dynamic_on_test_last); + return "on test: I am dynamically registered from a normal callback!"; +} + +fn dynamic_on_test_last(){ + return "on test last: I am dynamically registered from another dynamic callback!"; +} diff --git a/assets/tests/register_callback/rhai/empty.rhai b/assets/tests/register_callback/rhai/empty.rhai new file mode 100644 index 0000000000..e69de29bb2 diff --git a/assets/tests/register_callback/rhai/scenario.txt b/assets/tests/register_callback/rhai/scenario.txt new file mode 100644 index 0000000000..e20bf37566 --- /dev/null +++ b/assets/tests/register_callback/rhai/scenario.txt @@ -0,0 +1,27 @@ +// #main_script dynamic_on_test.rhai +SetCurrentLanguage language="@this_script_language" +InstallPlugin miliseconds_budget=999999 +SetupHandler OnTest=null, Update=null +SetupHandler OnTestPostUpdate=null, PostUpdate=null +SetupHandler Last=null, OnTestLast=null +FinalizeApp + +LoadScriptAs as_name="@this_script", path="@this_script" +WaitForScriptLoaded name="@this_script" +SpawnEntityWithScript name="test_entity", script="@this_script" +RunUpdateOnce +EmitScriptCallbackEvent emit_response=true, entity="test_entity", label="OnTest", language=null, recipients="EntityScript", script="@this_script" +EmitScriptCallbackEvent emit_response=true, entity="test_entity", label="OnTestLast", language=null, recipients="EntityScript", script="@this_script" +RunUpdateOnce +AssertCallbackSuccess attachment="EntityScript", entity="test_entity", label="OnTest", script="@this_script", expect_string_value="on test: I am dynamically registered from a normal callback!" +AssertCallbackSuccess attachment="EntityScript", entity="test_entity", label="OnTestLast", script="@this_script", expect_string_value="on test last: I am dynamically registered from another dynamic callback!" + +// reload, deleting old callbacks, expect stored callbacks to still work +ReloadScriptFrom script="@this_script", path="empty.rhai" +RunUpdateOnce +EmitScriptCallbackEvent emit_response=true, entity="test_entity", label="OnTest", language=null, recipients="EntityScript", script="@this_script" +EmitScriptCallbackEvent emit_response=true, entity="test_entity", label="OnTestLast", language=null, recipients="EntityScript", script="@this_script" +RunUpdateOnce +AssertCallbackSuccess attachment="EntityScript", entity="test_entity", label="OnTest", script="@this_script", expect_string_value="on test: I am dynamically registered from a normal callback!" +AssertCallbackSuccess attachment="EntityScript", entity="test_entity", label="OnTestLast", script="@this_script", expect_string_value="on test last: I am dynamically registered from another dynamic callback!" + diff --git a/crates/languages/bevy_mod_scripting_rhai/src/lib.rs b/crates/languages/bevy_mod_scripting_rhai/src/lib.rs index 0db90e9532..35dfe98fc1 100644 --- a/crates/languages/bevy_mod_scripting_rhai/src/lib.rs +++ b/crates/languages/bevy_mod_scripting_rhai/src/lib.rs @@ -1,6 +1,6 @@ //! Rhai scripting language support for Bevy. -use std::{ops::Deref, str::Utf8Error}; +use std::{ops::Deref, str::Utf8Error, sync::Arc}; use crate::bindings::script_value::{FromDynamic, IntoDynamic}; @@ -10,7 +10,7 @@ use ::{ bevy_ecs::{entity::Entity, world::World}, }; use bevy_app::App; -use bevy_ecs::world::WorldId; +use bevy_ecs::world::{Mut, WorldId}; use bevy_log::trace; use bevy_mod_scripting_asset::{Language, ScriptAsset}; use bevy_mod_scripting_bindings::{ @@ -19,6 +19,7 @@ use bevy_mod_scripting_bindings::{ }; use bevy_mod_scripting_core::{ IntoScriptPluginParams, ScriptingPlugin, + callbacks::ScriptCallbacks, config::{GetPluginThreadConfig, ScriptingPluginConfiguration}, event::CallbackLabel, make_plugin_config_static, @@ -30,7 +31,7 @@ use bindings::reference::{ReservedKeyword, RhaiReflectReference, RhaiStaticRefle use parking_lot::RwLock; pub use rhai; -use rhai::{AST, CallFnOptions, Dynamic, Engine, EvalAltResult, ParseError, Scope}; +use rhai::{AST, CallFnOptions, Dynamic, Engine, EvalAltResult, FnPtr, ParseError, Scope}; /// Bindings for rhai. pub mod bindings; @@ -129,6 +130,45 @@ impl AsMut> for RhaiScriptingPlugin { } } +fn register_plugin_globals(ctxt: &mut Engine) { + let register_callback_fn = |callback: String, func: FnPtr| { + let thread_ctxt = ThreadWorldContainer + .try_get_context() + .map_err(|e| Box::new(EvalAltResult::ErrorSystem("".to_string(), Box::new(e))))?; + let world = thread_ctxt.world; + let attachment = thread_ctxt.attachment; + world + .with_resource_mut(|res: Mut>| { + let mut callbacks = res.callbacks.write(); + callbacks.insert( + (attachment.clone(), callback), + Arc::new( + move |args: Vec, + rhai: &mut RhaiScriptContext, + world_id: WorldId| { + let config = RhaiScriptingPlugin::readonly_configuration(world_id); + let pre_handling_callbacks = config.pre_handling_callbacks; + let runtime = config.runtime; + let runtime_guard = runtime.read(); + pre_handling_callbacks + .iter() + .try_for_each(|init| init(&attachment, rhai))?; + + let ret = func + .call::(&runtime_guard, &rhai.ast, args) + .map_err(IntoInteropError::into_bms_error)?; + ScriptValue::from_dynamic(ret).map_err(IntoInteropError::into_bms_error) + }, + ), + ) + }) + .map_err(|e| Box::new(EvalAltResult::ErrorSystem("".to_string(), Box::new(e))))?; + Ok::<_, Box>(()) + }; + + ctxt.register_fn("register_callback", register_callback_fn); +} + impl Default for RhaiScriptingPlugin { fn default() -> Self { RhaiScriptingPlugin { @@ -140,6 +180,7 @@ impl Default for RhaiScriptingPlugin { engine.build_type::(); engine.build_type::(); engine.register_iterator_result::(); + register_plugin_globals(&mut engine); Ok(()) }], context_initializers: vec![ From 36c426d0616adc1b8d71b3eb74c68b5a11582f1a Mon Sep 17 00:00:00 2001 From: makspll Date: Sun, 12 Oct 2025 15:32:18 +0100 Subject: [PATCH 3/5] modify docs --- docs/src/SUMMARY.md | 1 + docs/src/ScriptingReference/core-callbacks.md | 4 ++- docs/src/Summary/callbacks.md | 36 +++++++++++++++++++ docs/src/Summary/contexts.md | 6 ++++ 4 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 docs/src/Summary/callbacks.md diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index bceac67b13..f62f56a21a 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -9,6 +9,7 @@ - [Controlling Script Bindings](./Summary/controlling-script-bindings.md) - [Modifying Script Contexts](./Summary/customizing-script-contexts.md) - [Contexts](./Summary/contexts.md) +- [Callbacks](./Summary/callbacks.md) - [Script Systems](./ScriptSystems/introduction.md) - [Examples](./Examples/introduction.md) diff --git a/docs/src/ScriptingReference/core-callbacks.md b/docs/src/ScriptingReference/core-callbacks.md index 07d3a1e328..4bad348ead 100644 --- a/docs/src/ScriptingReference/core-callbacks.md +++ b/docs/src/ScriptingReference/core-callbacks.md @@ -1,12 +1,14 @@ # Core Callbacks -On top of callbacks which are registered by your application, BMS provides a set of core callbacks which are always available. +On top of callbacks which are registered by your application, BMS provides a set of core callbacks which are always available (unless disabled via plugin settings). The three core callbacks are: - `on_script_loaded` - `on_script_unloaded` - `on_script_reloaded` +For more information on how callbacks generally work see [the callbacks section](../Summary/callbacks.md). + ## `on_script_loaded` This will be called right after a script has been loaded or reloaded. This is a good place to initialize your script. You should avoid placing a lot of logic into the global body of your script, and instead put it into this callback. Otherwise errors in the initialization will fail the loading of the script. diff --git a/docs/src/Summary/callbacks.md b/docs/src/Summary/callbacks.md new file mode 100644 index 0000000000..e3887e425b --- /dev/null +++ b/docs/src/Summary/callbacks.md @@ -0,0 +1,36 @@ +# Callbacks + +Callbacks generally refer to hooks called either manually or via rust event handlers, on scripts which can choose to subscribe to them. + +Callbacks come in two variants: +- Freestanding, top level functions +- Registered, or "frozen" callbacks + +## Freestanding callbacks +An example of a freestanding callback: +```lua +function on_script_loaded() + print("doing things") +end +``` + +this callback is refered to by "name", and on the rust side can be invoked either via `RunScriptCallback` command, or by setting up an event handler system which passes on `ScriptCallbackEvent`'s to scripts. + +The key thing to note about this type of callback, is that if the script is ever reloaded, and the contents of this callback change, the logic inside it will also be hot-reloaded. + +## Registerd Callbacks +You can also register a callback like so: +```lua +register_callback("on_script_loaded", my_registered_callback) + +function my_registered_callback() + print("doing things") +end +``` + +Registered callbacks, take priority over freestanding ones, and contrary to freestanding callbacks, they are "frozen". I.e. once a callback is registereed in this manner, +hot reloads won't affect the logic inside them. + +This works well when using shared contexts, where scripts will overwrite top level functions when being loaded. You can use the `on_script_loaded` callback to register all your scripts callbacks while they are loaded as top level functions, and when future loads happen, every callback will be issued correctly. + +This functionality is implemented at script plugin level, so some languages might not support this. All core languages do however. \ No newline at end of file diff --git a/docs/src/Summary/contexts.md b/docs/src/Summary/contexts.md index 8f6039d6c7..90c9f6213b 100644 --- a/docs/src/Summary/contexts.md +++ b/docs/src/Summary/contexts.md @@ -14,6 +14,12 @@ app.add_plugins(LuaScriptingPlugin::default().set_context_policy( )); ``` +

+ ### Per Script Context A per script context provides each script with their own context. However, scripts may be attached to multiple entities, in which case a single script context is shared by multiple entities. From 1fec62394be1a4ce9912d8fcb71ee8e8dd20d82a Mon Sep 17 00:00:00 2001 From: makspll Date: Sun, 12 Oct 2025 19:58:46 +0100 Subject: [PATCH 4/5] docs changes --- .../src/function/namespace.rs | 28 +++++++++-- .../src/function/script_function.rs | 8 ++- crates/bevy_mod_scripting_core/src/lib.rs | 5 +- .../src/derive/script_bindings.rs | 17 +++++++ crates/bevy_mod_scripting_derive/src/lib.rs | 1 + .../bevy_mod_scripting_functions/src/core.rs | 50 +++++++++++++++++-- crates/ladfile_builder/src/plugin.rs | 21 ++++++-- examples/docgen.rs | 7 +++ 8 files changed, 122 insertions(+), 15 deletions(-) diff --git a/crates/bevy_mod_scripting_bindings/src/function/namespace.rs b/crates/bevy_mod_scripting_bindings/src/function/namespace.rs index af8d8ea0b4..f61aa44fd0 100644 --- a/crates/bevy_mod_scripting_bindings/src/function/namespace.rs +++ b/crates/bevy_mod_scripting_bindings/src/function/namespace.rs @@ -1,6 +1,7 @@ //! A module for managing namespaces for functions use crate::{ + DummyScriptFunctionRegistry, ScriptFunctionRegistryArc, docgen::info::GetFunctionInfo, function::script_function::{AppScriptFunctionRegistry, ScriptFunction}, }; @@ -66,6 +67,8 @@ impl Namespace { /// A convenience builder for registering multiple functions in a namespace pub struct NamespaceBuilder<'a, N> { + /// If true will use the dummy function registry instead + registry: ScriptFunctionRegistryArc, /// phantom data to reference the namespace type namespace: PhantomData, /// a cached reference to the world @@ -86,6 +89,10 @@ impl<'a, S: IntoNamespace> NamespaceBuilder<'a, S> { registry.register::(); } Self { + registry: world + .get_resource_or_init::() + .0 + .clone(), namespace: Default::default(), world, } @@ -94,11 +101,27 @@ impl<'a, S: IntoNamespace> NamespaceBuilder<'a, S> { /// Prefer using the `register` method on the `NamespaceBuilder` instead pub fn new_unregistered(world: &'a mut World) -> Self { Self { + registry: world + .get_resource_or_init::() + .0 + .clone(), namespace: Default::default(), world, } } + /// Register functions for this namespace on the dummy function registry instead. + /// + /// This will appear in documentation but not become callable. + pub fn with_dummy_registry(mut self) -> Self { + self.registry = self + .world + .get_resource_or_init::() + .0 + .clone(); + self + } + /// Registers a function in the namespace pub fn register<'env, N, F, M>(&mut self, name: N, function: F) -> &mut Self where @@ -136,10 +159,7 @@ impl<'a, S: IntoNamespace> NamespaceBuilder<'a, S> { { { { - let mut registry = self - .world - .get_resource_or_init::(); - let mut registry = registry.write(); + let mut registry = self.registry.write(); registry.register_with_arg_names( S::into_namespace(), name, diff --git a/crates/bevy_mod_scripting_bindings/src/function/script_function.rs b/crates/bevy_mod_scripting_bindings/src/function/script_function.rs index d563aff1a1..2bd2829ceb 100644 --- a/crates/bevy_mod_scripting_bindings/src/function/script_function.rs +++ b/crates/bevy_mod_scripting_bindings/src/function/script_function.rs @@ -258,10 +258,16 @@ where } } +/// Identical to the [`AppScriptFunctionRegistry`], but the functions only exist for docs purposes, use if you provide functions at a lower level, +/// but still want to include the function in the docs +#[derive(Clone, Default, Resource, DebugWithTypeInfo)] +#[debug_with_type_info(bms_display_path = "bevy_mod_scripting_display")] +pub struct DummyScriptFunctionRegistry(pub ScriptFunctionRegistryArc); + /// Equivalent to [`AppFunctionRegistry`] but stores functions with a more convenient signature for scripting to avoid boxing every argument. #[derive(Clone, Default, Resource, DebugWithTypeInfo)] #[debug_with_type_info(bms_display_path = "bevy_mod_scripting_display")] -pub struct AppScriptFunctionRegistry(ScriptFunctionRegistryArc); +pub struct AppScriptFunctionRegistry(pub ScriptFunctionRegistryArc); impl Deref for AppScriptFunctionRegistry { type Target = ScriptFunctionRegistryArc; diff --git a/crates/bevy_mod_scripting_core/src/lib.rs b/crates/bevy_mod_scripting_core/src/lib.rs index 0067463b84..a73d2b8547 100644 --- a/crates/bevy_mod_scripting_core/src/lib.rs +++ b/crates/bevy_mod_scripting_core/src/lib.rs @@ -22,8 +22,8 @@ use bevy_mod_scripting_asset::{Language, LanguageExtensions, ScriptAsset, Script use bevy_mod_scripting_bindings::{ AppReflectAllocator, AppScheduleRegistry, AppScriptFunctionRegistry, - DynamicScriptComponentPlugin, MarkAsCore, ReflectReference, ScriptTypeRegistration, - ScriptValue, ThreadWorldContainer, garbage_collector, + DummyScriptFunctionRegistry, DynamicScriptComponentPlugin, MarkAsCore, ReflectReference, + ScriptTypeRegistration, ScriptValue, ThreadWorldContainer, garbage_collector, }; use context::{Context, ContextInitializer, ContextPreHandlingInitializer}; use event::{ScriptCallbackEvent, ScriptCallbackResponseEvent}; @@ -343,6 +343,7 @@ impl Plugin for BMSScriptingInfrastructurePlugin { .init_resource::() .init_asset::() .init_resource::() + .init_resource::() .insert_resource(AppScheduleRegistry::new()); app.register_type::(); diff --git a/crates/bevy_mod_scripting_derive/src/derive/script_bindings.rs b/crates/bevy_mod_scripting_derive/src/derive/script_bindings.rs index 14320398e1..8bc9debc5b 100644 --- a/crates/bevy_mod_scripting_derive/src/derive/script_bindings.rs +++ b/crates/bevy_mod_scripting_derive/src/derive/script_bindings.rs @@ -82,9 +82,18 @@ pub fn script_bindings( Default::default() }; + let use_dummy = if args.use_dummy_registry { + quote_spanned! {impl_span=> + .with_dummy_registry() + } + } else { + Default::default() + }; + let out = quote_spanned! {impl_span=> #visibility fn #function_name(world: &mut World) { #bms_bindings_path::function::namespace::NamespaceBuilder::<#type_ident_with_generics>::#builder_function_name(world) + #use_dummy #(#function_registrations)*; #mark_as_generated @@ -113,6 +122,9 @@ struct Args { pub core: bool, /// If true registers a marker type against the type registry to state that the type is significant (if unregistered is not set) pub significant: bool, + /// If true will register into the [`DummyScriptFunctionRegistry`] instead of the full one. + /// This is useful for documenting functions without actually making them available, if you're exposing them another way. + pub use_dummy_registry: bool, } impl syn::parse::Parse for Args { @@ -127,6 +139,7 @@ impl syn::parse::Parse for Args { let mut generated = false; let mut core = false; let mut significant = false; + let mut use_dummy_registry = false; let mut bms_bindings_path = syn::Path::from(syn::Ident::new("bevy_mod_scripting", Span::call_site())); bms_bindings_path.segments.push(syn::PathSegment { @@ -152,6 +165,9 @@ impl syn::parse::Parse for Args { } else if path.is_ident("significant") { significant = true; continue; + } else if path.is_ident("use_dummy_registry") { + use_dummy_registry = true; + continue; } } syn::Meta::NameValue(name_value) => { @@ -190,6 +206,7 @@ impl syn::parse::Parse for Args { generated, core, significant, + use_dummy_registry, }) } } diff --git a/crates/bevy_mod_scripting_derive/src/lib.rs b/crates/bevy_mod_scripting_derive/src/lib.rs index ac9daee6f8..cf0e56f80d 100644 --- a/crates/bevy_mod_scripting_derive/src/lib.rs +++ b/crates/bevy_mod_scripting_derive/src/lib.rs @@ -27,6 +27,7 @@ pub fn into_script(input: proc_macro::TokenStream) -> proc_macro::TokenStream { /// - `unregistered`: If set, will use `new_unregistered` instead of `new` for the namespace builder /// - `core`: If set, marks the type as `core` using the `MarkAsCore` type data /// - `significant`: If set, marks the type as `significant` using the `MarkAsSignificant` type data +/// - `use_dummy_registry`: If true will register into the [`DummyScriptFunctionRegistry`] instead of the full one. This is useful for documenting functions without actually making them available, if you're exposing them another way. /// /// It is encouraged to place `significant` markers on your own types, for the purposes of documentation generation. #[proc_macro_attribute] diff --git a/crates/bevy_mod_scripting_functions/src/core.rs b/crates/bevy_mod_scripting_functions/src/core.rs index 7c5c3227e2..5da871d8e0 100644 --- a/crates/bevy_mod_scripting_functions/src/core.rs +++ b/crates/bevy_mod_scripting_functions/src/core.rs @@ -9,9 +9,10 @@ use bevy_app::App; use bevy_asset::{AssetServer, Handle}; use bevy_ecs::{entity::Entity, prelude::AppTypeRegistry, schedule::Schedules, world::World}; use bevy_mod_scripting_bindings::{ - DynamicScriptFunctionMut, FunctionInfo, GlobalNamespace, InteropError, PartialReflectExt, - ReflectReference, ScriptComponentRegistration, ScriptQueryBuilder, ScriptQueryResult, - ScriptResourceRegistration, ScriptTypeRegistration, ThreadWorldContainer, Union, + DynamicScriptFunction, DynamicScriptFunctionMut, FunctionInfo, GlobalNamespace, InteropError, + PartialReflectExt, ReflectReference, ScriptComponentRegistration, ScriptQueryBuilder, + ScriptQueryResult, ScriptResourceRegistration, ScriptTypeRegistration, ThreadWorldContainer, + Union, function::{ from::{Ref, Val}, from_ref::FromScriptRef, @@ -1329,6 +1330,48 @@ impl Handle { } } +/// globals which are being registered at lower level within each language plugin. +#[script_bindings( + remote, + bms_bindings_path = "bevy_mod_scripting_bindings", + name = "global_namespace_dummy_functions", + unregistered, + use_dummy_registry +)] +impl GlobalNamespace { + /// Registers a "frozen" callback handler, + /// + /// For example, this code: + /// + /// ```lua + /// register_callback("on_script_unloaded", my_unload_handler) + /// + /// function my_unload_handler() + /// print("handling unload!") + /// end + /// ``` + /// + /// would call the `my_unload_handler` function, whenever the `on_script_unloaded` callback is triggered, which is when your script is about to be unloaded. + /// + /// Registered callbacks take precedence over free-standing function callbacks, i.e. the below top level function: + /// ```lua + /// function on_script_unloaded() + /// print("freestanding unload handler!") + /// end + /// ``` + /// + /// would be a valid handler, but if a registered callback existed, it would be called instead. + /// + /// Arguments: + /// * `callback`: the callback label to register this function against. + /// * `function`: the callback function which will be stored as a handler for this callback label. + fn register_callback(callback: String, function: DynamicScriptFunction) { + // to avoid clippy unused errors. + println!("dummy called!: {callback:?}, {function:?}"); + } +} + +/// Globals registered by us #[script_bindings( remote, bms_bindings_path = "bevy_mod_scripting_bindings", @@ -1421,5 +1464,6 @@ pub fn register_core_functions(app: &mut App) { register_script_handle_functions(world); register_global_namespace_functions(world); + register_global_namespace_dummy_functions(world); } } diff --git a/crates/ladfile_builder/src/plugin.rs b/crates/ladfile_builder/src/plugin.rs index 98f9ee20f1..80f5e6e897 100644 --- a/crates/ladfile_builder/src/plugin.rs +++ b/crates/ladfile_builder/src/plugin.rs @@ -8,7 +8,7 @@ use ::{ }; use bevy_log::{error, info}; use bevy_mod_scripting_bindings::{ - IntoNamespace, + DummyScriptFunctionRegistry, IntoNamespace, function::{namespace::Namespace, script_function::AppScriptFunctionRegistry}, globals::AppScriptGlobalsRegistry, }; @@ -74,11 +74,13 @@ impl ScriptingDocgenPlugin { pub fn generate_lad_file( type_registry: &AppTypeRegistry, function_registry: &AppScriptFunctionRegistry, + dummy_function_registry: &DummyScriptFunctionRegistry, global_registry: &AppScriptGlobalsRegistry, settings: &LadFileSettings, ) { let type_registry = type_registry.read(); let function_registry = function_registry.read(); + let dummy_function_registry = dummy_function_registry.0.read(); let global_registry = global_registry.read(); let mut builder = LadFileBuilder::new(&type_registry); builder @@ -92,7 +94,10 @@ pub fn generate_lad_file( r#"The ECS world containing all Components, Resources and Systems. Main point of interaction with a Bevy App."#.trim(), ); - for (_, function) in function_registry.iter_namespace(World::into_namespace()) { + for (_, function) in function_registry + .iter_namespace(World::into_namespace()) + .chain(dummy_function_registry.iter_namespace(World::into_namespace())) + { builder.add_function_info(&function.info); } @@ -113,15 +118,19 @@ pub fn generate_lad_file( builder.add_type_info(type_info); // find functions on the namespace - for (_, function) in - function_registry.iter_namespace(Namespace::OnType(type_info.type_id())) + for (_, function) in function_registry + .iter_namespace(Namespace::OnType(type_info.type_id())) + .chain(dummy_function_registry.iter_namespace(Namespace::OnType(type_info.type_id()))) { builder.add_function_info(&function.info); } } // find functions on the global namespace - for (_, function) in function_registry.iter_namespace(Namespace::Global) { + for (_, function) in function_registry + .iter_namespace(Namespace::Global) + .chain(dummy_function_registry.iter_namespace(Namespace::Global)) + { builder.add_function_info(&function.info); } @@ -171,12 +180,14 @@ pub fn generate_lad_file( fn generate_lad_file_system( type_registry: Res, function_registry: Res, + dummy_function_registry: Res, global_registry: Res, settings: Res, ) { generate_lad_file( &type_registry, &function_registry, + &dummy_function_registry, &global_registry, &settings, ); diff --git a/examples/docgen.rs b/examples/docgen.rs index 9ec757e8f1..3da9fead12 100644 --- a/examples/docgen.rs +++ b/examples/docgen.rs @@ -1,6 +1,7 @@ use bevy::{DefaultPlugins, app::App, ecs::reflect::AppTypeRegistry}; use bevy_mod_scripting::ScriptFunctionsPlugin; use bevy_mod_scripting_bindings::{ + DummyScriptFunctionRegistry, function::script_function::AppScriptFunctionRegistry, globals::{AppScriptGlobalsRegistry, core::CoreScriptGlobalsPlugin}, }; @@ -44,6 +45,11 @@ fn main() -> std::io::Result<()> { .get_resource::() .unwrap() .clone(); + let dummy_function_registry = app + .world() + .get_resource::() + .unwrap() + .clone(); let global_registry = app .world() .get_resource::() @@ -58,6 +64,7 @@ fn main() -> std::io::Result<()> { generate_lad_file( &type_registry, &function_registry, + &dummy_function_registry, &global_registry, &settings, ); From db04e3fa02d7574f7ee0f587fae624a592e9b4cf Mon Sep 17 00:00:00 2001 From: makspll Date: Sun, 12 Oct 2025 20:15:46 +0100 Subject: [PATCH 5/5] fix a bunch of docs links --- crates/bevy_mod_scripting_bindings/src/allocator.rs | 6 +++--- .../src/docgen/typed_through.rs | 4 ++-- .../bevy_mod_scripting_bindings/src/function/from.rs | 2 +- .../src/function/into_ref.rs | 4 ++-- .../src/function/script_function.rs | 2 +- crates/bevy_mod_scripting_bindings/src/reference.rs | 12 ++++++------ crates/bevy_mod_scripting_bindings/src/world.rs | 2 +- crates/bevy_mod_scripting_core/src/commands.rs | 2 +- crates/bevy_mod_scripting_core/src/lib.rs | 2 +- crates/bevy_mod_scripting_core/src/pipeline/mod.rs | 10 +++++----- crates/bevy_mod_scripting_core/src/pipeline/start.rs | 6 +++--- .../src/script/script_context.rs | 2 +- crates/bevy_mod_scripting_core/src/script_system.rs | 2 +- crates/bevy_mod_scripting_derive/src/lib.rs | 2 +- crates/bevy_mod_scripting_script/src/lib.rs | 2 +- .../mdbook_lad_preprocessor/src/markdown.rs | 4 ++-- crates/ladfile_builder/src/lib.rs | 2 +- .../bevy_mod_scripting_lua/src/bindings/reference.rs | 2 +- 18 files changed, 34 insertions(+), 34 deletions(-) diff --git a/crates/bevy_mod_scripting_bindings/src/allocator.rs b/crates/bevy_mod_scripting_bindings/src/allocator.rs index 3258911134..2e22e5ba10 100644 --- a/crates/bevy_mod_scripting_bindings/src/allocator.rs +++ b/crates/bevy_mod_scripting_bindings/src/allocator.rs @@ -195,13 +195,13 @@ pub struct ReflectAllocator { #[profiling::all_functions] impl ReflectAllocator { - /// Allocates a new [`Reflect`] value and returns an [`AllocationId`] which can be used to access it later. + /// Allocates a new `Reflect` value and returns an [`ReflectAllocationId`] which can be used to access it later. /// Use [`Self::allocate_boxed`] if you already have an allocated boxed value. pub fn allocate(&mut self, value: T) -> ReflectAllocationId { self.allocate_boxed(Box::new(value)) } - /// Allocates a new boxed [`PartialReflect`] value and returns an [`AllocationId`] which can be used to access it later. + /// Allocates a new boxed `PartialReflect` value and returns an [`ReflectAllocationId`] which can be used to access it later. pub fn allocate_boxed(&mut self, value: Box) -> ReflectAllocationId { static COUNTER: AtomicU64 = AtomicU64::new(0); @@ -236,7 +236,7 @@ impl ReflectAllocator { self.allocations.get(id) } - /// Deallocates the [`PartialReflect`] value with the given [`AllocationId`] + /// Deallocates the `PartialReflect` value with the given [`ReflectAllocationId`] pub fn deallocate(&mut self, id: &ReflectAllocationId) { self.allocations.remove(id); } diff --git a/crates/bevy_mod_scripting_bindings/src/docgen/typed_through.rs b/crates/bevy_mod_scripting_bindings/src/docgen/typed_through.rs index b56bc2b5f1..c5537ae803 100644 --- a/crates/bevy_mod_scripting_bindings/src/docgen/typed_through.rs +++ b/crates/bevy_mod_scripting_bindings/src/docgen/typed_through.rs @@ -1,5 +1,5 @@ -//! Defines a set of traits which destruture [`bevy::reflect::TypeInfo`] and implement a light weight wrapper around it, to allow types -//! which normally can't implement [`bevy::reflect::Typed`] to be used in a reflection context. +//! Defines a set of traits which destruture [`bevy_reflect::TypeInfo`] and implement a light weight wrapper around it, to allow types +//! which normally can't implement [`bevy_reflect::Typed`] to be used in a reflection context. use std::{ffi::OsString, path::PathBuf}; diff --git a/crates/bevy_mod_scripting_bindings/src/function/from.rs b/crates/bevy_mod_scripting_bindings/src/function/from.rs index c12872fb88..0ef3d713c7 100644 --- a/crates/bevy_mod_scripting_bindings/src/function/from.rs +++ b/crates/bevy_mod_scripting_bindings/src/function/from.rs @@ -229,7 +229,7 @@ impl FromScript for Val { /// Before downcasting the reference, it will claim write access to the object to ensure that the reference is valid. /// /// However, the access is NOT released when the `Mut` is dropped. This is not unsafe but can lead to deadlocks if not released later. -/// The [`ScriptFunction`] calling mechanism will take care of releasing all accesses claimed during the function call. +/// The script function calling mechanism will take care of releasing all accesses claimed during the function call. pub struct Ref<'w, T>(pub &'w T); impl Deref for Ref<'_, T> { diff --git a/crates/bevy_mod_scripting_bindings/src/function/into_ref.rs b/crates/bevy_mod_scripting_bindings/src/function/into_ref.rs index 33ef0473ef..9e50446860 100644 --- a/crates/bevy_mod_scripting_bindings/src/function/into_ref.rs +++ b/crates/bevy_mod_scripting_bindings/src/function/into_ref.rs @@ -10,7 +10,7 @@ use crate::{ reflection_extensions::PartialReflectExt, }; -/// Converts a value represented by a reference into a [`crate::function::ScriptValue`]. +/// Converts a value represented by a reference into a [`crate::ScriptValue`]. /// Instead of a direct conversion, the trait tries to peek into the value behind the reference and find out the most suitable representation. /// /// Type Erased version of [`super::from::FromScript`]. @@ -18,7 +18,7 @@ use crate::{ /// - Primitives are converted to simple values /// - Container types are converted to references (so the references persist after accesses inside them) pub trait IntoScriptRef { - /// Converts a value represented by a reference into a [`crate::function::ScriptValue`]. + /// Converts a value represented by a reference into a [`crate::ScriptValue`]. fn into_script_ref( self_: ReflectReference, world: WorldGuard, diff --git a/crates/bevy_mod_scripting_bindings/src/function/script_function.rs b/crates/bevy_mod_scripting_bindings/src/function/script_function.rs index 2bd2829ceb..aade83e584 100644 --- a/crates/bevy_mod_scripting_bindings/src/function/script_function.rs +++ b/crates/bevy_mod_scripting_bindings/src/function/script_function.rs @@ -264,7 +264,7 @@ where #[debug_with_type_info(bms_display_path = "bevy_mod_scripting_display")] pub struct DummyScriptFunctionRegistry(pub ScriptFunctionRegistryArc); -/// Equivalent to [`AppFunctionRegistry`] but stores functions with a more convenient signature for scripting to avoid boxing every argument. +/// Equivalent to [`AppScriptFunctionRegistry`] but stores functions with a more convenient signature for scripting to avoid boxing every argument. #[derive(Clone, Default, Resource, DebugWithTypeInfo)] #[debug_with_type_info(bms_display_path = "bevy_mod_scripting_display")] pub struct AppScriptFunctionRegistry(pub ScriptFunctionRegistryArc); diff --git a/crates/bevy_mod_scripting_bindings/src/reference.rs b/crates/bevy_mod_scripting_bindings/src/reference.rs index 6a6259f798..b423f20503 100644 --- a/crates/bevy_mod_scripting_bindings/src/reference.rs +++ b/crates/bevy_mod_scripting_bindings/src/reference.rs @@ -354,11 +354,11 @@ impl ReflectReference { })? } - /// Attempts to create a [`Box`] from the reference. This is possible using a few strategies: - /// - If the reference is to a world, a [`WorldCallbackAccess`] is created and boxed + /// Attempts to create a `Box` from the reference. This is possible using a few strategies: + /// - If the reference is to a world, a [`crate::world::WorldCallbackAccess`] is created and boxed /// - If the reference is to an allocation with no reflection path and references to it, the value is taken as is. - /// - If the reference has a [`bevy::reflect::ReflectFromReflect`] type data associated with it, the value is cloned using that impl. - /// - If all above fails, [`bevy::reflect::PartialReflect::clone_value`] is used to clone the value. + /// - If the reference has a [`bevy_reflect::ReflectFromReflect`] type data associated with it, the value is cloned using that impl. + /// - If all above fails, [`bevy_reflect::PartialReflect::clone_value`] is used to clone the value. /// pub fn to_owned_value( &self, @@ -466,7 +466,7 @@ impl ReflectReference { /// - The caller must ensure the cell has permission to access the underlying value /// - The caller must ensure no aliasing references to the same value exist at all at the same time /// - /// To do this safely you need to use [`WorldAccessGuard::claim_read_access`] or [`WorldAccessGuard::claim_global_access`] to ensure nobody else is currently accessing the value. + /// To do this safely you need to use [`crate::world::WorldAccessGuard::claim_read_access`] or [`crate::world::WorldAccessGuard::claim_global_access`] to ensure nobody else is currently accessing the value. pub unsafe fn reflect_unsafe<'w>( &self, world: WorldGuard<'w>, @@ -527,7 +527,7 @@ impl ReflectReference { /// - The caller must ensure the cell has permission to access the underlying value /// - The caller must ensure no other references to the same value exist at all at the same time (even if you have the correct access) /// - /// To do this safely you need to use [`WorldAccessGuard::claim_write_access`] or [`WorldAccessGuard::claim_global_access`] to ensure nobody else is currently accessing the value. + /// To do this safely you need to use [`crate::world::WorldAccessGuard::claim_write_access`] or [`crate::world::WorldAccessGuard::claim_global_access`] to ensure nobody else is currently accessing the value. pub unsafe fn reflect_mut_unsafe<'w>( &self, world: WorldGuard<'w>, diff --git a/crates/bevy_mod_scripting_bindings/src/world.rs b/crates/bevy_mod_scripting_bindings/src/world.rs index 2a76e1cfa3..8a5b8bbd5e 100644 --- a/crates/bevy_mod_scripting_bindings/src/world.rs +++ b/crates/bevy_mod_scripting_bindings/src/world.rs @@ -69,7 +69,7 @@ pub type WorldGuard<'w> = WorldAccessGuard<'w>; /// Similar to [`WorldGuard`], but without the arc, use for when you don't need the outer Arc. pub type WorldGuardRef<'w> = &'w WorldAccessGuard<'w>; -/// Provides safe access to the world via [`WorldAccess`] permissions, which enforce aliasing rules at runtime in multi-thread environments +/// Provides safe access to the world via [`AnyAccessMap`] permissions, which enforce aliasing rules at runtime in multi-thread environments #[derive(Clone, Debug)] pub struct WorldAccessGuard<'w> { /// The guard this guard pointer represents diff --git a/crates/bevy_mod_scripting_core/src/commands.rs b/crates/bevy_mod_scripting_core/src/commands.rs index ade22041f1..3fd9efa9c3 100644 --- a/crates/bevy_mod_scripting_core/src/commands.rs +++ b/crates/bevy_mod_scripting_core/src/commands.rs @@ -108,7 +108,7 @@ impl RunScriptCallback

{ result } - /// Equivalent to [`Self::run`], but usable in the case where you already have a [`HandlerContext`]. + /// Equivalent to [`Self::run`], but usable in the case where you already have [`ScriptContext`] and [`ScriptCallbacks`] resources available. pub fn run_with_contexts( self, guard: WorldGuard, diff --git a/crates/bevy_mod_scripting_core/src/lib.rs b/crates/bevy_mod_scripting_core/src/lib.rs index a73d2b8547..032b7718ca 100644 --- a/crates/bevy_mod_scripting_core/src/lib.rs +++ b/crates/bevy_mod_scripting_core/src/lib.rs @@ -64,7 +64,7 @@ pub enum ScriptingSystemSet { /// /// When implementing a new scripting plugin, also ensure the following implementations exist: /// - [`Plugin`] for the plugin, both [`Plugin::build`] and [`Plugin::finish`] methods need to be dispatched to the underlying [`ScriptingPlugin`] struct -/// - [`AsMut`] for the plugin struct +/// - [`AsMut>`] for the plugin struct pub trait IntoScriptPluginParams: 'static + GetPluginThreadConfig { /// The language of the scripts const LANGUAGE: Language; diff --git a/crates/bevy_mod_scripting_core/src/pipeline/mod.rs b/crates/bevy_mod_scripting_core/src/pipeline/mod.rs index cf7dc2936e..6fb5a13ba5 100644 --- a/crates/bevy_mod_scripting_core/src/pipeline/mod.rs +++ b/crates/bevy_mod_scripting_core/src/pipeline/mod.rs @@ -48,17 +48,17 @@ pub enum PipelineSet { /// A pipeline plugin which enables the loading and unloading of scripts in a highly modular way pub struct ScriptLoadingPipeline { - /// by default the plugin will listen to [`ScriptComponent`] attachments/detachments and synchronize scripts accordingly, + /// by default the plugin will listen to [`crate::ScriptComponent`] attachments/detachments and synchronize scripts accordingly, /// you can opt out of this behavior by disabling this flag. pub script_component_triggers: bool, - /// by default the plugin will listen to [`AssetEvent`] events, and trigger the pipeline on asset modifications. + /// by default the plugin will listen to [`bevy_asset::AssetEvent`] events, and trigger the pipeline on asset modifications. pub hot_loading_asset_triggers: bool, - /// If true the [`OnScriptLoaded`] callback will be triggered when loading scripts + /// If true the [`crate::event::OnScriptLoaded`] callback will be triggered when loading scripts pub on_script_loaded_callback: bool, - /// If true the [`OnScriptReloaded`] callback will be triggered when loading scripts + /// If true the [`crate::event::OnScriptReloaded`] callback will be triggered when loading scripts pub on_script_reloaded_callback: bool, - /// If true the [`OnScriptUnloaded`] callback will be triggered when loading scripts + /// If true the [`crate::event::OnScriptUnloaded`] callback will be triggered when loading scripts pub on_script_unloaded_callback: bool, _ph: PhantomData, diff --git a/crates/bevy_mod_scripting_core/src/pipeline/start.rs b/crates/bevy_mod_scripting_core/src/pipeline/start.rs index 5579d3b150..3b90e69bfe 100644 --- a/crates/bevy_mod_scripting_core/src/pipeline/start.rs +++ b/crates/bevy_mod_scripting_core/src/pipeline/start.rs @@ -112,7 +112,7 @@ pub fn filter_script_detachments( } } -/// Process [`ScriptAttachedEvent`]'s and generate loading machines with the [`LoadingInitializedState`] and [`ReloadingInitializedState`] states +/// Process [`ScriptAttachedEvent`]'s and generate loading machines with the [`LoadingInitialized`] and [`ReloadingInitialized`] states pub fn process_attachments( mut events: EventReader>, mut machines: ResMut>, @@ -151,7 +151,7 @@ pub fn process_attachments( }); } -/// Processes [`ScriptAttachedEvent`]'s and initialized unloading state machines with [`UnloadingInitializedState`] states +/// Processes [`ScriptAttachedEvent`]'s and initialized unloading state machines with [`UnloadingInitialized`] states pub fn process_detachments( mut events: EventReader>, mut machines: ResMut>, @@ -175,7 +175,7 @@ pub fn process_detachments( }); } -/// Processes [`ScriptAssetModifiedEvent`]'s and initializes loading state machines with [`ReloadingInitializedState`] states +/// Processes [`ScriptAssetModifiedEvent`]'s and initializes loading state machines with [`ReloadingInitialized`] states pub fn process_asset_modifications( mut events: EventReader>, mut machines: ResMut>, diff --git a/crates/bevy_mod_scripting_core/src/script/script_context.rs b/crates/bevy_mod_scripting_core/src/script/script_context.rs index fa622a077a..c5c160451e 100644 --- a/crates/bevy_mod_scripting_core/src/script/script_context.rs +++ b/crates/bevy_mod_scripting_core/src/script/script_context.rs @@ -447,7 +447,7 @@ impl ScriptContextInner

{ } /// Use one script context per entity and script by default; see -/// [ScriptContext::per_entity_and_script]. +/// [`ContextPolicy::per_entity_and_script`]. impl Default for ScriptContextInner

{ fn default() -> Self { Self { diff --git a/crates/bevy_mod_scripting_core/src/script_system.rs b/crates/bevy_mod_scripting_core/src/script_system.rs index 9ad6b54641..1171a03cd9 100644 --- a/crates/bevy_mod_scripting_core/src/script_system.rs +++ b/crates/bevy_mod_scripting_core/src/script_system.rs @@ -205,7 +205,7 @@ struct ScriptSystemState { script_callbacks: ScriptCallbacks

, } -/// Equivalent of [`SystemParam`] but for dynamic systems, these are the kinds of things +/// Equivalent of [`bevy_ecs::system::SystemParam`] but for dynamic systems, these are the kinds of things /// that scripts can ask for access to and get passed in through dynamic script systems. pub enum ScriptSystemParam { /// An exclusive resource access diff --git a/crates/bevy_mod_scripting_derive/src/lib.rs b/crates/bevy_mod_scripting_derive/src/lib.rs index cf0e56f80d..8f931cc005 100644 --- a/crates/bevy_mod_scripting_derive/src/lib.rs +++ b/crates/bevy_mod_scripting_derive/src/lib.rs @@ -27,7 +27,7 @@ pub fn into_script(input: proc_macro::TokenStream) -> proc_macro::TokenStream { /// - `unregistered`: If set, will use `new_unregistered` instead of `new` for the namespace builder /// - `core`: If set, marks the type as `core` using the `MarkAsCore` type data /// - `significant`: If set, marks the type as `significant` using the `MarkAsSignificant` type data -/// - `use_dummy_registry`: If true will register into the [`DummyScriptFunctionRegistry`] instead of the full one. This is useful for documenting functions without actually making them available, if you're exposing them another way. +/// - `use_dummy_registry`: If true will register into the [`bevy_mod_scripting_bindings::function::DummyScriptFunctionRegistry`] instead of the full one. This is useful for documenting functions without actually making them available, if you're exposing them another way. /// /// It is encouraged to place `significant` markers on your own types, for the purposes of documentation generation. #[proc_macro_attribute] diff --git a/crates/bevy_mod_scripting_script/src/lib.rs b/crates/bevy_mod_scripting_script/src/lib.rs index ecb62e5d76..ae84fc343c 100644 --- a/crates/bevy_mod_scripting_script/src/lib.rs +++ b/crates/bevy_mod_scripting_script/src/lib.rs @@ -7,7 +7,7 @@ use bevy_mod_scripting_display::DisplayProxy; use bevy_reflect::Reflect; use std::fmt; -/// Specifies a unique attachment of a script. These attachments are mapped to [`ContextKey`]'s depending on the context policy used. +/// Specifies a unique attachment of a script. These attachments are mapped to [`bevy_mod_scripting_core::ContextKey`]'s depending on the context policy used. #[derive(Debug, Hash, Clone, PartialEq, Eq, Reflect)] pub enum ScriptAttachment { /// a script attached to an entity, with an optional domain. By default selecting a domain will put the context of this script on a per-domain basis. diff --git a/crates/lad_backends/mdbook_lad_preprocessor/src/markdown.rs b/crates/lad_backends/mdbook_lad_preprocessor/src/markdown.rs index 5ae984fec3..d38c0bdd4b 100644 --- a/crates/lad_backends/mdbook_lad_preprocessor/src/markdown.rs +++ b/crates/lad_backends/mdbook_lad_preprocessor/src/markdown.rs @@ -341,8 +341,8 @@ impl IntoMarkdown for Box { } } -/// Usage: markdown_vec![item1, item2, item3] -/// Creates Vec from a list of items. +/// Usage: `markdown_vec![item1, item2, item3]` +/// Creates `Vec`` from a list of items. #[macro_export] macro_rules! markdown_vec { ($($x:expr),*$(,)?) => { diff --git a/crates/ladfile_builder/src/lib.rs b/crates/ladfile_builder/src/lib.rs index 11efb8a816..be099c86e3 100644 --- a/crates/ladfile_builder/src/lib.rs +++ b/crates/ladfile_builder/src/lib.rs @@ -362,7 +362,7 @@ impl<'t> LadFileBuilder<'t> { /// Add a function definition to the LAD file. /// Will overwrite any existing function definitions with the same function id. /// - /// Parses argument and return specific docstrings as per: https://github.com/rust-lang/rust/issues/57525 + /// Parses argument and return specific docstrings as per: /// /// i.e. looks for blocks like: /// ```rust,ignore 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 dc676b48ad..67d14f2e3f 100644 --- a/crates/languages/bevy_mod_scripting_lua/src/bindings/reference.rs +++ b/crates/languages/bevy_mod_scripting_lua/src/bindings/reference.rs @@ -10,7 +10,7 @@ use crate::IntoMluaError; use super::script_value::{LUA_CALLER_CONTEXT, LuaScriptValue}; -/// Lua UserData wrapper for [`bevy_mod_scripting_core::bindings::ReflectReference`]. +/// Lua UserData wrapper for [`ReflectReference`]. /// Acts as a lua reflection interface. Any value which is registered in the type registry can be interacted with using this type. #[derive(Debug, Clone, PartialEq, mlua::FromLua)] pub struct LuaReflectReference(pub ReflectReference);