Skip to content

Commit a3ab6ef

Browse files
committed
Virtual Functions in GDExtension
1 parent 7d42ebb commit a3ab6ef

File tree

19 files changed

+286
-133
lines changed

19 files changed

+286
-133
lines changed

examples/dodge-the-creeps/rust/src/hud.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use godot::engine::{Button, CanvasLayer, Label, Timer};
1+
use godot::engine::{Button, CanvasLayer, CanvasLayerVirtual, Label, Timer};
22
use godot::prelude::*;
33

44
#[derive(GodotClass)]
@@ -61,7 +61,7 @@ impl Hud {
6161
}
6262

6363
#[godot_api]
64-
impl GodotExt for Hud {
64+
impl CanvasLayerVirtual for Hud {
6565
fn init(base: Base<Self::Base>) -> Self {
6666
Self { base }
6767
}

examples/dodge-the-creeps/rust/src/main_scene.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::mob;
33
use crate::player;
44
use godot::engine::node::InternalMode;
55
use godot::engine::packed_scene::GenEditState;
6-
use godot::engine::{Marker2D, PathFollow2D, RigidBody2D, Timer};
6+
use godot::engine::{Marker2D, NodeVirtual, PathFollow2D, RigidBody2D, Timer};
77
use godot::prelude::*;
88
use rand::Rng as _;
99
use std::f64::consts::PI;
@@ -126,7 +126,7 @@ impl Main {
126126
}
127127

128128
#[godot_api]
129-
impl GodotExt for Main {
129+
impl NodeVirtual for Main {
130130
fn init(base: Base<Node>) -> Self {
131131
Main {
132132
mob_scene: PackedScene::new(),

examples/dodge-the-creeps/rust/src/mob.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use godot::engine::{AnimatedSprite2D, RigidBody2D};
1+
use godot::engine::{AnimatedSprite2D, RigidBody2D, RigidBody2DVirtual};
22
use godot::prelude::*;
33
use rand::seq::SliceRandom;
44

@@ -47,7 +47,7 @@ impl Mob {
4747
}
4848

4949
#[godot_api]
50-
impl GodotExt for Mob {
50+
impl RigidBody2DVirtual for Mob {
5151
fn init(base: Base<RigidBody2D>) -> Self {
5252
Mob {
5353
min_speed: 150.0,

examples/dodge-the-creeps/rust/src/player.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use godot::engine::{AnimatedSprite2D, Area2D, CollisionShape2D, PhysicsBody2D};
1+
use godot::engine::{AnimatedSprite2D, Area2D, Area2DVirtual, CollisionShape2D, PhysicsBody2D};
22
use godot::prelude::*;
33

44
#[derive(GodotClass)]
@@ -42,7 +42,7 @@ impl Player {
4242
}
4343

4444
#[godot_api]
45-
impl GodotExt for Player {
45+
impl Area2DVirtual for Player {
4646
fn init(base: Base<Area2D>) -> Self {
4747
Player {
4848
speed: 400.0,

godot-codegen/src/api_parser.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ pub struct BuiltinClassMethod {
167167
pub arguments: Option<Vec<MethodArg>>,
168168
}
169169

170-
#[derive(DeJson)]
170+
#[derive(DeJson, Clone)]
171171
pub struct ClassMethod {
172172
pub name: String,
173173
pub is_const: bool,
@@ -200,7 +200,7 @@ pub struct MethodArg {
200200
}
201201

202202
// Example: get_available_point_id -> {type: "int", meta: "int64"}
203-
#[derive(DeJson)]
203+
#[derive(DeJson, Clone)]
204204
pub struct MethodReturn {
205205
#[nserde(rename = "type")]
206206
pub type_: String,

godot-codegen/src/class_generator.rs

Lines changed: 143 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ fn make_constructor(class: &Class, ctx: &Context) -> TokenStream {
159159
fn make_class(class: &Class, class_name: &TyName, ctx: &mut Context) -> GeneratedClass {
160160
// Strings
161161
let godot_class_str = &class_name.godot_ty;
162+
let virtual_trait_str = class_name.virtual_trait_name();
162163

163164
// Idents and tokens
164165
let base = match class.inherits.as_ref() {
@@ -174,6 +175,7 @@ fn make_class(class: &Class, class_name: &TyName, ctx: &mut Context) -> Generate
174175
let enums = make_enums(&class.enums, class_name, ctx);
175176
let inherits_macro = format_ident!("inherits_transitive_{}", class_name.rust_ty);
176177
let all_bases = ctx.inheritance_tree().collect_all_bases(class_name);
178+
let virtual_trait = make_virtual_methods_trait(class, &all_bases, &virtual_trait_str, ctx);
177179

178180
let memory = if class_name.rust_ty == "Object" {
179181
ident("DynamicRefCount")
@@ -199,6 +201,7 @@ fn make_class(class: &Class, class_name: &TyName, ctx: &mut Context) -> Generate
199201
pub struct #class_name {
200202
object_ptr: sys::GDExtensionObjectPtr,
201203
}
204+
#virtual_trait
202205
impl #class_name {
203206
#constructor
204207
#methods
@@ -323,12 +326,14 @@ fn make_module_file(classes_and_modules: Vec<GeneratedClassModule>) -> TokenStre
323326
is_pub,
324327
..
325328
} = m;
329+
let virtual_trait_name = ident(&class_name.virtual_trait_name());
326330

327331
let vis = is_pub.then_some(quote! { pub });
328332

329333
quote! {
330334
#vis mod #module_name;
331335
pub use #module_name::re_export::#class_name;
336+
pub use #module_name::re_export::#virtual_trait_name;
332337
}
333338
});
334339

@@ -463,12 +468,15 @@ fn is_type_excluded(ty: &str, ctx: &mut Context) -> bool {
463468
}
464469
}
465470

466-
fn is_method_excluded(method: &ClassMethod, #[allow(unused_variables)] ctx: &mut Context) -> bool {
471+
fn is_method_excluded(
472+
method: &ClassMethod,
473+
is_virtual_impl: bool,
474+
#[allow(unused_variables)] ctx: &mut Context,
475+
) -> bool {
467476
// Currently excluded:
468477
//
469-
// * Private virtual methods designed for override; skip for now
470-
// E.g.: AudioEffectInstance::_process(const void*, AudioFrame*, int)
471-
// TODO decide what to do with them, overriding in a type-safe way?
478+
// * Private virtual methods are only included in a virtual
479+
// implementation.
472480
//
473481
// * Methods accepting pointers are often supplementary
474482
// E.g.: TextServer::font_set_data_ptr() -- in addition to TextServer::font_set_data().
@@ -490,11 +498,14 @@ fn is_method_excluded(method: &ClassMethod, #[allow(unused_variables)] ctx: &mut
490498
}
491499
// -- end.
492500

493-
method.name.starts_with('_')
494-
|| method
495-
.return_value
496-
.as_ref()
497-
.map_or(false, |ret| ret.type_.contains('*'))
501+
if method.name.starts_with('_') && !is_virtual_impl {
502+
return true;
503+
}
504+
505+
method
506+
.return_value
507+
.as_ref()
508+
.map_or(false, |ret| ret.type_.contains('*'))
498509
|| method
499510
.arguments
500511
.as_ref()
@@ -523,7 +534,8 @@ fn make_method_definition(
523534
class_name: &TyName,
524535
ctx: &mut Context,
525536
) -> TokenStream {
526-
if is_method_excluded(method, ctx) || special_cases::is_deleted(class_name, &method.name) {
537+
if is_method_excluded(method, false, ctx) || special_cases::is_deleted(class_name, &method.name)
538+
{
527539
return TokenStream::new();
528540
}
529541
/*if method.map_args(|args| args.is_empty()) {
@@ -768,13 +780,7 @@ fn make_receiver(
768780
is_const: bool,
769781
receiver_arg: TokenStream,
770782
) -> (TokenStream, TokenStream) {
771-
let receiver = if is_static {
772-
quote! {}
773-
} else if is_const {
774-
quote! { &self, }
775-
} else {
776-
quote! { &mut self, }
777-
};
783+
let receiver = make_receiver_self_param(is_static, is_const);
778784

779785
let receiver_arg = if is_static {
780786
quote! { std::ptr::null_mut() }
@@ -785,6 +791,16 @@ fn make_receiver(
785791
(receiver, receiver_arg)
786792
}
787793

794+
fn make_receiver_self_param(is_static: bool, is_const: bool) -> TokenStream {
795+
if is_static {
796+
quote! {}
797+
} else if is_const {
798+
quote! { &self, }
799+
} else {
800+
quote! { &mut self, }
801+
}
802+
}
803+
788804
fn make_params(
789805
method_args: &Option<Vec<MethodArg>>,
790806
is_varcall: bool,
@@ -891,3 +907,113 @@ fn make_return(
891907

892908
(return_decl, call)
893909
}
910+
911+
fn make_virtual_methods_trait(
912+
class: &Class,
913+
all_bases: &[TyName],
914+
trait_name: &str,
915+
ctx: &mut Context,
916+
) -> TokenStream {
917+
let trait_name = ident(trait_name);
918+
919+
let virtual_method_fns = make_all_virtual_methods(class, all_bases, ctx);
920+
let special_virtual_methods = special_virtual_methods();
921+
922+
quote! {
923+
#[allow(unused_variables)]
924+
#[allow(clippy::unimplemented)]
925+
pub trait #trait_name: crate::private::You_forgot_the_attribute__godot_api + crate::obj::GodotClass {
926+
#( #virtual_method_fns )*
927+
#special_virtual_methods
928+
}
929+
}
930+
}
931+
932+
fn special_virtual_methods() -> TokenStream {
933+
quote! {
934+
fn register_class(builder: &mut crate::builder::ClassBuilder<Self>) {
935+
unimplemented!()
936+
}
937+
fn init(base: crate::obj::Base<Self::Base>) -> Self {
938+
unimplemented!()
939+
}
940+
fn to_string(&self) -> crate::builtin::GodotString {
941+
unimplemented!()
942+
}
943+
}
944+
}
945+
946+
fn make_virtual_method(class_method: &ClassMethod, ctx: &mut Context) -> TokenStream {
947+
let method_name = ident(virtual_method_name(class_method));
948+
949+
// Virtual methods are never static.
950+
assert!(!class_method.is_static);
951+
952+
let receiver = make_receiver_self_param(false, class_method.is_const);
953+
let (params, _) = make_params(&class_method.arguments, class_method.is_vararg, ctx);
954+
955+
quote! {
956+
fn #method_name ( #receiver #( #params , )* ) {
957+
unimplemented!()
958+
}
959+
}
960+
}
961+
962+
fn make_all_virtual_methods(
963+
class: &Class,
964+
all_bases: &[TyName],
965+
ctx: &mut Context,
966+
) -> Vec<TokenStream> {
967+
let mut all_virtuals = vec![];
968+
let mut extend_virtuals = |class| {
969+
all_virtuals.extend(
970+
get_methods_in_class(class)
971+
.iter()
972+
.cloned()
973+
.filter(|m| m.is_virtual),
974+
);
975+
};
976+
977+
// Get virtuals defined on the current class.
978+
extend_virtuals(class);
979+
// Add virtuals from superclasses.
980+
for base in all_bases {
981+
let superclass = ctx.get_engine_class(base);
982+
extend_virtuals(superclass);
983+
}
984+
all_virtuals
985+
.into_iter()
986+
.filter_map(|method| {
987+
if is_method_excluded(&method, true, ctx) {
988+
None
989+
} else {
990+
Some(make_virtual_method(&method, ctx))
991+
}
992+
})
993+
.collect()
994+
}
995+
996+
fn get_methods_in_class(class: &Class) -> &[ClassMethod] {
997+
match &class.methods {
998+
None => &[],
999+
Some(methods) => methods,
1000+
}
1001+
}
1002+
1003+
fn virtual_method_name(class_method: &ClassMethod) -> &str {
1004+
// Matching the C++ convention, we remove the leading underscore
1005+
// from virtual method names.
1006+
let method_name = class_method
1007+
.name
1008+
.strip_prefix('_')
1009+
.unwrap_or(&class_method.name);
1010+
1011+
// As a special exception, a few classes define a virtual method
1012+
// called "_init" (distinct from the constructor), so we rename
1013+
// those to avoid a name conflict in our trait.
1014+
if method_name == "init" {
1015+
"init_ext"
1016+
} else {
1017+
method_name
1018+
}
1019+
}

godot-codegen/src/context.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
55
*/
66

7+
use crate::api_parser::Class;
78
use crate::{ExtensionApi, RustTy, TyName};
89
use std::collections::{HashMap, HashSet};
910

1011
#[derive(Default)]
1112
pub(crate) struct Context<'a> {
12-
engine_classes: HashSet<TyName>,
13+
engine_classes: HashMap<TyName, &'a Class>,
1314
builtin_types: HashSet<&'a str>,
1415
singletons: HashSet<&'a str>,
1516
inheritance_tree: InheritanceTree,
@@ -39,7 +40,7 @@ impl<'a> Context<'a> {
3940
}
4041

4142
println!("-- add engine class {}", class_name.description());
42-
ctx.engine_classes.insert(class_name.clone());
43+
ctx.engine_classes.insert(class_name.clone(), class);
4344

4445
if let Some(base) = class.inherits.as_ref() {
4546
let base_name = TyName::from_godot(base);
@@ -50,6 +51,10 @@ impl<'a> Context<'a> {
5051
ctx
5152
}
5253

54+
pub fn get_engine_class(&self, class_name: &TyName) -> &Class {
55+
self.engine_classes.get(class_name).unwrap()
56+
}
57+
5358
// pub fn is_engine_class(&self, class_name: &str) -> bool {
5459
// self.engine_classes.contains(class_name)
5560
// }

godot-codegen/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,10 @@ impl TyName {
197197
format!("{} [renamed {}]", self.godot_ty, self.rust_ty)
198198
}
199199
}
200+
201+
fn virtual_trait_name(&self) -> String {
202+
format!("{}Virtual", self.rust_ty)
203+
}
200204
}
201205

202206
impl ToTokens for TyName {

0 commit comments

Comments
 (0)