How the gdext-nim Internal Bridge Communicates Between Nim and Godot’s C++ API

The gdext-nim internal bridge communicates between Nim and Godot’s C++ API by implementing a thin C-level translation layer that converts Nim objects and methods into the callback functions expected by Godot’s GDExtension interface, using a global callbackTable to map Godot StringName references to Nim implementations.

The godot-nim/gdext-nim repository provides the infrastructure for writing Godot engine extensions in Nim. This internal bridge handles the complex interoperability between Nim’s garbage-collected runtime and Godot’s C++ object system, enabling developers to write game logic in Nim while maintaining full compatibility with Godot’s API expectations.

Core Components of the Internal Bridge

The bridge architecture consists of four specialized modules that handle different aspects of the Nim-to-Godot translation.

internalbridge.nim: Callback Implementation

Located at src/gdext/private/internalbridge.nim, this file supplies the concrete callback functions that Godot invokes directly. It implements generic callback procs including set_func[T], get_func[T], notification_func[T], create_instance_func[T], free_instance_func[T], and recreate_instance_func[T].

The creationInfo proc (lines 24–65) constructs a ClassCreationInfo5 struct containing pointers to these callbacks. When register MyNode is called from user code, this struct is passed to ClassDB.registerExtensionClass, telling Godot exactly which functions to invoke when manipulating Nim objects.

gdinterface.nim: C API Wrappers

The src/gdext/private/gdinterface.nim file wraps Godot’s raw C API (interface_… functions) in Nim procs marked with the {.gdcall.} pragma. This pragma ensures proper calling conventions when passing Nim functions as plain C function pointers to Godot.

This module also maintains the global callbackTable hash table, which maps a Godot StringName (the class identifier) to an InstanceBindingCallbacks record. This table enables the bridge to locate the correct Nim method tables when Godot requests property access or virtual method execution.

bridge.nim: Public Macro Interface

src/gdext/bridge.nim provides the developer-facing macros gdsync, gdexport, and register. These macros generate the metadata required by the internal bridge and orchestrate the registration process.

When you apply {.gdsync.} to a type or method, the macro generates the necessary metadata and eventually invokes internalbridge.creationInfo to establish the C-level bindings.

objectcallbacks.nim: Property Access Delegation

Located at src/gdext/objectcallbacks.nim, this module contains the helper routines that actually read and write properties. When Godot triggers set_func[T] or get_func[T] from internalbridge.nim, these callbacks delegate to objectcallbacks.set or objectcallbacks.get, which in turn invoke the Nim getter/setter procs generated by the gdexport macro.

Object Lifecycle and Communication Flow

The internal bridge manages the complete lifecycle of Nim objects within Godot’s engine, from initial registration through destruction.

Class Registration Process

When user code executes register MyNode, the following sequence occurs:

  1. The register macro calls internalbridge.creationInfo to prepare the binding metadata
  2. creationInfo instantiates a ClassCreationInfo5 record with function pointers to the generic callbacks specialized for MyNode
  3. The bridge invokes ClassDB.registerExtensionClass (wrapped in gdinterface.nim) to notify Godot’s C++ core about the new class
  4. Godot stores these pointers and associates them with the StringName "MyNode"

Instance Creation and Destruction

The create_instance_func[T] callback (defined in internalbridge.nim) handles object instantiation:

  • It allocates the Nim object using Nim’s memory manager
  • Calls instantiate_internal[T] (lines 24–33) to create the underlying Godot engine object
  • Stores a reference to the Nim instance via ObjectPtr.setInstanceBinding, linking the C++ object to its Nim wrapper

When Godot destroys an object, free_instance_func[T] destroys the Nim instance, executes any user-defined onDestroy logic, and frees the memory.

Property Access Mechanisms

When Godot needs to read or write a property on a Nim object:

  1. Godot invokes the exported set_func[T] or get_func[T] callbacks stored in the ClassCreationInfo5 struct
  2. These wrappers delegate to objectcallbacks.set or objectcallbacks.get
  3. The objectcallbacks module calls the actual Nim getter/setter procs generated by gdexport (see internalbridge.nim lines 96–108 for the registration logic)

