diff --git a/bevy_mod_scripting_core/src/event.rs b/bevy_mod_scripting_core/src/event.rs index 7ba3470709..5f11c18963 100644 --- a/bevy_mod_scripting_core/src/event.rs +++ b/bevy_mod_scripting_core/src/event.rs @@ -1,5 +1,9 @@ +use bevy::log::error; use bevy::prelude::Event; +use crate::hosts::ScriptHost; +use crate::systems::CachedScriptState; +use crate::world::WorldPointer; use crate::{error::ScriptError, hosts::Recipients}; /// An error coming from a script @@ -20,3 +24,23 @@ pub trait ScriptEvent: Send + Sync + Clone + Event + 'static { /// Retrieves the recipient scripts for this event fn recipients(&self) -> &Recipients; } + +pub fn write_error_event_with_world( + world: WorldPointer, + script_name: String, + error_text: String, +) { + let mut world = world.write(); + let mut state: CachedScriptState = world.remove_resource().unwrap(); + + let (_, mut error_wrt, _) = state.event_state.get_mut(&mut world); + + let error = ScriptError::RuntimeError { + script: script_name, + msg: error_text, + }; + + error!("{}", error); + error_wrt.send(ScriptErrorEvent { error }); + world.insert_resource(state); +} diff --git a/bevy_mod_scripting_core/src/hosts.rs b/bevy_mod_scripting_core/src/hosts.rs index 9e8ff58520..c45977ce1a 100644 --- a/bevy_mod_scripting_core/src/hosts.rs +++ b/bevy_mod_scripting_core/src/hosts.rs @@ -1,11 +1,13 @@ //! All script host related stuff use bevy::{asset::Asset, ecs::schedule::ScheduleLabel, prelude::*}; +use std::ops::DerefMut; use std::{ collections::HashMap, iter::once, sync::atomic::{AtomicU32, Ordering}, }; +use crate::world::WorldPointerGuard; use crate::{ asset::CodeAsset, docs::DocFragment, @@ -73,6 +75,7 @@ pub trait ScriptHost: Send + Sync + 'static + Default + Resource { /// to send useful errors. fn load_script( &mut self, + world_ptr: WorldPointer, script: &[u8], script_data: &ScriptData, providers: &mut APIProviders, @@ -113,11 +116,24 @@ pub trait ScriptHost: Send + Sync + 'static + Default + Resource { }; let mut providers: APIProviders = world.remove_resource().unwrap(); - let mut ctx = self.load_script(script, &fd, &mut providers).unwrap(); + // safety: + // - we have &mut World access + // - we do not use the original reference again anywhere in this function after this was created + let world = unsafe { WorldPointerGuard::new(world) }; + let mut ctx = self + .load_script(world.clone(), script, &fd, &mut providers) + .unwrap(); self.setup_script(&fd, &mut ctx, &mut providers)?; let events = [event; 1]; - self.handle_events(world, &events, once((fd, &mut ctx)), &mut providers); + let mut world = world.write(); + + self.handle_events( + world.deref_mut(), + &events, + once((fd, &mut ctx)), + &mut providers, + ); world.insert_resource(providers); @@ -352,12 +368,12 @@ impl Script { /// reloads the script by deleting the old context and inserting a new one /// if the script context never existed, it will after this call. pub(crate) fn reload_script( + world: &mut World, host: &mut H, script: &Script, script_assets: &Assets, providers: &mut APIProviders, contexts: &mut ScriptContexts, - event_writer: &mut EventWriter, ) { debug!("reloading script {}", script.id); @@ -367,13 +383,13 @@ impl Script { contexts.remove_context(script.id()); // insert new re-loaded context Self::insert_new_script_context::( + world, host, script, entity, script_assets, providers, contexts, - event_writer, ); } else { // remove old context @@ -385,14 +401,19 @@ impl Script { /// sets up (`ScriptHost::setup_script`) and inserts its new context into the contexts resource /// otherwise inserts None. Sends ScriptLoaded event if the script was loaded pub(crate) fn insert_new_script_context( + world: &mut World, host: &mut H, new_script: &Script, entity: Entity, script_assets: &Assets, providers: &mut APIProviders, contexts: &mut ScriptContexts, - event_writer: &mut EventWriter, ) { + // safety: + // - we have &mut World access + // - we do not use the original reference again anywhere in this function + let world = unsafe { WorldPointerGuard::new(world) }; + let fd = ScriptData { sid: new_script.id(), entity, @@ -410,14 +431,19 @@ impl Script { }; debug!("Inserted script {:?}", fd); - match host.load_script(script.bytes(), &fd, providers) { + match host.load_script(world.clone(), script.bytes(), &fd, providers) { Ok(mut ctx) => { host.setup_script(&fd, &mut ctx, providers) .expect("Failed to setup script"); contexts.insert_context(fd, Some(ctx)); - event_writer.send(ScriptLoaded { - sid: new_script.id(), - }); + { + let mut world = world.write(); + world.resource_scope(|_, mut event_writer: Mut>| { + event_writer.send(ScriptLoaded { + sid: new_script.id(), + }); + }) + } } Err(e) => { warn! {"Error in loading script {}:\n{}", &new_script.name,e} @@ -429,6 +455,17 @@ impl Script { } } +/// Allows the script handles to be cloned along with the explicit bevy asset handle clone +impl Clone for Script { + fn clone(&self) -> Self { + Self { + handle: self.handle.clone(), + name: self.name.clone(), + id: self.id, + } + } +} + #[derive(Component, Debug, Reflect)] #[reflect(Component, Default)] /// The component storing many scripts. @@ -439,6 +476,14 @@ pub struct ScriptCollection { pub scripts: Vec>, } +impl Clone for ScriptCollection { + fn clone(&self) -> Self { + Self { + scripts: self.scripts.clone(), + } + } +} + impl Default for ScriptCollection { fn default() -> Self { Self { diff --git a/bevy_mod_scripting_core/src/systems.rs b/bevy_mod_scripting_core/src/systems.rs index 000f9ecdcd..de4d60cf5e 100644 --- a/bevy_mod_scripting_core/src/systems.rs +++ b/bevy_mod_scripting_core/src/systems.rs @@ -19,36 +19,43 @@ pub enum ScriptSystemSet { /// Handles creating contexts for new/modified scripts /// Scripts are likely not loaded instantly at this point, so most of the time /// this system simply inserts an empty context -pub fn script_add_synchronizer( - query: Query< - ( - Entity, - &ScriptCollection, - Ref>, - ), - Changed>, - >, - mut host: ResMut, - mut providers: ResMut>, - script_assets: Res>, - mut contexts: ResMut>, - mut event_writer: EventWriter, -) { +pub fn script_add_synchronizer(world: &mut World) { debug!("Handling addition/modification of scripts"); - query.for_each(|(entity, new_scripts, tracker)| { - if tracker.is_added() { - new_scripts.scripts.iter().for_each(|new_script| { + let mut state: CachedScriptLoadState = world.remove_resource().unwrap(); + + // Entity, + // &'static ScriptCollection, + // Ref<'static, ScriptCollection>, + + let script_assets: Assets = world.remove_resource().unwrap(); + let mut contexts: ScriptContexts = world.remove_resource().unwrap(); + let mut host: H = world.remove_resource().unwrap(); + let mut providers: APIProviders = world.remove_resource().unwrap(); + + let query: Vec<_> = { + let mut q = vec![]; + let changed = state.scripts_changed_query.get(world); + for (entity, new_scripts, tracker) in changed.iter() { + q.push((entity, new_scripts.scripts.to_vec(), tracker.is_added())) + } + q + }; + world.insert_resource(state); + + for (entity, new_scripts, tracker) in query.iter() { + if *tracker { + for new_script in new_scripts { Script::::insert_new_script_context::( + world, &mut host, new_script, - entity, + *entity, &script_assets, &mut providers, &mut contexts, - &mut event_writer, ) - }) + } } else { // changed but structure already exists in contexts // find out what's changed @@ -58,14 +65,10 @@ pub fn script_add_synchronizer( let context_ids = contexts .context_entities .iter() - .filter_map(|(sid, (e, _, _))| if *e == entity { Some(sid) } else { None }) + .filter_map(|(sid, (e, _, _))| if e == entity { Some(sid) } else { None }) .cloned() .collect::>(); - let script_ids = new_scripts - .scripts - .iter() - .map(|s| s.id()) - .collect::>(); + let script_ids = new_scripts.iter().map(|s| s.id()).collect::>(); let removed_scripts = context_ids.difference(&script_ids); let added_scripts = script_ids.difference(&context_ids); @@ -75,19 +78,25 @@ pub fn script_add_synchronizer( } for a in added_scripts { - let script = new_scripts.scripts.iter().find(|e| &e.id() == a).unwrap(); + let script = new_scripts.iter().find(|e| &e.id() == a).unwrap(); Script::::insert_new_script_context::( + world, &mut host, script, - entity, + *entity, &script_assets, &mut providers, &mut contexts, - &mut event_writer, ) } } - }) + } + + // return ownership + world.insert_resource(script_assets); + world.insert_resource(contexts); + world.insert_resource(host); + world.insert_resource(providers); } /// Handles the removal of script components and their contexts @@ -112,44 +121,66 @@ pub fn script_remove_synchronizer( } /// Reloads hot-reloaded scripts, or loads missing contexts for scripts which were added but not loaded -pub fn script_hot_reload_handler( - mut events: EventReader>, - mut host: ResMut, - scripts: Query<&ScriptCollection>, - script_assets: Res>, - mut providers: ResMut>, - mut contexts: ResMut>, - mut event_writer: EventWriter, -) { - for e in events.iter() { - let (handle, created) = match e { - AssetEvent::Modified { handle } => (handle, false), - AssetEvent::Created { handle } => (handle, true), - _ => continue, - }; +pub fn script_hot_reload_handler(world: &mut World) { + let mut state: CachedScriptLoadState = world.remove_resource().unwrap(); + + let events = { + state + .event_state + .get_mut(world) + .1 + .iter() + .filter_map(|e| match e { + AssetEvent::Modified { handle } => Some((handle.clone(), false)), + AssetEvent::Created { handle } => Some((handle.clone(), true)), + _ => None, + }) + .collect::>() + }; + // collect all asset events up front + // let events = events.iter().collect::>(); + // collect all scripts from query upfront + let scripts = state + .scripts_query + .get(world) + .iter() + .cloned() + .collect::>(); + + world.insert_resource(state); + let script_assets: Assets = world.remove_resource().unwrap(); + let mut contexts: ScriptContexts = world.remove_resource().unwrap(); + let mut host: H = world.remove_resource().unwrap(); + let mut providers: APIProviders = world.remove_resource().unwrap(); + + for (handle, created) in events { // find script using this handle by handle id // whether this script was modified or created // if a script exists with this handle, we should reload it to load in a new context // which at this point will be either None or Some(outdated context) // both ways are fine for scripts in scripts.iter() { - for script in &scripts.scripts { + for script in scripts.scripts.iter() { // the script could have well loaded in the same frame that it was added // in that case it will have a context attached and we do not want to reload it - if script.handle() == handle && !(contexts.has_context(script.id()) && created) { + if script.handle() == &handle && !(contexts.has_context(script.id()) && created) { Script::::reload_script::( + world, &mut host, script, &script_assets, &mut providers, &mut contexts, - &mut event_writer, ); } } } } + world.insert_resource(script_assets); + world.insert_resource(contexts); + world.insert_resource(host); + world.insert_resource(providers); } /// Lets the script host handle all script events @@ -224,3 +255,36 @@ impl FromWorld for CachedScriptState { } } } + +#[derive(Resource)] +/// system state for exclusive systems dealing with script load events +pub struct CachedScriptLoadState { + pub event_state: SystemState<( + EventWriter<'static, ScriptLoaded>, + EventReader<'static, 'static, AssetEvent>, + )>, + pub scripts_query: + SystemState>>, + pub scripts_changed_query: SystemState< + Query< + 'static, + 'static, + ( + Entity, + &'static ScriptCollection, + Ref<'static, ScriptCollection>, + ), + Changed>, + >, + >, +} + +impl FromWorld for crate::systems::CachedScriptLoadState { + fn from_world(world: &mut World) -> Self { + Self { + event_state: SystemState::new(world), + scripts_query: SystemState::new(world), + scripts_changed_query: SystemState::new(world), + } + } +} diff --git a/languages/bevy_mod_scripting_lua/src/lib.rs b/languages/bevy_mod_scripting_lua/src/lib.rs index 8af061d514..c5641073cf 100644 --- a/languages/bevy_mod_scripting_lua/src/lib.rs +++ b/languages/bevy_mod_scripting_lua/src/lib.rs @@ -13,7 +13,10 @@ use tealr::mlu::mlua::{prelude::*, Function}; pub mod assets; pub mod docs; pub mod util; +use bevy_mod_scripting_core::event::write_error_event_with_world; +use bevy_mod_scripting_core::world::WorldPointer; pub use tealr; + pub mod prelude { pub use crate::{ assets::{LuaFile, LuaLoader}, @@ -83,6 +86,7 @@ impl ScriptHost for LuaScriptHost { .add_asset::() .init_asset_loader::() .init_resource::>() + .init_resource::>() .init_resource::>() .init_resource::>() .register_type::>() @@ -104,6 +108,7 @@ impl ScriptHost for LuaScriptHost { fn load_script( &mut self, + world: WorldPointer, script: &[u8], script_data: &ScriptData, providers: &mut APIProviders, @@ -115,19 +120,44 @@ impl ScriptHost for LuaScriptHost { // init lua api before loading script let mut lua = Mutex::new(lua); + + providers + .setup_runtime_all(world.clone(), script_data, &mut lua) + .expect("Could not setup script runtime"); + providers.attach_all(&mut lua)?; + // We do this twice to get around the issue of attach_all overriding values here for the sake of + // documenting, TODO: this is messy, shouldn't be a problem but it's messy + providers + .setup_runtime_all(world.clone(), script_data, &mut lua) + .expect("Could not setup script runtime"); + lua.get_mut() - .map_err(|e| ScriptError::FailedToLoad { - script: script_data.name.to_owned(), - msg: e.to_string(), + .map_err(|e| { + write_error_event_with_world::( + world.clone(), + script_data.name.to_owned(), + e.to_string(), + ); + ScriptError::FailedToLoad { + script: script_data.name.to_owned(), + msg: e.to_string(), + } })? .load(script) .set_name(script_data.name) .exec() - .map_err(|e| ScriptError::FailedToLoad { - script: script_data.name.to_owned(), - msg: e.to_string(), + .map_err(|e| { + write_error_event_with_world::( + world.clone(), + script_data.name.to_owned(), + e.to_string(), + ); + ScriptError::FailedToLoad { + script: script_data.name.to_owned(), + msg: e.to_string(), + } })?; Ok(lua) @@ -178,19 +208,11 @@ impl ScriptHost for LuaScriptHost { }; if let Err(error) = f.call::<_, ()>(event.args.clone()) { - let mut world = world.write(); - let mut state: CachedScriptState = world.remove_resource().unwrap(); - - let (_, mut error_wrt, _) = state.event_state.get_mut(&mut world); - - let error = ScriptError::RuntimeError { - script: script_data.name.to_owned(), - msg: error.to_string(), - }; - - error!("{}", error); - error_wrt.send(ScriptErrorEvent { error }); - world.insert_resource(state); + write_error_event_with_world::( + world.clone(), + script_data.name.to_owned(), + error.to_string(), + ); } } }); diff --git a/languages/bevy_mod_scripting_rhai/src/lib.rs b/languages/bevy_mod_scripting_rhai/src/lib.rs index 4b8751e214..c6606dae74 100644 --- a/languages/bevy_mod_scripting_rhai/src/lib.rs +++ b/languages/bevy_mod_scripting_rhai/src/lib.rs @@ -9,7 +9,9 @@ use std::marker::PhantomData; pub mod assets; pub mod docs; +use bevy_mod_scripting_core::world::WorldPointer; pub use rhai; + pub mod prelude { pub use crate::{ assets::{RhaiFile, RhaiLoader}, @@ -78,6 +80,7 @@ impl ScriptHost for RhaiScriptHost< .add_asset::() .init_asset_loader::() .init_resource::>() + .init_resource::>() .init_resource::>() .init_resource::>() .register_type::>() @@ -115,6 +118,7 @@ impl ScriptHost for RhaiScriptHost< fn load_script( &mut self, + _world_pointer: WorldPointer, script: &[u8], script_data: &ScriptData, _: &mut APIProviders, diff --git a/languages/bevy_mod_scripting_rune/src/lib.rs b/languages/bevy_mod_scripting_rune/src/lib.rs index 5683bde5a6..4eeae02e80 100644 --- a/languages/bevy_mod_scripting_rune/src/lib.rs +++ b/languages/bevy_mod_scripting_rune/src/lib.rs @@ -146,6 +146,7 @@ impl ScriptHost for RuneScriptHost { fn load_script( &mut self, + _world_ptr: WorldPointer, script: &[u8], script_data: &ScriptData, providers: &mut APIProviders,