Skip to content

Commit 04af742

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

File tree

9 files changed

+87
-16
lines changed

9 files changed

+87
-16
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 & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,10 +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.
174173
unsafe {
175-
let object_ptr = sys::interface_fn!(classdb_construct_object)(T::class_name().string_sys());
176-
Gd::from_obj_sys(object_ptr)
174+
let object_ptr = sys::classdb_construct_object(T::class_name().string_sys());
175+
let obj = Gd::<T>::from_obj_sys(object_ptr);
176+
if cfg!(since_api = "4.4") {
177+
obj.clone()
178+
.upcast_object()
179+
.notify(crate::classes::notify::ObjectNotification::POSTINITIALIZE);
180+
}
181+
obj
177182
}
178183
}
179184

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: 1 addition & 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) }

godot-core/src/registry/callbacks.rs

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ use crate::private::{handle_panic, PanicPayload};
2525
use crate::registry::plugin::ErasedDynGd;
2626
use crate::storage::{as_storage, InstanceStorage, Storage, StorageRefCounted};
2727

28+
#[cfg(since_api = "4.4")]
29+
unsafe fn notify_postinit(base_ptr: sys::GDExtensionObjectPtr) {
30+
// Should notify it with a weak pointer, during `NOTIFICATION_POSTINITIALIZE`, ref-counted object is not yet fully-initialized.
31+
let mut obj = Gd::<Object>::from_obj_sys_weak(base_ptr);
32+
obj.notify(crate::classes::notify::ObjectNotification::POSTINITIALIZE);
33+
std::mem::forget(obj);
34+
}
35+
2836
/// Godot FFI default constructor.
2937
///
3038
/// If the `init()` constructor panics, null is returned.
@@ -35,14 +43,18 @@ pub unsafe extern "C" fn create<T: cap::GodotDefault>(
3543
_class_userdata: *mut std::ffi::c_void,
3644
_notify_postinitialize: sys::GDExtensionBool,
3745
) -> sys::GDExtensionObjectPtr {
38-
create_custom(T::__godot_user_init).unwrap_or(std::ptr::null_mut())
46+
create_custom(
47+
T::__godot_user_init,
48+
sys::conv::bool_from_sys(_notify_postinitialize),
49+
)
50+
.unwrap_or(std::ptr::null_mut())
3951
}
4052

4153
#[cfg(before_api = "4.4")]
4254
pub unsafe extern "C" fn create<T: cap::GodotDefault>(
4355
_class_userdata: *mut std::ffi::c_void,
4456
) -> sys::GDExtensionObjectPtr {
45-
create_custom(T::__godot_user_init).unwrap_or(std::ptr::null_mut())
57+
create_custom(T::__godot_user_init, true).unwrap_or(std::ptr::null_mut())
4658
}
4759

4860
/// Workaround for <https://github.com/godot-rust/gdext/issues/874> before Godot 4.5.
@@ -71,7 +83,7 @@ pub unsafe extern "C" fn recreate<T: cap::GodotDefault>(
7183
_class_userdata: *mut std::ffi::c_void,
7284
object: sys::GDExtensionObjectPtr,
7385
) -> sys::GDExtensionClassInstancePtr {
74-
create_rust_part_for_existing_godot_part(T::__godot_user_init, object)
86+
create_rust_part_for_existing_godot_part(T::__godot_user_init, object, None)
7587
.unwrap_or(std::ptr::null_mut())
7688
}
7789

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

89101
pub(crate) fn create_custom<T, F>(
90102
make_user_instance: F,
103+
notify_postinitialize: bool,
91104
) -> Result<sys::GDExtensionObjectPtr, PanicPayload>
92105
where
93106
T: GodotClass,
@@ -96,7 +109,16 @@ where
96109
let base_class_name = T::Base::class_name();
97110
let base_ptr = unsafe { interface_fn!(classdb_construct_object)(base_class_name.string_sys()) };
98111

99-
match create_rust_part_for_existing_godot_part(make_user_instance, base_ptr) {
112+
#[cfg(before_api = "4.4")]
113+
let postinit = None;
114+
#[cfg(since_api = "4.4")]
115+
let postinit = if notify_postinitialize {
116+
Some(notify_postinit as unsafe fn(sys::GDExtensionObjectPtr))
117+
} else {
118+
None
119+
};
120+
121+
match create_rust_part_for_existing_godot_part(make_user_instance, base_ptr, postinit) {
100122
Ok(_extension_ptr) => Ok(base_ptr),
101123
Err(payload) => {
102124
// Creation of extension object failed; we must now also destroy the base object to avoid leak.
@@ -118,6 +140,7 @@ where
118140
fn create_rust_part_for_existing_godot_part<T, F>(
119141
make_user_instance: F,
120142
base_ptr: sys::GDExtensionObjectPtr,
143+
postinit: Option<unsafe fn(sys::GDExtensionObjectPtr)>,
121144
) -> Result<sys::GDExtensionClassInstancePtr, PanicPayload>
122145
where
123146
T: GodotClass,
@@ -153,6 +176,10 @@ where
153176
);
154177
}
155178

179+
if let Some(postinit) = postinit {
180+
unsafe { postinit(base_ptr) };
181+
}
182+
156183
// Mark initialization as complete, now that user constructor has finished.
157184
base_copy.mark_initialized();
158185

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: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77

88
use godot::builtin::real_consts::FRAC_PI_3;
99
use godot::builtin::Vector2;
10+
use godot::classes::notify::ObjectNotification;
1011
use godot::classes::{ClassDb, RefCounted};
1112
use godot::obj::{Gd, InstanceId, NewAlloc, NewGd, WithBaseField};
13+
use godot::prelude::*;
1214
use godot::task::TaskHandle;
1315

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

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)