Skip to content

Commit bb0fa8a

Browse files
committed
Reentrant context for script functions
1 parent e319200 commit bb0fa8a

File tree

9 files changed

+162
-49
lines changed

9 files changed

+162
-49
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ edition = "2021"
1111

1212
[workspace.dependencies]
1313
godot = { git = "https://github.com/godot-rust/gdext", rev = "22fd33d4d5213a3fe5db9a58547888cebe35c647" }
14+
godot-cell = { git = "https://github.com/godot-rust/gdext", rev = "22fd33d4d5213a3fe5db9a58547888cebe35c647" }
1415
itertools = "0.10.3"
1516
rand = "0.8.5"
1617
darling = { version = "0.20.3" }

derive/src/impl_attribute.rs

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use quote::{quote, quote_spanned, ToTokens};
99
use syn::{parse_macro_input, spanned::Spanned, FnArg, ImplItem, ItemImpl, ReturnType, Type};
1010

1111
use crate::{
12-
rust_to_variant_type,
12+
is_context_type, rust_to_variant_type,
1313
type_paths::{godot_types, property_hints, string_name_ty, variant_ty},
1414
};
1515

@@ -56,29 +56,37 @@ pub fn godot_script_impl(
5656
let arg_name = arg.pat.as_ref();
5757
let arg_type = rust_to_variant_type(arg.ty.as_ref()).unwrap();
5858

59-
(
60-
quote_spanned! {
61-
arg.span() =>
62-
::godot_rust_script::RustScriptPropDesc {
63-
name: stringify!(#arg_name),
64-
ty: #arg_type,
65-
exported: false,
66-
hint: #property_hints::NONE,
67-
hint_string: "",
68-
description: "",
59+
is_context_type(arg.ty.as_ref()).then(|| {
60+
(
61+
quote!(),
62+
63+
quote_spanned!(arg.span() => ctx,)
64+
)
65+
}).unwrap_or_else(|| {
66+
(
67+
quote_spanned! {
68+
arg.span() =>
69+
::godot_rust_script::RustScriptPropDesc {
70+
name: stringify!(#arg_name),
71+
ty: #arg_type,
72+
exported: false,
73+
hint: #property_hints::NONE,
74+
hint_string: "",
75+
description: "",
76+
},
6977
},
70-
},
71-
72-
quote_spanned! {
73-
arg.span() =>
74-
#godot_types::prelude::FromGodot::try_from_variant(
75-
args.get(#index).ok_or(#godot_types::sys::GDEXTENSION_CALL_ERROR_TOO_FEW_ARGUMENTS)?
76-
).map_err(|err| {
77-
#godot_types::log::godot_error!("failed to convert variant for argument {} of {}: {}", stringify!(#arg_name), #fn_name_str, err);
78-
#godot_types::sys::GDEXTENSION_CALL_ERROR_INVALID_ARGUMENT
79-
})?
80-
}
81-
)
78+
79+
quote_spanned! {
80+
arg.span() =>
81+
#godot_types::prelude::FromGodot::try_from_variant(
82+
args.get(#index).ok_or(#godot_types::sys::GDEXTENSION_CALL_ERROR_TOO_FEW_ARGUMENTS)?
83+
).map_err(|err| {
84+
#godot_types::log::godot_error!("failed to convert variant for argument {} of {}: {}", stringify!(#arg_name), #fn_name_str, err);
85+
#godot_types::sys::GDEXTENSION_CALL_ERROR_INVALID_ARGUMENT
86+
})?,
87+
}
88+
)
89+
})
8290
})
8391
.collect();
8492

@@ -143,7 +151,8 @@ pub fn godot_script_impl(
143151
let trait_impl = quote_spanned! {
144152
current_type.span() =>
145153
impl ::godot_rust_script::GodotScriptImpl for #current_type {
146-
fn call_fn(&mut self, name: #string_name_ty, args: &[&#variant_ty]) -> ::std::result::Result<#variant_ty, #call_error_ty> {
154+
#[allow(unused_variables)]
155+
fn call_fn(&mut self, name: #string_name_ty, args: &[&#variant_ty], ctx: ::godot_rust_script::Context) -> ::std::result::Result<#variant_ty, #call_error_ty> {
147156
match name.to_string().as_str() {
148157
#method_dispatch
149158

derive/src/lib.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,8 @@ pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
132132

133133
#set_fields_impl
134134

135-
fn call(&mut self, name: #string_name_ty, args: &[&#variant_ty]) -> ::std::result::Result<#variant_ty, #call_error_ty> {
136-
::godot_rust_script::GodotScriptImpl::call_fn(self, name, args)
135+
fn call(&mut self, name: #string_name_ty, args: &[&#variant_ty], ctx: ::godot_rust_script::Context) -> ::std::result::Result<#variant_ty, #call_error_ty> {
136+
::godot_rust_script::GodotScriptImpl::call_fn(self, name, args, ctx)
137137
}
138138

139139
fn to_string(&self) -> String {
@@ -205,6 +205,14 @@ fn rust_to_variant_type(ty: &syn::Type) -> Result<TokenStream, TokenStream> {
205205
}
206206
}
207207

208+
fn is_context_type(ty: &syn::Type) -> bool {
209+
let syn::Type::Path(path) = ty else {
210+
return false;
211+
};
212+
213+
path.path.segments.last().map(|segment| segment.ident == "Context").unwrap_or(false)
214+
}
215+
208216
fn derive_default_with_base(field_opts: &[FieldOpts]) -> TokenStream {
209217
let godot_types = godot_types();
210218
let fields: TokenStream = field_opts

rust-script/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ edition.workspace = true
77

88
[dependencies]
99
godot.workspace = true
10+
godot-cell.workspace = true
1011

1112
itertools = { workspace = true, optional = true }
1213
rand = { workspace = true, optional = true }

rust-script/src/runtime/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ use crate::{
3131

3232
use self::rust_script_language::RustScriptLanguage;
3333

34+
pub use rust_script_instance::Context;
35+
3436
#[macro_export]
3537
macro_rules! setup {
3638
($lib_crate:tt) => {

rust-script/src/runtime/rust_script_instance.rs

Lines changed: 81 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,21 @@
44
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
55
*/
66

7-
use std::{collections::HashMap, rc::Rc};
7+
use core::panic;
8+
use std::{collections::HashMap, fmt::Debug, ops::DerefMut, pin::Pin};
89

910
use godot::{
1011
builtin::meta::{MethodInfo, PropertyInfo},
1112
engine::{Script, ScriptInstance, SiMut},
1213
prelude::{GString, Gd, Object, StringName, Variant, VariantType},
1314
};
15+
use godot_cell::GdCell;
1416

1517
use crate::script_registry::GodotScriptObject;
1618

1719
use super::{rust_script::RustScript, rust_script_language::RustScriptLanguage, SCRIPT_REGISTRY};
1820

19-
fn script_method_list(script: &Gd<RustScript>) -> Rc<[MethodInfo]> {
21+
fn script_method_list(script: &Gd<RustScript>) -> Box<[MethodInfo]> {
2022
let rs = script.bind();
2123
let class_name = rs.str_class_name();
2224

@@ -25,7 +27,7 @@ fn script_method_list(script: &Gd<RustScript>) -> Rc<[MethodInfo]> {
2527
.expect("script registry is inaccessible")
2628
.get(&class_name)
2729
.map(|meta| meta.methods().iter().map(MethodInfo::from).collect())
28-
.unwrap_or_else(|| Rc::new([]) as Rc<[MethodInfo]>);
30+
.unwrap_or_else(|| Box::new([]) as Box<[MethodInfo]>);
2931

3032
methods
3133
}
@@ -34,7 +36,7 @@ fn script_class_name(script: &Gd<RustScript>) -> GString {
3436
script.bind().get_class_name()
3537
}
3638

37-
fn script_property_list(script: &Gd<RustScript>) -> Rc<[PropertyInfo]> {
39+
fn script_property_list(script: &Gd<RustScript>) -> Box<[PropertyInfo]> {
3840
let rs = script.bind();
3941
let class_name = rs.str_class_name();
4042

@@ -43,18 +45,67 @@ fn script_property_list(script: &Gd<RustScript>) -> Rc<[PropertyInfo]> {
4345
.expect("script registry is inaccessible")
4446
.get(&class_name)
4547
.map(|meta| meta.properties().iter().map(PropertyInfo::from).collect())
46-
.unwrap_or_else(|| Rc::new([]) as Rc<[PropertyInfo]>);
48+
.unwrap_or_else(|| Box::new([]) as Box<[PropertyInfo]>);
4749

4850
props
4951
}
5052

53+
pub struct Context<'a> {
54+
cell: Pin<&'a GdCell<Box<dyn GodotScriptObject>>>,
55+
data_ptr: *mut Box<dyn GodotScriptObject>,
56+
}
57+
58+
impl<'a> Debug for Context<'a> {
59+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60+
f.write_str("Context { <Call Context> }")
61+
}
62+
}
63+
64+
impl<'a> Context<'a> {
65+
unsafe fn new(
66+
cell: Pin<&'a GdCell<Box<dyn GodotScriptObject>>>,
67+
data_ptr: *mut Box<dyn GodotScriptObject>,
68+
) -> Self {
69+
Self { cell, data_ptr }
70+
}
71+
72+
pub fn reentrant_scope<T: GodotScriptObject + 'static, R>(
73+
&mut self,
74+
self_ref: &mut T,
75+
cb: impl FnOnce() -> R,
76+
) -> R {
77+
let known_ptr = unsafe {
78+
let any = (*self.data_ptr).as_any_mut();
79+
80+
any.downcast_mut::<T>().unwrap() as *mut T
81+
};
82+
83+
let self_ptr = self_ref as *mut _;
84+
85+
if known_ptr != self_ptr {
86+
panic!("unable to create reentrant scope with unrelated self reference!");
87+
}
88+
89+
let guard = self
90+
.cell
91+
.make_inaccessible(unsafe { &mut *self.data_ptr })
92+
.unwrap();
93+
94+
let result = cb();
95+
96+
drop(guard);
97+
98+
result
99+
}
100+
}
101+
51102
pub(super) struct RustScriptInstance {
52-
data: Box<dyn GodotScriptObject>,
103+
data: Pin<Box<GdCell<Box<dyn GodotScriptObject>>>>,
53104

54105
script: Gd<RustScript>,
55106
generic_script: Gd<Script>,
56-
property_list: Rc<[PropertyInfo]>,
57-
method_list: Rc<[MethodInfo]>,
107+
property_list: Box<[PropertyInfo]>,
108+
method_list: Box<[MethodInfo]>,
58109
}
59110

60111
impl RustScriptInstance {
@@ -64,7 +115,7 @@ impl RustScriptInstance {
64115
script: Gd<RustScript>,
65116
) -> Self {
66117
Self {
67-
data,
118+
data: GdCell::new(data),
68119
generic_script: script.clone().upcast(),
69120
property_list: script_property_list(&script),
70121
method_list: script_method_list(&script),
@@ -80,12 +131,17 @@ impl ScriptInstance for RustScriptInstance {
80131
script_class_name(&self.script)
81132
}
82133

83-
fn set_property(mut this: SiMut<Self>, name: StringName, value: &Variant) -> bool {
84-
this.data.set(name, value.to_owned())
134+
fn set_property(this: SiMut<Self>, name: StringName, value: &Variant) -> bool {
135+
let cell_ref = this.data.as_ref();
136+
let mut mut_data = cell_ref.borrow_mut().unwrap();
137+
138+
mut_data.set(name, value.to_owned())
85139
}
86140

87141
fn get_property(&self, name: StringName) -> Option<Variant> {
88-
self.data.get(name)
142+
let guard = self.data.as_ref().borrow().unwrap();
143+
144+
guard.get(name)
89145
}
90146

91147
fn get_property_list(&self) -> Vec<PropertyInfo> {
@@ -97,11 +153,18 @@ impl ScriptInstance for RustScriptInstance {
97153
}
98154

99155
fn call(
100-
mut this: SiMut<Self>,
156+
this: SiMut<Self>,
101157
method: StringName,
102158
args: &[&Variant],
103159
) -> Result<Variant, godot::sys::GDExtensionCallErrorType> {
104-
this.data.call(method, args)
160+
let cell = this.data.as_ref();
161+
let mut data_guard = cell.borrow_mut().unwrap();
162+
let data = data_guard.deref_mut();
163+
let data_ptr = data as *mut _;
164+
165+
let context = unsafe { Context::new(cell, data_ptr) };
166+
167+
data.call(method, args, context)
105168
}
106169

107170
fn get_script(&self) -> &Gd<Script> {
@@ -127,7 +190,8 @@ impl ScriptInstance for RustScriptInstance {
127190
}
128191

129192
fn to_string(&self) -> GString {
130-
self.data.to_string().into()
193+
// self.data.to_string().into()
194+
GString::new()
131195
}
132196

133197
fn get_property_state(&self) -> Vec<(StringName, Variant)> {
@@ -166,8 +230,8 @@ pub(super) struct RustScriptPlaceholder {
166230
script: Gd<RustScript>,
167231
generic_script: Gd<Script>,
168232
properties: HashMap<StringName, Variant>,
169-
property_list: Rc<[PropertyInfo]>,
170-
method_list: Rc<[MethodInfo]>,
233+
property_list: Box<[PropertyInfo]>,
234+
method_list: Box<[MethodInfo]>,
171235
}
172236

173237
impl RustScriptPlaceholder {

rust-script/src/script_registry.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
55
*/
66

7-
use std::{collections::HashMap, fmt::Debug, sync::Arc};
7+
use std::{any::Any, collections::HashMap, fmt::Debug, sync::Arc};
88

99
use godot::{
1010
builtin::{
@@ -17,13 +17,16 @@ use godot::{
1717
sys::VariantType,
1818
};
1919

20+
use crate::Context;
21+
2022
pub trait GodotScript: Debug + GodotScriptImpl {
2123
fn set(&mut self, name: StringName, value: Variant) -> bool;
2224
fn get(&self, name: StringName) -> Option<Variant>;
2325
fn call(
2426
&mut self,
2527
method: StringName,
2628
args: &[&Variant],
29+
context: Context,
2730
) -> Result<Variant, godot::sys::GDExtensionCallErrorType>;
2831

2932
fn to_string(&self) -> String;
@@ -37,6 +40,7 @@ pub trait GodotScriptImpl {
3740
&mut self,
3841
name: StringName,
3942
args: &[&Variant],
43+
context: Context,
4044
) -> Result<Variant, godot::sys::GDExtensionCallErrorType>;
4145
}
4246

@@ -47,12 +51,15 @@ pub trait GodotScriptObject {
4751
&mut self,
4852
method: StringName,
4953
args: &[&Variant],
54+
context: Context,
5055
) -> Result<Variant, godot::sys::GDExtensionCallErrorType>;
5156
fn to_string(&self) -> String;
5257
fn property_state(&self) -> HashMap<StringName, Variant>;
58+
59+
fn as_any_mut(&mut self) -> &mut dyn Any;
5360
}
5461

55-
impl<T: GodotScript> GodotScriptObject for T {
62+
impl<T: GodotScript + 'static> GodotScriptObject for T {
5663
fn set(&mut self, name: StringName, value: Variant) -> bool {
5764
GodotScript::set(self, name, value)
5865
}
@@ -65,8 +72,9 @@ impl<T: GodotScript> GodotScriptObject for T {
6572
&mut self,
6673
method: StringName,
6774
args: &[&Variant],
75+
context: Context,
6876
) -> Result<Variant, godot::sys::GDExtensionCallErrorType> {
69-
GodotScript::call(self, method, args)
77+
GodotScript::call(self, method, args, context)
7078
}
7179

7280
fn to_string(&self) -> String {
@@ -76,6 +84,10 @@ impl<T: GodotScript> GodotScriptObject for T {
7684
fn property_state(&self) -> HashMap<StringName, Variant> {
7785
GodotScript::property_state(self)
7886
}
87+
88+
fn as_any_mut(&mut self) -> &mut dyn Any {
89+
self
90+
}
7991
}
8092

8193
#[derive(Debug, Clone)]

0 commit comments

Comments
 (0)