Skip to content

Commit

Permalink
add on load and unload hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
makspll committed Jan 4, 2025
1 parent d52d896 commit 0307fb9
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 26 deletions.
39 changes: 29 additions & 10 deletions assets/scripts/game_of_life.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
73 changes: 69 additions & 4 deletions crates/bevy_mod_scripting_core/src/commands.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -33,17 +40,47 @@ impl<P: IntoScriptPluginParams> Command for DeleteScript<P> {
.expect("No ScriptLoadingSettings resource found")
.clone();

let runner = world
.get_resource::<CallbackSettings<P>>()
.expect("No CallbackSettings resource found")
.callback_handler
.expect("No callback handler set");

let mut ctxts = world
.remove_non_send_resource::<ScriptContexts<P>>()
.unwrap();

let mut runtime_container = world
.remove_non_send_resource::<RuntimeContainer<P>>()
.unwrap();

world.resource_scope(|world, mut scripts: Mut<Scripts>| {
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::<ScriptContexts<P>>();
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::<P::R>(), type_name::<P::C>()))].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!",
Expand All @@ -53,6 +90,8 @@ impl<P: IntoScriptPluginParams> Command for DeleteScript<P> {
});

world.insert_resource(settings);
world.insert_non_send_resource(ctxts);
world.insert_non_send_resource(runtime_container);
}
}

Expand Down Expand Up @@ -80,6 +119,10 @@ impl<P: IntoScriptPluginParams> CreateOrUpdateScript<P> {

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

let mut runner = world.get_resource::<CallbackSettings<P>>().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<Scripts>| {

Expand All @@ -111,6 +157,15 @@ impl<P: IntoScriptPluginParams> Command for CreateOrUpdateScript<P> {
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::<P::R>(), type_name::<P::C>()))].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);
Expand All @@ -123,6 +178,7 @@ impl<P: IntoScriptPluginParams> Command for CreateOrUpdateScript<P> {
}
};


if let Some(previous) = previous_context_id {
if previous != current_context_id {
debug!(
Expand All @@ -134,6 +190,13 @@ impl<P: IntoScriptPluginParams> Command for CreateOrUpdateScript<P> {
}
}

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::<P::R>(), type_name::<P::C>()))].into_iter());
},
}

// now we can insert the actual script
scripts.scripts.insert(
Expand All @@ -144,6 +207,8 @@ impl<P: IntoScriptPluginParams> Command for CreateOrUpdateScript<P> {
context_id: current_context_id,
},
);

// finally we trigger on_script_loaded
});
world.insert_resource(settings);
world.insert_non_send_resource(runtime);
Expand Down
22 changes: 15 additions & 7 deletions crates/bevy_mod_scripting_core/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ impl<P: IntoScriptPluginParams> ScriptContexts<P> {
pub fn remove(&mut self, id: ContextId) -> Option<P::C> {
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
Expand Down Expand Up @@ -93,19 +101,19 @@ pub struct ContextBuilder<P: IntoScriptPluginParams> {
pub load: fn(
script: &ScriptId,
content: &[u8],
&[ContextInitializer<P>],
&[ContextPreHandlingInitializer<P>],
&mut World,
context_initializers: &[ContextInitializer<P>],
pre_handling_initializers: &[ContextPreHandlingInitializer<P>],
world: &mut World,
runtime: &mut P::R,
) -> Result<P::C, ScriptError>,
pub reload: fn(
script: &ScriptId,
new_content: &[u8],
context: &mut P::C,
&[ContextInitializer<P>],
&[ContextPreHandlingInitializer<P>],
&mut World,
&mut P::R,
context_initializers: &[ContextInitializer<P>],
pre_handling_initializers: &[ContextPreHandlingInitializer<P>],
world: &mut World,
runtime: &mut P::R,
) -> Result<(), ScriptError>,
}

Expand Down
5 changes: 5 additions & 0 deletions crates/bevy_mod_scripting_core/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_mod_scripting_core/src/systems.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ pub fn sync_script_data<P: IntoScriptPluginParams>(
continue;
}
};
info!("Creating or updating script with id: {}", script_id);
commands.queue(CreateOrUpdateScript::<P>::new(
script_id,
asset.content.clone(),
Expand Down
10 changes: 5 additions & 5 deletions examples/lua/game_of_life.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -90,8 +92,6 @@ pub enum GameOfLifeCommand {
fn game_of_life_app(app: &mut App) -> &mut App {
app.insert_resource(Time::<Fixed>::from_seconds(UPDATE_FREQUENCY.into()))
.add_plugins((
// for FPS counters
FrameTimeDiagnosticsPlugin,
// for scripting
LuaScriptingPlugin::default(),
ScriptFunctionsPlugin,
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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![
Expand Down

0 comments on commit 0307fb9

Please sign in to comment.