Skip to content

Commit

Permalink
Allow godot-visible functions to receive a Gd instead of self.
Browse files Browse the repository at this point in the history
This is done by tagging the function with #[func(gd_self)].
  • Loading branch information
mhoff12358 committed Sep 22, 2023
1 parent 3151842 commit 9f05617
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 17 deletions.
1 change: 1 addition & 0 deletions godot-macros/src/class/data_models/field_var.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ impl GetterSetterImpl {
FuncDefinition {
func: signature,
rename: None,
has_gd_self: false,
},
);

Expand Down
26 changes: 21 additions & 5 deletions godot-macros/src/class/data_models/func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub struct FuncDefinition {
pub func: venial::Function,
/// The name the function will be exposed as in Godot. If `None`, the Rust function name is used.
pub rename: Option<String>,
pub has_gd_self: bool,
}

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

let wrapped_method = make_forwarding_closure(class_name, &signature_info);
Expand Down Expand Up @@ -54,7 +55,7 @@ pub fn make_method_registration(
class_name: &Ident,
func_definition: FuncDefinition,
) -> TokenStream {
let signature_info = get_signature_info(&func_definition.func);
let signature_info = get_signature_info(&func_definition.func, func_definition.has_gd_self);
let sig_tuple =
util::make_signature_tuple_type(&signature_info.ret_type, &signature_info.param_types);

Expand Down Expand Up @@ -127,6 +128,7 @@ pub fn make_method_registration(
enum ReceiverType {
Ref,
Mut,
GdSelf,
Static,
}

Expand Down Expand Up @@ -167,6 +169,18 @@ fn make_forwarding_closure(class_name: &Ident, signature_info: &SignatureInfo) -
}
}
}
ReceiverType::GdSelf => {
quote! {
|instance_ptr, params| {
let ( #(#params,)* ) = params;

let storage =
unsafe { ::godot::private::as_storage::<#class_name>(instance_ptr) };

<#class_name>::#method_name(storage.get_gd(), #(#params),*)
}
}
}
ReceiverType::Static => {
quote! {
|_, params| {
Expand All @@ -178,7 +192,7 @@ fn make_forwarding_closure(class_name: &Ident, signature_info: &SignatureInfo) -
}
}

fn get_signature_info(signature: &venial::Function) -> SignatureInfo {
fn get_signature_info(signature: &venial::Function, gd_self: bool) -> SignatureInfo {
let method_name = signature.name.clone();
let mut receiver_type = ReceiverType::Static;
let mut param_idents: Vec<Ident> = Vec::new();
Expand All @@ -192,7 +206,9 @@ fn get_signature_info(signature: &venial::Function) -> SignatureInfo {
for (arg, _) in &signature.params.inner {
match arg {
venial::FnParam::Receiver(recv) => {
receiver_type = if recv.tk_mut.is_some() {
receiver_type = if gd_self {
ReceiverType::GdSelf
} else if recv.tk_mut.is_some() {
ReceiverType::Mut
} else if recv.tk_ref.is_some() {
ReceiverType::Ref
Expand Down Expand Up @@ -228,7 +244,7 @@ fn get_signature_info(signature: &venial::Function) -> SignatureInfo {

fn make_method_flags(method_type: ReceiverType) -> TokenStream {
match method_type {
ReceiverType::Ref | ReceiverType::Mut => {
ReceiverType::Ref | ReceiverType::Mut | ReceiverType::GdSelf => {
quote! { ::godot::engine::global::MethodFlags::METHOD_FLAGS_DEFAULT }
}
ReceiverType::Static => {
Expand Down
41 changes: 33 additions & 8 deletions godot-macros/src/class/godot_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use proc_macro2::{Ident, TokenStream};
use quote::quote;
use quote::spanned::Spanned;
use venial::{
Attribute, AttributeValue, Constant, Declaration, Error, FnParam, Function, Impl, ImplMember,
TyExpr,
Attribute, AttributeValue, Constant, Declaration, Error, FnParam, FnReceiverParam, Function,
Impl, ImplMember, TyExpr,
};

use crate::class::{make_method_registration, make_virtual_method_callback, FuncDefinition};
Expand Down Expand Up @@ -47,7 +47,10 @@ pub fn attribute_godot_api(input_decl: Declaration) -> Result<TokenStream, Error

/// Attribute for user-declared function
enum BoundAttrType {
Func { rename: Option<String> },
Func {
rename: Option<String>,
has_gd_self: bool,
},
Signal(AttributeValue),
Const(AttributeValue),
}
Expand Down Expand Up @@ -229,11 +232,29 @@ fn process_godot_fns(decl: &mut Impl) -> Result<(Vec<FuncDefinition>, Vec<Functi
return attr.bail("generic fn parameters are not supported", method);
}

match attr.ty {
BoundAttrType::Func { rename } => {
match &attr.ty {
BoundAttrType::Func {
rename,
has_gd_self,
} => {
// Signatures are the same thing without body
let sig = util::reduce_to_signature(method);
func_definitions.push(FuncDefinition { func: sig, rename });
let mut sig = util::reduce_to_signature(method);
if *has_gd_self {
if sig.params.is_empty() {
return attr.bail("with attribute key `gd_self`, the method must have a first parameter of type Gd<Self>", method);
}
sig.params[0].0 = FnParam::Receiver(FnReceiverParam {
attributes: vec![],
tk_ref: Some(proc_macro2::Punct::new('&', proc_macro2::Spacing::Alone)),
tk_mut: None,
tk_self: Ident::new("self", proc_macro2::Span::call_site()),
});
}
func_definitions.push(FuncDefinition {
func: sig,
rename: rename.clone(),
has_gd_self: *has_gd_self,
});
}
BoundAttrType::Signal(ref _attr_val) => {
if method.return_ty.is_some() {
Expand Down Expand Up @@ -316,11 +337,15 @@ where
let mut parser = KvParser::parse(attributes, "func")?.unwrap();

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

Some(BoundAttr {
attr_name: attr_name.clone(),
index,
ty: BoundAttrType::Func { rename },
ty: BoundAttrType::Func {
rename,
has_gd_self,
},
})
}
name if name == "signal" => {
Expand Down
19 changes: 19 additions & 0 deletions itest/godot/ManualFfiTests.gd
Original file line number Diff line number Diff line change
Expand Up @@ -304,3 +304,22 @@ func test_func_rename():
assert_eq(func_rename.has_method("renamed_static"), false)
assert_eq(func_rename.has_method("spell_static"), true)
assert_eq(func_rename.spell_static(), "static")

var gd_self_reference: GdSelfReference
func update_self_reference(value):
gd_self_reference.update_internal(value)

func test_gd_self_reference_fails():
# Create the gd_self_reference and connect its signal to a gdscript method that calls back into it.
gd_self_reference = GdSelfReference.new()
gd_self_reference.update_internal_signal.connect(update_self_reference)

# The returned value will still be 0 because update_internal can't be called in update_self_reference due to a borrowing issue.
assert_eq(gd_self_reference.fail_to_update_internal_value_due_to_conflicting_borrow(10), 0)

func test_gd_self_reference_succeeds():
# Create the gd_self_reference and connect its signal to a gdscript method that calls back into it.
gd_self_reference = GdSelfReference.new()
gd_self_reference.update_internal_signal.connect(update_self_reference)

assert_eq(gd_self_reference.succeed_at_updating_internal_value(10), 10)
69 changes: 65 additions & 4 deletions itest/rust/src/register_tests/func_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use godot::prelude::*;

#[derive(GodotClass)]
#[class(base=RefCounted)]
#[class(init, base=RefCounted)]
struct FuncRename;

#[godot_api]
Expand Down Expand Up @@ -40,9 +40,70 @@ impl FuncRename {
}
}

#[derive(GodotClass)]
#[class(base=RefCounted)]
struct GdSelfReference {
internal_value: i32,

#[base]
base: Base<RefCounted>,
}

#[godot_api]
impl GdSelfReference {
// A signal that will be looped back to update_internal through gdscript.
#[signal]
fn update_internal_signal(new_internal: i32);

#[func]
fn update_internal(&mut self, new_value: i32) {
self.internal_value = new_value;
}

#[func]
fn fail_to_update_internal_value_due_to_conflicting_borrow(
&mut self,
new_internal: i32,
) -> i32 {
// Since a self reference is held while the signal is emitted, when
// GDScript tries to call update_internal(), there will be a failure due
// to the double borrow and self.internal_value won't be changed.
self.base.emit_signal(
"update_internal_signal".into(),
&[new_internal.to_variant()],
);
return self.internal_value;
}

#[func(gd_self)]
fn succeed_at_updating_internal_value(mut this: Gd<Self>, new_internal: i32) -> i32 {
// Since this isn't bound while the signal is emitted, GDScript will succeed at calling
// update_internal() and self.internal_value will be changed.
this.emit_signal(
"update_internal_signal".into(),
&[new_internal.to_variant()],
);
return this.bind().internal_value;
}

#[func(gd_self)]
fn takes_gd_as_equivalent(mut this: Gd<GdSelfReference>) -> bool {
this.bind_mut();
true
}

#[func(gd_self)]
fn takes_gd_as_self_no_return_type(this: Gd<GdSelfReference>) {
this.bind();
}
}

#[godot_api]
impl RefCountedVirtual for FuncRename {
fn init(_base: Base<Self::Base>) -> Self {
Self
impl RefCountedVirtual for GdSelfReference {
fn init(base: Base<Self::Base>) -> Self {
Self {
internal_value: 0,
base,
}
}
}

0 comments on commit 9f05617

Please sign in to comment.