Skip to content

Commit 56d62b8

Browse files
authored
feat: optimize get and set functions, add MagicFunctions sub-registry (#397)
# Summary Attempts to speed up the get and set operations by: - No longer looking for `get` and `set` overrides on `ReflectReference`'s - Extracting those functions from the function registry and keeping them as `MagicFunctions` which do not require script value conversions
1 parent 158cde2 commit 56d62b8

File tree

10 files changed

+178
-130
lines changed

10 files changed

+178
-130
lines changed

crates/bevy_mod_scripting_core/Cargo.toml

+3-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ mlua_impls = ["mlua"]
2525
rhai_impls = ["rhai"]
2626

2727
[dependencies]
28-
mlua = { version = "0.10", default-features = false, optional = true }
28+
mlua = { version = "0.10", default-features = false, optional = true, features = [
29+
"lua54",
30+
] }
2931
rhai = { version = "1.21", default-features = false, features = [
3032
"sync",
3133
], optional = true }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
//! All the switchable special functions used by language implementors
2+
use super::{FromScriptRef, FunctionCallContext, IntoScriptRef};
3+
use crate::{
4+
bindings::{ReflectReference, ReflectionPathExt, ScriptValue},
5+
error::InteropError,
6+
reflection_extensions::TypeIdExtensions,
7+
};
8+
use bevy::reflect::{ParsedPath, PartialReflect};
9+
10+
/// A list of magic methods, these only have one replacable implementation, and apply to all `ReflectReferences`.
11+
/// It's up to the language implementer to call these in the right order (after any type specific overrides).
12+
///
13+
/// These live in a separate mini registry since they are so commonly needed. This helps us avoid needless hashing and lookups as well as script value conversions
14+
#[derive(Debug)]
15+
pub struct MagicFunctions {
16+
/// Indexer function
17+
pub get:
18+
fn(FunctionCallContext, ReflectReference, ScriptValue) -> Result<ScriptValue, InteropError>,
19+
/// Indexer setter function
20+
pub set: fn(
21+
FunctionCallContext,
22+
ReflectReference,
23+
ScriptValue,
24+
ScriptValue,
25+
) -> Result<(), InteropError>,
26+
}
27+
28+
impl MagicFunctions {
29+
/// Calls the currently set `get` function with the given arguments.
30+
pub fn get(
31+
&self,
32+
ctxt: FunctionCallContext,
33+
reference: ReflectReference,
34+
key: ScriptValue,
35+
) -> Result<ScriptValue, InteropError> {
36+
(self.get)(ctxt, reference, key)
37+
}
38+
39+
/// Calls the currently set `set` function with the given arguments.
40+
pub fn set(
41+
&self,
42+
ctxt: FunctionCallContext,
43+
reference: ReflectReference,
44+
key: ScriptValue,
45+
value: ScriptValue,
46+
) -> Result<(), InteropError> {
47+
(self.set)(ctxt, reference, key, value)
48+
}
49+
50+
/// Indexes into the given reference and if the nested type is a reference type, returns a deeper reference, otherwise
51+
/// returns the concrete value.
52+
///
53+
/// Does not support map types at the moment, for maps see `map_get`
54+
///
55+
/// Arguments:
56+
/// * `ctxt`: The function call context.
57+
/// * `reference`: The reference to index into.
58+
/// * `key`: The key to index with.
59+
///
60+
/// Returns:
61+
/// * `value`: The value at the key, if the reference is indexable.
62+
pub fn default_get(
63+
ctxt: FunctionCallContext,
64+
mut reference: ReflectReference,
65+
key: ScriptValue,
66+
) -> Result<ScriptValue, InteropError> {
67+
let mut path: ParsedPath = key.try_into()?;
68+
if ctxt.convert_to_0_indexed() {
69+
path.convert_to_0_indexed();
70+
}
71+
reference.index_path(path);
72+
let world = ctxt.world()?;
73+
ReflectReference::into_script_ref(reference, world)
74+
}
75+
76+
/// Sets the value under the specified path on the underlying value.
77+
///
78+
/// Arguments:
79+
/// * `ctxt`: The function call context.
80+
/// * `reference`: The reference to set the value on.
81+
/// * `key`: The key to set the value at.
82+
/// * `value`: The value to set.
83+
///
84+
/// Returns:
85+
/// * `result`: Nothing if the value was set successfully.
86+
pub fn default_set(
87+
ctxt: FunctionCallContext,
88+
mut reference: ReflectReference,
89+
key: ScriptValue,
90+
value: ScriptValue,
91+
) -> Result<(), InteropError> {
92+
let world = ctxt.world()?;
93+
let mut path: ParsedPath = key.try_into()?;
94+
if ctxt.convert_to_0_indexed() {
95+
path.convert_to_0_indexed();
96+
}
97+
reference.index_path(path);
98+
reference.with_reflect_mut(world.clone(), |r| {
99+
let target_type_id = r
100+
.get_represented_type_info()
101+
.map(|i| i.type_id())
102+
.or_fake_id();
103+
let other =
104+
<Box<dyn PartialReflect>>::from_script_ref(target_type_id, value, world.clone())?;
105+
r.try_apply(other.as_partial_reflect())
106+
.map_err(|e| InteropError::external_error(Box::new(e)))?;
107+
Ok::<_, InteropError>(())
108+
})?
109+
}
110+
}
111+
112+
impl Default for MagicFunctions {
113+
fn default() -> Self {
114+
Self {
115+
get: MagicFunctions::default_get,
116+
set: MagicFunctions::default_set,
117+
}
118+
}
119+
}

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

+10-8
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
//! Abstractions to do with dynamic script functions
22
3-
crate::private::export_all_in_modules!{
3+
crate::private::export_all_in_modules! {
44
arg_meta,
55
from,
66
from_ref,
77
into,
88
into_ref,
99
namespace,
1010
script_function,
11-
type_dependencies
11+
type_dependencies,
12+
magic_functions
1213
}
1314

1415
#[cfg(test)]
@@ -18,12 +19,13 @@ mod test {
1819
use bevy_mod_scripting_derive::script_bindings;
1920

2021
use crate::bindings::{
21-
function::{
22-
from::{Ref, Union, Val},
23-
namespace::IntoNamespace,
24-
script_function::AppScriptFunctionRegistry,
25-
}, script_value::ScriptValue
26-
};
22+
function::{
23+
from::{Ref, Union, Val},
24+
namespace::IntoNamespace,
25+
script_function::AppScriptFunctionRegistry,
26+
},
27+
script_value::ScriptValue,
28+
};
2729

2830
use super::arg_meta::{ScriptArgument, ScriptReturn, TypedScriptArgument, TypedScriptReturn};
2931

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Implementations of the [`ScriptFunction`] and [`ScriptFunctionMut`] traits for functions with up to 13 arguments.
22
3+
use super::MagicFunctions;
34
use super::{from::FromScript, into::IntoScript, namespace::Namespace};
45
use crate::asset::Language;
56
use crate::bindings::function::arg_meta::ArgMeta;
@@ -16,7 +17,6 @@ use std::collections::{HashMap, VecDeque};
1617
use std::hash::Hash;
1718
use std::ops::{Deref, DerefMut};
1819
use std::sync::Arc;
19-
2020
#[diagnostic::on_unimplemented(
2121
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",
2222
note = "If you're trying to return a non-primitive type, you might need to use Val<T> Ref<T> or Mut<T> wrappers"
@@ -315,6 +315,8 @@ pub struct FunctionKey {
315315
/// A registry of dynamic script functions
316316
pub struct ScriptFunctionRegistry {
317317
functions: HashMap<FunctionKey, DynamicScriptFunction>,
318+
/// A registry of magic functions
319+
pub magic_functions: MagicFunctions,
318320
}
319321

320322
#[profiling::all_functions]

crates/bevy_mod_scripting_core/src/bindings/reference.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ use std::{any::TypeId, fmt::Debug};
2828
///
2929
/// References are composed of two parts:
3030
/// - The base kind, which specifies where the reference points to
31-
/// - The path, which specifies how to access the value from the base
31+
/// - The path, which specifies how to access the value from the base.
32+
///
33+
/// Bindings defined on this type, apply to ALL references.
3234
#[derive(Debug, Clone, PartialEq, Eq, Reflect)]
3335
#[reflect(Default, opaque)]
3436
pub struct ReflectReference {

crates/bevy_mod_scripting_functions/src/core.rs

+3-71
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use std::collections::HashMap;
44

5-
use bevy::{prelude::*, reflect::ParsedPath};
5+
use bevy::prelude::*;
66
use bevy_mod_scripting_core::{
77
bindings::{
88
function::{
@@ -24,9 +24,8 @@ use bindings::{
2424
},
2525
pretty_print::DisplayWithWorld,
2626
script_value::ScriptValue,
27-
ReflectReference, ReflectionPathExt, ScriptComponentRegistration, ScriptQueryBuilder,
28-
ScriptQueryResult, ScriptResourceRegistration, ScriptTypeRegistration, ThreadWorldContainer,
29-
WorldContainer,
27+
ReflectReference, ScriptComponentRegistration, ScriptQueryBuilder, ScriptQueryResult,
28+
ScriptResourceRegistration, ScriptTypeRegistration, ThreadWorldContainer, WorldContainer,
3029
};
3130
use error::InteropError;
3231
use reflection_extensions::{PartialReflectExt, TypeIdExtensions};
@@ -568,73 +567,6 @@ impl ReflectReference {
568567
})?
569568
}
570569

571-
/// Indexes into the given reference and if the nested type is a reference type, returns a deeper reference, otherwise
572-
/// returns the concrete value.
573-
///
574-
/// Does not support map types at the moment, for maps see `map_get`
575-
///
576-
/// Arguments:
577-
/// * `ctxt`: The function call context.
578-
/// * `reference`: The reference to index into.
579-
/// * `key`: The key to index with.
580-
/// Returns:
581-
/// * `value`: The value at the key, if the reference is indexable.
582-
fn get(
583-
ctxt: FunctionCallContext,
584-
mut reference: ReflectReference,
585-
key: ScriptValue,
586-
) -> Result<ScriptValue, InteropError> {
587-
profiling::function_scope!("get");
588-
let mut path: ParsedPath = key.try_into()?;
589-
if ctxt.convert_to_0_indexed() {
590-
path.convert_to_0_indexed();
591-
}
592-
reference.index_path(path);
593-
let world = ctxt.world()?;
594-
ReflectReference::into_script_ref(reference, world)
595-
}
596-
597-
/// Sets the value under the specified path on the underlying value.
598-
///
599-
/// Arguments:
600-
/// * `ctxt`: The function call context.
601-
/// * `reference`: The reference to set the value on.
602-
/// * `key`: The key to set the value at.
603-
/// * `value`: The value to set.
604-
/// Returns:
605-
/// * `result`: Nothing if the value was set successfully.
606-
fn set(
607-
ctxt: FunctionCallContext,
608-
reference: ScriptValue,
609-
key: ScriptValue,
610-
value: ScriptValue,
611-
) -> Result<(), InteropError> {
612-
profiling::function_scope!("set");
613-
if let ScriptValue::Reference(mut self_) = reference {
614-
let world = ctxt.world()?;
615-
let mut path: ParsedPath = key.try_into()?;
616-
if ctxt.convert_to_0_indexed() {
617-
path.convert_to_0_indexed();
618-
}
619-
self_.index_path(path);
620-
self_.with_reflect_mut(world.clone(), |r| {
621-
let target_type_id = r
622-
.get_represented_type_info()
623-
.map(|i| i.type_id())
624-
.or_fake_id();
625-
let other = <Box<dyn PartialReflect>>::from_script_ref(
626-
target_type_id,
627-
value,
628-
world.clone(),
629-
)?;
630-
r.try_apply(other.as_partial_reflect())
631-
.map_err(|e| InteropError::external_error(Box::new(e)))?;
632-
Ok::<_, InteropError>(())
633-
})??;
634-
}
635-
Ok(())
636-
}
637-
638570
/// Pushes the value into the reference, if the reference is an appropriate container type.
639571
///
640572
/// Arguments:

crates/languages/bevy_mod_scripting_lua/src/bindings/reference.rs

+12-19
Original file line numberDiff line numberDiff line change
@@ -57,15 +57,13 @@ impl UserData for LuaReflectReference {
5757
Err(key) => key,
5858
};
5959

60-
let func = world
61-
.lookup_function([type_id, TypeId::of::<ReflectReference>()], "get")
62-
.map_err(|f| {
63-
InteropError::missing_function(TypeId::of::<ReflectReference>(), f)
64-
})?;
60+
// call the default magic getter
61+
let registry = world.script_function_registry();
62+
let registry = registry.read();
6563

66-
// call the function with the key
67-
let out =
68-
func.call(vec![ScriptValue::Reference(self_), key], LUA_CALLER_CONTEXT)?;
64+
let out = registry
65+
.magic_functions
66+
.get(LUA_CALLER_CONTEXT, self_, key)?;
6967
Ok(LuaScriptValue(out))
7068
},
7169
);
@@ -78,20 +76,15 @@ impl UserData for LuaReflectReference {
7876
let self_: ReflectReference = self_.into();
7977
let key: ScriptValue = key.into();
8078
let value: ScriptValue = value.into();
81-
let type_id = self_.tail_type_id(world.clone())?.or_fake_id();
8279

83-
let func = world
84-
.lookup_function([type_id, TypeId::of::<ReflectReference>()], "set")
85-
.map_err(|f| {
86-
InteropError::missing_function(TypeId::of::<ReflectReference>(), f)
87-
})?;
80+
let registry = world.script_function_registry();
81+
let registry = registry.read();
8882

89-
let out = func.call(
90-
vec![ScriptValue::Reference(self_), key, value],
91-
LUA_CALLER_CONTEXT,
92-
)?;
83+
registry
84+
.magic_functions
85+
.set(LUA_CALLER_CONTEXT, self_, key, value)?;
9386

94-
Ok(LuaScriptValue(out))
87+
Ok(())
9588
},
9689
);
9790

0 commit comments

Comments
 (0)