Skip to content

Move #[godot_dyn] code generation to its own function #1235

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions godot-macros/src/class/data_models/func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use proc_macro2::{Group, Ident, TokenStream, TokenTree};
use quote::{format_ident, quote};

/// Information used for registering a Rust function with Godot.
#[derive(Debug)]
pub struct FuncDefinition {
/// Refined signature, with higher level info and renamed parameters.
pub signature_info: SignatureInfo,
Expand Down
191 changes: 191 additions & 0 deletions godot-macros/src/class/data_models/godot_dyn_impl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
use crate::{
bail,
class::{
add_virtual_script_call, extract_gd_self, into_signature_info, parse_attributes,
FuncDefinition, ItemAttrType, SignalDefinition,
},
util, ParseResult,
};

use proc_macro2::{Ident, TokenStream};
use quote::quote;

pub fn transform_dyn_trait_impl(
mut decl: venial::Impl,
prv: TokenStream,
class_path: venial::TypeExpr,
trait_path: venial::TypeExpr,
assoc_type_constraints: TokenStream,
) -> ParseResult<TokenStream> {
//eprintln!("decl: {decl:?}");

// Extract Ident from class_path for into_signature_info
let class_path = class_path.as_path().unwrap();
let class_name = class_path.segments.last().unwrap().ident.clone();

let (funcs, signals) = process_godot_fns(&class_name, &mut decl)?;

eprintln!("funcs: {funcs:?}");
eprintln!("signals: {signals:?}");

let new_code = quote! {
#decl

impl ::godot::obj::AsDyn<dyn #trait_path #assoc_type_constraints> for #class_path {
fn dyn_upcast(&self) -> &(dyn #trait_path #assoc_type_constraints + 'static) {
self
}

fn dyn_upcast_mut(&mut self) -> &mut (dyn #trait_path #assoc_type_constraints + 'static) {
self
}
}

::godot::sys::plugin_add!(#prv::__GODOT_PLUGIN_REGISTRY; #prv::ClassPlugin::new::<#class_path>(
#prv::PluginItem::DynTraitImpl(#prv::DynTraitImpl::new::<#class_path, dyn #trait_path #assoc_type_constraints>()))
);

};
Ok(new_code)
}

fn process_godot_fns(
class_name: &Ident,
impl_block: &mut venial::Impl,
) -> ParseResult<(Vec<FuncDefinition>, Vec<SignalDefinition>)> {
let mut func_definitions = vec![];
let mut signal_definitions = vec![];
let mut virtual_functions = vec![];

let mut removed_indexes = vec![];
for (index, item) in impl_block.body_items.iter_mut().enumerate() {
let venial::ImplMember::AssocFunction(function) = item else {
continue;
};

let Some(attr) = parse_attributes(function)? else {
continue;
};

if function.qualifiers.tk_default.is_some()
|| function.qualifiers.tk_const.is_some()
|| function.qualifiers.tk_async.is_some()
|| function.qualifiers.tk_unsafe.is_some()
|| function.qualifiers.tk_extern.is_some()
|| function.qualifiers.extern_abi.is_some()
{
return bail!(
&function.qualifiers,
"#[func]: fn qualifiers are not allowed"
);
}

eprintln!(
"Processing function: {} with attributes: {:?}",
function.name, attr
);

if function.generic_params.is_some() {
return bail!(
&function.generic_params,
"#[func]: generic fn parameters are not supported"
);
}

match attr.ty {
ItemAttrType::Func(func, rpc_info) => {
let external_attributes = function.attributes.clone();

// Transforms the following.
// from function: #[attr] pub fn foo(&self, a: i32) -> i32 { ... }
// into signature: fn foo(&self, a: i32) -> i32
let mut signature = util::reduce_to_signature(function);
let gd_self_parameter = if func.has_gd_self {
// Removes Gd<Self> receiver from signature for further processing.
let param_name = extract_gd_self(&mut signature, &attr.attr_name)?;
Some(param_name)
} else {
None
};

// Clone might not strictly be necessary, but the 2 other callers of into_signature_info() are better off with pass-by-value.
let signature_info =
into_signature_info(signature.clone(), class_name, gd_self_parameter.is_some());

// For virtual methods, rename/mangle existing user method and create a new method with the original name,
// which performs a dynamic dispatch.
let registered_name = if func.is_virtual {
let registered_name = add_virtual_script_call(
&mut virtual_functions,
function,
&signature_info,
class_name,
&func.rename,
gd_self_parameter,
);

Some(registered_name)
} else {
func.rename
};

func_definitions.push(FuncDefinition {
signature_info,
external_attributes,
registered_name,
is_script_virtual: func.is_virtual,
rpc_info,
});
}

ItemAttrType::Signal(ref signal, ref _attr_val) => {
if function.return_ty.is_some() {
return bail!(
&function.return_ty,
"#[signal] does not support return types"
);
}
if function.body.is_some() {
return bail!(
&function.body,
"#[signal] must not have a body; declare the function with a semicolon"
);
}

let external_attributes = function.attributes.clone();

let mut fn_signature = util::reduce_to_signature(function);
fn_signature.vis_marker = function.vis_marker.clone();

signal_definitions.push(SignalDefinition {
fn_signature,
external_attributes,
has_builder: !signal.no_builder,
});

removed_indexes.push(index);
}

ItemAttrType::Const(_) => {
return bail!(
function,
"#[constant] can only be used on associated constant",
)
}
}
}

// Remove some elements (e.g. signals) from impl.
// O(n^2); alternative: retain(), but elements themselves don't have the necessary information.
for index in removed_indexes.into_iter().rev() {
impl_block.body_items.remove(index);
}

// Add script-virtual extra functions at the end of same impl block (subject to same attributes).
for f in virtual_functions.into_iter() {
let member = venial::ImplMember::AssocFunction(f);
impl_block.body_items.push(member);
}

Ok((func_definitions, signal_definitions))
}
28 changes: 15 additions & 13 deletions godot-macros/src/class/data_models/inherent_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,20 @@ use quote::spanned::Spanned;
use quote::{format_ident, quote, ToTokens};

