Skip to content

Commit 4737427

Browse files
authored
Merge pull request #1211 from beicause/update-postinit
Emit `POSTINITIALIZE` notification after `init()`
2 parents 8526478 + f9e7576 commit 4737427

File tree

9 files changed

+100
-22
lines changed

9 files changed

+100
-22
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: 9 additions & 5 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)
177-
}
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+
#[cfg(since_api = "4.4")]
178+
obj.upcast_object_mut()
179+
.notify(crate::classes::notify::ObjectNotification::POSTINITIALIZE);
180+
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: 9 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) }
@@ -335,6 +335,14 @@ impl<T: GodotClass> Gd<T> {
335335
Some(rc.map(|i| i as usize))
336336
}
337337

338+
/// Drop without decrementing ref-counter.
339+
///
340+
/// Needed in situations where the instance should effectively be forgotten, but without leaking other associated data.
341+
pub(crate) fn drop_weak(self) {
342+
// As soon as fields need custom Drop, this won't be enough anymore.
343+
std::mem::forget(self);
344+
}
345+
338346
#[cfg(feature = "trace")] // itest only.
339347
#[doc(hidden)]
340348
pub fn test_refcount(&self) -> Option<usize> {

godot-core/src/registry/callbacks.rs

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,19 @@ 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+
// `notify_postinitialize` doesn't matter before 4.4, it's sent by Godot when constructing object and we don't send it.
50+
create_custom(T::__godot_user_init, true).unwrap_or(std::ptr::null_mut())
4651
}
4752

4853
/// Workaround for <https://github.com/godot-rust/gdext/issues/874> before Godot 4.5.
@@ -71,7 +76,7 @@ pub unsafe extern "C" fn recreate<T: cap::GodotDefault>(
7176
_class_userdata: *mut std::ffi::c_void,
7277
object: sys::GDExtensionObjectPtr,
7378
) -> sys::GDExtensionClassInstancePtr {
74-
create_rust_part_for_existing_godot_part(T::__godot_user_init, object)
79+
create_rust_part_for_existing_godot_part(T::__godot_user_init, object, |_| {})
7580
.unwrap_or(std::ptr::null_mut())
7681
}
7782

@@ -88,15 +93,26 @@ pub unsafe extern "C" fn recreate_null<T>(
8893

8994
pub(crate) fn create_custom<T, F>(
9095
make_user_instance: F,
96+
notify_postinitialize: bool,
9197
) -> Result<sys::GDExtensionObjectPtr, PanicPayload>
9298
where
9399
T: GodotClass,
94100
F: FnOnce(Base<T::Base>) -> T,
95101
{
96102
let base_class_name = T::Base::class_name();
97-
let base_ptr = unsafe { interface_fn!(classdb_construct_object)(base_class_name.string_sys()) };
103+
let base_ptr = unsafe { sys::classdb_construct_object(base_class_name.string_sys()) };
104+
105+
let postinit = |base_ptr| {
106+
#[cfg(since_api = "4.4")]
107+
if notify_postinitialize {
108+
// Should notify it with a weak pointer, during `NOTIFICATION_POSTINITIALIZE`, ref-counted object is not yet fully-initialized.
109+
let mut obj = unsafe { Gd::<Object>::from_obj_sys_weak(base_ptr) };
110+
obj.notify(crate::classes::notify::ObjectNotification::POSTINITIALIZE);
111+
obj.drop_weak();
112+
}
113+
};
98114

99-
match create_rust_part_for_existing_godot_part(make_user_instance, base_ptr) {
115+
match create_rust_part_for_existing_godot_part(make_user_instance, base_ptr, postinit) {
100116
Ok(_extension_ptr) => Ok(base_ptr),
101117
Err(payload) => {
102118
// Creation of extension object failed; we must now also destroy the base object to avoid leak.
@@ -115,13 +131,15 @@ where
115131
/// With godot-rust, custom objects consist of two parts: the Godot object and the Rust object. This method takes the Godot part by pointer,
116132
/// creates the Rust part with the supplied state, and links them together. This is used for both brand-new object creation and hot reload.
117133
/// 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>(
134+
fn create_rust_part_for_existing_godot_part<T, F, P>(
119135
make_user_instance: F,
120136
base_ptr: sys::GDExtensionObjectPtr,
137+
postinit: P,
121138
) -> Result<sys::GDExtensionClassInstancePtr, PanicPayload>
122139
where
123140
T: GodotClass,
124141
F: FnOnce(Base<T::Base>) -> T,
142+
P: Fn(sys::GDExtensionObjectPtr),
125143
{
126144
let class_name = T::class_name();
127145
//out!("create callback: {}", class_name.backing);
@@ -153,6 +171,8 @@ where
153171
);
154172
}
155173

174+
postinit(base_ptr);
175+
156176
// Mark initialization as complete, now that user constructor has finished.
157177
base_copy.mark_initialized();
158178

godot-ffi/src/lib.rs

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

474+
/// Construct Godot object.
475+
///
476+
/// "NOTIFICATION_POSTINITIALIZE" must be sent after construction since 4.4.
477+
///
478+
/// # Safety
479+
/// `class_name` is assumed to be valid.
480+
pub unsafe fn classdb_construct_object(
481+
class_name: GDExtensionConstStringNamePtr,
482+
) -> GDExtensionObjectPtr {
483+
#[cfg(before_api = "4.4")]
484+
return interface_fn!(classdb_construct_object)(class_name);
485+
#[cfg(since_api = "4.4")]
486+
return interface_fn!(classdb_construct_object2)(class_name);
487+
}
488+
474489
// ----------------------------------------------------------------------------------------------------------------------------------------------
475490
// Macros to access low-level function bindings
476491

itest/rust/src/object_tests/base_init_test.rs

Lines changed: 31 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,29 @@ 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(async)]
194+
fn base_postinit_refcounted() -> TaskHandle {
195+
let obj = RefcPostinit::new_gd();
196+
assert_eq!(obj.get_meta("meta"), "postinited".to_variant());
197+
assert_eq!(obj.get_reference_count(), 2);
198+
next_frame(move || assert_eq!(obj.get_reference_count(), 1, "eventual dec-ref happens"))
199+
}

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)