1use super::MagicFunctions;
4use super::{from::FromScript, into::IntoScript, namespace::Namespace};
5use crate::VariadicTuple;
6use crate::docgen::info::{FunctionInfo, GetFunctionInfo};
7use crate::function::arg_meta::ArgMeta;
8use crate::{ScriptValue, error::InteropError};
9use bevy_ecs::prelude::Resource;
10use bevy_mod_scripting_asset::Language;
11use bevy_mod_scripting_derive::DebugWithTypeInfo;
12use bevy_mod_scripting_display::DisplayWithTypeInfo;
13use bevy_mod_scripting_world::{ThreadWorldContainer, WorldGuard};
14use bevy_platform::collections::HashMap;
15use bevy_reflect::Reflect;
16use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
17use std::borrow::Cow;
18use std::collections::VecDeque;
19use std::hash::Hash;
20use std::ops::{Deref, DerefMut};
21use std::sync::Arc;
22
23#[diagnostic::on_unimplemented(
24 message = "This function does not fulfil the requirements to be a script callable function. All arguments must implement the ScriptArgument trait and all return values must implement the ScriptReturn trait"
25)]
26pub trait ScriptFunction<'env, Marker> {
28 fn into_dynamic_script_function(self) -> DynamicScriptFunction;
30}
31
32#[diagnostic::on_unimplemented(
33 message = "Only functions with all arguments impplementing FromScript and return values supporting IntoScript are supported. Registering functions also requires they implement GetTypeDependencies",
34 note = "If you're trying to use a non-primitive type, you might need to use V<T> R<T> or M<T> wrappers"
35)]
36pub trait ScriptFunctionMut<'env, Marker> {
38 fn into_dynamic_script_function_mut(self) -> DynamicScriptFunctionMut;
40}
41
42#[derive(Clone, Reflect, DebugWithTypeInfo)]
45#[debug_with_type_info(bms_display_path = "bevy_mod_scripting_display")]
46#[reflect(opaque)]
47pub struct FunctionCallContext {
48 language: Language,
49 location_context: Option<LocationContext>,
50}
51
52impl std::fmt::Display for FunctionCallContext {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 f.write_str("in language: ")?;
55 self.language.fmt(f)?;
56 if let Some(context) = &self.location_context {
57 if let Some(script_name) = &context.script_name {
58 f.write_str(", in script: ")?;
59 script_name.fmt(f)?;
60 }
61
62 f.write_str(", at line: ")?;
63 context.line.fmt(f)?;
64
65 if let Some(col) = &context.col {
66 f.write_str(", at column: ")?;
67 col.fmt(f)?;
68 }
69 }
70 Ok(())
71 }
72}
73
74#[derive(Clone, Reflect, DebugWithTypeInfo)]
75#[debug_with_type_info(bms_display_path = "bevy_mod_scripting_display")]
76pub struct LocationContext {
78 pub script_name: Option<String>,
80 pub line: u32,
82 pub col: Option<u32>,
84}
85
86impl FunctionCallContext {
87 pub const fn new(language: Language) -> Self {
89 Self {
90 language,
91 location_context: None,
92 }
93 }
94
95 pub const fn new_with_location(
97 language: Language,
98 location_context: Option<LocationContext>,
99 ) -> Self {
100 Self {
101 language,
102 location_context,
103 }
104 }
105
106 #[profiling::function]
108 pub fn world<'l>(&self) -> Result<WorldGuard<'l>, InteropError> {
109 Ok(ThreadWorldContainer.try_get_context().map(|c| c.world)?)
110 }
111 #[profiling::function]
113 pub fn convert_to_0_indexed(&self) -> bool {
114 self.language.one_indexed()
115 }
116
117 #[profiling::function]
119 pub fn language(&self) -> Language {
120 self.language.clone()
121 }
122
123 pub fn location(&self) -> Option<&LocationContext> {
125 self.location_context.as_ref()
126 }
127}
128
129#[derive(Reflect, Clone, DebugWithTypeInfo)]
130#[reflect(opaque)]
131#[debug_with_type_info(bms_display_path = "bevy_mod_scripting_display")]
132pub struct DynamicScriptFunction {
134 pub info: Arc<FunctionInfo>,
136 #[debug_with_type_info(skip)]
138 func: Arc<
139 dyn Fn(FunctionCallContext, VecDeque<ScriptValue>) -> ScriptValue + Send + Sync + 'static,
140 >,
141}
142
143#[derive(Reflect, Clone, DebugWithTypeInfo)]
144#[reflect(opaque)]
145#[debug_with_type_info(bms_display_path = "bevy_mod_scripting_display")]
146pub struct DynamicScriptFunctionMut {
148 pub info: Arc<FunctionInfo>,
150 #[debug_with_type_info(skip)]
151 func: Arc<
152 RwLock<
153 dyn FnMut(FunctionCallContext, VecDeque<ScriptValue>) -> ScriptValue
155 + Send
156 + Sync
157 + 'static,
158 >,
159 >,
160}
161
162impl DisplayWithTypeInfo for DynamicScriptFunction {
163 fn display_with_type_info(
164 &self,
165 f: &mut std::fmt::Formatter<'_>,
166 type_info_provider: Option<&WorldGuard>,
167 ) -> std::fmt::Result {
168 f.write_str("fn ")?;
169 let name = &self.info.name;
170 f.write_str(name)?;
171 f.write_str("(")?;
172 for arg in &self.info.arg_info {
173 arg.display_with_type_info(f, type_info_provider)?;
174 f.write_str(", ")?;
175 }
176 f.write_str(") -> ")?;
177 self.info
178 .return_info
179 .display_with_type_info(f, type_info_provider)
180 }
181}
182
183impl DisplayWithTypeInfo for DynamicScriptFunctionMut {
184 fn display_with_type_info(
185 &self,
186 f: &mut std::fmt::Formatter<'_>,
187 type_info_provider: Option<&WorldGuard>,
188 ) -> std::fmt::Result {
189 f.write_str("fn mut ")?;
190 let name = &self.info.name;
191 f.write_str(name)?;
192 f.write_str("(")?;
193 for arg in &self.info.arg_info {
194 arg.display_with_type_info(f, type_info_provider)?;
195 f.write_str(", ")?;
196 }
197 f.write_str(") -> ")?;
198 self.info
199 .return_info
200 .display_with_type_info(f, type_info_provider)
201 }
202}
203
204#[profiling::all_functions]
205impl DynamicScriptFunction {
206 pub fn call<I: IntoIterator<Item = ScriptValue>>(
210 &self,
211 args: I,
212 context: FunctionCallContext,
213 ) -> Result<ScriptValue, InteropError> {
214 profiling::scope!("Dynamic Call ", self.name().deref());
215 let args = args.into_iter().collect::<VecDeque<_>>();
216 let return_val = (self.func)(context.clone(), args);
218 match return_val {
219 ScriptValue::Error(e) => Err(InteropError::function_interop_error(
220 self.name(),
221 self.info.namespace,
222 e,
223 Some(context),
224 )),
225 v => Ok(v),
226 }
227 }
228
229 pub fn name(&self) -> &Cow<'static, str> {
231 &self.info.name
232 }
233
234 pub fn with_info(mut self, info: FunctionInfo) -> Self {
236 self.info = Arc::new(info);
237 self
238 }
239}
240
241#[profiling::all_functions]
242impl DynamicScriptFunctionMut {
243 pub fn call<I: IntoIterator<Item = ScriptValue>>(
247 &self,
248 args: I,
249 context: FunctionCallContext,
250 ) -> Result<ScriptValue, InteropError> {
251 profiling::scope!("Dynamic Call Mut", self.name().deref());
252 let args = args.into_iter().collect::<VecDeque<_>>();
253 let mut write = self.func.write();
255 let return_val = (write)(context.clone(), args);
256 match return_val {
257 ScriptValue::Error(e) => Err(InteropError::function_interop_error(
258 self.name(),
259 self.info.namespace,
260 e,
261 Some(context),
262 )),
263 v => Ok(v),
264 }
265 }
266
267 pub fn name(&self) -> &Cow<'static, str> {
269 &self.info.name
270 }
271
272 pub fn with_info(mut self, info: FunctionInfo) -> Self {
274 self.info = Arc::new(info);
275 self
276 }
277}
278
279impl PartialEq for DynamicScriptFunction {
280 fn eq(&self, other: &Self) -> bool {
281 std::ptr::addr_eq(self as *const Self, other as *const Self)
282 }
283}
284
285impl PartialEq for DynamicScriptFunctionMut {
286 fn eq(&self, other: &Self) -> bool {
287 std::ptr::addr_eq(self as *const Self, other as *const Self)
288 }
289}
290
291impl<F> From<F> for DynamicScriptFunction
292where
293 F: Fn(FunctionCallContext, VecDeque<ScriptValue>) -> ScriptValue + Send + Sync + 'static,
294{
295 fn from(fn_: F) -> Self {
296 DynamicScriptFunction {
297 info: FunctionInfo::default()
298 .with_name(std::any::type_name::<F>())
299 .into(),
300 func: Arc::new(fn_),
301 }
302 }
303}
304
305impl<F> From<F> for DynamicScriptFunctionMut
306where
307 F: FnMut(FunctionCallContext, VecDeque<ScriptValue>) -> ScriptValue + Send + Sync + 'static,
308{
309 fn from(fn_: F) -> Self {
310 DynamicScriptFunctionMut {
311 info: FunctionInfo::default()
312 .with_name(std::any::type_name::<F>())
313 .into(),
314 func: Arc::new(RwLock::new(fn_)),
315 }
316 }
317}
318
319#[derive(Clone, Default, Resource, DebugWithTypeInfo)]
322#[debug_with_type_info(bms_display_path = "bevy_mod_scripting_display")]
323pub struct DummyScriptFunctionRegistry(pub ScriptFunctionRegistryArc);
324
325#[derive(Clone, Default, Resource, DebugWithTypeInfo)]
327#[debug_with_type_info(bms_display_path = "bevy_mod_scripting_display")]
328pub struct AppScriptFunctionRegistry(pub ScriptFunctionRegistryArc);
329
330impl Deref for AppScriptFunctionRegistry {
331 type Target = ScriptFunctionRegistryArc;
332
333 fn deref(&self) -> &Self::Target {
334 &self.0
335 }
336}
337
338impl DerefMut for AppScriptFunctionRegistry {
339 fn deref_mut(&mut self) -> &mut Self::Target {
340 &mut self.0
341 }
342}
343
344#[derive(Clone, Default, DebugWithTypeInfo)]
345#[debug_with_type_info(bms_display_path = "bevy_mod_scripting_display")]
346pub struct ScriptFunctionRegistryArc(pub Arc<RwLock<ScriptFunctionRegistry>>);
348
349#[profiling::all_functions]
350impl ScriptFunctionRegistryArc {
351 pub fn read(&self) -> RwLockReadGuard<'_, ScriptFunctionRegistry> {
353 self.0.read()
354 }
355
356 pub fn write(&mut self) -> RwLockWriteGuard<'_, ScriptFunctionRegistry> {
358 self.0.write()
359 }
360}
361
362#[derive(Clone, PartialEq, Eq, Hash, DebugWithTypeInfo)]
363#[debug_with_type_info(bms_display_path = "bevy_mod_scripting_display")]
364pub struct FunctionKey {
366 pub name: Cow<'static, str>,
368 pub namespace: Namespace,
370}
371
372#[derive(Default, DebugWithTypeInfo)]
373#[debug_with_type_info(bms_display_path = "bevy_mod_scripting_display")]
374pub struct ScriptFunctionRegistry {
376 functions: HashMap<FunctionKey, DynamicScriptFunction>,
377 pub magic_functions: MagicFunctions,
379}
380
381#[profiling::all_functions]
382impl ScriptFunctionRegistry {
383 pub fn register<'env, F, M>(
388 &mut self,
389 namespace: Namespace,
390 name: impl Into<Cow<'static, str>>,
391 func: F,
392 ) where
393 F: ScriptFunction<'env, M> + GetFunctionInfo<M>,
394 {
395 self.register_overload(namespace, name, func, false, None::<&'static str>, None);
396 }
397
398 pub fn register_documented<'env, F, M>(
402 &mut self,
403 namespace: Namespace,
404 name: impl Into<Cow<'static, str>>,
405 func: F,
406 docs: &'static str,
407 ) where
408 F: ScriptFunction<'env, M> + GetFunctionInfo<M>,
409 {
410 self.register_overload(namespace, name, func, false, Some(docs), None);
411 }
412
413 pub fn register_with_arg_names<'env, F, M>(
417 &mut self,
418 namespace: Namespace,
419 name: impl Into<Cow<'static, str>>,
420 func: F,
421 docs: &'static str,
422 arg_names: &'static [&'static str],
423 ) where
424 F: ScriptFunction<'env, M> + GetFunctionInfo<M>,
425 {
426 self.register_overload(namespace, name, func, false, Some(docs), Some(arg_names));
427 }
428
429 pub fn overwrite<'env, F, M>(
431 &mut self,
432 namespace: Namespace,
433 name: impl Into<Cow<'static, str>>,
434 func: F,
435 ) where
436 F: ScriptFunction<'env, M> + GetFunctionInfo<M>,
437 {
438 self.register_overload(namespace, name, func, true, None::<&'static str>, None);
439 }
440
441 pub fn overwrite_documented<'env, F, M>(
443 &mut self,
444 namespace: Namespace,
445 name: impl Into<Cow<'static, str>>,
446 func: F,
447 docs: &'static str,
448 ) where
449 F: ScriptFunction<'env, M> + GetFunctionInfo<M>,
450 {
451 self.register_overload(namespace, name, func, true, Some(docs), None);
452 }
453
454 pub fn remove(
459 &mut self,
460 namespace: Namespace,
461 name: impl Into<Cow<'static, str>>,
462 ) -> Option<DynamicScriptFunction> {
463 let name = name.into();
464 self.functions.remove(&FunctionKey { name, namespace })
465 }
466
467 pub fn remove_all_overloads(
469 &mut self,
470 namespace: Namespace,
471 name: impl Into<Cow<'static, str>>,
472 ) -> Result<Vec<DynamicScriptFunction>, Cow<'static, str>> {
473 let overloads: Vec<_> = self.iter_overloads(namespace, name)?.cloned().collect();
474 for overload in overloads.iter() {
475 self.functions.remove(&FunctionKey {
476 name: overload.info.name.clone(),
477 namespace,
478 });
479 }
480 Ok(overloads)
481 }
482
483 fn register_overload<'env, F, M>(
486 &mut self,
487 namespace: Namespace,
488 name: impl Into<Cow<'static, str>>,
489 func: F,
490 overwrite: bool,
491 docs: Option<impl Into<Cow<'static, str>>>,
492 arg_names: Option<&'static [&'static str]>,
493 ) where
494 F: ScriptFunction<'env, M> + GetFunctionInfo<M>,
495 {
496 let name: Cow<'static, str> = name.into();
499 if overwrite || !self.contains(namespace, name.clone()) {
500 let info = func.get_function_info(name.clone(), namespace);
501 let info = match docs {
502 Some(docs) => info.with_docs(docs.into()),
503 None => info,
504 };
505 let info = match arg_names {
506 Some(arg_names) => info.with_arg_names(arg_names),
507 None => info,
508 };
509 let func = func.into_dynamic_script_function().with_info(info);
510 self.functions.insert(FunctionKey { name, namespace }, func);
511 return;
512 }
513
514 for i in 1.. {
515 let overload = format!("{name}-{i}");
516 if !self.contains(namespace, overload.clone()) {
517 self.register(namespace, overload, func);
518 return;
519 }
520 }
521 }
522
523 pub fn contains(&self, namespace: Namespace, name: impl Into<Cow<'static, str>>) -> bool {
525 self.functions.contains_key(&FunctionKey {
526 name: name.into(),
527 namespace,
528 })
529 }
530
531 pub fn get_function(
533 &self,
534 namespace: Namespace,
535 name: impl Into<Cow<'static, str>>,
536 ) -> Result<&DynamicScriptFunction, Cow<'static, str>> {
537 let name = name.into();
538 let key = FunctionKey { name, namespace };
539 if let Some(func) = self.functions.get(&key) {
540 Ok(func)
541 } else {
542 Err(key.name)
543 }
544 }
545
546 pub fn iter_overloads(
549 &self,
550 namespace: Namespace,
551 name: impl Into<Cow<'static, str>>,
552 ) -> Result<impl Iterator<Item = &DynamicScriptFunction>, Cow<'static, str>> {
553 let name: Cow<'static, str> = name.into();
554 let seed = match self.get_function(namespace, name.clone()) {
555 Ok(func) => std::iter::once(func),
556 Err(name) => return Err(name),
557 };
558
559 let overloads = (1..)
560 .map(move |i| {
561 if i == 0 {
562 self.get_function(namespace, name.clone())
563 } else {
564 let name: Cow<'static, str> = format!("{name}-{i}").into();
565 self.get_function(namespace, name)
566 }
567 })
568 .take_while(|o| o.is_ok())
569 .filter_map(|o| o.ok());
570
571 Ok(seed.chain(overloads))
572 }
573
574 pub fn iter_all(&self) -> impl Iterator<Item = (&FunctionKey, &DynamicScriptFunction)> {
576 self.functions.iter()
577 }
578
579 pub fn iter_namespace(
581 &self,
582 namespace: Namespace,
583 ) -> impl Iterator<Item = (&FunctionKey, &DynamicScriptFunction)> {
584 self.functions
585 .iter()
586 .filter(move |(key, _)| key.namespace == namespace)
587 }
588
589 pub fn raw_insert(
592 &mut self,
593 namespace: Namespace,
594 name: impl Into<Cow<'static, str>>,
595 func: DynamicScriptFunction,
596 ) {
597 self.functions.insert(
598 FunctionKey {
599 name: name.into(),
600 namespace,
601 },
602 func,
603 );
604 }
605}
606
607macro_rules! count {
608 () => (0usize);
609 ( $x:tt $($xs:tt)* ) => (1usize + $crate::function::script_function::count!($($xs)*));
610}
611
612fn pop_args_stack_for_arg<A: ArgMeta>(args: &mut VecDeque<ScriptValue>) -> Option<ScriptValue> {
615 if A::variadic() {
616 if !args.is_empty() {
618 return Some(ScriptValue::Tuple(VariadicTuple(std::mem::take(args))));
619 }
620 }
621
622 args.pop_front().or_else(A::default_value)
623}
624
625pub(crate) use count;
626
627macro_rules! impl_script_function {
628
629 ($( $param:ident ),* ) => {
630 impl_script_function!(@ ScriptFunction Fn DynamicScriptFunction into_dynamic_script_function $( $param ),* : -> O => O );
635 impl_script_function!(@ ScriptFunctionMut FnMut DynamicScriptFunctionMut into_dynamic_script_function_mut $( $param ),* : -> O => O );
637
638 impl_script_function!(@ ScriptFunction Fn DynamicScriptFunction into_dynamic_script_function $( $param ),* : (context: FunctionCallContext) -> O => O);
640 impl_script_function!(@ ScriptFunctionMut FnMut DynamicScriptFunctionMut into_dynamic_script_function_mut $( $param ),* : (context: FunctionCallContext) -> O => O);
642
643 impl_script_function!(@ ScriptFunction Fn DynamicScriptFunction into_dynamic_script_function $( $param ),* : -> O => Result<O, InteropError> where s);
645 impl_script_function!(@ ScriptFunctionMut FnMut DynamicScriptFunctionMut into_dynamic_script_function_mut $( $param ),* : -> O => Result<O, InteropError> where s);
647
648 impl_script_function!(@ ScriptFunction Fn DynamicScriptFunction into_dynamic_script_function $( $param ),* : (context: FunctionCallContext)-> O => Result<O, InteropError> where s);
650 impl_script_function!(@ ScriptFunctionMut FnMut DynamicScriptFunctionMut into_dynamic_script_function_mut $( $param ),* : (context: FunctionCallContext) -> O => Result<O, InteropError> where s);
652
653
654 };
655
656 (@ $trait_type:ident $fn_type:ident $dynamic_type:ident $trait_fn_name:ident $( $param:ident ),* : $(($context:ident: $contextty:ty))? -> O => $res:ty $(where $out:ident)?) => {
657 #[allow(non_snake_case)]
658 impl<
659 'env,
660 $( $param: FromScript + ArgMeta,)*
661 O,
662 F
663 > $trait_type<'env,
664 fn( $($contextty,)? $($param ),* ) -> $res
665 > for F
666 where
667 O: IntoScript,
668 F: $fn_type( $($contextty,)? $($param ),* ) -> $res + Send + Sync + 'static,
669 $( $param::This<'env>: Into<$param>,)*
670 {
671 #[allow(unused_mut,unused_variables)]
672 #[profiling::function]
673 fn $trait_fn_name(mut self) -> $dynamic_type {
674
675 let func = (move |caller_context: FunctionCallContext, mut args: VecDeque<ScriptValue> | {
676 let res: Result<ScriptValue, InteropError> = (|| {
677 profiling::scope!("script function call mechanism");
678 let received_args_len = args.len();
679 let expected_arg_count = count!($($param )*);
680
681 $( let $context = caller_context.clone(); )?
682 let world = caller_context.world()?;
683 let ret: Result<ScriptValue, InteropError> = unsafe {
685 world.with_access_scope(||{
686 let mut current_arg = 0;
687
688 $(let $param = {
689 profiling::scope!("argument conversion", &format!("argument #{}", current_arg));
690 current_arg += 1;
691 let $param = match pop_args_stack_for_arg::<$param>(&mut args) {
692 Some($param) => $param,
693 None => {
694 return Err(InteropError::argument_count_mismatch(expected_arg_count,received_args_len));
695 }
696 };
697 let $param = <$param>::from_script($param, world.clone())
698 .map_err(|e| InteropError::function_arg_conversion_error(current_arg.to_string(), e))?;
699 $param
700 };
701 )*
702
703 let ret = {
704 let out = {
705 profiling::scope!("function call");
706 self( $( $context,)? $( $param.into(), )* )
707 };
708
709 $(
710 let $out = out?;
711 let out = $out;
712 )?
713 profiling::scope!("return type conversion");
714 out.into_script(world.clone()).map_err(|e| InteropError::function_arg_conversion_error("return value".to_owned(), e))
715 };
716 ret
717 })?
718 };
719 ret
720 })();
721 let script_value: ScriptValue = res.into();
722 script_value
723 });
724
725 func.into()
726 }
727 }
728 };
729}
730
731variadics_please::all_tuples!(impl_script_function, 0, 13, T);
732
733#[cfg(test)]
734mod test {
735 use crate::{CurrentScriptAttachment, WorldExtensions};
736
737 use super::*;
738 use bevy_ecs::{prelude::Component, world::World};
739 use bevy_mod_scripting_world::{ThreadScriptContext, WorldAccessGuard};
740
741 fn with_local_world<F: Fn()>(f: F) {
742 let mut world = World::default();
743 let cache = WorldAccessGuard::setup_cache(&world, CurrentScriptAttachment::default());
744 WorldGuard::with_static_guard(&mut world, cache, |world| {
745 ThreadWorldContainer.set_context(ThreadScriptContext { world });
746 f()
747 });
748 }
749
750 #[test]
751 fn test_register_script_function() {
752 let mut registry = ScriptFunctionRegistry::default();
753 let fn_ = |a: usize, b: usize| a + b;
754
755 let namespace = Namespace::Global;
756 registry.register(namespace, "test", fn_);
757 let function = registry
758 .get_function(namespace, "test")
759 .expect("Failed to get function");
760
761 assert_eq!(function.info.name, "test");
762 assert_eq!(function.info.namespace, namespace);
763 }
764
765 #[test]
766 fn test_optional_argument_not_required() {
767 let fn_ = |a: usize, b: Option<usize>| a + b.unwrap_or(0);
768 let script_function = fn_.into_dynamic_script_function();
769
770 with_local_world(|| {
771 let out = script_function
772 .call(
773 vec![ScriptValue::from(1)],
774 FunctionCallContext::new(Language::Lua),
775 )
776 .unwrap();
777
778 assert_eq!(out, ScriptValue::from(1));
779 });
780 }
781
782 #[test]
783 fn test_invalid_amount_of_args_errors_nicely() {
784 let fn_ = |a: usize, b: usize| a + b;
785 let script_function = fn_.into_dynamic_script_function();
786
787 with_local_world(|| {
788 let out = script_function.call(
789 vec![ScriptValue::from(1)],
790 FunctionCallContext::new(Language::Lua),
791 );
792
793 assert!(out.is_err());
794
795 let gotten = out.unwrap_err();
796 let expected_function_name = "<bevy_mod_scripting_bindings::function::script_function::test::test_invalid_amount_of_args_errors_nicely::{{closure}} as bevy_mod_scripting_bindings::function::script_function::ScriptFunction<'_, fn(usize, usize) -> usize>>::into_dynamic_script_function::{{closure}}";
797 let expected_namespace = Namespace::Global;
798
799 if let InteropError::FunctionInteropError {
800 function_name,
801 on,
802 error,
803 ..
804 } = gotten
805 {
806 assert_eq!(*function_name, expected_function_name);
807 assert_eq!(*on, expected_namespace);
808 if let InteropError::ArgumentCountMismatch { expected, got } = error.as_ref() {
809 assert_eq!(*expected, 2);
810 assert_eq!(*got, 1);
811 } else {
812 panic!("Expected ArgumentCountMismatch, got {error:?}");
813 }
814 } else {
815 panic!("Expected FunctionInteropError, got {gotten:?}");
816 }
817 });
818 }
819
820 #[test]
821 fn test_interrupted_call_releases_access_scope() {
822 #[derive(Component, Reflect)]
823 struct Comp;
824
825 let fn_ = |_a: crate::function::from::M<Comp>| 0usize;
826 let script_function = fn_.into_dynamic_script_function();
827
828 with_local_world(|| {
829 let out = script_function.call(
830 vec![ScriptValue::from(1)],
831 FunctionCallContext::new(Language::Lua),
832 );
833
834 assert!(out.is_err());
835 let world = FunctionCallContext::new(Language::Lua).world().unwrap();
836 assert!(world.list_accesses().is_empty());
838 });
839 }
840
841 #[test]
842 fn test_overloaded_script_function() {
843 let mut registry = ScriptFunctionRegistry::default();
844 let fn_ = |a: usize, b: usize| a + b;
845 let namespace = Namespace::Global;
846 registry.register(namespace, "test", fn_);
847 let fn_2 = |a: usize, b: i32| a + (b as usize);
848 registry.register(namespace, "test", fn_2);
849
850 let first_function = registry
851 .get_function(namespace, "test")
852 .expect("Failed to get function");
853
854 assert_eq!(first_function.info.name, "test");
855 assert_eq!(first_function.info.namespace, namespace);
856
857 let all_functions = registry
858 .iter_overloads(namespace, "test")
859 .expect("Failed to get overloads")
860 .collect::<Vec<_>>();
861
862 assert_eq!(all_functions.len(), 2);
863 assert_eq!(all_functions[0].info.name, "test");
864 assert_eq!(all_functions[1].info.name, "test-1");
865 }
866
867 #[test]
868 fn test_overwrite_script_function() {
869 let mut registry = ScriptFunctionRegistry::default();
870 let fn_ = |a: usize, b: usize| a + b;
871 let namespace = Namespace::Global;
872 registry.register(namespace, "test", fn_);
873 let fn_2 = |a: usize, b: i32| a + (b as usize);
874 registry.overwrite(namespace, "test", fn_2);
875
876 let all_functions = registry
877 .iter_overloads(namespace, "test")
878 .expect("Failed to get overloads")
879 .collect::<Vec<_>>();
880
881 assert_eq!(all_functions.len(), 1);
882 assert_eq!(all_functions[0].info.name, "test");
883 }
884
885 #[test]
886 fn test_remove_script_function() {
887 let mut registry = ScriptFunctionRegistry::default();
888 let fn_ = |a: usize, b: usize| a + b;
889 let namespace = Namespace::Global;
890 registry.register(namespace, "test", fn_);
891 let removed = registry.remove(namespace, "test");
892 assert!(removed.is_some());
893 let removed = registry.remove(namespace, "test");
894 assert!(removed.is_none());
895 }
896
897 #[test]
898 fn test_remove_all_overloads() {
899 let mut registry = ScriptFunctionRegistry::default();
900 let fn_ = |a: usize, b: usize| a + b;
901 let namespace = Namespace::Global;
902 registry.register(namespace, "test", fn_);
903 let fn_2 = |a: usize, b: i32| a + (b as usize);
904 registry.register(namespace, "test", fn_2);
905
906 let removed = registry
907 .remove_all_overloads(namespace, "test")
908 .expect("Failed to remove overloads");
909 assert_eq!(removed.len(), 2);
910 assert!(registry.get_function(namespace, "test").is_err());
911 }
912}