From 108e429f1c206388d192076ef67397e2da4eefe5 Mon Sep 17 00:00:00 2001 From: makspll Date: Wed, 19 Mar 2025 17:15:47 +0000 Subject: [PATCH 01/11] WIP --- .../src/bindings/mod.rs | 1 + .../src/bindings/script_component.rs | 85 +++++++++++++++++++ .../src/bindings/world.rs | 50 ++++++++--- 3 files changed, 125 insertions(+), 11 deletions(-) create mode 100644 crates/bevy_mod_scripting_core/src/bindings/script_component.rs diff --git a/crates/bevy_mod_scripting_core/src/bindings/mod.rs b/crates/bevy_mod_scripting_core/src/bindings/mod.rs index 90bdcb0c36..100eb6ad27 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/mod.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/mod.rs @@ -12,4 +12,5 @@ crate::private::export_all_in_modules! { script_system, script_value, world, + script_component, } diff --git a/crates/bevy_mod_scripting_core/src/bindings/script_component.rs b/crates/bevy_mod_scripting_core/src/bindings/script_component.rs new file mode 100644 index 0000000000..4b58a3936a --- /dev/null +++ b/crates/bevy_mod_scripting_core/src/bindings/script_component.rs @@ -0,0 +1,85 @@ +//! Everything necessary to support scripts registering their own components + +use std::{alloc::Layout, mem::needs_drop, sync::Arc}; + +use bevy::{ + ecs::{ + component::{Component, ComponentDescriptor, StorageType}, + reflect::ReflectComponent, + }, + reflect::{GetTypeRegistration, Reflect, TypeRegistration}, + utils::HashMap, +}; +use parking_lot::RwLock; + +use crate::error::InteropError; + +use super::{ScriptComponentRegistration, ScriptTypeRegistration, ScriptValue, WorldGuard}; + +/// A dynamic script component, with script set +#[derive(Reflect, Clone)] +#[reflect(Component)] +pub struct ScriptComponent { + data: ScriptValue, +} + +/// Some metadata about dynamic script components +pub struct ScriptComponentInfo { + /// The name of the component + pub name: String, + /// The type registration for the component + pub registration: ScriptComponentRegistration, +} + +impl Component for ScriptComponent { + const STORAGE_TYPE: StorageType = StorageType::Table; +} + +/// A registry of dynamically registered script components +pub struct AppScriptComponentRegistry(pub Arc>); + +pub struct ScriptComponentRegistry { + components: HashMap, +} + +impl WorldGuard<'_> { + pub fn script_component_registry(&self) -> AppScriptComponentRegistry { + let component_registry = + self.with_resource(|app_component_registry: &AppScriptComponentRegistry| { + app_component_registry.clone() + })?; + } + + /// Registers a dynamic script component, and returns a reference to its registration + pub fn register_script_component( + &self, + component_name: String, + ) -> Result { + let component_id = self.with_global_access(|w| { + let descriptor = unsafe { + // Safety: same safety guarantees as ComponentDescriptor::new + // we know the type in advance + ComponentDescriptor::new_with_layout( + component_name, + ScriptComponent::STORAGE_TYPE, + Layout::new::(), + needs_drop::().then_some(|x| x.drop_as::()), + ) + }; + w.register_component_with_descriptor(descriptor) + })?; + + // we need to register this as a type in the type registry with this name so its retrievable as any other type + let type_registry = self.type_registry(); + let mut type_registry = type_registry.write(); + + // TODO: we should probably retrieve this from the registry, but I don't see what people would want to register on this type + // in addition to the existing registrations. + Ok(ScriptComponentRegistration::new( + ScriptTypeRegistration::new(Arc::new( + ::get_type_registration(), + )), + component_id, + )) + } +} diff --git a/crates/bevy_mod_scripting_core/src/bindings/world.rs b/crates/bevy_mod_scripting_core/src/bindings/world.rs index b6ef742261..08d49b5651 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/world.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/world.rs @@ -9,13 +9,23 @@ use super::{ access_map::{ AccessCount, AccessMapKey, AnyAccessMap, DynamicSystemMeta, ReflectAccessId, ReflectAccessKind, SubsetAccessMap, - }, function::{ + }, + function::{ namespace::Namespace, script_function::{AppScriptFunctionRegistry, DynamicScriptFunction, FunctionCallContext}, - }, pretty_print::DisplayWithWorld, schedule::AppScheduleRegistry, script_value::ScriptValue, with_global_access, AppReflectAllocator, ReflectBase, ReflectBaseType, ReflectReference, ScriptComponentRegistration, ScriptResourceRegistration, ScriptTypeRegistration, Union + }, + pretty_print::DisplayWithWorld, + schedule::AppScheduleRegistry, + script_value::ScriptValue, + with_global_access, AppReflectAllocator, AppScriptComponentRegistry, ReflectBase, + ReflectBaseType, ReflectReference, ScriptComponentRegistration, ScriptResourceRegistration, + ScriptTypeRegistration, Union, }; use crate::{ - bindings::{function::{from::FromScript, from_ref::FromScriptRef}, with_access_read, with_access_write}, + bindings::{ + function::{from::FromScript, from_ref::FromScriptRef}, + with_access_read, with_access_write, + }, error::InteropError, reflection_extensions::PartialReflectExt, }; @@ -73,6 +83,8 @@ pub(crate) struct WorldAccessGuardInner<'w> { function_registry: AppScriptFunctionRegistry, /// The schedule registry for the world schedule_registry: AppScheduleRegistry, + /// The registry of script registered components + script_component_registry: AppScriptComponentRegistry, } impl std::fmt::Debug for WorldAccessGuardInner<'_> { @@ -542,7 +554,6 @@ impl<'w> WorldAccessGuard<'w> { /// Impl block for higher level world methods #[profiling::all_functions] impl WorldAccessGuard<'_> { - fn construct_from_script_value( &self, descriptor: impl Into>, @@ -812,8 +823,16 @@ impl WorldAccessGuard<'_> { } /// get a type erased type registration for the type including information about whether it's a component or resource - pub(crate) fn get_type_registration(&self, registration: ScriptTypeRegistration) -> Result>, InteropError> { - + pub(crate) fn get_type_registration( + &self, + registration: ScriptTypeRegistration, + ) -> Result< + Union< + ScriptTypeRegistration, + Union, + >, + InteropError, + > { let registration = match self.get_resource_type(registration)? { Ok(res) => { return Ok(Union::new_right(Union::new_right(res))); @@ -831,14 +850,23 @@ impl WorldAccessGuard<'_> { Ok(Union::new_left(registration)) } - /// Similar to [`Self::get_type_by_name`] but returns a type erased [`ScriptTypeRegistration`], [`ScriptComponentRegistration`] or [`ScriptResourceRegistration`] + /// Similar to [`Self::get_type_by_name`] but returns a type erased [`ScriptTypeRegistration`], [`ScriptComponentRegistration`] or [`ScriptResourceRegistration`] /// depending on the underlying type and state of the world. - pub fn get_type_registration_by_name(&self, type_name: String) -> Result>>, InteropError> { + pub fn get_type_registration_by_name( + &self, + type_name: String, + ) -> Result< + Option< + Union< + ScriptTypeRegistration, + Union, + >, + >, + InteropError, + > { let val = self.get_type_by_name(type_name); Ok(match val { - Some(registration) => { - Some(self.get_type_registration(registration)?) - } + Some(registration) => Some(self.get_type_registration(registration)?), None => None, }) } From 2165c433fc377ab09143337767ed281332ec703c Mon Sep 17 00:00:00 2001 From: makspll Date: Wed, 19 Mar 2025 21:57:26 +0000 Subject: [PATCH 02/11] get something working --- .../new_component_can_be_retrieved.lua | 12 +++ .../src/bindings/script_component.rs | 90 ++++++++++++++----- .../src/bindings/script_system.rs | 10 ++- .../src/bindings/script_value.rs | 3 +- .../src/bindings/world.rs | 48 ++++++++-- crates/bevy_mod_scripting_core/src/lib.rs | 5 +- .../bevy_mod_scripting_functions/src/core.rs | 15 ++++ 7 files changed, 149 insertions(+), 34 deletions(-) create mode 100644 assets/tests/register_new_component/new_component_can_be_retrieved.lua diff --git a/assets/tests/register_new_component/new_component_can_be_retrieved.lua b/assets/tests/register_new_component/new_component_can_be_retrieved.lua new file mode 100644 index 0000000000..b9e6cd777c --- /dev/null +++ b/assets/tests/register_new_component/new_component_can_be_retrieved.lua @@ -0,0 +1,12 @@ +local NewComponent = world.register_new_component("NewComponent") +assert(NewComponent ~= nil, "Failed to register new component") +assert(NewComponent:short_name() == "ScriptComponent", "Unexpected component type") + + +local new_entity = world.spawn() + +world.add_default_component(new_entity, NewComponent) + +local component_intance = world.get_component(new_entity, NewComponent) + +assert(component_intance ~= nil, "Failed to get component instance") \ No newline at end of file diff --git a/crates/bevy_mod_scripting_core/src/bindings/script_component.rs b/crates/bevy_mod_scripting_core/src/bindings/script_component.rs index 4b58a3936a..6a3278986a 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/script_component.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/script_component.rs @@ -1,24 +1,23 @@ //! Everything necessary to support scripts registering their own components -use std::{alloc::Layout, mem::needs_drop, sync::Arc}; - +use super::{ScriptComponentRegistration, ScriptTypeRegistration, ScriptValue, WorldAccessGuard}; +use crate::error::InteropError; use bevy::{ + app::{App, Plugin}, ecs::{ component::{Component, ComponentDescriptor, StorageType}, reflect::ReflectComponent, + system::Resource, }, - reflect::{GetTypeRegistration, Reflect, TypeRegistration}, + reflect::{prelude::ReflectDefault, GetTypeRegistration, Reflect}, utils::HashMap, }; use parking_lot::RwLock; - -use crate::error::InteropError; - -use super::{ScriptComponentRegistration, ScriptTypeRegistration, ScriptValue, WorldGuard}; +use std::{alloc::Layout, mem::needs_drop, sync::Arc}; /// A dynamic script component, with script set -#[derive(Reflect, Clone)] -#[reflect(Component)] +#[derive(Reflect, Clone, Default)] +#[reflect(Component, Default)] pub struct ScriptComponent { data: ScriptValue, } @@ -36,20 +35,40 @@ impl Component for ScriptComponent { } /// A registry of dynamically registered script components +#[derive(Clone, Resource, Default)] pub struct AppScriptComponentRegistry(pub Arc>); +impl AppScriptComponentRegistry { + /// Reads the underlying registry + pub fn read(&self) -> parking_lot::RwLockReadGuard { + self.0.read() + } + + /// Writes to the underlying registry + pub fn write(&self) -> parking_lot::RwLockWriteGuard { + self.0.write() + } +} + +#[derive(Default)] +/// A registry of dynamically registered script components pub struct ScriptComponentRegistry { components: HashMap, } -impl WorldGuard<'_> { - pub fn script_component_registry(&self) -> AppScriptComponentRegistry { - let component_registry = - self.with_resource(|app_component_registry: &AppScriptComponentRegistry| { - app_component_registry.clone() - })?; +impl ScriptComponentRegistry { + /// Registers a dynamic script component, possibly overwriting an existing one + pub fn register(&mut self, info: ScriptComponentInfo) { + self.components.insert(info.name.clone(), info); + } + + /// Gets a dynamic script component by name + pub fn get(&self, name: &str) -> Option<&ScriptComponentInfo> { + self.components.get(name) } +} +impl WorldAccessGuard<'_> { /// Registers a dynamic script component, and returns a reference to its registration pub fn register_script_component( &self, @@ -59,8 +78,9 @@ impl WorldGuard<'_> { let descriptor = unsafe { // Safety: same safety guarantees as ComponentDescriptor::new // we know the type in advance + // we only use this method to name the component ComponentDescriptor::new_with_layout( - component_name, + component_name.clone(), ScriptComponent::STORAGE_TYPE, Layout::new::(), needs_drop::().then_some(|x| x.drop_as::()), @@ -69,17 +89,41 @@ impl WorldGuard<'_> { w.register_component_with_descriptor(descriptor) })?; - // we need to register this as a type in the type registry with this name so its retrievable as any other type - let type_registry = self.type_registry(); - let mut type_registry = type_registry.write(); + let component_registry = self.component_registry(); + let mut component_registry = component_registry.write(); - // TODO: we should probably retrieve this from the registry, but I don't see what people would want to register on this type - // in addition to the existing registrations. - Ok(ScriptComponentRegistration::new( + let registration = ScriptComponentRegistration::new( ScriptTypeRegistration::new(Arc::new( ::get_type_registration(), )), component_id, - )) + ); + + bevy::log::debug!( + "Registering dynamic script component: {}, component id assigned: {:?}", + component_name, + component_id + ); + + let component_info = ScriptComponentInfo { + name: component_name.clone(), + registration: registration.clone(), + }; + + component_registry.register(component_info); + + // TODO: we should probably retrieve this from the registry, but I don't see what people would want to register on this type + // in addition to the existing registrations. + Ok(registration) + } +} + +/// A plugin to support dynamic script components +pub(crate) struct DynamicScriptComponentPlugin; + +impl Plugin for DynamicScriptComponentPlugin { + fn build(&self, app: &mut App) { + app.init_resource::() + .register_type::(); } } diff --git a/crates/bevy_mod_scripting_core/src/bindings/script_system.rs b/crates/bevy_mod_scripting_core/src/bindings/script_system.rs index feaf42be54..f9be183de8 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/script_system.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/script_system.rs @@ -5,8 +5,9 @@ use super::{ function::{from::Val, into::IntoScript, script_function::AppScriptFunctionRegistry}, schedule::AppScheduleRegistry, script_value::ScriptValue, - AppReflectAllocator, ReflectBaseType, ReflectReference, ScriptQueryBuilder, ScriptQueryResult, - ScriptResourceRegistration, WorldAccessGuard, WorldGuard, + AppReflectAllocator, AppScriptComponentRegistry, ReflectBaseType, ReflectReference, + ScriptQueryBuilder, ScriptQueryResult, ScriptResourceRegistration, WorldAccessGuard, + WorldGuard, }; use crate::{ bindings::pretty_print::DisplayWithWorld, @@ -288,6 +289,7 @@ struct ScriptSystemState { type_registry: AppTypeRegistry, function_registry: AppScriptFunctionRegistry, schedule_registry: AppScheduleRegistry, + component_registry: AppScriptComponentRegistry, allocator: AppReflectAllocator, subset: HashSet, callback_label: CallbackLabel, @@ -424,6 +426,7 @@ impl System for DynamicScriptSystem

