Skip to content

Commit 4dd712c

Browse files
committed
Allow Deref/DerefMut for all Gd<T>; remove Gd::base/base_mut() again
More uniform and should be safe.
1 parent c7561e7 commit 4dd712c

File tree

9 files changed

+98
-109
lines changed

9 files changed

+98
-109
lines changed

examples/dodge-the-creeps/rust/src/main_scene.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,13 +100,12 @@ impl Main {
100100
rng.gen_range(mob.min_speed..mob.max_speed)
101101
};
102102

103-
let mut mob = mob.base_mut();
104103
mob.set_linear_velocity(Vector2::new(range, 0.0));
105104
let lin_vel = mob.get_linear_velocity().rotated(real::from_f32(direction));
106105
mob.set_linear_velocity(lin_vel);
107106

108107
let mut hud = self.base.get_node_as::<Hud>("Hud");
109-
hud.base_mut().connect(
108+
hud.connect(
110109
"start_game".into(),
111110
Callable::from_object_method(mob, "on_start_game"),
112111
);

godot-core/src/obj/gd.rs

Lines changed: 12 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -160,36 +160,6 @@ where
160160
GdMut::from_cell(self.storage().get_mut())
161161
}
162162

163-
/// Access base without locking the user object.
164-
///
165-
/// If you need mutations, use [`base_mut()`][Self::base_mut]. If you need a value copy, use [`base().share()`][Share::share].
166-
pub fn base(&self) -> &Gd<T::Base> {
167-
self.storage().base().deref()
168-
}
169-
170-
/// Access base mutably without locking the user object.
171-
///
172-
/// The idea is to support the `gd.base_mut().mutating_method()` pattern, which is not allowed with `gd.base()`.
173-
/// If you need a copy of a `Gd` object, use `base().share()`.
174-
///
175-
/// The return type is currently `Gd` and not `&mut Gd` because of `&mut` aliasing restrictions. This may change in the future.
176-
pub fn base_mut(&mut self) -> Gd<T::Base> {
177-
// Note: we cannot give safe mutable access to a base without RefCell, because multiple Gd pointers could call base_mut(),
178-
// leading to aliased &mut. And we don't want RefCell, as C++ objects (nodes etc.) don't underly Rust's exclusive-ref limitations.
179-
// The whole point of the Gd::base*() methods are to not require runtime-exclusive access to the Rust object.
180-
//
181-
// This is not a problem when accessing the `#[base]` field of a user struct directly, because `self` is guarded by the
182-
// RefCell/RwLock in the InstanceStorage.
183-
//
184-
// Here, we instead return a copy (for now), which for the user looks mostly the same. The idea is that:
185-
// - gd.base().mutating_method() fails
186-
// - gd.base_mut().mutating_method() works.
187-
//
188-
// Downside: small ref-counting overhead for `RefCounted` types. If this is an issue in a real game (highly unlikely),
189-
// the user could always cache Gd base pointers.
190-
self.storage().base().share()
191-
}
192-
193163
/// Storage object associated with the extension instance.
194164
pub(crate) fn storage(&self) -> &InstanceStorage<T> {
195165
// SAFETY: instance pointer belongs to this instance. We only get a shared reference, no exclusive access, so even
@@ -541,16 +511,16 @@ where
541511
}
542512
}
543513

