How to Handle Godot Object Callbacks like `_ready` and `_process` in gdext-nim
In gdext-nim, you implement Godot callbacks by declaring Nim methods with the {.gdsync.} pragma that override the virtual functions defined in gdnode.nim, which automatically registers them with the engine's virtual method table.
gdext-nim is the official Nim language binding for Godot 4's GDExtension API. When building games or editor tools with this framework, you must respond to engine lifecycle events through callbacks such as _ready and _process. These virtual methods are bridged to Nim through a compile-time registration system that eliminates manual boilerplate.
Understanding the Virtual Method Architecture
Godot's engine invokes callbacks through a virtual method table (VMT) stored on each class. In gdnode.nim, gdext-nim pre-defines registration helpers that wire these engine calls to your Nim implementations.
The Registration Mechanism
The binding layer provides typed registration functions for each callback:
| Callback | Registration Function | Location |
|---|---|---|
_ready |
registerVirtual_ready |
src/gdext/classes/gdnode.nim |
_process |
registerVirtual_process |
src/gdext/classes/gdnode.nim |
_physics_process |
registerVirtual_physicsProcess |
src/gdext/classes/gdnode.nim |
When you declare a method with the correct signature and {.gdsync.} pragma, the compiler generates a call to the appropriate registerVirtual_* function. This stores a C-compatible function pointer in the class's vmethods table, which Godot invokes when the corresponding event occurs.
Method Signature Requirements
Your callback methods must match the expected signatures exactly. The base definitions in gdnode.nim use Nim's method dispatch, requiring the first parameter to be self: YourClassType.
Implementing Core Lifecycle Callbacks
To receive engine callbacks, define a ptr object inheriting from a Godot base class and override the virtual methods.
Overriding _ready
The ready method executes once when the node enters the scene tree. Declare it with no return value and the {.gdsync.} pragma:
import gdext
import gdext/classes/gdNode
type MyNode* {.gdsync.} = ptr object of Node
method ready(self: MyNode) {.gdsync.} =
## Called when the node enters the scene tree.
echo "MyNode is initialized"
Overriding _process
The process method runs every frame, receiving the frame time delta as a parameter. You must enable processing via set_process(true) (typically in ready) or the engine will skip this callback:
method ready(self: MyNode) {.gdsync.} =
self.set_process(true)
method process(self: MyNode; delta: float) {.gdsync.} =
## Called each frame while processing is enabled.
self.position.x += 100.0 * delta
Using the onInit Constructor Hook
Unlike ready, onInit is a gdext-nim-specific hook that runs immediately when the native object is allocated, before Godot initializes the node:
method onInit(self: MyNode) =
## Runs once during native object construction.
self.custom_id = cast[uint64](self)
Use onInit for low-level initialization that must occur before any Godot callbacks or property setters run.
Complete Working Example
Below is a minimal, compile-ready class that demonstrates property export, the ready callback, and frame-based movement:
# src/classes/player.nim
import gdext
import gdext/classes/gdNode2D
type Player* {.gdsync.} = ptr object of Node2D
speed*: float = 400.0 # Exported to the Godot editor
method ready(self: Player) {.gdsync.} =
echo "Player ready with speed: ", self.speed
self.set_process(true)
method process(self: Player; delta: float) {.gdsync.} =
self.position.x += self.speed * delta
When this code compiles, the {.gdsync.} pragma on the type generates the GDExtension class registration. The method pragmas trigger the insertion of function pointers into the virtual method table, allowing Godot's C++ core to dispatch _ready and _process calls directly into your Nim methods.
Enabling Physics Process
For physics-frame logic, override physics_process and enable it separately:
method ready(self: MyNode) {.gdsync.} =
self.set_physics_process(true)
method physicsProcess(self: MyNode; delta: float) {.gdsync.} =
## Runs at fixed timestep (default 60 TPS).
self.velocity = self.calculate_movement(delta)
Note that the Nim method name uses camelCase (physicsProcess) while the engine callback is _physics_process. The registration macro maps these correctly.
Summary
- Declare callbacks as methods with the exact signature (
self: YourClass) and the{.gdsync.}pragma to auto-register them viaregisterVirtual_*functions ingdnode.nim. - Enable processing explicitly using
self.set_process(true)orself.set_physics_process(true)insideready, or the engine will not invokeprocessorphysicsProcess. - Use
onInitfor construction-time logic that must run before Godot initializes the node properties. - Inherit from Godot classes using
ptr object of Node(orNode2D,Control, etc.) to ensure proper memory layout and VMT compatibility.
Frequently Asked Questions
How do I know if my callback is actually registered with Godot?
If you declare a method with {.gdsync.} and the correct signature, the gdext-nim macro system automatically inserts the registration code at compile time. You can verify this by checking that your class compiles without errors and that the method executes when you run the scene. The bridge code in gdnode.nim handles the vmethods table insertion transparently.
Why isn't my process method being called?
The process callback only runs if processing is enabled on the node. You must call self.set_process(true) (or self.set_physics_process(true) for physics frames), typically inside your ready method. Without this flag, Godot skips the node during the main loop iteration to save performance.
Can I rename the Nim method to something other than ready or process?
No, you must use the exact method names ready, process, and physicsProcess (camelCase for the latter) because the registration macros in gdnode.nim look for these specific identifiers when building the virtual method table. Using different names would result in the engine calling the base stub implementation instead of your override.
What is the difference between onInit and ready?
onInit is a gdext-nim-specific hook that runs immediately when the native Nim object is allocated, before Godot has finished setting up the node or its properties. The ready callback runs later, once the node has entered the scene tree and all its children are initialized. Use onInit for native memory setup and ready for game logic initialization that depends on the scene state.
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 →