Skip to content
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

Add freeing signal to Object #101396

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

dog-on-moon
Copy link
Contributor

@dog-on-moon dog-on-moon commented Jan 10, 2025

@AThousandShips
Copy link
Member

This does not implement what the proposal asked for at least from the title, it asks for a signal when queue_free is called

@Mickeon
Copy link
Contributor

Mickeon commented Jan 10, 2025

This does not implement what the proposal asked for at least from the title,

I would normally not be pedantic about this, but there's a stark difference between free and queue_free. The proposal makes it clear OP wants to know when a node is queued for deletion. So yeah, I agree this PR does not address it, not even indirectly.

Even then, having Object itself emit this signal is possibly not a good idea. Everything in the engine would emit this signal, yet nothing would be connected to it almost all the time, and that affects both internal code and scripting. Emitting signals has a pretty small cost, but it does add up for frequent operations.

@Lazy-Rabbit-2001
Copy link
Contributor

Lazy-Rabbit-2001 commented Jan 10, 2025

Note that there is a notification NOTIFICATION_PREDELETE, which is sent to an Object that is to be deleted/freed. This is also how GDScript operates a destructor.
I think it has been already possible to achieve what this pr aims to do by overriding _notification() in which do a match check with one pattern of that notification, in which you emit a custom signal that should be emitted on being freed. If possible, a @tool might be necessary in some situations.
I can see that there will still be inconvenience in some cases, but the solution is cheap and easy to be done (except the flaw of coming boilerplates)

@dog-on-moon
Copy link
Contributor Author

This PR is currently too extreme in many ways, but I have a few thoughts as for why I'd like it.

First I'd like to clarify the difference between this PR and the proposal. Yes, the proposal's title asks for nodes attached to queue_free. However, some of the suggested workarounds utilized NOTIFICATION_PREDELETE, which is an Object notification. And this PR does resolve the proposed case from the post:

_target = _get_nearest_enemy()
if _target:
    _target.freeing.connect(func(): _target = null)

I also described my intended use case in a comment on the PR, which requires the signal to live on Object. Ultimately I think the proposal had left a bit of technical ambiguity over where exactly the freeing signal should live, and I implemented it in a way that was preferential to how I needed it -- my apologies if this was improper. Regardless, I do think this PR does address the proposal very directly: a "queue_freeing" signal is not the same as a "freeing" signal, especially since the former doesn't even guarantee the Node is being freed (as cancel_free exists, and because queue_free defers the Node free to later).

As for my use case, I have been developing several multiplayer APIs for Godot over the past 3 years. The last one I made provided integrated replication/synchronization for Nodes on a low level. To do this, the server tracks each node within its multiplayer subtree with a unique networking ID, and replicates scenes over to the client with all of its networking IDs, so that both the client and server have equivalent networking IDs for each node. These IDs are used for routing RPCs, synchronization, and replication packets without using NodePaths. These IDs are stored in separate ID repositories (note: big dictionary) on the separate client and server subtrees.

I've been thoroughly developing and testing this networking model for my own projects over the last 4 months, and it has been very successful and stable for me. However, the repository is too Node-centric, and I'd like to be able to replicate/synchronize the properties within Resources too. I cannot give Resources network IDs for RPCs, synchronization, and replication, because the network repository only accepts Nodes. I could swap it to Object, but then the repository has no way of knowing when to clean up its stored IDs, besides periodically checking is_instance_valid on hundreds, potentially thousands of different Objects.

For most cases, yes, there are workarounds for this. The most functional workaround listed on the PR entails listening for Node tree_exiting and determining if it was queued for deletion, which is particularly obscure and only works for Nodes. But for the case of "distributed object registration in a large multiplayer API", the best (incomplete) workaround is NOTIFICATION_PREDELETE on a custom Resource base class. This might work, but the typechecking conflation adds overhead to a common registration process for an API which would otherwise be working properly for all Objects.

And this could just be me personally, but I've kept finding it strange that there's no way to check for the moment any Node cleans up in Godot without implementing an unusual barrier call on tree_exiting. This luxury does not exist to developers who might be working at the Object level.

Right now, the worst part about this PR is the performance overhead on this signal emission. My friend suggested implementing a function on Object for setting a flag before it could emit the signal (Object.set_emit_free(bool)), which would be false by default. I'd also want to implement another small barrier so that cancel_free would work if called during the freeing signal.

@dog-on-moon
Copy link
Contributor Author

Some further testing with the is_queued_for_deletion workaround has shown me that there are edge cases where it simply does not work, meaning there is currently no robust way in Godot to perform code when a Node has been cleaned up -- the only way of knowing is to check is_instance_valid on a process frame or listening to NOTIFICATION_PREDELETE, neither of which are viable solutions for my API.

image

I'm still interested in patching this PR to address its performance of it (via a hypothetical Object.set_emit_free), but given the resposne I feel as though I jumped the gun with this PR. I still believe it solves the described issue in the proposal, even though it generalizes the solution past the PR's title to be usable for my own. Let me know what would be best.

@dog-on-moon dog-on-moon force-pushed the object-freeing-signal branch from c95c9dc to 2b51762 Compare January 16, 2025 22:18
@dog-on-moon dog-on-moon force-pushed the object-freeing-signal branch from 2b51762 to 0bbba18 Compare January 16, 2025 22:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Emit a freeing signal when queue_free is called on a Node
4 participants