544-
impl<T> Deref for Gd<T>
545-
where
546-
T: GodotClass<Declarer = dom::EngineDomain>,
547-
{
548-
type Target = T;
514+
impl<T: GodotClass> Deref for Gd<T> {
515+
// Target is always an engine class:
516+
// * if T is an engine class => T
517+
// * if T is a user class => T::Base
518+
type Target = <<T as GodotClass>::Declarer as dom::Domain>::DerefTarget<T>;
549519

550520
fn deref(&self) -> &Self::Target {
551521
// SAFETY:
552522
//
553-
// This relies on `Gd<Node3D>` having the layout as `Node3D` (as an example),
523+
// This relies on `Gd<Node3D>.opaque` having the layout as `Node3D` (as an example),
554524
// which also needs #[repr(transparent)]:
555525
//
556526
// struct Gd<T: GodotClass> {
@@ -560,22 +530,21 @@ where
560530
// struct Node3D {
561531
// object_ptr: sys::GDExtensionObjectPtr,
562532
// }
563-
unsafe { std::mem::transmute::<&OpaqueObject, &T>(&self.opaque) }
533+
unsafe { std::mem::transmute::<&OpaqueObject, &Self::Target>(&self.opaque) }
564534
}
565535
}
566536

567-
impl<T> DerefMut for Gd<T>
568-
where
569-
T: GodotClass<Declarer = dom::EngineDomain>,
570-
{
571-
fn deref_mut(&mut self) -> &mut T {
537+
impl<T: GodotClass> DerefMut for Gd<T> {
538+
fn deref_mut(&mut self) -> &mut Self::Target {
572539
// SAFETY: see also Deref
573540
//
574541
// The resulting `&mut T` is transmuted from `&mut OpaqueObject`, i.e. a *pointer* to the `opaque` field.
575542
// `opaque` itself has a different *address* for each Gd instance, meaning that two simultaneous
576543
// DerefMut borrows on two Gd instances will not alias, *even if* the underlying Godot object is the
577544
// same (i.e. `opaque` has the same value, but not address).
578-
unsafe { std::mem::transmute::<&mut OpaqueObject, &mut T>(&mut self.opaque) }
545+
//
546+
// The `&mut self` guarantees that no other base access can take place for *the same Gd instance* (access to other Gds is OK).
547+
unsafe { std::mem::transmute::<&mut OpaqueObject, &mut Self::Target>(&mut self.opaque) }
579548
}
580549
}
581550

godot-core/src/obj/traits.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,8 @@ pub mod dom {
260260

261261
/// Trait that specifies who declares a given `GodotClass`.
262262
pub trait Domain: Sealed {
263+
type DerefTarget<T: GodotClass>;
264+
263265
#[doc(hidden)]
264266
fn scoped_mut<T, F, R>(obj: &mut Gd<T>, closure: F) -> R
265267
where
@@ -271,6 +273,8 @@ pub mod dom {
271273
pub enum EngineDomain {}
272274
impl Sealed for EngineDomain {}
273275
impl Domain for EngineDomain {
276+
type DerefTarget<T: GodotClass> = T;
277+
274278
fn scoped_mut<T, F, R>(obj: &mut Gd<T>, closure: F) -> R
275279
where
276280
T: GodotClass<Declarer = EngineDomain>,
@@ -284,6 +288,8 @@ pub mod dom {
284288
pub enum UserDomain {}
285289
impl Sealed for UserDomain {}
286290
impl Domain for UserDomain {
291+
type DerefTarget<T: GodotClass> = T::Base;
292+
287293
fn scoped_mut<T, F, R>(obj: &mut Gd<T>, closure: F) -> R
288294
where
289295
T: GodotClass<Declarer = Self>,

godot-core/src/registry.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -311,10 +311,7 @@ pub mod callbacks {
311311
let base = unsafe { Base::from_sys(base_ptr) };
312312
let user_instance = make_user_instance(base);
313313

314-
// Create 2nd base to cache inside storage. This one will remain weak forever (no action on destruction).
315-
let cached_base = unsafe { Base::from_sys(base_ptr) };
316-
317-
let instance = InstanceStorage::<T>::construct(user_instance, cached_base);
314+
let instance = InstanceStorage::<T>::construct(user_instance);
318315
let instance_ptr = instance.into_raw();
319316
let instance_ptr = instance_ptr as sys::GDExtensionClassInstancePtr;
320317

godot-core/src/storage.rs

Lines changed: 58 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,31 +12,31 @@ use std::any::type_name;
1212

1313
#[derive(Copy, Clone, Debug)]
1414
pub enum Lifecycle {
15+
// Warning: when reordering/changing enumerators, update match in AtomicLifecycle below
1516
Alive,
1617
Destroying,
1718
Dead, // reading this would typically already be too late, only best-effort in case of UB
1819
}
1920

2021
#[cfg(not(feature = "threads"))]
21-
pub use single_thread::*;
22+
pub(crate) use single_threaded::*;
2223

2324
#[cfg(feature = "threads")]
24-
pub use multi_thread::*;
25+
pub(crate) use multi_threaded::*;
2526

2627
#[cfg(not(feature = "threads"))]
27-
mod single_thread {
28+
mod single_threaded {
2829
use std::any::type_name;
2930
use std::cell;
3031

31-
use crate::obj::{Base, GodotClass};
32+
use crate::obj::GodotClass;
3233
use crate::out;
3334

3435
use super::Lifecycle;
3536

3637
/// Manages storage and lifecycle of user's extension class instances.
3738
pub struct InstanceStorage<T: GodotClass> {
3839
user_instance: cell::RefCell<T>,
39-
cached_base: Base<T::Base>,
4040

4141
// Declared after `user_instance`, is dropped last
4242
pub lifecycle: cell::Cell<Lifecycle>,
@@ -45,12 +45,11 @@ mod single_thread {
4545

4646
/// For all Godot extension classes
4747
impl<T: GodotClass> InstanceStorage<T> {
48-
pub fn construct(user_instance: T, cached_base: Base<T::Base>) -> Self {
48+
pub fn construct(user_instance: T) -> Self {
4949
out!(" Storage::construct <{}>", type_name::<T>());
5050

5151
Self {
5252
user_instance: cell::RefCell::new(user_instance),
53-
cached_base,
5453
lifecycle: cell::Cell::new(Lifecycle::Alive),
5554
godot_ref_count: cell::Cell::new(1),
5655
}
@@ -102,51 +101,70 @@ mod single_thread {
102101
})
103102
}
104103

105-
pub fn base(&self) -> &Base<T::Base> {
106-
&self.cached_base
107-
}
108-
109104
pub(super) fn godot_ref_count(&self) -> u32 {
110105
self.godot_ref_count.get()
111106
}
112107
}
113108
}
114109

115110
#[cfg(feature = "threads")]
116-
mod multi_thread {
111+
mod multi_threaded {
117112
use std::any::type_name;
113+
use std::sync;
118114
use std::sync::atomic::{AtomicU32, Ordering};
119-
use std::{cell, sync};
120115

121-
use crate::obj::{Base, GodotClass};
122-
use crate::{out, sys};
116+
use crate::obj::GodotClass;
117+
use crate::out;
123118

124119
use super::Lifecycle;
125120

121+
pub struct AtomicLifecycle {
122+
atomic: AtomicU32,
123+
}
124+
125+
impl AtomicLifecycle {
126+
pub fn new(value: Lifecycle) -> Self {
127+
Self {
128+
atomic: AtomicU32::new(value as u32),
129+
}
130+
}
131+
132+
pub fn get(&self) -> Lifecycle {
133+
match self.atomic.load(Ordering::Relaxed) {
134+
0 => Lifecycle::Alive,
135+
1 => Lifecycle::Dead,
136+
2 => Lifecycle::Destroying,
137+
other => panic!("Invalid lifecycle {other}"),
138+
}
139+
}
140+
141+
pub fn set(&self, value: Lifecycle) {
142+
self.atomic.store(value as u32, Ordering::Relaxed);
143+
}
144+
}
145+
126146
/// Manages storage and lifecycle of user's extension class instances.
127147
pub struct InstanceStorage<T: GodotClass> {
128148
user_instance: sync::RwLock<T>,
129-
cached_base: Base<T::Base>,
130149

131150
// Declared after `user_instance`, is dropped last
132-
pub lifecycle: cell::Cell<Lifecycle>,
151+
pub lifecycle: AtomicLifecycle,
133152
godot_ref_count: AtomicU32,
134153
}
135154

136155
/// For all Godot extension classes
137156
impl<T: GodotClass> InstanceStorage<T> {
138-
pub fn construct(user_instance: T, cached_base: Base<T::Base>) -> Self {
157+
pub fn construct(user_instance: T) -> Self {
139158
out!(" Storage::construct <{}>", type_name::<T>());
140159

141160
Self {
142161
user_instance: sync::RwLock::new(user_instance),
143-
cached_base,
144-
lifecycle: cell::Cell::new(Lifecycle::Alive),
162+
lifecycle: AtomicLifecycle::new(Lifecycle::Alive),
145163
godot_ref_count: AtomicU32::new(1),
146164
}
147165
}
148166

149-
pub(crate) fn on_inc_ref(&mut self) {
167+
pub(crate) fn on_inc_ref(&self) {
150168
self.godot_ref_count.fetch_add(1, Ordering::Relaxed);
151169
out!(
152170
" Storage::on_inc_ref (rc={}) <{}>", // -- {:?}",
@@ -156,7 +174,7 @@ mod multi_thread {
156174
);
157175
}
158176

159-
pub(crate) fn on_dec_ref(&mut self) {
177+
pub(crate) fn on_dec_ref(&self) {
160178
self.godot_ref_count.fetch_sub(1, Ordering::Relaxed);
161179
out!(
162180
" | Storage::on_dec_ref (rc={}) <{}>", // -- {:?}",
@@ -188,14 +206,25 @@ mod multi_thread {
188206
})
189207
}
190208

191-
pub fn base(&self) -> &Base<T::Base> {
192-
&self.cached_base
193-
}
194-
195209
pub(super) fn godot_ref_count(&self) -> u32 {
196210
self.godot_ref_count.load(Ordering::Relaxed)
197211
}
212+
213+
// fn __static_type_check() {
214+
// enforce_sync::<InstanceStorage<T>>();
215+
// }
198216
}
217+
218+
// TODO make InstanceStorage<T> Sync
219+
// This type can be accessed concurrently from multiple threads, so it should be Sync. That implies however that T must be Sync too
220+
// (and possibly Send, because with `&mut` access, a `T` can be extracted as a value using mem::take() etc.).
221+
// Which again means that we need to infest half the codebase with T: Sync + Send bounds, *and* make it all conditional on
222+
// `#[cfg(feature = "threads")]`. Until the multi-threading design is clarified, we'll thus leave it as is.
223+
//
224+
// The following code + __static_type_check() above would make sure that InstanceStorage is Sync.
225+
226+
// Make sure storage is Sync in multi-threaded case, as it can be concurrently accessed through aliased Gd<T> pointers.
227+
// fn enforce_sync<T: Sync>() {}
199228
}
200229

201230
impl<T: GodotClass> InstanceStorage<T> {
@@ -267,6 +296,9 @@ pub unsafe fn destroy_storage<T: GodotClass>(instance_ptr: sys::GDExtensionClass
267296
let _drop = Box::from_raw(instance_ptr as *mut InstanceStorage<T>);
268297
}
269298

299+
// ----------------------------------------------------------------------------------------------------------------------------------------------
300+
// Callbacks
301+
270302
pub fn nop_instance_callbacks() -> sys::GDExtensionInstanceBindingCallbacks {
271303
// These could also be null pointers, if they are definitely not invoked (e.g. create_callback only passed to object_get_instance_binding(),
272304
// when there is already a binding). Current "empty but not null" impl corresponds to godot-cpp (wrapped.hpp).

itest/rust/src/base_test.rs

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,9 @@ fn base_instance_id() {
2727
fn base_access_unbound() {
2828
let mut obj = Gd::<Based>::new_default();
2929

30-
{
31-
let pos = Vector2::new(-5.5, 7.0);
32-
obj.base_mut().set_position(pos);
33-
34-
assert_eq!(obj.base().get_position(), pos);
35-
}
30+
let pos = Vector2::new(-5.5, 7.0);
31+
obj.set_position(pos);
32+
assert_eq!(obj.get_position(), pos);
3633

3734
obj.free();
3835
}
@@ -42,12 +39,9 @@ fn base_access_unbound() {
4239
fn base_access_unbound_no_field() {
4340
let mut obj = Gd::<Baseless>::new_default();
4441

45-
{
46-
let pos = Vector2::new(-5.5, 7.0);
47-
obj.base_mut().set_position(pos);
48-
49-
assert_eq!(obj.base().get_position(), pos);
50-
}
42+
let pos = Vector2::new(-5.5, 7.0);
43+
obj.set_position(pos);
44+
assert_eq!(obj.get_position(), pos);
5145

5246
obj.free();
5347
}

itest/rust/src/object_test.rs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -763,9 +763,6 @@ pub mod object_test_gd {
763763
#[derive(GodotClass)]
764764
#[class(base=Object)]
765765
pub struct CustomConstructor {
766-
#[base]
767-
base: Base<Object>,
768-
769766
#[var]
770767
pub val: i64,
771768
}
@@ -774,7 +771,7 @@ pub mod object_test_gd {
774771
impl CustomConstructor {
775772
#[func]
776773
pub fn construct_object(val: i64) -> Gd<CustomConstructor> {
777-
Gd::with_base(|base| Self { base, val })
774+
Gd::with_base(|_base| Self { val })
778775
}
779776
}
780777
}
@@ -804,10 +801,7 @@ impl DoubleUse {
804801

805802
#[derive(GodotClass)]
806803
#[class(init, base=Object)]
807-
struct SignalEmitter {
808-
#[base]
809-
base: Base<Object>,
810-
}
804+
struct SignalEmitter {}
811805

812806
#[godot_api]
813807
impl SignalEmitter {

0 commit comments

Comments
 (0)