use std::{sync::Arc, time::Duration};
use crate::{
IntoScriptPluginParams, ScriptContext,
callbacks::ScriptCallbacks,
error::ScriptError,
event::{
CallbackLabel, ForPlugin, ScriptAttachedEvent, ScriptCallbackResponseEvent,
ScriptDetachedEvent,
},
handler::{ScriptingHandler, send_callback_response, send_script_errors},
pipeline::RunProcessingPipelineOnce,
};
use bevy_ecs::{system::Command, world::World};
use bevy_log::trace;
use bevy_mod_scripting_bindings::{ScriptValue, WorldGuard};
use bevy_mod_scripting_display::DisplayProxy;
use bevy_mod_scripting_script::ScriptAttachment;
use parking_lot::Mutex;
pub struct RunScriptCallback<P: IntoScriptPluginParams> {
pub attachment: ScriptAttachment,
pub callback: CallbackLabel,
pub context: Vec<String>,
pub args: Vec<ScriptValue>,
pub trigger_response: bool,
pub _ph: std::marker::PhantomData<fn(P::R, P::C)>,
}
impl<P: IntoScriptPluginParams> RunScriptCallback<P> {
pub fn new(
attachment: ScriptAttachment,
callback: CallbackLabel,
args: Vec<ScriptValue>,
trigger_response: bool,
) -> Self {
Self {
attachment,
context: vec![],
callback,
args,
trigger_response,
_ph: std::marker::PhantomData,
}
}
pub fn with_context(mut self, context: impl ToString) -> Self {
self.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]);
}
}
pub fn run_with_context(
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(
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.context {
err = err.with_context(ctxt)
}
err.with_context(format!("in callback: {}", self.callback))
.with_language(P::LANGUAGE)
});
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,
self.attachment.clone(),
result.clone(),
P::LANGUAGE,
),
);
}
result
}
pub fn run_with_contexts(
self,
guard: WorldGuard,
script_contexts: ScriptContext<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(ctxt) => ctxt,
None => {
let 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);
return Err(err);
}
};
self.run_with_context(guard, ctxt.clone(), script_callbacks)
}
pub fn run(self, world: &mut World, send_errors: bool) -> Result<ScriptValue, ScriptError> {
let script_contexts = world.get_resource_or_init::<ScriptContext<P>>().clone();
let script_callbacks = world.get_resource_or_init::<ScriptCallbacks<P>>().clone();
let guard = WorldGuard::new_exclusive(world);
let res = self.run_with_contexts(guard.clone(), script_contexts, script_callbacks);
if send_errors && res.is_err() {
Self::handle_error(&res, guard);
}
res
}
}
impl<P: IntoScriptPluginParams> Command for RunScriptCallback<P> {
fn apply(self, world: &mut World) {
let _ = self.run(world, true);
}
}
pub struct AttachScript<P: IntoScriptPluginParams>(pub ForPlugin<ScriptAttachedEvent, P>);
impl<P: IntoScriptPluginParams> AttachScript<P> {
pub fn new(attachment: ScriptAttachment) -> Self {
Self(ForPlugin::new(ScriptAttachedEvent(attachment)))
}
}
pub struct DetachScript<P: IntoScriptPluginParams>(pub ForPlugin<ScriptDetachedEvent, P>);
impl<P: IntoScriptPluginParams> DetachScript<P> {
pub fn new(attachment: ScriptAttachment) -> Self {
Self(ForPlugin::new(ScriptDetachedEvent(attachment)))
}
}
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)
}
}
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)
}
}