diff --git a/assets/scripts/game_of_life.lua b/assets/scripts/game_of_life.lua index c10d8b99..9f36e46d 100644 --- a/assets/scripts/game_of_life.lua +++ b/assets/scripts/game_of_life.lua @@ -2,32 +2,40 @@ LifeState = world.get_type_by_name("LifeState") Settings = world.get_type_by_name("Settings") world.info("Lua: The game_of_life.lua script just got loaded") -world.info("Lua: Hello! I am initiating the game of life simulation state with randomness!") math.randomseed(os.time()) function fetch_life_state() -- find the entity with life state local life_state = nil + for i,result in pairs(world.query({LifeState}):build()) do life_state = result:components()[1] break end return life_state -end - -local life_state = fetch_life_state() -local cells = life_state.cells +end --- set some cells alive -for _=1,1000 do - local index = math.random(#cells) - cells[index] = 255 -end +function on_script_loaded() + world.info("Lua: Hello! I am initiating the game of life simulation state with randomness!") + world.info("Lua: Click on the screen to set cells alive") + + local life_state = fetch_life_state() + local cells = life_state.cells + + -- set some cells alive + for _=1,1000 do + local index = math.random(#cells) + cells[index] = 255 + end +end function on_click(x,y) -- get the settings world.info("Lua: Clicked at x: " .. x .. " y: " .. y) + local life_state = fetch_life_state() + local cells = life_state.cells + local settings = world.get_resource(Settings) local dimensions = settings.physical_grid_dimensions local screen = settings.display_grid_dimensions @@ -83,4 +91,15 @@ function on_update() cells[i] = 0 end end +end + +function on_script_unloaded() + world.info("Lua: I am being unloaded, goodbye!") + + -- set state to 0's + local life_state = fetch_life_state() + local cells = life_state.cells + for i=1,#cells do + cells[i] = 0 + end end \ No newline at end of file diff --git a/crates/bevy_mod_scripting_core/src/commands.rs b/crates/bevy_mod_scripting_core/src/commands.rs index 3ff8c91f..cd1a57d4 100644 --- a/crates/bevy_mod_scripting_core/src/commands.rs +++ b/crates/bevy_mod_scripting_core/src/commands.rs @@ -1,10 +1,17 @@ use std::{any::type_name, marker::PhantomData}; -use bevy::{asset::Handle, ecs::world::Mut, log::debug, prelude::Command}; +use bevy::{ + asset::Handle, + ecs::world::Mut, + log::{debug, info}, + prelude::Command, +}; use crate::{ asset::ScriptAsset, context::{Context, ContextLoadingSettings, ScriptContexts}, + event::{IntoCallbackLabel, OnScriptLoaded, OnScriptUnloaded}, + handler::CallbackSettings, prelude::{Runtime, RuntimeContainer}, script::{Script, ScriptId, Scripts}, systems::handle_script_errors, @@ -33,17 +40,47 @@ impl Command for DeleteScript

{ .expect("No ScriptLoadingSettings resource found") .clone(); + let runner = world + .get_resource::>() + .expect("No CallbackSettings resource found") + .callback_handler + .expect("No callback handler set"); + + let mut ctxts = world + .remove_non_send_resource::>() + .unwrap(); + + let mut runtime_container = world + .remove_non_send_resource::>() + .unwrap(); + world.resource_scope(|world, mut scripts: Mut| { if let Some(script) = scripts.scripts.remove(&self.id) { debug!("Deleting script with id: {}", self.id); - let mut ctxts = world.get_non_send_resource_mut::>(); - let ctxts = ctxts.as_deref_mut().unwrap(); + + // first let the script uninstall itself + match (runner)( + vec![], + bevy::ecs::entity::Entity::from_raw(0), + &self.id, + &OnScriptUnloaded::into_callback_label(), + ctxts.get_mut(script.context_id).unwrap(), + &settings.context_pre_handling_initializers, + &mut runtime_container.runtime, + world, + ) { + Ok(_) => {}, + Err(e) => { + handle_script_errors(world, [e.with_context(format!("Running unload hook for script with id: {}. Runtime type: {}, Context type: {}", self.id, type_name::(), type_name::()))].into_iter()); + } + } + let assigner = settings .assigner .as_ref() .expect("Could not find context assigner in settings"); debug!("Removing script with id: {}", self.id); - (assigner.remove)(script.context_id, &script, ctxts) + (assigner.remove)(script.context_id, &script, &mut ctxts) } else { bevy::log::error!( "Attempted to delete script with id: {} but it does not exist, doing nothing!", @@ -53,6 +90,8 @@ impl Command for DeleteScript

{ }); world.insert_resource(settings); + world.insert_non_send_resource(ctxts); + world.insert_non_send_resource(runtime_container); } } @@ -80,6 +119,10 @@ impl CreateOrUpdateScript

{ impl Command for CreateOrUpdateScript

{ fn apply(self, world: &mut bevy::prelude::World) { + debug!( + "CreateOrUpdateScript command applying to script_id: {}", + self.id + ); let settings = world .get_resource::>() .unwrap() @@ -90,9 +133,12 @@ impl Command for CreateOrUpdateScript

{ let mut runtime = world .remove_non_send_resource::>() .unwrap(); + + let mut runner = world.get_resource::>().unwrap(); // assign context let assigner = settings.assigner.clone().expect("No context assigner set"); let builder = settings.loader.clone().expect("No context loader set"); + let runner = runner.callback_handler.expect("No callback handler set"); world.resource_scope(|world, mut scripts: Mut| { @@ -111,6 +157,15 @@ impl Command for CreateOrUpdateScript

{ debug!("Context assigned: {:?}", current_context_id); let current_context_id = if let Some(id) = current_context_id { + // reload existing context + let current_context = contexts.get_mut(id).unwrap(); + match (builder.reload)(&self.id, &self.content, current_context, &settings.context_initializers, &settings.context_pre_handling_initializers, world, &mut runtime.runtime) { + Ok(_) => {}, + Err(e) => { + handle_script_errors(world, [e.with_context(format!("Reloading script with id: {}. Runtime type: {}, Context type: {}", self.id, type_name::(), type_name::()))].into_iter()); + return; + } + }; id } else { let ctxt = (builder.load)(&self.id, &self.content, &settings.context_initializers, &settings.context_pre_handling_initializers, world, &mut runtime.runtime); @@ -123,6 +178,7 @@ impl Command for CreateOrUpdateScript

{ } }; + if let Some(previous) = previous_context_id { if previous != current_context_id { debug!( @@ -134,6 +190,13 @@ impl Command for CreateOrUpdateScript

{ } } + let context = contexts.get_mut(current_context_id).expect("Context not found"); + match (runner)(vec![], bevy::ecs::entity::Entity::from_raw(0), &self.id, &OnScriptLoaded::into_callback_label(), context, &settings.context_pre_handling_initializers, &mut runtime.runtime, world) { + Ok(_) => {}, + Err(e) => { + handle_script_errors(world, [e.with_context(format!("Running initialization hook for script with id: {}. Runtime type: {}, Context type: {}", self.id, type_name::(), type_name::()))].into_iter()); + }, + } // now we can insert the actual script scripts.scripts.insert( @@ -144,6 +207,8 @@ impl Command for CreateOrUpdateScript

{ context_id: current_context_id, }, ); + + // finally we trigger on_script_loaded }); world.insert_resource(settings); world.insert_non_send_resource(runtime); diff --git a/crates/bevy_mod_scripting_core/src/context.rs b/crates/bevy_mod_scripting_core/src/context.rs index 48625cc4..154c496a 100644 --- a/crates/bevy_mod_scripting_core/src/context.rs +++ b/crates/bevy_mod_scripting_core/src/context.rs @@ -49,6 +49,14 @@ impl ScriptContexts

{ pub fn remove(&mut self, id: ContextId) -> Option { self.contexts.remove(&id) } + + pub fn get(&self, id: ContextId) -> Option<&P::C> { + self.contexts.get(&id) + } + + pub fn get_mut(&mut self, id: ContextId) -> Option<&mut P::C> { + self.contexts.get_mut(&id) + } } /// Initializer run once after creating a context but before executing it for the first time @@ -93,19 +101,19 @@ pub struct ContextBuilder { pub load: fn( script: &ScriptId, content: &[u8], - &[ContextInitializer

], - &[ContextPreHandlingInitializer

], - &mut World, + context_initializers: &[ContextInitializer

], + pre_handling_initializers: &[ContextPreHandlingInitializer

], + world: &mut World, runtime: &mut P::R, ) -> Result, pub reload: fn( script: &ScriptId, new_content: &[u8], context: &mut P::C, - &[ContextInitializer

], - &[ContextPreHandlingInitializer

], - &mut World, - &mut P::R, + context_initializers: &[ContextInitializer

], + pre_handling_initializers: &[ContextPreHandlingInitializer

], + world: &mut World, + runtime: &mut P::R, ) -> Result<(), ScriptError>, } diff --git a/crates/bevy_mod_scripting_core/src/event.rs b/crates/bevy_mod_scripting_core/src/event.rs index f0d41d76..0cc8fd64 100644 --- a/crates/bevy_mod_scripting_core/src/event.rs +++ b/crates/bevy_mod_scripting_core/src/event.rs @@ -65,6 +65,11 @@ macro_rules! callback_labels { }; } +callback_labels!( + OnScriptLoaded => "on_script_loaded", + OnScriptUnloaded => "on_script_unloaded" +); + pub trait IntoCallbackLabel { fn into_callback_label() -> CallbackLabel; } diff --git a/crates/bevy_mod_scripting_core/src/systems.rs b/crates/bevy_mod_scripting_core/src/systems.rs index 0ef24a40..bf4b4096 100644 --- a/crates/bevy_mod_scripting_core/src/systems.rs +++ b/crates/bevy_mod_scripting_core/src/systems.rs @@ -80,6 +80,7 @@ pub fn sync_script_data( continue; } }; + info!("Creating or updating script with id: {}", script_id); commands.queue(CreateOrUpdateScript::

::new( script_id, asset.content.clone(), diff --git a/examples/lua/game_of_life.rs b/examples/lua/game_of_life.rs index 097a5ab9..27aa2d8e 100644 --- a/examples/lua/game_of_life.rs +++ b/examples/lua/game_of_life.rs @@ -54,14 +54,16 @@ fn run_script_cmd( match command { GameOfLifeCommand::Start => { // create an entity with the script component - bevy::log::info!("Starting game of life!"); + bevy::log::info!( + "Starting game of life spawning entity with the game_of_life.lua script" + ); commands.spawn(ScriptComponent::new( vec!["scripts/game_of_life.lua".into()], )); } GameOfLifeCommand::Stop => { // we can simply drop the handle, or manually delete, I'll just drop the handle - bevy::log::info!("Stopping game of life!"); + bevy::log::info!("Stopping game of life by dropping the handle to the script"); // I am not mapping the handles to the script names, so I'll just clear the entire list loaded_scripts.0.clear(); @@ -90,8 +92,6 @@ pub enum GameOfLifeCommand { fn game_of_life_app(app: &mut App) -> &mut App { app.insert_resource(Time::::from_seconds(UPDATE_FREQUENCY.into())) .add_plugins(( - // for FPS counters - FrameTimeDiagnosticsPlugin, // for scripting LuaScriptingPlugin::default(), ScriptFunctionsPlugin, @@ -205,6 +205,7 @@ pub fn init_game_of_life_state( }); bevy::log::info!("Game of life was initialized. use `gol start` to start the game!"); + bevy::log::info!("Type `help gol` for more commands."); } pub fn sync_window_size( @@ -281,7 +282,6 @@ pub fn send_on_click( let pos = window.cursor_position().unwrap_or_default(); let x = pos.x as u32; let y = pos.y as u32; - bevy::log::info!("Mouse clicked at: ({}, {})", x, y); events.send(ScriptCallbackEvent::new_for_all( OnClick, vec![