diff --git a/Cargo.toml b/Cargo.toml index e9a301cd9a..41d08e564a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -211,6 +211,7 @@ bevy = { workspace = true, features = [ "bevy_asset", "bevy_core_pipeline", "bevy_sprite", + "x11", ] } bevy_platform = { workspace = true } clap = { workspace = true, features = ["derive"] } diff --git a/crates/bevy_mod_scripting_core/src/asset.rs b/crates/bevy_mod_scripting_core/src/asset.rs index f8d84cbeb5..44b93378f0 100644 --- a/crates/bevy_mod_scripting_core/src/asset.rs +++ b/crates/bevy_mod_scripting_core/src/asset.rs @@ -12,7 +12,7 @@ use ::{ // }, bevy_reflect::Reflect, }; -use bevy_asset::AssetServer; +use bevy_asset::{AssetServer, Handle}; use bevy_ecs::{ entity::Entity, event::{EventReader, EventWriter}, @@ -23,11 +23,11 @@ use bevy_ecs::{ use serde::{Deserialize, Serialize}; use crate::{ - IntoScriptPluginParams, LanguageExtensions, ScriptComponent, ScriptingSystemSet, StaticScripts, + IntoScriptPluginParams, LanguageExtensions, ScriptComponent, ScriptingSystemSet, commands::{CreateOrUpdateScript, DeleteScript}, error::ScriptError, event::ScriptEvent, - script::{ContextKey, DisplayProxy, ScriptAttachment}, + script::{ContextKey, DisplayProxy, ScriptAttachment, ScriptContext}, }; /// Represents a scripting language. Languages which compile into another language should use the target language as their language. @@ -215,10 +215,10 @@ fn sync_assets( fn handle_script_events( mut events: EventReader, script_assets: Res>, - static_scripts: Res, scripts: Query<(Entity, &ScriptComponent)>, asset_server: Res, mut script_queue: Local, + script_contexts: Res>, mut commands: Commands, world_id: WorldId, ) { @@ -233,6 +233,7 @@ fn handle_script_events( // We need to reload the script for any context it's // associated with. That could be static scripts, script // components. + for (entity, script_component) in &scripts { if let Some(handle) = script_component.0.iter().find(|handle| handle.id() == *id) @@ -247,13 +248,17 @@ fn handle_script_events( } } - if let Some(handle) = static_scripts.scripts.iter().find(|s| s.id() == *id) { - commands.queue( - CreateOrUpdateScript::

::new(ScriptAttachment::StaticScript( - handle.clone(), - )) - .with_responses(P::readonly_configuration(world_id).emit_responses), - ); + let handle = Handle::Weak(*id); + let attachment = ScriptAttachment::StaticScript(handle.clone()); + for (resident, _) in script_contexts + .residents(&attachment) + .filter(|(r, _)| r.script() == handle && r.is_static()) + { + // if the script does not have any associated entity it's static. + commands + .queue(CreateOrUpdateScript::

::new(resident).with_responses( + P::readonly_configuration(world_id).emit_responses, + )); } } } diff --git a/crates/bevy_mod_scripting_core/src/commands.rs b/crates/bevy_mod_scripting_core/src/commands.rs index 3ad3b35eb9..998c351c23 100644 --- a/crates/bevy_mod_scripting_core/src/commands.rs +++ b/crates/bevy_mod_scripting_core/src/commands.rs @@ -14,7 +14,7 @@ use crate::{ }, extractors::{HandlerContext, with_handler_system_state}, handler::{handle_script_errors, send_callback_response}, - script::{DisplayProxy, ScriptAttachment, StaticScripts}, + script::{DisplayProxy, ScriptAttachment}, }; use bevy_ecs::{system::Command, world::World}; use bevy_log::{error, info, trace}; @@ -56,7 +56,20 @@ impl Command for DeleteScript

