-
-
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. |
I implemented alternative 2: global variable. Since we are currently limited to the main thread due to From my side, once I fixed CI, this would be ready for an initial implementation. @beicause I'm not against future improvements for earlier destruction, but I'd like to proceed since this fixes a long-standing issue, even if not perfectly. I'm happy to hear concrete proposals about how immediate destruction (also for GDScript-side |
Deferred destruction may be fine in most cases, as it's already the case for script languages with GC. However, I don't really like having to use the hack and performance overheads since I don't know whether the upstream could have a solution for this. I have a naive idea that adding a weak gd that does not affect reference count. To make it safe, we can check whether the instance id is still valid before each method call in debug mode, while disabling the check in release mode. |
Not sure I follow this example -- the But generally it is possible to immediately destruct. We have this FFI callback, which you know already from your other PR: gdext/godot-core/src/registry/callbacks.rs Lines 34 to 39 in 870c725
And we call the gdext/godot-core/src/obj/gd.rs Lines 154 to 162 in 870c725
Now, The problem is the FFI call here -- we are not in control what happens after the C function I was thinking if we should do immediate-destruction for Rust-constructed objects, and deferred for GDScript ones. But I'm not sure if the behavior then isn't even more confusing?
There is no concept of "weak Gd" in godot-rust. Every public-facing There are some places where we internally use weak But if it's public-facing, it would likely need to be its own type due to very different semantics, just like Maybe using Rust-side immediate destruction is better? For now I don't want to spend more time on implementation, but I'll gladly discuss this further and see what people think 🙂 |
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 🤔 ↩