{ state.allocator.clone(), state.function_registry.clone(), state.schedule_registry.clone(), + state.component_registry.clone(), ) }; @@ -577,6 +580,9 @@ impl System for DynamicScriptSystem

{ .clone(), schedule_registry: world.get_resource_or_init::().clone(), allocator: world.get_resource_or_init::().clone(), + component_registry: world + .get_resource_or_init::() + .clone(), subset, callback_label: self.name.to_string().into(), system_params, diff --git a/crates/bevy_mod_scripting_core/src/bindings/script_value.rs b/crates/bevy_mod_scripting_core/src/bindings/script_value.rs index 306d318a32..c8cbb21e48 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/script_value.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/script_value.rs @@ -13,10 +13,11 @@ use super::{ /// An abstraction of values that can be passed to and from scripts. /// This allows us to re-use logic between scripting languages. -#[derive(Debug, Clone, PartialEq, Reflect)] +#[derive(Debug, Clone, PartialEq, Reflect, Default)] #[reflect(opaque)] pub enum ScriptValue { /// Represents the absence of a value. + #[default] Unit, /// Represents a boolean value. Bool(bool), diff --git a/crates/bevy_mod_scripting_core/src/bindings/world.rs b/crates/bevy_mod_scripting_core/src/bindings/world.rs index 08d49b5651..f8650cef58 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/world.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/world.rs @@ -171,6 +171,7 @@ impl<'w> WorldAccessGuard<'w> { allocator: AppReflectAllocator, function_registry: AppScriptFunctionRegistry, schedule_registry: AppScheduleRegistry, + script_component_registry: AppScriptComponentRegistry, ) -> Self { Self { inner: Rc::new(WorldAccessGuardInner { @@ -184,6 +185,7 @@ impl<'w> WorldAccessGuard<'w> { allocator, function_registry, schedule_registry, + script_component_registry, }), invalid: Rc::new(false.into()), } @@ -206,6 +208,10 @@ impl<'w> WorldAccessGuard<'w> { .get_resource_or_init::() .clone(); + let script_component_registry = world + .get_resource_or_init::() + .clone(); + let schedule_registry = world.get_resource_or_init::().clone(); Self { inner: Rc::new(WorldAccessGuardInner { @@ -215,6 +221,7 @@ impl<'w> WorldAccessGuard<'w> { type_registry, function_registry, schedule_registry, + script_component_registry, }), invalid: Rc::new(false.into()), } @@ -330,6 +337,11 @@ impl<'w> WorldAccessGuard<'w> { self.inner.schedule_registry.clone() } + /// Returns the component registry for the world + pub fn component_registry(&self) -> AppScriptComponentRegistry { + self.inner.script_component_registry.clone() + } + /// Returns the script allocator for the world pub fn allocator(&self) -> AppReflectAllocator { self.inner.allocator.clone() @@ -813,12 +825,12 @@ impl WorldAccessGuard<'_> { } /// get a type registration for the type, without checking if it's a component or resource - pub fn get_type_by_name(&self, type_name: String) -> Option { + pub fn get_type_by_name(&self, type_name: &str) -> Option { let type_registry = self.type_registry(); let type_registry = type_registry.read(); type_registry - .get_with_short_type_path(&type_name) - .or_else(|| type_registry.get_with_type_path(&type_name)) + .get_with_short_type_path(type_name) + .or_else(|| type_registry.get_with_type_path(type_name)) .map(|registration| ScriptTypeRegistration::new(Arc::new(registration.clone()))) } @@ -864,10 +876,17 @@ impl WorldAccessGuard<'_> { >, InteropError, > { - let val = self.get_type_by_name(type_name); + let val = self.get_type_by_name(&type_name); Ok(match val { Some(registration) => Some(self.get_type_registration(registration)?), - None => None, + None => { + // try the component registry + let components = self.component_registry(); + let components = components.read(); + components + .get(&type_name) + .map(|c| Union::new_right(Union::new_left(c.registration.clone()))) + } }) } @@ -921,20 +940,25 @@ impl WorldAccessGuard<'_> { ) })?; + bevy::log::debug!("found component data"); + // we look for ReflectDefault or ReflectFromWorld data then a ReflectComponent data let instance = if let Some(default_td) = registration .type_registration() .type_registration() .data::() { + bevy::log::debug!("found default data"); default_td.default() } else if let Some(from_world_td) = registration .type_registration() .type_registration() .data::() { + bevy::log::debug!("found reflect from world"); self.with_global_access(|world| from_world_td.from_world(world))? } else { + bevy::log::debug!("found neither"); return Err(InteropError::missing_type_data( registration.registration.type_id(), "ReflectDefault or ReflectFromWorld".to_owned(), @@ -950,6 +974,7 @@ impl WorldAccessGuard<'_> { .map_err(|_| InteropError::missing_entity(entity))?; { let registry = type_registry.read(); + bevy::log::debug!("inserting component instance using component data"); component_data.insert(&mut entity, instance.as_partial_reflect(), ®istry); } Ok(()) @@ -1001,12 +1026,23 @@ impl WorldAccessGuard<'_> { .get_entity(entity) .ok_or_else(|| InteropError::missing_entity(entity))?; + bevy::log::debug!("Retrieving component with component id: {:?}", component_id); + let component_info = cell .components() .get_info(component_id) .ok_or_else(|| InteropError::invalid_component(component_id))?; - if entity.contains_id(component_id) { + bevy::log::debug!( + "Retrieved component with component info: {:?}", + component_info + ); + + if entity.contains_id(component_id) + || component_info + .type_id() + .is_some_and(|t| entity.contains_type_id(t)) + { Ok(Some(ReflectReference { base: ReflectBaseType { type_id: component_info.type_id().ok_or_else(|| { diff --git a/crates/bevy_mod_scripting_core/src/lib.rs b/crates/bevy_mod_scripting_core/src/lib.rs index de21ffed62..a244130b61 100644 --- a/crates/bevy_mod_scripting_core/src/lib.rs +++ b/crates/bevy_mod_scripting_core/src/lib.rs @@ -14,7 +14,8 @@ use bindings::{ globals::{core::CoreScriptGlobalsPlugin, AppScriptGlobalsRegistry}, schedule::AppScheduleRegistry, script_value::ScriptValue, - AppReflectAllocator, ReflectAllocator, ReflectReference, ScriptTypeRegistration, + AppReflectAllocator, DynamicScriptComponentPlugin, ReflectAllocator, ReflectReference, + ScriptTypeRegistration, }; use commands::{AddStaticScript, RemoveStaticScript}; use context::{ @@ -312,7 +313,7 @@ fn once_per_app_init(app: &mut App) { ((garbage_collector).in_set(ScriptingSystemSet::GarbageCollection),), ); - app.add_plugins(CoreScriptGlobalsPlugin); + app.add_plugins((CoreScriptGlobalsPlugin, DynamicScriptComponentPlugin)); configure_asset_systems(app); } diff --git a/crates/bevy_mod_scripting_functions/src/core.rs b/crates/bevy_mod_scripting_functions/src/core.rs index bcac621f01..0f7047528f 100644 --- a/crates/bevy_mod_scripting_functions/src/core.rs +++ b/crates/bevy_mod_scripting_functions/src/core.rs @@ -445,6 +445,21 @@ impl World { let world = ctxt.world()?; world.exit() } + + /// Registers a new component type with the world. + /// Arguments: + /// * `ctxt`: The function call context. + /// * `name`: The name of the component type. + /// Returns: + /// * `registration`: The registration of the new component type if successful. + fn register_new_component( + ctxt: FunctionCallContext, + name: String, + ) -> Result, InteropError> { + profiling::function_scope!("register_new_component"); + let world = ctxt.world()?; + world.register_script_component(name).map(Val) + } } #[script_bindings( From 247aec6904ca66535e805ffc670d9cf79021ec83 Mon Sep 17 00:00:00 2001 From: makspll Date: Wed, 19 Mar 2025 23:08:12 +0000 Subject: [PATCH 03/11] make component retrieval work --- .../new_component_can_be_retrieved.lua | 2 +- .../src/bindings/query.rs | 76 ++++++++++++++++++- .../src/bindings/script_component.rs | 60 ++++++++++++++- .../src/bindings/world.rs | 46 +++-------- 4 files changed, 144 insertions(+), 40 deletions(-) diff --git a/assets/tests/register_new_component/new_component_can_be_retrieved.lua b/assets/tests/register_new_component/new_component_can_be_retrieved.lua index b9e6cd777c..48e63df8d4 100644 --- a/assets/tests/register_new_component/new_component_can_be_retrieved.lua +++ b/assets/tests/register_new_component/new_component_can_be_retrieved.lua @@ -1,4 +1,4 @@ -local NewComponent = world.register_new_component("NewComponent") +local NewComponent = world.register_new_component("ScriptComponentA") assert(NewComponent ~= nil, "Failed to register new component") assert(NewComponent:short_name() == "ScriptComponent", "Unexpected component type") diff --git a/crates/bevy_mod_scripting_core/src/bindings/query.rs b/crates/bevy_mod_scripting_core/src/bindings/query.rs index 648b71bd38..10809cb9ca 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/query.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/query.rs @@ -1,18 +1,20 @@ //! Utilities for querying the world. -use super::{with_global_access, ReflectReference, WorldAccessGuard}; +use super::{with_global_access, ReflectReference, ScriptComponent, WorldAccessGuard, WorldGuard}; use crate::error::InteropError; use bevy::{ ecs::{ component::ComponentId, entity::Entity, query::{QueryData, QueryState}, + reflect::ReflectComponent, world::World, }, prelude::{EntityRef, QueryBuilder}, + ptr::OwningPtr, reflect::{ParsedPath, Reflect, TypeRegistration}, }; -use std::{any::TypeId, collections::VecDeque, sync::Arc}; +use std::{any::TypeId, collections::VecDeque, ptr::NonNull, sync::Arc}; /// A reference to a type which is not a `Resource` or `Component`. /// @@ -27,9 +29,13 @@ pub struct ScriptTypeRegistration { /// A reference to a component type's reflection registration. /// /// In general think of this as a handle to a type. +/// +/// Not to be confused with script registered dynamic components, although this can point to a script registered component. pub struct ScriptComponentRegistration { pub(crate) registration: ScriptTypeRegistration, pub(crate) component_id: ComponentId, + /// whether this is a component registered BY a script + pub(crate) is_dynamic_script_component: bool, } #[derive(Clone, Reflect, Debug)] @@ -100,6 +106,8 @@ impl ScriptComponentRegistration { /// Creates a new [`ScriptComponentRegistration`] from a [`ScriptTypeRegistration`] and a [`ComponentId`]. pub fn new(registration: ScriptTypeRegistration, component_id: ComponentId) -> Self { Self { + is_dynamic_script_component: registration.type_id() + == std::any::TypeId::of::(), registration, component_id, } @@ -120,6 +128,70 @@ impl ScriptComponentRegistration { pub fn into_type_registration(self) -> ScriptTypeRegistration { self.registration } + + /// Inserts an instance of this component into the given entity + /// + /// Requires whole world access + pub fn insert_into_entity( + &self, + world: WorldGuard, + entity: Entity, + instance: Box, + ) -> Result<(), InteropError> { + if self.is_dynamic_script_component { + // if dynamic we already know the type i.e. `ScriptComponent` + // so we can just insert it + + world.with_global_access(|world| { + let mut entity = world + .get_entity_mut(entity) + .map_err(|_| InteropError::missing_entity(entity))?; + let mut cast = instance.downcast::().map_err(|v| { + InteropError::type_mismatch(TypeId::of::(), Some(v.type_id())) + })?; + let ptr = (cast.as_mut() as *mut ScriptComponent).cast(); + // Safety: cannot be null as we just created it from a valid reference + let non_null_ptr = unsafe { NonNull::new_unchecked(ptr) }; + // Safety: + // - we know the type is ScriptComponent, as we just created the pointer + // - the box will stay valid for the life of this function, and we do not return the ptr + // - pointer is alligned correctly + let owning_ptr = unsafe { OwningPtr::new(non_null_ptr) }; + // Safety: + // - Owning Ptr is valid as we just created it + // - TODO: do we need to check if ComponentId is from this world? How? + unsafe { entity.insert_by_id(self.component_id, owning_ptr) }; + Ok(()) + })? + } else { + let component_data = self + .type_registration() + .type_registration() + .data::() + .ok_or_else(|| { + InteropError::missing_type_data( + self.registration.type_id(), + "ReflectComponent".to_owned(), + ) + })?; + + bevy::log::debug!("found component data"); + + // TODO: this shouldn't need entire world access it feels + let type_registry = world.type_registry(); + world.with_global_access(|world| { + let mut entity = world + .get_entity_mut(entity) + .map_err(|_| InteropError::missing_entity(entity))?; + { + let registry = type_registry.read(); + bevy::log::debug!("inserting component instance using component data"); + component_data.insert(&mut entity, instance.as_partial_reflect(), ®istry); + } + Ok(()) + })? + } + } } impl std::fmt::Debug for ScriptTypeRegistration { diff --git a/crates/bevy_mod_scripting_core/src/bindings/script_component.rs b/crates/bevy_mod_scripting_core/src/bindings/script_component.rs index 6a3278986a..2cc41eaf9a 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/script_component.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/script_component.rs @@ -6,7 +6,6 @@ use bevy::{ app::{App, Plugin}, ecs::{ component::{Component, ComponentDescriptor, StorageType}, - reflect::ReflectComponent, system::Resource, }, reflect::{prelude::ReflectDefault, GetTypeRegistration, Reflect}, @@ -17,7 +16,7 @@ use std::{alloc::Layout, mem::needs_drop, sync::Arc}; /// A dynamic script component, with script set #[derive(Reflect, Clone, Default)] -#[reflect(Component, Default)] +#[reflect(Default)] pub struct ScriptComponent { data: ScriptValue, } @@ -74,6 +73,14 @@ impl WorldAccessGuard<'_> { &self, component_name: String, ) -> Result { + if !component_name.starts_with("Script") { + return Err(InteropError::unsupported_operation( + None, + None, + "script registered component name must start with 'Script'", + )); + } + let component_id = self.with_global_access(|w| { let descriptor = unsafe { // Safety: same safety guarantees as ComponentDescriptor::new @@ -127,3 +134,52 @@ impl Plugin for DynamicScriptComponentPlugin { .register_type::(); } } + +// #[cfg(test)] +// mod test { +// use std::ptr::NonNull; + +// use super::*; +// use bevy::{ecs::world::World, ptr::OwningPtr}; + +// #[test] +// fn test_script_component() { +// let mut world = World::new(); +// let component_name = "MyScriptComponent"; + +// #[derive(Reflect, Component)] +// struct UnderlyingComponent; + +// // initialize component descriptor dynamically +// let descriptor = unsafe { +// // Safety: same safety guarantees as ComponentDescriptor::new +// // we know the type in advance +// // we only use this method to name the component +// ComponentDescriptor::new_with_layout( +// component_name, +// UnderlyingComponent::STORAGE_TYPE, +// Layout::new::(), +// needs_drop::() +// .then_some(|x| x.drop_as::()), +// ) +// }; + +// // register with the world +// let component_id = world.register_component_with_descriptor(descriptor); + +// // insert into the entity +// let entity = world.spawn_empty().id(); +// let mut entity = world.entity_mut(entity); + +// let value = Box::new(UnderlyingComponent); +// let value_ref = Box::into_raw(value).cast::(); +// let ptr = unsafe { OwningPtr::new(NonNull::new(value_ref).unwrap()) }; +// unsafe { entity.insert_by_id(component_id, ptr) }; + +// // check it gets inserted +// assert!( +// entity.contains_id(component_id), +// "entity does not contain freshly inserted component" +// ) +// } +// } diff --git a/crates/bevy_mod_scripting_core/src/bindings/world.rs b/crates/bevy_mod_scripting_core/src/bindings/world.rs index f8650cef58..145c1e641b 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/world.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/world.rs @@ -24,7 +24,7 @@ use super::{ use crate::{ bindings::{ function::{from::FromScript, from_ref::FromScriptRef}, - with_access_read, with_access_write, + with_access_read, with_access_write, ScriptComponent, }, error::InteropError, reflection_extensions::PartialReflectExt, @@ -928,20 +928,6 @@ impl WorldAccessGuard<'_> { entity: Entity, registration: ScriptComponentRegistration, ) -> Result<(), InteropError> { - // let cell = self.as_unsafe_world_cell()?; - let component_data = registration - .type_registration() - .type_registration() - .data::() - .ok_or_else(|| { - InteropError::missing_type_data( - registration.registration.type_id(), - "ReflectComponent".to_owned(), - ) - })?; - - bevy::log::debug!("found component data"); - // we look for ReflectDefault or ReflectFromWorld data then a ReflectComponent data let instance = if let Some(default_td) = registration .type_registration() @@ -965,20 +951,7 @@ impl WorldAccessGuard<'_> { )); }; - // TODO: this shouldn't need entire world access it feels - self.with_global_access(|world| { - let type_registry = self.type_registry(); - - let mut entity = world - .get_entity_mut(entity) - .map_err(|_| InteropError::missing_entity(entity))?; - { - let registry = type_registry.read(); - bevy::log::debug!("inserting component instance using component data"); - component_data.insert(&mut entity, instance.as_partial_reflect(), ®istry); - } - Ok(()) - })? + registration.insert_into_entity(self.clone(), entity, instance) } /// insert the component into the entity @@ -1038,14 +1011,17 @@ impl WorldAccessGuard<'_> { component_info ); - if entity.contains_id(component_id) - || component_info - .type_id() - .is_some_and(|t| entity.contains_type_id(t)) - { + if entity.contains_id(component_id) { + let type_id = component_info.type_id().or_else(|| { + // check its a script component + component_info + .name() + .starts_with("Script") + .then_some(TypeId::of::()) + }); Ok(Some(ReflectReference { base: ReflectBaseType { - type_id: component_info.type_id().ok_or_else(|| { + type_id: type_id.ok_or_else(|| { InteropError::unsupported_operation( None, None, From 18085478741d950943acc8fe77ef35db7a999f7d Mon Sep 17 00:00:00 2001 From: makspll Date: Thu, 20 Mar 2025 21:00:46 +0000 Subject: [PATCH 04/11] fix unsafeties --- .../new_component_can_be_set.lua | 17 +++ .../src/bindings/function/from_ref.rs | 11 +- .../src/bindings/function/into_ref.rs | 1 + .../src/bindings/query.rs | 7 +- .../src/bindings/script_component.rs | 120 ++++++++++-------- .../src/bindings/world.rs | 35 ++--- crates/bevy_mod_scripting_core/src/error.rs | 4 +- crates/bevy_mod_scripting_core/src/lib.rs | 1 + .../bevy_mod_scripting_functions/src/core.rs | 2 +- .../bevy_mod_scripting_lua/tests/lua_tests.rs | 8 +- 10 files changed, 120 insertions(+), 86 deletions(-) create mode 100644 assets/tests/register_new_component/new_component_can_be_set.lua diff --git a/assets/tests/register_new_component/new_component_can_be_set.lua b/assets/tests/register_new_component/new_component_can_be_set.lua new file mode 100644 index 0000000000..9bb27afd7b --- /dev/null +++ b/assets/tests/register_new_component/new_component_can_be_set.lua @@ -0,0 +1,17 @@ +function on_test() + local NewComponent = world.register_new_component("ScriptComponentA") + + local new_entity = world.spawn() + world.insert_component(new_entity, NewComponent, construct(types.ScriptComponent, { + data = "Hello World" + })) + + local component_instance = world.get_component(new_entity, NewComponent) + assert(component_instance.data == "Hello World", "unexpected value: " .. component_instance.data) + + component_instance.data = { + foo = "bar" + } + + assert(component_instance.data.foo == "bar", "unexpected value: " .. component_instance.data.foo) +end \ No newline at end of file diff --git a/crates/bevy_mod_scripting_core/src/bindings/function/from_ref.rs b/crates/bevy_mod_scripting_core/src/bindings/function/from_ref.rs index bf43ccf5a6..3308392750 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/function/from_ref.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/function/from_ref.rs @@ -1,15 +1,15 @@ //! Contains the [`FromScriptRef`] trait and its implementations. -use std::{any::TypeId, ffi::OsString, path::PathBuf}; -use bevy::reflect::{ - DynamicEnum, DynamicList, DynamicMap, DynamicTuple, DynamicVariant, Map, PartialReflect, -}; use crate::{ - bindings::{match_by_type, WorldGuard, FromScript}, + bindings::{match_by_type, FromScript, WorldGuard}, error::InteropError, reflection_extensions::TypeInfoExtensions, ScriptValue, }; +use bevy::reflect::{ + DynamicEnum, DynamicList, DynamicMap, DynamicTuple, DynamicVariant, Map, PartialReflect, +}; +use std::{any::TypeId, ffi::OsString, path::PathBuf}; /// Converts from a [`ScriptValue`] to a value equivalent to the given [`TypeId`]. /// @@ -56,6 +56,7 @@ impl FromScriptRef for Box { tq : String => return ::from_script(value, world).map(|a| Box::new(a) as _), tr : PathBuf => return ::from_script(value, world).map(|a| Box::new(a) as _), ts : OsString=> return ::from_script(value, world).map(|a| Box::new(a) as _), + tsv: ScriptValue => return ::from_script(value, world).map(|a| Box::new(a) as _), tn : () => return <()>::from_script(value, world).map(|a| Box::new(a) as _) } ); diff --git a/crates/bevy_mod_scripting_core/src/bindings/function/into_ref.rs b/crates/bevy_mod_scripting_core/src/bindings/function/into_ref.rs index 3679c784a2..bdce9f9630 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/function/into_ref.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/function/into_ref.rs @@ -102,6 +102,7 @@ fn into_script_ref( }, tr : PathBuf => return downcast_into_value!(r, PathBuf).clone().into_script(world), ts : OsString=> return downcast_into_value!(r, OsString).clone().into_script(world), + tsv: ScriptValue=> return Ok(downcast_into_value!(r, ScriptValue).clone()), tn : () => return Ok(ScriptValue::Unit) } ); diff --git a/crates/bevy_mod_scripting_core/src/bindings/query.rs b/crates/bevy_mod_scripting_core/src/bindings/query.rs index 10809cb9ca..36e805c03c 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/query.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/query.rs @@ -146,16 +146,19 @@ impl ScriptComponentRegistration { let mut entity = world .get_entity_mut(entity) .map_err(|_| InteropError::missing_entity(entity))?; - let mut cast = instance.downcast::().map_err(|v| { + let cast = instance.downcast::().map_err(|v| { InteropError::type_mismatch(TypeId::of::(), Some(v.type_id())) })?; - let ptr = (cast.as_mut() as *mut ScriptComponent).cast(); + // the reason we leak the box, is because we don't want to double drop the owning ptr + + let ptr = (Box::leak(cast) as *mut ScriptComponent).cast(); // Safety: cannot be null as we just created it from a valid reference let non_null_ptr = unsafe { NonNull::new_unchecked(ptr) }; // Safety: // - we know the type is ScriptComponent, as we just created the pointer // - the box will stay valid for the life of this function, and we do not return the ptr // - pointer is alligned correctly + // - nothing else will call drop on this let owning_ptr = unsafe { OwningPtr::new(non_null_ptr) }; // Safety: // - Owning Ptr is valid as we just created it diff --git a/crates/bevy_mod_scripting_core/src/bindings/script_component.rs b/crates/bevy_mod_scripting_core/src/bindings/script_component.rs index 2cc41eaf9a..6549abd20c 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/script_component.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/script_component.rs @@ -80,8 +80,23 @@ impl WorldAccessGuard<'_> { "script registered component name must start with 'Script'", )); } + let component_registry = self.component_registry(); + let component_registry_read = component_registry.read(); + if component_registry_read.get(&component_name).is_some() { + return Err(InteropError::unsupported_operation( + None, + None, + "script registered component already exists", + )); + } let component_id = self.with_global_access(|w| { + bevy::log::info!( + "components present: {}. script: {}. World id: {:?}", + w.components().len(), + component_registry_read.components.len(), + w.id() + ); let descriptor = unsafe { // Safety: same safety guarantees as ComponentDescriptor::new // we know the type in advance @@ -93,10 +108,11 @@ impl WorldAccessGuard<'_> { needs_drop::().then_some(|x| x.drop_as::()), ) }; - w.register_component_with_descriptor(descriptor) + let o = w.register_component_with_descriptor(descriptor); + bevy::log::info!("components present after: {}", w.components().len()); + o })?; - - let component_registry = self.component_registry(); + drop(component_registry_read); let mut component_registry = component_registry.write(); let registration = ScriptComponentRegistration::new( @@ -135,51 +151,53 @@ impl Plugin for DynamicScriptComponentPlugin { } } -// #[cfg(test)] -// mod test { -// use std::ptr::NonNull; - -// use super::*; -// use bevy::{ecs::world::World, ptr::OwningPtr}; - -// #[test] -// fn test_script_component() { -// let mut world = World::new(); -// let component_name = "MyScriptComponent"; - -// #[derive(Reflect, Component)] -// struct UnderlyingComponent; - -// // initialize component descriptor dynamically -// let descriptor = unsafe { -// // Safety: same safety guarantees as ComponentDescriptor::new -// // we know the type in advance -// // we only use this method to name the component -// ComponentDescriptor::new_with_layout( -// component_name, -// UnderlyingComponent::STORAGE_TYPE, -// Layout::new::(), -// needs_drop::() -// .then_some(|x| x.drop_as::()), -// ) -// }; - -// // register with the world -// let component_id = world.register_component_with_descriptor(descriptor); - -// // insert into the entity -// let entity = world.spawn_empty().id(); -// let mut entity = world.entity_mut(entity); - -// let value = Box::new(UnderlyingComponent); -// let value_ref = Box::into_raw(value).cast::(); -// let ptr = unsafe { OwningPtr::new(NonNull::new(value_ref).unwrap()) }; -// unsafe { entity.insert_by_id(component_id, ptr) }; - -// // check it gets inserted -// assert!( -// entity.contains_id(component_id), -// "entity does not contain freshly inserted component" -// ) -// } -// } +#[cfg(test)] +mod test { + use std::ptr::NonNull; + + use super::*; + use bevy::{ecs::world::World, ptr::OwningPtr}; + + #[test] + fn test_script_component() { + let mut world = World::new(); + let component_name = "MyScriptComponent"; + + #[derive(Reflect, Component)] + struct UnderlyingComponent; + + // initialize component descriptor dynamically + let descriptor = unsafe { + // Safety: same safety guarantees as ComponentDescriptor::new + // we know the type in advance + // we only use this method to name the component + ComponentDescriptor::new_with_layout( + component_name, + UnderlyingComponent::STORAGE_TYPE, + Layout::new::(), + needs_drop::() + .then_some(|x| x.drop_as::()), + ) + }; + + // register with the world + let component_id = world.register_component_with_descriptor(descriptor.clone()); + let component_id_2 = world.register_component_with_descriptor(descriptor); + assert_eq!(component_id, component_id_2); // iam getting a double free for this in scritps somehow + + // insert into the entity + let entity = world.spawn_empty().id(); + let mut entity = world.entity_mut(entity); + + let value = Box::new(UnderlyingComponent); + let value_ref = Box::into_raw(value).cast::(); + let ptr = unsafe { OwningPtr::new(NonNull::new(value_ref).unwrap()) }; + unsafe { entity.insert_by_id(component_id, ptr) }; + + // check it gets inserted + assert!( + entity.contains_id(component_id), + "entity does not contain freshly inserted component" + ) + } +} diff --git a/crates/bevy_mod_scripting_core/src/bindings/world.rs b/crates/bevy_mod_scripting_core/src/bindings/world.rs index 145c1e641b..e6e4fc9183 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/world.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/world.rs @@ -961,31 +961,20 @@ impl WorldAccessGuard<'_> { registration: ScriptComponentRegistration, value: ReflectReference, ) -> Result<(), InteropError> { - let component_data = registration - .type_registration() - .type_registration() - .data::() - .ok_or_else(|| { - InteropError::missing_type_data( - registration.registration.type_id(), - "ReflectComponent".to_owned(), - ) - })?; - - with_global_access!(&self.inner.accesses, "Could not insert element", { - let cell = self.as_unsafe_world_cell()?; - let type_registry = self.type_registry(); - let type_registry = type_registry.read(); - let world_mut = unsafe { cell.world_mut() }; - let mut entity = world_mut - .get_entity_mut(entity) - .map_err(|_| InteropError::missing_entity(entity))?; + let instance = >::from_script_ref( + registration.type_registration().type_id(), + ScriptValue::Reference(value), + self.clone(), + )?; - let ref_ = unsafe { value.reflect_unsafe(self.clone())? }; - component_data.apply_or_insert(&mut entity, ref_, &type_registry); + let reflect = instance.try_into_reflect().map_err(|v| { + InteropError::failed_from_reflect( + Some(registration.type_registration().type_id()), + format!("instance produced by conversion to target type when inserting component is not a full reflect type: {v:?}"), + ) + })?; - Ok(()) - })? + registration.insert_into_entity(self.clone(), entity, reflect) } /// get the component from the entity diff --git a/crates/bevy_mod_scripting_core/src/error.rs b/crates/bevy_mod_scripting_core/src/error.rs index 1c63c64bd0..3e2a104c57 100644 --- a/crates/bevy_mod_scripting_core/src/error.rs +++ b/crates/bevy_mod_scripting_core/src/error.rs @@ -401,10 +401,10 @@ impl InteropError { /// Thrown if a type cannot be converted from reflect, this can happen if the type was unable to /// re-construct itself from a dynamic value. - pub fn failed_from_reflect(type_id: Option, reason: String) -> Self { + pub fn failed_from_reflect(type_id: Option, reason: impl Into) -> Self { Self(Arc::new(InteropErrorInner::FailedFromReflect { type_id, - reason, + reason: reason.into(), })) } diff --git a/crates/bevy_mod_scripting_core/src/lib.rs b/crates/bevy_mod_scripting_core/src/lib.rs index a244130b61..7f39558775 100644 --- a/crates/bevy_mod_scripting_core/src/lib.rs +++ b/crates/bevy_mod_scripting_core/src/lib.rs @@ -252,6 +252,7 @@ fn once_per_app_finalize(app: &mut App) { if app.world().contains_resource::() { return; } + println!("Running init"); app.insert_resource(BMSFinalized); // read extensions from asset settings diff --git a/crates/bevy_mod_scripting_functions/src/core.rs b/crates/bevy_mod_scripting_functions/src/core.rs index 0f7047528f..d63849891b 100644 --- a/crates/bevy_mod_scripting_functions/src/core.rs +++ b/crates/bevy_mod_scripting_functions/src/core.rs @@ -1287,7 +1287,7 @@ impl GlobalNamespace { let reflect_val = val.try_into_reflect().map_err(|_| { InteropError::failed_from_reflect( Some(registration.type_id()), - "Could not construct the type".into(), + "Could not construct the type", ) })?; diff --git a/crates/languages/bevy_mod_scripting_lua/tests/lua_tests.rs b/crates/languages/bevy_mod_scripting_lua/tests/lua_tests.rs index 3fa8b4b75d..cacfd11bb6 100644 --- a/crates/languages/bevy_mod_scripting_lua/tests/lua_tests.rs +++ b/crates/languages/bevy_mod_scripting_lua/tests/lua_tests.rs @@ -14,12 +14,15 @@ use std::{ path::{Path, PathBuf}, }; +#[derive(Debug)] struct Test { path: PathBuf, } impl Test { fn execute(self) -> Result<(), Failed> { + println!("Running test: {:?}", self.path); + execute_integration_test::( |world, type_registry| { let _ = world; @@ -126,10 +129,11 @@ fn main() { let args = Arguments::from_args(); // Create a list of tests and/or benchmarks (in this case: two dummy tests). - let tests = discover_all_tests() + let all_tests = discover_all_tests(); + println!("discovered {} tests. {:?}", all_tests.len(), all_tests); + let tests = all_tests .into_iter() .map(|t| Trial::test(t.name(), move || t.execute())); - // Run all tests and exit the application appropriatly. libtest_mimic::run(&args, tests.collect()).exit(); } From 6a95cf5b859d03e84a2242bcd8f7c7771421d135 Mon Sep 17 00:00:00 2001 From: makspll Date: Thu, 20 Mar 2025 21:17:15 +0000 Subject: [PATCH 05/11] remove logs and add test --- .../src/bindings/query.rs | 3 - .../src/bindings/script_component.rs | 76 ++++++------------- 2 files changed, 22 insertions(+), 57 deletions(-) diff --git a/crates/bevy_mod_scripting_core/src/bindings/query.rs b/crates/bevy_mod_scripting_core/src/bindings/query.rs index 36e805c03c..151dc29dd1 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/query.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/query.rs @@ -178,8 +178,6 @@ impl ScriptComponentRegistration { ) })?; - bevy::log::debug!("found component data"); - // TODO: this shouldn't need entire world access it feels let type_registry = world.type_registry(); world.with_global_access(|world| { @@ -188,7 +186,6 @@ impl ScriptComponentRegistration { .map_err(|_| InteropError::missing_entity(entity))?; { let registry = type_registry.read(); - bevy::log::debug!("inserting component instance using component data"); component_data.insert(&mut entity, instance.as_partial_reflect(), ®istry); } Ok(()) diff --git a/crates/bevy_mod_scripting_core/src/bindings/script_component.rs b/crates/bevy_mod_scripting_core/src/bindings/script_component.rs index 6549abd20c..14c67e7550 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/script_component.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/script_component.rs @@ -91,12 +91,6 @@ impl WorldAccessGuard<'_> { } let component_id = self.with_global_access(|w| { - bevy::log::info!( - "components present: {}. script: {}. World id: {:?}", - w.components().len(), - component_registry_read.components.len(), - w.id() - ); let descriptor = unsafe { // Safety: same safety guarantees as ComponentDescriptor::new // we know the type in advance @@ -108,9 +102,7 @@ impl WorldAccessGuard<'_> { needs_drop::().then_some(|x| x.drop_as::()), ) }; - let o = w.register_component_with_descriptor(descriptor); - bevy::log::info!("components present after: {}", w.components().len()); - o + w.register_component_with_descriptor(descriptor) })?; drop(component_registry_read); let mut component_registry = component_registry.write(); @@ -122,12 +114,6 @@ impl WorldAccessGuard<'_> { component_id, ); - bevy::log::debug!( - "Registering dynamic script component: {}, component id assigned: {:?}", - component_name, - component_id - ); - let component_info = ScriptComponentInfo { name: component_name.clone(), registration: registration.clone(), @@ -153,51 +139,33 @@ impl Plugin for DynamicScriptComponentPlugin { #[cfg(test)] mod test { - use std::ptr::NonNull; - use super::*; - use bevy::{ecs::world::World, ptr::OwningPtr}; + use bevy::ecs::world::World; #[test] fn test_script_component() { let mut world = World::new(); - let component_name = "MyScriptComponent"; - - #[derive(Reflect, Component)] - struct UnderlyingComponent; - - // initialize component descriptor dynamically - let descriptor = unsafe { - // Safety: same safety guarantees as ComponentDescriptor::new - // we know the type in advance - // we only use this method to name the component - ComponentDescriptor::new_with_layout( - component_name, - UnderlyingComponent::STORAGE_TYPE, - Layout::new::(), - needs_drop::() - .then_some(|x| x.drop_as::()), - ) + let registration = { + let guard = WorldAccessGuard::new_exclusive(&mut world); + + guard + .register_script_component("ScriptTest".to_string()) + .unwrap() }; - // register with the world - let component_id = world.register_component_with_descriptor(descriptor.clone()); - let component_id_2 = world.register_component_with_descriptor(descriptor); - assert_eq!(component_id, component_id_2); // iam getting a double free for this in scritps somehow - - // insert into the entity - let entity = world.spawn_empty().id(); - let mut entity = world.entity_mut(entity); - - let value = Box::new(UnderlyingComponent); - let value_ref = Box::into_raw(value).cast::(); - let ptr = unsafe { OwningPtr::new(NonNull::new(value_ref).unwrap()) }; - unsafe { entity.insert_by_id(component_id, ptr) }; - - // check it gets inserted - assert!( - entity.contains_id(component_id), - "entity does not contain freshly inserted component" - ) + let registry = world.get_resource::().unwrap(); + + let registry = registry.read(); + let info = registry.get("ScriptTest").unwrap(); + assert_eq!(info.registration.component_id, registration.component_id); + assert_eq!(info.name, "ScriptTest"); + + // can get the component through the world + let component = world + .components() + .get_info(info.registration.component_id) + .unwrap(); + + assert_eq!(component.name(), "ScriptTest"); } } From 6ec1f4dc4f559aeb8ab429ef5daa051235b0fa85 Mon Sep 17 00:00:00 2001 From: makspll Date: Thu, 20 Mar 2025 22:26:02 +0000 Subject: [PATCH 06/11] more tests and cleanup --- .../tests/has_component/dynamic_component.lua | 6 ++++ .../has_component/dynamic_component.rhai | 6 ++++ .../can_remove_dynamic_component.lua | 11 +++++++ .../can_remove_dynamic_component.rhai | 11 +++++++ .../src/bindings/query.rs | 15 +++++++++ .../src/bindings/script_component.rs | 2 +- .../src/bindings/world.rs | 32 ++----------------- crates/bevy_mod_scripting_core/src/lib.rs | 1 - 8 files changed, 52 insertions(+), 32 deletions(-) create mode 100644 assets/tests/has_component/dynamic_component.lua create mode 100644 assets/tests/has_component/dynamic_component.rhai create mode 100644 assets/tests/remove_component/can_remove_dynamic_component.lua create mode 100644 assets/tests/remove_component/can_remove_dynamic_component.rhai diff --git a/assets/tests/has_component/dynamic_component.lua b/assets/tests/has_component/dynamic_component.lua new file mode 100644 index 0000000000..fc197bb2db --- /dev/null +++ b/assets/tests/has_component/dynamic_component.lua @@ -0,0 +1,6 @@ +local NewComponent = world.register_new_component("ScriptComponentA") +local entity = world.spawn() + +assert(world.has_component(entity, NewComponent) == false, "Entity should not have component") +world.add_default_component(entity, NewComponent) +assert(world.has_component(entity, NewComponent) == true, "Entity should have component") \ No newline at end of file diff --git a/assets/tests/has_component/dynamic_component.rhai b/assets/tests/has_component/dynamic_component.rhai new file mode 100644 index 0000000000..0c628ccaa4 --- /dev/null +++ b/assets/tests/has_component/dynamic_component.rhai @@ -0,0 +1,6 @@ +let NewComponent = world.register_new_component.call("ScriptComponentA"); +let entity = world.spawn_.call(); + +assert(world.has_component.call(entity, NewComponent) == false, "Entity should not have component"); +world.add_default_component.call(entity, NewComponent); +assert(world.has_component.call(entity, NewComponent) == true, "Entity should have component"); \ No newline at end of file diff --git a/assets/tests/remove_component/can_remove_dynamic_component.lua b/assets/tests/remove_component/can_remove_dynamic_component.lua new file mode 100644 index 0000000000..d7eb6204af --- /dev/null +++ b/assets/tests/remove_component/can_remove_dynamic_component.lua @@ -0,0 +1,11 @@ +local NewComponent = world.register_new_component("ScriptComponentA") +local new_entity = world.spawn() +world.add_default_component(new_entity, NewComponent) + +local component_instance = world.get_component(new_entity, NewComponent) +assert(component_instance ~= nil, "unexpected value: " .. tostring(component_instance.data)) + +world.remove_component(new_entity, NewComponent) +local component_instance = world.get_component(new_entity, NewComponent) + +assert(component_instance == nil, "unexpected value: " .. tostring(component_instance)) diff --git a/assets/tests/remove_component/can_remove_dynamic_component.rhai b/assets/tests/remove_component/can_remove_dynamic_component.rhai new file mode 100644 index 0000000000..a7ad664d65 --- /dev/null +++ b/assets/tests/remove_component/can_remove_dynamic_component.rhai @@ -0,0 +1,11 @@ +let NewComponent = world.register_new_component.call("ScriptComponentA"); +let new_entity = world.spawn_.call(); +world.add_default_component.call(new_entity, NewComponent); + +let component_instance = world.get_component.call(new_entity, NewComponent); +assert(type_of(component_instance) != "()", "unexpected value: " + component_instance.data); + +world.remove_component.call(new_entity, NewComponent); +let component_instance_after = world.get_component.call(new_entity, NewComponent); + +assert(type_of(component_instance_after) == "()", "unexpected value: " + component_instance_after); \ No newline at end of file diff --git a/crates/bevy_mod_scripting_core/src/bindings/query.rs b/crates/bevy_mod_scripting_core/src/bindings/query.rs index 151dc29dd1..d71dea8425 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/query.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/query.rs @@ -129,6 +129,21 @@ impl ScriptComponentRegistration { self.registration } + /// Removes an instance of this component from the given entity + pub fn remove_from_entity( + &self, + world: WorldGuard, + entity: Entity, + ) -> Result<(), InteropError> { + world.with_global_access(|world| { + let mut entity = world + .get_entity_mut(entity) + .map_err(|_| InteropError::missing_entity(entity))?; + entity.remove_by_id(self.component_id); + Ok(()) + })? + } + /// Inserts an instance of this component into the given entity /// /// Requires whole world access diff --git a/crates/bevy_mod_scripting_core/src/bindings/script_component.rs b/crates/bevy_mod_scripting_core/src/bindings/script_component.rs index 14c67e7550..e84144833c 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/script_component.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/script_component.rs @@ -14,7 +14,7 @@ use bevy::{ use parking_lot::RwLock; use std::{alloc::Layout, mem::needs_drop, sync::Arc}; -/// A dynamic script component, with script set +/// A dynamic script component #[derive(Reflect, Clone, Default)] #[reflect(Default)] pub struct ScriptComponent { diff --git a/crates/bevy_mod_scripting_core/src/bindings/world.rs b/crates/bevy_mod_scripting_core/src/bindings/world.rs index e6e4fc9183..f4a426fc86 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/world.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/world.rs @@ -34,7 +34,7 @@ use bevy::{ ecs::{ component::{Component, ComponentId}, entity::Entity, - reflect::{AppTypeRegistry, ReflectComponent, ReflectFromWorld, ReflectResource}, + reflect::{AppTypeRegistry, ReflectFromWorld, ReflectResource}, system::{Commands, Resource}, world::{unsafe_world_cell::UnsafeWorldCell, CommandQueue, Mut, World}, }, @@ -934,17 +934,14 @@ impl WorldAccessGuard<'_> { .type_registration() .data::() { - bevy::log::debug!("found default data"); default_td.default() } else if let Some(from_world_td) = registration .type_registration() .type_registration() .data::() { - bevy::log::debug!("found reflect from world"); self.with_global_access(|world| from_world_td.from_world(world))? } else { - bevy::log::debug!("found neither"); return Err(InteropError::missing_type_data( registration.registration.type_id(), "ReflectDefault or ReflectFromWorld".to_owned(), @@ -988,18 +985,11 @@ impl WorldAccessGuard<'_> { .get_entity(entity) .ok_or_else(|| InteropError::missing_entity(entity))?; - bevy::log::debug!("Retrieving component with component id: {:?}", component_id); - let component_info = cell .components() .get_info(component_id) .ok_or_else(|| InteropError::invalid_component(component_id))?; - bevy::log::debug!( - "Retrieved component with component info: {:?}", - component_info - ); - if entity.contains_id(component_id) { let type_id = component_info.type_id().or_else(|| { // check its a script component @@ -1049,25 +1039,7 @@ impl WorldAccessGuard<'_> { entity: Entity, registration: ScriptComponentRegistration, ) -> Result<(), InteropError> { - let component_data = registration - .type_registration() - .type_registration() - .data::() - .ok_or_else(|| { - InteropError::missing_type_data( - registration.registration.type_id(), - "ReflectComponent".to_owned(), - ) - })?; - - // TODO: this shouldn't need entire world access it feels - self.with_global_access(|world| { - let mut entity = world - .get_entity_mut(entity) - .map_err(|_| InteropError::missing_entity(entity))?; - component_data.remove(&mut entity); - Ok(()) - })? + registration.remove_from_entity(self.clone(), entity) } /// get the given resource diff --git a/crates/bevy_mod_scripting_core/src/lib.rs b/crates/bevy_mod_scripting_core/src/lib.rs index 7f39558775..a244130b61 100644 --- a/crates/bevy_mod_scripting_core/src/lib.rs +++ b/crates/bevy_mod_scripting_core/src/lib.rs @@ -252,7 +252,6 @@ fn once_per_app_finalize(app: &mut App) { if app.world().contains_resource::() { return; } - println!("Running init"); app.insert_resource(BMSFinalized); // read extensions from asset settings From 449f3c56c197a0459647254699d177aad867b9b5 Mon Sep 17 00:00:00 2001 From: makspll Date: Thu, 20 Mar 2025 22:31:20 +0000 Subject: [PATCH 07/11] docs --- crates/bevy_mod_scripting_functions/src/core.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/bevy_mod_scripting_functions/src/core.rs b/crates/bevy_mod_scripting_functions/src/core.rs index d63849891b..8473f1aa74 100644 --- a/crates/bevy_mod_scripting_functions/src/core.rs +++ b/crates/bevy_mod_scripting_functions/src/core.rs @@ -447,9 +447,16 @@ impl World { } /// Registers a new component type with the world. + /// + /// The component will behave like any other native component for all intents and purposes. + /// The type that will be instantiated to back this component will be `ScriptComponent` which contains two fields: + /// - `data` + /// + /// This field can be set to any value and modified freely. + /// /// Arguments: /// * `ctxt`: The function call context. - /// * `name`: The name of the component type. + /// * `name`: The name of the component type. The name MUST begin with `Script` or an error will be thrown /// Returns: /// * `registration`: The registration of the new component type if successful. fn register_new_component( From 7292543dfa23872c4fac3e7d0e40faeeb41708c7 Mon Sep 17 00:00:00 2001 From: makspll Date: Thu, 20 Mar 2025 22:40:05 +0000 Subject: [PATCH 08/11] allow any component names --- .../src/bindings/script_component.rs | 7 ---- .../src/bindings/world.rs | 34 +++++-------------- .../bevy_mod_scripting_functions/src/core.rs | 6 ++-- 3 files changed, 11 insertions(+), 36 deletions(-) diff --git a/crates/bevy_mod_scripting_core/src/bindings/script_component.rs b/crates/bevy_mod_scripting_core/src/bindings/script_component.rs index e84144833c..703d34d851 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/script_component.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/script_component.rs @@ -73,13 +73,6 @@ impl WorldAccessGuard<'_> { &self, component_name: String, ) -> Result { - if !component_name.starts_with("Script") { - return Err(InteropError::unsupported_operation( - None, - None, - "script registered component name must start with 'Script'", - )); - } let component_registry = self.component_registry(); let component_registry_read = component_registry.read(); if component_registry_read.get(&component_name).is_some() { diff --git a/crates/bevy_mod_scripting_core/src/bindings/world.rs b/crates/bevy_mod_scripting_core/src/bindings/world.rs index f4a426fc86..79d082a6c1 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/world.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/world.rs @@ -24,7 +24,7 @@ use super::{ use crate::{ bindings::{ function::{from::FromScript, from_ref::FromScriptRef}, - with_access_read, with_access_write, ScriptComponent, + with_access_read, with_access_write, }, error::InteropError, reflection_extensions::PartialReflectExt, @@ -978,39 +978,21 @@ impl WorldAccessGuard<'_> { pub fn get_component( &self, entity: Entity, - component_id: ComponentId, + component_registration: ScriptComponentRegistration, ) -> Result, InteropError> { let cell = self.as_unsafe_world_cell()?; let entity = cell .get_entity(entity) .ok_or_else(|| InteropError::missing_entity(entity))?; - let component_info = cell - .components() - .get_info(component_id) - .ok_or_else(|| InteropError::invalid_component(component_id))?; - - if entity.contains_id(component_id) { - let type_id = component_info.type_id().or_else(|| { - // check its a script component - component_info - .name() - .starts_with("Script") - .then_some(TypeId::of::()) - }); + if entity.contains_id(component_registration.component_id) { Ok(Some(ReflectReference { base: ReflectBaseType { - type_id: type_id.ok_or_else(|| { - InteropError::unsupported_operation( - None, - None, - format!( - "Component {} does not have a type id. Such components are not supported by BMS.", - component_id.display_without_world() - ), - ) - })?, - base_id: ReflectBase::Component(entity.id(), component_id), + type_id: component_registration.type_registration().type_id(), + base_id: ReflectBase::Component( + entity.id(), + component_registration.component_id, + ), }, reflect_path: ParsedPath(vec![]), })) diff --git a/crates/bevy_mod_scripting_functions/src/core.rs b/crates/bevy_mod_scripting_functions/src/core.rs index 8473f1aa74..b81e1bdc81 100644 --- a/crates/bevy_mod_scripting_functions/src/core.rs +++ b/crates/bevy_mod_scripting_functions/src/core.rs @@ -116,7 +116,7 @@ impl World { ) -> Result, InteropError> { profiling::function_scope!("get_component"); let world = ctxt.world()?; - let val = world.get_component(*entity, registration.component_id())?; + let val = world.get_component(*entity, registration.into_inner())?; Ok(val) } @@ -449,14 +449,14 @@ impl World { /// Registers a new component type with the world. /// /// The component will behave like any other native component for all intents and purposes. - /// The type that will be instantiated to back this component will be `ScriptComponent` which contains two fields: + /// The type that will be instantiated to back this component will be `ScriptComponent` which contains just one field: /// - `data` /// /// This field can be set to any value and modified freely. /// /// Arguments: /// * `ctxt`: The function call context. - /// * `name`: The name of the component type. The name MUST begin with `Script` or an error will be thrown + /// * `name`: The name of the component type /// Returns: /// * `registration`: The registration of the new component type if successful. fn register_new_component( From 8bfd0e6c17339a4de50df7903c0d3087b43eb348 Mon Sep 17 00:00:00 2001 From: makspll Date: Thu, 20 Mar 2025 22:47:21 +0000 Subject: [PATCH 09/11] fix errors --- assets/tests/remove_component/no_component_data.lua | 5 +++++ ...o_component_data_errors.rhai => no_component_data.rhai} | 5 ++--- assets/tests/remove_component/no_component_data_errors.lua | 7 ------- 3 files changed, 7 insertions(+), 10 deletions(-) create mode 100644 assets/tests/remove_component/no_component_data.lua rename assets/tests/remove_component/{no_component_data_errors.rhai => no_component_data.rhai} (50%) delete mode 100644 assets/tests/remove_component/no_component_data_errors.lua diff --git a/assets/tests/remove_component/no_component_data.lua b/assets/tests/remove_component/no_component_data.lua new file mode 100644 index 0000000000..c473ca055c --- /dev/null +++ b/assets/tests/remove_component/no_component_data.lua @@ -0,0 +1,5 @@ + +local entity = world._get_entity_with_test_component("CompWithDefault") +local component = world.get_type_by_name("CompWithDefault") +world.remove_component(entity, component) +assert(world.has_component(entity, component) == false, "Component was not removed") diff --git a/assets/tests/remove_component/no_component_data_errors.rhai b/assets/tests/remove_component/no_component_data.rhai similarity index 50% rename from assets/tests/remove_component/no_component_data_errors.rhai rename to assets/tests/remove_component/no_component_data.rhai index 07f8f714a8..bf70543716 100644 --- a/assets/tests/remove_component/no_component_data_errors.rhai +++ b/assets/tests/remove_component/no_component_data.rhai @@ -2,6 +2,5 @@ let entity = world._get_entity_with_test_component.call("CompWithDefault"); let component = world.get_type_by_name.call("CompWithDefault"); -assert_throws(||{ - world.remove_component.call(entity, component); -}, "Missing type data ReflectComponent for type: .*CompWithDefault.*") +world.remove_component.call(entity, component); +assert(world.has_component.call(entity, component) == false, "Component was not removed"); \ No newline at end of file diff --git a/assets/tests/remove_component/no_component_data_errors.lua b/assets/tests/remove_component/no_component_data_errors.lua deleted file mode 100644 index 0dc5c2d780..0000000000 --- a/assets/tests/remove_component/no_component_data_errors.lua +++ /dev/null @@ -1,7 +0,0 @@ - -local entity = world._get_entity_with_test_component("CompWithDefault") -local component = world.get_type_by_name("CompWithDefault") - -assert_throws(function () - world.remove_component(entity, component) -end, "Missing type data ReflectComponent for type: .*CompWithDefault.*") From 8b7506849775d695e3bf63916e7b068de557cc87 Mon Sep 17 00:00:00 2001 From: makspll Date: Thu, 20 Mar 2025 22:58:03 +0000 Subject: [PATCH 10/11] add more crates to paths for mdbook stuff --- .github/workflows/mdbook.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/mdbook.yml b/.github/workflows/mdbook.yml index ab68716504..2d6f1489c5 100644 --- a/.github/workflows/mdbook.yml +++ b/.github/workflows/mdbook.yml @@ -9,6 +9,7 @@ on: - 'docs/**' - 'crates/xtask/**' - '.github/workflows/mdbook.yml' + - 'crates/bevy_mod_scripting_functions/**' pull_request: branches: - "**" @@ -16,6 +17,7 @@ on: - 'docs/**' - 'crates/xtask/**' - '.github/workflows/mdbook.yml' + - 'crates/bevy_mod_scripting_functions/**' jobs: From 0d3bb7a58d73096f5680249113c719c4ed0d7ec7 Mon Sep 17 00:00:00 2001 From: makspll Date: Fri, 21 Mar 2025 08:31:16 +0000 Subject: [PATCH 11/11] fix up tests --- .../new_component_can_be_retrieved.lua | 2 +- .../new_component_can_be_set.lua | 2 +- .../src/bindings/globals/core.rs | 2 ++ .../src/bindings/query.rs | 10 ++++---- .../src/bindings/script_component.rs | 24 +++++++++---------- .../bevy_mod_scripting_functions/src/core.rs | 2 +- 6 files changed, 22 insertions(+), 20 deletions(-) diff --git a/assets/tests/register_new_component/new_component_can_be_retrieved.lua b/assets/tests/register_new_component/new_component_can_be_retrieved.lua index 48e63df8d4..30e66b30ce 100644 --- a/assets/tests/register_new_component/new_component_can_be_retrieved.lua +++ b/assets/tests/register_new_component/new_component_can_be_retrieved.lua @@ -1,6 +1,6 @@ local NewComponent = world.register_new_component("ScriptComponentA") assert(NewComponent ~= nil, "Failed to register new component") -assert(NewComponent:short_name() == "ScriptComponent", "Unexpected component type") +assert(NewComponent:short_name() == "DynamicComponent", "Unexpected component type") local new_entity = world.spawn() diff --git a/assets/tests/register_new_component/new_component_can_be_set.lua b/assets/tests/register_new_component/new_component_can_be_set.lua index 9bb27afd7b..fe4c4458ba 100644 --- a/assets/tests/register_new_component/new_component_can_be_set.lua +++ b/assets/tests/register_new_component/new_component_can_be_set.lua @@ -2,7 +2,7 @@ function on_test() local NewComponent = world.register_new_component("ScriptComponentA") local new_entity = world.spawn() - world.insert_component(new_entity, NewComponent, construct(types.ScriptComponent, { + world.insert_component(new_entity, NewComponent, construct(types.DynamicComponent, { data = "Hello World" })) diff --git a/crates/bevy_mod_scripting_core/src/bindings/globals/core.rs b/crates/bevy_mod_scripting_core/src/bindings/globals/core.rs index 5dee9ee92e..202ae6ea70 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/globals/core.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/globals/core.rs @@ -69,6 +69,8 @@ impl CoreGlobals { /// A cache of types normally available through the `world.get_type_by_name` function. /// /// You can use this to avoid having to store type references. + /// + /// Note that this cache will NOT contain types manually registered by scripts via `register_new_component`. fn types( guard: WorldGuard, ) -> Result< diff --git a/crates/bevy_mod_scripting_core/src/bindings/query.rs b/crates/bevy_mod_scripting_core/src/bindings/query.rs index d71dea8425..33e4ce3c02 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/query.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/query.rs @@ -1,6 +1,6 @@ //! Utilities for querying the world. -use super::{with_global_access, ReflectReference, ScriptComponent, WorldAccessGuard, WorldGuard}; +use super::{with_global_access, DynamicComponent, ReflectReference, WorldAccessGuard, WorldGuard}; use crate::error::InteropError; use bevy::{ ecs::{ @@ -107,7 +107,7 @@ impl ScriptComponentRegistration { pub fn new(registration: ScriptTypeRegistration, component_id: ComponentId) -> Self { Self { is_dynamic_script_component: registration.type_id() - == std::any::TypeId::of::(), + == std::any::TypeId::of::(), registration, component_id, } @@ -161,12 +161,12 @@ impl ScriptComponentRegistration { let mut entity = world .get_entity_mut(entity) .map_err(|_| InteropError::missing_entity(entity))?; - let cast = instance.downcast::().map_err(|v| { - InteropError::type_mismatch(TypeId::of::(), Some(v.type_id())) + let cast = instance.downcast::().map_err(|v| { + InteropError::type_mismatch(TypeId::of::(), Some(v.type_id())) })?; // the reason we leak the box, is because we don't want to double drop the owning ptr - let ptr = (Box::leak(cast) as *mut ScriptComponent).cast(); + let ptr = (Box::leak(cast) as *mut DynamicComponent).cast(); // Safety: cannot be null as we just created it from a valid reference let non_null_ptr = unsafe { NonNull::new_unchecked(ptr) }; // Safety: diff --git a/crates/bevy_mod_scripting_core/src/bindings/script_component.rs b/crates/bevy_mod_scripting_core/src/bindings/script_component.rs index 703d34d851..b1e12f3dc3 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/script_component.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/script_component.rs @@ -17,19 +17,19 @@ use std::{alloc::Layout, mem::needs_drop, sync::Arc}; /// A dynamic script component #[derive(Reflect, Clone, Default)] #[reflect(Default)] -pub struct ScriptComponent { +pub struct DynamicComponent { data: ScriptValue, } /// Some metadata about dynamic script components -pub struct ScriptComponentInfo { +pub struct DynamicComponentInfo { /// The name of the component pub name: String, /// The type registration for the component pub registration: ScriptComponentRegistration, } -impl Component for ScriptComponent { +impl Component for DynamicComponent { const STORAGE_TYPE: StorageType = StorageType::Table; } @@ -52,17 +52,17 @@ impl AppScriptComponentRegistry { #[derive(Default)] /// A registry of dynamically registered script components pub struct ScriptComponentRegistry { - components: HashMap, + components: HashMap, } impl ScriptComponentRegistry { /// Registers a dynamic script component, possibly overwriting an existing one - pub fn register(&mut self, info: ScriptComponentInfo) { + pub fn register(&mut self, info: DynamicComponentInfo) { self.components.insert(info.name.clone(), info); } /// Gets a dynamic script component by name - pub fn get(&self, name: &str) -> Option<&ScriptComponentInfo> { + pub fn get(&self, name: &str) -> Option<&DynamicComponentInfo> { self.components.get(name) } } @@ -90,9 +90,9 @@ impl WorldAccessGuard<'_> { // we only use this method to name the component ComponentDescriptor::new_with_layout( component_name.clone(), - ScriptComponent::STORAGE_TYPE, - Layout::new::(), - needs_drop::().then_some(|x| x.drop_as::()), + DynamicComponent::STORAGE_TYPE, + Layout::new::(), + needs_drop::().then_some(|x| x.drop_as::()), ) }; w.register_component_with_descriptor(descriptor) @@ -102,12 +102,12 @@ impl WorldAccessGuard<'_> { let registration = ScriptComponentRegistration::new( ScriptTypeRegistration::new(Arc::new( - ::get_type_registration(), + ::get_type_registration(), )), component_id, ); - let component_info = ScriptComponentInfo { + let component_info = DynamicComponentInfo { name: component_name.clone(), registration: registration.clone(), }; @@ -126,7 +126,7 @@ pub(crate) struct DynamicScriptComponentPlugin; impl Plugin for DynamicScriptComponentPlugin { fn build(&self, app: &mut App) { app.init_resource::() - .register_type::(); + .register_type::(); } } diff --git a/crates/bevy_mod_scripting_functions/src/core.rs b/crates/bevy_mod_scripting_functions/src/core.rs index b81e1bdc81..a3689932ca 100644 --- a/crates/bevy_mod_scripting_functions/src/core.rs +++ b/crates/bevy_mod_scripting_functions/src/core.rs @@ -449,7 +449,7 @@ impl World { /// Registers a new component type with the world. /// /// The component will behave like any other native component for all intents and purposes. - /// The type that will be instantiated to back this component will be `ScriptComponent` which contains just one field: + /// The type that will be instantiated to back this component will be `DynamicComponent` which contains just one field: /// - `data` /// /// This field can be set to any value and modified freely.