Controlling Script Bindings

In this book we refer to anything accessible by a script, which allows it to communicate with your Rust code a binding (which in previous versions was more generically referred to as a script API).

The "binding" here being used as in: binding script code to rust code.

Namespaces

Namespaces are a way to group functions together, and are used to prevent naming conflicts. You can have multiple namespaces, and each namespace can have multiple functions.

Language implementations will also look for specific functions registered on your type first before looking at the generic ReflectReference namespace.

Dynamic Functions

Everything callable by scripts must first be registered in the dynamic function registry. Notably we do not make use of the normal bevy function registry to improve performance and usability. This means you cannot call just any function.

In order for a function to be callable by a script it must adhere to a few requirements:

  • Each argument must implement FromScript.
  • Each return type must implement IntoScript.
  • Each argument must also implement GetTypeDependencies
  • Each return type must also implement GetTypeDependencies

The into/from requirements allow us to convert these types to ScriptValue's, and each supported scripting language can then marshall these into the script.

Note these types are implemented for primitives, but if you want to interact with one of your Reflect implementing types, you will need to use one of Ref<T>, Mut<T> or Val<T> wrappers in place of &T, &mut T and T respectively.

These wrappers enable us to safely interact with bevy, and claim any necessary mutex'es on Resources, Components or Allocations.

The GetTypeDependencies, trait is simply a local trait alias for GetTypeRegistration with less strict type requirements. It allows us to register all the types necessary for the function calls, so that you don't have to register anything manually. If your type implements GetTypeRegistration you should not face any issues on this front.

Registering Script Functions

Registering functions can be done via the NamespaceBuilder like below:

    NamespaceBuilder::<ReflectReference>::new(&mut world)
        .register(
            "hello_world",
            |s: String| {
                println!(s)
            },
        );

    NamespaceBuilder::<GlobalNamespace>::new_unregistered(&mut world)
        .register(
            "hello_world2",
            |s: String| {
                println!(s)
            },
        );

This will allow you to call this function within lua like so:

some_type:hello_world("hi from method!");
hello_world2("hi from global!");

Note the new_unregistered call instead of new, this is because GlobalNamespace is not a Reflect type, and the new call also automatically registers the type in the reflection registry.

Context Arguments

Each script function call always receives an additional context argument: FunctionCallContext. You can opt-in to receive this argument in your own function definitions by adding it as the first argument.

The context contains requests from the caller to your function, such as "I am calling you from a 1-indexed array system, please convert the index first", This argument is only relevant if you're targeting multiple languages.

It also allows you to retrieve the world via FunctionCallContext::world().

You can use this as follows:

    NamespaceBuilder::<ReflectReference>::new(&mut world)
        .register(
            "hello_world",
            |ctx: FunctionCallContext, s: String| {
                let world = ctx.world()?;
                let should_use_0_indexing = ctx.convert_to_0_indexed;
                println!(should_use_0_indexing);
                println!(s)
                Ok(())
            },
        );

Generic Arguments

Sometimes you might want to be generic over the type of argument you're accepting, you can do so by accepting ScriptValue arguments like so:

    NamespaceBuilder::<ReflectReference>::new(&mut world)
        .register(
            "is_integer",
            |s: ScriptValue| {
                match s {
                    ScriptValue::Integer(i) => true,
                    _ => false
                }
            },
        );

You can treat return values similarly.

Fallible functions

Your script functions can return errors either by:

  • Returning Result<T: IntoScript, InteropError>
  • Returning ScriptValue and manually creating the ScriptValue::Error(into_interop_erorr.into()) variant.

Reserved Functions

There are a few reserved functions that you can override by registering them on a specific type:

Function NameDescriptionOverridable?Has Default Implementation?
geta getter function, used for indexing into a type
seta setter function, used for setting a value on a type
suba subtraction function, used for subtracting two values
addan addition function, used for adding two values
mula multiplication function, used for multiplying two values
diva division function, used for dividing two values
rema remainder function, used for getting the remainder of two values
nega negation function, used for negating a value
powa power function, used for raising a value to a power
eqan equality function, used for checking if two values are equal
lta less than function, used for checking if a value is less than another
iteran iterator function, used for iterating over a value
display_refa display function, used for displaying a reference to a value
display_valuea display function, used for displaying a mutable reference to a value

In this context overridable indicates whether language implementations will look for a specific function on your type before looking at the generic ReflectReference namespace. You can still remove the existing registration for these functions on the ReflectReference namespace if you want to replace them with your own implementation.