Understanding the Difference Between Object, RefCounted, and Node in gdext’s Type Hierarchy
In gdext-nim, RefCounted objects use automatic reference counting via GdRef[T] wrappers for memory-safe resource management, while Node objects participate in Godot’s scene tree lifecycle without reference counting, requiring manual tree management via add_child and queue_free.
The godot-nim/gdext-nim repository provides Nim bindings for Godot 4.x that faithfully mirror the engine’s class hierarchy. At the root sits Object, but the critical architectural split occurs between RefCounted and Node branches—each representing fundamentally different memory management philosophies that determine how you instantiate, store, and lifecycle-manage objects in your Nim code.
The Foundation: Object as the Root
All engine-exposed types in gdext ultimately derive from Object, defined in src/gdext/builtinindex.nim. This base type provides the low-level method binding infrastructure used by both branches. However, Object itself is rarely used directly; instead, developers choose between RefCounted for autonomous resources or Node for scene-graph entities.
RefCounted: Automatic Memory Management
The RefCounted branch implements Godot’s reference-counting pattern, where the engine maintains an internal counter that increments on reference() and decrements on unreference() calls. When the count reaches zero, the object frees itself automatically.
GdRef Wrapper Implementation
In src/gdext/builtinindex.nim (lines 108-112), gdext exposes RefCounted instances through the GdRef[T] type—a distinct wrapper that manages the reference count through Nim’s destructor mechanism. This ensures that when the last GdRef goes out of scope, the underlying engine object receives its final unreference() call.
Instantiation and Type Safety
The instantiate procedure for RefCounted types, defined in src/gdext/objecttools.nim (lines 17-23), returns a GdRef[T] rather than a raw pointer. This overload leverages the encoding logic in src/gdext/private/typeshift.nim (lines 143-155) to safely bridge Nim’s memory model with Godot’s reference counting.
import gdext
# Returns GdRef[RefCounted] - memory managed automatically
let rc = instantiate RefCounted
echo rc.referenceCount # Access via GdRef wrapper
Node: Scene Graph Integration
The Node branch, defined in src/gdext/gen/classindex.nim (lines 35-36) as ptr object of Object, represents entities that exist within Godot’s SceneTree. Unlike RefCounted, Node instances do not use reference counting; instead, their lifetime is strictly bound to their position in the tree hierarchy.
Tree-Based Lifecycle Management
Nodes are owned by the SceneTree. When you call addChild (bound in src/gdext/private/native.nim as Node_add_child), the engine assumes ownership. Removal occurs through queue_free or remove_child, at which point the engine schedules deletion. Because there is no reference counter, storing a raw Node pointer after the node leaves the tree results in undefined behavior—hence gdext does not provide a GdRef wrapper for Node types.
Instantiation Differences
As implemented in src/gdext/objecttools.nim, instantiating a Node subclass returns a raw pointer (e.g., Node2D), not a GdRef. You must immediately attach it to the scene tree to ensure it remains valid.
import gdext
# Returns raw Node2D pointer - NOT reference counted
let myNode = instantiate(Node2D, "Hero")
self.addChild(myNode) # Must add to tree to prevent garbage collection
# Later removal
myNode.queueFree()
Key Differences at a Glance
| Feature | RefCounted | Node |
|---|---|---|
| Base inheritance | Object → RefCounted |
Object → Node |
| Memory management | Automatic reference counting via GdRef[T] |
Manual scene-tree ownership |
| Wrapper type | GdRef[T] (from builtinindex.nim) |
Raw pointer (no wrapper) |
| Instantiation | instantiate RefCounted → GdRef[T] |
instantiate(NodeType, "Name") → raw pointer |
| Lifecycle control | reference() / unreference() (automatic) |
add_child / queue_free (manual) |
| Typical usage | Resources, data containers, utilities | Scene entities, UI elements, physics bodies |
Implementation Details
The distinction between these types is enforced at the binding level across several core files:
-
src/gdext/builtinindex.nim: Defines theGdRefwrapper andRefCountedbase methods. TheGdRef[T]type ensures that Nim’s destructor pattern invokes Godot’sunreference()when the wrapper goes out of scope. -
src/gdext/private/typeshift.nim: Contains the marshalling logic (lines 143-155) that converts between Nim’sGdRefand Godot’s rawRefCountedpointers during API calls. -
src/gdext/private/native.nim: Houses the low-level method bindings for both branches, includingRefCounted_reference,RefCounted_unreference, andNode_add_child,Node_queue_free. -
src/gdext/objecttools.nim: Provides theinstantiateoverloads that return the appropriate type—GdRef[T]forRefCounteddescendants and raw pointers forNodedescendants. -
src/gdext/sugars.nim: Offers theasandcastTohelpers for converting between node types without theGdRefwrapper overhead.
Summary
- Object is the abstract root for all engine types in gdext, but you typically work with its two concrete branches.
- RefCounted provides automatic memory management through the
GdRef[T]wrapper, making it ideal for resources and data objects that exist outside the scene tree. - Node requires manual lifecycle management via the scene tree (
add_child/queue_free) and returns raw pointers on instantiation, reflecting its tight integration with Godot’s scene graph. - Choose RefCounted when you need safe, copyable references to engine objects; choose Node when building scene hierarchies, UI, or gameplay entities.
Frequently Asked Questions
When should I use RefCounted instead of Node?
Use RefCounted for objects that function as data containers or utilities independent of the scene graph—such as custom resource classes, mathematical helpers, or configuration objects. Use Node exclusively for entities that must participate in the scene tree, including visible objects, physics bodies, UI controls, and anything requiring _process or _physics_process callbacks.
Can I convert a RefCounted to a Node or vice versa?
No, these represent distinct inheritance branches under Object. While you can cast between sibling types within the same branch (e.g., Node2D to Node using as from sugars.nim), you cannot cast a RefCounted to a Node because they share only the Object base and have incompatible memory management semantics.
How does gdext handle memory safety for Nodes?
gdext does not provide a GdRef wrapper for Node types because Nodes are owned by the SceneTree, not by reference counts. To prevent use-after-free errors, you must ensure that you do not hold raw Node pointers after calling queue_free or remove_child. The binding relies on the developer to respect the scene tree lifecycle, as enforced by Godot’s engine architecture.
What happens if I forget to add a Node to the scene tree?
If you instantiate a Node subclass using instantiate(NodeType, "Name") but never call add_child, the node remains detached from the SceneTree. Since Nodes lack reference counting, the Nim garbage collector may eventually collect the raw pointer if no
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →