Skip to content

Commit 0c9fdf4

Browse files
committed
Make base_mut not clone Gd and work in init and predelete
1 parent c4051c0 commit 0c9fdf4

File tree

7 files changed

+116
-55
lines changed

7 files changed

+116
-55
lines changed

godot-core/src/classes/class_runtime.rs

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -216,30 +216,6 @@ pub(crate) fn ensure_object_inherits(derived: ClassId, base: ClassId, instance_i
216216
)
217217
}
218218

219-
#[cfg(debug_assertions)]
220-
pub(crate) fn ensure_binding_not_null<T>(binding: sys::GDExtensionClassInstancePtr)
221-
where
222-
T: GodotClass + Bounds<Declarer = bounds::DeclUser>,
223-
{
224-
if !binding.is_null() {
225-
return;
226-
}
227-
228-
// Non-tool classes can't be instantiated in the editor.
229-
if crate::classes::Engine::singleton().is_editor_hint() {
230-
panic!(
231-
"Class {} -- null instance; does the class have a Godot creator function? \
232-
Ensure that the given class is a tool class with #[class(tool)], if it is being accessed in the editor.",
233-
std::any::type_name::<T>()
234-
)
235-
} else {
236-
panic!(
237-
"Class {} -- null instance; does the class have a Godot creator function?",
238-
std::any::type_name::<T>()
239-
);
240-
}
241-
}
242-
243219
// ----------------------------------------------------------------------------------------------------------------------------------------------
244220
// Implementation of this file
245221

