Skip to content

Commit 2d9fba8

Browse files
authored
fix!: script contexts being completely overwritten on a re-load (#345)
1 parent 1d79876 commit 2d9fba8

File tree

2 files changed

+156
-33
lines changed
  • crates/languages
    • bevy_mod_scripting_lua/src
    • bevy_mod_scripting_rhai/src

2 files changed

+156
-33
lines changed

crates/languages/bevy_mod_scripting_lua/src/lib.rs

Lines changed: 79 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -156,35 +156,54 @@ impl Plugin for LuaScriptingPlugin {
156156
self.scripting_plugin.finish(app);
157157
}
158158
}
159-
#[profiling::function]
160-
/// Load a lua context from a script
161-
pub fn lua_context_load(
159+
160+
fn load_lua_content_into_context(
161+
context: &mut Lua,
162162
script_id: &ScriptId,
163163
content: &[u8],
164164
initializers: &[ContextInitializer<LuaScriptingPlugin>],
165165
pre_handling_initializers: &[ContextPreHandlingInitializer<LuaScriptingPlugin>],
166-
_: &mut (),
167-
) -> Result<Lua, ScriptError> {
168-
#[cfg(feature = "unsafe_lua_modules")]
169-
let mut context = unsafe { Lua::unsafe_new() };
170-
#[cfg(not(feature = "unsafe_lua_modules"))]
171-
let mut context = Lua::new();
172-
166+
) -> Result<(), ScriptError> {
173167
initializers
174168
.iter()
175-
.try_for_each(|init| init(script_id, &mut context))?;
169+
.try_for_each(|init| init(script_id, context))?;
176170

177171
pre_handling_initializers
178172
.iter()
179-
.try_for_each(|init| init(script_id, Entity::from_raw(0), &mut context))?;
173+
.try_for_each(|init| init(script_id, Entity::from_raw(0), context))?;
180174

181175
context
182176
.load(content)
183177
.exec()
184178
.map_err(ScriptError::from_mlua_error)?;
185179

180+
Ok(())
181+
}
182+
183+
#[profiling::function]
184+
/// Load a lua context from a script
185+
pub fn lua_context_load(
186+
script_id: &ScriptId,
187+
content: &[u8],
188+
initializers: &[ContextInitializer<LuaScriptingPlugin>],
189+
pre_handling_initializers: &[ContextPreHandlingInitializer<LuaScriptingPlugin>],
190+
_: &mut (),
191+
) -> Result<Lua, ScriptError> {
192+
#[cfg(feature = "unsafe_lua_modules")]
193+
let mut context = unsafe { Lua::unsafe_new() };
194+
#[cfg(not(feature = "unsafe_lua_modules"))]
195+
let mut context = Lua::new();
196+
197+
load_lua_content_into_context(
198+
&mut context,
199+
script_id,
200+
content,
201+
initializers,
202+
pre_handling_initializers,
203+
)?;
186204
Ok(context)
187205
}
206+
188207
#[profiling::function]
189208
/// Reload a lua context from a script
190209
pub fn lua_context_reload(
@@ -195,12 +214,12 @@ pub fn lua_context_reload(
195214
pre_handling_initializers: &[ContextPreHandlingInitializer<LuaScriptingPlugin>],
196215
_: &mut (),
197216
) -> Result<(), ScriptError> {
198-
*old_ctxt = lua_context_load(
217+
load_lua_content_into_context(
218+
old_ctxt,
199219
script,
200220
content,
201221
initializers,
202222
pre_handling_initializers,
203-
&mut (),
204223
)?;
205224
Ok(())
206225
}
@@ -243,3 +262,49 @@ pub fn lua_handler(
243262
let out = handler.call::<LuaScriptValue>(input)?;
244263
Ok(out.into())
245264
}
265+
266+
#[cfg(test)]
267+
mod test {
268+
use mlua::Value;
269+
270+
use super::*;
271+
272+
#[test]
273+
fn test_reload_doesnt_overwrite_old_context() {
274+
let lua = Lua::new();
275+
let script_id = ScriptId::from("asd.lua");
276+
let initializers = vec![];
277+
let pre_handling_initializers = vec![];
278+
let mut old_ctxt = lua.clone();
279+
280+
lua_context_load(
281+
&script_id,
282+
"function hello_world_from_first_load()
283+
284+
end"
285+
.as_bytes(),
286+
&initializers,
287+
&pre_handling_initializers,
288+
&mut (),
289+
)
290+
.unwrap();
291+
292+
lua_context_reload(
293+
&script_id,
294+
"function hello_world_from_second_load()
295+
296+
end"
297+
.as_bytes(),
298+
&mut old_ctxt,
299+
&initializers,
300+
&pre_handling_initializers,
301+
&mut (),
302+
)
303+
.unwrap();
304+
305+
// assert both functions exist in globals
306+
let globals = lua.globals();
307+
assert!(globals.get::<Value>("hello_world_from_first_load").is_ok());
308+
assert!(globals.get::<Value>("hello_world_from_second_load").is_ok());
309+
}
310+
}

crates/languages/bevy_mod_scripting_rhai/src/lib.rs

Lines changed: 77 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -179,37 +179,53 @@ impl Plugin for RhaiScriptingPlugin {
179179
}
180180
}
181181

182-
/// Load a rhai context from a script.
183-
pub fn rhai_context_load(
182+
// NEW helper function to load content into an existing context without clearing previous definitions.
183+
fn load_rhai_content_into_context(
184+
context: &mut RhaiScriptContext,
184185
script: &ScriptId,
185186
content: &[u8],
186187
initializers: &[ContextInitializer<RhaiScriptingPlugin>],
187188
pre_handling_initializers: &[ContextPreHandlingInitializer<RhaiScriptingPlugin>],
188189
runtime: &mut RhaiRuntime,
189-
) -> Result<RhaiScriptContext, ScriptError> {
190+
) -> Result<(), ScriptError> {
190191
let mut ast = runtime.compile(std::str::from_utf8(content)?)?;
191192
ast.set_source(script.to_string());
192-
193-
let mut context = RhaiScriptContext {
194-
ast,
195-
scope: Scope::new(),
196-
};
197193
initializers
198194
.iter()
199-
.try_for_each(|init| init(script, &mut context))?;
200-
195+
.try_for_each(|init| init(script, context))?;
201196
pre_handling_initializers
202197
.iter()
203-
.try_for_each(|init| init(script, Entity::from_raw(0), &mut context))?;
204-
205-
runtime.eval_ast_with_scope(&mut context.scope, &context.ast)?;
206-
// do not invoke top level statements after the first time we run the script
207-
context.ast.clear_statements();
198+
.try_for_each(|init| init(script, Entity::from_raw(0), context))?;
199+
runtime.eval_ast_with_scope(&mut context.scope, &ast)?;
200+
// Unlike before, do not clear statements so that definitions persist.
201+
Ok(())
202+
}
208203

204+
/// Load a rhai context from a script.
205+
pub fn rhai_context_load(
206+
script: &ScriptId,
207+
content: &[u8],
208+
initializers: &[ContextInitializer<RhaiScriptingPlugin>],
209+
pre_handling_initializers: &[ContextPreHandlingInitializer<RhaiScriptingPlugin>],
210+
runtime: &mut RhaiRuntime,
211+
) -> Result<RhaiScriptContext, ScriptError> {
212+
let mut context = RhaiScriptContext {
213+
// Using an empty AST as a placeholder.
214+
ast: AST::empty(),
215+
scope: Scope::new(),
216+
};
217+
load_rhai_content_into_context(
218+
&mut context,
219+
script,
220+
content,
221+
initializers,
222+
pre_handling_initializers,
223+
runtime,
224+
)?;
209225
Ok(context)
210226
}
211227

212-
/// Reload a rhai context from a script.
228+
/// Reload a rhai context from a script. New content is appended to the existing context.
213229
pub fn rhai_context_reload(
214230
script: &ScriptId,
215231
content: &[u8],
@@ -218,14 +234,14 @@ pub fn rhai_context_reload(
218234
pre_handling_initializers: &[ContextPreHandlingInitializer<RhaiScriptingPlugin>],
219235
runtime: &mut RhaiRuntime,
220236
) -> Result<(), ScriptError> {
221-
*context = rhai_context_load(
237+
load_rhai_content_into_context(
238+
context,
222239
script,
223240
content,
224241
initializers,
225242
pre_handling_initializers,
226243
runtime,
227-
)?;
228-
Ok(())
244+
)
229245
}
230246

231247
#[allow(clippy::too_many_arguments)]
@@ -278,3 +294,45 @@ pub fn rhai_callback_handler(
278294
}
279295
}
280296
}
297+
298+
#[cfg(test)]
299+
mod test {
300+
use super::*;
301+
302+
#[test]
303+
fn test_reload_doesnt_overwrite_old_context() {
304+
let mut runtime = RhaiRuntime::new();
305+
let script_id = ScriptId::from("asd.rhai");
306+
let initializers: Vec<ContextInitializer<RhaiScriptingPlugin>> = vec![];
307+
let pre_handling_initializers: Vec<ContextPreHandlingInitializer<RhaiScriptingPlugin>> =
308+
vec![];
309+
310+
// Load first content defining a function that returns 42.
311+
let mut context = rhai_context_load(
312+
&script_id,
313+
b"let hello = 2;",
314+
&initializers,
315+
&pre_handling_initializers,
316+
&mut runtime,
317+
)
318+
.unwrap();
319+
320+
// Reload with additional content defining a second function that returns 24.
321+
rhai_context_reload(
322+
&script_id,
323+
b"let hello2 = 3",
324+
&mut context,
325+
&initializers,
326+
&pre_handling_initializers,
327+
&mut runtime,
328+
)
329+
.unwrap();
330+
331+
// get first var
332+
let hello = context.scope.get_value::<i64>("hello").unwrap();
333+
assert_eq!(hello, 2);
334+
// get second var
335+
let hello2 = context.scope.get_value::<i64>("hello2").unwrap();
336+
assert_eq!(hello2, 3);
337+
}
338+
}

0 commit comments

Comments
 (0)