Add opt-in Instance downcasting support via tracking #211
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
...also this thing which I've been dogfooding (albeit not very extensively yet) for a little while:
With customizable userdata merged, downcasting to NativeClass instances can now be supported with an opt-in userdata wrapper adaptor that performs pointer tracking, as described in the future possibilities section of the original Instance PR.
Tracking is performed only on construction, destruction, and downcasts, and only for types that opt-in. There is no additional runtime cost for calls from Godot.
Downcast attempts on types that are not tracked (or can otherwise be checked) are statically prevented.
Explanation
The
Tracked<UD<T>>
type is a user-data wrapper adapter, which wraps around wrapper implementations, tracking the pointers they produce in a globalHashMap
:into_user_data
, the pointer is inserted into a set corresponding to theTypeId
of the target type.consume_user_data_unchecked
, the pointer is removed.try_clone_from_user_data
, the map is queried with the given pointer. If the pointer is present in the set for the type, it's assumed to be valid. Otherwise,None
is returned.Like other locking types, it makes use of
parking_lot::RwLock
which is very efficient in uncontended situations. The map itself is alazy_static
, so it's not created at all ifTracked
is unused.Once
const_fn
is stablized, thelazy_static
can be replaced with aRwLock<Option<_>>
and initialized with a constnew
, removing the extra overhead.Drawbacks
I don't believe there is much except introducing more APIs in the crate, since it's purely opt-in. I think it's useful enough to warrant an addition, especially in cases where the code is expected to interact fairly often with GDScript.
Also "user-data wrapper adapter" sounds like a tongue twister...
Rationale and alternatives
External interfaces
The use case for this overlaps a bit with #200, but with some key differences:
Variant
.I believe both features can co-exist serving different needs.
TypeId
tagsInstead of tracking pointers in a global map, it's also possible to store the
TypeId
beside the data, removing the need to perform locks and hash map lookups. This, however, poses its own problem when other GDNative libraries are in play.As considered in the original
Instance
PR, the current implementation ofgodot_nativescript_get_userdata
in Godot only checks that the script is any NativeScript: that is, not necessarily from this library or even language. Thus, it's well possible to read invalid data from a foreign pointer, or even cause segfaults in some cases (e.g. Rust ZSTs).For projects where there will be only one GDNative library present, a tagged representation can be useful. Regardless, I consider it less fit for inclusion in the core library, because of the level of attention required in its use.