Virtual Methods and Notifications

The bridge handles Godot’s notification system through notification_func[T], which forwards Godot notification IDs (such as NotificationReady) to the corresponding Nim notification method.

For virtual method overrides, the optional get_virtual_func callback (lines 103–108 in internalbridge.nim) provides the memory address of a Nim-implemented virtual method when Godot queries for it during runtime.

Practical Implementation Examples

Exposing a Nim Class to Godot

import gdext

type
  MyNode* {.gdsync.} = ptr object of Node
    counter: Int

proc _ready(self: MyNode) {.gdsync.} =
  print "MyNode ready!"

proc get_counter(self: MyNode): Int {.gdsync.} =
  result = self.counter

proc set_counter(self: MyNode; v: Int) {.gdsync.} =
  self.counter = v

gdexport "counter", MyNode, Int, get_counter, set_counter
register MyNode

Under the hood, gdsync registers MyNode as a Godot class, gdexport creates the property binding via gdexport_internal, and register triggers creationInfo to populate the ClassCreationInfo5 struct with pointers to set_func[MyNode] and get_func[MyNode].

Implementing Virtual Methods

type
  MySprite* {.gdsync, base.} = ptr object of Sprite

proc _process(self: MySprite; delta: Float) {.gdsync, base.} =
  self.rotation += delta

register MySprite

The base pragma marks this as a virtual method override. The bridge stores this in the metadata, and when Godot queries for the virtual method through get_virtual_func, the internal bridge returns the Nim function pointer.

Manual Object Instantiation

let myNode = instantiate_internal(MyNode)
myNode.set_name "MyNodeFromNim".newStringNameInternal
Engine.get_singleton("SceneTree").add_child(myNode)

The instantiate_internal proc creates the Godot engine object, binds the Nim instance using setInstanceBinding, and registers the necessary lifecycle callbacks.

Summary

  • The internal bridge in src/gdext/private/internalbridge.nim implements the C-level callbacks Godot expects, including set_func, get_func, and create_instance_func
  • gdinterface.nim wraps Godot’s C API using the {.gdcall.} pragma and maintains the global callbackTable mapping StringName references to Nim bindings
  • The bridge.nim module provides ergonomic macros (gdsync, gdexport, register) that generate metadata and trigger the low-level registration process
  • Object lifecycle management uses instantiate_internal to bind Nim instances to Godot objects and free_instance_func to handle cleanup
  • Property access flows from Godot’s C++ core through the callback table to objectcallbacks.nim, which invokes the actual Nim getter/setter procs

Frequently Asked Questions

What is the performance overhead of the gdext-nim internal bridge?

The overhead is minimal because the bridge uses thin C-level wrappers rather than complex marshalling layers. Once registered through ClassCreationInfo5, Godot calls the Nim callbacks directly as function pointers without intermediate translation layers. The primary cost occurs during initial class registration and object instantiation, not during steady-state property access or method calls.

How does the internal bridge handle memory management between Nim and Godot?

The bridge implements a bidirectional ownership model. When create_instance_func instantiates a Nim object, it stores a reference via ObjectPtr.setInstanceBinding, ensuring the Nim garbage collector maintains the object while Godot holds a pointer. Conversely, free_instance_func ensures that when Godot destroys the C++ object, the corresponding Nim instance is properly deallocated and any user-defined onDestroy callbacks execute.

Can I use the internal bridge without the gdsync macro?

While possible, manual registration requires directly populating ClassCreationInfo5 structs and maintaining the callbackTable entries yourself. The gdsync macro in bridge.nim automates the generation of method signatures and the invocation of internalbridge.creationInfo. Without it, you would need to manually implement set_func, get_func, and lifecycle callbacks for each class in internalbridge.nim.

How does the bridge map Godot virtual method calls to Nim implementations?

When Godot requests a virtual method pointer, the get_virtual_func callback (lines 103–108 in internalbridge.nim) queries the callbackTable using the class StringName. It returns the address of the corresponding Nim proc marked with the base pragma. This allows Nim methods to override Godot’s virtual functions like _process or _physics_process while maintaining the C++ vtable compatibility Godot requires.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →