Skip to content

Commit c1b6375

Browse files
authored
feat: Implement global namespace registration (#202)
* feat: add more functionality to the script registry * add global namespace alias * register global functions in lua
1 parent 7921e19 commit c1b6375

File tree

5 files changed

+132
-46
lines changed

5 files changed

+132
-46
lines changed

crates/bevy_mod_scripting_core/src/bindings/function/namespace.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,20 @@ pub enum Namespace {
6363
OnType(TypeId),
6464
}
6565

66+
/// A type which implements [`IntoNamespace`] by always converting to the global namespace
67+
pub struct GlobalNamespace;
68+
6669
pub trait IntoNamespace {
6770
fn into_namespace() -> Namespace;
6871
}
6972

7073
impl<T: ?Sized + 'static> IntoNamespace for T {
7174
fn into_namespace() -> Namespace {
72-
Namespace::OnType(TypeId::of::<T>())
75+
if TypeId::of::<T>() == TypeId::of::<GlobalNamespace>() {
76+
Namespace::Global
77+
} else {
78+
Namespace::OnType(TypeId::of::<T>())
79+
}
7380
}
7481
}
7582

crates/bevy_mod_scripting_core/src/bindings/function/script_function.rs

+59-4
Original file line numberDiff line numberDiff line change
@@ -360,8 +360,8 @@ impl ScriptFunctionRegistryArc {
360360

361361
#[derive(Debug, PartialEq, Eq, Hash)]
362362
pub struct FunctionKey {
363-
name: Cow<'static, str>,
364-
namespace: Namespace,
363+
pub name: Cow<'static, str>,
364+
pub namespace: Namespace,
365365
}
366366

367367
#[derive(Debug, Default)]
@@ -372,6 +372,8 @@ pub struct ScriptFunctionRegistry {
372372
impl ScriptFunctionRegistry {
373373
/// Register a script function with the given name. If the name already exists,
374374
/// the new function will be registered as an overload of the function.
375+
///
376+
/// If you want to overwrite an existing function, use [`ScriptFunctionRegistry::overwrite`]
375377
pub fn register<F, M>(
376378
&mut self,
377379
namespace: Namespace,
@@ -380,21 +382,44 @@ impl ScriptFunctionRegistry {
380382
) where
381383
F: ScriptFunction<'static, M>,
382384
{
383-
self.register_overload(namespace, name, func);
385+
self.register_overload(namespace, name, func, false);
386+
}
387+
388+
/// Overwrite a function with the given name. If the function does not exist, it will be registered as a new function.
389+
pub fn overwrite<F, M>(
390+
&mut self,
391+
namespace: Namespace,
392+
name: impl Into<Cow<'static, str>>,
393+
func: F,
394+
) where
395+
F: ScriptFunction<'static, M>,
396+
{
397+
self.register_overload(namespace, name, func, true);
398+
}
399+
400+
/// Remove a function from the registry if it exists. Returns the removed function if it was found.
401+
pub fn remove(
402+
&mut self,
403+
namespace: Namespace,
404+
name: impl Into<Cow<'static, str>>,
405+
) -> Option<DynamicScriptFunction> {
406+
let name = name.into();
407+
self.functions.remove(&FunctionKey { name, namespace })
384408
}
385409

386410
fn register_overload<F, M>(
387411
&mut self,
388412
namespace: Namespace,
389413
name: impl Into<Cow<'static, str>>,
390414
func: F,
415+
overwrite: bool,
391416
) where
392417
F: ScriptFunction<'static, M>,
393418
{
394419
// always start with non-suffixed registration
395420
// TODO: we do alot of string work, can we make this all more efficient?
396421
let name: Cow<'static, str> = name.into();
397-
if !self.contains(namespace, name.clone()) {
422+
if overwrite || !self.contains(namespace, name.clone()) {
398423
let func = func
399424
.into_dynamic_script_function()
400425
.with_name(name.clone())
@@ -636,4 +661,34 @@ mod test {
636661
assert_eq!(all_functions[0].info.name(), "test");
637662
assert_eq!(all_functions[1].info.name(), "test-1");
638663
}
664+
665+
#[test]
666+
fn test_overwrite_script_function() {
667+
let mut registry = ScriptFunctionRegistry::default();
668+
let fn_ = |a: usize, b: usize| a + b;
669+
let namespace = Namespace::Global;
670+
registry.register(namespace, "test", fn_);
671+
let fn_2 = |a: usize, b: i32| a + (b as usize);
672+
registry.overwrite(namespace, "test", fn_2);
673+
674+
let all_functions = registry
675+
.iter_overloads(namespace, "test")
676+
.expect("Failed to get overloads")
677+
.collect::<Vec<_>>();
678+
679+
assert_eq!(all_functions.len(), 1);
680+
assert_eq!(all_functions[0].info.name(), "test");
681+
}
682+
683+
#[test]
684+
fn test_remove_script_function() {
685+
let mut registry = ScriptFunctionRegistry::default();
686+
let fn_ = |a: usize, b: usize| a + b;
687+
let namespace = Namespace::Global;
688+
registry.register(namespace, "test", fn_);
689+
let removed = registry.remove(namespace, "test");
690+
assert!(removed.is_some());
691+
let removed = registry.remove(namespace, "test");
692+
assert!(removed.is_none());
693+
}
639694
}

crates/bevy_mod_scripting_functions/src/test_functions.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use bevy::{
88
use bevy_mod_scripting_core::{
99
bindings::{
1010
function::{
11-
namespace::NamespaceBuilder,
11+
namespace::{GlobalNamespace, NamespaceBuilder},
1212
script_function::{CallerContext, DynamicScriptFunctionMut},
1313
},
1414
pretty_print::DisplayWithWorld,
@@ -79,4 +79,7 @@ pub fn register_test_functions(world: &mut App) {
7979
}
8080
},
8181
);
82+
83+
NamespaceBuilder::<GlobalNamespace>::new_unregistered(world)
84+
.register("global_hello_world", || Ok("hi!"));
8285
}

crates/languages/bevy_mod_scripting_lua/src/lib.rs

+60-40
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
use bevy::{
2-
app::{App, Plugin},
2+
app::Plugin,
33
ecs::{entity::Entity, world::World},
44
};
55
use bevy_mod_scripting_core::{
66
asset::{AssetPathToLanguageMapper, Language},
77
bindings::{
8-
script_value::ScriptValue, ThreadWorldContainer, WorldCallbackAccess, WorldContainer,
8+
function::namespace::Namespace, script_value::ScriptValue, ThreadWorldContainer,
9+
WorldCallbackAccess, WorldContainer,
910
},
1011
context::{ContextBuilder, ContextInitializer, ContextPreHandlingInitializer},
1112
error::ScriptError,
1213
event::CallbackLabel,
1314
reflection_extensions::PartialReflectExt,
1415
script::ScriptId,
15-
AddContextInitializer, IntoScriptPluginParams, ScriptingPlugin,
16+
IntoScriptPluginParams, ScriptingPlugin,
1617
};
1718
use bindings::{
1819
reference::{LuaReflectReference, LuaStaticReflectReference},
@@ -48,16 +49,62 @@ impl Default for LuaScriptingPlugin {
4849
language_mapper: Some(AssetPathToLanguageMapper {
4950
map: lua_language_mapper,
5051
}),
51-
context_initializers: vec![|_script_id, context| {
52-
context
53-
.globals()
54-
.set(
55-
"world",
56-
LuaStaticReflectReference(std::any::TypeId::of::<World>()),
57-
)
58-
.map_err(ScriptError::from_mlua_error)?;
59-
Ok(())
60-
}],
52+
context_initializers: vec![
53+
|_script_id, context| {
54+
// set the world global
55+
context
56+
.globals()
57+
.set(
58+
"world",
59+
LuaStaticReflectReference(std::any::TypeId::of::<World>()),
60+
)
61+
.map_err(ScriptError::from_mlua_error)?;
62+
Ok(())
63+
},
64+
|_script_id, context: &mut Lua| {
65+
// set static globals
66+
let world = ThreadWorldContainer.get_world();
67+
let type_registry = world.type_registry();
68+
let type_registry = type_registry.read();
69+
70+
for registration in type_registry.iter() {
71+
// only do this for non generic types
72+
// we don't want to see `Vec<Entity>:function()` in lua
73+
if !registration.type_info().generics().is_empty() {
74+
continue;
75+
}
76+
77+
if let Some(global_name) =
78+
registration.type_info().type_path_table().ident()
79+
{
80+
let ref_ = LuaStaticReflectReference(registration.type_id());
81+
context
82+
.globals()
83+
.set(global_name, ref_)
84+
.map_err(ScriptError::from_mlua_error)?;
85+
}
86+
}
87+
88+
// go through functions in the global namespace and add them to the lua context
89+
let script_function_registry = world.script_function_registry();
90+
let script_function_registry = script_function_registry.read();
91+
92+
for (key, function) in script_function_registry
93+
.iter_all()
94+
.filter(|(k, _)| k.namespace == Namespace::Global)
95+
{
96+
context
97+
.globals()
98+
.set(
99+
key.name.to_string(),
100+
LuaScriptValue::from(ScriptValue::Function(function.clone())),
101+
)
102+
.map_err(ScriptError::from_mlua_error)?;
103+
}
104+
105+
Ok(())
106+
},
107+
],
61108
context_pre_handling_initializers: vec![|script_id, entity, context| {
62109
let world = ThreadWorldContainer.get_world();
63110
context
@@ -89,33 +136,6 @@ impl Plugin for LuaScriptingPlugin {
89136
fn build(&self, app: &mut bevy::prelude::App) {
90137
self.scripting_plugin.build(app);
91138
}
92-
93-
fn cleanup(&self, app: &mut App) {
94-
// find all registered types, and insert dummy for calls
95-
96-
app.add_context_initializer::<LuaScriptingPlugin>(|_script_id, context: &mut Lua| {
97-
let world = ThreadWorldContainer.get_world();
98-
let type_registry = world.type_registry();
99-
let type_registry = type_registry.read();
100-
101-
for registration in type_registry.iter() {
102-
// only do this for non generic types
103-
// we don't want to see `Vec<Entity>:function()` in lua
104-
if !registration.type_info().generics().is_empty() {
105-
continue;
106-
}
107-
108-
if let Some(global_name) = registration.type_info().type_path_table().ident() {
109-
let ref_ = LuaStaticReflectReference(registration.type_id());
110-
context
111-
.globals()
112-
.set(global_name, ref_)
113-
.map_err(ScriptError::from_mlua_error)?;
114-
}
115-
}
116-
Ok(())
117-
});
118-
}
119139
}
120140

121141
pub fn lua_context_load(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
assert(global_hello_world() == "hi!", "global_hello_world() == 'hi!'")

0 commit comments

Comments
 (0)