Skip to content

Commit 7d37342

Browse files
authored
Add runtime state hook (#28)
1 parent 1e08639 commit 7d37342

File tree

25 files changed

+508
-305
lines changed

25 files changed

+508
-305
lines changed

api_gen_config.toml

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
2-
31
imports = """
42
#[cfg(feature="lua")]
53
use {
@@ -25,6 +23,41 @@ other = """
2523
crate::impl_tealr_generic!(pub(crate) struct T);
2624
"""
2725

26+
lua_api_defaults = """
27+
fn setup_script(
28+
&mut self,
29+
script_data: &ScriptData,
30+
ctx: &mut Self::ScriptContext,
31+
) -> Result<(), ScriptError> {
32+
let ctx = ctx.get_mut().expect("Could not get context");
33+
let globals = ctx.globals();
34+
globals
35+
.set(
36+
"entity",
37+
crate::lua::bevy::LuaEntity::new(script_data.entity),
38+
)
39+
.map_err(ScriptError::new_other)?;
40+
globals
41+
.set::<_, crate::lua::bevy::LuaScriptData>("script", script_data.into())
42+
.map_err(ScriptError::new_other)?;
43+
44+
Ok(())
45+
}
46+
47+
fn setup_script_runtime(
48+
&mut self,
49+
world_ptr: bevy_mod_scripting_core::world::WorldPointer,
50+
_script_data: &ScriptData,
51+
ctx: &mut Self::ScriptContext,
52+
) -> Result<(), ScriptError> {
53+
let ctx = ctx.get_mut().expect("Could not get context");
54+
let globals = ctx.globals();
55+
globals
56+
.set("world", crate::lua::bevy::LuaWorld::new(world_ptr))
57+
.map_err(ScriptError::new_other)
58+
}
59+
"""
60+
2861
primitives = ["usize","isize","f32","f64","u128","u64","u32","u16","u8","i128","i64","i32","i16","i8","String","bool"]
2962

3063

assets/scripts/event_recipients.lua

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
2-
31
function on_event(id)
4-
print(string.format("on_event, script_id: %d, Handling:",script.sid))
2+
print(string.format("on_event, script_id: %d, Handling:", script_id))
53
print(string.format("\t-> id: %d", id))
6-
end
4+
end

bevy_api_gen/src/config.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ pub struct Config {
3232
pub imports: String,
3333
pub other: String,
3434

35+
pub lua_api_defaults: String,
36+
3537
/// Describes the set of non generic things which are representible
3638
/// as simple lua types and don't need UserData proxies
3739
pub primitives: HashSet<String>,

bevy_api_gen/src/main.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,11 @@ pub(crate) fn generate_macros(
300300
writer.close_brace();
301301
// } get_doc_fragment
302302

303+
// impl default members
304+
for line in config.lua_api_defaults.lines() {
305+
writer.write_line(line);
306+
}
307+
303308
// register_with_app {
304309
writer.write_no_newline("fn register_with_app(&self, app: &mut App)");
305310
writer.open_brace();

bevy_mod_scripting_common/src/arg.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ pub enum ArgType {
182182
Self_(SelfType),
183183
}
184184

185-
#[allow(clippy::eval_order_dependence)]
185+
#[allow(clippy::mixed_read_write_in_expression)]
186186
impl Parse for ArgType {
187187
fn parse(input: ParseStream) -> Result<Self, syn::Error> {
188188
if input.peek(Ident) {

bevy_mod_scripting_common/src/derive_flag.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ pub enum DeriveFlag {
8484
// pub args: Punctuated<MethodMacroArg, Token![,]>,
8585
// }
8686

87-
// #[allow(clippy::eval_order_dependence)]
87+
// #[allow(clippy::mixed_read_write_in_expression)]
8888
// impl Parse for MethodMacroInvokation {
8989
// fn parse(input: ParseStream) -> Result<Self, syn::Error> {
9090
// let f;
@@ -122,7 +122,7 @@ impl ToTokens for AutoMethod {
122122
}
123123
}
124124

125-
#[allow(clippy::eval_order_dependence)]
125+
#[allow(clippy::mixed_read_write_in_expression)]
126126
impl Parse for AutoMethod {
127127
fn parse(input: ParseStream) -> Result<Self, syn::Error> {
128128
let f;

bevy_mod_scripting_common/src/newtype.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ impl ToTokens for WrapperFunctionList {
139139
}
140140
}
141141

142-
#[allow(clippy::eval_order_dependence)]
142+
#[allow(clippy::mixed_read_write_in_expression)]
143143
impl Parse for WrapperFunctionList {
144144
fn parse(input: ParseStream) -> Result<Self, syn::Error> {
145145
let f;

bevy_mod_scripting_common/src/utils.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ macro_rules! impl_parse_enum {
4040
),*
4141

4242
}
43-
#[allow(clippy::eval_order_dependence)]
43+
#[allow(clippy::mixed_read_write_in_expression)]
4444
impl Parse for $name {
4545
fn parse($input_stream: ParseStream) -> Result<Self,syn::Error> {
4646
let $parsed_ident : syn::Ident = $input_stream.parse()?;

bevy_mod_scripting_core/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,6 @@ bevy = { version = "0.8.0", default-features = false, features=["bevy_asset","be
3232
bevy_event_priority = {path = "../bevy_event_priority", version = "0.1.1"}
3333
thiserror = "1.0.31"
3434
paste = "1.0.7"
35-
35+
parking_lot = "0.12.1"
3636

3737

bevy_mod_scripting_core/src/event.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
use crate::{error::ScriptError, hosts::Recipients};
2+
3+
/// An error coming from a script
4+
#[derive(Debug)]
5+
pub struct ScriptErrorEvent {
6+
pub error: ScriptError,
7+
}
8+
9+
/// An event emitted when a script was loaded or re-loaded (with a hot-reload),
10+
/// guaranteed to be sent for every script at least once and immediately after it's loaded.
11+
#[derive(Clone, Debug)]
12+
pub struct ScriptLoaded {
13+
pub sid: u32,
14+
}
15+
16+
/// A trait for events to be handled by scripts
17+
pub trait ScriptEvent: Send + Sync + Clone + 'static {
18+
/// Retrieves the recipient scripts for this event
19+
fn recipients(&self) -> &Recipients;
20+
}

bevy_mod_scripting_core/src/hosts.rs

Lines changed: 68 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,17 @@
22
use bevy::{asset::Asset, prelude::*, reflect::FromReflect};
33
use std::{
44
collections::HashMap,
5+
iter::once,
56
sync::atomic::{AtomicU32, Ordering},
67
};
78

8-
use crate::{asset::CodeAsset, docs::DocFragment, error::ScriptError};
9+
use crate::{
10+
asset::CodeAsset,
11+
docs::DocFragment,
12+
error::ScriptError,
13+
event::{ScriptEvent, ScriptLoaded},
14+
world::WorldPointer,
15+
};
916

1017
/// Describes the target set of scripts this event should
1118
/// be handled by
@@ -30,7 +37,7 @@ pub struct ScriptData<'a> {
3037
}
3138

3239
impl Recipients {
33-
/// Returns true if the given script should
40+
/// Returns true if the given script is a recipient
3441
pub fn is_recipient(&self, c: &ScriptData) -> bool {
3542
match self {
3643
Recipients::All => true,
@@ -47,11 +54,6 @@ impl Default for Recipients {
4754
}
4855
}
4956

50-
pub trait ScriptEvent: Send + Sync + Clone + 'static {
51-
/// Retrieves the recipient scripts for this event
52-
fn recipients(&self) -> &Recipients;
53-
}
54-
5557
/// A script host is the interface between your rust application
5658
/// and the scripts in some interpreted language.
5759
pub trait ScriptHost: Send + Sync + 'static + Default {
@@ -76,18 +78,27 @@ pub trait ScriptHost: Send + Sync + 'static + Default {
7678
providers: &mut APIProviders<Self>,
7779
) -> Result<Self::ScriptContext, ScriptError>;
7880

81+
/// Perform one-off initialization of scripts (happens for every new or re-loaded script)
82+
fn setup_script(
83+
&mut self,
84+
script_data: &ScriptData,
85+
ctx: &mut Self::ScriptContext,
86+
providers: &mut APIProviders<Self>,
87+
) -> Result<(), ScriptError>;
88+
7989
/// the main point of contact with the bevy world.
8090
/// Scripts are called with appropriate events in the event order
8191
fn handle_events<'a>(
8292
&self,
83-
world: &mut World,
93+
world_ptr: &mut World,
8494
events: &[Self::ScriptEvent],
8595
ctxs: impl Iterator<Item = (ScriptData<'a>, &'a mut Self::ScriptContext)>,
96+
providers: &mut APIProviders<Self>,
8697
);
8798

8899
/// Loads and runs script instantaneously without storing any script data into the world.
89-
/// The script receives the `world` global as normal, but `entity` is set to `u64::MAX`.
90-
/// The script id is set to `u32::MAX`.
100+
/// The host receives a fake script with `entity` set to `u64::MAX`
101+
/// and where the script id is set to `u32::MAX`.
91102
fn run_one_shot(
92103
&mut self,
93104
script: &[u8],
@@ -101,13 +112,15 @@ pub trait ScriptHost: Send + Sync + 'static + Default {
101112
entity: Entity::from_bits(u64::MAX),
102113
};
103114

104-
let providers: &mut APIProviders<Self> = &mut world.resource_mut();
105-
let mut ctx = self.load_script(script, &fd, providers).unwrap();
106-
115+
let mut providers: APIProviders<Self> = world.remove_resource().unwrap();
116+
let mut ctx = self.load_script(script, &fd, &mut providers).unwrap();
117+
self.setup_script(&fd, &mut ctx, &mut providers)?;
107118
let events = [event; 1];
108-
let ctx_iter = [(fd, &mut ctx); 1].into_iter();
109119

110-
self.handle_events(world, &events, ctx_iter);
120+
self.handle_events(world, &events, once((fd, &mut ctx)), &mut providers);
121+
122+
world.insert_resource(providers);
123+
111124
Ok(())
112125
}
113126

@@ -133,6 +146,16 @@ pub trait APIProvider: 'static + Send + Sync {
133146
/// engine. For one-time setup use `Self::setup_script`
134147
fn attach_api(&mut self, ctx: &mut Self::APITarget) -> Result<(), ScriptError>;
135148

149+
/// Hook executed every time a script is about to handle events, most notably used to "refresh" world pointers
150+
fn setup_script_runtime(
151+
&mut self,
152+
_world_ptr: WorldPointer,
153+
_script_data: &ScriptData,
154+
_ctx: &mut Self::ScriptContext,
155+
) -> Result<(), ScriptError> {
156+
Ok(())
157+
}
158+
136159
/// Setup meant to be executed once for every single script. Use this if you need to consistently setup scripts.
137160
/// For API's use `Self::attach_api` instead.
138161
fn setup_script(
@@ -184,6 +207,19 @@ impl<T: ScriptHost> APIProviders<T> {
184207
Ok(())
185208
}
186209

210+
pub fn setup_runtime_all(
211+
&mut self,
212+
world_ptr: WorldPointer,
213+
script_data: &ScriptData,
214+
ctx: &mut T::ScriptContext,
215+
) -> Result<(), ScriptError> {
216+
for p in self.providers.iter_mut() {
217+
p.setup_script_runtime(world_ptr.clone(), script_data, ctx)?;
218+
}
219+
220+
Ok(())
221+
}
222+
187223
pub fn setup_all(
188224
&mut self,
189225
script_data: &ScriptData,
@@ -253,6 +289,10 @@ impl<C> ScriptContexts<C> {
253289
.get(&script_id)
254290
.map_or(false, |(_, c, _)| c.is_some())
255291
}
292+
293+
pub fn is_empty(&self) -> bool {
294+
self.context_entities.is_empty()
295+
}
256296
}
257297

258298
/// A struct defining an instance of a script asset.
@@ -309,6 +349,7 @@ impl<T: Asset> Script<T> {
309349
script_assets: &Assets<H::ScriptAsset>,
310350
providers: &mut APIProviders<H>,
311351
contexts: &mut ScriptContexts<H::ScriptContext>,
352+
event_writer: &mut EventWriter<ScriptLoaded>,
312353
) {
313354
debug!("reloading script {}", script.id);
314355
// retrieve owning entity
@@ -325,17 +366,21 @@ impl<T: Asset> Script<T> {
325366
script_assets,
326367
providers,
327368
contexts,
369+
event_writer,
328370
);
329371
}
330372

331-
/// inserts a new script context for the given script
373+
/// checks if a script has loaded, and if so loads (`ScriptHost::load_script`),
374+
/// sets up (`ScriptHost::setup_script`) and inserts its new context into the contexts resource
375+
/// otherwise inserts None. Sends ScriptLoaded event if the script was loaded
332376
pub(crate) fn insert_new_script_context<H: ScriptHost>(
333377
host: &mut H,
334378
new_script: &Script<H::ScriptAsset>,
335379
entity: Entity,
336380
script_assets: &Assets<H::ScriptAsset>,
337381
providers: &mut APIProviders<H>,
338382
contexts: &mut ScriptContexts<H::ScriptContext>,
383+
event_writer: &mut EventWriter<ScriptLoaded>,
339384
) {
340385
let fd = ScriptData {
341386
sid: new_script.id(),
@@ -355,14 +400,19 @@ impl<T: Asset> Script<T> {
355400
debug!("Inserted script {:?}", fd);
356401

357402
match host.load_script(script.bytes(), &fd, providers) {
358-
Ok(ctx) => {
403+
Ok(mut ctx) => {
404+
host.setup_script(&fd, &mut ctx, providers)
405+
.expect("Failed to setup script");
359406
contexts.insert_context(fd, Some(ctx));
407+
event_writer.send(ScriptLoaded {
408+
sid: new_script.id(),
409+
});
360410
}
361411
Err(e) => {
362412
warn! {"Error in loading script {}:\n{}", &new_script.name,e}
363413
// this script will now never execute, unless manually reloaded
364414
// but contexts are left in a valid state
365-
contexts.insert_context(fd, None)
415+
contexts.insert_context(fd, None);
366416
}
367417
}
368418
}

0 commit comments

Comments
 (0)