use std::{marker::PhantomData, sync::Arc, time::Duration};
use crate::{
IntoScriptPluginParams, ScriptContexts,
callbacks::ScriptCallbacks,
error::ScriptError,
event::{CallbackLabel, ScriptAttachedEvent, ScriptCallbackResponseEvent, ScriptDetachedEvent},
handler::{ScriptingHandler, send_callback_response, send_script_errors},
pipeline::RunProcessingPipelineOnce,
script::Context,
};
use bevy_ecs::{
error::{BevyError, Severity},
system::Command,
world::World,
};
use bevy_log::trace;
use bevy_mod_scripting_bindings::{CurrentScriptAttachment, ScriptValue, WorldExtensions};
use bevy_mod_scripting_display::DisplayProxy;
use bevy_mod_scripting_script::ScriptAttachment;
use bevy_mod_scripting_world::{WorldAccessGuard, WorldGuard};
use parking_lot::Mutex;
pub struct RunScriptCallback<P: IntoScriptPluginParams> {
pub attachment: ScriptAttachment,
pub callback: CallbackLabel,
pub error_context: Vec<String>,
pub args: Vec<ScriptValue>,
pub trigger_response: bool,
pub _ph: std::marker::PhantomData<fn(P::R, P::C)>,
pub send_errors: bool,
pub context_override: Option<Arc<Mutex<P::C>>>,
pub post_callback:
fn(&mut World, attachment: ScriptAttachment, response: &Result<ScriptValue, ScriptError>),
}
impl<P: IntoScriptPluginParams> RunScriptCallback<P> {
pub fn new(
attachment: ScriptAttachment,
callback: CallbackLabel,
args: Vec<ScriptValue>,
trigger_response: bool,
) -> Self {
Self {
attachment,
error_context: vec![],
callback,
args,
trigger_response,
_ph: std::marker::PhantomData,
post_callback: |_, _, _| {},
send_errors: true,
context_override: None,
}
}
pub fn with_post_callback_handler(
mut self,
handler: fn(
&mut World,
attachment: ScriptAttachment,
response: &Result<ScriptValue, ScriptError>,
),
) -> Self {
self.post_callback = handler;
self
}
pub fn with_context_override(mut self, context: Arc<Mutex<P::C>>) -> Self {
self.context_override = Some(context);
self
}
pub fn with_send_errors(mut self, send_errors: bool) -> Self {
self.send_errors = send_errors;
self
}
pub fn with_error_context(mut self, context: impl ToString) -> Self {
self.error_context.push(context.to_string());
self
}
fn handle_error(res: &Result<ScriptValue, ScriptError>, guard: WorldGuard) {
if let Err(err) = res {
send_script_errors(guard, [err]);
}
}
fn run_with_context(
&mut self,
guard: WorldGuard,
ctxt: Arc<Mutex<P::C>>,
script_callbacks: ScriptCallbacks<P>,
) -> Result<ScriptValue, ScriptError> {
let mut ctxt_guard = ctxt.lock();
let result = P::handle(
std::mem::take(&mut self.args),
&self.attachment,
&self.callback,
&mut ctxt_guard,
script_callbacks,
guard.clone(),
);
let result = result.map_err(|e| {
let mut err = ScriptError::from(e).with_script(self.attachment.script().display());
for ctxt in &self.error_context {
err = err.with_context(ctxt.clone())
}
err.with_context(format!("in callback: {}", self.callback))
.with_language(P::LANGUAGE)
});
drop(ctxt_guard);
if self.trigger_response {
trace!(
"{}: Sending callback response for callback: {}, attachment: {}",
P::LANGUAGE,
self.callback,
self.attachment,
);
send_callback_response(
guard.clone(),
ScriptCallbackResponseEvent::new(
self.callback.clone(),
self.attachment.clone(),
result.clone(),
P::LANGUAGE,
),
);
}
result
}
fn run_with_contexts(
&mut self,
guard: WorldGuard,
script_contexts: ScriptContexts<P>,
script_callbacks: ScriptCallbacks<P>,
) -> Result<ScriptValue, ScriptError> {
let script_contexts = script_contexts.read();
let ctxt = script_contexts.get_context(&self.attachment);
let ctxt = match ctxt {
Some(Context::LoadedAndActive(context)) => context,
Some(s) => {
return Err(ScriptError::new_boxed_without_type_info(
format!("Cannot run callback on script while in state of: {s}").into(),
)
.with_script(self.attachment.script().display())
.with_language(P::LANGUAGE));
}
None => {
return Err(ScriptError::new_boxed_without_type_info(
String::from("No context found for script").into(),
)
.with_script(self.attachment.script().display())
.with_language(P::LANGUAGE));
}
};
self.run_with_context(guard, ctxt.clone(), script_callbacks)
}
fn run(mut self, world: &mut World) -> Result<ScriptValue, ScriptError> {
let script_contexts = world.get_resource_or_init::<ScriptContexts<P>>().clone();
let script_callbacks = world.get_resource_or_init::<ScriptCallbacks<P>>().clone();
let cache = WorldAccessGuard::setup_cache(
world,
CurrentScriptAttachment(Some(self.attachment.clone())),
);
let guard = WorldGuard::new_exclusive(world, cache);
let res = if let Some(context_override) = &self.context_override {
self.run_with_context(guard.clone(), context_override.clone(), script_callbacks)
} else {
self.run_with_contexts(guard.clone(), script_contexts, script_callbacks)
};
if self.send_errors && res.is_err() {
Self::handle_error(&res, guard);
}
(self.post_callback)(world, self.attachment, &res);
res
}
}
impl<P: IntoScriptPluginParams> Command for RunScriptCallback<P> {
fn apply(self, world: &mut World) -> Self::Out {
self.run(world)
.map_err(|e| BevyError::from(e).with_severity(Severity::Ignore))
}
type Out = Result<ScriptValue, BevyError>;
}
pub struct AttachScript<P: IntoScriptPluginParams>(ScriptAttachedEvent, PhantomData<fn(P)>);
impl<P: IntoScriptPluginParams> AttachScript<P> {
pub fn new(attachment: ScriptAttachment) -> Self {
Self(ScriptAttachedEvent(attachment), Default::default())
}
}
pub struct DetachScript<P: IntoScriptPluginParams>(ScriptDetachedEvent, PhantomData<fn(P)>);
impl<P: IntoScriptPluginParams> DetachScript<P> {
pub fn new(attachment: ScriptAttachment) -> Self {
Self(ScriptDetachedEvent(attachment), Default::default())
}
}
impl<P: IntoScriptPluginParams> Command for AttachScript<P> {
fn apply(self, world: &mut World) {
world.write_message(self.0);
RunProcessingPipelineOnce::<P>::new(Some(Duration::from_secs(9999))).apply(world)
}
type Out = ();
}
impl<P: IntoScriptPluginParams> Command for DetachScript<P> {
fn apply(self, world: &mut World) {
world.write_message(self.0);
RunProcessingPipelineOnce::<P>::new(Some(Duration::from_secs(9999))).apply(world)
}
type Out = ();
}