-
-
Notifications
You must be signed in to change notification settings - Fork 236
Emit POSTINITIALIZE
notification after init()
#1211
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
base: master
Are you sure you want to change the base?
Conversation
ba02ea9
to
0812e49
Compare
Thanks!
There are many more changes than these two. Please edit your initial description to elaborate what exactly the PR does and its motivation. Follow-up to #1208. |
API docs are being generated and will be shortly available at: https://godot-rust.github.io/docs/gdext/pr-1211 |
0812e49
to
14d9878
Compare
I'm relatively new to gdext. It seems that we can't modify the object itself (e.g. |
`NOTIFICATION_POSTINITIALIZE` will be recevied correctly in godot 4.4 and later
14d9878
to
99e092a
Compare
Part of that is what I'm looking into in #557. This PR seems to integrate with Godot's own POST_INIT notification. Can you demonstrate what it enables with some code examples? Please edit the initial post. |
POSTINITIALIZE
notification after init()
Unfortunately, I encountered a crash when using #[derive(GodotClass)]
#[class(base=ArrayMesh,init,tool)]
pub struct EmptyTriangleMesh {
base: Base<ArrayMesh>,
}
#[godot_api]
#[allow(unused_variables)]
impl IArrayMesh for EmptyTriangleMesh {
fn on_notification(&mut self, what: notify::ObjectNotification) {
if what == notify::ObjectNotification::POSTINITIALIZE {
let mut arrays = VariantArray::new();
arrays.resize(
mesh::ArrayType::MAX.ord().try_into().unwrap(),
&Variant::nil(),
);
let indices = PackedInt32Array::from([0, 1, 2]);
arrays.set(
mesh::ArrayType::INDEX.ord().try_into().unwrap(),
&indices.to_variant(),
);
self.base_mut()
.add_surface_from_arrays_ex(mesh::PrimitiveType::TRIANGLES, &arrays)
.flags(mesh::ArrayFormat::FLAG_USES_EMPTY_VERTEX_ARRAY)
.done();
}
}
fn get_surface_count(&self) -> i32 {
todo!()
}
fn surface_get_array_len(&self, index: i32) -> i32 {
todo!()
}
fn surface_get_array_index_len(&self, index: i32) -> i32 {
todo!()
}
fn surface_get_arrays(&self, index: i32) -> VariantArray {
todo!()
}
fn surface_get_blend_shape_arrays(&self, index: i32) -> Array<VariantArray> {
todo!()
}
fn surface_get_lods(&self, index: i32) -> Dictionary {
todo!()
}
fn surface_get_format(&self, index: i32) -> u32 {
todo!()
}
fn surface_get_primitive_type(&self, index: i32) -> u32 {
todo!()
}
fn surface_set_material(&mut self, index: i32, material: Option<Gd<Material>>) {
todo!()
}
fn surface_get_material(&self, index: i32) -> Option<Gd<Material>> {
todo!()
}
fn get_blend_shape_count(&self) -> i32 {
todo!()
}
fn get_blend_shape_name(&self, index: i32) -> StringName {
todo!()
}
fn set_blend_shape_name(&mut self, index: i32, name: StringName) {
todo!()
}
fn get_aabb(&self) -> Aabb {
todo!()
}
}
So I must be missing something. |
Hmm, interesting – it seems that our refcount is still unset before we pass it to godot (after "create" function will yield the result). I checked what godot-cpp is doing, and it seems that their flow is the same? (create instance -> construct object 2 -> set instance -> set instance binding -> and finally, a postinitialize). https://github.com/godotengine/godot-cpp/blob/6a870949a5d38c62638af23dc13210f77154aa2c/include/godot_cpp/core/class_db.hpp#L124 |
Surprisingly, I tested the following code on godot-cpp-template: #include "example_class.h"
void ExampleClass::_bind_methods() {
godot::ClassDB::bind_method(D_METHOD("print_type", "variant"), &ExampleClass::print_type);
}
void ExampleClass::_notification(int p_what) {
if (p_what == NOTIFICATION_POSTINITIALIZE) {
Ref<ExampleClass> t = this;
UtilityFunctions::print("ref count: ", t->get_reference_count());
}
}
void ExampleClass::print_type(const Variant &p_variant) const {
print_line(vformat("Type: %d", p_variant.get_type()));
} extends Node
func _ready() -> void:
var example := ExampleClass.new()
example.print_type(example) It prints a error that indicates
But if I remove
So could this be a issue with Godot? Edit: Additionally, obtaining |
Yep, it seems to be a problem with Godot itself – such behavior in |
@beicause you closed godotengine/godot#108395 with:
Given that, does it make sense to support If yes, what is the use case? In case we decide not to implement |
POSTINITIALIZE may be useful, as it allows base classes to execute logic after subclass initialization - for example Control updates its theme cache during POSTINITIALIZE. And godotengine/godot#91018 (comment) 🤔 If #557 gets resolved, could the same solution enable POSTINITIALIZE to access base class? |
I see... Then we would need to emit it. But how to handle it properly in |
One silly idea I had was using deferred refcount with Very silly hack, it adds some, potentially pretty serious, overhead, but hey, it works |
I've used a hack to avoid releasing RefCounted in this PR: let mut obj = Gd::<T>::from_obj_sys_weak(object_ptr).upcast_object();
obj.notify(crate::classes::notify::ObjectNotification::POSTINITIALIZE);
std::mem::forget(obj); |
It is not a hack, it is totally sensible thing to do – don't increase the refcount while passing an object to the user internally. What I've been thinking is something along lines of: if let Ok(object_ptr) = create_custom(T::__godot_user_init) {
let mut obj = Gd::<T>::from_obj_sys_weak(object_ptr).upcast_object();
if T::inherits::<RefCounted>() {
<classes::Object as Bounds>::DynMemory::maybe_inc_ref(&mut obj.raw);
obj.linked_callable("deferred refcount", move |_| {
// Run RawGd<T> destructor.
drop(Gd::<T>::from_obj_sys_weak(object_ptr));
Ok(Variant::nil())
}).call_deferred(&[]);
}
obj.notify(crate::classes::notify::ObjectNotification::POSTINITIALIZE);
std::mem::forget(obj);
object_ptr It works but is very janky – instance is being destroyed at the end of a frame, not imminently, appending callable to every RefCounted has some inherent overhead etc – thus if we resort to such a hack, it should be explicitly called by user (by invoking base for the first time before Don't actually implement it – firstly we need to reason if it is sensible thing to do, check for potential side effects etc 😅 |
This seems fragile. There's no guarantee the object remains alive when the callable executes. I'd prefer having something like |
I don't like it either. It is choose-yer-poison situation (on another hand nothing better comes to my mind).
…and objects staying alive longer than they should are not perfect either.
Yeah, I would prefer it as well – unfortunately a lot of interactions with Godot require increasing the RefCount, thus making this way inherently unsound (but it would allow user to "route" calls via I'll underline – I'm not (and don't strive to be) authoritative in this matter, it is just one possible solution 🤷 |
Weak ref doesn't keep the object alive though -- that's the point. |
It's true. Even if it is unsound, it's still better than nothing. Why can't we ever provide unsafe APIs? |
First, unsafe != unsound. The latter means there's a violation of Rust's memory safety principles. In other words, a bug that must be fixed. Obviously we can provide That said, I didn't test your code. It is possible it works well. Scenarios that we need to protect against:
And panic in these cases is good enough. But UB (undefined behavior) isn't. |
Changes:
classdb_construct_object
->classdb_construct_object2
string_new_with_utf8_chars_and_len
->string_new_with_utf8_chars_and_len2
After this PR,
NOTIFICATION_POSTINITIALIZE
will be sent afterinit()
and can be received correctly inon_notification()
in godot 4.4 or later. See alsotest_notifications
in the itest.Partially addresses #557