Skip to content

Commit d34855c

Browse files
committed
Emit POSTINITIALIZE notification after init()
1 parent 8526478 commit d34855c

File tree

9 files changed

+87
-20
lines changed

9 files changed

+87
-20
lines changed

godot-core/src/builtin/string/gstring.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,11 @@ impl From<&str> for GString {
328328

329329
unsafe {
330330
Self::new_with_string_uninit(|string_ptr| {
331+
#[cfg(before_api = "4.3")]
331332
let ctor = interface_fn!(string_new_with_utf8_chars_and_len);
333+
#[cfg(since_api = "4.3")]
334+
let ctor = interface_fn!(string_new_with_utf8_chars_and_len2);
335+
332336
ctor(
333337
string_ptr,
334338
bytes.as_ptr() as *const std::ffi::c_char,

godot-core/src/classes/class_runtime.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -170,11 +170,15 @@ pub(crate) fn construct_engine_object<T>() -> Gd<T>
170170
where
171171
T: GodotClass + Bounds<Declarer = bounds::DeclEngine>,
172172
{
173-
// SAFETY: adhere to Godot API; valid class name and returned pointer is an object.
174-
unsafe {
175-
let object_ptr = sys::interface_fn!(classdb_construct_object)(T::class_name().string_sys());
176-
Gd::from_obj_sys(object_ptr)
173+
let mut obj = unsafe {
174+
let object_ptr = sys::classdb_construct_object(T::class_name().string_sys());
175+
Gd::<T>::from_obj_sys(object_ptr)
176+
};
177+
if cfg!(since_api = "4.4") {
178+
obj.upcast_object_mut()
179+
.notify(crate::classes::notify::ObjectNotification::POSTINITIALIZE);
177180
}
181+
obj
178182
}
179183

180184
pub(crate) fn ensure_object_alive(

godot-core/src/obj/base.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,15 +166,15 @@ impl<T: GodotClass> Base<T> {
166166
(*self.obj).clone()
167167
}
168168

169-
/// Returns a [`Gd`] referencing the base object, for exclusive use during object initialization.
169+
/// Returns a [`Gd`] referencing the base object, for exclusive use during object initialization and `NOTIFICATION_POSTINITIALIZE`.
170170
///
171-
/// Can be used during an initialization function [`I*::init()`][crate::classes::IObject::init] or [`Gd::from_init_fn()`].
171+
/// Can be used during an initialization function [`I*::init()`][crate::classes::IObject::init] or [`Gd::from_init_fn()`], or [`POSTINITIALIZE`][crate::classes::notify::ObjectNotification::POSTINITIALIZE].
172172
///
173173
/// The base pointer is only pointing to a base object; you cannot yet downcast it to the object being constructed.
174174
/// The instance ID is the same as the one the in-construction object will have.
175175
///
176176
/// # Lifecycle for ref-counted classes
177-
/// If `T: Inherits<RefCounted>`, then the ref-counted object is not yet fully-initialized at the time of the `init` function running.
177+
/// If `T: Inherits<RefCounted>`, then the ref-counted object is not yet fully-initialized at the time of the `init` function and [`POSTINITIALIZE`][crate::classes::notify::ObjectNotification::POSTINITIALIZE] running.
178178
/// Accessing the base object without further measures would be dangerous. Here, godot-rust employs a workaround: the `Base` object (which
179179
/// holds a weak pointer to the actual instance) is temporarily upgraded to a strong pointer, preventing use-after-free.
180180
///

godot-core/src/obj/bounds.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -398,11 +398,7 @@ impl Declarer for DeclEngine {
398398
where
399399
T: GodotDefault + Bounds<Declarer = Self>,
400400
{
401-
unsafe {
402-
let object_ptr =
403-
sys::interface_fn!(classdb_construct_object)(T::class_name().string_sys());
404-
Gd::from_obj_sys(object_ptr)
405-
}
401+
crate::classes::construct_engine_object()
406402
}
407403
}
408404

godot-core/src/obj/gd.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ where
155155
where
156156
F: FnOnce(crate::obj::Base<T::Base>) -> T,
157157
{
158-
let object_ptr = callbacks::create_custom(init) // or propagate panic.
158+
let object_ptr = callbacks::create_custom(init, true) // or propagate panic.
159159
.unwrap_or_else(|payload| PanicPayload::repanic(payload));
160160

161161
unsafe { Gd::from_obj_sys(object_ptr) }
@@ -207,6 +207,10 @@ where
207207

208208
/// _The methods in this impl block are available for any `T`._ <br><br>
209209
impl<T: GodotClass> Gd<T> {
210+
pub(crate) fn drop_weak(self) {
211+
std::mem::forget(self);
212+
}
213+
210214
/// Looks up the given instance ID and returns the associated object, if possible.
211215
///
212216
/// If no such instance ID is registered, or if the dynamic type of the object behind that instance ID

godot-core/src/registry/callbacks.rs

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,18 @@ pub unsafe extern "C" fn create<T: cap::GodotDefault>(
3535
_class_userdata: *mut std::ffi::c_void,
3636
_notify_postinitialize: sys::GDExtensionBool,
3737
) -> sys::GDExtensionObjectPtr {
38-
create_custom(T::__godot_user_init).unwrap_or(std::ptr::null_mut())
38+
create_custom(
39+
T::__godot_user_init,
40+
sys::conv::bool_from_sys(_notify_postinitialize),
41+
)
42+
.unwrap_or(std::ptr::null_mut())
3943
}
4044

4145
#[cfg(before_api = "4.4")]
4246
pub unsafe extern "C" fn create<T: cap::GodotDefault>(
4347
_class_userdata: *mut std::ffi::c_void,
4448
) -> sys::GDExtensionObjectPtr {
45-
create_custom(T::__godot_user_init).unwrap_or(std::ptr::null_mut())
49+
create_custom(T::__godot_user_init, true).unwrap_or(std::ptr::null_mut())
4650
}
4751

4852
/// Workaround for <https://github.com/godot-rust/gdext/issues/874> before Godot 4.5.
@@ -71,7 +75,7 @@ pub unsafe extern "C" fn recreate<T: cap::GodotDefault>(
7175
_class_userdata: *mut std::ffi::c_void,
7276
object: sys::GDExtensionObjectPtr,
7377
) -> sys::GDExtensionClassInstancePtr {
74-
create_rust_part_for_existing_godot_part(T::__godot_user_init, object)
78+
create_rust_part_for_existing_godot_part(T::__godot_user_init, object, |_| {})
7579
.unwrap_or(std::ptr::null_mut())
7680
}
7781

@@ -88,6 +92,7 @@ pub unsafe extern "C" fn recreate_null<T>(
8892

8993
pub(crate) fn create_custom<T, F>(
9094
make_user_instance: F,
95+
notify_postinitialize: bool,
9196
) -> Result<sys::GDExtensionObjectPtr, PanicPayload>
9297
where
9398
T: GodotClass,
@@ -96,7 +101,17 @@ where
96101
let base_class_name = T::Base::class_name();
97102
let base_ptr = unsafe { interface_fn!(classdb_construct_object)(base_class_name.string_sys()) };
98103

99-
match create_rust_part_for_existing_godot_part(make_user_instance, base_ptr) {
104+
let postinit = |base_ptr| {
105+
#[cfg(since_api = "4.4")]
106+
if notify_postinitialize {
107+
// Should notify it with a weak pointer, during `NOTIFICATION_POSTINITIALIZE`, ref-counted object is not yet fully-initialized.
108+
let mut obj = unsafe { Gd::<Object>::from_obj_sys_weak(base_ptr) };
109+
obj.notify(crate::classes::notify::ObjectNotification::POSTINITIALIZE);
110+
obj.drop_weak();
111+
}
112+
};
113+
114+
match create_rust_part_for_existing_godot_part(make_user_instance, base_ptr, postinit) {
100115
Ok(_extension_ptr) => Ok(base_ptr),
101116
Err(payload) => {
102117
// Creation of extension object failed; we must now also destroy the base object to avoid leak.
@@ -115,13 +130,15 @@ where
115130
/// With godot-rust, custom objects consist of two parts: the Godot object and the Rust object. This method takes the Godot part by pointer,
116131
/// creates the Rust part with the supplied state, and links them together. This is used for both brand-new object creation and hot reload.
117132
/// During hot reload, Rust objects are disposed of and then created again with updated code, so it's necessary to re-link them to Godot objects.
118-
fn create_rust_part_for_existing_godot_part<T, F>(
133+
fn create_rust_part_for_existing_godot_part<T, F, P>(
119134
make_user_instance: F,
120135
base_ptr: sys::GDExtensionObjectPtr,
136+
postinit: P,
121137
) -> Result<sys::GDExtensionClassInstancePtr, PanicPayload>
122138
where
123139
T: GodotClass,
124140
F: FnOnce(Base<T::Base>) -> T,
141+
P: Fn(sys::GDExtensionObjectPtr),
125142
{
126143
let class_name = T::class_name();
127144
//out!("create callback: {}", class_name.backing);
@@ -153,6 +170,8 @@ where
153170
);
154171
}
155172

173+
postinit(base_ptr);
174+
156175
// Mark initialization as complete, now that user constructor has finished.
157176
base_copy.mark_initialized();
158177

godot-ffi/src/lib.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,17 @@ pub unsafe fn discover_main_thread() {
471471
}
472472
}
473473

474+
/// # Safety
475+
/// `class_name` is assumed to be valid.
476+
pub unsafe fn classdb_construct_object(
477+
class_name: GDExtensionConstStringNamePtr,
478+
) -> GDExtensionObjectPtr {
479+
#[cfg(before_api = "4.4")]
480+
return interface_fn!(classdb_construct_object)(class_name);
481+
#[cfg(since_api = "4.4")]
482+
return interface_fn!(classdb_construct_object2)(class_name);
483+
}
484+
474485
// ----------------------------------------------------------------------------------------------------------------------------------------------
475486
// Macros to access low-level function bindings
476487

itest/rust/src/object_tests/base_init_test.rs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@
77

88
use godot::builtin::real_consts::FRAC_PI_3;
99
use godot::builtin::Vector2;
10-
use godot::classes::{ClassDb, RefCounted};
11-
use godot::obj::{Gd, InstanceId, NewAlloc, NewGd, WithBaseField};
10+
use godot::classes::notify::ObjectNotification;
11+
use godot::classes::{ClassDb, IRefCounted, RefCounted};
12+
use godot::meta::ToGodot;
13+
use godot::obj::{Base, Gd, InstanceId, NewAlloc, NewGd, WithBaseField};
14+
use godot::register::{godot_api, GodotClass};
1215
use godot::task::TaskHandle;
1316

1417
use crate::framework::{expect_panic, itest, next_frame};
@@ -168,3 +171,27 @@ fn base_init_to_gd() {
168171
});
169172
});
170173
}
174+
175+
#[derive(GodotClass)]
176+
#[class(init)]
177+
struct RefcPostinit {
178+
pub base: Base<RefCounted>,
179+
}
180+
181+
#[godot_api]
182+
impl IRefCounted for RefcPostinit {
183+
fn on_notification(&mut self, what: ObjectNotification) {
184+
if what == ObjectNotification::POSTINITIALIZE {
185+
self.base
186+
.to_init_gd()
187+
.set_meta("meta", &"postinited".to_variant());
188+
}
189+
}
190+
}
191+
192+
#[cfg(since_api = "4.4")]
193+
#[itest]
194+
fn base_postinit_refcounted() {
195+
let obj = RefcPostinit::new_gd();
196+
assert_eq!(obj.get_meta("meta"), "postinited".to_variant())
197+
}

itest/rust/src/object_tests/virtual_methods_test.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,8 @@ fn test_notifications() {
520520
assert_eq!(
521521
obj.bind().sequence,
522522
vec![
523+
#[cfg(since_api = "4.4")]
524+
ReceivedEvent::Notification(NodeNotification::POSTINITIALIZE),
523525
ReceivedEvent::Notification(NodeNotification::UNPAUSED),
524526
ReceivedEvent::Notification(NodeNotification::EDITOR_POST_SAVE),
525527
ReceivedEvent::Ready,

0 commit comments

Comments
 (0)