use super::{
WorldGuard,
function::arg_meta::{ScriptReturn, TypedScriptReturn},
script_value::ScriptValue,
};
use crate::{
docgen::{TypedThrough, into_through_type_info, typed_through::ThroughTypeInfo},
error::InteropError,
};
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use std::{any::TypeId, borrow::Cow, sync::Arc};
use {bevy_ecs::resource::Resource, bevy_platform::collections::HashMap, bevy_reflect::Typed};
pub mod core;
pub use core::*;
#[derive(Default, Resource, Clone)]
pub struct AppScriptGlobalsRegistry(Arc<RwLock<ScriptGlobalsRegistry>>);
#[profiling::all_functions]
impl AppScriptGlobalsRegistry {
pub fn read(&self) -> RwLockReadGuard<'_, ScriptGlobalsRegistry> {
self.0.read()
}
pub fn write(&self) -> RwLockWriteGuard<'_, ScriptGlobalsRegistry> {
self.0.write()
}
}
pub type ScriptGlobalMakerFn<T> =
dyn Fn(WorldGuard) -> Result<T, InteropError> + 'static + Send + Sync;
pub struct ScriptGlobal {
pub maker: Option<Arc<ScriptGlobalMakerFn<ScriptValue>>>,
pub documentation: Option<Cow<'static, str>>,
pub type_id: TypeId,
pub type_information: ThroughTypeInfo,
}
pub struct ScriptGlobalDummy {
pub type_id: TypeId,
pub type_information: Option<ThroughTypeInfo>,
pub documentation: Option<Cow<'static, str>>,
}
#[derive(Default)]
pub struct ScriptGlobalsRegistry {
globals: HashMap<Cow<'static, str>, ScriptGlobal>,
dummies: HashMap<Cow<'static, str>, ScriptGlobalDummy>,
}
#[profiling::all_functions]
impl ScriptGlobalsRegistry {
pub fn get(&self, name: &str) -> Option<&ScriptGlobal> {
self.globals.get(name)
}
pub fn get_mut(&mut self, name: &str) -> Option<&mut ScriptGlobal> {
self.globals.get_mut(name)
}
pub fn len(&self) -> usize {
self.globals.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn iter(&self) -> impl Iterator<Item = (&Cow<'static, str>, &ScriptGlobal)> {
self.globals.iter()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&Cow<'static, str>, &mut ScriptGlobal)> {
self.globals.iter_mut()
}
pub fn iter_dummies(&self) -> impl Iterator<Item = (&Cow<'static, str>, &ScriptGlobalDummy)> {
self.dummies.iter()
}
fn type_erase_maker<
T: ScriptReturn,
F: Fn(WorldGuard) -> Result<T, InteropError> + Send + Sync + 'static,
>(
maker: F,
) -> Arc<ScriptGlobalMakerFn<ScriptValue>> {
Arc::new(move |world| T::into_script(maker(world.clone())?, world))
}
#[must_use]
pub fn register<
T: ScriptReturn + 'static + Typed,
F: Fn(WorldGuard) -> Result<T, InteropError> + 'static + Send + Sync,
>(
&mut self,
name: impl Into<Cow<'static, str>>,
maker: F,
) -> Option<ScriptGlobal> {
self.globals.insert(
name.into(),
ScriptGlobal {
maker: Some(Self::type_erase_maker(maker)),
documentation: None,
type_id: TypeId::of::<T>(),
type_information: into_through_type_info(T::type_info()),
},
)
}
#[must_use]
pub fn register_dummy<T: 'static>(
&mut self,
name: impl Into<Cow<'static, str>>,
documentation: impl Into<Cow<'static, str>>,
) -> Option<ScriptGlobalDummy> {
self.dummies.insert(
name.into(),
ScriptGlobalDummy {
documentation: Some(documentation.into()),
type_id: TypeId::of::<T>(),
type_information: None,
},
)
}
#[must_use]
pub fn register_dummy_typed<T: 'static + TypedThrough>(
&mut self,
name: impl Into<Cow<'static, str>>,
documentation: impl Into<Cow<'static, str>>,
) -> Option<ScriptGlobalDummy> {
self.dummies.insert(
name.into(),
ScriptGlobalDummy {
documentation: Some(documentation.into()),
type_id: TypeId::of::<T>(),
type_information: Some(T::through_type_info()),
},
)
}
#[must_use]
pub fn register_documented<
T: TypedScriptReturn + 'static,
F: Fn(WorldGuard) -> Result<T, InteropError> + 'static + Send + Sync,
>(
&mut self,
name: impl Into<Cow<'static, str>>,
maker: F,
documentation: impl Into<Cow<'static, str>>,
) -> Option<ScriptGlobal> {
self.globals.insert(
name.into(),
ScriptGlobal {
maker: Some(Self::type_erase_maker(maker)),
documentation: Some(documentation.into()),
type_id: TypeId::of::<T>(),
type_information: T::through_type_info(),
},
)
}
#[must_use]
pub fn register_static<T: 'static + Typed>(
&mut self,
name: Cow<'static, str>,
) -> Option<ScriptGlobal> {
self.globals.insert(
name,
ScriptGlobal {
maker: None,
documentation: None,
type_id: TypeId::of::<T>(),
type_information: into_through_type_info(T::type_info()),
},
)
}
#[must_use]
pub fn register_static_documented<T: TypedScriptReturn + 'static>(
&mut self,
name: Cow<'static, str>,
documentation: Cow<'static, str>,
) -> Option<ScriptGlobal> {
self.globals.insert(
name,
ScriptGlobal {
maker: None,
documentation: Some(documentation),
type_id: TypeId::of::<T>(),
type_information: T::through_type_info(),
},
)
}
#[must_use]
pub fn register_static_documented_dynamic(
&mut self,
type_id: TypeId,
type_information: ThroughTypeInfo,
name: Cow<'static, str>,
documentation: Cow<'static, str>,
) -> Option<ScriptGlobal> {
self.globals.insert(
name,
ScriptGlobal {
maker: None,
documentation: Some(documentation),
type_id,
type_information,
},
)
}
}
#[cfg(test)]
mod test {
use bevy_ecs::world::World;
use super::*;
#[test]
fn test_script_globals_registry() {
let mut registry = ScriptGlobalsRegistry::default();
let maker = |_: WorldGuard| Ok(ScriptValue::from(42));
let maker2 = |_: WorldGuard| Ok(ScriptValue::from(43));
assert_eq!(registry.len(), 0);
assert!(registry.is_empty());
assert!(registry.register(Cow::Borrowed("foo"), maker).is_none());
assert_eq!(registry.len(), 1);
assert_eq!(
(registry.get("foo").unwrap().maker.clone().unwrap())(WorldGuard::new_exclusive(
&mut World::new()
))
.unwrap(),
ScriptValue::from(42)
);
assert!(registry.register(Cow::Borrowed("foo"), maker2).is_some());
assert_eq!(registry.len(), 1);
assert_eq!(
(registry.get("foo").unwrap().maker.clone().unwrap())(WorldGuard::new_exclusive(
&mut World::new()
))
.unwrap(),
ScriptValue::from(43)
);
}
#[test]
fn test_documentation_is_stored() {
let mut registry = ScriptGlobalsRegistry::default();
let maker = |_: WorldGuard| Ok(ScriptValue::from(42));
assert!(
registry
.register_documented(Cow::Borrowed("foo"), maker, Cow::Borrowed("This is a test"))
.is_none()
);
let global = registry.get("foo").unwrap();
assert_eq!(global.documentation.as_deref(), Some("This is a test"));
}
#[test]
fn test_static_globals() {
let mut registry = ScriptGlobalsRegistry::default();
_ = registry.register_static::<i32>(Cow::Borrowed("foo"));
let global = registry.get("foo").unwrap();
assert!(global.maker.is_none());
assert_eq!(global.type_id, TypeId::of::<i32>());
_ = registry.register_static_documented::<i32>(
Cow::Borrowed("bar"),
Cow::Borrowed("This is a test"),
);
let global = registry.get("bar").unwrap();
assert!(global.maker.is_none());
assert_eq!(global.type_id, TypeId::of::<i32>());
assert_eq!(global.documentation.as_deref(), Some("This is a test"));
}
}