Skip to content

Godot FFI: postinit create, compat virtual methods, icon paths #991

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

Merged
merged 11 commits into from
Jan 1, 2025
17 changes: 11 additions & 6 deletions godot-codegen/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,14 @@ impl<'a> Context<'a> {
// Populate derived-to-base relations
if let Some(base) = class.inherits.as_ref() {
let base_name = TyName::from_godot(base);
println!(
"* Add engine class {} <- inherits {}",
class_name.description(),
base_name.description()
);
// println!(
// "* Add engine class {} <- inherits {}",
// class_name.description(),
// base_name.description()
// );
ctx.inheritance_tree.insert(class_name.clone(), base_name);
} else {
println!("* Add engine class {}", class_name.description());
// println!("* Add engine class {}", class_name.description());
}

// Populate notification constants (first, only for classes that declare them themselves).
Expand Down Expand Up @@ -347,6 +347,11 @@ impl InheritanceTree {
assert!(existing.is_none(), "Duplicate inheritance insert");
}

#[allow(unused)] // Currently 4.4 gated, for virtual method hashes.
pub fn direct_base(&self, derived_name: &TyName) -> Option<TyName> {
self.derived_to_base.get(derived_name).cloned()
}

/// Returns all base classes, without the class itself, in order from nearest to furthest (object).
pub fn collect_all_bases(&self, derived_name: &TyName) -> Vec<TyName> {
let mut upgoer = derived_name;
Expand Down
10 changes: 5 additions & 5 deletions godot-codegen/src/generator/functions_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use crate::generator::default_parameters;
use crate::models::domain::{ArgPassing, FnParam, FnQualifier, Function, RustTy};
use crate::special_cases;
use crate::util::{lifetime, safe_ident};
use crate::util::lifetime;
use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote};

Expand Down Expand Up @@ -155,11 +155,11 @@ pub fn make_function_definition(
make_params_exprs(sig.params().iter(), passing)
};

let rust_function_name_str = sig.name();
let rust_function_name = sig.name_ident();

let (primary_fn_name, default_fn_code, default_structs_code);
if has_default_params {
primary_fn_name = format_ident!("{}_full", safe_ident(rust_function_name_str));
primary_fn_name = format_ident!("{}_full", rust_function_name);

(default_fn_code, default_structs_code) =
default_parameters::make_function_definition_with_defaults(
Expand All @@ -169,7 +169,7 @@ pub fn make_function_definition(
cfg_attributes,
);
} else {
primary_fn_name = safe_ident(rust_function_name_str);
primary_fn_name = rust_function_name.clone();
default_fn_code = TokenStream::new();
default_structs_code = TokenStream::new();
};
Expand Down Expand Up @@ -227,7 +227,7 @@ pub fn make_function_definition(
}
} else {
let try_return_decl = &sig.return_value().call_result_decl();
let try_fn_name = format_ident!("try_{}", rust_function_name_str);
let try_fn_name = format_ident!("try_{}", rust_function_name);

// Note: all varargs functions are non-static, which is why there are some shortcuts in try_*() argument forwarding.
// This can be made more complex if ever necessary.
Expand Down
4 changes: 2 additions & 2 deletions godot-codegen/src/generator/method_tables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,8 @@ pub fn make_utility_function_table(api: &ExtensionApi) -> TokenStream {
};

for function in api.utility_functions.iter() {
let field = generator::utility_functions::make_utility_function_ptr_name(function);
let fn_name_str = function.name();
let field = generator::utility_functions::make_utility_function_ptr_name(fn_name_str);
let hash = function.hash();

table.method_decls.push(quote! {
Expand Down Expand Up @@ -191,7 +191,7 @@ struct NamedMethodTable {
method_count: usize,
}

#[allow(dead_code)] // for lazy feature
#[allow(dead_code)] // Individual fields would need to be cfg'ed with: feature = "codegen-lazy-fptrs".
struct IndexedMethodTable {
table_name: Ident,
imports: TokenStream,
Expand Down
14 changes: 14 additions & 0 deletions godot-codegen/src/generator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ pub mod notifications;
pub mod utility_functions;
pub mod virtual_traits;

#[cfg(since_api = "4.4")]
pub mod virtual_hashes;

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

// Some file generation functions are in specific modules:
Expand All @@ -44,6 +47,8 @@ pub fn generate_sys_module_file(sys_gen_path: &Path, submit_fn: &mut SubmitFn) {
pub mod table_scene_classes;
pub mod table_editor_classes;
pub mod table_utilities;
#[cfg(since_api = "4.4")]
pub mod virtual_hashes;

pub mod central;
pub mod gdextension_interface;
Expand Down Expand Up @@ -77,6 +82,15 @@ pub fn generate_sys_classes_file(
submit_fn(sys_gen_path.join(filename), code);
watch.record(format!("generate_classes_{}_file", api_level.lower()));
}

// From 4.4 onward, generate table that maps all virtual methods to their known hashes.
// This allows Godot to fall back to an older compatibility function if one is not supported.
#[cfg(since_api = "4.4")]
{
let code = virtual_hashes::make_virtual_hashes_file(api, ctx);
submit_fn(sys_gen_path.join("virtual_hashes.rs"), code);
watch.record("generate_virtual_hashes_file");
}
}

pub fn generate_sys_utilities_file(
Expand Down
10 changes: 5 additions & 5 deletions godot-codegen/src/generator/utility_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,16 @@ pub(crate) fn generate_utilities_file(
submit_fn(gen_path.join("utilities.rs"), tokens);
}

pub(crate) fn make_utility_function_ptr_name(godot_function_name: &str) -> Ident {
util::safe_ident(godot_function_name)
pub(crate) fn make_utility_function_ptr_name(function: &dyn Function) -> Ident {
function.name_ident()
}

pub(crate) fn make_utility_function_definition(function: &UtilityFunction) -> TokenStream {
let function_ident = make_utility_function_ptr_name(function);
let function_name_str = function.name();
let fn_ptr = make_utility_function_ptr_name(function_name_str);

let ptrcall_invocation = quote! {
let utility_fn = sys::utility_function_table().#fn_ptr;
let utility_fn = sys::utility_function_table().#function_ident;

<CallSig as PtrcallSignatureTuple>::out_utility_ptrcall(
utility_fn,
Expand All @@ -54,7 +54,7 @@ pub(crate) fn make_utility_function_definition(function: &UtilityFunction) -> To
};

let varcall_invocation = quote! {
let utility_fn = sys::utility_function_table().#fn_ptr;
let utility_fn = sys::utility_function_table().#function_ident;

<CallSig as VarcallSignatureTuple>::out_utility_ptrcall_varargs(
utility_fn,
Expand Down
61 changes: 61 additions & 0 deletions godot-codegen/src/generator/virtual_hashes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright (c) godot-rust; Bromeon and contributors.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

use crate::context::Context;
use crate::models::domain::{Class, ClassLike, ExtensionApi, FnDirection, Function};
use proc_macro2::TokenStream;
use quote::quote;

pub fn make_virtual_hashes_file(api: &ExtensionApi, ctx: &mut Context) -> TokenStream {
make_virtual_hashes_for_all_classes(&api.classes, ctx)
}

fn make_virtual_hashes_for_all_classes(all_classes: &[Class], ctx: &mut Context) -> TokenStream {
let modules = all_classes
.iter()
.map(|class| make_virtual_hashes_for_class(class, ctx));

quote! {
#![allow(non_snake_case, non_upper_case_globals, unused_imports)]

#( #modules )*
}
}

fn make_virtual_hashes_for_class(class: &Class, ctx: &mut Context) -> TokenStream {
let class_name = class.name();

// Import all base class hashes via `use` statements.
let use_base_class = if let Some(base_class) = ctx.inheritance_tree().direct_base(class_name) {
quote! {
pub use super::#base_class::*;
}
} else {
TokenStream::new()
};

let constants = class.methods.iter().filter_map(|method| {
let FnDirection::Virtual { hash } = method.direction() else {
return None;
};

let method_name = method.name_ident();
let constant = quote! {
pub const #method_name: u32 = #hash;
};

Some(constant)
});

// Even if there are no virtual methods, we need to generate the module, to enable base class imports via `use`.
quote! {
pub mod #class_name {
#use_base_class
#( #constants )*
}
}
}
17 changes: 13 additions & 4 deletions godot-codegen/src/models/domain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,9 +291,14 @@ pub trait Function: fmt::Display {
fn surrounding_class(&self) -> Option<&TyName>;

// Default:
/// Rust name as string slice.
fn name(&self) -> &str {
&self.common().name
}
/// Rust name as `Ident`. Might be cached in future.
fn name_ident(&self) -> Ident {
safe_ident(self.name())
}
fn godot_name(&self) -> &str {
&self.common().godot_name
}
Expand All @@ -310,7 +315,7 @@ pub trait Function: fmt::Display {
self.common().is_private
}
fn is_virtual(&self) -> bool {
matches!(self.direction(), FnDirection::Virtual)
matches!(self.direction(), FnDirection::Virtual { .. })
}
fn direction(&self) -> FnDirection {
self.common().direction
Expand All @@ -330,7 +335,7 @@ pub struct UtilityFunction {
impl UtilityFunction {
pub fn hash(&self) -> i64 {
match self.direction() {
FnDirection::Virtual => unreachable!("utility function cannot be virtual"),
FnDirection::Virtual { .. } => unreachable!("utility function cannot be virtual"),
FnDirection::Outbound { hash } => hash,
}
}
Expand Down Expand Up @@ -370,7 +375,7 @@ pub struct BuiltinMethod {
impl BuiltinMethod {
pub fn hash(&self) -> i64 {
match self.direction() {
FnDirection::Virtual => unreachable!("builtin method cannot be virtual"),
FnDirection::Virtual { .. } => unreachable!("builtin method cannot be virtual"),
FnDirection::Outbound { hash } => hash,
}
}
Expand Down Expand Up @@ -441,7 +446,11 @@ impl fmt::Display for ClassMethod {
#[derive(Copy, Clone, Debug)]
pub enum FnDirection {
/// Godot -> Rust.
Virtual,
Virtual {
// Since PR https://github.com/godotengine/godot/pull/100674, virtual methods have a compat hash, too.
#[cfg(since_api = "4.4")]
hash: u32,
},

/// Rust -> Godot.
Outbound { hash: i64 },
Expand Down
36 changes: 25 additions & 11 deletions godot-codegen/src/models/domain_mapping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -419,20 +419,34 @@ impl ClassMethod {
ctx: &mut Context,
) -> Option<Self> {
assert!(method.is_virtual);
assert!(
method.hash.is_none(),
"hash present for virtual class method"
);

// Hash for virtual methods is available from Godot 4.4, see https://github.com/godotengine/godot/pull/100674.
let direction = FnDirection::Virtual {
#[cfg(since_api = "4.4")]
hash: {
// TODO(v0.3,virtual-compat): re-enable this in favor of 0 fallback.
/*let hash_i64 = method.hash.unwrap_or_else(|| {
panic!(
"virtual class methods must have a hash since Godot 4.4; missing: {}.{}",
class_name.godot_ty, method.name
)
});*/
// Temporarily use hash 0 until upstream PR is merged.
let hash_i64 = method.hash.unwrap_or(0);

// TODO see if we can use u32 everywhere.
hash_i64.try_into().unwrap_or_else(|_| {
panic!(
"virtual method {}.{} has hash {} that is out of range for u32",
class_name.godot_ty, method.name, hash_i64
)
})
},
};

let rust_method_name = Self::make_virtual_method_name(class_name, &method.name);

Self::from_json_inner(
method,
rust_method_name,
class_name,
FnDirection::Virtual,
ctx,
)
Self::from_json_inner(method, rust_method_name, class_name, direction, ctx)
}

fn from_json_inner(
Expand Down
6 changes: 1 addition & 5 deletions godot-core/src/obj/bounds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@

use crate::obj::cap::GodotDefault;
use crate::obj::{Bounds, Gd, GodotClass, RawGd};
use crate::registry::callbacks;
use crate::storage::{InstanceCache, Storage};
use crate::{out, sys};
use private::Sealed;
Expand Down Expand Up @@ -418,10 +417,7 @@ impl Declarer for DeclUser {
where
T: GodotDefault + Bounds<Declarer = Self>,
{
unsafe {
let object_ptr = callbacks::create::<T>(std::ptr::null_mut());
Gd::from_obj_sys(object_ptr)
}
Gd::default_instance()
}
}

Expand Down
5 changes: 5 additions & 0 deletions godot-core/src/obj/gd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,12 @@ impl<T: GodotClass> Gd<T> {
T: cap::GodotDefault,
{
unsafe {
// Default value (and compat one) for `p_notify_postinitialize` is true in Godot.
#[cfg(since_api = "4.4")]
let object_ptr = callbacks::create::<T>(std::ptr::null_mut(), sys::conv::SYS_TRUE);
#[cfg(before_api = "4.4")]
let object_ptr = callbacks::create::<T>(std::ptr::null_mut());

Gd::from_obj_sys(object_ptr)
}
}
Expand Down
10 changes: 8 additions & 2 deletions godot-core/src/obj/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,10 @@ pub trait UserClass: Bounds<Declarer = bounds::DeclUser> {
fn __before_ready(&mut self);

#[doc(hidden)]
fn __default_virtual_call(_method_name: &str) -> sys::GDExtensionClassCallVirtual {
fn __default_virtual_call(
_method_name: &str,
#[cfg(since_api = "4.4")] _hash: u32,
) -> sys::GDExtensionClassCallVirtual {
None
}
}
Expand Down Expand Up @@ -586,6 +589,9 @@ pub mod cap {
/// Auto-implemented for `#[godot_api] impl XyVirtual for MyClass` blocks
pub trait ImplementsGodotVirtual: GodotClass {
#[doc(hidden)]
fn __virtual_call(_name: &str) -> sys::GDExtensionClassCallVirtual;
fn __virtual_call(
name: &str,
#[cfg(since_api = "4.4")] hash: u32,
) -> sys::GDExtensionClassCallVirtual;
}
}
Loading
Loading