Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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" }
Expand Down Expand Up @@ -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"]
Expand Down
12 changes: 12 additions & 0 deletions assets/tests/register_callback/lua/dynamic_on_test.lua
Original file line number Diff line number Diff line change
@@ -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
Empty file.
27 changes: 27 additions & 0 deletions assets/tests/register_callback/lua/scenario.txt
Original file line number Diff line number Diff line change
@@ -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!"

12 changes: 12 additions & 0 deletions assets/tests/register_callback/rhai/dynamic_on_test.rhai
Original file line number Diff line number Diff line change
@@ -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!";
}
Empty file.
27 changes: 27 additions & 0 deletions assets/tests/register_callback/rhai/scenario.txt
Original file line number Diff line number Diff line change
@@ -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!"

1 change: 1 addition & 0 deletions crates/bevy_mod_scripting_bindings/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
6 changes: 3 additions & 3 deletions crates/bevy_mod_scripting_bindings/src/allocator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T: PartialReflect>(&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<dyn PartialReflect>) -> ReflectAllocationId {
static COUNTER: AtomicU64 = AtomicU64::new(0);

Expand Down Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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};

Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_mod_scripting_bindings/src/function/from.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ impl<T: FromReflect> FromScript for Val<T> {
/// 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<T> Deref for Ref<'_, T> {
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_mod_scripting_bindings/src/function/into_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ 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`].
///
/// - 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,
Expand Down
28 changes: 24 additions & 4 deletions crates/bevy_mod_scripting_bindings/src/function/namespace.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! A module for managing namespaces for functions

use crate::{
DummyScriptFunctionRegistry, ScriptFunctionRegistryArc,
docgen::info::GetFunctionInfo,
function::script_function::{AppScriptFunctionRegistry, ScriptFunction},
};
Expand Down Expand Up @@ -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<N>,
/// a cached reference to the world
Expand All @@ -86,6 +89,10 @@ impl<'a, S: IntoNamespace> NamespaceBuilder<'a, S> {
registry.register::<S>();
}
Self {
registry: world
.get_resource_or_init::<AppScriptFunctionRegistry>()
.0
.clone(),
namespace: Default::default(),
world,
}
Expand All @@ -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::<AppScriptFunctionRegistry>()
.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::<DummyScriptFunctionRegistry>()
.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
Expand Down Expand Up @@ -136,10 +159,7 @@ impl<'a, S: IntoNamespace> NamespaceBuilder<'a, S> {
{
{
{
let mut registry = self
.world
.get_resource_or_init::<AppScriptFunctionRegistry>();
let mut registry = registry.write();
let mut registry = self.registry.write();
registry.register_with_arg_names(
S::into_namespace(),
name,
Expand Down
23 changes: 18 additions & 5 deletions crates/bevy_mod_scripting_bindings/src/function/script_function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<WorldGuard<'l>, 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]
Expand Down Expand Up @@ -258,10 +258,16 @@ where
}
}

/// Equivalent to [`AppFunctionRegistry`] but stores functions with a more convenient signature for scripting to avoid boxing every argument.
/// 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 AppScriptFunctionRegistry(ScriptFunctionRegistryArc);
pub struct DummyScriptFunctionRegistry(pub ScriptFunctionRegistryArc);

/// 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);

impl Deref for AppScriptFunctionRegistry {
type Target = ScriptFunctionRegistryArc;
Expand Down Expand Up @@ -661,12 +667,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: Fn()>(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()
});
}
Expand Down
21 changes: 10 additions & 11 deletions crates/bevy_mod_scripting_bindings/src/reference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -68,7 +67,7 @@ impl DisplayWithTypeInfo for ReflectReference {

let guard = any.downcast_ref::<WorldGuard>().cloned().or_else(|| {
any.downcast_ref::<ThreadWorldContainer>()
.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 {
Expand Down Expand Up @@ -355,11 +354,11 @@ impl ReflectReference {
})?
}

/// Attempts to create a [`Box<dyn PartialReflect>`] 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<dyn PartialReflect>` 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,
Expand Down Expand Up @@ -467,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>,
Expand Down Expand Up @@ -528,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>,
Expand Down Expand Up @@ -767,7 +766,7 @@ impl DisplayWithTypeInfo for ReflectBase {

let guard = any.downcast_ref::<WorldGuard>().cloned().or_else(|| {
any.downcast_ref::<ThreadWorldContainer>()
.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 {
Expand Down
Loading
Loading