Skip to content

Commit e5c72ab

Browse files
authored
feat: implement variadics in bindings via VariadicTuple and add ScriptValue::Tuple (#527)
1 parent 4604b19 commit e5c72ab

52 files changed

Lines changed: 525 additions & 147 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
function on_test()
2+
local unpacked_a, unpacked_b = unpack_args({ 1, 2 })
3+
4+
local packed = pack_args(1, 2)
5+
6+
assert(packed[1] == unpacked_a,
7+
"Expected packed[1] to be: " .. tostring(unpacked_a) .. " Got: " .. tostring(packed[1]))
8+
assert(packed[2] == unpacked_b,
9+
"Expected packed[2] to be:" .. tostring(unpacked_b) .. " Got: " .. tostring(packed[2]))
10+
end

benches/benchmarks.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ extern crate bevy_mod_scripting;
22
extern crate script_integration_test_harness;
33
extern crate test_utils;
44
use bevy_platform::collections::HashMap;
5-
use std::{path::PathBuf, sync::LazyLock, time::Duration};
5+
use std::{collections::VecDeque, path::PathBuf, sync::LazyLock, time::Duration};
66

77
use bevy::{
88
log::{
@@ -173,9 +173,9 @@ fn conversion_benchmarks(criterion: &mut Criterion) {
173173
perform_benchmark_with_generator(
174174
"ScriptValue::List",
175175
&|rng, _| {
176-
let mut array = Vec::new();
176+
let mut array = VecDeque::new();
177177
for _ in 0..10 {
178-
array.push(ScriptValue::Integer(rng.random()));
178+
array.push_back(ScriptValue::Integer(rng.random()));
179179
}
180180
ScriptValue::List(array)
181181
},

crates/bevy_mod_scripting_bindings/src/docgen/info.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ use std::{any::TypeId, borrow::Cow};
1010
use super::typed_through::{ThroughTypeInfo, TypedThrough};
1111

1212
/// for things you can call and provide some introspection capability.
13+
#[diagnostic::on_unimplemented(
14+
message = "This function contains types in its signature which don't implement [`ArgMeta`] or [`TypedThrough`].",
15+
note = "These types are necessary to build [`FunctionInfo`] structs for each registered function."
16+
)]
1317
pub trait GetFunctionInfo<Marker> {
1418
/// Get the function info for the function.
1519
fn get_function_info(&self, name: Cow<'static, str>, namespace: Namespace) -> FunctionInfo;

crates/bevy_mod_scripting_bindings/src/docgen/typed_through.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
//! Defines a set of traits which destruture [`bevy_reflect::TypeInfo`] and implement a light weight wrapper around it, to allow types
22
//! which normally can't implement [`bevy_reflect::Typed`] to be used in a reflection context.
33
4-
use std::{any::TypeId, ffi::OsString, path::PathBuf};
4+
use std::{any::TypeId, collections::VecDeque, ffi::OsString, path::PathBuf};
55

66
use crate::{
7-
ReflectReference,
7+
ReflectReference, VariadicTuple,
88
function::{
99
from::{M, R, Union, V},
1010
script_function::{DynamicScriptFunction, DynamicScriptFunctionMut, FunctionCallContext},
@@ -76,6 +76,8 @@ pub enum TypedWrapperKind {
7676
InteropResult(Box<ThroughTypeInfo>),
7777
/// Wraps a tuple of through typed types.
7878
Tuple(Vec<ThroughTypeInfo>),
79+
/// Wraps a tuple of script values
80+
UntypedTuple,
7981
}
8082

8183
/// A dynamic version of [`TypedThrough`], which can be used to convert a [`TypeInfo`] into a [`ThroughTypeInfo`].
@@ -248,6 +250,12 @@ impl<T: TypedThrough> TypedThrough for Vec<T> {
248250
}
249251
}
250252

253+
impl<T: TypedThrough> TypedThrough for VecDeque<T> {
254+
fn through_type_info() -> ThroughTypeInfo {
255+
ThroughTypeInfo::TypedWrapper(TypedWrapperKind::Vec(Box::new(T::through_type_info())))
256+
}
257+
}
258+
251259
impl<K: TypedThrough, V: TypedThrough> TypedThrough for HashMap<K, V> {
252260
fn through_type_info() -> ThroughTypeInfo {
253261
ThroughTypeInfo::TypedWrapper(TypedWrapperKind::HashMap(
@@ -286,6 +294,12 @@ impl<T: TypedThrough> TypedThrough for Option<T> {
286294
}
287295
}
288296

297+
impl TypedThrough for VariadicTuple {
298+
fn through_type_info() -> ThroughTypeInfo {
299+
ThroughTypeInfo::TypedWrapper(TypedWrapperKind::UntypedTuple)
300+
}
301+
}
302+
289303
macro_rules! impl_through_typed {
290304
($($ty:ty => $ident:ident),*) => {
291305
$(

crates/bevy_mod_scripting_bindings/src/function/arg_meta.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
//! Trait implementations to help with function dispatch.
22
3-
use std::{ffi::OsString, path::PathBuf};
3+
use std::{collections::VecDeque, ffi::OsString, path::PathBuf};
44

55
use bevy_platform::collections::HashMap;
66

7-
use crate::{ReflectReference, ScriptValue, docgen::TypedThrough, error::InteropError};
7+
use crate::{
8+
ReflectReference, ScriptValue, VariadicTuple, docgen::TypedThrough, error::InteropError,
9+
};
810

911
use super::{
1012
from::{FromScript, M, R, Union, V},
@@ -35,10 +37,25 @@ pub trait ArgMeta {
3537
fn default_value() -> Option<ScriptValue> {
3638
None
3739
}
40+
41+
/// If returns true, will absorb all arguments following itself
42+
fn variadic() -> bool {
43+
false
44+
}
3845
}
3946

4047
impl ArgMeta for ScriptValue {}
4148

49+
impl ArgMeta for VariadicTuple {
50+
fn default_value() -> Option<ScriptValue> {
51+
Some(ScriptValue::Tuple(Default::default()))
52+
}
53+
54+
fn variadic() -> bool {
55+
true
56+
}
57+
}
58+
4259
macro_rules! impl_arg_info {
4360
($($ty:ty),*) => {
4461
$(
@@ -85,6 +102,8 @@ impl<T> ArgMeta for Option<T> {
85102
}
86103

87104
impl<T> ArgMeta for Vec<T> {}
105+
impl<T> ArgMeta for VecDeque<T> {}
106+
88107
impl<T, const N: usize> ArgMeta for [T; N] {}
89108

90109
impl<K, V> ArgMeta for HashMap<K, V> {}

crates/bevy_mod_scripting_bindings/src/function/from.rs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
33
use crate::{
44
ReflectReference, ScriptValue, WorldGuard, access_map::ReflectAccessId, error::InteropError,
5+
script_value::VariadicTuple,
56
};
67
use bevy_platform::collections::{HashMap, HashSet};
78
use bevy_reflect::{FromReflect, Reflect};
89
use nonmax::NonMaxU32;
910
use std::{
1011
any::TypeId,
12+
collections::VecDeque,
1113
ffi::OsString,
1214
ops::{Deref, DerefMut},
1315
path::PathBuf,
@@ -411,6 +413,30 @@ where
411413
}
412414
}
413415

416+
#[profiling::all_functions]
417+
impl<T: FromScript + 'static> FromScript for VecDeque<T>
418+
where
419+
for<'w> T::This<'w>: Into<T>,
420+
{
421+
type This<'w> = Self;
422+
#[profiling::function]
423+
fn from_script(value: ScriptValue, world: WorldGuard) -> Result<Self, InteropError> {
424+
match value {
425+
ScriptValue::List(list) => {
426+
let mut vec = VecDeque::with_capacity(list.len());
427+
for item in list {
428+
vec.push_back(T::from_script(item, world.clone())?.into());
429+
}
430+
Ok(vec)
431+
}
432+
_ => Err(InteropError::value_mismatch(
433+
std::any::TypeId::of::<VecDeque<T>>(),
434+
value,
435+
)),
436+
}
437+
}
438+
}
439+
414440
#[profiling::all_functions]
415441
impl<T: FromScript + 'static, const N: usize> FromScript for [T; N]
416442
where
@@ -615,6 +641,23 @@ where
615641
}
616642
}
617643

644+
impl FromScript for VariadicTuple {
645+
type This<'w> = Self;
646+
647+
fn from_script(
648+
value: ScriptValue,
649+
_world: WorldGuard<'_>,
650+
) -> Result<Self::This<'_>, InteropError>
651+
where
652+
Self: Sized,
653+
{
654+
match value {
655+
ScriptValue::List(tuple) | ScriptValue::Tuple(VariadicTuple(tuple)) => Ok(Self(tuple)),
656+
v => Ok(Self(VecDeque::from_iter([v]))),
657+
}
658+
}
659+
}
660+
618661
macro_rules! impl_from_script_tuple {
619662
($($ty:ident),*) => {
620663
#[allow(non_snake_case)]
@@ -630,7 +673,7 @@ macro_rules! impl_from_script_tuple {
630673

631674
fn from_script(value: ScriptValue, world: WorldGuard<'_>) -> Result<Self, InteropError> {
632675
match value {
633-
ScriptValue::List(list) => {
676+
ScriptValue::List(list) | ScriptValue::Tuple(VariadicTuple(list)) => {
634677
let expected_arg_count = $crate::function::script_function::count!( $($ty)* );
635678
if list.len() != expected_arg_count {
636679
return Err(InteropError::length_mismatch(expected_arg_count, list.len()));

crates/bevy_mod_scripting_bindings/src/function/into.rs

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
//! Implementations of the [`IntoScript`] trait for various types.
22
33
use super::{DynamicScriptFunction, DynamicScriptFunctionMut, Union, V};
4-
use crate::{ReflectReference, ScriptValue, WorldGuard, error::InteropError};
4+
use crate::{ReflectReference, ScriptValue, VariadicTuple, WorldGuard, error::InteropError};
55
use bevy_platform::collections::HashMap;
66
use bevy_reflect::Reflect;
7-
use std::{borrow::Cow, ffi::OsString, path::PathBuf};
7+
use std::{borrow::Cow, collections::VecDeque, ffi::OsString, path::PathBuf};
88

99
/// Converts a value into a [`ScriptValue`].
1010
pub trait IntoScript {
@@ -136,9 +136,20 @@ impl<T: IntoScript> IntoScript for Option<T> {
136136
#[profiling::all_functions]
137137
impl<T: IntoScript> IntoScript for Vec<T> {
138138
fn into_script(self, world: WorldGuard) -> Result<ScriptValue, InteropError> {
139-
let mut values = Vec::with_capacity(self.len());
139+
let mut values = VecDeque::with_capacity(self.len());
140140
for val in self {
141-
values.push(val.into_script(world.clone())?);
141+
values.push_back(val.into_script(world.clone())?);
142+
}
143+
Ok(ScriptValue::List(values))
144+
}
145+
}
146+
147+
#[profiling::all_functions]
148+
impl<T: IntoScript> IntoScript for VecDeque<T> {
149+
fn into_script(self, world: WorldGuard) -> Result<ScriptValue, InteropError> {
150+
let mut values = VecDeque::with_capacity(self.len());
151+
for val in self {
152+
values.push_back(val.into_script(world.clone())?);
142153
}
143154
Ok(ScriptValue::List(values))
144155
}
@@ -147,9 +158,9 @@ impl<T: IntoScript> IntoScript for Vec<T> {
147158
#[profiling::all_functions]
148159
impl<T: IntoScript, const N: usize> IntoScript for [T; N] {
149160
fn into_script(self, world: WorldGuard) -> Result<ScriptValue, InteropError> {
150-
let mut values = Vec::with_capacity(N);
161+
let mut values = VecDeque::with_capacity(N);
151162
for val in self {
152-
values.push(val.into_script(world.clone())?);
163+
values.push_back(val.into_script(world.clone())?);
153164
}
154165
Ok(ScriptValue::List(values))
155166
}
@@ -194,14 +205,20 @@ impl IntoScript for InteropError {
194205
}
195206
}
196207

208+
impl IntoScript for VariadicTuple {
209+
fn into_script(self, _world: WorldGuard) -> Result<ScriptValue, InteropError> {
210+
Ok(ScriptValue::Tuple(self))
211+
}
212+
}
213+
197214
macro_rules! impl_into_script_tuple {
198215
($( $ty:ident ),* ) => {
199216
#[allow(non_snake_case)]
200217
#[profiling::all_functions]
201218
impl<$($ty: IntoScript),*> IntoScript for ($($ty,)*) {
202219
fn into_script(self, world: WorldGuard) -> Result<ScriptValue, InteropError> {
203220
let ($($ty,)*) = self;
204-
Ok(ScriptValue::List(vec![$($ty.into_script(world.clone())?),*]))
221+
Ok(ScriptValue::Tuple(crate::script_value::VariadicTuple(VecDeque::from_iter([$($ty.into_script(world.clone())?),*].into_iter()))))
205222
}
206223
}
207224
}

crates/bevy_mod_scripting_bindings/src/function/script_function.rs

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
use super::MagicFunctions;
44
use super::{from::FromScript, into::IntoScript, namespace::Namespace};
5+
use crate::VariadicTuple;
56
use crate::docgen::info::{FunctionInfo, GetFunctionInfo};
67
use crate::function::arg_meta::ArgMeta;
78
use crate::{ScriptValue, ThreadWorldContainer, WorldGuard, error::InteropError};
@@ -17,10 +18,8 @@ use std::collections::VecDeque;
1718
use std::hash::Hash;
1819
use std::ops::{Deref, DerefMut};
1920
use std::sync::Arc;
20-
2121
#[diagnostic::on_unimplemented(
22-
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",
23-
note = "If you're trying to return a non-primitive type, you might need to use V<T> R<T> or M<T> wrappers"
22+
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"
2423
)]
2524
/// A trait implemented by functions which can act as dynamic script functions, which can then be registered against a [`ScriptFunctionRegistry`].
2625
pub trait ScriptFunction<'env, Marker> {
@@ -30,7 +29,7 @@ pub trait ScriptFunction<'env, Marker> {
3029

3130
#[diagnostic::on_unimplemented(
3231
message = "Only functions with all arguments impplementing FromScript and return values supporting IntoScript are supported. Registering functions also requires they implement GetTypeDependencies",
33-
note = "If you're trying to return a non-primitive type, you might need to use V<T> R<T> or M<T> wrappers"
32+
note = "If you're trying to use a non-primitive type, you might need to use V<T> R<T> or M<T> wrappers"
3433
)]
3534
/// A trait implemented by functions which can act as mutable dynamic script functions.
3635
pub trait ScriptFunctionMut<'env, Marker> {
@@ -608,6 +607,19 @@ macro_rules! count {
608607
( $x:tt $($xs:tt)* ) => (1usize + $crate::function::script_function::count!($($xs)*));
609608
}
610609

610+
/// Pops the stack of args depending on how many are requested by the current argument.
611+
/// If failed to find argument, returns None
612+
fn pop_args_stack_for_arg<A: ArgMeta>(args: &mut VecDeque<ScriptValue>) -> Option<ScriptValue> {
613+
if A::variadic() {
614+
// just absorb the rest, tuplify it
615+
if !args.is_empty() {
616+
return Some(ScriptValue::Tuple(VariadicTuple(std::mem::take(args))));
617+
}
618+
}
619+
620+
args.pop_front().or_else(A::default_value)
621+
}
622+
611623
pub(crate) use count;
612624

613625
macro_rules! impl_script_function {
@@ -674,15 +686,10 @@ macro_rules! impl_script_function {
674686
$(let $param = {
675687
profiling::scope!("argument conversion", &format!("argument #{}", current_arg));
676688
current_arg += 1;
677-
let $param = args.pop_front();
678-
let $param = match $param {
689+
let $param = match pop_args_stack_for_arg::<$param>(&mut args) {
679690
Some($param) => $param,
680691
None => {
681-
if let Some(default) = <$param>::default_value() {
682-
default
683-
} else {
684-
return Err(InteropError::argument_count_mismatch(expected_arg_count,received_args_len));
685-
}
692+
return Err(InteropError::argument_count_mismatch(expected_arg_count,received_args_len));
686693
}
687694
};
688695
let $param = <$param>::from_script($param, world.clone())

0 commit comments

Comments
 (0)