use bevy_ecs::{
message::{MessageCursor, Messages},
world::WorldId,
};
use bevy_mod_scripting_bindings::{
CurrentScriptAttachment, InteropError, ScriptValue, WorldExtensions,
};
use bevy_mod_scripting_display::{DisplayProxy, WithTypeInfo};
use bevy_mod_scripting_script::ScriptAttachment;
use bevy_mod_scripting_world::{WorldAccessGuard, WorldGuard};
use crate::{
IntoScriptPluginParams,
callbacks::ScriptCallbacks,
error::ScriptError,
event::{
CallbackLabel, IntoCallbackLabel, Recipients, ScriptCallbackEvent,
ScriptCallbackResponseEvent, ScriptErrorEvent,
},
script::ScriptContexts,
};
use {
bevy_ecs::{
system::{Local, SystemState},
world::{Mut, World},
},
bevy_log::error,
};
pub type HandlerFn<P> = fn(
args: Vec<ScriptValue>,
context_key: &ScriptAttachment,
callback: &CallbackLabel,
context: &mut <P as IntoScriptPluginParams>::C,
world_id: WorldId,
) -> Result<ScriptValue, InteropError>;
pub trait ScriptingHandler<P: IntoScriptPluginParams> {
fn handle(
args: Vec<ScriptValue>,
context_key: &ScriptAttachment,
callback: &CallbackLabel,
script_ctxt: &mut P::C,
script_callbacks: ScriptCallbacks<P>,
world: WorldGuard,
) -> Result<ScriptValue, InteropError>;
}
impl<P: IntoScriptPluginParams> ScriptingHandler<P> for P {
fn handle(
args: Vec<ScriptValue>,
attachment: &ScriptAttachment,
callback: &CallbackLabel,
script_ctxt: &mut P::C,
script_callbacks: ScriptCallbacks<P>,
world: WorldGuard,
) -> Result<ScriptValue, InteropError> {
WorldGuard::with_existing_static_guard(world.clone(), |world| {
world.set_current_attachment(attachment.clone());
let world_id = world.id();
let callbacks = script_callbacks.callbacks.read();
if let Some(callback) = callbacks
.get(&(attachment.clone(), callback.to_string()))
.cloned()
{
drop(callbacks);
callback(args, script_ctxt, world_id)
} else {
drop(callbacks);
Self::handler()(args, attachment, callback, script_ctxt, world_id)
}
})
}
}
#[allow(deprecated)]
pub fn event_handler<L: IntoCallbackLabel, P: IntoScriptPluginParams>(
world: &mut World,
state: &mut SystemState<Local<MessageCursor<ScriptCallbackEvent>>>,
) {
{
let script_context = world.get_resource_or_init::<ScriptContexts<P>>().clone();
let script_callbacks = world.get_resource_or_init::<ScriptCallbacks<P>>().clone();
let event_cursor = state.get_mut(world);
let cache = WorldAccessGuard::setup_cache(world, CurrentScriptAttachment::default());
let guard = WorldAccessGuard::new_exclusive(world, cache);
event_handler_inner::<P>(
L::into_callback_label(),
event_cursor,
script_context,
script_callbacks,
guard,
);
}
}
#[profiling::function]
#[allow(deprecated)]
pub(crate) fn event_handler_inner<P: IntoScriptPluginParams>(
callback_label: CallbackLabel,
mut event_cursor: Local<MessageCursor<ScriptCallbackEvent>>,
script_context: ScriptContexts<P>,
script_callbacks: ScriptCallbacks<P>,
guard: WorldAccessGuard,
) {
let mut errors = Vec::default();
let events = guard.with_resource(|events: &Messages<ScriptCallbackEvent>| {
event_cursor
.read(events)
.filter(|e| e.label == callback_label)
.cloned()
.collect::<Vec<_>>()
});
let events = match events {
Ok(events) => events,
Err(err) => {
error!(
"Failed to read script callback events: {}",
WithTypeInfo::new_with_info(&err, &guard)
);
return;
}
};
let mut events_to_requeue = vec![];
for event in events.into_iter().filter(|e| {
e.label == callback_label && e.language.as_ref().is_none_or(|l| l == &P::LANGUAGE)
}) {
let recipients = event.recipients.get_recipients(script_context.clone());
let highly_specific = matches!(
event.recipients,
Recipients::ScriptEntity(_, _) | Recipients::StaticScript(_)
);
let might_not_have_reached_pipeline_if_new = event.iteration == 0;
for (attachment, ctxt) in recipients {
let ctxt = if let Some(ctxt) = ctxt.as_loaded() {
ctxt
} else if highly_specific && ctxt.is_loading_or_reloading()
|| might_not_have_reached_pipeline_if_new
{
events_to_requeue.push(event.clone().with_incremented_iteration());
continue;
} else {
continue;
};
let mut ctxt = ctxt.lock();
let call_result = P::handle(
event.args.clone(),
&attachment,
&callback_label,
&mut ctxt,
script_callbacks.clone(),
guard.clone(),
);
let call_result = call_result.map_err(|e| {
ScriptError::from(e)
.with_script(attachment.script().display())
.with_context(format!("callback: {}", event.label))
.with_type_info_context(Some("args: "), event.args.clone())
.with_language(P::LANGUAGE)
});
drop(ctxt);
if event.trigger_response {
send_callback_response(
guard.clone(),
ScriptCallbackResponseEvent::new(
callback_label.clone(),
attachment,
call_result.clone(),
P::LANGUAGE,
),
);
}
collect_errors(call_result, &mut errors);
}
}
if let Err(err) = guard.with_resource_mut(|mut writer: Mut<Messages<ScriptCallbackEvent>>| {
writer.write_batch(events_to_requeue);
}) {
errors.push(err.into());
}
send_script_errors(guard, errors.iter());
}
fn collect_errors(call_result: Result<ScriptValue, ScriptError>, errors: &mut Vec<ScriptError>) {
match call_result {
Ok(_) => {}
Err(e) => {
errors.push(e);
}
}
}
pub fn send_callback_response(world: WorldGuard, response: ScriptCallbackResponseEvent) {
let err = world.with_resource_mut(|mut events: Mut<Messages<ScriptCallbackResponseEvent>>| {
events.write(response);
});
if let Err(err) = err {
error!(
"Failed to send script callback response: {}",
WithTypeInfo::new_with_info(&err, &world)
);
}
}
pub fn send_script_errors<'e>(
world: WorldGuard,
errors: impl IntoIterator<Item = &'e ScriptError>,
) {
let iter = errors.into_iter();
let err = world.with_resource_mut(|mut error_events: Mut<Messages<ScriptErrorEvent>>| {
for error in iter {
error_events.write(ScriptErrorEvent {
error: error.clone(),
});
}
});
if let Err(err) = err {
error!(
"Failed to send script error events: {}",
WithTypeInfo::new_with_info(&err, &world)
);
}
}
pub fn script_error_logger(
world: &mut World,
mut errors_cursor: Local<MessageCursor<ScriptErrorEvent>>,
) {
let cache = WorldGuard::setup_cache(world, CurrentScriptAttachment::default());
let guard = WorldGuard::new_exclusive(world, cache);
let errors = guard.with_resource(|events: &Messages<ScriptErrorEvent>| {
errors_cursor
.read(events)
.map(|e| e.error.clone())
.collect::<Vec<_>>()
});
match errors {
Ok(errors) => {
for error in errors {
error!("{}", &WithTypeInfo::new_with_info(&error, &guard))
}
}
Err(err) => {
error!(
"Script errors occured but could not be accessed:\n{}",
WithTypeInfo::new_with_info(&err, &guard)
);
}
}
}