-
-
Notifications
You must be signed in to change notification settings - Fork 198
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Move Deref/DerefMut
out of T
to Gd<T>
; InstanceStorage
safety
#370
Conversation
API docs are being generated and will be shortly available at: https://godot-rust.github.io/docs/gdext/pr-370 |
godot-core/src/storage.rs
Outdated
|
||
// Declared after `user_instance`, is dropped last | ||
pub lifecycle: Lifecycle, | ||
pub lifecycle: cell::Cell<Lifecycle>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cell
isn't sync, so i dont think this is safe. we could make this an atomic integer instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a cool trick for these things, btw:
fn enforce_sync<T: Sync>();
// never called
fn __static_type_check() {
enforce_sync::<Type>();
}
I need to see if it's already applicable, if not I'd like to avoid spending time now to improve multi-threaded soundness, this needs more thought anyway...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I cannot apply this yet. Just discovered that T
would need to be Sync
and likely Send
if used in a thread-shared Gd<T>
under the threads
feature. This will however mean adding those bounds in a ton of places, which I'm not gonna do now.
Possibly we could imply the bounds in something like
trait GodotClass: 'static + Sync + Send {
...
}
but then that would also affect engine types, not just user-defined T
. Which I guess is the right move, anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
one possible solution to this is to have a sync version of Gd
. which can only hold Sync
rust objects (would we need Send
? i guess the destructor takes the object out of the Gd
, and i assume it can be called on another thread.)
which does sorta mirror how the std has sync and unsync versions of some types. like Rc
vs Arc
.
it also means we dont need a separate feature for thread safety, we'd just need people to use the right smart pointer. im not sure how this would need to work with Base
though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would we need
Send
? i guess the destructor takes the object out of theGd
, and i assume it can be called on another thread
Not just the destructor, but as long as bind_mut()
still hands out a guard with DerefMut
, one can do this:
// on thread 1:
let obj = MyClass { ... };
let ptr = Gd::new(obj);
// on thread 2:
let ptr: Gd<MyClass> // passed from thread 1
let guard = ptr.bind_mut();
let extracted = mem::take(*guard); // replaces with default
// do whatever on thread 2
Sync
works with &T
access + interior mutability, but not really with &mut
because that is essentially a free pass to get the value...
it also means we dont need a separate feature for thread safety, we'd just need people to use the right smart pointer.
This whole topic probably needs more thought... In general, I'd also like to avoid accidental use of threads when there's no explicit opt-in, i.e. runtime validations in some places.
Having two types can also be unergonomic, requiring extra conversions, and less flexible API design. On GDScript there is no such distinction, so there may be friction at the boundary. Another option is also special impl <T: GodotClass + Sync> Gd<T> { ... }
blocks provide functionality on top.
936e3af
to
c10b7a7
Compare
c10b7a7
to
dd23b53
Compare
T::deref
/deref_mut
with Gd<T>::base
/base_mut
; rework InstanceStorage
Deref/DerefMut
out of T
to Gd<T>
; InstanceStorage
safety
6e8d8fd
to
33bffbd
Compare
…torage No more implicit access to base; avoids runtime-borrowing (Godot objects don't have Rust borrow limits) and makes access more explicit. InstanceStorage is now only accessed via shared-ref, which should fix some soundness issues with unbounded &mut. All fields are immutable or have interior mutability.
More uniform and should be safe.
33bffbd
to
4dd712c
Compare
No more implicit access to base from inside the object. Access to base objects inside
Gd<T>
must go through the#[base]
field. For detailed motivation, see #131. In summary:self
and the Godot objectself.base
apart, when not all symbols are in one namespace.gd.bind_mut().get_position()
.gd.bind_mut().base.get_position()
-- however one can as well dogd.get_position()
directly.Deref
/DerefMut
onT
, we don't impose an implementation (see comment).T
lives in another crate.Instead,
Deref
andDerefMut
are now implemented for allGd<T>
pointers, not just the engine ones. This means that calling Godot methods will be uniform, and there is always a clear separation between accessing the Godot object and the user object.T
self
Gd<T>
gd.bind/bind_mut()
gd.method()
Also,
InstanceStorage
is now only accessed via shared-ref, which should fix some soundness issues with previously unbounded&mut
. All its fields are immutable or have interior mutability.Closes #131.
Outdated
Where I'm still not sure is, if we can provide
Deref
/DerefMut
onGd<T>
into&T::Base
and&mut T::Base
.This is different from the current behavior, where
Deref
/DerefMut
was implemented on the user-objectT
.A big unknown is especially the mutable case -- how would we avoid aliased
&mut
when twoGd<T>
are concurrently mut-derefed?We do already something similar for
Gd<T>
with engine types, but there, the engine instances (Node
handles) have physically different locations. As of this PR's current state, they have one location (insideInstanceStorage
) -- but maybe it would be possible to re-interpret the opaque object insideGd
directly (or if that doesn't work, create a new field with theNode
itself).(All in all it's rather an academic problem, as we're not actually mutating the engine object, but Rust's aliasing rules are potentially problematic here.)
Solution to the above: instead of storing a central
Base<T>
in the storage, as it was done in the earlier commit, we directly transmute theGd.opaque
field intoT
. This object is at a distinct memory location for eachGd
pointer, as such there is no&mut
aliasing when usingDerefMut
on aliasedGd
pointers.