Engine Modules and Their Integration in Equilibrium Engine: Structure and Patterns

Equilibrium Engine modules follow a strict flecs-based pattern requiring a header with component declarations, a source file with an import function using ECS_MODULE, and integration via either static ECS_IMPORT or runtime ECS_IMPORT_HOT_RELOADABLE macros.

The clibequilibrium/equilibriumengine repository implements a modular Entity Component System (ECS) architecture built on top of the flecs library. Understanding the expected structure for engine modules and their integration is essential for extending the engine with new components, systems, or utilities while maintaining support for both static linking and runtime hot-reloading.

Module Structure and Conventions

Every module in the Equilibrium Engine adheres to a four-part convention that separates public interfaces from implementation details.

Header Files and Component Declarations

Header files located in equilibrium/components/*.h or equilibrium/systems/*.h provide the public API and forward-declare flecs components using ECS_COMPONENT_DECLARE. For example, equilibrium/components/transform.h declares transform-related components and types, establishing the contract that other modules or game code will depend on.

Source Files and Module Registration

Source files located in equilibrium/components/*.c or equilibrium/systems/*.c define the module implementation. They call ECS_MODULE(world, <ModuleName>) to register the module with the flecs world, use ECS_IMPORT to pull in dependencies, and create components via ECS_COMPONENT_DEFINE. The equilibrium/components/transform.c file demonstrates this pattern by registering the TransformComponents module and defining its associated component storage.

The Import Function Pattern

Each module must export a single import function named <ModuleName>Import that accepts a world_t *world parameter. This function serves as the entry point for the module. For instance, TransformComponentsImport in equilibrium/components/transform.c initializes the module when called by the engine or a parent module.

Hot-Reload Support

Modules intended for dynamic reloading implement the HotReloadableModule component defined in equilibrium/components/hot_reloadable_module.h. This optional marker allows the engine to track module entities and clean them up when the underlying shared library changes, enabling iterative development without restarting the application.

Integration Patterns

The engine supports two distinct integration paths depending on whether the module is built into the binary or loaded as a plugin.

Static Compilation and Linking

For modules compiled directly into the engine, integration occurs at engine initialization using the ECS_IMPORT macro. In equilibrium/engine.c, the engine_init() function imports built-in modules like FlecsMonitor and custom engine modules before the main loop begins. This approach requires only that the module's import function be linked into the final executable.

/* equilibrium/engine.c */
ECS_TAG_DEFINE(engine.world, Entity);
ECS_IMPORT(engine.world, FlecsMonitor);   // built-in flecs monitoring
ECS_IMPORT(engine.world, TransformComponents); // custom engine module

Runtime Hot-Reloading

For plugin-style development, the engine provides the ECS_IMPORT_HOT_RELOADABLE macro defined in equilibrium/components/hot_reloadable_module.h. This macro performs three critical actions: it imports the specified module and the hot-reloadable helper component, creates a hidden entity storing a HotReloadableModule component linked to the plugin's cr_plugin context, and tracks the module name for cleanup during reload cycles.

/* editor/editor.c */
ECS_IMPORT_HOT_RELOADABLE(ctx, ImguiBgfxSdlSystem);
ECS_IMPORT_HOT_RELOADABLE(ctx, ImguiDockspaceSystem);

When the plugin receives a CR_LOAD operation, the macro expands to call reload_modules(ctx), which iterates over all HotReloadableModule entities, deletes their associated flecs entities to prevent stale data, and re-imports the fresh code.

Engine Entry Points and Lifecycle

Understanding where modules fit into the engine lifecycle requires examining three key files that manage initialization, updating, and plugin bridging.

Core Engine Initialization

The equilibrium/engine.c file defines the engine_t façade and contains engine_init(), which creates the flecs world, registers core tags like Entity, and stores the world pointer used by all subsequent module imports. The companion engine_update() function runs system queries each frame, processing all active ECS systems.

Plugin Simulation Bridge

The equilibrium/engine-simulation/engine_simulation.c file provides the glue between the engine and the cr plugin system. It forwards CR_STEP events to engine_update(), allowing hot-reloadable modules to participate in the frame update loop while maintaining their dynamic loading capabilities.

Editor Integration Example

