use std::any::TypeId;
#[cfg(test)]
use std::collections::HashSet;
use rhai::packages::{Package, StandardPackage};
use rhai::{Engine, FuncRegistration, Module, RhaiNativeFunc, Shared, Variant};
pub(crate) struct Registry {
namespace: Module,
globals: Vec<Shared<Module>>,
statics: Vec<(&'static str, Shared<Module>)>,
names: Engine,
docs: super::docs::DocIndex,
#[cfg(test)]
seen: HashSet<(String, Vec<TypeId>)>,
#[cfg(test)]
replaced: Vec<String>,
}
pub(crate) struct SharedRegistrations {
standard: Shared<Module>,
namespace: Shared<Module>,
globals: Vec<Shared<Module>>,
statics: Vec<(&'static str, Shared<Module>)>,
#[cfg(test)]
replaced: Vec<String>,
}
impl Registry {
fn new() -> Self {
Self {
namespace: Module::new(),
globals: Vec::new(),
statics: Vec::new(),
names: Engine::new_raw(),
docs: super::docs::DocIndex::new(),
#[cfg(test)]
seen: HashSet::new(),
#[cfg(test)]
replaced: Vec::new(),
}
}
fn param_type(&self, ty: &str) -> String {
if let Some(inner) = ty.strip_prefix("&mut ") {
let mapped = self.param_type(inner);
return if mapped == inner {
ty.to_string()
} else {
format!("&mut {mapped}")
};
}
self.names.map_type_name(ty).to_string()
}
pub(crate) fn register_fn<A: 'static, const N: usize, const X: bool, R, const F: bool, FUNC>(
&mut self,
name: impl AsRef<str>,
func: FUNC,
) -> &mut Self
where
R: Variant + Clone,
FUNC: RhaiNativeFunc<A, N, X, R, F> + 'static,
{
let name = name.as_ref();
#[cfg(test)]
if !self
.seen
.insert((name.to_string(), FUNC::param_types().to_vec()))
{
self.replaced
.push(format!("{name}{:?}", FUNC::param_names()));
}
let mut params: Vec<String> = FUNC::param_names()
.iter()
.map(|ty| format!("_: {}", self.param_type(ty)))
.collect();
if FUNC::return_type() != TypeId::of::<()>() {
params.push(self.param_type(FUNC::return_type_name()));
}
let receiver = super::docs::receiver_of(FUNC::param_names().first().copied());
let comments = self.docs.get(receiver, name);
FuncRegistration::new(name)
.with_params_info(params)
.with_comments(comments)
.in_global_namespace()
.set_into_module(&mut self.namespace, func);
self
}
pub(crate) fn doc_comments(&mut self, receiver: &'static str, name: &str) -> Vec<String> {
self.docs.get(receiver, name)
}
pub(crate) fn register_fn_as<
A: 'static,
const N: usize,
const X: bool,
R,
const F: bool,
FUNC,
>(
&mut self,
names: &[&str],
func: FUNC,
) -> &mut Self
where
R: Variant + Clone,
FUNC: RhaiNativeFunc<A, N, X, R, F> + Clone + 'static,
{
for name in names {
self.register_fn(name, func.clone());
}
self
}
pub(crate) fn register_get<A: 'static, const X: bool, R, const F: bool, FUNC>(
&mut self,
name: &str,
func: FUNC,
) -> &mut Self
where
R: Variant + Clone,
FUNC: RhaiNativeFunc<A, 1, X, R, F> + 'static,
{
self.register_fn(format!("get${name}"), func)
}
pub(crate) fn register_indexer_get<A: 'static, const X: bool, R, const F: bool, FUNC>(
&mut self,
func: FUNC,
) -> &mut Self
where
R: Variant + Clone,
FUNC: RhaiNativeFunc<A, 2, X, R, F> + 'static,
{
self.register_fn("index$get$", func)
}
pub(crate) fn register_iterator<T>(&mut self) -> &mut Self
where
T: Variant + Clone + IntoIterator,
<T as IntoIterator>::Item: Variant + Clone,
{
self.namespace.set_iterable::<T>();
self
}
pub(crate) fn register_type_with_name<T: Variant + Clone>(&mut self, name: &str) -> &mut Self {
self.namespace.set_custom_type::<T>(name);
self.names.register_type_with_name::<T>(name);
self
}
pub(crate) fn register_static_module(
&mut self,
name: &'static str,
module: Shared<Module>,
) -> &mut Self {
let mut module = Shared::try_unwrap(module).unwrap_or_else(|shared| (*shared).clone());
module.build_index();
if module.id().is_none() {
module.set_id(name);
}
let module: Shared<Module> = module.into();
self.names.register_static_module(name, module.clone());
self.statics.push((name, module));
self
}
pub(crate) fn register_global_module(&mut self, module: Shared<Module>) -> &mut Self {
self.names.register_global_module(module.clone());
self.globals.push(module);
self
}
fn finish(self) -> SharedRegistrations {
SharedRegistrations {
standard: StandardPackage::new().as_shared_module(),
namespace: self.namespace.into(),
globals: self.globals,
statics: self.statics,
#[cfg(test)]
replaced: self.replaced,
}
}
}
thread_local! {
static SHARED: SharedRegistrations = {
let mut registry = Registry::new();
super::register_all(&mut registry);
registry.finish()
};
}
pub(crate) fn new_engine() -> Engine {
let mut engine = Engine::new_raw();
#[cfg(any(not(target_family = "wasm"), not(target_os = "unknown")))]
{
engine.on_print(|text| {
use std::io::Write as _;
let _ = writeln!(std::io::stdout(), "{text}");
});
engine.on_debug(|text, source, pos| {
use std::io::Write as _;
let mut out = std::io::stdout();
let _ = match (source, pos) {
(Some(source), rhai::Position::NONE) => writeln!(out, "{source} | {text}"),
(Some(source), pos) => writeln!(out, "{source} @ {pos:?} | {text}"),
(None, rhai::Position::NONE) => writeln!(out, "{text}"),
(None, pos) => writeln!(out, "{pos:?} | {text}"),
};
});
}
engine.set_max_strings_interned(rhai::default_limits::MAX_STRINGS_INTERNED);
SHARED.with(|shared| {
engine.register_global_module(shared.standard.clone());
for module in &shared.globals {
engine.register_global_module(module.clone());
}
engine.register_global_module(shared.namespace.clone());
for (name, module) in &shared.statics {
engine.register_static_module(*name, module.clone());
}
});
engine
}
pub(crate) fn display_type_name(raw: &str) -> String {
let raw = raw.trim();
let primitive = match raw {
"i64" | "i32" | "INT" | "u64" | "usize" => Some("int64"),
"f64" | "f32" | "FLOAT" => Some("float64"),
"bool" => Some("boolean"),
"string"
| "&str"
| "String"
| "ImmutableString"
| "&str | ImmutableString | String"
| "rhai::ImmutableString"
| "alloc::string::String" => Some("string"),
"()" => Some("()"),
"array" | "rhai::Array" | "Array" => Some("array"),
"map" | "rhai::Map" | "Map" => Some("map"),
_ => None,
};
if let Some(name) = primitive {
return name.to_string();
}
if let Some(inner) = raw.strip_prefix("&mut ") {
return display_type_name(inner);
}
if raw == std::any::type_name::<crate::script::ScriptUuid>() {
return "uuid".to_string();
}
let registered = SHARED
.try_with(|shared| {
shared
.namespace
.get_custom_type_display_by_name(raw)
.map(str::to_string)
})
.ok()
.flatten();
match registered {
Some(name) => name,
None if raw.contains("::") => {
let last = raw.rsplit("::").next().unwrap_or(raw);
last.strip_prefix("Script").unwrap_or(last).to_string()
}
None => raw.to_string(),
}
}
pub(crate) fn display_type_names_in(text: &str) -> String {
let mut out = String::with_capacity(text.len());
let mut token = String::new();
let flush = |token: &mut String, out: &mut String| {
if !token.is_empty() {
let mapped = if token.contains("::")
|| matches!(token.as_str(), "i64" | "f64" | "bool" | "ImmutableString")
{
display_type_name(token)
} else {
token.clone()
};
out.push_str(&mapped);
token.clear();
}
};
for c in text.chars() {
if c.is_alphanumeric() || c == '_' || c == ':' {
token.push(c);
} else {
flush(&mut token, &mut out);
out.push(c);
}
}
flush(&mut token, &mut out);
out.replace("&str | string | String", "string")
}
#[cfg(test)]
pub(crate) fn replaced_signatures() -> Vec<String> {
SHARED.with(|shared| shared.replaced.clone())
}