Skip to content

Commit 15b1d9a

Browse files
authored
feat: Add script_bindings impl block derive macro (#263)
* feat: Add `script_bindings` impl block derive macro * make paths check via conditional to allow required checks to be skipped * consolidate filters
1 parent c5347fb commit 15b1d9a

File tree

12 files changed

+386
-20
lines changed

12 files changed

+386
-20
lines changed

.github/workflows/bevy_mod_scripting.yml

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,9 @@ on:
33
branches:
44
- main
55
- staging
6-
paths-ignore:
7-
- '.github/workflows/release-plz.yml'
8-
- 'docs/**'
96
pull_request:
107
branches:
118
- "**"
12-
paths-ignore:
13-
- '.github/workflows/release-plz.yml'
14-
- 'docs/**'
159

1610

1711
name: CI
@@ -31,6 +25,27 @@ concurrency:
3125
cancel-in-progress: true
3226

3327
jobs:
28+
29+
check-needs-run:
30+
outputs:
31+
any-changes: ${{ steps.changes.outputs.src }}
32+
permissions:
33+
pull-requests: read
34+
contents: read
35+
steps:
36+
- name: Checkout
37+
uses: actions/checkout@v4
38+
- uses: dorny/paths-filter@v3
39+
id: changes
40+
with:
41+
base: main
42+
filters: |
43+
src:
44+
- 'src/**'
45+
- 'docs/**'
46+
- '.github/workflows/bevy_mod_scripting.yml'
47+
48+
3449
generate-job-matrix:
3550
runs-on: ubuntu-latest
3651
# container: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
@@ -49,13 +64,14 @@ jobs:
4964
echo "matrix=$(cat matrix-one-line.json)" >> $GITHUB_OUTPUT
5065
5166
check:
67+
needs: [check-needs-run]
5268
permissions:
5369
pull-requests: write
5470
contents: write
5571
issues: write
5672
name: Check - ${{ matrix.run_args.name }}
5773
runs-on: ${{ matrix.run_args.os }}
58-
# container: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
74+
if: ${{ needs.check-needs-run.outputs.any-changes == 'true' }}
5975
needs:
6076
- generate-job-matrix
6177
strategy:

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,14 @@ bevy_mod_scripting_lua = { path = "crates/languages/bevy_mod_scripting_lua", ver
5757
bevy_mod_scripting_rhai = { path = "crates/languages/bevy_mod_scripting_rhai", version = "0.9.3", optional = true }
5858
# bevy_mod_scripting_rune = { path = "crates/languages/bevy_mod_scripting_rune", version = "0.9.0-alpha.2", optional = true }
5959
bevy_mod_scripting_functions = { workspace = true }
60+
bevy_mod_scripting_derive = { workspace = true }
61+
6062
[workspace.dependencies]
6163
profiling = { version = "1.0" }
6264
bevy = { version = "0.15.1", default-features = false }
6365
bevy_mod_scripting_core = { path = "crates/bevy_mod_scripting_core", version = "0.9.3" }
6466
bevy_mod_scripting_functions = { path = "crates/bevy_mod_scripting_functions", version = "0.9.3", default-features = false }
67+
bevy_mod_scripting_derive = { path = "crates/bevy_mod_scripting_derive", version = "0.9.3" }
6568

6669
# test utilities
6770
script_integration_test_harness = { path = "crates/script_integration_test_harness" }
@@ -85,6 +88,7 @@ members = [
8588
"crates/bevy_mod_scripting_functions",
8689
"crates/xtask",
8790
"crates/script_integration_test_harness",
91+
"crates/bevy_mod_scripting_derive",
8892
]
8993
resolver = "2"
9094
exclude = ["crates/bevy_api_gen", "crates/macro_tests"]

crates/bevy_mod_scripting_core/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ smallvec = "1.11"
4141
itertools = "0.13"
4242
derivative = "2.2"
4343
profiling = { workspace = true }
44+
bevy_mod_scripting_derive = { workspace = true }
45+
4446
[dev-dependencies]
4547
test_utils = { workspace = true }
4648
tokio = { version = "1", features = ["rt", "macros"] }

crates/bevy_mod_scripting_core/src/bindings/function/mod.rs

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,65 @@ pub mod type_dependencies;
1212
#[cfg(test)]
1313
#[allow(dead_code)]
1414
mod test {
15-
use bevy::reflect::{FromReflect, GetTypeRegistration, Typed};
15+
use bevy::reflect::{FromReflect, GetTypeRegistration, Reflect, Typed};
16+
use bevy_mod_scripting_derive::script_bindings;
1617

1718
use crate::{
18-
bindings::function::from::{Ref, Val},
19+
bindings::function::{
20+
from::{Ref, Val},
21+
namespace::IntoNamespace,
22+
script_function::AppScriptFunctionRegistry,
23+
},
1924
error::InteropError,
2025
};
2126

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

29+
#[test]
30+
fn test_macro_generates_correct_registrator_function() {
31+
#[derive(Reflect)]
32+
struct TestStruct;
33+
34+
#[script_bindings(bms_core_path = "crate", name = "test_fn")]
35+
impl TestStruct {
36+
/// My docs !!
37+
fn test_fn(_self: Ref<TestStruct>, mut _arg1: usize) {}
38+
}
39+
40+
let mut test_world = bevy::ecs::world::World::default();
41+
42+
register_test_fn(&mut test_world);
43+
44+
let app_registry = test_world
45+
.get_resource::<AppScriptFunctionRegistry>()
46+
.unwrap();
47+
let app_registry = app_registry.read();
48+
49+
let test_fn = app_registry
50+
.get_function(TestStruct::into_namespace(), "test_fn")
51+
.unwrap();
52+
53+
assert_eq!(test_fn.info.docs, Some("My docs !!".into()));
54+
assert_eq!(test_fn.info.arg_info.len(), 2);
55+
56+
assert_eq!(
57+
test_fn.info.arg_info[0].type_id,
58+
std::any::TypeId::of::<Ref<TestStruct>>()
59+
);
60+
assert_eq!(test_fn.info.arg_info[0].name, Some("_self".into()));
61+
62+
assert_eq!(
63+
test_fn.info.arg_info[1].type_id,
64+
std::any::TypeId::of::<usize>()
65+
);
66+
assert_eq!(test_fn.info.arg_info[1].name, Some("_arg1".into()));
67+
68+
assert_eq!(
69+
test_fn.info.return_info.type_id,
70+
std::any::TypeId::of::<()>()
71+
);
72+
}
73+
2474
fn test_is_valid_return<T: ScriptReturn>() {}
2575
fn test_is_valid_arg<T: ScriptArgument>() {}
2676
fn test_is_valid_arg_and_return<T: ScriptArgument + ScriptReturn>() {}

crates/bevy_mod_scripting_core/src/bindings/function/namespace.rs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,35 @@ impl<'a, S: IntoNamespace> NamespaceBuilder<'a, S> {
9898

9999
/// Registers a function in the namespace
100100
pub fn register<'env, N, F, M>(&mut self, name: N, function: F) -> &mut Self
101+
where
102+
N: Into<Cow<'static, str>>,
103+
F: ScriptFunction<'env, M> + GetFunctionTypeDependencies<M> + GetFunctionInfo<M>,
104+
{
105+
self.register_inner(name, function, None, None)
106+
}
107+
108+
/// Registers a function in the namespace with a docstring
109+
pub fn register_documented<'env, N, F, M>(
110+
&mut self,
111+
name: N,
112+
function: F,
113+
docstring: &'static str,
114+
arg_names: &'static [&'static str],
115+
) -> &mut Self
116+
where
117+
N: Into<Cow<'static, str>>,
118+
F: ScriptFunction<'env, M> + GetFunctionTypeDependencies<M> + GetFunctionInfo<M>,
119+
{
120+
self.register_inner(name, function, Some(docstring), Some(arg_names))
121+
}
122+
123+
fn register_inner<'env, N, F, M>(
124+
&mut self,
125+
name: N,
126+
function: F,
127+
docstring: Option<&'static str>,
128+
arg_names: Option<&'static [&'static str]>,
129+
) -> &mut Self
101130
where
102131
N: Into<Cow<'static, str>>,
103132
F: ScriptFunction<'env, M> + GetFunctionTypeDependencies<M> + GetFunctionInfo<M>,
@@ -108,7 +137,13 @@ impl<'a, S: IntoNamespace> NamespaceBuilder<'a, S> {
108137
.world
109138
.get_resource_or_init::<AppScriptFunctionRegistry>();
110139
let mut registry = registry.write();
111-
registry.register(S::into_namespace(), name, function);
140+
registry.register_with_arg_names(
141+
S::into_namespace(),
142+
name,
143+
function,
144+
docstring.unwrap_or_default(),
145+
arg_names.unwrap_or(&[]),
146+
);
112147
}
113148
{
114149
let type_registry = self.world.get_resource_or_init::<AppTypeRegistry>();

crates/bevy_mod_scripting_core/src/bindings/function/script_function.rs

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -320,47 +320,63 @@ impl ScriptFunctionRegistry {
320320
) where
321321
F: ScriptFunction<'env, M> + GetFunctionInfo<M>,
322322
{
323-
self.register_overload(namespace, name, func, false, None::<&'static str>);
323+
self.register_overload(namespace, name, func, false, None::<&'static str>, None);
324324
}
325325

326326
/// Equivalent to [`ScriptFunctionRegistry::register`] but with the ability to provide documentation for the function.
327327
///
328328
/// The docstring will be added to the function's metadata and can be accessed at runtime.
329-
pub fn register_documented<F, M>(
329+
pub fn register_documented<'env, F, M>(
330330
&mut self,
331331
namespace: Namespace,
332332
name: impl Into<Cow<'static, str>>,
333333
func: F,
334334
docs: &'static str,
335335
) where
336-
F: ScriptFunction<'static, M> + GetFunctionInfo<M>,
336+
F: ScriptFunction<'env, M> + GetFunctionInfo<M>,
337+
{
338+
self.register_overload(namespace, name, func, false, Some(docs), None);
339+
}
340+
341+
/// Equivalent to [`ScriptFunctionRegistry::register`] but with the ability to provide argument names for the function as well as documentation.
342+
///
343+
/// The argument names and docstring will be added to the function's metadata and can be accessed at runtime.
344+
pub fn register_with_arg_names<'env, F, M>(
345+
&mut self,
346+
namespace: Namespace,
347+
name: impl Into<Cow<'static, str>>,
348+
func: F,
349+
docs: &'static str,
350+
arg_names: &'static [&'static str],
351+
) where
352+
F: ScriptFunction<'env, M> + GetFunctionInfo<M>,
337353
{
338-
self.register_overload(namespace, name, func, false, Some(docs));
354+
self.register_overload(namespace, name, func, false, Some(docs), Some(arg_names));
339355
}
340356

341357
/// Overwrite a function with the given name. If the function does not exist, it will be registered as a new function.
342-
pub fn overwrite<F, M>(
358+
pub fn overwrite<'env, F, M>(
343359
&mut self,
344360
namespace: Namespace,
345361
name: impl Into<Cow<'static, str>>,
346362
func: F,
347363
) where
348-
F: ScriptFunction<'static, M> + GetFunctionInfo<M>,
364+
F: ScriptFunction<'env, M> + GetFunctionInfo<M>,
349365
{
350-
self.register_overload(namespace, name, func, true, None::<&'static str>);
366+
self.register_overload(namespace, name, func, true, None::<&'static str>, None);
351367
}
352368

353369
/// Equivalent to [`ScriptFunctionRegistry::overwrite`] but with the ability to provide documentation for the function.
354-
pub fn overwrite_documented<F, M>(
370+
pub fn overwrite_documented<'env, F, M>(
355371
&mut self,
356372
namespace: Namespace,
357373
name: impl Into<Cow<'static, str>>,
358374
func: F,
359375
docs: &'static str,
360376
) where
361-
F: ScriptFunction<'static, M> + GetFunctionInfo<M>,
377+
F: ScriptFunction<'env, M> + GetFunctionInfo<M>,
362378
{
363-
self.register_overload(namespace, name, func, true, Some(docs));
379+
self.register_overload(namespace, name, func, true, Some(docs), None);
364380
}
365381

366382
/// Remove a function from the registry if it exists. Returns the removed function if it was found.
@@ -401,6 +417,7 @@ impl ScriptFunctionRegistry {
401417
func: F,
402418
overwrite: bool,
403419
docs: Option<impl Into<Cow<'static, str>>>,
420+
arg_names: Option<&'static [&'static str]>,
404421
) where
405422
F: ScriptFunction<'env, M> + GetFunctionInfo<M>,
406423
{
@@ -413,6 +430,10 @@ impl ScriptFunctionRegistry {
413430
Some(docs) => info.with_docs(docs.into()),
414431
None => info,
415432
};
433+
let info = match arg_names {
434+
Some(arg_names) => info.with_arg_names(arg_names),
435+
None => info,
436+
};
416437
let func = func.into_dynamic_script_function().with_info(info);
417438
self.functions.insert(FunctionKey { name, namespace }, func);
418439
return;

crates/bevy_mod_scripting_core/src/docgen/info.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,20 @@ impl FunctionInfo {
7878
self.docs = Some(docs.into());
7979
self
8080
}
81+
82+
/// Add argument names to the function info.
83+
///
84+
/// If the number of argument names is less than the number of arguments, the remaining arguments will be unnamed.
85+
/// If the number of argument names is greater than the number of arguments, the extra argument names will be ignored.
86+
pub fn with_arg_names(mut self, arg_names: &[&'static str]) -> Self {
87+
self.arg_info
88+
.iter_mut()
89+
.zip(arg_names.iter())
90+
.for_each(|(arg, name)| {
91+
arg.name = Some(Cow::Borrowed(*name));
92+
});
93+
self
94+
}
8195
}
8296

8397
#[derive(Debug, Clone, PartialEq, Reflect)]
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[package]
2+
name = "bevy_mod_scripting_derive"
3+
version = "0.9.3"
4+
edition = "2021"
5+
authors = ["Maksymilian Mozolewski <makspl17@gmail.com>"]
6+
license = "MIT OR Apache-2.0"
7+
description = "Necessary functionality for Lua support with bevy_mod_scripting"
8+
repository = "https://github.com/makspll/bevy_mod_scripting"
9+
homepage = "https://github.com/makspll/bevy_mod_scripting"
10+
keywords = ["bevy", "gamedev", "scripting", "rhai"]
11+
categories = ["game-development"]
12+
readme = "readme.md"
13+
14+
[dependencies]
15+
syn = "2"
16+
proc-macro2 = "1"
17+
quote = "1"
18+
19+
[lib]
20+
proc-macro = true
21+
22+
[lints]
23+
workspace = true
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# bevy_mod_scripting_lua_derive
2+
3+
This crate is a part of the ["bevy_mod_scripting" workspace](https://github.com/makspll/bevy_mod_scripting).

0 commit comments

Comments
 (0)