mod assets;
mod callback;
mod components;
mod promise;
mod systems;
pub mod runtimes;
pub use crate::components::Script;
use assets::GetExtensions;
use promise::Promise;
use std::{
any::TypeId,
fmt::Debug,
hash::Hash,
marker::PhantomData,
sync::{Arc, Mutex},
};
use bevy::{
app::MainScheduleOrder,
ecs::{component::Mutable, schedule::ScheduleLabel},
prelude::*,
};
use callback::{Callback, IntoCallbackSystem};
use systems::{init_callbacks, log_errors, process_calls};
use thiserror::Error;
use self::{
assets::ScriptLoader,
systems::{process_new_scripts, reload_scripts},
};
#[cfg(any(feature = "rhai", feature = "lua"))]
const ENTITY_VAR_NAME: &str = "entity";
#[derive(Error, Debug)]
pub enum ScriptingError {
#[error("script runtime error:\n{0}")]
RuntimeError(String),
#[error("script compilation error:\n{0}")]
CompileError(Box<dyn std::error::Error + Send>),
#[error("no runtime resource present")]
NoRuntimeResource,
#[error("no settings resource present")]
NoSettingsResource,
}
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
pub enum ScriptSystemSet {
Reload,
Process,
}
pub trait Runtime: Resource + Default {
type Schedule: ScheduleLabel + Debug + Clone + Eq + Hash + Default;
type ScriptAsset: Asset + From<String> + GetExtensions;
type ScriptData: Component<Mutability = Mutable>;
type CallContext: Send + Clone;
type Value: Send + Clone;
type RawEngine;
fn with_engine_send_mut<T: Send + 'static>(
&mut self,
f: impl FnOnce(&mut Self::RawEngine) -> T + Send + 'static,
) -> T;
fn with_engine_send<T: Send + 'static>(
&self,
f: impl FnOnce(&Self::RawEngine) -> T + Send + 'static,
) -> T;
fn with_engine_mut<T>(&mut self, f: impl FnOnce(&mut Self::RawEngine) -> T) -> T;
fn with_engine<T>(&self, f: impl FnOnce(&Self::RawEngine) -> T) -> T;
fn eval(
&self,
script: &Self::ScriptAsset,
entity: Entity,
) -> Result<Self::ScriptData, ScriptingError>;
fn register_fn(
&mut self,
name: String,
arg_types: Vec<TypeId>,
f: impl Fn(
Self::CallContext,
Vec<Self::Value>,
) -> Result<Promise<Self::CallContext, Self::Value>, ScriptingError>
+ Send
+ Sync
+ 'static,
) -> Result<(), ScriptingError>;
fn call_fn(
&self,
name: &str,
script_data: &mut Self::ScriptData,
entity: Entity,
args: impl for<'a> FuncArgs<'a, Self::Value, Self> + Send + 'static,
) -> Result<Self::Value, ScriptingError>;
fn call_fn_from_value(
&self,
value: &Self::Value,
context: &Self::CallContext,
args: Vec<Self::Value>,
) -> Result<Self::Value, ScriptingError>;
fn needs_rdynamic_linking() -> bool {
false
}
}
pub trait FuncArgs<'a, V, R: Runtime> {
fn parse(self, engine: &'a R::RawEngine) -> Vec<V>;
}
pub trait BuildScriptingRuntime {
fn add_scripting<R: Runtime>(&mut self, f: impl Fn(ScriptingRuntimeBuilder<R>)) -> &mut Self;
fn add_scripting_api<R: Runtime>(
&mut self,
f: impl Fn(ScriptingRuntimeBuilder<R>),
) -> &mut Self;
}
pub struct ScriptingRuntimeBuilder<'a, R: Runtime> {
_phantom_data: PhantomData<R>,
world: &'a mut World,
}
impl<'a, R: Runtime> ScriptingRuntimeBuilder<'a, R> {
fn new(world: &'a mut World) -> Self {
Self {
_phantom_data: PhantomData,
world,
}
}
pub fn add_function<In, Out, Marker>(
self,
name: String,
fun: impl IntoCallbackSystem<R, In, Out, Marker>,
) -> Self
where
In: SystemInput,
{
let system = fun.into_callback_system(self.world);
let mut callbacks_resource = self.world.resource_mut::<Callbacks<R>>();
callbacks_resource.uninitialized_callbacks.push(Callback {
name,
system: Arc::new(Mutex::new(system)),
calls: Arc::new(Mutex::new(vec![])),
});
self
}
}
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
pub enum ScriptSystems {
Reload,
InitCallbacks,
ProcessNewScripts,
ProcessCalls,
}
impl BuildScriptingRuntime for App {
fn add_scripting<R: Runtime>(&mut self, f: impl Fn(ScriptingRuntimeBuilder<R>)) -> &mut Self {
#[cfg(debug_assertions)]
if R::needs_rdynamic_linking() && !is_rdynamic_linking() {
panic!(
"Missing `-rdynamic`: symbol resolution failed.\n\
It is needed by {:?}.\n\
Please add `println!(\"cargo:rustc-link-arg=-rdynamic\");` to your build.rs\n\
or set `RUSTFLAGS=\"-C link-arg=-rdynamic\"`.",
std::any::type_name::<R>()
);
}
self.world_mut()
.resource_mut::<MainScheduleOrder>()
.insert_after(Update, R::Schedule::default());
self.register_asset_loader(ScriptLoader::<R::ScriptAsset>::default())
.init_schedule(R::Schedule::default())
.init_asset::<R::ScriptAsset>()
.init_resource::<Callbacks<R>>()
.insert_resource(R::default())
.configure_sets(
R::Schedule::default(),
(
ScriptSystems::Reload,
ScriptSystems::InitCallbacks.after(ScriptSystems::Reload),
ScriptSystems::ProcessNewScripts.after(ScriptSystems::InitCallbacks),
ScriptSystems::ProcessCalls.after(ScriptSystems::ProcessNewScripts),
),
)
.add_systems(
R::Schedule::default(),
(
reload_scripts::<R>.in_set(ScriptSystems::Reload),
init_callbacks::<R>
.pipe(log_errors)
.in_set(ScriptSystems::InitCallbacks)
.after(ScriptSystems::Reload),
process_new_scripts::<R>
.pipe(log_errors)
.in_set(ScriptSystems::ProcessNewScripts)
.after(ScriptSystems::InitCallbacks),
process_calls::<R>
.pipe(log_errors)
.in_set(ScriptSystems::ProcessCalls)
.after(ScriptSystems::ProcessNewScripts),
),
);
let runtime = ScriptingRuntimeBuilder::<R>::new(self.world_mut());
f(runtime);
self
}
fn add_scripting_api<R: Runtime>(
&mut self,
f: impl Fn(ScriptingRuntimeBuilder<R>),
) -> &mut Self {
let runtime = ScriptingRuntimeBuilder::<R>::new(self.world_mut());
f(runtime);
self
}
}
#[derive(Resource)]
struct Callbacks<R: Runtime> {
uninitialized_callbacks: Vec<Callback<R>>,
callbacks: Mutex<Vec<Callback<R>>>,
}
impl<R: Runtime> Default for Callbacks<R> {
fn default() -> Self {
Self {
uninitialized_callbacks: Default::default(),
callbacks: Default::default(),
}
}
}
#[cfg(all(debug_assertions, unix))]
pub extern "C" fn is_rdynamic_linking() -> bool {
unsafe {
let addr = is_rdynamic_linking as *const libc::c_void;
let mut info: libc::Dl_info = std::mem::zeroed();
let result = libc::dladdr(addr, &mut info);
result != 0 && !info.dli_sname.is_null()
}
}
#[cfg(any(not(debug_assertions), not(unix)))]
pub extern "C" fn is_rdynamic_linking() -> bool {
true
}
pub mod prelude {
pub use crate::{BuildScriptingRuntime as _, Runtime as _, Script};
}