# How Virtual Methods Work in GDExt‑Nim: Overriding from Nim and GDScript

> Understand how GDExt-Nim maps Godot virtual methods to Nim procedures. Learn to override from both Nim and GDScript using the gdsync pragma and discover seamless method dispatch.

- Repository: [godot-nim 4+/gdext-nim](https://github.com/godot-nim/gdext-nim)
- Tags: internals
- Published: 2026-03-02

---

**GDExt‑Nim maps Godot virtual methods to Nim procedures using the `{.gdsync.}` pragma and a compile-time macro that generates emitters capable of dispatching to either Nim implementations or GDScript overrides.**

GDExt‑Nim bridges Godot’s GDExtension API with Nim, allowing developers to define engine-callable virtual methods—such as `_ready` or `_process`—directly in systems language code. This article explains the three-tier registration mechanism that connects Nim method declarations to Godot’s virtual method table, and demonstrates how to override these methods from both Nim subclasses and attached GDScript files.

## The Three Components of Virtual Method Dispatch

GDExt‑Nim implements virtual method support through tightly-coupled compile-time and runtime systems.

### Method Declarations with `{.gdsync.}`

You declare a virtual method by defining a Nim `method` with the `{.gdsync.}` pragma. Adding the `{.base.}` pragma marks the declaration as the root implementation in your class hierarchy.

```nim
type VirtualNode01* {.gdsync.} = ptr object of Node

method virtualMethod*(self: VirtualNode01; str: string): string {.gdsync, base.} =
  "virtualMethod of VirtualNode01 is called " & str

```

*Source:* `testproject/runtime/nim/src/classes/gdvirtualnode01.nim` lines 4–8.

### The sync_virtualDef Macro

When the compiler encounters a virtual method declaration, the `sync_virtualDef` macro—located in `src/gdext/private/userclass/virtuals.nim` (lines 70–71)—rewrites the definition at compile time. This macro parses the signature into a **MiddleExp**, generates an **emitter** that forwards calls to GDScript if a script override exists, and emits a registration call to `ClassDB.registerExtensionClassVirtualMethod`.

### Godot’s Virtual Method Table

At runtime, Godot queries a class-specific table of function pointers stored in the `GodotClassMeta` struct. The bridge code in `src/gdext/private/internalbridge.nim` (lines 104–119) implements `call_virtual_with_data_func`, which retrieves the stored pointer by method name and dispatches the call to the Nim-generated thunk.

## Registration Flow from Nim to Godot

The path from source code to engine registration follows four distinct steps:

1. **Declaration.** You write a Nim `method` with `{.gdsync, base.}`.
2. **Macro Expansion.** `sync_virtualDef` processes the AST, builds the emitter, and constructs the `virtualMethodInfo` metadata.
3. **ClassDB Registration.** The macro emits a call to `ClassDB.registerExtensionClassVirtualMethod`, inserting the method into Godot’s extension class virtual table.
4. **Runtime Resolution.** When Godot calls the virtual method, it invokes the pointer stored in `GodotClassMeta.virtualMethods`, which targets `call_virtual_with_data_func` in `internalbridge.nim`. This function casts the userdata to `ClassCallVirtual` and executes the generated emitter.

## Overriding Virtual Methods from Nim

To override a virtual method in a Nim subclass, inherit the base type and redeclare the method **without** the `{.base.}` pragma. The macro treats this as an override and updates the virtual method table entry for the new class.

```nim
type VirtualNode02* {.gdsync.} = ptr object of VirtualNode01

method virtualMethod*(self: VirtualNode02; str: string): string {.gdsync.} =
  "virtualMethod of VirtualNode02 is called " & str

```

*Source:* `testproject/runtime/nim/src/classes/gdvirtualnode02.nim` lines 5–9.

Because the registration step replaces the virtual-method table entry for `VirtualNode02` with the new Nim thunk, any engine call dispatches to this implementation unless a GDScript override is present.

## Overriding Virtual Methods from GDScript

GDScript can override Nim-defined virtual methods by implementing a function whose name matches Godot’s internal snake_case convention (typically prefixed with an underscore). The emitter generated by `sync_virtualDef` checks `self.hasScriptMethod(namesym)` before executing the Nim body.

```gdscript
extends VirtualNode01

func _virtual_method(str: String) -> String:
    return "virtualMethod of InheritedNode01 is called " + str

```

*Source:* `testproject/runtime/inherited_node01.gd` lines 1–5.

If the attached script defines `_virtual_method`, the emitter’s `callScriptMethod` logic redirects execution to GDScript. If no script override exists, the call falls back to the Nim implementation defined in the base class or subclass.

## Runtime Dispatch Architecture

The full call path demonstrates how GDExt‑Nim maintains compatibility with Godot’s scripting system:

1. The **engine** requests a call to `VirtualNode02._virtual_method`.
2. **GodotClassMeta** looks up `virtualMethods["virtual_method"]` and finds the pointer to `call_virtual_with_data_func`.
3. The **bridge** function casts the stored userdata to `ClassCallVirtual` and invokes it.
4. The **generated thunk** (created by `sync_virtualDef`) executes the emitter logic:
   - If `hasScriptMethod` returns true, the emitter calls the GDScript override.
   - Otherwise, it executes the Nim method body.

This architecture allows virtual methods to behave as first-class citizens, supporting pure Nim implementations, pure GDScript overrides, or mixed usage where GDScript calls `super` via `self.callScriptMethod`.

## Configuring Virtual Method Naming

By default, GDExt‑Nim uses the identifier as-is when registering virtual methods. To automatically convert Nim camelCase names to Godot’s internal `_snake_case` format, configure the formatter in `src/gdext/nameformats.nim` (lines 176–186):

```nim
import gdext/nameformats

proc set_formatters {.execon: EntryPoint.} =
  nameformats.defaultVirtualMethodFormatter = nameformats.toGodotInternalFuncCase

```

Setting `defaultVirtualMethodFormatter` to `toGodotInternalFuncCase` prepends an underscore and snake-cases the identifier, ensuring that a Nim method named `virtualMethod` registers as `_virtual_method` in Godot’s virtual table.

## Summary

- Declare base virtual methods in Nim using the `{.gdsync, base.}` pragma pair.
- Override in Nim subclasses by redeclaring the method with `{.gdsync.}` but omitting `{.base.}`.
- Override in GDScript by defining a function with the internal snake_case name (e.g., `_virtual_method`); the generated emitter automatically detects and dispatches to the script.
- The `sync_virtualDef` macro in `src/gdext/private/userclass/virtuals.nim` handles compile-time registration, while `src/gdext/private/internalbridge.nim` manages runtime dispatch via function pointers stored in `GodotClassMeta`.
- Use `defaultVirtualMethodFormatter` in `src/gdext/nameformats.nim` to automate naming convention conversion.

## Frequently Asked Questions

### What pragmas are required to expose a Nim method as a Godot virtual method?

You must annotate the method with `{.gdsync.}`. If the class is the base definition of the virtual method, add the `{.base.}` pragma as well. The `sync_virtualDef` macro detects these pragmas and generates the necessary registration code and runtime emitters.

### Can a GDScript attached to a Nim node completely replace the Nim implementation?

Yes. The emitter generated for every virtual method checks `self.hasScriptMethod` before executing the Nim body. If the attached GDScript defines the corresponding snake_case method (e.g., `_virtual_method`), the call redirects to the script. If no script override exists, execution falls back to the Nim implementation.

### How does GDExt‑Nim resolve the naming mismatch between Nim camelCase and Godot’s underscore-prefixed snake_case?

The library provides the `defaultVirtualMethodFormatter` variable in `src/gdext/nameformats.nim`. By default it uses `asIs`, but you can assign `toGodotInternalFuncCase` (typically during `EntryPoint` execution) to automatically prepend underscores and convert camelCase identifiers to snake_case when registering with Godot’s virtual method table.

### Where is the virtual method table stored at runtime?

Godot stores the virtual method pointers in the `GodotClassMeta` struct associated with each registered class. The bridge code in `src/gdext/private/internalbridge.nim` (lines 104–119) implements `call_virtual_with_data_func`, which retrieves the correct pointer by name from this metadata and dispatches the call to the Nim-generated thunk.