Skip to main content

bevy_mod_scripting_bindings/function/
script_function.rs

1//! Implementations of the [`ScriptFunction`] and [`ScriptFunctionMut`] traits for functions with up to 13 arguments.
2
3use 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)]
26/// A trait implemented by functions which can act as dynamic script functions, which can then be registered against a [`ScriptFunctionRegistry`].
27pub trait ScriptFunction<'env, Marker> {
28    /// Convert this function into a [`DynamicScriptFunction`]
29    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)]
36/// A trait implemented by functions which can act as mutable dynamic script functions.
37pub trait ScriptFunctionMut<'env, Marker> {
38    /// Convert this function into a [`DynamicScriptFunctionMut`]
39    fn into_dynamic_script_function_mut(self) -> DynamicScriptFunctionMut;
40}
41
42/// The caller context when calling a script function.
43/// Functions can choose to react to caller preferences such as converting 1-indexed numbers to 0-indexed numbers
44#[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")]
76/// Describes a location within a script
77pub struct LocationContext {
78    /// The name of the script the function call originates from
79    pub script_name: Option<String>,
80    /// The line number
81    pub line: u32,
82    /// The column number
83    pub col: Option<u32>,
84}
85
86impl FunctionCallContext {
87    /// Create a new FunctionCallContext with the given 1-indexing conversion preference
88    pub const fn new(language: Language) -> Self {
89        Self {
90            language,
91            location_context: None,
92        }
93    }
94
95    /// Creates a new function call context with location information
96    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    /// Tries to access the world, returning an error if the world is not available
107    #[profiling::function]
108    pub fn world<'l>(&self) -> Result<WorldGuard<'l>, InteropError> {
109        Ok(ThreadWorldContainer.try_get_context().map(|c| c.world)?)
110    }
111    /// Whether the caller uses 1-indexing on all indexes and expects 0-indexing conversions to be performed.
112    #[profiling::function]
113    pub fn convert_to_0_indexed(&self) -> bool {
114        self.language.one_indexed()
115    }
116
117    /// Gets the scripting language of the caller
118    #[profiling::function]
119    pub fn language(&self) -> Language {
120        self.language.clone()
121    }
122
123    /// Returns call location inside the script if available
124    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")]
132/// A dynamic script function.
133pub struct DynamicScriptFunction {
134    /// The meta information about the function
135    pub info: Arc<FunctionInfo>,
136    // TODO: info about the function, this is hard right now because of non 'static lifetimes in wrappers, we can't use TypePath etc
137    #[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")]
146/// A dynamic mutable script function.
147pub struct DynamicScriptFunctionMut {
148    /// The meta information about the function
149    pub info: Arc<FunctionInfo>,
150    #[debug_with_type_info(skip)]
151    func: Arc<
152        RwLock<
153            // I'd rather consume an option or something instead of having the RWLock but I just wanna get this release out
154            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    /// Call the function with the given arguments and caller context.
207    ///
208    /// In the case of errors wraps the error in a [`InteropError::function_interop_error`] to provide more context.
209    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        // should we be inlining call errors into the return value?
217        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    /// Get the name of the function
230    pub fn name(&self) -> &Cow<'static, str> {
231        &self.info.name
232    }
233
234    /// Set the meta information about the function
235    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    /// Call the function with the given arguments and caller context.
244    ///
245    /// In the case of errors wraps the error in a [`InteropError::function_interop_error`] to provide more context.
246    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        // should we be inlining call errors into the return value?
254        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    /// Get the name of the function
268    pub fn name(&self) -> &Cow<'static, str> {
269        &self.info.name
270    }
271
272    /// Set the meta information about the function
273    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/// Identical to the [`AppScriptFunctionRegistry`], but the functions only exist for docs purposes, use if you provide functions at a lower level,
320/// but still want to include the function in the docs
321#[derive(Clone, Default, Resource, DebugWithTypeInfo)]
322#[debug_with_type_info(bms_display_path = "bevy_mod_scripting_display")]
323pub struct DummyScriptFunctionRegistry(pub ScriptFunctionRegistryArc);
324
325/// Equivalent to [`AppScriptFunctionRegistry`] but stores functions with a more convenient signature for scripting to avoid boxing every argument.
326#[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")]
346/// A thread-safe reference counted wrapper around a [`ScriptFunctionRegistry`]
347pub struct ScriptFunctionRegistryArc(pub Arc<RwLock<ScriptFunctionRegistry>>);
348
349#[profiling::all_functions]
350impl ScriptFunctionRegistryArc {
351    /// claim a read lock on the registry
352    pub fn read(&self) -> RwLockReadGuard<'_, ScriptFunctionRegistry> {
353        self.0.read()
354    }
355
356    /// claim a write lock on the registry
357    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")]
364/// A key used to identify a function in the registry
365pub struct FunctionKey {
366    /// The name of the function
367    pub name: Cow<'static, str>,
368    /// The namespace of the function
369    pub namespace: Namespace,
370}
371
372#[derive(Default, DebugWithTypeInfo)]
373#[debug_with_type_info(bms_display_path = "bevy_mod_scripting_display")]
374/// A registry of dynamic script functions
375pub struct ScriptFunctionRegistry {
376    functions: HashMap<FunctionKey, DynamicScriptFunction>,
377    /// A registry of magic functions
378    pub magic_functions: MagicFunctions,
379}
380
381#[profiling::all_functions]
382impl ScriptFunctionRegistry {
383    /// Register a script function with the given name. If the name already exists,
384    /// the new function will be registered as an overload of the function.
385    ///
386    /// If you want to overwrite an existing function, use [`ScriptFunctionRegistry::overwrite`]
387    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    /// Equivalent to [`ScriptFunctionRegistry::register`] but with the ability to provide documentation for the function.
399    ///
400    /// The docstring will be added to the function's metadata and can be accessed at runtime.
401    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    /// Equivalent to [`ScriptFunctionRegistry::register`] but with the ability to provide argument names for the function as well as documentation.
414    ///
415    /// The argument names and docstring will be added to the function's metadata and can be accessed at runtime.
416    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    /// Overwrite a function with the given name. If the function does not exist, it will be registered as a new function.
430    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    /// Equivalent to [`ScriptFunctionRegistry::overwrite`] but with the ability to provide documentation for the function.
442    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    /// Remove a function from the registry if it exists. Returns the removed function if it was found.
455    ///
456    /// Note if the function is overloaded, you will need to remove each overload individually.
457    /// Use [`ScriptFunctionRegistry::remove_all_overloads`] to remove all overloads at once.
458    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    /// Remove all overloads of a function with the given name. Returns a vector of the removed functions.
468    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    /// Register a script function with the given name. If the name already exists,
484    /// the new function will be registered as an overload of the function.
485    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        // always start with non-suffixed registration
497        // TODO: we do alot of string work, can we make this all more efficient?
498        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    /// Check if a function with the given name and namespace exists
524    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    /// Get the first overload for the function with the given name and namespace
532    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    /// Iterate over all overloads for the function with the given name and namespace
547    /// If the iterator variant is returned it is guaranteed to contain at least one element
548    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    /// Iterates over all functions including overloads
575    pub fn iter_all(&self) -> impl Iterator<Item = (&FunctionKey, &DynamicScriptFunction)> {
576        self.functions.iter()
577    }
578
579    /// Iterates over all functions in the given namespace
580    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    /// Insert a function into the registry with the given key, this will not perform any overloading logic.
590    /// Do not use unless you really need to.
591    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
612/// Pops the stack of args depending on how many are requested by the current argument.
613/// If failed to find argument, returns None
614fn pop_args_stack_for_arg<A: ArgMeta>(args: &mut VecDeque<ScriptValue>) -> Option<ScriptValue> {
615    if A::variadic() {
616        // just absorb the rest, tuplify it
617        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        // all of this is pretty heavy on the compile time.
631        // ideally we'd do less, but for now this will suffice
632
633        // Fn(T1...Tn) -> O
634        impl_script_function!(@ ScriptFunction Fn DynamicScriptFunction into_dynamic_script_function $( $param ),* : -> O => O );
635        // FnMut(T1...Tn) -> O
636        impl_script_function!(@ ScriptFunctionMut FnMut DynamicScriptFunctionMut into_dynamic_script_function_mut $( $param ),* : -> O => O );
637
638        // Fn(CallerContext, T1...Tn) -> O
639        impl_script_function!(@ ScriptFunction Fn DynamicScriptFunction into_dynamic_script_function $( $param ),* : (context: FunctionCallContext) -> O => O);
640        // FnMut(FunctionCallContext, T1...Tn) -> O
641        impl_script_function!(@ ScriptFunctionMut FnMut DynamicScriptFunctionMut into_dynamic_script_function_mut $( $param ),* : (context: FunctionCallContext) -> O => O);
642
643        // Fn(T1...Tn) -> Result<O, InteropError>
644        impl_script_function!(@ ScriptFunction Fn DynamicScriptFunction into_dynamic_script_function $( $param ),* : -> O => Result<O, InteropError> where s);
645        // FnMut(T1...Tn) -> Result<O, InteropError>
646        impl_script_function!(@ ScriptFunctionMut FnMut DynamicScriptFunctionMut into_dynamic_script_function_mut $( $param ),* : -> O => Result<O, InteropError> where s);
647
648        // Fn(FunctionCallContext, WorldGuard<'w>, T1...Tn) -> Result<O, InteropError>
649        impl_script_function!(@ ScriptFunction Fn DynamicScriptFunction into_dynamic_script_function $( $param ),* : (context: FunctionCallContext)-> O => Result<O, InteropError> where s);
650        // FnMut(FunctionCallContext, WorldGuard<'w>, T1...Tn) -> Result<O, InteropError>
651        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                        // Safety: we're not holding any references to the world, the arguments which might have aliased will always be dropped
684                        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 no access is held
837            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}