Godot Version
v4.4.1.stable.official [49a5bc7b6]
Question
I am trying to determine why a specific node is being freed. The issue is that when I attempt to debug, I only get one frame in the stack trace (the current line). The origin of the call is never available.
I’ve tried in _notification
, and in a bound signal to exit_tree
:
func _notification(what: int) -> void:
if what == NOTIFICATION_PREDELETE:
print("Node freed") ## Debug placed here
func exit_tree():
print("Node freed") ## Debug placed here
The stack trace isn’t helpful Frame 0 - res://some_script.gd:122 in function '_notification'
What tools do I have to debug a node being prematurely freed (somebody is calling free or queue_free presumably)?
I managed to track it down. I was queue_freeing
a slot note, adding a new slot node, and then attaching a node to the first available slot. The first available slot was the slot I earlier queue freed, which next tick went out of scope.
I would still like more robust debugging tools for this use case.
I also tried overriding queue_free()
and free()
in the object, to see if that allowed me more information, but sadly that’s not possible.
Rather than that, what you might want to do is not call queue_free()
or free()
directly, but call them through a debug passthrough:
# Maybe this is a global script called ObjDebug or something? Untested...
# Camel case to differentiate from the builtins...
func TrackQueueFree(o: Object) -> void:
o.set_meta(&"debug:queue_free", true)
func QueueFree(o: Object, src: Object, debug: String) -> void:
if o.has_meta(&"debug:queue_free"):
print("[DEB] QueueFree(%s, %s, %s)" % [o.to_string(), src.to_string(), debug])
o.queue_free()
Then, let’s say you’ve got a bullet that you want to track. Its script might look like:
#[...]
func _ready() -> void:
ObjDebug.TrackQueueFree(self)
func _process(delta: float) -> void:
lifetime -= delta
if lifetime <= 0.0:
ObjDebug.QueueFree(self, self, "Lifetime expired")
And if the player can sweep their sword to destroy bullets, the player might have:
#[...]
ObjDebug.QueueFree(bullet, self, "Hit with sword.")
The scheme uses metadata to make the telemetry selective, so you don’t get spammed with every queue_free() unless you want to. It adds a little overhead (this is where #ifdef
or comptime or the like would be nice to have, but AFAIK we don’t…), but not much and only on object deletion, which is a relatively rare event.
Thanks for the idea!
I did consider replacing all the free/queue-free calls in the project with a tracked version. I had 18 I think, so it wouldn’t have been too terrible to retrofit.
In general I do with we had better capabilities in debugging a signal back to the call site.