/// Attribute for user-declared function.
enum ItemAttrType {
#[derive(Debug)]
pub(crate) enum ItemAttrType {
Func(FuncAttr, Option<RpcAttr>),
Signal(SignalAttr, venial::AttributeValue),
Const(#[allow(dead_code)] venial::AttributeValue),
}

struct ItemAttr {
attr_name: Ident,
ty: ItemAttrType,
#[derive(Debug)]
pub(crate) struct ItemAttr {
pub(crate) attr_name: Ident,
pub(crate) ty: ItemAttrType,
}

enum AttrParseResult {
pub(crate) enum AttrParseResult {
Func(FuncAttr),
Rpc(RpcAttr),
FuncRpc(FuncAttr, RpcAttr),
Expand All @@ -53,15 +55,15 @@ impl AttrParseResult {
}
}

#[derive(Default)]
struct FuncAttr {
#[derive(Default, Debug)]
pub(crate) struct FuncAttr {
pub rename: Option<String>,
pub is_virtual: bool,
pub has_gd_self: bool,
}

#[derive(Default)]
struct SignalAttr {
#[derive(Default, Debug)]
pub(crate) struct SignalAttr {
pub no_builder: bool,
}

Expand Down Expand Up @@ -224,7 +226,7 @@ fn extract_hint_attribute(impl_block: &mut venial:: Impl) -> ParseResult<GodotAp
}
*/

fn extract_gd_self(signature: &mut venial::Function, attr_name: &Ident) -> ParseResult<Ident> {
pub fn extract_gd_self(signature: &mut venial::Function, attr_name: &Ident) -> ParseResult<Ident> {
if signature.params.is_empty() {
return bail_attr(
attr_name,
Expand Down Expand Up @@ -430,7 +432,7 @@ fn process_godot_constants(decl: &mut venial::Impl) -> ParseResult<Vec<ConstDefi
/// Appends the virtual function to `virtual_functions`.
///
/// Returns the Godot-registered name of the virtual function, usually `_<name>` (but overridable with `#[func(rename = ...)]`).
fn add_virtual_script_call(
pub(crate) fn add_virtual_script_call(
virtual_functions: &mut Vec<venial::Function>,
function: &mut venial::Function,
signature_info: &SignatureInfo,
Expand Down Expand Up @@ -514,7 +516,7 @@ fn add_virtual_script_call(
/// Parses an entire item (`fn`, `const`) inside an `impl` block and returns a domain representation.
///
/// See also [`parse_attributes_inner`].
fn parse_attributes<T: ImplItem>(item: &mut T) -> ParseResult<Option<ItemAttr>> {
pub(crate) fn parse_attributes<T: ImplItem>(item: &mut T) -> ParseResult<Option<ItemAttr>> {
let span = util::span_of(item);
parse_attributes_inner(item.attributes_mut(), span)
}
Expand Down Expand Up @@ -694,7 +696,7 @@ fn bail_attr<R>(attr_name: &Ident, msg: &str, method_name: &Ident) -> ParseResul

// ----------------------------------------------------------------------------------------------------------------------------------------------

trait ImplItem
pub(crate) trait ImplItem
where
Self: ToTokens,
for<'a> &'a Self: Spanned,
Expand Down
5 changes: 3 additions & 2 deletions godot-macros/src/class/data_models/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use proc_macro2::{Ident, TokenStream};
use quote::quote;

/// Possible ways the user can specify RPC configuration.
#[derive(Debug)]
pub enum RpcAttr {
// Individual keys in the `rpc` attribute.
// Example: `#[rpc(any_peer, reliable, call_remote, channel = 3)]`
Expand All @@ -27,7 +28,7 @@ pub enum RpcAttr {
Expression(TokenStream),
}

#[derive(Clone, Copy)]
#[derive(Debug, Clone, Copy)]
pub enum RpcMode {
AnyPeer,
Authority,
Expand All @@ -43,7 +44,7 @@ impl RpcMode {
}
}

#[derive(Clone, Copy)]
#[derive(Debug, Clone, Copy)]
pub enum TransferMode {
Reliable,
Unreliable,
Expand Down
1 change: 1 addition & 0 deletions godot-macros/src/class/data_models/signal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use proc_macro2::{Delimiter, Ident, TokenStream, TokenTree};
use quote::{format_ident, quote, ToTokens};

/// Holds information known from a signal's definition
#[derive(Debug)]
pub struct SignalDefinition {
/// The signal's function signature (simplified, not original declaration).
pub fn_signature: venial::Function,
Expand Down
29 changes: 10 additions & 19 deletions godot-macros/src/class/godot_dyn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

use crate::class::data_models::godot_dyn_impl::transform_dyn_trait_impl;
use crate::util::bail;
use crate::ParseResult;
use proc_macro2::TokenStream;
Expand Down Expand Up @@ -53,24 +54,14 @@ pub fn attribute_godot_dyn(input_decl: venial::Item) -> ParseResult<TokenStream>
let class_path = &decl.self_ty;
let prv = quote! { ::godot::private };

let new_code = quote! {
#decl
// TODO: Remove this println! when the code is stable.
eprintln!("Adding dyn trait impl for {class_path:?}: {trait_path:?} {assoc_type_constraints}");

impl ::godot::obj::AsDyn<dyn #trait_path #assoc_type_constraints> for #class_path {
fn dyn_upcast(&self) -> &(dyn #trait_path #assoc_type_constraints + 'static) {
self
}

fn dyn_upcast_mut(&mut self) -> &mut (dyn #trait_path #assoc_type_constraints + 'static) {
self
}
}

::godot::sys::plugin_add!(#prv::__GODOT_PLUGIN_REGISTRY; #prv::ClassPlugin::new::<#class_path>(
#prv::PluginItem::DynTraitImpl(#prv::DynTraitImpl::new::<#class_path, dyn #trait_path #assoc_type_constraints>()))
);

};

Ok(new_code)
transform_dyn_trait_impl(
decl.clone(),
prv.clone(),
class_path.clone(),
trait_path.clone(),
assoc_type_constraints,
)
}
1 change: 1 addition & 0 deletions godot-macros/src/class/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ mod data_models {
pub mod field_export;
pub mod field_var;
pub mod func;
pub mod godot_dyn_impl;
pub mod inherent_impl;
pub mod interface_trait_impl;
pub mod property;
Expand Down
9 changes: 9 additions & 0 deletions itest/rust/src/object_tests/dyn_gd_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,10 @@ trait Health: 'static {

fn deal_damage(&mut self, damage: u8);

fn cool_method(&self) -> GString {
"This is a cool method!".into()
}

fn kill(&mut self) {
self.deal_damage(self.get_hitpoints());
}
Expand Down Expand Up @@ -511,6 +515,11 @@ impl Health for RefcHealth {
fn deal_damage(&mut self, damage: u8) {
self.hp -= damage;
}

#[func]
fn cool_method(&self) -> GString {
format!("RefcHealth is cool! Current HP: {}", self.hp).into()
}
}

#[godot_dyn]
Expand Down
Loading