# Understanding Static Events in gdext-nim: Compile-Time Initialization Callbacks

> Learn how to use static events in gdext-nim for compile-time initialization callbacks. Explore macros like execon and expandEvent for zero-overhead registration.

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

---

**Static events in gdext-nim are a compile-time mechanism that uses macros to register and execute initialization callbacks before the Godot engine starts your extension, ensuring zero-overhead registration through the `execon` and `expandEvent` macros.**

Static events are the foundation of initialization in the godot-nim/gdext-nim bindings. These **compile-time callbacks** allow you to hook into Godot's extension lifecycle—such as `initialize_core` and `initialize_scene`—without writing manual registration boilerplate or incurring runtime overhead.

## What Are Static Events in gdext-nim?

Static events are **compile-time sequences** defined as `Event = CacheSeq` in `src/gdext/private/staticevents.nim`. They store callback identifiers during compilation and vanish at runtime, leaving behind only the generated initialization code.

### The Core Data Structure

In `src/gdext/private/staticevents.nim` at lines 7–8, the `event` helper creates these compile-time containers:

```nim
proc event*(name: string): Event = Event name

```

This constructor turns a string identifier into a static event handle. Because `Event` is a `CacheSeq`, it accumulates callbacks during the compilation phase but resolves to a flat list of procedure calls in the final binary.

### The Macro Pipeline: expandEvent and execon

Two macros form the engine of this system according to `src/gdext/private/staticevents.nim`:

**`expandEvent`** (lines 14–23) takes a static `Event` and a code block, then expands that block into a series of calls—one for each action recorded in the event. It guards against double expansion to ensure idempotent initialization.

**`execon`** (lines 41–53) generates a uniquely-named procedure, registers it with the given static event, and optionally emits debug information. The body you provide becomes the callback that runs when the event expands.

## How Static Events Execute During Extension Loading

Static events bridge the gap between Nim's macro system and Godot's extension lifecycle. The binding uses the `GDExtension_EntryPoint` template defined in `src/gdext.nim` (lines 88–106) to orchestrate this.

### Compile-Time Expansion

When you compile your extension, the entry point binds `expandEvent` and declares execution procedures for each built-in lifecycle stage:

```nim
template GDExtension_EntryPoint*: untyped =
  bind expandEvent
  proc exec_initialize_core {.expandEvent: initialize_core.}
  # ... additional initialization levels

```

Lines 94–101 in `src/gdext.nim` expand built-in events including `initialize_core` and `initialize_scene`. During compilation, the `expandEvent` macro replaces these placeholders with actual calls to every procedure registered via `execon`.

### Runtime Execution Flow

At runtime, Godot invokes the generated `exec_initialize_*` procedures when the corresponding initialization level is reached. For example, `exec_initialize_core()` is called from the `initializer` when `p_level == Initialization_Core`. These procedures, in turn, invoke your attached callbacks in the order they were registered.

## How to Use Static Events for Initialization Callbacks

You interact with static events through the `execon` macro for simple callbacks or by defining **contracts** for complex class registration.

### Attaching Callbacks to Built-in Events

Use the `execon` macro to attach procedures directly to gdext-nim's built-in lifecycle events. In `src/gdext/private/staticevents.nim`, the `execon` macro (lines 41–53) handles the registration automatically.

```nim

# src/my_extension.nim

import gdext
import gdext/private/staticevents

proc myCoreSetup() {.execon: staticevents.initialize_core.} =
  ## Runs automatically during Core initialization

  echo "Extension registered at core init"

```

When `GDExtension_EntryPoint` expands `initialize_core`, it generates a call to `myCoreSetup()` without any manual registration code.

### Custom Static Events for Class Contracts

For user-defined classes, the `invoke` template (lines 62–78 in `src/gdext/private/staticevents.nim`) automates registration of enums, virtual methods, and properties. This pattern is implemented in `src/gdext/private/internalbridge.nim` and you can replicate it for your own contracts:

```nim

# src/my_user_class.nim

import gdext/private/staticevents

type MyContract* = Contract[void]

template myEnums = event $MyContract.T & "::contract::enums"
template myVirtual = event $MyContract.T & "::contract::virtual"

template invoke*(contract: typedesc[MyContract]) =
  proc register_enums {.expandEvent: contract.myEnums.}
  proc register_virtual {.expandEvent: contract.myVirtual.}
  register_enums()
  register_virtual()
  static: invoked.incl $contract.T

proc registerMyEnums() {.execon: MyContract.myEnums.} =
  echo "Registering enums"

proc registerMyVirtual() {.execon: MyContract.myVirtual.} =
  echo "Registering virtual methods"

# Trigger the contract

invoke MyContract

```

This ensures that `registerMyEnums` and `registerMyVirtual` execute exactly once when the contract is invoked, with the compiler enforcing single expansion through the `invoked` static set.

## Summary

- **Static events** are compile-time `CacheSeq` containers defined in `src/gdext/private/staticevents.nim` that store initialization callbacks.
- The **`execon` macro** attaches your procedures to specific lifecycle points without runtime overhead.
- **`expandEvent`** generates the actual call sequence during compilation, guarding against double expansion.
- Built-in events like **`initialize_core`** and **`initialize_scene`** are expanded in `src/gdext.nim` via the `GDExtension_EntryPoint` template.
- For complex classes, the **`invoke`** template automates registration of multiple callbacks through custom contracts, as seen in `src/gdext/private/internalbridge.nim`.
- All static event processing happens at **compile time**; runtime sees only flat procedure calls.

## Frequently Asked Questions

### What is the difference between static events and runtime callbacks?

Static events are resolved entirely during compilation in `src/gdext/private/staticevents.nim` using Nim's macro system. They generate direct procedure calls in the binary, eliminating the need for runtime registration tables or dynamic dispatch. Runtime callbacks would require manual registration and storage in global lists that persist during execution.

### When should I create a custom static event instead of using a built-in one?

Create custom static events when you need **class-specific initialization stages** that don't map to Godot's core lifecycle. According to the implementation in `src/gdext/private/internalbridge.nim`, custom contracts allow you to group related callbacks—such as enum registration and virtual method binding—into logical units that fire together when you invoke the contract template.

### How does the system prevent duplicate initialization?

The `expandEvent` macro in `src/gdext/private/staticevents.nim` (lines 14–23) contains guards that prevent double expansion of the same event. Additionally, the `invoke` template tracks processed contracts in a static `invoked` set (line 78), ensuring that `register_*` procedures execute exactly once per compilation unit even if the contract is referenced multiple times.

### Can I use static events outside of GDExtension_EntryPoint?

While static events are designed for the extension initialization sequence defined in `src/gdext.nim`, you can use `expandEvent` and `execon` in any compile-time context. However, the callbacks will only execute if the event is explicitly expanded via `expandEvent` or triggered through a contract's `invoke` template, as the macros rely on static code generation rather than runtime discovery.