-
-
Notifications
You must be signed in to change notification settings - Fork 194
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
Virtual function dispatch #136
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice addition, thanks a lot! I'm surprised how well you got around in the codegen source 😅 was it difficult to understand the existing code?
bors try
if method.name.starts_with('_') && !is_virtual_impl { | ||
return true; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You removed the condition about pointers, why that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I removed the blanket ban on all method names starting with _
. All virtual methods have a Godot name that starts with an underscore, so that would exclude all of the methods I intend to add with this MR. The pointer condition is still present (line 508). Looks like the Rust formatter just re-indented it a bit, which is why it shows as a diff.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I see. This is where semantic diff like difftastic might come in really handy 😎
All good then, thanks for explanation!
godot-core/src/obj/traits.rs
Outdated
pub trait GodotToString: GodotClass { | ||
#[doc(hidden)] | ||
fn __virtual_to_string(&self) -> GodotString; | ||
} | ||
|
||
pub trait GodotRegisterClass: GodotClass { | ||
#[doc(hidden)] | ||
fn __virtual_register_class(builder: &mut ClassBuilder<Self>); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do these need to be public traits? (Note that "public" doesn't necessarily mean pub
; as you see with many symbols, #[doc(hidden)]
allows them to be used in proc-macros without making them part of the public APIs).
GodotInit
is public because it's used as a generic bound, to determine whether an object is default-constructible from Godot or not. I don't think to_string
and register_class
contribute to the type in such a way (maybe this could be discussed for GodotToString
, similar to Display
). I think they are more like ImplementsGodotVirtual
below. However, I see that you use them as bounds in the builder API -- possibly something to revisit once we get to that.
One issue with the current approach is that every time we add another method to what was previously in GodotExt
, we need to code-generate a new trait and adjust the bounds in several places. But I'm also fine to leave it for now and address that problem, once we have it.
What I would definitely change is to remove the "virtual" from the method name, as those method are not technically related to Godot virtuals. The 2 underscores prefix can stay.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My logic here was to make these other traits like GodotInit
, since they follow the same principles: Can be used as a Rust-side bound, intended to be implemented automatically based on #[godot_api]
, and is a special case in Godot to begin with. Open to other suggestions for how to handle that though. Will remove __virtual
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regarding __register_class
, I don't know if this abstraction would be necessary in user-facing code? No one is ever going to invoke the class registration method manually, and I also don't think whether a class has a registration method or not should be part of its public API.
__to_string
is more reasonable, but also here, I'm not sure if the Rust-side bound fully reflects reality: in Godot, it's possible to invoke to_string()
on any object, independently of whether it implements such a method or not. So we may constrain implementations more than necessary by requiring such a bound.
Maybe you could mark them #[doc(hidden)]
for now and add a // TODO
-- we probably have to revisit this anyway once we get to builder APIs. Until then, I don't really see a use case for having these traits public.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good. I don't have a strong opinion on whether they're public or not, so I'm happy to hide them away for now and have the discussion in the future.
tryBuild succeeded: |
Made some changes. Thanks for the quick review, Bromeon! The code generation is actually very straightforward and well-designed. You've done a good job here. |
Thanks for the update! One small disadvantage compared to #[derive(GodotClass)]
#[class(base=CanvasLayer)] // 1
pub struct Hud {
#[base]
base: Base<CanvasLayer>, // 2 (could use Self::Base)
}
#[godot_api]
impl CanvasLayerVirtual for Hud { // 3
fn init(base: Base<Self::Base>) -> Self {
Self { base }
}
} So if you change the base of a class, you have one more place to update now -- even if you use only That's probably fine though -- it could also be seen as a feature that you immediately know to which Godot class the virtual methods belong. Merge conflicts arised during a refactoring in the proc-macro handling, sorry about that 🙂 You now have 24 commits -- could you squash them into a single one? |
928439a
to
a3ab6ef
Compare
I believe I've fixed the merge conflicts, and the commits are down to one. |
In pub use super::engine::{
load, try_load, utilities, AudioStreamPlayer, Camera2D, Camera3D, Input, Node, Node2D,
Node3D, Object, PackedScene, RefCounted, Resource, SceneTree,
}; What do you think about adding the Those could then be used inside the dodge-the-creeps example. Also, do you think it would be possible to test that virtual methods are actually invoked, inside |
#164 should allow to test lifecycle functions directly on the the scene tree ( |
a3ab6ef
to
73b1b6d
Compare
Added tests for |
Thanks for updating! Regarding bors try |
tryBuild succeeded: |
Let's merge this, so everyone can start using it 🚀 bors r+ |
Build succeeded: |
See #134.
This MR replaces
GodotExt
with a new trait for each built-in Godot class. The trait is the name of the Godot class followed by the word "Virtual". So, for instance, a Rust class that extendsNode2D
would implement the trait calledgodot::engine::Node2DVirtual
. This trait contains all of the virtual methods belonging toNode2D
and its ancestors, as well as a few special-cased methods that used to be onGodotExt
(init
,to_string
, andregister_class
).All methods are optional. Any unimplemented methods call
unimplemented!()
on the Rust side and never get registered as virtual methods on the Godot side.Internally,
ImplementsGodotExt
has been split out intoImplementsGodotVirtual
(which only contains real Godot virtual methods),GodotToString
, andGodotRegisterClass
. All of these are automatically implemented when you implement theClassNameVirtual
trait, and only the ones that you actually define are implemented (so, for example,GodotToString
is only implemented if you actually define ato_string
function in your traitimpl
). The user never sees any of this. From the user's perspective, the expectation used to be "define an inherent impl and implementGodotExt
". Now the expectation is "define an inherent impl and implementClassNameVirtual
for your class".The tests and dodge-the-creeps example have been updated to reflect these changes.