Skip to content

Commit b619959

Browse files
authored
Merge pull request #991 from godot-rust/feature/compat-virtual-methods
Godot FFI: postinit create, compat virtual methods, icon paths
2 parents d10e9a0 + 76386c3 commit b619959

File tree

20 files changed

+421
-148
lines changed

20 files changed

+421
-148
lines changed

godot-codegen/src/context.rs

+11-6
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,14 @@ impl<'a> Context<'a> {
6969
// Populate derived-to-base relations
7070
if let Some(base) = class.inherits.as_ref() {
7171
let base_name = TyName::from_godot(base);
72-
println!(
73-
"* Add engine class {} <- inherits {}",
74-
class_name.description(),
75-
base_name.description()
76-
);
72+
// println!(
73+
// "* Add engine class {} <- inherits {}",
74+
// class_name.description(),
75+
// base_name.description()
76+
// );
7777
ctx.inheritance_tree.insert(class_name.clone(), base_name);
7878
} else {
79-
println!("* Add engine class {}", class_name.description());
79+
// println!("* Add engine class {}", class_name.description());
8080
}
8181

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

350+
#[allow(unused)] // Currently 4.4 gated, for virtual method hashes.
351+
pub fn direct_base(&self, derived_name: &TyName) -> Option<TyName> {
352+
self.derived_to_base.get(derived_name).cloned()
353+
}
354+
350355
/// Returns all base classes, without the class itself, in order from nearest to furthest (object).
351356
pub fn collect_all_bases(&self, derived_name: &TyName) -> Vec<TyName> {
352357
let mut upgoer = derived_name;

godot-codegen/src/generator/functions_common.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
use crate::generator::default_parameters;
99
use crate::models::domain::{ArgPassing, FnParam, FnQualifier, Function, RustTy};
1010
use crate::special_cases;
11-
use crate::util::{lifetime, safe_ident};
11+
use crate::util::lifetime;
1212
use proc_macro2::{Ident, TokenStream};
1313
use quote::{format_ident, quote};
1414

@@ -155,11 +155,11 @@ pub fn make_function_definition(
155155
make_params_exprs(sig.params().iter(), passing)
156156
};
157157

158-
let rust_function_name_str = sig.name();
158+
let rust_function_name = sig.name_ident();
159159

160160
let (primary_fn_name, default_fn_code, default_structs_code);
161161
if has_default_params {
162-
primary_fn_name = format_ident!("{}_full", safe_ident(rust_function_name_str));
162+
primary_fn_name = format_ident!("{}_full", rust_function_name);
163163

164164
(default_fn_code, default_structs_code) =
165165
default_parameters::make_function_definition_with_defaults(
@@ -169,7 +169,7 @@ pub fn make_function_definition(
169169
cfg_attributes,
170170
);
171171
} else {
172-
primary_fn_name = safe_ident(rust_function_name_str);
172+
primary_fn_name = rust_function_name.clone();
173173
default_fn_code = TokenStream::new();
174174
default_structs_code = TokenStream::new();
175175
};
@@ -227,7 +227,7 @@ pub fn make_function_definition(
227227
}
228228
} else {
229229
let try_return_decl = &sig.return_value().call_result_decl();
230-
let try_fn_name = format_ident!("try_{}", rust_function_name_str);
230+
let try_fn_name = format_ident!("try_{}", rust_function_name);
231231

232232
// Note: all varargs functions are non-static, which is why there are some shortcuts in try_*() argument forwarding.
233233
// This can be made more complex if ever necessary.

godot-codegen/src/generator/method_tables.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,8 @@ pub fn make_utility_function_table(api: &ExtensionApi) -> TokenStream {
159159
};
160160

161161
for function in api.utility_functions.iter() {
162+
let field = generator::utility_functions::make_utility_function_ptr_name(function);
162163
let fn_name_str = function.name();
163-
let field = generator::utility_functions::make_utility_function_ptr_name(fn_name_str);
164164
let hash = function.hash();
165165

166166
table.method_decls.push(quote! {
@@ -191,7 +191,7 @@ struct NamedMethodTable {
191191
method_count: usize,
192192
}
193193

194-
#[allow(dead_code)] // for lazy feature
194+
#[allow(dead_code)] // Individual fields would need to be cfg'ed with: feature = "codegen-lazy-fptrs".
195195
struct IndexedMethodTable {
196196
table_name: Ident,
197197
imports: TokenStream,

godot-codegen/src/generator/mod.rs

+14
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ pub mod notifications;
2828
pub mod utility_functions;
2929
pub mod virtual_traits;
3030

31+
#[cfg(since_api = "4.4")]
32+
pub mod virtual_hashes;
33+
3134
// ----------------------------------------------------------------------------------------------------------------------------------------------
3235

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

4853
pub mod central;
4954
pub mod gdextension_interface;
@@ -77,6 +82,15 @@ pub fn generate_sys_classes_file(
7782
submit_fn(sys_gen_path.join(filename), code);
7883
watch.record(format!("generate_classes_{}_file", api_level.lower()));
7984
}
85+
86+
// From 4.4 onward, generate table that maps all virtual methods to their known hashes.
87+
// This allows Godot to fall back to an older compatibility function if one is not supported.
88+
#[cfg(since_api = "4.4")]
89+
{
90+
let code = virtual_hashes::make_virtual_hashes_file(api, ctx);
91+
submit_fn(sys_gen_path.join("virtual_hashes.rs"), code);
92+
watch.record("generate_virtual_hashes_file");
93+
}
8094
}
8195

8296
pub fn generate_sys_utilities_file(

godot-codegen/src/generator/utility_functions.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,16 @@ pub(crate) fn generate_utilities_file(
3535
submit_fn(gen_path.join("utilities.rs"), tokens);
3636
}
3737

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

4242
pub(crate) fn make_utility_function_definition(function: &UtilityFunction) -> TokenStream {
43+
let function_ident = make_utility_function_ptr_name(function);
4344
let function_name_str = function.name();
44-
let fn_ptr = make_utility_function_ptr_name(function_name_str);
4545

4646
let ptrcall_invocation = quote! {
47-
let utility_fn = sys::utility_function_table().#fn_ptr;
47+
let utility_fn = sys::utility_function_table().#function_ident;
4848

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

5656
let varcall_invocation = quote! {
57-
let utility_fn = sys::utility_function_table().#fn_ptr;
57+
let utility_fn = sys::utility_function_table().#function_ident;
5858

5959
<CallSig as VarcallSignatureTuple>::out_utility_ptrcall_varargs(
6060
utility_fn,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright (c) godot-rust; Bromeon and contributors.
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
6+
*/
7+
8+
use crate::context::Context;
9+
use crate::models::domain::{Class, ClassLike, ExtensionApi, FnDirection, Function};
10+
use proc_macro2::TokenStream;
11+
use quote::quote;
12+
13+
pub fn make_virtual_hashes_file(api: &ExtensionApi, ctx: &mut Context) -> TokenStream {
14+
make_virtual_hashes_for_all_classes(&api.classes, ctx)
15+
}
16+
17+
fn make_virtual_hashes_for_all_classes(all_classes: &[Class], ctx: &mut Context) -> TokenStream {
18+
let modules = all_classes
19+
.iter()
20+
.map(|class| make_virtual_hashes_for_class(class, ctx));
21+
22+
quote! {
23+
#![allow(non_snake_case, non_upper_case_globals, unused_imports)]
24+
25+
#( #modules )*
26+
}
27+
}
28+
29+
fn make_virtual_hashes_for_class(class: &Class, ctx: &mut Context) -> TokenStream {
30+
let class_name = class.name();
31+
32+
// Import all base class hashes via `use` statements.
33+
let use_base_class = if let Some(base_class) = ctx.inheritance_tree().direct_base(class_name) {
34+
quote! {
35+
pub use super::#base_class::*;
36+
}
37+
} else {
38+
TokenStream::new()
39+
};
40+
41+
let constants = class.methods.iter().filter_map(|method| {
42+
let FnDirection::Virtual { hash } = method.direction() else {
43+
return None;
44+
};
45+
46+
let method_name = method.name_ident();
47+
let constant = quote! {
48+
pub const #method_name: u32 = #hash;
49+
};
50+
51+
Some(constant)
52+
});
53+
54+
// Even if there are no virtual methods, we need to generate the module, to enable base class imports via `use`.
55+
quote! {
56+
pub mod #class_name {
57+
#use_base_class
58+
#( #constants )*
59+
}
60+
}
61+
}

godot-codegen/src/models/domain.rs

+13-4
Original file line numberDiff line numberDiff line change
@@ -291,9 +291,14 @@ pub trait Function: fmt::Display {
291291
fn surrounding_class(&self) -> Option<&TyName>;
292292

293293
// Default:
294+
/// Rust name as string slice.
294295
fn name(&self) -> &str {
295296
&self.common().name
296297
}
298+
/// Rust name as `Ident`. Might be cached in future.
299+
fn name_ident(&self) -> Ident {
300+
safe_ident(self.name())
301+
}
297302
fn godot_name(&self) -> &str {
298303
&self.common().godot_name
299304
}
@@ -310,7 +315,7 @@ pub trait Function: fmt::Display {
310315
self.common().is_private
311316
}
312317
fn is_virtual(&self) -> bool {
313-
matches!(self.direction(), FnDirection::Virtual)
318+
matches!(self.direction(), FnDirection::Virtual { .. })
314319
}
315320
fn direction(&self) -> FnDirection {
316321
self.common().direction
@@ -330,7 +335,7 @@ pub struct UtilityFunction {
330335
impl UtilityFunction {
331336
pub fn hash(&self) -> i64 {
332337
match self.direction() {
333-
FnDirection::Virtual => unreachable!("utility function cannot be virtual"),
338+
FnDirection::Virtual { .. } => unreachable!("utility function cannot be virtual"),
334339
FnDirection::Outbound { hash } => hash,
335340
}
336341
}
@@ -370,7 +375,7 @@ pub struct BuiltinMethod {
370375
impl BuiltinMethod {
371376
pub fn hash(&self) -> i64 {
372377
match self.direction() {
373-
FnDirection::Virtual => unreachable!("builtin method cannot be virtual"),
378+
FnDirection::Virtual { .. } => unreachable!("builtin method cannot be virtual"),
374379
FnDirection::Outbound { hash } => hash,
375380
}
376381
}
@@ -441,7 +446,11 @@ impl fmt::Display for ClassMethod {
441446
#[derive(Copy, Clone, Debug)]
442447
pub enum FnDirection {
443448
/// Godot -> Rust.
444-
Virtual,
449+
Virtual {
450+
// Since PR https://github.com/godotengine/godot/pull/100674, virtual methods have a compat hash, too.
451+
#[cfg(since_api = "4.4")]
452+
hash: u32,
453+
},
445454

446455
/// Rust -> Godot.
447456
Outbound { hash: i64 },

godot-codegen/src/models/domain_mapping.rs

+25-11
Original file line numberDiff line numberDiff line change
@@ -419,20 +419,34 @@ impl ClassMethod {
419419
ctx: &mut Context,
420420
) -> Option<Self> {
421421
assert!(method.is_virtual);
422-
assert!(
423-
method.hash.is_none(),
424-
"hash present for virtual class method"
425-
);
422+
423+
// Hash for virtual methods is available from Godot 4.4, see https://github.com/godotengine/godot/pull/100674.
424+
let direction = FnDirection::Virtual {
425+
#[cfg(since_api = "4.4")]
426+
hash: {
427+
// TODO(v0.3,virtual-compat): re-enable this in favor of 0 fallback.
428+
/*let hash_i64 = method.hash.unwrap_or_else(|| {
429+
panic!(
430+
"virtual class methods must have a hash since Godot 4.4; missing: {}.{}",
431+
class_name.godot_ty, method.name
432+
)
433+
});*/
434+
// Temporarily use hash 0 until upstream PR is merged.
435+
let hash_i64 = method.hash.unwrap_or(0);
436+
437+
// TODO see if we can use u32 everywhere.
438+
hash_i64.try_into().unwrap_or_else(|_| {
439+
panic!(
440+
"virtual method {}.{} has hash {} that is out of range for u32",
441+
class_name.godot_ty, method.name, hash_i64
442+
)
443+
})
444+
},
445+
};
426446

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

429-
Self::from_json_inner(
430-
method,
431-
rust_method_name,
432-
class_name,
433-
FnDirection::Virtual,
434-
ctx,
435-
)
449+
Self::from_json_inner(method, rust_method_name, class_name, direction, ctx)
436450
}
437451

438452
fn from_json_inner(

godot-core/src/obj/bounds.rs

+1-5
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@
4949

5050
use crate::obj::cap::GodotDefault;
5151
use crate::obj::{Bounds, Gd, GodotClass, RawGd};
52-
use crate::registry::callbacks;
5352
use crate::storage::{InstanceCache, Storage};
5453
use crate::{out, sys};
5554
use private::Sealed;
@@ -418,10 +417,7 @@ impl Declarer for DeclUser {
418417
where
419418
T: GodotDefault + Bounds<Declarer = Self>,
420419
{
421-
unsafe {
422-
let object_ptr = callbacks::create::<T>(std::ptr::null_mut());
423-
Gd::from_obj_sys(object_ptr)
424-
}
420+
Gd::default_instance()
425421
}
426422
}
427423

godot-core/src/obj/gd.rs

+5
Original file line numberDiff line numberDiff line change
@@ -454,7 +454,12 @@ impl<T: GodotClass> Gd<T> {
454454
T: cap::GodotDefault,
455455
{
456456
unsafe {
457+
// Default value (and compat one) for `p_notify_postinitialize` is true in Godot.
458+
#[cfg(since_api = "4.4")]
459+
let object_ptr = callbacks::create::<T>(std::ptr::null_mut(), sys::conv::SYS_TRUE);
460+
#[cfg(before_api = "4.4")]
457461
let object_ptr = callbacks::create::<T>(std::ptr::null_mut());
462+
458463
Gd::from_obj_sys(object_ptr)
459464
}
460465
}

godot-core/src/obj/traits.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,10 @@ pub trait UserClass: Bounds<Declarer = bounds::DeclUser> {
162162
fn __before_ready(&mut self);
163163

164164
#[doc(hidden)]
165-
fn __default_virtual_call(_method_name: &str) -> sys::GDExtensionClassCallVirtual {
165+
fn __default_virtual_call(
166+
_method_name: &str,
167+
#[cfg(since_api = "4.4")] _hash: u32,
168+
) -> sys::GDExtensionClassCallVirtual {
166169
None
167170
}
168171
}
@@ -586,6 +589,9 @@ pub mod cap {
586589
/// Auto-implemented for `#[godot_api] impl XyVirtual for MyClass` blocks
587590
pub trait ImplementsGodotVirtual: GodotClass {
588591
#[doc(hidden)]
589-
fn __virtual_call(_name: &str) -> sys::GDExtensionClassCallVirtual;
592+
fn __virtual_call(
593+
name: &str,
594+
#[cfg(since_api = "4.4")] hash: u32,
595+
) -> sys::GDExtensionClassCallVirtual;
590596
}
591597
}

0 commit comments

Comments
 (0)