Skip to content

Commit c28de15

Browse files
committed
Allow godot-visible functions to receive a Gd instead of self.
This is done by tagging the function with #[func(gd_self)].
1 parent 3151842 commit c28de15

File tree

5 files changed

+143
-15
lines changed

5 files changed

+143
-15
lines changed

godot-macros/src/class/data_models/field_var.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ impl GetterSetterImpl {
195195
FuncDefinition {
196196
func: signature,
197197
rename: None,
198+
has_gd_self: false,
198199
},
199200
);
200201

godot-macros/src/class/data_models/func.rs

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub struct FuncDefinition {
1414
pub func: venial::Function,
1515
/// The name the function will be exposed as in Godot. If `None`, the Rust function name is used.
1616
pub rename: Option<String>,
17+
pub has_gd_self: bool,
1718
}
1819

1920
/// Returns a C function which acts as the callback when a virtual method of this instance is invoked.
@@ -24,7 +25,7 @@ pub fn make_virtual_method_callback(
2425
class_name: &Ident,
2526
method_signature: &venial::Function,
2627
) -> TokenStream {
27-
let signature_info = get_signature_info(method_signature);
28+
let signature_info = get_signature_info(method_signature, false);
2829
let method_name = &method_signature.name;
2930

3031
let wrapped_method = make_forwarding_closure(class_name, &signature_info);
@@ -54,7 +55,7 @@ pub fn make_method_registration(
5455
class_name: &Ident,
5556
func_definition: FuncDefinition,
5657
) -> TokenStream {
57-
let signature_info = get_signature_info(&func_definition.func);
58+
let signature_info = get_signature_info(&func_definition.func, func_definition.has_gd_self);
5859
let sig_tuple =
5960
util::make_signature_tuple_type(&signature_info.ret_type, &signature_info.param_types);
6061

@@ -127,6 +128,7 @@ pub fn make_method_registration(
127128
enum ReceiverType {
128129
Ref,
129130
Mut,
131+
GdSelf,
130132
Static,
131133
}
132134

@@ -167,6 +169,18 @@ fn make_forwarding_closure(class_name: &Ident, signature_info: &SignatureInfo) -
167169
}
168170
}
169171
}
172+
ReceiverType::GdSelf => {
173+
quote! {
174+
|instance_ptr, params| {
175+
let ( #(#params,)* ) = params;
176+
177+
let storage =
178+
unsafe { ::godot::private::as_storage::<#class_name>(instance_ptr) };
179+
180+
<#class_name>::#method_name(storage.get_gd(), #(#params),*)
181+
}
182+
}
183+
}
170184
ReceiverType::Static => {
171185
quote! {
172186
|_, params| {
@@ -178,9 +192,13 @@ fn make_forwarding_closure(class_name: &Ident, signature_info: &SignatureInfo) -
178192
}
179193
}
180194

181-
fn get_signature_info(signature: &venial::Function) -> SignatureInfo {
195+
fn get_signature_info(signature: &venial::Function, has_gd_self: bool) -> SignatureInfo {
182196
let method_name = signature.name.clone();
183-
let mut receiver_type = ReceiverType::Static;
197+
let mut receiver_type = if has_gd_self {
198+
ReceiverType::GdSelf
199+
} else {
200+
ReceiverType::Static
201+
};
184202
let mut param_idents: Vec<Ident> = Vec::new();
185203
let mut param_types = Vec::new();
186204
let ret_type = match &signature.return_ty {
@@ -192,6 +210,11 @@ fn get_signature_info(signature: &venial::Function) -> SignatureInfo {
192210
for (arg, _) in &signature.params.inner {
193211
match arg {
194212
venial::FnParam::Receiver(recv) => {
213+
if receiver_type == ReceiverType::GdSelf {
214+
// This shouldn't happen, as when has_gd_self is true the first function parameter should have been removed.
215+
// And the first parameter should be the only one that can be a Receiver.
216+
panic!("has_gd_self is true for a signature starting with a Receiver param.");
217+
}
195218
receiver_type = if recv.tk_mut.is_some() {
196219
ReceiverType::Mut
197220
} else if recv.tk_ref.is_some() {
@@ -228,7 +251,7 @@ fn get_signature_info(signature: &venial::Function) -> SignatureInfo {
228251

229252
fn make_method_flags(method_type: ReceiverType) -> TokenStream {
230253
match method_type {
231-
ReceiverType::Ref | ReceiverType::Mut => {
254+
ReceiverType::Ref | ReceiverType::Mut | ReceiverType::GdSelf => {
232255
quote! { ::godot::engine::global::MethodFlags::METHOD_FLAGS_DEFAULT }
233256
}
234257
ReceiverType::Static => {

godot-macros/src/class/godot_api.rs

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@ pub fn attribute_godot_api(input_decl: Declaration) -> Result<TokenStream, Error
4747

4848
/// Attribute for user-declared function
4949
enum BoundAttrType {
50-
Func { rename: Option<String> },
50+
Func {
51+
rename: Option<String>,
52+
has_gd_self: bool,
53+
},
5154
Signal(AttributeValue),
5255
Const(AttributeValue),
5356
}
@@ -229,11 +232,25 @@ fn process_godot_fns(decl: &mut Impl) -> Result<(Vec<FuncDefinition>, Vec<Functi
229232
return attr.bail("generic fn parameters are not supported", method);
230233
}
231234

232-
match attr.ty {
233-
BoundAttrType::Func { rename } => {
235+
match &attr.ty {
236+
BoundAttrType::Func {
237+
rename,
238+
has_gd_self,
239+
} => {
234240
// Signatures are the same thing without body
235-
let sig = util::reduce_to_signature(method);
236-
func_definitions.push(FuncDefinition { func: sig, rename });
241+
let mut sig = util::reduce_to_signature(method);
242+
if *has_gd_self {
243+
if sig.params.is_empty() {
244+
return attr.bail("with attribute key `gd_self`, the method must have a first parameter of type Gd<Self>", method);
245+
} else {
246+
sig.params.inner.remove(0);
247+
}
248+
}
249+
func_definitions.push(FuncDefinition {
250+
func: sig,
251+
rename: rename.clone(),
252+
has_gd_self: *has_gd_self,
253+
});
237254
}
238255
BoundAttrType::Signal(ref _attr_val) => {
239256
if method.return_ty.is_some() {
@@ -316,11 +333,15 @@ where
316333
let mut parser = KvParser::parse(attributes, "func")?.unwrap();
317334

318335
let rename = parser.handle_expr("rename")?.map(|ts| ts.to_string());
336+
let has_gd_self = parser.handle_alone("gd_self")?;
319337

320338
Some(BoundAttr {
321339
attr_name: attr_name.clone(),
322340
index,
323-
ty: BoundAttrType::Func { rename },
341+
ty: BoundAttrType::Func {
342+
rename,
343+
has_gd_self,
344+
},
324345
})
325346
}
326347
name if name == "signal" => {

itest/godot/ManualFfiTests.gd

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,3 +304,25 @@ func test_func_rename():
304304
assert_eq(func_rename.has_method("renamed_static"), false)
305305
assert_eq(func_rename.has_method("spell_static"), true)
306306
assert_eq(func_rename.spell_static(), "static")
307+
308+
var gd_self_reference: GdSelfReference
309+
func update_self_reference(value):
310+
gd_self_reference.update_internal(value)
311+
312+
# Todo: Once there is a way to assert for a SCRIPT ERROR failure this can be reenabled.
313+
"""
314+
func test_gd_self_reference_fails():
315+
# Create the gd_self_reference and connect its signal to a gdscript method that calls back into it.
316+
gd_self_reference = GdSelfReference.new()
317+
gd_self_reference.update_internal_signal.connect(update_self_reference)
318+
319+
# The returned value will still be 0 because update_internal can't be called in update_self_reference due to a borrowing issue.
320+
assert_eq(gd_self_reference.fail_to_update_internal_value_due_to_conflicting_borrow(10), 0)
321+
"""
322+
323+
func test_gd_self_reference_succeeds():
324+
# Create the gd_self_reference and connect its signal to a gdscript method that calls back into it.
325+
gd_self_reference = GdSelfReference.new()
326+
gd_self_reference.update_internal_signal.connect(update_self_reference)
327+
328+
assert_eq(gd_self_reference.succeed_at_updating_internal_value(10), 10)

itest/rust/src/register_tests/func_test.rs

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
use godot::prelude::*;
88

99
#[derive(GodotClass)]
10-
#[class(base=RefCounted)]
10+
#[class(init, base=RefCounted)]
1111
struct FuncRename;
1212

1313
#[godot_api]
@@ -40,9 +40,70 @@ impl FuncRename {
4040
}
4141
}
4242

43+
#[derive(GodotClass)]
44+
#[class(base=RefCounted)]
45+
struct GdSelfReference {
46+
internal_value: i32,
47+
48+
#[base]
49+
base: Base<RefCounted>,
50+
}
51+
52+
#[godot_api]
53+
impl GdSelfReference {
54+
// A signal that will be looped back to update_internal through gdscript.
55+
#[signal]
56+
fn update_internal_signal(new_internal: i32);
57+
58+
#[func]
59+
fn update_internal(&mut self, new_value: i32) {
60+
self.internal_value = new_value;
61+
}
62+
63+
#[func]
64+
fn fail_to_update_internal_value_due_to_conflicting_borrow(
65+
&mut self,
66+
new_internal: i32,
67+
) -> i32 {
68+
// Since a self reference is held while the signal is emitted, when
69+
// GDScript tries to call update_internal(), there will be a failure due
70+
// to the double borrow and self.internal_value won't be changed.
71+
self.base.emit_signal(
72+
"update_internal_signal".into(),
73+
&[new_internal.to_variant()],
74+
);
75+
self.internal_value
76+
}
77+
78+
#[func(gd_self)]
79+
fn succeed_at_updating_internal_value(mut this: Gd<Self>, new_internal: i32) -> i32 {
80+
// Since this isn't bound while the signal is emitted, GDScript will succeed at calling
81+
// update_internal() and self.internal_value will be changed.
82+
this.emit_signal(
83+
"update_internal_signal".into(),
84+
&[new_internal.to_variant()],
85+
);
86+
return this.bind().internal_value;
87+
}
88+
89+
#[func(gd_self)]
90+
fn takes_gd_as_equivalent(mut this: Gd<GdSelfReference>) -> bool {
91+
this.bind_mut();
92+
true
93+
}
94+
95+
#[func(gd_self)]
96+
fn takes_gd_as_self_no_return_type(this: Gd<GdSelfReference>) {
97+
this.bind();
98+
}
99+
}
100+
43101
#[godot_api]
44-
impl RefCountedVirtual for FuncRename {
45-
fn init(_base: Base<Self::Base>) -> Self {
46-
Self
102+
impl RefCountedVirtual for GdSelfReference {
103+
fn init(base: Base<Self::Base>) -> Self {
104+
Self {
105+
internal_value: 0,
106+
base,
107+
}
47108
}
48109
}

0 commit comments

Comments
 (0)