Skip to content

Commit 9b29247

Browse files
committed
add script function registry and update registrations
1 parent 68ba256 commit 9b29247

File tree

5 files changed

+226
-236
lines changed

5 files changed

+226
-236
lines changed

crates/bevy_api_gen/templates/footer.tera

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ impl ::bevy::app::Plugin for {{ "ScriptingPlugin" | prefix_cratename | convert_c
1414
{% for item in items %}
1515
NamespaceBuilder::<::{{ item.import_path }}>::new(world)
1616
{%- for function in item.functions -%}
17-
.overwrite_script_function("{{ function.ident }}", |
17+
.register("{{ function.ident }}", |
1818
{%- for arg in function.args -%}
1919
{%- if arg.proxy_ty is matching("Mut.*")-%}
2020
mut {% endif -%}

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

Lines changed: 166 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,26 @@ use crate::{
88
prelude::{ScriptValue, WorldCallbackAccess},
99
};
1010
use bevy::{
11-
prelude::{AppFunctionRegistry, IntoFunction, Reflect, World},
11+
prelude::{AppFunctionRegistry, IntoFunction, Reflect, Resource, World},
1212
reflect::{
13-
func::{DynamicFunction, FunctionInfo},
14-
FromReflect, GetTypeRegistration, PartialReflect, TypeRegistration, TypeRegistry, Typed,
13+
func::{args::GetOwnership, DynamicFunction, FunctionError, FunctionInfo, TypedFunction},
14+
FromReflect, GetTypeRegistration, PartialReflect, TypePath, TypeRegistration, TypeRegistry,
15+
Typed,
1516
},
1617
};
18+
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
19+
use std::borrow::Cow;
1720
use std::collections::HashMap;
1821
use std::hash::Hash;
22+
use std::ops::{Deref, DerefMut};
1923
use std::sync::Arc;
2024

2125
#[diagnostic::on_unimplemented(
2226
message = "Only functions with all arguments impplementing FromScript and return values supporting IntoScript are supported. Registering functions also requires they implement GetInnerTypeDependencies",
2327
note = "If you're trying to return a non-primitive type, you might need to use Val<T> Ref<T> or Mut<T> wrappers"
2428
)]
2529
pub trait ScriptFunction<'env, Marker> {
26-
fn into_dynamic_function(self) -> DynamicFunction<'static>;
30+
fn into_dynamic_script_function(self) -> DynamicScriptFunction;
2731
}
2832

2933
/// Functionally identical to [`GetTypeRegistration`] but without the 'static bound
@@ -111,18 +115,127 @@ pub struct CallerContext {
111115
}
112116

113117
/// The Script Function equivalent for dynamic functions. Currently unused
114-
/// TODO: have a separate function registry to avoid the need for boxing script args every time
118+
#[derive(Clone)]
115119
pub struct DynamicScriptFunction {
116-
pub info: FunctionInfo,
120+
// TODO: info about the function, this is hard right now because of non 'static lifetimes in wrappers, we can't use TypePath etc
117121
pub func: Arc<
118-
dyn Fn(
119-
CallerContext,
120-
WorldCallbackAccess,
121-
Vec<ScriptValue>,
122-
) -> Result<ScriptValue, InteropError>,
122+
dyn Fn(CallerContext, WorldCallbackAccess, Vec<ScriptValue>) -> ScriptValue
123+
+ Send
124+
+ Sync
125+
+ 'static,
123126
>,
124127
}
125128

129+
impl std::fmt::Debug for DynamicScriptFunction {
130+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131+
f.debug_struct("DynamicScriptFunction").finish()
132+
}
133+
}
134+
135+
/// Equivalent to [`AppFunctionRegistry`] but stores functions with a more convenient signature for scripting to avoid boxing every argument.
136+
#[derive(Clone, Debug, Default, Resource)]
137+
pub struct AppScriptFunctionRegistry(ScriptFunctionRegistryArc);
138+
139+
impl Deref for AppScriptFunctionRegistry {
140+
type Target = ScriptFunctionRegistryArc;
141+
142+
fn deref(&self) -> &Self::Target {
143+
&self.0
144+
}
145+
}
146+
147+
impl DerefMut for AppScriptFunctionRegistry {
148+
fn deref_mut(&mut self) -> &mut Self::Target {
149+
&mut self.0
150+
}
151+
}
152+
153+
#[derive(Clone, Debug, Default)]
154+
pub struct ScriptFunctionRegistryArc(pub Arc<RwLock<ScriptFunctionRegistry>>);
155+
156+
impl ScriptFunctionRegistryArc {
157+
pub fn read(&self) -> RwLockReadGuard<ScriptFunctionRegistry> {
158+
self.0.read()
159+
}
160+
161+
pub fn write(&mut self) -> RwLockWriteGuard<ScriptFunctionRegistry> {
162+
self.0.write()
163+
}
164+
}
165+
166+
#[derive(Debug, Default)]
167+
pub struct ScriptFunctionRegistry {
168+
functions: HashMap<Cow<'static, str>, DynamicScriptFunction>,
169+
}
170+
171+
impl ScriptFunctionRegistry {
172+
/// Register a script function with the given name. If the name already exists,
173+
/// the new function will be registered as an overload of the function.
174+
pub fn register<F, M>(&mut self, name: impl Into<Cow<'static, str>>, func: F)
175+
where
176+
F: ScriptFunction<'static, M>,
177+
{
178+
self.register_overload(name, func);
179+
}
180+
181+
pub fn register_overload<F, M>(&mut self, name: impl Into<Cow<'static, str>>, func: F)
182+
where
183+
F: ScriptFunction<'static, M>,
184+
{
185+
// always start with non-suffixed registration
186+
let name = name.into().clone();
187+
188+
if !self.contains(&name) {
189+
let func = func.into_dynamic_script_function();
190+
self.functions.insert(name, func);
191+
return;
192+
}
193+
194+
for i in 1..16 {
195+
let overload = format!("{name}-{i}");
196+
if !self.contains(&overload) {
197+
self.register(overload, func);
198+
return;
199+
}
200+
}
201+
202+
panic!(
203+
"Could not register overload for function {name}. Maximum number of overloads reached"
204+
);
205+
}
206+
207+
pub fn contains(&self, name: impl AsRef<str>) -> bool {
208+
self.functions.contains_key(name.as_ref())
209+
}
210+
211+
pub fn get_first(&self, name: impl AsRef<str>) -> Option<&DynamicScriptFunction> {
212+
self.functions.get(name.as_ref())
213+
}
214+
215+
pub fn iter_overloads(
216+
&self,
217+
name: impl Into<Cow<'static, str>>,
218+
) -> impl Iterator<Item = &DynamicScriptFunction> {
219+
let name = name.into();
220+
(0..16)
221+
.map(move |i| {
222+
if i == 0 {
223+
self.functions.get(&name)
224+
} else {
225+
let name: Cow<'static, str> = format!("{}-{i}", name).into();
226+
self.functions.get(&name)
227+
}
228+
})
229+
.take_while(|o| o.is_some())
230+
.map(|o| o.unwrap())
231+
}
232+
}
233+
234+
macro_rules! count {
235+
() => (0usize);
236+
( $x:tt $($xs:tt)* ) => (1usize + count!($($xs)*));
237+
}
238+
126239
macro_rules! impl_script_function {
127240

128241
($( $param:ident ),* ) => {
@@ -153,24 +266,31 @@ macro_rules! impl_script_function {
153266
fn( $($contextty,)? $( $callbackty, )? $($param ),* ) -> $res
154267
> for F
155268
where
156-
O: IntoScript,
269+
O: IntoScript + TypePath + GetOwnership,
157270
F: Fn( $($contextty,)? $( $callbackty, )? $($param ),* ) -> $res + Send + Sync + 'static,
158-
$( $param::This<'env>: Into<$param>),*
271+
$( $param::This<'env>: Into<$param>,)*
159272
{
160-
#[allow(unused_variables)]
161-
fn into_dynamic_function(self) -> DynamicFunction<'static> {
162-
(move |caller_context: CallerContext, world: WorldCallbackAccess, $( $param: ScriptValue ),* | {
273+
#[allow(unused_mut,unused_variables)]
274+
fn into_dynamic_script_function(self) -> DynamicScriptFunction {
275+
let func = (move |caller_context: CallerContext, world: WorldCallbackAccess, args: Vec<ScriptValue> | {
163276
let res: Result<ScriptValue, InteropError> = (|| {
277+
let expected_arg_count = count!($($param),*);
278+
if args.len() != expected_arg_count {
279+
return Err(InteropError::function_call_error(FunctionError::ArgCountMismatch{
280+
expected: expected_arg_count,
281+
received: args.len()
282+
}));
283+
}
164284
$( let $context = caller_context; )?
165285
$( let $callback = world.clone(); )?
166286
let world = world.try_read()?;
167287
world.begin_access_scope()?;
168288
let ret = {
169-
#[allow(unused_mut,unused_variables)]
170289
let mut current_arg = 0;
290+
let mut arg_iter = args.into_iter();
171291
$(
172292
current_arg += 1;
173-
let $param = <$param>::from_script($param, world.clone())
293+
let $param = <$param>::from_script(arg_iter.next().expect("invariant"), world.clone())
174294
.map_err(|e| InteropError::function_arg_conversion_error(current_arg.to_string(), e))?;
175295
)*
176296
let out = self( $( $context,)? $( $callback, )? $( $param.into(), )* );
@@ -186,7 +306,9 @@ macro_rules! impl_script_function {
186306
})();
187307
let script_value: ScriptValue = res.into();
188308
script_value
189-
}).into_function()
309+
});
310+
311+
DynamicScriptFunction { func: Arc::new(func) }
190312
}
191313
}
192314
};
@@ -264,110 +386,47 @@ macro_rules! assert_is_script_function {
264386

265387
#[cfg(test)]
266388
mod test {
267-
use crate::prelude::AppReflectAllocator;
268-
269389
use super::*;
390+
use crate::bindings::function::script_function::ScriptFunction;
391+
use crate::prelude::AppReflectAllocator;
270392
use bevy::reflect::func::{ArgList, ArgValue, Return};
271393
use test_utils::test_data::*;
272394

273395
fn test_setup_world() -> World {
274396
setup_world(|w, _| w.insert_resource(AppReflectAllocator::default()))
275397
}
276398

277-
macro_rules! call_script_function_with {
278-
($world:ident, $fun:expr, ($($args: expr),*)) => {
279-
{
280-
let f = $fun;
281-
let f = f.into_dynamic_function();
282-
283-
let o = WorldCallbackAccess::with_callback_access(&mut $world, |world| {
284-
let mut arg_list = ArgList::new();
285-
arg_list = arg_list.push_arg(ArgValue::Owned(Box::new(world.clone())));
286-
$(
287-
arg_list = arg_list.push_arg($args);
288-
)*
289-
f.call(arg_list)
290-
}).expect("Failed to call function");
291-
292-
match o {
293-
Return::Owned(v) => v.try_take().expect("Failed to convert to target type"),
294-
_ => panic!("Expected owned value")
295-
}
296-
}
297-
};
298-
}
299-
300-
#[test]
301-
fn primitive_function_should_work() {
302-
let mut world = test_setup_world();
303-
304-
let out: ScriptValue = call_script_function_with!(
305-
world,
306-
|a: usize, b: usize| a + b,
307-
(
308-
ArgValue::Owned(Box::new(ScriptValue::Integer(1))),
309-
ArgValue::Owned(Box::new(ScriptValue::Integer(1)))
310-
)
399+
fn assert_function_info_eq(a: &FunctionInfo, b: &FunctionInfo) {
400+
assert_eq!(a.name(), b.name(), "Function names do not match");
401+
assert_eq!(
402+
a.args().len(),
403+
b.args().len(),
404+
"Function arg count does not match"
311405
);
312-
assert_eq!(out, ScriptValue::Integer(2));
406+
for (a, b) in a.args().iter().zip(b.args().iter()) {
407+
assert_eq!(a.type_id(), b.type_id(), "Function arg types do not match");
408+
assert_eq!(a.name(), b.name(), "Function arg names do not match");
409+
}
313410
}
314411

315412
#[test]
316-
fn primitive_result_function_should_work() {
317-
let mut world = test_setup_world();
318-
319-
let out: ScriptValue = call_script_function_with!(
320-
world,
321-
|a: usize, b: usize| Ok(a + b),
322-
(
323-
ArgValue::Owned(Box::new(ScriptValue::Integer(1))),
324-
ArgValue::Owned(Box::new(ScriptValue::Integer(1)))
325-
)
326-
);
327-
assert_eq!(out, ScriptValue::Integer(2));
328-
329-
let out: ScriptValue = call_script_function_with!(
330-
world,
331-
|| Err::<usize, _>(InteropError::missing_world()),
332-
()
333-
);
334-
assert!(matches!(out, ScriptValue::Error(_)));
413+
fn test_register_script_function() {
414+
let mut registry = ScriptFunctionRegistry::default();
415+
let fn_ = |a: usize, b: usize| a + b;
416+
registry.register("test", fn_);
417+
registry.get_first("test").expect("Failed to get function");
335418
}
336419

337420
#[test]
338-
fn primitive_function_with_world_should_work() {
339-
let mut world = test_setup_world();
340-
341-
let out: ScriptValue = call_script_function_with!(
342-
world,
343-
|_w: WorldCallbackAccess, a: usize, b: usize| a + b,
344-
(
345-
ArgValue::Owned(Box::new(ScriptValue::Integer(1))),
346-
ArgValue::Owned(Box::new(ScriptValue::Integer(1)))
347-
)
348-
);
349-
assert_eq!(out, ScriptValue::Integer(2));
350-
}
421+
fn test_overloaded_script_function() {
422+
let mut registry = ScriptFunctionRegistry::default();
423+
let fn_ = |a: usize, b: usize| a + b;
424+
registry.register("test", fn_);
425+
let fn_2 = |a: usize, b: i32| a + (b as usize);
426+
registry.register("test", fn_2);
351427

352-
#[test]
353-
fn primitive_result_function_with_world_should_work() {
354-
let mut world = test_setup_world();
355-
356-
let out: ScriptValue = call_script_function_with!(
357-
world,
358-
|_w: WorldCallbackAccess, a: usize, b: usize| Ok(a + b),
359-
(
360-
ArgValue::Owned(Box::new(ScriptValue::Integer(1))),
361-
ArgValue::Owned(Box::new(ScriptValue::Integer(1)))
362-
)
363-
);
364-
assert_eq!(out, ScriptValue::Integer(2));
428+
registry.get_first("test").expect("Failed to get function");
365429

366-
let out: ScriptValue = call_script_function_with!(
367-
world,
368-
|| Err::<usize, _>(InteropError::missing_world()),
369-
()
370-
);
371-
assert!(matches!(out, ScriptValue::Error(_)));
430+
assert_eq!(registry.iter_overloads("test").collect::<Vec<_>>().len(), 2);
372431
}
373432
}

crates/bevy_mod_scripting_core/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ use crate::event::ScriptErrorEvent;
44
use asset::{ScriptAsset, ScriptAssetLoader, ScriptAssetSettings};
55
use bevy::prelude::*;
66
use bindings::{
7-
script_value::ScriptValue, AppReflectAllocator, ReflectAllocator, ScriptTypeRegistration,
8-
WorldCallbackAccess,
7+
function::script_function::AppScriptFunctionRegistry, script_value::ScriptValue,
8+
AppReflectAllocator, ReflectAllocator, ScriptTypeRegistration, WorldCallbackAccess,
99
};
1010
use context::{
1111
Context, ContextAssigner, ContextBuilder, ContextInitializer, ContextLoadingSettings,
@@ -79,6 +79,7 @@ impl<A: Args, C: Context, R: Runtime> Plugin for ScriptingPlugin<A, C, R> {
7979
preprocessor: None,
8080
})
8181
.insert_resource(self.runtime_settings.as_ref().cloned().unwrap_or_default())
82+
.init_resource::<AppScriptFunctionRegistry>()
8283
.insert_non_send_resource::<RuntimeContainer<R>>(RuntimeContainer {
8384
runtime: (self.runtime_builder)(),
8485
})

0 commit comments

Comments
 (0)