-
-
Notifications
You must be signed in to change notification settings - Fork 241
Access to base pointer during initialization #1273
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
base: master
Are you sure you want to change the base?
Conversation
API docs are being generated and will be shortly available at: https://godot-rust.github.io/docs/gdext/pr-1273 |
For manually-managed objects, the implementation is simple: just create a copy of the internal Gd pointer. For ref-counted objects, we need to deal with the problem that `Base<T>` stores a weak Gd pointer, and thus dropping handed-out pointers will trigger object destruction. To deal with this, we keep an extra strong pointer around, and defer its destruction to the next frame. It would be even better if we could immediately destroy the strong ref after object construction, however we could only do so for Rust paths `new_gd()`, `Gd::from_init_fn()`, not for Godot-side construction. Unfortunately, Godot doesn't offer a hook to run code immediately after creation. The POSTINITIALIZE notification needs to be dispatched by godot-rust, and thus suffers from the same issue.
Currently no purpose.
329122d
to
15527d0
Compare
The side effect is that any In godot-cpp I only need to use |
This is clearly documented though. But I'm happy to hear how we can safely implement immediate drop. I've only managed that for Rust-side construction, not
This PR allows that. Why is deferred destruction a problem? |
When I was experimenting with late init I've been doing pretty much the same stuff – just with atomics instead of
I haven't thought about global state before and it seems like the best approach – it can be "glued" on top of existing implementation to cover fairly niche use case and just works ™️. I don't see any inherent issues related to it as well 🤔, albeit I've never dig into this approach. Using IMO there should be another GDExtension callback for it similar to gdscript …and there is always a fifth option – gave up, document it, and advise to use gdscript-glue when necessary (It would make accessing post_init impossible though) 😅. Doesn't feel (and definitively isn't) like the best way to solve this problem.
It ain't that terrible since Godot itself tends to not optimize for memory usage (with an exception of cases when there is some inherent problem with it or where it matters a lot). It doesn't mean that we shouldn't care at all – it means that someone who cares a lot about memory usage would rather use other APIs (i.e. servers and Rids, rust structs attached to nodes instead of other nodes etc) 🤔 – but yeah, paying a toll on everything because of fairly niche usecase (required mostly for effect compositors and few others) feels silly. |
Closes #557.
Closes #997.
Important
This may or may not be the final API. See below for trade-offs.
Feature
Allows accessing
Gd<Base>
during construction.Current API:
Importantly, this works for
RefCounted
, without UB or memory leaks.Implementation
For manually-managed objects, the implementation is simple. Just copy the pointer.
For ref-counted objects, we need to deal with the problem that
Base<T>
stores a weakGd
pointer, and thus dropping handed-out pointers will trigger object destruction. To deal with this, we keep an extra strong pointer around1, and defer its destruction to the next frame.It would be even better if we could immediately destroy the strong ref after object construction, however we could only do so for Rust paths
new_gd()
,Gd::from_init_fn()
, not for Godot-side construction. Unfortunately, Godot doesn't offer a hook to run code immediately after creation. ThePOSTINITIALIZE
notification needs to be dispatched by godot-rust, and thus suffers from the same issue.Alternatives and trade-offs
Alternative 1:
Initer
type-stateProblem is that
Base<T>
needs to store an extra field to keep track of the strong-ref. The current implementation does this for allT
s, but it would be easy to limit it toRefCounted
classes.This means that most user-defined types will also need 8 extra bytes, if they declare a
Base
field. While this is not terrible, I don't like the thought of everyone paying for this, when access-during-init is a rare use case, and some people may not need it at all.I was thus also thinking about an alternative API:
Gd::from_init_fn
andITrait::init()
would then takeIniter
instead ofBase
as a parameter.This has two main advantages:
Base
no longer needs to store the strong ref, which is anyway only useful during initialization.to_gd()
is available onIniter
and not onBase
, it's statically impossible to call it after initialization.to_init_gd()
is clear enough to not be called later, so this is more of a theoretical nicety.The drawbacks are:
init
functions and closures.#[class(init)]
instead of manual inits.base
, the user now needs the syntactically heavierbase: init.into_base()
, although there's no benefit in 99% of cases.One option would also be to allow both APIs, taking
Base
orIniter
depending on needs. This could be done backwards-compatibly, but would not only mean we need aGd::from_init2_fn
, but alsoITrait::init2
2. But it would enlarge the API surface even further.Alternative 2: global state
Another option is to store a global
HashMap<InstanceId, Gd<RefCounted>>
with the interim strong pointers. This could also simplify theRc<RefCell<Option<Gd<T>>>>
which is currently needed. It would effectively remove the field from allBase
instances, but still keep the information around.We would need to ensure thread safety, but I'm happy to accept synchronization overhead for this rather rare case. One thing we do need to make sure is is thread-safe deferred dec-ref. This is already the case for the current implementation.
The nice thing about this approach is that it is fully backwards-compatible with the current API, needs no new "initer" concept and still allows to reduce the size of the
Base
type. In other words, it's a good example of "pay what you use".Alternative 3: Godot per-instance metadata
We could use Godot's
Object::set_meta()
to store per-object metadata. This is generally an interesting idea, however we would need to make sure that:A Rust-based storage mechanism would be safer, as we're fully in control.
So, I'm not yet sure. I'd like to get the current version merged soon-ish, so we have a base (:monocle_face:) for further iteration. This might also be a candidate to be postponed to v0.4, to give us some time to iterate.
Footnotes
It is tempting to just store a
bool
flag instead of another strongGd
, since we already have the weakGd
as a field. However, we need to have a physicalGd
to uphold the strong guarantee. When that is dropped, theBase
becomes dangling. So, an enum might be possible, but it's not taking up less space. ↩Unless we do some dirty macro-tricks like
f64
/f32
inprocess
/physics_process
, but I don't think that helps ease-of-use 🤔 ↩