Skip to content

Commit e284705

Browse files
committed
remove event handler panics
1 parent f12a039 commit e284705

File tree

4 files changed

+136
-40
lines changed

4 files changed

+136
-40
lines changed

crates/bevy_mod_scripting_core/src/error.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,27 @@ impl From<mlua::Error> for ScriptError {
241241
// }
242242
// }
243243

244+
#[derive(Clone, Debug, PartialEq)]
245+
pub struct MissingResourceError(&'static str);
246+
247+
impl MissingResourceError {
248+
pub fn new<R>() -> Self {
249+
Self(std::any::type_name::<R>())
250+
}
251+
}
252+
253+
impl Display for MissingResourceError {
254+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
255+
write!(
256+
f,
257+
"Missing resource: {}. Was the plugin initialized correctly?",
258+
self.0
259+
)
260+
}
261+
}
262+
263+
impl std::error::Error for MissingResourceError {}
264+
244265
#[derive(Debug, Clone, PartialEq, Reflect)]
245266
pub struct InteropError(#[reflect(ignore)] Arc<InteropErrorInner>);
246267

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//! Systems which are used to extract the various resources and components used by BMS.
2+
//!
3+
//! These are designed to be used to pipe inputs into other systems which require them, while handling any configuration erorrs nicely.
4+
5+
use bevy::prelude::World;
6+
7+
use crate::{
8+
context::{ContextLoadingSettings, ScriptContexts},
9+
error::MissingResourceError,
10+
handler::CallbackSettings,
11+
runtime::RuntimeContainer,
12+
script::Scripts,
13+
IntoScriptPluginParams,
14+
};
15+
16+
/// Context for systems which handle events for scripts
17+
pub(crate) struct HandlerContext<P: IntoScriptPluginParams> {
18+
pub callback_settings: CallbackSettings<P>,
19+
pub context_loading_settings: ContextLoadingSettings<P>,
20+
pub scripts: Scripts,
21+
pub runtime_container: RuntimeContainer<P>,
22+
pub script_contexts: ScriptContexts<P>,
23+
}
24+
25+
pub(crate) fn extract_handler_context<P: IntoScriptPluginParams>(
26+
world: &mut World,
27+
) -> Result<HandlerContext<P>, MissingResourceError> {
28+
// we don't worry about re-inserting these resources if we fail to extract them, as the plugin is misconfigured anyway,
29+
// so the only solution is to stop the program and fix the configuration
30+
// the config is either all in or nothing
31+
32+
let callback_settings = world
33+
.remove_resource::<CallbackSettings<P>>()
34+
.ok_or_else(MissingResourceError::new::<CallbackSettings<P>>)?;
35+
let context_loading_settings = world
36+
.remove_resource::<ContextLoadingSettings<P>>()
37+
.ok_or_else(MissingResourceError::new::<ContextLoadingSettings<P>>)?;
38+
let scripts = world
39+
.remove_resource::<Scripts>()
40+
.ok_or_else(MissingResourceError::new::<Scripts>)?;
41+
let runtime_container = world
42+
.remove_non_send_resource::<RuntimeContainer<P>>()
43+
.ok_or_else(MissingResourceError::new::<RuntimeContainer<P>>)?;
44+
let script_contexts = world
45+
.remove_non_send_resource::<ScriptContexts<P>>()
46+
.ok_or_else(MissingResourceError::new::<ScriptContexts<P>>)?;
47+
48+
Ok(HandlerContext {
49+
callback_settings,
50+
context_loading_settings,
51+
scripts,
52+
runtime_container,
53+
script_contexts,
54+
})
55+
}
56+
57+
pub(crate) fn yield_handler_context<P: IntoScriptPluginParams>(
58+
world: &mut World,
59+
context: HandlerContext<P>,
60+
) {
61+
world.insert_resource(context.callback_settings);
62+
world.insert_resource(context.context_loading_settings);
63+
world.insert_resource(context.scripts);
64+
world.insert_non_send_resource(context.runtime_container);
65+
world.insert_non_send_resource(context.script_contexts);
66+
}

crates/bevy_mod_scripting_core/src/handler.rs

Lines changed: 48 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
1-
use std::any::type_name;
2-
31
use crate::{
42
bindings::{
53
pretty_print::DisplayWithWorld, script_value::ScriptValue, WorldAccessGuard, WorldGuard,
64
},
7-
context::{ContextLoadingSettings, ContextPreHandlingInitializer, ScriptContexts},
5+
context::ContextPreHandlingInitializer,
86
error::ScriptError,
97
event::{CallbackLabel, IntoCallbackLabel, ScriptCallbackEvent, ScriptErrorEvent},
10-
runtime::RuntimeContainer,
11-
script::{ScriptComponent, ScriptId, Scripts},
8+
extractors::{extract_handler_context, yield_handler_context, HandlerContext},
9+
script::{ScriptComponent, ScriptId},
1210
IntoScriptPluginParams,
1311
};
1412
use bevy::{
@@ -18,7 +16,7 @@ use bevy::{
1816
world::World,
1917
},
2018
log::{debug, trace},
21-
prelude::{EventReader, Events, Query, Ref, Res},
19+
prelude::{EventReader, Events, Query, Ref},
2220
};
2321

2422
pub trait Args: Clone + Send + Sync + 'static {}
@@ -53,36 +51,17 @@ macro_rules! push_err_and_continue {
5351
};
5452
}
5553

56-
/// Passes events with the specified label to the script callback with the same name and runs the callback
57-
pub fn event_handler<L: IntoCallbackLabel, P: IntoScriptPluginParams>(
54+
/// A utility to separate the event handling logic from the retrieval of the handler context
55+
pub(crate) fn event_handler_internal<L: IntoCallbackLabel, P: IntoScriptPluginParams>(
5856
world: &mut World,
57+
res_ctxt: &mut HandlerContext<P>,
5958
params: &mut SystemState<(
6059
EventReader<ScriptCallbackEvent>,
61-
Res<CallbackSettings<P>>,
62-
Res<ContextLoadingSettings<P>>,
63-
Res<Scripts>,
6460
Query<(Entity, Ref<ScriptComponent>)>,
6561
)>,
6662
) {
67-
let mut runtime_container = world
68-
.remove_non_send_resource::<RuntimeContainer<P>>()
69-
.unwrap_or_else(|| {
70-
panic!(
71-
"No runtime container for runtime {} found. Was the scripting plugin initialized correctly?",
72-
type_name::<P::R>()
73-
)
74-
});
75-
let runtime = &mut runtime_container.runtime;
76-
let mut script_contexts = world
77-
.remove_non_send_resource::<ScriptContexts<P>>()
78-
.unwrap_or_else(|| panic!("No script contexts found for context {}", type_name::<P>()));
63+
let (mut script_events, entities) = params.get_mut(world);
7964

80-
let (mut script_events, callback_settings, context_settings, scripts, entities) =
81-
params.get_mut(world);
82-
83-
let handler = callback_settings.callback_handler;
84-
let pre_handling_initializers = context_settings.context_pre_handling_initializers.clone();
85-
let scripts = scripts.clone();
8665
let mut errors = Vec::default();
8766

8867
let events = script_events.read().cloned().collect::<Vec<_>>();
@@ -112,7 +91,7 @@ pub fn event_handler<L: IntoCallbackLabel, P: IntoScriptPluginParams>(
11291
"Handling event for script {} on entity {:?}",
11392
script_id, entity
11493
);
115-
let script = match scripts.scripts.get(script_id) {
94+
let script = match res_ctxt.scripts.scripts.get(script_id) {
11695
Some(s) => s,
11796
None => {
11897
trace!(
@@ -123,7 +102,11 @@ pub fn event_handler<L: IntoCallbackLabel, P: IntoScriptPluginParams>(
123102
}
124103
};
125104

126-
let ctxt = match script_contexts.contexts.get_mut(&script.context_id) {
105+
let ctxt = match res_ctxt
106+
.script_contexts
107+
.contexts
108+
.get_mut(&script.context_id)
109+
{
127110
Some(ctxt) => ctxt,
128111
None => {
129112
// if we don't have a context for the script, it's either:
@@ -133,14 +116,16 @@ pub fn event_handler<L: IntoCallbackLabel, P: IntoScriptPluginParams>(
133116
}
134117
};
135118

136-
let handler_result = (handler)(
119+
let handler_result = (res_ctxt.callback_settings.callback_handler)(
137120
event.args.clone(),
138121
*entity,
139122
&script.id,
140123
&L::into_callback_label(),
141124
ctxt,
142-
&pre_handling_initializers,
143-
runtime,
125+
&res_ctxt
126+
.context_loading_settings
127+
.context_pre_handling_initializers,
128+
&mut res_ctxt.runtime_container.runtime,
144129
world,
145130
)
146131
.map_err(|e| {
@@ -153,20 +138,43 @@ pub fn event_handler<L: IntoCallbackLabel, P: IntoScriptPluginParams>(
153138
}
154139
}
155140

156-
world.insert_non_send_resource(runtime_container);
157-
world.insert_non_send_resource(script_contexts);
158-
159141
handle_script_errors(world, errors.into_iter());
160142
}
161143

144+
/// Passes events with the specified label to the script callback with the same name and runs the callback.
145+
///
146+
/// If any of the resources required for the handler are missing, the system will log this issue and do nothing.
147+
pub fn event_handler<L: IntoCallbackLabel, P: IntoScriptPluginParams>(
148+
world: &mut World,
149+
params: &mut SystemState<(
150+
EventReader<ScriptCallbackEvent>,
151+
Query<(Entity, Ref<ScriptComponent>)>,
152+
)>,
153+
) {
154+
let mut res_ctxt = match extract_handler_context::<P>(world) {
155+
Ok(handler_context) => handler_context,
156+
Err(e) => {
157+
bevy::log::error_once!(
158+
"Event handler for language `{}` will not run due to missing resource: {}",
159+
P::LANGUAGE,
160+
e
161+
);
162+
return;
163+
}
164+
};
165+
166+
// this ensures the internal handler cannot early return without yielding the context
167+
event_handler_internal::<L, P>(world, &mut res_ctxt, params);
168+
169+
yield_handler_context(world, res_ctxt);
170+
}
171+
162172
/// Handles errors caused by script execution and sends them to the error event channel
163173
pub(crate) fn handle_script_errors<I: Iterator<Item = ScriptError> + Clone>(
164174
world: &mut World,
165175
errors: I,
166176
) {
167-
let mut error_events = world
168-
.get_resource_mut::<Events<ScriptErrorEvent>>()
169-
.expect("Missing events resource");
177+
let mut error_events = world.get_resource_or_init::<Events<ScriptErrorEvent>>();
170178

171179
for error in errors.clone() {
172180
error_events.send(ScriptErrorEvent { error });

crates/bevy_mod_scripting_core/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pub mod commands;
2424
pub mod context;
2525
pub mod error;
2626
pub mod event;
27+
pub mod extractors;
2728
pub mod handler;
2829
pub mod reflection_extensions;
2930
pub mod runtime;

0 commit comments

Comments
 (0)