Hi, first of all, thanks for the addon, I use it a lot. :)
I’ve been working on a strategy game with heavy non-UI, data-only logic where I instantiate custom LogStreams from my classes like this:
class_name Pathfinding
extends RefCounted
var _log := LogStream.new("Pathfinding", LogStream.LogLevel.DEBUG)
This is very similar to what I'm used to from Java/Python which I appreciate a lot. :)
However I noticed that my GUT unit tests complain about orphan nodes:
==============================================
= 43 Orphans
==============================================
res://test/unit/test_pathfinding.gd
- Outside Tests
* ...
test_pathfinding.gd
- test_path_on_plains_is_cheapest
* [LogStream[Pathfinding](log-stream.gd)]
- test_path_avoids_costly_terrain_like_forest
* [LogStream[Pathfinding](log-stream.gd)]
GUT explanation for orphans link:
- Any Node (or a subclass of Node) that is not currently in the tree is considered an orphan. Children of orphaned Nodes are also considered orphans.
Technical background
This is what I learned after looking into this (I'm still new to Godot).
Godot’s memory model (docs here):
Node and other Object-derived classes are not reference-counted.
- They remain in memory until explicitly freed (
free() / queue_free()).
- Only
RefCounted instances are freed automatically when the last reference is dropped.
Because the current version add-on encourages creating streams like this:
var network_log = LogStream.new("Networking")
network_log.warn("No internet")
the result is a permanently orphaned Node (not attached to the scene tree, never freed).
This means every such stream instance will stay in memory until the engine shuts down,
or the developer manually frees it -- which is not mentioned in the documentation.
This can be especially bad if it's on an object that has many instances, like an enemy unit in the game for example.
My suggestion: consider switching LogStream to extend RefCounted
From what I understand, changing LogStream to extend RefCounted instead of Node would eliminate the orphan problem.
This would make each stream self-managed and automatically freed once there are no references.
Draft steps
-
Remove _ready() logic
RefCounted objects don’t receive _ready().
The current _ready() in log-stream.gd connects a tree_exiting cleanup signal,
but this is already handled by plugin.gd when the plugin is enabled:
# plugin.gd
tree_exiting.connect(_LogInternalPrinter._cleanup)
So this code can safely be removed or moved to the plugin initialization.
-
Verify signal/callback behavior
All Node signal connections (e.g. tree_exiting) that assume a scene-tree context
should be removed or replaced with manual disconnection when appropriate.
Risks?
I might be missing some lifecycle stuff and RefCounted can still lead to memory leaks if
there are circular references.
So we should be careful.
If LogStream keeps deriving from Node
If you prefer to keep LogStream as a Node, the docs should at least recommend a cleanup pattern. Something like this maybe (?):
var log = LogStream.new("MyLogger")
get_tree().root.add_child(log)
# or explicitly free when done
log.queue_free()
and note that otherwise, it will stay in memory.
I'd be happy to help with the implementation, please let me know what you think.
Hi, first of all, thanks for the addon, I use it a lot. :)
I’ve been working on a strategy game with heavy non-UI, data-only logic where I instantiate custom LogStreams from my classes like this:
This is very similar to what I'm used to from Java/Python which I appreciate a lot. :)
However I noticed that my GUT unit tests complain about orphan nodes:
GUT explanation for orphans link:
Technical background
This is what I learned after looking into this (I'm still new to Godot).
Godot’s memory model (docs here):
Nodeand otherObject-derived classes are not reference-counted.free()/queue_free()).RefCountedinstances are freed automatically when the last reference is dropped.Because the current version add-on encourages creating streams like this:
the result is a permanently orphaned
Node(not attached to the scene tree, never freed).This means every such stream instance will stay in memory until the engine shuts down,
or the developer manually frees it -- which is not mentioned in the documentation.
This can be especially bad if it's on an object that has many instances, like an enemy unit in the game for example.
My suggestion: consider switching
LogStreamto extendRefCountedFrom what I understand, changing
LogStreamto extendRefCountedinstead ofNodewould eliminate the orphan problem.This would make each stream self-managed and automatically freed once there are no references.
Draft steps
Remove
_ready()logicRefCountedobjects don’t receive_ready().The current
_ready()inlog-stream.gdconnects atree_exitingcleanup signal,but this is already handled by
plugin.gdwhen the plugin is enabled:So this code can safely be removed or moved to the plugin initialization.
Verify signal/callback behavior
All
Nodesignal connections (e.g.tree_exiting) that assume a scene-tree contextshould be removed or replaced with manual disconnection when appropriate.
Risks?
I might be missing some lifecycle stuff and RefCounted can still lead to memory leaks if
there are circular references.
So we should be careful.
If
LogStreamkeeps deriving fromNodeIf you prefer to keep
LogStreamas aNode, the docs should at least recommend a cleanup pattern. Something like this maybe (?):and note that otherwise, it will stay in memory.
I'd be happy to help with the implementation, please let me know what you think.