{ // we demote to weak from here on out, so as not to hold the asset hostage self.context_key = self.context_key.into_weak(); - // first apply unload callback + // first check the script exists, if it does not it could have been deleted by another command + { + let script_contexts = world.get_resource_or_init::>(); + if !script_contexts.contains(&self.context_key) { + debug!( + "{}: No context found for {}, not deleting.", + P::LANGUAGE, + self.context_key + ); + return; + } + } + + // apply unload callback Command::apply( RunScriptCallback::

::new( self.context_key.clone(), @@ -66,23 +79,6 @@ impl Command for DeleteScript

{ ), world, ); - match &self.context_key { - ScriptAttachment::EntityScript(_, _) => { - // nothing special needs to be done, just the context removal - } - ScriptAttachment::StaticScript(script) => { - // remove the static script - let mut scripts = world.get_resource_or_init::(); - if scripts.remove(script.id()) { - debug!("Deleted static script {}", script.display()); - } else { - warn!( - "Attempted to delete static script {}, but it was not found", - script.display() - ); - } - } - } let mut script_contexts = world.get_resource_or_init::>(); let residents_count = script_contexts.residents_len(&self.context_key); @@ -225,10 +221,6 @@ impl CreateOrUpdateScript

{ ) -> Result<(), ScriptError> { // we demote to weak from here on out, so as not to hold the asset hostage let attachment = attachment.clone().into_weak(); - if let ScriptAttachment::StaticScript(id) = &attachment { - // add to static scripts - handler_ctxt.static_scripts.insert(id.clone()); - } let script_id = attachment.script(); diff --git a/crates/bevy_mod_scripting_core/src/config.rs b/crates/bevy_mod_scripting_core/src/config.rs index 5c48a80477..b327edb5af 100644 --- a/crates/bevy_mod_scripting_core/src/config.rs +++ b/crates/bevy_mod_scripting_core/src/config.rs @@ -46,7 +46,7 @@ pub trait GetPluginThreadConfig { fn readonly_configuration(world: WorldId) -> ScriptingPluginConfiguration

; /// Set the configuration or overwrites it if already set. - fn set_thread_config(world: WorldId, config: ScriptingPluginConfiguration

); + fn set_world_local_config(world: WorldId, config: ScriptingPluginConfiguration

); } #[macro_export] @@ -76,7 +76,7 @@ macro_rules! make_plugin_config_static { ) } - fn set_thread_config( + fn set_world_local_config( world: bevy_ecs::world::WorldId, config: ScriptingPluginConfiguration<$ty>, ) { diff --git a/crates/bevy_mod_scripting_core/src/extractors.rs b/crates/bevy_mod_scripting_core/src/extractors.rs index 8d0d8235e4..b3059cfe12 100644 --- a/crates/bevy_mod_scripting_core/src/extractors.rs +++ b/crates/bevy_mod_scripting_core/src/extractors.rs @@ -24,7 +24,7 @@ use crate::{ error::{InteropError, ScriptError}, event::{CallbackLabel, IntoCallbackLabel}, handler::ScriptingHandler, - script::{ScriptAttachment, ScriptContext, StaticScripts}, + script::{ScriptAttachment, ScriptContext}, }; /// Executes `system_state.get_mut` followed by `system_state.apply` after running the given closure, makes sure state is correctly handled in the context of an exclusive system. @@ -47,8 +47,6 @@ pub fn with_handler_system_state< /// Context for systems which handle events for scripts pub struct HandlerContext { - /// List of static scripts - pub(crate) static_scripts: StaticScripts, /// Script context pub(crate) script_context: ScriptContext

, } @@ -58,7 +56,6 @@ impl HandlerContext

{ /// Every call to this function must be paired with a call to [`Self::release`]. pub fn yoink(world: &mut World) -> Self { Self { - static_scripts: world.remove_resource().unwrap_or_default(), script_context: world.remove_resource().unwrap_or_default(), } } @@ -67,15 +64,9 @@ impl HandlerContext

{ /// Only call this if you have previously yoinked the handler context from the world. pub fn release(self, world: &mut World) { // insert the handler context back into the world - world.insert_resource(self.static_scripts); world.insert_resource(self.script_context); } - /// Get the static scripts - pub fn static_scripts(&mut self) -> &mut StaticScripts { - &mut self.static_scripts - } - /// Get the static scripts pub fn script_context(&mut self) -> &mut ScriptContext

{ &mut self.script_context diff --git a/crates/bevy_mod_scripting_core/src/lib.rs b/crates/bevy_mod_scripting_core/src/lib.rs index aa51e17467..45303ed9cd 100644 --- a/crates/bevy_mod_scripting_core/src/lib.rs +++ b/crates/bevy_mod_scripting_core/src/lib.rs @@ -33,7 +33,7 @@ use context::{Context, ContextInitializer, ContextPreHandlingInitializer}; use event::{ScriptCallbackEvent, ScriptCallbackResponseEvent, ScriptEvent}; use handler::HandlerFn; use runtime::{Runtime, RuntimeInitializer}; -use script::{ContextPolicy, ScriptComponent, ScriptContext, StaticScripts}; +use script::{ContextPolicy, ScriptComponent, ScriptContext}; use std::ops::{Deref, DerefMut}; pub mod asset; @@ -165,7 +165,7 @@ impl Plugin for ScriptingPlugin

{ runtime: Box::leak(Box::new(runtime)), }; - P::set_thread_config(app.world().id(), config); + P::set_world_local_config(app.world().id(), config); app.insert_resource(ScriptContext::

::new(self.context_policy.clone())); @@ -294,7 +294,6 @@ impl Plugin for BMSScriptingInfrastructurePlugin { .add_event::() .add_event::() .init_resource::() - .init_resource::() .init_asset::() .init_resource::() .insert_resource(AppScheduleRegistry::new()); diff --git a/crates/bevy_mod_scripting_core/src/script/context_key.rs b/crates/bevy_mod_scripting_core/src/script/context_key.rs index 294ba51790..45f695441a 100644 --- a/crates/bevy_mod_scripting_core/src/script/context_key.rs +++ b/crates/bevy_mod_scripting_core/src/script/context_key.rs @@ -60,6 +60,16 @@ impl ScriptAttachment { } } } + + /// Returns true if the attachment is a static script. + pub fn is_static(&self) -> bool { + matches!(self, ScriptAttachment::StaticScript(_)) + } + + /// Returns true if the attachment is an entity script. + pub fn is_entity_script(&self) -> bool { + matches!(self, ScriptAttachment::EntityScript(_, _)) + } } impl From for ContextKey { diff --git a/crates/bevy_mod_scripting_core/src/script/mod.rs b/crates/bevy_mod_scripting_core/src/script/mod.rs index 7d9ab9cb7d..30a56dc404 100644 --- a/crates/bevy_mod_scripting_core/src/script/mod.rs +++ b/crates/bevy_mod_scripting_core/src/script/mod.rs @@ -125,88 +125,12 @@ impl ScriptComponent { } } -/// A collection of scripts, not associated with any entity. -/// -/// Useful for `global` or `static` scripts which operate over a larger scope than a single entity. -#[derive(Default, Resource)] -pub struct StaticScripts { - pub(crate) scripts: HashSet>, -} - -#[profiling::all_functions] -impl StaticScripts { - /// Inserts a static script into the collection - pub fn insert>>(&mut self, script: S) { - self.scripts.insert(script.into()); - } - - /// Removes a static script from the collection, returning `true` if the script was in the collection, `false` otherwise - pub fn remove(&mut self, script_id: impl Into) -> bool { - let script_id = script_id.into(); - self.scripts - .extract_if(|handle| handle.id() == script_id) - .next() - .is_some() - } - - /// Checks if a static script is in the collection - /// Returns `true` if the script is in the collection, `false` otherwise - pub fn contains(&self, script_id: impl Into) -> bool { - let script_id = script_id.into(); - self.scripts.iter().any(|handle| handle.id() == script_id) - } - - /// Returns an iterator over the static scripts - pub fn values(&self) -> impl Iterator> { - self.scripts.iter() - } -} - #[cfg(test)] mod tests { use bevy_ecs::{event::Events, world::World}; use super::*; - #[test] - fn static_scripts_insert() { - let mut static_scripts = StaticScripts::default(); - let script1 = Handle::default(); - static_scripts.insert(script1.clone()); - assert_eq!(static_scripts.scripts.len(), 1); - assert!(static_scripts.scripts.contains(&script1)); - } - - #[test] - fn static_scripts_remove() { - let mut static_scripts = StaticScripts::default(); - let script1 = Handle::default(); - static_scripts.insert(script1.clone()); - assert_eq!(static_scripts.scripts.len(), 1); - assert!(static_scripts.scripts.contains(&script1)); - assert!(static_scripts.remove(&script1)); - assert_eq!(static_scripts.scripts.len(), 0); - assert!(!static_scripts.scripts.contains(&script1)); - } - - fn scriptid_from_u128(uuid: u128) -> ScriptId { - ScriptId::from(uuid::Builder::from_random_bytes(uuid.to_le_bytes()).into_uuid()) - } - - fn handle_from_u128(uuid: u128) -> Handle { - Handle::Weak(scriptid_from_u128(uuid)) - } - - #[test] - fn static_scripts_contains() { - let mut static_scripts = StaticScripts::default(); - let script1 = handle_from_u128(0); - let script2 = handle_from_u128(1); - static_scripts.insert(script1.clone()); - assert!(static_scripts.contains(&script1)); - assert!(!static_scripts.contains(&script2)); - } - #[test] fn test_component_add() { let mut world = World::new(); diff --git a/crates/languages/bevy_mod_scripting_lua/src/lib.rs b/crates/languages/bevy_mod_scripting_lua/src/lib.rs index 3c7ca7df7f..e884889352 100644 --- a/crates/languages/bevy_mod_scripting_lua/src/lib.rs +++ b/crates/languages/bevy_mod_scripting_lua/src/lib.rs @@ -279,14 +279,23 @@ mod test { let mut old_ctxt = lua.clone(); let handle = Handle::Weak(AssetId::from(AssetIndex::from_bits(0))); let context_key = ScriptAttachment::EntityScript(Entity::from_raw(1), handle); - + let world_id = WorldId::new().unwrap(); + LuaScriptingPlugin::set_world_local_config( + world_id, + ScriptingPluginConfiguration { + pre_handling_callbacks: &[], + context_initialization_callbacks: &[], + emit_responses: false, + runtime: &(), + }, + ); lua_context_load( &context_key, "function hello_world_from_first_load() end" .as_bytes(), - WorldId::new().unwrap(), + world_id, ) .unwrap(); @@ -297,7 +306,7 @@ mod test { end" .as_bytes(), &mut old_ctxt, - WorldId::new().unwrap(), + world_id, ) .unwrap(); diff --git a/examples/game_of_life.rs b/examples/game_of_life.rs index 4c769b462d..2b796a4a00 100644 --- a/examples/game_of_life.rs +++ b/examples/game_of_life.rs @@ -16,7 +16,7 @@ use bevy::{ }; use bevy_console::{AddConsoleCommand, ConsoleCommand, ConsoleOpen, ConsolePlugin, make_layer}; use bevy_mod_scripting::{core::bindings::AllocatorDiagnosticPlugin, prelude::*}; -use bevy_mod_scripting_core::{commands::RemoveStaticScript, script::StaticScripts}; +use bevy_mod_scripting_core::commands::RemoveStaticScript; use clap::Parser; // CONSOLE SETUP @@ -42,7 +42,7 @@ fn run_script_cmd( mut commands: Commands, asset_server: Res, script_comps: Query<(Entity, &ScriptComponent)>, - static_scripts: Res, + mut static_scripts: Local>>, ) { if let Some(Ok(command)) = log.take() { match command { @@ -60,6 +60,7 @@ fn run_script_cmd( } else { bevy::log::info!("Using static script instead of spawning an entity"); let handle = asset_server.load(script_path); + static_scripts.push(handle.clone()); commands.queue(AddStaticScript::new(handle)) } } @@ -72,7 +73,7 @@ fn run_script_cmd( commands.entity(id).despawn(); } - for script in static_scripts.values() { + for script in static_scripts.iter() { commands.queue(RemoveStaticScript::new(script.clone())); } } @@ -115,10 +116,8 @@ fn game_of_life_app(app: &mut App) -> &mut App { send_on_update.after(update_rendered_state), ( event_handler::, - #[cfg(feature = "rhai")] event_handler::, event_handler::, - #[cfg(feature = "rhai")] event_handler::, ) .after(send_on_update), diff --git a/tests/script_tests.rs b/tests/script_tests.rs index 737896b47c..05fec9dc1c 100644 --- a/tests/script_tests.rs +++ b/tests/script_tests.rs @@ -28,10 +28,7 @@ impl TestExecutor for Test { // do this in a separate thread to isolate the thread locals - match execute_integration_test(scenario) { - Ok(_) => Ok(()), - Err(e) => Err(Failed::from(format!("{e:?}"))), // print whole error from anyhow including source and backtrace - } + Ok(execute_integration_test(scenario)?) } fn name(&self) -> String {