godot-core/src/obj/base.rs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ impl<T: GodotClass> Base<T> {
320320

321321
/// Returns `true` if this `Base<T>` is currently in the initializing state.
322322
#[cfg(debug_assertions)]
323-
fn is_initializing(&self) -> bool {
323+
pub(crate) fn is_initializing(&self) -> bool {
324324
self.init_state.get() == InitState::ObjectConstructing
325325
}
326326

@@ -344,12 +344,6 @@ impl<T: GodotClass> Base<T> {
344344
/// # Safety
345345
/// Caller must ensure that the underlying object remains valid for the entire lifetime of the returned `PassiveGd`.
346346
pub(crate) unsafe fn constructed_passive(&self) -> PassiveGd<T> {
347-
#[cfg(debug_assertions)] // debug_assert! still checks existence of symbols.
348-
assert!(
349-
!self.is_initializing(),
350-
"WithBaseField::base(), base_mut() can only be called on fully-constructed objects, after I*::init() or Gd::from_init_fn()"
351-
);
352-
353347
// SAFETY: object pointer is valid and remains valid as long as self is alive (per safety precondition of this fn).
354348
unsafe { PassiveGd::from_obj_sys(self.obj.obj_sys()) }
355349
}

godot-core/src/obj/guards.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,13 +232,14 @@ macro_rules! make_base_mut {
232232
#[doc = concat!("See [`", stringify!($doc_type), "::base_mut()`](", stringify!($doc_path), "::base_mut()) for usage.\n")]
233233
pub struct $ident<'a, T: $bound> {
234234
passive_gd: PassiveGd<T::Base>,
235-
_inaccessible_guard: InaccessibleGuard<'a, T>,
235+
// Option because we can't make a guard yet since the instance storage is null during initializing.
236+
_inaccessible_guard: Option<InaccessibleGuard<'a, T>>,
236237
}
237238

238239
impl<'a, T: $bound> $ident<'a, T> {
239240
pub(crate) fn new(
240241
passive_gd: PassiveGd<T::Base>,
241-
inaccessible_guard: InaccessibleGuard<'a, T>,
242+
inaccessible_guard: Option<InaccessibleGuard<'a, T>>,
242243
) -> Self {
243244
Self {
244245
passive_gd,

godot-core/src/obj/raw_gd.rs

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,12 @@ where
440440
/// Storage object associated with the extension instance.
441441
///
442442
/// Returns `None` if self is null.
443+
///
444+
/// # Panics
445+
/// In Debug mode, if binding is null.
443446
pub(crate) fn storage(&self) -> Option<&InstanceStorage<T>> {
447+
#[cfg(debug_assertions)]
448+
self.ensure_storage_not_null();
444449
// SAFETY:
445450
// - We have a `&self`, so the storage must already have been created.
446451
// - The storage cannot be destroyed while we have a `&self` reference, so it will not be
@@ -450,7 +455,7 @@ where
450455

451456
/// Storage object associated with the extension instance.
452457
///
453-
/// Returns `None` if self is null.
458+
/// Returns `None` if self is null, or if binding is null.
454459
///
455460
/// # Safety
456461
///
@@ -474,18 +479,36 @@ where
474479
}
475480
}
476481

482+
#[cfg(debug_assertions)]
483+
pub(crate) fn ensure_storage_not_null(&self) {
484+
let binding = self.resolve_instance_ptr();
485+
if !binding.is_null() {
486+
return;
487+
}
488+
// Non-tool classes can't be instantiated in the editor.
489+
if crate::classes::Engine::singleton().is_editor_hint() {
490+
panic!(
491+
"Class {} -- null instance; does the class have a Godot creator function? \
492+
Ensure that the given class is a tool class with #[class(tool)], if it is being accessed in the editor.",
493+
std::any::type_name::<Self>()
494+
)
495+
} else {
496+
panic!(
497+
"Class {} -- null instance; does the class have a Godot creator function?",
498+
std::any::type_name::<Self>()
499+
);
500+
}
501+
}
502+
477503
/// Retrieves and caches pointer to this class instance if `self.obj` is non-null.
478-
/// Returns a null pointer otherwise.
504+
/// Returns a null pointer otherwise, or if binding is null.
479505
///
480506
/// Note: The returned pointer to the GDExtensionClass instance (even when `self.obj` is non-null)
481507
/// might still be null when:
482508
/// - The class isn't instantiable in the current context.
483509
/// - The instance is a placeholder (e.g., non-`tool` classes in the editor).
484510
///
485511
/// However, null pointers might also occur in other, undocumented contexts.
486-
///
487-
/// # Panics
488-
/// In Debug mode, if binding is null.
489512
fn resolve_instance_ptr(&self) -> sys::GDExtensionClassInstancePtr {
490513
if self.is_null() {
491514
return ptr::null_mut();
@@ -509,8 +532,9 @@ where
509532

510533
let ptr: sys::GDExtensionClassInstancePtr = binding.cast();
511534

512-
#[cfg(debug_assertions)]
513-
crate::classes::ensure_binding_not_null::<T>(ptr);
535+
if ptr.is_null() {
536+
return ptr::null_mut();
537+
}
514538

515539
self.cached_storage_ptr.set(ptr);
516540
ptr

godot-core/src/obj/script.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,7 @@ impl<'a, T: ScriptInstance> SiMut<'a, T> {
503503
let guard = self.cell.make_inaccessible(self.mut_ref).unwrap();
504504
let passive_gd = self.base_ref.to_script_passive();
505505

506-
ScriptBaseMut::new(passive_gd, guard)
506+
ScriptBaseMut::new(passive_gd, Some(guard))
507507
}
508508
}
509509

godot-core/src/obj/traits.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::builtin::GString;
1212
use crate::init::InitLevel;
1313
use crate::meta::inspect::EnumConstant;
1414
use crate::meta::ClassId;
15-
use crate::obj::{bounds, Base, BaseMut, BaseRef, Bounds, Gd};
15+
use crate::obj::{bounds, Base, BaseMut, BaseRef, Bounds, Gd, PassiveGd};
1616
use crate::registry::signal::SignalObject;
1717
use crate::storage::Storage;
1818

@@ -516,26 +516,26 @@ pub trait WithBaseField: GodotClass + Bounds<Declarer = bounds::DeclUser> {
516516
// We need to construct this first, as the mut-borrow below will block all other access.
517517
// SAFETY: lifetime is re-established at the bottom BaseMut construction, since return type of this fn has lifetime bound to instance.
518518
let passive_gd = unsafe { self.base_field().constructed_passive() };
519+
// SAFETY: The object ptr of `Gd<Self::Base>` is guaranteed to be valid `Gd<Self>`.
520+
let gd = unsafe { PassiveGd::from_obj_sys(passive_gd.obj_sys()) };
519521

520-
let gd = self.to_gd();
522+
// If the object is fully constructed, the associated instance storage must have been created.
523+
#[cfg(debug_assertions)]
524+
if !self.base_field().is_initializing() {
525+
gd.raw.ensure_storage_not_null();
526+
}
521527

522528
// SAFETY:
523-
// - We have a `Gd<Self>` so, provided that `storage_unbounded` succeeds, the associated instance
524-
// storage has been created.
529+
// - `storage_unbounded` will succeed if the object is fully constructed, otherwise it returns None.
525530
//
526531
// - Since we can get a `&'a Base<Self::Base>` from `&'a self`, that must mean we have a Rust object
527532
// somewhere that has this base object. The only way to have such a base object is by being the
528533
// Rust object referenced by that base object. I.e. this storage's user-instance is that Rust
529534
// object. That means this storage cannot be destroyed for the lifetime of that Rust object. And
530535
// since we have a reference to the base object derived from that Rust object, then that Rust
531536
// object must outlive `'a`. And so the storage cannot be destroyed during the lifetime `'a`.
532-
let storage = unsafe {
533-
gd.raw
534-
.storage_unbounded()
535-
.expect("we have Gd<Self>; its RawGd should not be null")
536-
};
537-
538-
let guard = storage.get_inaccessible(self);
537+
let storage = unsafe { gd.raw.storage_unbounded() };
538+
let guard = storage.map(|s| s.get_inaccessible(self));
539539

540540
// Narrows lifetime again from 'static to 'self.
541541
BaseMut::new(passive_gd, guard)

itest/rust/src/object_tests/base_init_test.rs

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
*/
77

88
use godot::builtin::real_consts::FRAC_PI_3;
9-
use godot::builtin::Vector2;
9+
use godot::builtin::{Array, PackedArray, Variant, Vector2};
1010
use godot::classes::notify::ObjectNotification;
11-
use godot::classes::{ClassDb, IRefCounted, RefCounted};
11+
use godot::classes::{mesh, ArrayMesh, ClassDb, IArrayMesh, IRefCounted, RefCounted};
1212
use godot::meta::ToGodot;
13-
use godot::obj::{Base, Gd, InstanceId, NewAlloc, NewGd, WithBaseField};
13+
use godot::obj::{Base, Gd, IndexEnum, InstanceId, NewAlloc, NewGd, WithBaseField};
1414
use godot::register::{godot_api, GodotClass};
1515
use godot::task::TaskHandle;
1616

@@ -197,3 +197,69 @@ fn base_postinit_refcounted() -> TaskHandle {
197197
assert_eq!(obj.get_reference_count(), 2);
198198
next_frame(move || assert_eq!(obj.get_reference_count(), 1, "eventual dec-ref happens"))
199199
}
200+
201+
fn make_mesh_arrays() -> Array<Variant> {
202+
let mut arrays = Array::new();
203+
arrays.resize(mesh::ArrayType::ENUMERATOR_COUNT, &Variant::nil());
204+
let indices = PackedArray::<i32>::from([0, 1, 2]);
205+
let vertex = PackedArray::<Vector2>::from([
206+
Vector2::new(0.0, 0.0),
207+
Vector2::new(1.0, 0.0),
208+
Vector2::new(0.0, 1.0),
209+
]);
210+
arrays.set(mesh::ArrayType::INDEX.to_index(), &indices.to_variant());
211+
arrays.set(mesh::ArrayType::VERTEX.to_index(), &vertex.to_variant());
212+
arrays
213+
}
214+
215+
#[derive(GodotClass)]
216+
#[class(base=ArrayMesh)]
217+
struct InitArrayMeshTest {
218+
base: Base<ArrayMesh>,
219+
}
220+
221+
#[rustfmt::skip]
222+
#[godot_api]
223+
impl IArrayMesh for InitArrayMeshTest {
224+
fn init(base: Base<ArrayMesh>) -> Self {
225+
let mut sf = Self { base };
226+
sf.base_mut()
227+
.add_surface_from_arrays(mesh::PrimitiveType::TRIANGLES, &make_mesh_arrays());
228+
sf
229+
}
230+
231+
fn on_notification(&mut self, what: ObjectNotification) {
232+
if what == ObjectNotification::PREDELETE {
233+
let arr = make_mesh_arrays();
234+
self.base_mut().add_surface_from_arrays(mesh::PrimitiveType::TRIANGLES, &arr);
235+
236+
assert_eq!(self.base().get_surface_count(), 2);
237+
assert_eq!(self.base().surface_get_arrays(0), arr);
238+
assert_eq!(self.base().surface_get_arrays(1), arr);
239+
}
240+
}
241+
242+
fn get_surface_count(&self) -> i32 { unreachable!(); }
243+
fn surface_get_array_len(&self, _index: i32) -> i32 { unreachable!(); }
244+
fn surface_get_array_index_len(&self, _index: i32) -> i32 { unreachable!(); }
245+
fn surface_get_arrays(&self, _index: i32) -> Array<Variant> { unreachable!(); }
246+
fn surface_get_blend_shape_arrays(&self, _index: i32) -> Array<Array<Variant>> { unreachable!(); }
247+
fn surface_get_lods(&self, _index: i32) -> godot::builtin::Dictionary { unreachable!(); }
248+
fn surface_get_format(&self, _index: i32) -> u32 { unreachable!(); }
249+
fn surface_get_primitive_type(&self, _index: i32) -> u32 { unreachable!(); }
250+
#[cfg(feature = "codegen-full")]
251+
fn surface_set_material(&mut self, _index: i32, _material: Option<Gd<godot::classes::Material>>) { unreachable!(); }
252+
#[cfg(feature = "codegen-full")]
253+
fn surface_get_material(&self, _index: i32) -> Option<Gd<godot::classes::Material>> { unreachable!(); }
254+
fn get_blend_shape_count(&self) -> i32 { unreachable!(); }
255+
fn get_blend_shape_name(&self, _index: i32) -> godot::builtin::StringName { unreachable!(); }
256+
fn set_blend_shape_name(&mut self, _index: i32, _name: godot::builtin::StringName){ unreachable!(); }
257+
fn get_aabb(&self) -> godot::builtin::Aabb { unreachable!(); }
258+
}
259+
260+
#[itest]
261+
fn base_mut_init_array_mesh() {
262+
let mesh = InitArrayMeshTest::new_gd();
263+
assert_eq!(mesh.get_surface_count(), 1);
264+
assert_eq!(mesh.surface_get_arrays(0), make_mesh_arrays());
265+
}

0 commit comments

Comments
 (0)