The editor/editor.c file demonstrates practical hot-reloadable usage in a real application. It imports UI systems as plugins and performs cleanup routines during reload events, serving as a reference implementation for developers building their own hot-reloadable tools.

Practical Implementation Examples

The following examples illustrate creating a new physics module and integrating it through both static and hot-reloadable paths.

Example 1: Creating a Component Module

First, define the header in physics_component.h:

#ifndef PHYSICS_COMPONENT_H
#define PHYSICS_COMPONENT_H

#include "base.h"

ECS_COMPONENT_DECLARE(RigidBody);
EQUILIBRIUM_API void PhysicsComponentsImport(world_t *world);

#endif

Next, implement the module in physics_component.c:

#include "physics_component.h"

ECS_COMPONENT_DEFINE(world, RigidBody);

void PhysicsComponentsImport(world_t *world) {
    ECS_MODULE(world, PhysicsComponents);
    ECS_IMPORT(world, CglmComponents);   // reuse glm types
    ECS_COMPONENT_DEFINE(world, RigidBody);
}

Integrate statically in engine_init:

ECS_IMPORT(engine.world, PhysicsComponents);

Or integrate as hot-reloadable in a plugin:

ECS_IMPORT_HOT_RELOADABLE(ctx, PhysicsComponents);

Example 2: Implementing a Dependent System

Create a system that operates on the physics component in physics_system.c:

#include "physics_component.h"

void SimulatePhysics(ecs_iter_t *it) {
    RigidBody *rb = ecs_field(it, RigidBody, 1);
    for (int i = 0; i < it->count; ++i) {
        /* simple Euler integration */
        rb[i].position[0] += rb[i].velocity[0] * it->delta_time;
        rb[i].position[1] += rb[i].velocity[1] * it->delta_time;
        rb[i].position[2] += rb[i].velocity[2] * it->delta_time;
    }
}

void PhysicsSystemImport(world_t *world) {
    ECS_MODULE(world, PhysicsSystem);
    ECS_IMPORT(world, PhysicsComponents);

    ECS_SYSTEM(world, SimulatePhysics, EcsOnUpdate,
               [in] physics.components.RigidBody);
}

Summary

  • Module structure requires a header with ECS_COMPONENT_DECLARE, a source file with ECS_MODULE and ECS_COMPONENT_DEFINE, and an import function named <ModuleName>Import.
  • Static integration uses ECS_IMPORT(world, ModuleName) during engine initialization in equilibrium/engine.c.
  • Hot-reloadable integration uses ECS_IMPORT_HOT_RELOADABLE(ctx, ModuleName) to enable runtime reloading via the HotReloadableModule component.
  • Dependencies between modules are resolved using ECS_IMPORT inside the import function before defining components or systems.
  • Key files include equilibrium/components/transform.c for component patterns, equilibrium/components/hot_reloadable_module.c for reloading infrastructure, and editor/editor.c for practical usage examples.

Frequently Asked Questions

What is the minimum required structure for a new Equilibrium Engine module?

Every module requires a header file containing ECS_COMPONENT_DECLARE statements and an import function prototype, plus a source file implementing the import function with ECS_MODULE(world, ModuleName) and ECS_COMPONENT_DEFINE calls. This pattern is visible in equilibrium/components/transform.h and equilibrium/components/transform.c.

How does hot-reloading work with engine modules?

Hot-reloading leverages the ECS_IMPORT_HOT_RELOADABLE macro from equilibrium/components/hot_reloadable_module.h, which creates a tracking entity with a HotReloadableModule component. When the plugin reloads, the engine iterates these entities, cleans up stale flecs data, and re-imports the fresh code without restarting the application.

Can modules depend on other modules?

Yes, modules declare dependencies by calling ECS_IMPORT(world, DependencyName) inside their import function before using components from that dependency. For example, PhysicsComponentsImport imports CglmComponents to access math types before defining physics components that use them.

Where is the best place to look for a complete module example?

The equilibrium/components/transform.c file provides the canonical pattern for component modules, while equilibrium/systems/transform_system.c demonstrates system modules that import component modules. For hot-reloadable integration patterns, examine editor/editor.c which imports UI systems dynamically.

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 →