Skip to content

feat: Add script_bindings impl block derive macro #263

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 23 additions & 7 deletions .github/workflows/bevy_mod_scripting.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,9 @@ on:
branches:
- main
- staging
paths-ignore:
- '.github/workflows/release-plz.yml'
- 'docs/**'
pull_request:
branches:
- "**"
paths-ignore:
- '.github/workflows/release-plz.yml'
- 'docs/**'


name: CI
Expand All @@ -31,6 +25,27 @@ concurrency:
cancel-in-progress: true

jobs:

check-needs-run:
outputs:
any-changes: ${{ steps.changes.outputs.src }}
permissions:
pull-requests: read
contents: read
steps:
- name: Checkout
uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: changes
with:
base: main
filters: |
src:
- 'src/**'
- 'docs/**'
- '.github/workflows/bevy_mod_scripting.yml'


generate-job-matrix:
runs-on: ubuntu-latest
# container: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
Expand All @@ -49,13 +64,14 @@ jobs:
echo "matrix=$(cat matrix-one-line.json)" >> $GITHUB_OUTPUT

check:
needs: [check-needs-run]
permissions:
pull-requests: write
contents: write
issues: write
name: Check - ${{ matrix.run_args.name }}
runs-on: ${{ matrix.run_args.os }}
# container: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
if: ${{ needs.check-needs-run.outputs.any-changes == 'true' }}
needs:
- generate-job-matrix
strategy:
Expand Down
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,14 @@ bevy_mod_scripting_lua = { path = "crates/languages/bevy_mod_scripting_lua", ver
bevy_mod_scripting_rhai = { path = "crates/languages/bevy_mod_scripting_rhai", version = "0.9.3", optional = true }
# bevy_mod_scripting_rune = { path = "crates/languages/bevy_mod_scripting_rune", version = "0.9.0-alpha.2", optional = true }
bevy_mod_scripting_functions = { workspace = true }
bevy_mod_scripting_derive = { workspace = true }

[workspace.dependencies]
profiling = { version = "1.0" }
bevy = { version = "0.15.1", default-features = false }
bevy_mod_scripting_core = { path = "crates/bevy_mod_scripting_core", version = "0.9.3" }
bevy_mod_scripting_functions = { path = "crates/bevy_mod_scripting_functions", version = "0.9.3", default-features = false }
bevy_mod_scripting_derive = { path = "crates/bevy_mod_scripting_derive", version = "0.9.3" }

# test utilities
script_integration_test_harness = { path = "crates/script_integration_test_harness" }
Expand All @@ -85,6 +88,7 @@ members = [
"crates/bevy_mod_scripting_functions",
"crates/xtask",
"crates/script_integration_test_harness",
"crates/bevy_mod_scripting_derive",
]
resolver = "2"
exclude = ["crates/bevy_api_gen", "crates/macro_tests"]
Expand Down
2 changes: 2 additions & 0 deletions crates/bevy_mod_scripting_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ smallvec = "1.11"
itertools = "0.13"
derivative = "2.2"
profiling = { workspace = true }
bevy_mod_scripting_derive = { workspace = true }

[dev-dependencies]
test_utils = { workspace = true }
tokio = { version = "1", features = ["rt", "macros"] }
Expand Down
54 changes: 52 additions & 2 deletions crates/bevy_mod_scripting_core/src/bindings/function/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,65 @@ pub mod type_dependencies;
#[cfg(test)]
#[allow(dead_code)]
mod test {
use bevy::reflect::{FromReflect, GetTypeRegistration, Typed};
use bevy::reflect::{FromReflect, GetTypeRegistration, Reflect, Typed};
use bevy_mod_scripting_derive::script_bindings;

use crate::{
bindings::function::from::{Ref, Val},
bindings::function::{
from::{Ref, Val},
namespace::IntoNamespace,
script_function::AppScriptFunctionRegistry,
},
error::InteropError,
};

use super::arg_meta::{ScriptArgument, ScriptReturn};

#[test]
fn test_macro_generates_correct_registrator_function() {
#[derive(Reflect)]
struct TestStruct;

#[script_bindings(bms_core_path = "crate", name = "test_fn")]
impl TestStruct {
/// My docs !!
fn test_fn(_self: Ref<TestStruct>, mut _arg1: usize) {}
}

let mut test_world = bevy::ecs::world::World::default();

register_test_fn(&mut test_world);

let app_registry = test_world
.get_resource::<AppScriptFunctionRegistry>()
.unwrap();
let app_registry = app_registry.read();

let test_fn = app_registry
.get_function(TestStruct::into_namespace(), "test_fn")
.unwrap();

assert_eq!(test_fn.info.docs, Some("My docs !!".into()));
assert_eq!(test_fn.info.arg_info.len(), 2);

assert_eq!(
test_fn.info.arg_info[0].type_id,
std::any::TypeId::of::<Ref<TestStruct>>()
);
assert_eq!(test_fn.info.arg_info[0].name, Some("_self".into()));

assert_eq!(
test_fn.info.arg_info[1].type_id,
std::any::TypeId::of::<usize>()
);
assert_eq!(test_fn.info.arg_info[1].name, Some("_arg1".into()));

assert_eq!(
test_fn.info.return_info.type_id,
std::any::TypeId::of::<()>()
);
}

fn test_is_valid_return<T: ScriptReturn>() {}
fn test_is_valid_arg<T: ScriptArgument>() {}
fn test_is_valid_arg_and_return<T: ScriptArgument + ScriptReturn>() {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,35 @@ impl<'a, S: IntoNamespace> NamespaceBuilder<'a, S> {

/// Registers a function in the namespace
pub fn register<'env, N, F, M>(&mut self, name: N, function: F) -> &mut Self
where
N: Into<Cow<'static, str>>,
F: ScriptFunction<'env, M> + GetFunctionTypeDependencies<M> + GetFunctionInfo<M>,
{
self.register_inner(name, function, None, None)
}

/// Registers a function in the namespace with a docstring
pub fn register_documented<'env, N, F, M>(
&mut self,
name: N,
function: F,
docstring: &'static str,
arg_names: &'static [&'static str],
) -> &mut Self
where
N: Into<Cow<'static, str>>,
F: ScriptFunction<'env, M> + GetFunctionTypeDependencies<M> + GetFunctionInfo<M>,
{
self.register_inner(name, function, Some(docstring), Some(arg_names))
}

fn register_inner<'env, N, F, M>(
&mut self,
name: N,
function: F,
docstring: Option<&'static str>,
arg_names: Option<&'static [&'static str]>,
) -> &mut Self
where
N: Into<Cow<'static, str>>,
F: ScriptFunction<'env, M> + GetFunctionTypeDependencies<M> + GetFunctionInfo<M>,
Expand All @@ -108,7 +137,13 @@ impl<'a, S: IntoNamespace> NamespaceBuilder<'a, S> {
.world
.get_resource_or_init::<AppScriptFunctionRegistry>();
let mut registry = registry.write();
registry.register(S::into_namespace(), name, function);
registry.register_with_arg_names(
S::into_namespace(),
name,
function,
docstring.unwrap_or_default(),
arg_names.unwrap_or(&[]),
);
}
{
let type_registry = self.world.get_resource_or_init::<AppTypeRegistry>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,47 +320,63 @@ impl ScriptFunctionRegistry {
) where
F: ScriptFunction<'env, M> + GetFunctionInfo<M>,
{
self.register_overload(namespace, name, func, false, None::<&'static str>);
self.register_overload(namespace, name, func, false, None::<&'static str>, None);
}

/// Equivalent to [`ScriptFunctionRegistry::register`] but with the ability to provide documentation for the function.
///
/// The docstring will be added to the function's metadata and can be accessed at runtime.
pub fn register_documented<F, M>(
pub fn register_documented<'env, F, M>(
&mut self,
namespace: Namespace,
name: impl Into<Cow<'static, str>>,
func: F,
docs: &'static str,
) where
F: ScriptFunction<'static, M> + GetFunctionInfo<M>,
F: ScriptFunction<'env, M> + GetFunctionInfo<M>,
{
self.register_overload(namespace, name, func, false, Some(docs), None);
}

/// Equivalent to [`ScriptFunctionRegistry::register`] but with the ability to provide argument names for the function as well as documentation.
///
/// The argument names and docstring will be added to the function's metadata and can be accessed at runtime.
pub fn register_with_arg_names<'env, F, M>(
&mut self,
namespace: Namespace,
name: impl Into<Cow<'static, str>>,
func: F,
docs: &'static str,
arg_names: &'static [&'static str],
) where
F: ScriptFunction<'env, M> + GetFunctionInfo<M>,
{
self.register_overload(namespace, name, func, false, Some(docs));
self.register_overload(namespace, name, func, false, Some(docs), Some(arg_names));
}

/// Overwrite a function with the given name. If the function does not exist, it will be registered as a new function.
pub fn overwrite<F, M>(
pub fn overwrite<'env, F, M>(
&mut self,
namespace: Namespace,
name: impl Into<Cow<'static, str>>,
func: F,
) where
F: ScriptFunction<'static, M> + GetFunctionInfo<M>,
F: ScriptFunction<'env, M> + GetFunctionInfo<M>,
{
self.register_overload(namespace, name, func, true, None::<&'static str>);
self.register_overload(namespace, name, func, true, None::<&'static str>, None);
}

/// Equivalent to [`ScriptFunctionRegistry::overwrite`] but with the ability to provide documentation for the function.
pub fn overwrite_documented<F, M>(
pub fn overwrite_documented<'env, F, M>(
&mut self,
namespace: Namespace,
name: impl Into<Cow<'static, str>>,
func: F,
docs: &'static str,
) where
F: ScriptFunction<'static, M> + GetFunctionInfo<M>,
F: ScriptFunction<'env, M> + GetFunctionInfo<M>,
{
self.register_overload(namespace, name, func, true, Some(docs));
self.register_overload(namespace, name, func, true, Some(docs), None);
}

/// Remove a function from the registry if it exists. Returns the removed function if it was found.
Expand Down Expand Up @@ -401,6 +417,7 @@ impl ScriptFunctionRegistry {
func: F,
overwrite: bool,
docs: Option<impl Into<Cow<'static, str>>>,
arg_names: Option<&'static [&'static str]>,
) where
F: ScriptFunction<'env, M> + GetFunctionInfo<M>,
{
Expand All @@ -413,6 +430,10 @@ impl ScriptFunctionRegistry {
Some(docs) => info.with_docs(docs.into()),
None => info,
};
let info = match arg_names {
Some(arg_names) => info.with_arg_names(arg_names),
None => info,
};
let func = func.into_dynamic_script_function().with_info(info);
self.functions.insert(FunctionKey { name, namespace }, func);
return;
Expand Down
14 changes: 14 additions & 0 deletions crates/bevy_mod_scripting_core/src/docgen/info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,20 @@ impl FunctionInfo {
self.docs = Some(docs.into());
self
}

/// Add argument names to the function info.
///
/// If the number of argument names is less than the number of arguments, the remaining arguments will be unnamed.
/// If the number of argument names is greater than the number of arguments, the extra argument names will be ignored.
pub fn with_arg_names(mut self, arg_names: &[&'static str]) -> Self {
self.arg_info
.iter_mut()
.zip(arg_names.iter())
.for_each(|(arg, name)| {
arg.name = Some(Cow::Borrowed(*name));
});
self
}
}

#[derive(Debug, Clone, PartialEq, Reflect)]
Expand Down
23 changes: 23 additions & 0 deletions crates/bevy_mod_scripting_derive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "bevy_mod_scripting_derive"
version = "0.9.3"
edition = "2021"
authors = ["Maksymilian Mozolewski <makspl17@gmail.com>"]
license = "MIT OR Apache-2.0"
description = "Necessary functionality for Lua support with bevy_mod_scripting"
repository = "https://github.com/makspll/bevy_mod_scripting"
homepage = "https://github.com/makspll/bevy_mod_scripting"
keywords = ["bevy", "gamedev", "scripting", "rhai"]
categories = ["game-development"]
readme = "readme.md"

[dependencies]
syn = "2"
proc-macro2 = "1"
quote = "1"

[lib]
proc-macro = true

[lints]
workspace = true
3 changes: 3 additions & 0 deletions crates/bevy_mod_scripting_derive/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# bevy_mod_scripting_lua_derive

This crate is a part of the ["bevy_mod_scripting" workspace](https://github.com/makspll/bevy_mod_scripting).
Loading