diff --git a/assets/tests/add_system/added_systems_run_in_parallel.lua b/assets/tests/add_system/added_systems_run_in_parallel.lua index deecdf06ad..f5c129280c 100644 --- a/assets/tests/add_system/added_systems_run_in_parallel.lua +++ b/assets/tests/add_system/added_systems_run_in_parallel.lua @@ -25,51 +25,53 @@ digraph { node_0 [label="bevy_asset::assets::Assets::asset_events"]; node_1 [label="bevy_asset::assets::Assets::asset_events"]; node_2 [label="bevy_asset::assets::Assets<()>::asset_events"]; - node_3 [label="bevy_asset::assets::Assets::asset_events"]; - node_4 [label="bevy_mod_scripting_bindings::allocator::garbage_collector"]; - node_5 [label="bevy_mod_scripting_core::handler::script_error_logger"]; - node_6 [label="script_integration_test_harness::dummy_before_post_update_system"]; - node_7 [label="script_integration_test_harness::dummy_post_update_system"]; - node_8 [label="bevy_mod_scripting_core::pipeline::start::filter_script_attachments"]; - node_9 [label="bevy_mod_scripting_core::pipeline::start::filter_script_detachments"]; - node_10 [label="bevy_mod_scripting_core::pipeline::start::filter_script_modifications"]; - node_11 [label="bevy_mod_scripting_core::pipeline::start::process_attachments"]; - node_12 [label="bevy_mod_scripting_core::pipeline::start::process_detachments"]; - node_13 [label="bevy_mod_scripting_core::pipeline::start::process_asset_modifications"]; - node_14 [label="bevy_mod_scripting_core::pipeline::automatic_pipeline_runner"]; - node_15 [label="on_test_post_update"]; - node_16 [label="custom_system_a"]; - node_17 [label="custom_system_b"]; - node_18 [label="SystemSet AssetEvents"]; - node_19 [label="SystemSet GarbageCollection"]; - node_20 [label="SystemSet ListeningPhase"]; - node_21 [label="SystemSet MachineStartPhase"]; - node_22 [label="SystemSet ScriptSystem(custom_system_a)"]; - node_23 [label="SystemSet ScriptSystem(custom_system_b)"]; - node_0 -> node_18 [color=red, label="child of", arrowhead=diamond]; - node_1 -> node_18 [color=red, label="child of", arrowhead=diamond]; - node_2 -> node_18 [color=red, label="child of", arrowhead=diamond]; - node_3 -> node_18 [color=red, label="child of", arrowhead=diamond]; + node_3 [label="bevy_asset::assets::Assets::asset_events"]; + node_4 [label="bevy_asset::assets::Assets::asset_events"]; + node_5 [label="bevy_mod_scripting_bindings::allocator::garbage_collector"]; + node_6 [label="bevy_mod_scripting_core::handler::script_error_logger"]; + node_7 [label="script_integration_test_harness::dummy_before_post_update_system"]; + node_8 [label="script_integration_test_harness::dummy_post_update_system"]; + node_9 [label="bevy_mod_scripting_core::pipeline::start::filter_script_attachments"]; + node_10 [label="bevy_mod_scripting_core::pipeline::start::filter_script_detachments"]; + node_11 [label="bevy_mod_scripting_core::pipeline::start::filter_script_modifications"]; + node_12 [label="bevy_mod_scripting_core::pipeline::start::process_attachments"]; + node_13 [label="bevy_mod_scripting_core::pipeline::start::process_detachments"]; + node_14 [label="bevy_mod_scripting_core::pipeline::start::process_asset_modifications"]; + node_15 [label="bevy_mod_scripting_core::pipeline::automatic_pipeline_runner"]; + node_16 [label="on_test_post_update"]; + node_17 [label="custom_system_a"]; + node_18 [label="custom_system_b"]; + node_19 [label="SystemSet AssetEvents"]; + node_20 [label="SystemSet GarbageCollection"]; + node_21 [label="SystemSet ListeningPhase"]; + node_22 [label="SystemSet MachineStartPhase"]; + node_23 [label="SystemSet ScriptSystem(custom_system_a)"]; + node_24 [label="SystemSet ScriptSystem(custom_system_b)"]; + node_0 -> node_19 [color=red, label="child of", arrowhead=diamond]; + node_1 -> node_19 [color=red, label="child of", arrowhead=diamond]; + node_2 -> node_19 [color=red, label="child of", arrowhead=diamond]; + node_3 -> node_19 [color=red, label="child of", arrowhead=diamond]; node_4 -> node_19 [color=red, label="child of", arrowhead=diamond]; - node_8 -> node_20 [color=red, label="child of", arrowhead=diamond]; - node_9 -> node_20 [color=red, label="child of", arrowhead=diamond]; - node_10 -> node_20 [color=red, label="child of", arrowhead=diamond]; + node_5 -> node_20 [color=red, label="child of", arrowhead=diamond]; + node_9 -> node_21 [color=red, label="child of", arrowhead=diamond]; + node_10 -> node_21 [color=red, label="child of", arrowhead=diamond]; node_11 -> node_21 [color=red, label="child of", arrowhead=diamond]; - node_12 -> node_21 [color=red, label="child of", arrowhead=diamond]; - node_13 -> node_21 [color=red, label="child of", arrowhead=diamond]; - node_16 -> node_22 [color=red, label="child of", arrowhead=diamond]; + node_12 -> node_22 [color=red, label="child of", arrowhead=diamond]; + node_13 -> node_22 [color=red, label="child of", arrowhead=diamond]; + node_14 -> node_22 [color=red, label="child of", arrowhead=diamond]; node_17 -> node_23 [color=red, label="child of", arrowhead=diamond]; - node_6 -> node_7 [color=blue, label="runs before", arrowhead=normal]; - node_8 -> node_9 [color=blue, label="runs before", arrowhead=normal]; - node_8 -> node_9 [color=blue, label="runs before", arrowhead=normal]; - node_8 -> node_10 [color=blue, label="runs before", arrowhead=normal]; + node_18 -> node_24 [color=red, label="child of", arrowhead=diamond]; + node_7 -> node_8 [color=blue, label="runs before", arrowhead=normal]; node_9 -> node_10 [color=blue, label="runs before", arrowhead=normal]; - node_11 -> node_12 [color=blue, label="runs before", arrowhead=normal]; + node_9 -> node_10 [color=blue, label="runs before", arrowhead=normal]; + node_9 -> node_11 [color=blue, label="runs before", arrowhead=normal]; + node_10 -> node_11 [color=blue, label="runs before", arrowhead=normal]; node_12 -> node_13 [color=blue, label="runs before", arrowhead=normal]; - node_15 -> node_16 [color=blue, label="runs before", arrowhead=normal]; - node_15 -> node_17 [color=blue, label="runs before", arrowhead=normal]; - node_20 -> node_21 [color=blue, label="runs before", arrowhead=normal]; - node_21 -> node_14 [color=blue, label="runs before", arrowhead=normal]; + node_13 -> node_14 [color=blue, label="runs before", arrowhead=normal]; + node_16 -> node_17 [color=blue, label="runs before", arrowhead=normal]; + node_16 -> node_18 [color=blue, label="runs before", arrowhead=normal]; + node_21 -> node_22 [color=blue, label="runs before", arrowhead=normal]; + node_22 -> node_15 [color=blue, label="runs before", arrowhead=normal]; } ]] assert_str_eq(dot_graph, expected_dot_graph, "Expected the schedule graph to match the expected graph") diff --git a/assets/tests/add_system/added_systems_run_in_parallel.rhai b/assets/tests/add_system/added_systems_run_in_parallel.rhai index ef83796407..b87e5e75a9 100644 --- a/assets/tests/add_system/added_systems_run_in_parallel.rhai +++ b/assets/tests/add_system/added_systems_run_in_parallel.rhai @@ -24,51 +24,53 @@ digraph { node_0 [label="bevy_asset::assets::Assets::asset_events"]; node_1 [label="bevy_asset::assets::Assets::asset_events"]; node_2 [label="bevy_asset::assets::Assets<()>::asset_events"]; - node_3 [label="bevy_asset::assets::Assets::asset_events"]; - node_4 [label="bevy_mod_scripting_bindings::allocator::garbage_collector"]; - node_5 [label="bevy_mod_scripting_core::handler::script_error_logger"]; - node_6 [label="script_integration_test_harness::dummy_before_post_update_system"]; - node_7 [label="script_integration_test_harness::dummy_post_update_system"]; - node_8 [label="bevy_mod_scripting_core::pipeline::start::filter_script_attachments"]; - node_9 [label="bevy_mod_scripting_core::pipeline::start::filter_script_detachments"]; - node_10 [label="bevy_mod_scripting_core::pipeline::start::filter_script_modifications"]; - node_11 [label="bevy_mod_scripting_core::pipeline::start::process_attachments"]; - node_12 [label="bevy_mod_scripting_core::pipeline::start::process_detachments"]; - node_13 [label="bevy_mod_scripting_core::pipeline::start::process_asset_modifications"]; - node_14 [label="bevy_mod_scripting_core::pipeline::automatic_pipeline_runner"]; - node_15 [label="on_test_post_update"]; - node_16 [label="custom_system_a"]; - node_17 [label="custom_system_b"]; - node_18 [label="SystemSet AssetEvents"]; - node_19 [label="SystemSet GarbageCollection"]; - node_20 [label="SystemSet ListeningPhase"]; - node_21 [label="SystemSet MachineStartPhase"]; - node_22 [label="SystemSet ScriptSystem(custom_system_a)"]; - node_23 [label="SystemSet ScriptSystem(custom_system_b)"]; - node_0 -> node_18 [color=red, label="child of", arrowhead=diamond]; - node_1 -> node_18 [color=red, label="child of", arrowhead=diamond]; - node_2 -> node_18 [color=red, label="child of", arrowhead=diamond]; - node_3 -> node_18 [color=red, label="child of", arrowhead=diamond]; + node_3 [label="bevy_asset::assets::Assets::asset_events"]; + node_4 [label="bevy_asset::assets::Assets::asset_events"]; + node_5 [label="bevy_mod_scripting_bindings::allocator::garbage_collector"]; + node_6 [label="bevy_mod_scripting_core::handler::script_error_logger"]; + node_7 [label="script_integration_test_harness::dummy_before_post_update_system"]; + node_8 [label="script_integration_test_harness::dummy_post_update_system"]; + node_9 [label="bevy_mod_scripting_core::pipeline::start::filter_script_attachments"]; + node_10 [label="bevy_mod_scripting_core::pipeline::start::filter_script_detachments"]; + node_11 [label="bevy_mod_scripting_core::pipeline::start::filter_script_modifications"]; + node_12 [label="bevy_mod_scripting_core::pipeline::start::process_attachments"]; + node_13 [label="bevy_mod_scripting_core::pipeline::start::process_detachments"]; + node_14 [label="bevy_mod_scripting_core::pipeline::start::process_asset_modifications"]; + node_15 [label="bevy_mod_scripting_core::pipeline::automatic_pipeline_runner"]; + node_16 [label="on_test_post_update"]; + node_17 [label="custom_system_a"]; + node_18 [label="custom_system_b"]; + node_19 [label="SystemSet AssetEvents"]; + node_20 [label="SystemSet GarbageCollection"]; + node_21 [label="SystemSet ListeningPhase"]; + node_22 [label="SystemSet MachineStartPhase"]; + node_23 [label="SystemSet ScriptSystem(custom_system_a)"]; + node_24 [label="SystemSet ScriptSystem(custom_system_b)"]; + node_0 -> node_19 [color=red, label="child of", arrowhead=diamond]; + node_1 -> node_19 [color=red, label="child of", arrowhead=diamond]; + node_2 -> node_19 [color=red, label="child of", arrowhead=diamond]; + node_3 -> node_19 [color=red, label="child of", arrowhead=diamond]; node_4 -> node_19 [color=red, label="child of", arrowhead=diamond]; - node_8 -> node_20 [color=red, label="child of", arrowhead=diamond]; - node_9 -> node_20 [color=red, label="child of", arrowhead=diamond]; - node_10 -> node_20 [color=red, label="child of", arrowhead=diamond]; + node_5 -> node_20 [color=red, label="child of", arrowhead=diamond]; + node_9 -> node_21 [color=red, label="child of", arrowhead=diamond]; + node_10 -> node_21 [color=red, label="child of", arrowhead=diamond]; node_11 -> node_21 [color=red, label="child of", arrowhead=diamond]; - node_12 -> node_21 [color=red, label="child of", arrowhead=diamond]; - node_13 -> node_21 [color=red, label="child of", arrowhead=diamond]; - node_16 -> node_22 [color=red, label="child of", arrowhead=diamond]; + node_12 -> node_22 [color=red, label="child of", arrowhead=diamond]; + node_13 -> node_22 [color=red, label="child of", arrowhead=diamond]; + node_14 -> node_22 [color=red, label="child of", arrowhead=diamond]; node_17 -> node_23 [color=red, label="child of", arrowhead=diamond]; - node_6 -> node_7 [color=blue, label="runs before", arrowhead=normal]; - node_8 -> node_9 [color=blue, label="runs before", arrowhead=normal]; - node_8 -> node_9 [color=blue, label="runs before", arrowhead=normal]; - node_8 -> node_10 [color=blue, label="runs before", arrowhead=normal]; + node_18 -> node_24 [color=red, label="child of", arrowhead=diamond]; + node_7 -> node_8 [color=blue, label="runs before", arrowhead=normal]; node_9 -> node_10 [color=blue, label="runs before", arrowhead=normal]; - node_11 -> node_12 [color=blue, label="runs before", arrowhead=normal]; + node_9 -> node_10 [color=blue, label="runs before", arrowhead=normal]; + node_9 -> node_11 [color=blue, label="runs before", arrowhead=normal]; + node_10 -> node_11 [color=blue, label="runs before", arrowhead=normal]; node_12 -> node_13 [color=blue, label="runs before", arrowhead=normal]; - node_15 -> node_16 [color=blue, label="runs before", arrowhead=normal]; - node_15 -> node_17 [color=blue, label="runs before", arrowhead=normal]; - node_20 -> node_21 [color=blue, label="runs before", arrowhead=normal]; - node_21 -> node_14 [color=blue, label="runs before", arrowhead=normal]; + node_13 -> node_14 [color=blue, label="runs before", arrowhead=normal]; + node_16 -> node_17 [color=blue, label="runs before", arrowhead=normal]; + node_16 -> node_18 [color=blue, label="runs before", arrowhead=normal]; + node_21 -> node_22 [color=blue, label="runs before", arrowhead=normal]; + node_22 -> node_15 [color=blue, label="runs before", arrowhead=normal]; }`; assert_str_eq.call(dot_graph, expected_dot_graph, "Expected the schedule graph to match the expected graph"); diff --git a/assets/tests/asset_operations/asset_operations.lua b/assets/tests/asset_operations/asset_operations.lua new file mode 100644 index 0000000000..d77fe4a05b --- /dev/null +++ b/assets/tests/asset_operations/asset_operations.lua @@ -0,0 +1,10 @@ +function on_test() + local test_handle = create_test_asset(42, "TestAssetName") + + assert(test_handle ~= nil, "Test asset handle should not be nil") + assert(world.has_asset(test_handle) == true, "has_asset should return true") + + local retrieved_asset = world.get_asset(test_handle, types.TestAsset) + assert(retrieved_asset.value == 42, "Asset value should be 42") + assert(retrieved_asset.name == "TestAssetName", "Asset name should be 'TestAssetName'") +end diff --git a/assets/tests/asset_operations/asset_operations.rhai b/assets/tests/asset_operations/asset_operations.rhai new file mode 100644 index 0000000000..6a82caac84 --- /dev/null +++ b/assets/tests/asset_operations/asset_operations.rhai @@ -0,0 +1,10 @@ +fn on_test() { + let test_handle = create_test_asset(42, "TestAssetName"); + + assert(test_handle != (), "Test asset handle should not be nil"); + assert(world.has_asset.call(test_handle) == true, "has_asset should return true"); + + let retrieved_asset = world.get_asset.call(test_handle, types.TestAsset); + assert(retrieved_asset.value == 42, "Asset value should be 42"); + assert(retrieved_asset.name == "TestAssetName", "Asset name should be 'TestAssetName'"); +} \ No newline at end of file diff --git a/crates/bevy_mod_scripting_bindings/src/access_map.rs b/crates/bevy_mod_scripting_bindings/src/access_map.rs index efa1524d5e..5ff6961e40 100644 --- a/crates/bevy_mod_scripting_bindings/src/access_map.rs +++ b/crates/bevy_mod_scripting_bindings/src/access_map.rs @@ -223,6 +223,7 @@ impl ReflectAccessId { ReflectBase::Resource(id) => Self::for_component_id(id), ReflectBase::Component(_, id) => Self::for_component_id(id), ReflectBase::Owned(id) => Self::for_allocation(id), + ReflectBase::Asset(_, assets_resource_id) => Self::for_component_id(assets_resource_id), } } } diff --git a/crates/bevy_mod_scripting_bindings/src/reference.rs b/crates/bevy_mod_scripting_bindings/src/reference.rs index a9f4a08f80..664cabcd43 100644 --- a/crates/bevy_mod_scripting_bindings/src/reference.rs +++ b/crates/bevy_mod_scripting_bindings/src/reference.rs @@ -10,6 +10,7 @@ use crate::{ 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}; use bevy_mod_scripting_derive::DebugWithTypeInfo; use bevy_mod_scripting_display::{ @@ -252,6 +253,90 @@ impl ReflectReference { } } + /// Create a new reference to an asset by untyped handle. + /// If the type id is incorrect, you will get runtime errors when trying to access the value. + pub fn new_asset_ref( + handle: UntypedHandle, + asset_type_id: TypeId, + world: WorldGuard, + ) -> Result { + Ok(Self { + base: ReflectBaseType::new_asset_base(handle, asset_type_id, world)?, + reflect_path: ParsedPath(Vec::default()), + }) + } + + /// Tries get an untyped asset handle from this reference. + pub fn try_untyped_asset_handle( + &self, + world: WorldGuard, + ) -> Result { + let handle_type_id = self.tail_type_id(world.clone())?.ok_or_else(|| { + InteropError::invariant("Cannot determine handle type ID from reflection") + .with_context("Asset handle reflection failed - handle may be invalid or corrupted") + })?; + + let type_registry = world.type_registry(); + let type_registry = type_registry.read(); + let reflect_handle = type_registry + .get_type_data::(handle_type_id) + .ok_or_else(|| { + InteropError::missing_type_data( + handle_type_id, + stringify!(ReflectHandle).into(), + ) + .with_context("Handle type is not registered for asset operations - ensure that you registered it with bevy::App::register_asset_reflect::()") + })?; + + let untyped_handle = self.with_reflect(world.clone(), |reflect| { + let reflect_any = reflect.try_as_reflect().ok_or_else(|| { + InteropError::unsupported_operation( + Some(handle_type_id), + None, + "Asset handle must implement Reflect trait for asset operations", + ) + })?; + + reflect_handle + .downcast_handle_untyped(reflect_any.as_any()) + .ok_or_else(|| { + InteropError::could_not_downcast(self.clone(), handle_type_id).with_context( + "UntypedHandle downcast failed - handle may be of wrong type or corrupted", + ) + }) + })??; + Ok(untyped_handle) + } + + /// Get asset from world and return a mutable reference to it + unsafe fn load_asset_mut<'w>( + &self, + handle: &UntypedHandle, + world: WorldGuard<'w>, + ) -> Result<&'w mut dyn Reflect, InteropError> { + let type_registry = world.type_registry(); + let type_registry = type_registry.read(); + + let reflect_asset: &ReflectAsset = type_registry + .get_type_data(self.base.type_id) + .ok_or_else(|| InteropError::unregistered_base(self.base.clone()))?; + + let world_cell = world.as_unsafe_world_cell()?; + // Safety: The caller guarantees exclusive access to the asset through the WorldGuard, + // and we've validated that the type_id matches the ReflectAsset type data. + // The UnsafeWorldCell is valid for the lifetime 'w of the WorldGuard. + let asset = unsafe { reflect_asset.get_unchecked_mut(world_cell, handle.clone()) } + .ok_or_else(|| { + InteropError::unsupported_operation( + Some(self.base.type_id), + None, + "Asset not loaded or handle is invalid", + ) + })?; + + Ok(asset) + } + /// Indexes into the reflect path inside this reference. /// You can use [`Self::reflect`] and [`Self::reflect_mut`] to get the actual value. pub fn index_path>(&mut self, index: T) { @@ -399,6 +484,11 @@ impl ReflectReference { return self.walk_path(unsafe { &*arc.get_ptr() }); } + if let ReflectBase::Asset(handle, _) = &self.base.base_id { + let asset = unsafe { self.load_asset_mut(handle, world.clone())? }; + return self.walk_path(asset.as_partial_reflect()); + } + let type_registry = world.type_registry(); let type_registry = type_registry.read(); @@ -454,6 +544,11 @@ impl ReflectReference { return self.walk_path_mut(unsafe { &mut *arc.get_ptr() }); }; + if let ReflectBase::Asset(handle, _) = &self.base.base_id { + let asset = unsafe { self.load_asset_mut(handle, world.clone())? }; + return self.walk_path_mut(asset.as_partial_reflect_mut()); + }; + let type_registry = world.type_registry(); let type_registry = type_registry.read(); @@ -589,6 +684,47 @@ impl ReflectBaseType { )), } } + + /// Create a new reflection base pointing to an asset with untyped handle + pub fn new_asset_base( + handle: UntypedHandle, + asset_type_id: TypeId, + world: WorldGuard, + ) -> Result { + // We need to get the Assets resource ComponentId by type registry lookup + let type_registry = world.type_registry(); + let type_registry = type_registry.read(); + + // Get the ReflectAsset data to find the Assets resource type ID + let reflect_asset: &ReflectAsset = + type_registry.get_type_data(asset_type_id).ok_or_else(|| { + InteropError::unsupported_operation( + Some(asset_type_id), + None, + "Asset type is not registered with ReflectAsset type data", + ) + })?; + + let assets_resource_type_id = reflect_asset.assets_resource_type_id(); + + // Convert the TypeId to ComponentId via unsafe world cell + let world_cell = world.as_unsafe_world_cell()?; + let components = world_cell.components(); + let assets_resource_id = components + .get_resource_id(assets_resource_type_id) + .ok_or_else(|| { + InteropError::unsupported_operation( + Some(assets_resource_type_id), + None, + "Assets resource is not registered in the world", + ) + })?; + + Ok(Self { + type_id: asset_type_id, + base_id: ReflectBase::Asset(handle, assets_resource_id), + }) + } } /// The Id of the kind of reflection base being pointed to @@ -599,8 +735,10 @@ pub enum ReflectBase { Component(Entity, ComponentId), /// A resource Resource(ComponentId), - /// an allocation + /// An allocation Owned(ReflectAllocationId), + /// An asset accessed by handle + Asset(UntypedHandle, ComponentId), } impl DisplayWithTypeInfo for ReflectBase { @@ -655,6 +793,13 @@ impl DisplayWithTypeInfo for ReflectBase { WithTypeInfo::new_with_opt_info(id, type_info_provider) .display_with_type_info(f, type_info_provider) } + ReflectBase::Asset(handle, assets_resource_id) => { + f.write_str("asset with handle: ")?; + write!(f, "{handle:?}")?; + f.write_str(", in Assets resource: ")?; + WithTypeInfo::new_with_opt_info(assets_resource_id, type_info_provider) + .display_with_type_info(f, type_info_provider) + } } } } diff --git a/crates/bevy_mod_scripting_display/Cargo.toml b/crates/bevy_mod_scripting_display/Cargo.toml index 14c2cc29c6..5971727727 100644 --- a/crates/bevy_mod_scripting_display/Cargo.toml +++ b/crates/bevy_mod_scripting_display/Cargo.toml @@ -13,6 +13,7 @@ readme.workspace = true [dependencies] bevy_reflect = { workspace = true } +bevy_asset = { workspace = true } bevy_ecs = { workspace = true, features = ["bevy_reflect"] } bevy_platform = { workspace = true } parking_lot = { workspace = true } diff --git a/crates/bevy_mod_scripting_display/src/impls/bevy_asset.rs b/crates/bevy_mod_scripting_display/src/impls/bevy_asset.rs new file mode 100644 index 0000000000..8f5c2bfe2c --- /dev/null +++ b/crates/bevy_mod_scripting_display/src/impls/bevy_asset.rs @@ -0,0 +1,9 @@ +impl crate::DebugWithTypeInfo for bevy_asset::UntypedHandle { + fn to_string_with_type_info( + &self, + f: &mut std::fmt::Formatter<'_>, + _type_info_provider: Option<&dyn crate::GetTypeInfo>, + ) -> std::fmt::Result { + write!(f, "{self:?}") + } +} diff --git a/crates/bevy_mod_scripting_display/src/impls/mod.rs b/crates/bevy_mod_scripting_display/src/impls/mod.rs index 6a22bf2bb2..ea627420e3 100644 --- a/crates/bevy_mod_scripting_display/src/impls/mod.rs +++ b/crates/bevy_mod_scripting_display/src/impls/mod.rs @@ -1,3 +1,4 @@ +mod bevy_asset; mod bevy_ecs; mod bevy_platform; mod bevy_reflect; diff --git a/crates/bevy_mod_scripting_functions/src/core.rs b/crates/bevy_mod_scripting_functions/src/core.rs index 8fc5c8bdaa..c3ffd8584a 100644 --- a/crates/bevy_mod_scripting_functions/src/core.rs +++ b/crates/bevy_mod_scripting_functions/src/core.rs @@ -512,6 +512,39 @@ impl World { let world = ctxt.world()?; world.register_script_component(name).map(Val) } + + /// Retrieves an asset by its handle and asset type registration. + /// + /// Arguments: + /// * `ctxt`: The function call context. + /// * `handle_reference`: The handle to the asset (as a reflect reference). + /// * `registration`: The type registration of the asset type. + /// Returns: + /// * `asset`: The asset reference, if the asset is loaded. + fn get_asset( + ctxt: FunctionCallContext, + handle_reference: ReflectReference, + registration: Val, + ) -> Result, InteropError> { + profiling::function_scope!("get_asset"); + let untyped_handle = handle_reference.try_untyped_asset_handle(ctxt.world()?)?; + Ok(Some(ReflectReference::new_asset_ref( + untyped_handle, + registration.type_id(), + ctxt.world()?, + )?)) + } + + /// Checks if can get asset handle + fn has_asset( + ctxt: FunctionCallContext, + handle_reference: ReflectReference, + ) -> Result { + profiling::function_scope!("has_asset"); + Ok(handle_reference + .try_untyped_asset_handle(ctxt.world()?) + .is_ok()) + } } #[script_bindings( diff --git a/crates/testing_crates/script_integration_test_harness/src/test_functions.rs b/crates/testing_crates/script_integration_test_harness/src/test_functions.rs index 5c4ed1c694..1070b3eaba 100644 --- a/crates/testing_crates/script_integration_test_harness/src/test_functions.rs +++ b/crates/testing_crates/script_integration_test_harness/src/test_functions.rs @@ -5,7 +5,8 @@ use std::{ use ::{ bevy_app::App, - bevy_ecs::{component::ComponentId, entity::Entity, world::World}, + bevy_asset::Assets, + bevy_ecs::{change_detection::Mut, component::ComponentId, entity::Entity, world::World}, bevy_reflect::{Reflect, TypeRegistration}, }; use bevy_mod_scripting_asset::Language; @@ -20,7 +21,7 @@ use bevy_mod_scripting_bindings::{ }; use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha12Rng; -use test_utils::test_data::EnumerateTestComponents; +use test_utils::test_data::{EnumerateTestComponents, TestAsset}; // lazy lock rng state pub static RNG: std::sync::LazyLock> = std::sync::LazyLock::new(|| { @@ -146,5 +147,18 @@ pub fn register_test_functions(world: &mut App) { reason.unwrap_or_default() ) }, + ) + .register( + "create_test_asset", + |s: FunctionCallContext, value: i32, name: String| { + let world = s.world()?; + let test_asset = TestAsset::new(value, name); + let handle = world.with_resource_mut(|mut assets: Mut>| { + assets.add(test_asset) + })?; + let allocator = world.allocator(); + let mut allocator = allocator.write(); + Ok(ReflectReference::new_allocated(handle, &mut allocator)) + }, ); } diff --git a/crates/testing_crates/test_utils/src/test_data.rs b/crates/testing_crates/test_utils/src/test_data.rs index 5cadbaf63f..e25ce138be 100644 --- a/crates/testing_crates/test_utils/src/test_data.rs +++ b/crates/testing_crates/test_utils/src/test_data.rs @@ -6,7 +6,7 @@ use bevy_log::LogPlugin; use bevy_time::TimePlugin; use ::{ - bevy_asset::AssetPlugin, + bevy_asset::{Asset, AssetApp, AssetPlugin}, bevy_diagnostic::DiagnosticsPlugin, bevy_ecs::{component::*, prelude::*, world::World}, bevy_reflect::{prelude::*, *}, @@ -27,6 +27,18 @@ impl TestComponent { } } +#[derive(Asset, Reflect, PartialEq, Debug, Clone)] +pub struct TestAsset { + pub value: i32, + pub name: String, +} + +impl TestAsset { + pub fn new(value: i32, name: String) -> Self { + Self { value, name } + } +} + #[derive(Component, Reflect, PartialEq, Eq, Debug, Default)] #[reflect(Component)] pub struct GenericComponent { @@ -361,6 +373,10 @@ pub fn setup_integration_test(init: F) ..Default::default() }, )); + + app.init_asset::(); + app.register_asset_reflect::(); + app } diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 81a4420c42..bceac67b13 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -30,6 +30,7 @@ - [Constructing Arbitrary Types](./ScriptingReference/constructing-arbitrary-types.md) - [Core Bindings](./ladfiles/bindings.lad.json) - [Core Callbacks](./ScriptingReference/core-callbacks.md) +- [Asset Operations](./ScriptingReference/asset-operations.md) # Developing BMS diff --git a/docs/src/ScriptingReference/asset-operations.md b/docs/src/ScriptingReference/asset-operations.md new file mode 100644 index 0000000000..075965ace7 --- /dev/null +++ b/docs/src/ScriptingReference/asset-operations.md @@ -0,0 +1,41 @@ +# Asset Operations + +BMS provides built-in support for working with Bevy assets from scripts. You can check for asset existence, retrieve assets by handle, and manipulate asset data through the reflection system. + +## Prerequisites + +To use asset operations in your scripts, you need to ensure that: + +1. Your asset type implements `Asset` and `Reflect` +2. The asset type is registered with `app.register_asset_reflect::()` +3. The asset handle type has `ReflectHandle` type data registered + +## Available Functions + +### `world.has_asset(handle)` + +Checks if an asset exists and is loaded for the given handle. + +**Parameters:** + +- `handle`: A ReflectReference to an existing asset handle + +**Returns:** + +- `boolean`: `true` if the asset exists and is loaded, `false` otherwise + +### `world.get_asset(handle, asset_type)` + +Retrieves a loaded asset by its handle and returns a reflected reference to it. + +**Parameters:** + +- `handle`: A reflected reference to an asset handle +- `asset_type`: The type registration of the asset (e.g., `types.Image`, `types.Mesh`) + +**Returns:** + +- `ReflectReference`: A reference to the asset data, or `nil`/`()` if not loaded + +Examples: +See usage examples in the `assets\tests\asset_operations` directory.