Skip to content

Commit 0307fb9

Browse files
committed
add on load and unload hooks
1 parent d52d896 commit 0307fb9

File tree

6 files changed

+124
-26
lines changed

6 files changed

+124
-26
lines changed

assets/scripts/game_of_life.lua

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,40 @@ LifeState = world.get_type_by_name("LifeState")
22
Settings = world.get_type_by_name("Settings")
33

44
world.info("Lua: The game_of_life.lua script just got loaded")
5-
world.info("Lua: Hello! I am initiating the game of life simulation state with randomness!")
65

76
math.randomseed(os.time())
87

98
function fetch_life_state()
109
-- find the entity with life state
1110
local life_state = nil
11+
1212
for i,result in pairs(world.query({LifeState}):build()) do
1313
life_state = result:components()[1]
1414
break
1515
end
1616
return life_state
17-
end
18-
19-
local life_state = fetch_life_state()
20-
local cells = life_state.cells
17+
end
2118

22-
-- set some cells alive
23-
for _=1,1000 do
24-
local index = math.random(#cells)
25-
cells[index] = 255
26-
end
19+
function on_script_loaded()
20+
world.info("Lua: Hello! I am initiating the game of life simulation state with randomness!")
21+
world.info("Lua: Click on the screen to set cells alive")
22+
23+
local life_state = fetch_life_state()
24+
local cells = life_state.cells
25+
26+
-- set some cells alive
27+
for _=1,1000 do
28+
local index = math.random(#cells)
29+
cells[index] = 255
30+
end
31+
end
2732

2833
function on_click(x,y)
2934
-- get the settings
3035
world.info("Lua: Clicked at x: " .. x .. " y: " .. y)
36+
local life_state = fetch_life_state()
37+
local cells = life_state.cells
38+
3139
local settings = world.get_resource(Settings)
3240
local dimensions = settings.physical_grid_dimensions
3341
local screen = settings.display_grid_dimensions
@@ -83,4 +91,15 @@ function on_update()
8391
cells[i] = 0
8492
end
8593
end
94+
end
95+
96+
function on_script_unloaded()
97+
world.info("Lua: I am being unloaded, goodbye!")
98+
99+
-- set state to 0's
100+
local life_state = fetch_life_state()
101+
local cells = life_state.cells
102+
for i=1,#cells do
103+
cells[i] = 0
104+
end
86105
end

crates/bevy_mod_scripting_core/src/commands.rs

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
use std::{any::type_name, marker::PhantomData};
22

3-
use bevy::{asset::Handle, ecs::world::Mut, log::debug, prelude::Command};
3+
use bevy::{
4+
asset::Handle,
5+
ecs::world::Mut,
6+
log::{debug, info},
7+
prelude::Command,
8+
};
49

510
use crate::{
611
asset::ScriptAsset,
712
context::{Context, ContextLoadingSettings, ScriptContexts},
13+
event::{IntoCallbackLabel, OnScriptLoaded, OnScriptUnloaded},
14+
handler::CallbackSettings,
815
prelude::{Runtime, RuntimeContainer},
916
script::{Script, ScriptId, Scripts},
1017
systems::handle_script_errors,
@@ -33,17 +40,47 @@ impl<P: IntoScriptPluginParams> Command for DeleteScript<P> {
3340
.expect("No ScriptLoadingSettings resource found")
3441
.clone();
3542

43+
let runner = world
44+
.get_resource::<CallbackSettings<P>>()
45+
.expect("No CallbackSettings resource found")
46+
.callback_handler
47+
.expect("No callback handler set");
48+
49+
let mut ctxts = world
50+
.remove_non_send_resource::<ScriptContexts<P>>()
51+
.unwrap();
52+
53+
let mut runtime_container = world
54+
.remove_non_send_resource::<RuntimeContainer<P>>()
55+
.unwrap();
56+
3657
world.resource_scope(|world, mut scripts: Mut<Scripts>| {
3758
if let Some(script) = scripts.scripts.remove(&self.id) {
3859
debug!("Deleting script with id: {}", self.id);
39-
let mut ctxts = world.get_non_send_resource_mut::<ScriptContexts<P>>();
40-
let ctxts = ctxts.as_deref_mut().unwrap();
60+
61+
// first let the script uninstall itself
62+
match (runner)(
63+
vec![],
64+
bevy::ecs::entity::Entity::from_raw(0),
65+
&self.id,
66+
&OnScriptUnloaded::into_callback_label(),
67+
ctxts.get_mut(script.context_id).unwrap(),
68+
&settings.context_pre_handling_initializers,
69+
&mut runtime_container.runtime,
70+
world,
71+
) {
72+
Ok(_) => {},
73+
Err(e) => {
74+
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());
75+
}
76+
}
77+
4178
let assigner = settings
4279
.assigner
4380
.as_ref()
4481
.expect("Could not find context assigner in settings");
4582
debug!("Removing script with id: {}", self.id);
46-
(assigner.remove)(script.context_id, &script, ctxts)
83+
(assigner.remove)(script.context_id, &script, &mut ctxts)
4784
} else {
4885
bevy::log::error!(
4986
"Attempted to delete script with id: {} but it does not exist, doing nothing!",
@@ -53,6 +90,8 @@ impl<P: IntoScriptPluginParams> Command for DeleteScript<P> {
5390
});
5491

5592
world.insert_resource(settings);
93+
world.insert_non_send_resource(ctxts);
94+
world.insert_non_send_resource(runtime_container);
5695
}
5796
}
5897

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

81120
impl<P: IntoScriptPluginParams> Command for CreateOrUpdateScript<P> {
82121
fn apply(self, world: &mut bevy::prelude::World) {
122+
debug!(
123+
"CreateOrUpdateScript command applying to script_id: {}",
124+
self.id
125+
);
83126
let settings = world
84127
.get_resource::<ContextLoadingSettings<P>>()
85128
.unwrap()
@@ -90,9 +133,12 @@ impl<P: IntoScriptPluginParams> Command for CreateOrUpdateScript<P> {
90133
let mut runtime = world
91134
.remove_non_send_resource::<RuntimeContainer<P>>()
92135
.unwrap();
136+
137+
let mut runner = world.get_resource::<CallbackSettings<P>>().unwrap();
93138
// assign context
94139
let assigner = settings.assigner.clone().expect("No context assigner set");
95140
let builder = settings.loader.clone().expect("No context loader set");
141+
let runner = runner.callback_handler.expect("No callback handler set");
96142

97143
world.resource_scope(|world, mut scripts: Mut<Scripts>| {
98144

@@ -111,6 +157,15 @@ impl<P: IntoScriptPluginParams> Command for CreateOrUpdateScript<P> {
111157
debug!("Context assigned: {:?}", current_context_id);
112158

113159
let current_context_id = if let Some(id) = current_context_id {
160+
// reload existing context
161+
let current_context = contexts.get_mut(id).unwrap();
162+
match (builder.reload)(&self.id, &self.content, current_context, &settings.context_initializers, &settings.context_pre_handling_initializers, world, &mut runtime.runtime) {
163+
Ok(_) => {},
164+
Err(e) => {
165+
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());
166+
return;
167+
}
168+
};
114169
id
115170
} else {
116171
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<P: IntoScriptPluginParams> Command for CreateOrUpdateScript<P> {
123178
}
124179
};
125180

181+
126182
if let Some(previous) = previous_context_id {
127183
if previous != current_context_id {
128184
debug!(
@@ -134,6 +190,13 @@ impl<P: IntoScriptPluginParams> Command for CreateOrUpdateScript<P> {
134190
}
135191
}
136192

193+
let context = contexts.get_mut(current_context_id).expect("Context not found");
194+
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) {
195+
Ok(_) => {},
196+
Err(e) => {
197+
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());
198+
},
199+
}
137200

138201
// now we can insert the actual script
139202
scripts.scripts.insert(
@@ -144,6 +207,8 @@ impl<P: IntoScriptPluginParams> Command for CreateOrUpdateScript<P> {
144207
context_id: current_context_id,
145208
},
146209
);
210+
211+
// finally we trigger on_script_loaded
147212
});
148213
world.insert_resource(settings);
149214
world.insert_non_send_resource(runtime);

crates/bevy_mod_scripting_core/src/context.rs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ impl<P: IntoScriptPluginParams> ScriptContexts<P> {
4949
pub fn remove(&mut self, id: ContextId) -> Option<P::C> {
5050
self.contexts.remove(&id)
5151
}
52+
53+
pub fn get(&self, id: ContextId) -> Option<&P::C> {
54+
self.contexts.get(&id)
55+
}
56+
57+
pub fn get_mut(&mut self, id: ContextId) -> Option<&mut P::C> {
58+
self.contexts.get_mut(&id)
59+
}
5260
}
5361

5462
/// Initializer run once after creating a context but before executing it for the first time
@@ -93,19 +101,19 @@ pub struct ContextBuilder<P: IntoScriptPluginParams> {
93101
pub load: fn(
94102
script: &ScriptId,
95103
content: &[u8],
96-
&[ContextInitializer<P>],
97-
&[ContextPreHandlingInitializer<P>],
98-
&mut World,
104+
context_initializers: &[ContextInitializer<P>],
105+
pre_handling_initializers: &[ContextPreHandlingInitializer<P>],
106+
world: &mut World,
99107
runtime: &mut P::R,
100108
) -> Result<P::C, ScriptError>,
101109
pub reload: fn(
102110
script: &ScriptId,
103111
new_content: &[u8],
104112
context: &mut P::C,
105-
&[ContextInitializer<P>],
106-
&[ContextPreHandlingInitializer<P>],
107-
&mut World,
108-
&mut P::R,
113+
context_initializers: &[ContextInitializer<P>],
114+
pre_handling_initializers: &[ContextPreHandlingInitializer<P>],
115+
world: &mut World,
116+
runtime: &mut P::R,
109117
) -> Result<(), ScriptError>,
110118
}
111119

crates/bevy_mod_scripting_core/src/event.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ macro_rules! callback_labels {
6565
};
6666
}
6767

68+
callback_labels!(
69+
OnScriptLoaded => "on_script_loaded",
70+
OnScriptUnloaded => "on_script_unloaded"
71+
);
72+
6873
pub trait IntoCallbackLabel {
6974
fn into_callback_label() -> CallbackLabel;
7075
}

crates/bevy_mod_scripting_core/src/systems.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ pub fn sync_script_data<P: IntoScriptPluginParams>(
8080
continue;
8181
}
8282
};
83+
info!("Creating or updating script with id: {}", script_id);
8384
commands.queue(CreateOrUpdateScript::<P>::new(
8485
script_id,
8586
asset.content.clone(),

examples/lua/game_of_life.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,16 @@ fn run_script_cmd(
5454
match command {
5555
GameOfLifeCommand::Start => {
5656
// create an entity with the script component
57-
bevy::log::info!("Starting game of life!");
57+
bevy::log::info!(
58+
"Starting game of life spawning entity with the game_of_life.lua script"
59+
);
5860
commands.spawn(ScriptComponent::new(
5961
vec!["scripts/game_of_life.lua".into()],
6062
));
6163
}
6264
GameOfLifeCommand::Stop => {
6365
// we can simply drop the handle, or manually delete, I'll just drop the handle
64-
bevy::log::info!("Stopping game of life!");
66+
bevy::log::info!("Stopping game of life by dropping the handle to the script");
6567

6668
// I am not mapping the handles to the script names, so I'll just clear the entire list
6769
loaded_scripts.0.clear();
@@ -90,8 +92,6 @@ pub enum GameOfLifeCommand {
9092
fn game_of_life_app(app: &mut App) -> &mut App {
9193
app.insert_resource(Time::<Fixed>::from_seconds(UPDATE_FREQUENCY.into()))
9294
.add_plugins((
93-
// for FPS counters
94-
FrameTimeDiagnosticsPlugin,
9595
// for scripting
9696
LuaScriptingPlugin::default(),
9797
ScriptFunctionsPlugin,
@@ -205,6 +205,7 @@ pub fn init_game_of_life_state(
205205
});
206206

207207
bevy::log::info!("Game of life was initialized. use `gol start` to start the game!");
208+
bevy::log::info!("Type `help gol` for more commands.");
208209
}
209210

210211
pub fn sync_window_size(
@@ -281,7 +282,6 @@ pub fn send_on_click(
281282
let pos = window.cursor_position().unwrap_or_default();
282283
let x = pos.x as u32;
283284
let y = pos.y as u32;
284-
bevy::log::info!("Mouse clicked at: ({}, {})", x, y);
285285
events.send(ScriptCallbackEvent::new_for_all(
286286
OnClick,
287287
vec![

0 commit comments

Comments
 (0)