@@ -8,22 +8,26 @@ use crate::{
8
8
prelude:: { ScriptValue , WorldCallbackAccess } ,
9
9
} ;
10
10
use bevy:: {
11
- prelude:: { AppFunctionRegistry , IntoFunction , Reflect , World } ,
11
+ prelude:: { AppFunctionRegistry , IntoFunction , Reflect , Resource , World } ,
12
12
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 ,
15
16
} ,
16
17
} ;
18
+ use parking_lot:: { RwLock , RwLockReadGuard , RwLockWriteGuard } ;
19
+ use std:: borrow:: Cow ;
17
20
use std:: collections:: HashMap ;
18
21
use std:: hash:: Hash ;
22
+ use std:: ops:: { Deref , DerefMut } ;
19
23
use std:: sync:: Arc ;
20
24
21
25
#[ diagnostic:: on_unimplemented(
22
26
message = "Only functions with all arguments impplementing FromScript and return values supporting IntoScript are supported. Registering functions also requires they implement GetInnerTypeDependencies" ,
23
27
note = "If you're trying to return a non-primitive type, you might need to use Val<T> Ref<T> or Mut<T> wrappers"
24
28
) ]
25
29
pub trait ScriptFunction < ' env , Marker > {
26
- fn into_dynamic_function ( self ) -> DynamicFunction < ' static > ;
30
+ fn into_dynamic_script_function ( self ) -> DynamicScriptFunction ;
27
31
}
28
32
29
33
/// Functionally identical to [`GetTypeRegistration`] but without the 'static bound
@@ -111,18 +115,127 @@ pub struct CallerContext {
111
115
}
112
116
113
117
/// 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 ) ]
115
119
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
117
121
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 ,
123
126
> ,
124
127
}
125
128
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
+
126
239
macro_rules! impl_script_function {
127
240
128
241
( $( $param: ident ) ,* ) => {
@@ -153,24 +266,31 @@ macro_rules! impl_script_function {
153
266
fn ( $( $contextty, ) ? $( $callbackty, ) ? $( $param ) ,* ) -> $res
154
267
> for F
155
268
where
156
- O : IntoScript ,
269
+ O : IntoScript + TypePath + GetOwnership ,
157
270
F : Fn ( $( $contextty, ) ? $( $callbackty, ) ? $( $param ) ,* ) -> $res + Send + Sync + ' static ,
158
- $( $param:: This <' env>: Into <$param>) , *
271
+ $( $param:: This <' env>: Into <$param>, ) *
159
272
{
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 > | {
163
276
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
+ }
164
284
$( let $context = caller_context; ) ?
165
285
$( let $callback = world. clone( ) ; ) ?
166
286
let world = world. try_read( ) ?;
167
287
world. begin_access_scope( ) ?;
168
288
let ret = {
169
- #[ allow( unused_mut, unused_variables) ]
170
289
let mut current_arg = 0 ;
290
+ let mut arg_iter = args. into_iter( ) ;
171
291
$(
172
292
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( ) )
174
294
. map_err( |e| InteropError :: function_arg_conversion_error( current_arg. to_string( ) , e) ) ?;
175
295
) *
176
296
let out = self ( $( $context, ) ? $( $callback, ) ? $( $param. into( ) , ) * ) ;
@@ -186,7 +306,9 @@ macro_rules! impl_script_function {
186
306
} ) ( ) ;
187
307
let script_value: ScriptValue = res. into( ) ;
188
308
script_value
189
- } ) . into_function( )
309
+ } ) ;
310
+
311
+ DynamicScriptFunction { func: Arc :: new( func) }
190
312
}
191
313
}
192
314
} ;
@@ -264,110 +386,47 @@ macro_rules! assert_is_script_function {
264
386
265
387
#[ cfg( test) ]
266
388
mod test {
267
- use crate :: prelude:: AppReflectAllocator ;
268
-
269
389
use super :: * ;
390
+ use crate :: bindings:: function:: script_function:: ScriptFunction ;
391
+ use crate :: prelude:: AppReflectAllocator ;
270
392
use bevy:: reflect:: func:: { ArgList , ArgValue , Return } ;
271
393
use test_utils:: test_data:: * ;
272
394
273
395
fn test_setup_world ( ) -> World {
274
396
setup_world ( |w, _| w. insert_resource ( AppReflectAllocator :: default ( ) ) )
275
397
}
276
398
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"
311
405
) ;
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
+ }
313
410
}
314
411
315
412
#[ 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" ) ;
335
418
}
336
419
337
420
#[ 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) ;
351
427
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" ) ;
365
429
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 ) ;
372
431
}
373
432
}
0 commit comments