The Complete Guide to ECS-Driven and Immediate Mode Graphics Programming

Table of Contents

  1. Introduction to Data-Oriented Design
  2. ECS Architecture Fundamentals
  3. Setting Up the ECS Framework
  4. Immediate Mode Concepts
  5. Building Your First ECS Engine
  6. Component Design Patterns
  7. System Architecture
  8. Immediate Mode Rendering
  9. Immediate Mode GUI Integration
  10. Memory Management in ECS
  11. Advanced ECS Patterns
  12. Data-Oriented Performance
  13. Physics System with ECS
  14. Rendering System with ECS
  15. Input System with ECS
  16. Audio System with ECS
  17. Particle System with ECS
  18. Collision System with ECS
  19. Complete Game Examples
  20. Production-Ready ECS Engine

Chapter 1: Introduction to Data-Oriented Design

1.1 Why ECS?

Traditional Object-Oriented Programming (OOP) for games leads to:

  • Deep inheritance hierarchies
  • Cache-unfriendly memory layouts
  • Difficulty sharing behavior
  • Performance bottlenecks

Entity Component System (ECS) offers:

  • Composition over inheritance - Build entities from components
  • Data locality - Components packed in memory for cache efficiency
  • Parallelization - Systems operate on component arrays independently
  • Flexibility - Add/remove components at runtime

1.2 ECS Philosophy

Entity: Just an ID (unsigned integer)
Component: Pure data (no behavior)
System: Pure logic (operates on components)

Example:

Entity 42:
  - Position{x: 100, y: 200}
  - Velocity{vx: 50, vy: -30}
  - Sprite{textureID: 5}
  
MovementSystem processes ALL entities with Position + Velocity
RenderSystem processes ALL entities with Position + Sprite

This separation enables:

  • Easy serialization (components are just data)
  • Simple networking (send component updates)
  • Hot-reloading (reload systems without touching data)
  • Multithreading (systems don't share state)

1.3 Immediate Mode Philosophy

Retained Mode (traditional):

Sprite* sprite = createSprite(texture);
setPosition(sprite, x, y);
// Sprite persists between frames
// Must explicitly update and manage state

Immediate Mode:

// Each frame, just describe what you want
drawSprite(texture, x, y);
// No persistent state, no memory management
// Just declare intent

Benefits:

  • Simpler mental model
  • No stale state bugs
  • Easier debugging
  • More declarative code

1.4 Combining ECS + Immediate Mode

We'll build a system where:

  • Data lives in ECS components (retained)
  • Rendering is immediate mode (no retained graphics state)
  • GUI is immediate mode (no widget objects)
  • Systems process data and issue immediate commands

This gives us the best of both worlds: cache-friendly data with simple, declarative rendering.


Chapter 2: ECS Architecture Fundamentals

2.1 Core ECS Concepts

// ecs_core.h
#ifndef ECS_CORE_H
#define ECS_CORE_H

#include <stdint.h>
#include <stdbool.h>

// Entity is just an ID
typedef uint32_t Entity;
#define ENTITY_NULL 0
#define MAX_ENTITIES 10000

// Component type identifier
typedef uint32_t ComponentType;
#define MAX_COMPONENT_TYPES 64

// Component mask for quick queries
typedef uint64_t ComponentMask;

// Signature = which components an entity has
typedef ComponentMask Signature;

#endif // ECS_CORE_H

2.2 Memory Layout for Cache Efficiency

Bad (OOP style):

// Objects scattered in memory
struct GameObject {
    Position pos;      // 8 bytes
    Velocity vel;      // 8 bytes
    Sprite sprite;     // 16 bytes
    Health health;     // 4 bytes
    AI ai;            // 32 bytes
    // GameObject is 68 bytes, mostly unused by most systems
};

GameObject objects[1000]; // Huge waste when iterating

Good (ECS style):

// Components packed by type
Position positions[1000];  // 8KB contiguous
Velocity velocities[1000]; // 8KB contiguous
Sprite sprites[1000];      // 16KB contiguous

// System only touches what it needs
void MovementSystem() {
    for (int i = 0; i < count; i++) {
        positions[i].x += velocities[i].x * dt;
        positions[i].y += velocities[i].y * dt;
        // Perfect cache line utilization!
    }
}

2.3 Archetypes vs Sparse Sets

Two main ECS implementations:

1. Archetype-based (Unity DOTS, Bevy):

  • Group entities by component signature
  • Extremely cache-friendly
  • Costly to add/remove components
  • Best for stable entity compositions

2. Sparse Set (EnTT):

  • Each component type stored separately
  • Fast component add/remove
  • Good cache usage
  • More flexible

We'll implement both approaches!

2.4 Basic ECS Structure

// ecs_types.h
#include <stdint.h>

// Forward declarations
typedef struct World World;
typedef struct ComponentArray ComponentArray;
typedef struct System System;

// Component data stored by type
struct ComponentArray {
    void* data;              // Raw component data
    uint32_t* entityToIndex; // Entity ID -> array index
    Entity* indexToEntity;   // Array index -> Entity ID
    uint32_t size;           // Current number of components
    uint32_t componentSize;  // Size of each component in bytes
};

// System function signature
typedef void (*SystemFunc)(World* world, float dt);

struct System {
    const char* name;
    SystemFunc update;
    Signature signature;     // Which components this system needs
    int priority;            // Update order
};

struct World {
    // Entity management
    Entity nextEntity;
    Signature signatures[MAX_ENTITIES];
    
    // Component storage
    ComponentArray* componentArrays[MAX_COMPONENT_TYPES];
    uint32_t componentCount;
    
    // System management
    System systems[64];
    uint32_t systemCount;
    
    // Frame data
    float deltaTime;
    uint64_t frameCount;
};

Chapter 3: Setting Up the ECS Framework

3.1 World Creation and Management

// world.c
#include "ecs_types.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

World* createWorld() {
    World* world = calloc(1, sizeof(World));
    world->nextEntity = 1; // 0 is ENTITY_NULL
    world->componentCount = 0;
    world->systemCount = 0;
    world->frameCount = 0;
    
    return world;
}

Entity createEntity(World* world) {
    if (world->nextEntity >= MAX_ENTITIES) {
        printf("Error: Max entities reached!\n");
        return ENTITY_NULL;
    }
    
    Entity entity = world->nextEntity++;
    world->signatures[entity] = 0;
    
    return entity;
}

void destroyEntity(World* world, Entity entity) {
    if (entity == ENTITY_NULL || entity >= world->nextEntity) return;
    
    // Remove from all component arrays
    for (uint32_t i = 0; i < world->componentCount; i++) {
        ComponentArray* array = world->componentArrays[i];
        if (array && array->entityToIndex[entity] != UINT32_MAX) {
            removeComponentFromArray(array, entity);
        }
    }
    
    world->signatures[entity] = 0;
}

void destroyWorld(World* world) {
    // Free all component arrays
    for (uint32_t i = 0; i < world->componentCount; i++) {
        if (world->componentArrays[i]) {
            free(world->componentArrays[i]->data);
            free(world->componentArrays[i]->entityToIndex);
            free(world->componentArrays[i]->indexToEntity);
            free(world->componentArrays[i]);
        }
    }
    
    free(world);
}

3.2 Component Registration

// component_registry.c

ComponentType registerComponent(World* world, size_t componentSize) {
    if (world->componentCount >= MAX_COMPONENT_TYPES) {
        printf("Error: Max component types reached!\n");
        return UINT32_MAX;
    }
    
    ComponentType type = world->componentCount++;
    
    ComponentArray* array = calloc(1, sizeof(ComponentArray));
    array->data = calloc(MAX_ENTITIES, componentSize);
    array->entityToIndex = malloc(MAX_ENTITIES * sizeof(uint32_t));
    array->indexToEntity = malloc(MAX_ENTITIES * sizeof(Entity));
    array->size = 0;
    array->componentSize = componentSize;
    
    // Initialize entityToIndex to UINT32_MAX (not present)
    for (uint32_t i = 0; i < MAX_ENTITIES; i++) {
        array->entityToIndex[i] = UINT32_MAX;
    }
    
    world->componentArrays[type] = array;
    
    return type;
}

void* addComponent(World* world, Entity entity, ComponentType type, void* data) {
    if (type >= world->componentCount) return NULL;
    
    ComponentArray* array = world->componentArrays[type];
    
    // Check if entity already has this component
    if (array->entityToIndex[entity] != UINT32_MAX) {
        printf("Warning: Entity already has this component\n");
        return NULL;
    }
    
    // Add component
    uint32_t index = array->size++;
    array->entityToIndex[entity] = index;
    array->indexToEntity[index] = entity;
    
    void* componentPtr = (char*)array->data + (index * array->componentSize);
    if (data) {
        memcpy(componentPtr, data, array->componentSize);
    }
    
    // Update entity signature
    world->signatures[entity] |= (1ULL << type);
    
    return componentPtr;
}

void* getComponent(World* world, Entity entity, ComponentType type) {
    if (type >= world->componentCount) return NULL;
    
    ComponentArray* array = world->componentArrays[type];
    uint32_t index = array->entityToIndex[entity];
    
    if (index == UINT32_MAX) return NULL;
    
    return (char*)array->data + (index * array->componentSize);
}

bool hasComponent(World* world, Entity entity, ComponentType type) {
    if (type >= world->componentCount) return false;
    return (world->signatures[entity] & (1ULL << type)) != 0;
}

void removeComponent(World* world, Entity entity, ComponentType type) {
    if (type >= world->componentCount) return;
    
    ComponentArray* array = world->componentArrays[type];
    uint32_t index = array->entityToIndex[entity];
    
    if (index == UINT32_MAX) return;
    
    // Swap with last element
    uint32_t lastIndex = array->size - 1;
    Entity lastEntity = array->indexToEntity[lastIndex];
    
    // Copy last element to removed position
    void* removedPos = (char*)array->data + (index * array->componentSize);
    void* lastPos = (char*)array->data + (lastIndex * array->componentSize);
    memcpy(removedPos, lastPos, array->componentSize);
    
    // Update mappings
    array->entityToIndex[lastEntity] = index;
    array->indexToEntity[index] = lastEntity;
    array->entityToIndex[entity] = UINT32_MAX;
    
    array->size--;
    
    // Update entity signature
    world->signatures[entity] &= ~(1ULL << type);
}

3.3 System Registration and Execution

// system_manager.c

void registerSystem(World* world, const char* name, SystemFunc update, 
                    Signature signature, int priority) {
    if (world->systemCount >= 64) {
        printf("Error: Max systems reached!\n");
        return;
    }
    
    System* sys = &world->systems[world->systemCount++];
    sys->name = name;
    sys->update = update;
    sys->signature = signature;
    sys->priority = priority;
}

// Sort systems by priority
int compareSystemPriority(const void* a, const void* b) {
    return ((System*)a)->priority - ((System*)b)->priority;
}

void sortSystems(World* world) {
    qsort(world->systems, world->systemCount, sizeof(System), 
          compareSystemPriority);
}

void updateSystems(World* world, float deltaTime) {
    world->deltaTime = deltaTime;
    world->frameCount++;
    
    for (uint32_t i = 0; i < world->systemCount; i++) {
        world->systems[i].update(world, deltaTime);
    }
}

3.4 Query System for Efficient Iteration

// query.c

// Query iterator
typedef struct {
    World* world;
    Signature signature;
    Entity currentEntity;
    uint32_t entityIndex;
} Query;

Query createQuery(World* world, Signature signature) {
    Query query;
    query.world = world;
    query.signature = signature;
    query.currentEntity = ENTITY_NULL;
    query.entityIndex = 0;
    return query;
}

Entity queryNext(Query* query) {
    while (query->entityIndex < query->world->nextEntity) {
        Entity entity = query->entityIndex++;
        
        // Check if entity has all required components
        if ((query->world->signatures[entity] & query->signature) == query->signature) {
            query->currentEntity = entity;
            return entity;
        }
    }
    
    return ENTITY_NULL;
}

// Helper macro for iteration
#define QUERY_EACH(world, sig, entity) \
    for (Query _q = createQuery(world, sig); \
         ((entity) = queryNext(&_q)) != ENTITY_NULL;)

// Usage example:
void exampleSystem(World* world, float dt) {
    Signature sig = (1 << COMPONENT_POSITION) | (1 << COMPONENT_VELOCITY);
    
    Entity entity;
    QUERY_EACH(world, sig, entity) {
        Position* pos = getComponent(world, entity, COMPONENT_POSITION);
        Velocity* vel = getComponent(world, entity, COMPONENT_VELOCITY);
        
        pos->x += vel->x * dt;
        pos->y += vel->y * dt;
    }
}

Chapter 4: Immediate Mode Concepts

4.1 Immediate vs Retained Mode

Retained Mode (Traditional):

// Create and store state
Button* button = createButton("Click Me", 100, 100);
addButton(gui, button);

// Each frame
updateGUI(gui);
renderGUI(gui);

// Must manually clean up
destroyButton(button);

Immediate Mode:

// Each frame, just describe what you want
if (button("Click Me", 100, 100)) {
    // Button was clicked
}
// No state, no memory management, no lifecycle

4.2 Building an Immediate Mode API

// im_render.h - Immediate Mode Rendering API
#ifndef IM_RENDER_H
#define IM_RENDER_H

#include <SDL2/SDL.h>
#include <stdbool.h>

// Immediate mode rendering context
typedef struct {
    SDL_Renderer* renderer;
    
    // Current state (reset each frame)
    SDL_Color currentColor;
    float currentAlpha;
    SDL_BlendMode currentBlendMode;
    
    // Draw command buffer
    struct DrawCommand* commands;
    int commandCount;
    int commandCapacity;
    
    // Current layer for sorting
    int currentLayer;
} IMRenderContext;

// Initialize immediate mode renderer
IMRenderContext* imCreateContext(SDL_Renderer* renderer);
void imDestroyContext(IMRenderContext* ctx);

// Frame management
void imBeginFrame(IMRenderContext* ctx);
void imEndFrame(IMRenderContext* ctx);

// State management (immediate)
void imSetColor(IMRenderContext* ctx, uint8_t r, uint8_t g, uint8_t b, uint8_t a);
void imSetAlpha(IMRenderContext* ctx, float alpha);
void imSetBlendMode(IMRenderContext* ctx, SDL_BlendMode mode);
void imSetLayer(IMRenderContext* ctx, int layer);

// Drawing primitives (immediate - no retained state)
void imDrawRect(IMRenderContext* ctx, float x, float y, float w, float h, bool filled);
void imDrawCircle(IMRenderContext* ctx, float x, float y, float radius, bool filled);
void imDrawLine(IMRenderContext* ctx, float x1, float y1, float x2, float y2, float thickness);
void imDrawTriangle(IMRenderContext* ctx, float x1, float y1, float x2, float y2, 
                    float x3, float y3, bool filled);

// Texture drawing (immediate)
void imDrawTexture(IMRenderContext* ctx, SDL_Texture* texture, 
                   float x, float y, float w, float h, float rotation);
void imDrawTextureRegion(IMRenderContext* ctx, SDL_Texture* texture,
                         SDL_Rect* src, float x, float y, float w, float h, float rotation);

#endif // IM_RENDER_H

4.3 Implementing Immediate Rendering

// im_render.c

typedef enum {
    DRAW_CMD_RECT,
    DRAW_CMD_CIRCLE,
    DRAW_CMD_LINE,
    DRAW_CMD_TRIANGLE,
    DRAW_CMD_TEXTURE
} DrawCommandType;

typedef struct DrawCommand {
    DrawCommandType type;
    int layer;
    SDL_Color color;
    float alpha;
    SDL_BlendMode blendMode;
    
    union {
        struct { float x, y, w, h; bool filled; } rect;
        struct { float x, y, radius; bool filled; } circle;
        struct { float x1, y1, x2, y2, thickness; } line;
        struct { float x1, y1, x2, y2, x3, y3; bool filled; } triangle;
        struct { 
            SDL_Texture* texture;
            SDL_Rect src;
            float x, y, w, h, rotation;
        } texture;
    };
} DrawCommand;

IMRenderContext* imCreateContext(SDL_Renderer* renderer) {
    IMRenderContext* ctx = calloc(1, sizeof(IMRenderContext));
    ctx->renderer = renderer;
    ctx->commandCapacity = 10000;
    ctx->commands = malloc(sizeof(DrawCommand) * ctx->commandCapacity);
    ctx->currentColor = (SDL_Color){255, 255, 255, 255};
    ctx->currentAlpha = 1.0f;
    ctx->currentBlendMode = SDL_BLENDMODE_BLEND;
    ctx->currentLayer = 0;
    return ctx;
}

void imBeginFrame(IMRenderContext* ctx) {
    ctx->commandCount = 0;
    ctx->currentLayer = 0;
}

void imSetColor(IMRenderContext* ctx, uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
    ctx->currentColor = (SDL_Color){r, g, b, a};
}

void imDrawRect(IMRenderContext* ctx, float x, float y, float w, float h, bool filled) {
    if (ctx->commandCount >= ctx->commandCapacity) return;
    
    DrawCommand* cmd = &ctx->commands[ctx->commandCount++];
    cmd->type = DRAW_CMD_RECT;
    cmd->layer = ctx->currentLayer;
    cmd->color = ctx->currentColor;
    cmd->alpha = ctx->currentAlpha;
    cmd->blendMode = ctx->currentBlendMode;
    cmd->rect.x = x;
    cmd->rect.y = y;
    cmd->rect.w = w;
    cmd->rect.h = h;
    cmd->rect.filled = filled;
}

// Comparison function for sorting by layer
int compareDrawCommands(const void* a, const void* b) {
    return ((DrawCommand*)a)->layer - ((DrawCommand*)b)->layer;
}

void imEndFrame(IMRenderContext* ctx) {
    // Sort commands by layer
    qsort(ctx->commands, ctx->commandCount, sizeof(DrawCommand), 
          compareDrawCommands);
    
    // Execute all draw commands
    for (int i = 0; i < ctx->commandCount; i++) {
        DrawCommand* cmd = &ctx->commands[i];
        
        SDL_SetRenderDrawColor(ctx->renderer, 
                              cmd->color.r, cmd->color.g, cmd->color.b,
                              (uint8_t)(cmd->color.a * cmd->alpha));
        SDL_SetRenderDrawBlendMode(ctx->renderer, cmd->blendMode);
        
        switch (cmd->type) {
            case DRAW_CMD_RECT: {
                SDL_Rect rect = {
                    (int)cmd->rect.x, (int)cmd->rect.y,
                    (int)cmd->rect.w, (int)cmd->rect.h
                };
                if (cmd->rect.filled) {
                    SDL_RenderFillRect(ctx->renderer, &rect);
                } else {
                    SDL_RenderDrawRect(ctx->renderer, &rect);
                }
                break;
            }
            
            case DRAW_CMD_CIRCLE: {
                // Draw circle using midpoint algorithm
                int x0 = (int)cmd->circle.x;
                int y0 = (int)cmd->circle.y;
                int radius = (int)cmd->circle.radius;
                
                int x = radius;
                int y = 0;
                int err = 0;
                
                while (x >= y) {
                    if (cmd->circle.filled) {
                        SDL_RenderDrawLine(ctx->renderer, x0 - x, y0 + y, x0 + x, y0 + y);
                        SDL_RenderDrawLine(ctx->renderer, x0 - x, y0 - y, x0 + x, y0 - y);
                        SDL_RenderDrawLine(ctx->renderer, x0 - y, y0 + x, x0 + y, y0 + x);
                        SDL_RenderDrawLine(ctx->renderer, x0 - y, y0 - x, x0 + y, y0 - x);
                    } else {
                        SDL_RenderDrawPoint(ctx->renderer, x0 + x, y0 + y);
                        SDL_RenderDrawPoint(ctx->renderer, x0 + y, y0 + x);
                        SDL_RenderDrawPoint(ctx->renderer, x0 - y, y0 + x);
                        SDL_RenderDrawPoint(ctx->renderer, x0 - x, y0 + y);
                        SDL_RenderDrawPoint(ctx->renderer, x0 - x, y0 - y);
                        SDL_RenderDrawPoint(ctx->renderer, x0 - y, y0 - x);
                        SDL_RenderDrawPoint(ctx->renderer, x0 + y, y0 - x);
                        SDL_RenderDrawPoint(ctx->renderer, x0 + x, y0 - y);
                    }
                    
                    if (err <= 0) {
                        y++;
                        err += 2*y + 1;
                    }
                    if (err > 0) {
                        x--;
                        err -= 2*x + 1;
                    }
                }
                break;
            }
            
            case DRAW_CMD_LINE: {
                SDL_RenderDrawLine(ctx->renderer,
                    (int)cmd->line.x1, (int)cmd->line.y1,
                    (int)cmd->line.x2, (int)cmd->line.y2);
                break;
            }
            
            case DRAW_CMD_TEXTURE: {
                SDL_Rect dest = {
                    (int)cmd->texture.x, (int)cmd->texture.y,
                    (int)cmd->texture.w, (int)cmd->texture.h
                };
                
                SDL_SetTextureAlphaMod(cmd->texture.texture, 
                    (uint8_t)(255 * cmd->alpha));
                SDL_SetTextureColorMod(cmd->texture.texture,
                    cmd->color.r, cmd->color.g, cmd->color.b);
                
                SDL_RenderCopyEx(ctx->renderer, cmd->texture.texture,
                    &cmd->texture.src, &dest, cmd->texture.rotation,
                    NULL, SDL_FLIP_NONE);
                break;
            }
        }
    }
}

void imDrawTexture(IMRenderContext* ctx, SDL_Texture* texture,
                   float x, float y, float w, float h, float rotation) {
    int tw, th;
    SDL_QueryTexture(texture, NULL, NULL, &tw, &th);
    SDL_Rect src = {0, 0, tw, th};
    imDrawTextureRegion(ctx, texture, &src, x, y, w, h, rotation);
}

void imDrawTextureRegion(IMRenderContext* ctx, SDL_Texture* texture,
                         SDL_Rect* src, float x, float y, float w, float h, 
                         float rotation) {
    if (ctx->commandCount >= ctx->commandCapacity) return;
    
    DrawCommand* cmd = &ctx->commands[ctx->commandCount++];
    cmd->type = DRAW_CMD_TEXTURE;
    cmd->layer = ctx->currentLayer;
    cmd->color = ctx->currentColor;
    cmd->alpha = ctx->currentAlpha;
    cmd->blendMode = ctx->currentBlendMode;
    cmd->texture.texture = texture;
    cmd->texture.src = *src;
    cmd->texture.x = x;
    cmd->texture.y = y;
    cmd->texture.w = w;
    cmd->texture.h = h;
    cmd->texture.rotation = rotation;
}

Chapter 5: Building Your First ECS Engine

5.1 Define Core Components

// components.h
#ifndef COMPONENTS_H
#define COMPONENTS_H

#include <SDL2/SDL.h>
#include <stdbool.h>

// Component type IDs
typedef enum {
    COMPONENT_POSITION = 0,
    COMPONENT_VELOCITY,
    COMPONENT_SPRITE,
    COMPONENT_COLLISION,
    COMPONENT_LIFETIME,
    COMPONENT_PLAYER,
    COMPONENT_ENEMY,
    COMPONENT_BULLET,
    COMPONENT_HEALTH,
    COMPONENT_INPUT,
    COMPONENT_COUNT
} ComponentTypeID;

// Position component
typedef struct {
    float x, y;
    float rotation;
} Position;

// Velocity component
typedef struct {
    float x, y;
    float angular; // Rotation speed
} Velocity;

// Sprite component
typedef struct {
    SDL_Texture* texture;
    SDL_Rect srcRect;
    float width, height;
    SDL_Color tint;
    int layer;
} Sprite;

// Collision component
typedef struct {
    float radius;      // For circle collision
    bool isTrigger;    // Doesn't resolve, just detects
    uint32_t layer;    // Collision layer mask
} Collision;

// Lifetime component
typedef struct {
    float remaining;   // Seconds remaining
    bool destroyOnExpire;
} Lifetime;

// Player tag component (empty, just marks entity as player)
typedef struct {
    int playerID;
} Player;

// Enemy tag
typedef struct {
    int enemyType;
} Enemy;

// Bullet component
typedef struct {
    float damage;
    Entity owner;  // Who fired this bullet
} Bullet;

// Health component
typedef struct {
    float current;
    float maximum;
    bool invulnerable;
    float invulnerabilityTimer;
} Health;

// Input component
typedef struct {
    float moveX, moveY;    // Movement input (-1 to 1)
    bool shoot;
    bool jump;
    bool action;
} Input;

#endif // COMPONENTS_H

5.2 Initialize the Engine

// engine.c
#include "ecs_types.h"
#include "components.h"
#include "im_render.h"

typedef struct {
    SDL_Window* window;
    SDL_Renderer* renderer;
    IMRenderContext* imContext;
    World* world;
    bool running;
    
    // Component type IDs
    ComponentType componentTypes[COMPONENT_COUNT];
    
    // Resources
    SDL_Texture* textures[256];
    int textureCount;
} Engine;

Engine* createEngine(int width, int height, const char* title) {
    Engine* engine = calloc(1, sizeof(Engine));
    
    // Initialize SDL
    SDL_Init(SDL_INIT_VIDEO);
    engine->window = SDL_CreateWindow(title,
        SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
        width, height, SDL_WINDOW_SHOWN);
    engine->renderer = SDL_CreateRenderer(engine->window, -1,
        SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
    
    // Create immediate mode context
    engine->imContext = imCreateContext(engine->renderer);
    
    // Create ECS world
    engine->world = createWorld();
    
    // Register component types
    engine->componentTypes[COMPONENT_POSITION] = 
        registerComponent(engine->world, sizeof(Position));
    engine->componentTypes[COMPONENT_VELOCITY] = 
        registerComponent(engine->world, sizeof(Velocity));
    engine->componentTypes[COMPONENT_SPRITE] = 
        registerComponent(engine->world, sizeof(Sprite));
    engine->componentTypes[COMPONENT_COLLISION] = 
        registerComponent(engine->world, sizeof(Collision));
    engine->componentTypes[COMPONENT_LIFETIME] = 
        registerComponent(engine->world, sizeof(Lifetime));
    engine->componentTypes[COMPONENT_PLAYER] = 
        registerComponent(engine->world, sizeof(Player));
    engine->componentTypes[COMPONENT_ENEMY] = 
        registerComponent(engine->world, sizeof(Enemy));
    engine->componentTypes[COMPONENT_BULLET] = 
        registerComponent(engine->world, sizeof(Bullet));
    engine->componentTypes[COMPONENT_HEALTH] = 
        registerComponent(engine->world, sizeof(Health));
    engine->componentTypes[COMPONENT_INPUT] = 
        registerComponent(engine->world, sizeof(Input));
    
    engine->running = true;
    engine->textureCount = 0;
    
    return engine;
}

void destroyEngine(Engine* engine) {
    // Free textures
    for (int i = 0; i < engine->textureCount; i++) {
        SDL_DestroyTexture(engine->textures[i]);
    }
    
    destroyWorld(engine->world);
    imDestroyContext(engine->imContext);
    SDL_DestroyRenderer(engine->renderer);
    SDL_DestroyWindow(engine->window);
    SDL_Quit();
    free(engine);
}

SDL_Texture* loadTexture(Engine* engine, const char* path) {
    SDL_Surface* surface = SDL_LoadBMP(path);
    if (!surface) return NULL;
    
    SDL_Texture* texture = SDL_CreateTextureFromSurface(engine->renderer, surface);
    SDL_FreeSurface(surface);
    
    if (engine->textureCount < 256) {
        engine->textures[engine->textureCount++] = texture;
    }
    
    return texture;
}

5.3 Core Systems Implementation

// systems.c

// Movement System
void movementSystem(World* world, float dt) {
    Signature sig = (1ULL << COMPONENT_POSITION) | (1ULL << COMPONENT_VELOCITY);
    
    Entity entity;
    QUERY_EACH(world, sig, entity) {
        Position* pos = getComponent(world, entity, COMPONENT_POSITION);
        Velocity* vel = getComponent(world, entity, COMPONENT_VELOCITY);
        
        pos->x += vel->x * dt;
        pos->y += vel->y * dt;
        pos->rotation += vel->angular * dt;
    }
}

// Lifetime System
void lifetimeSystem(World* world, float dt) {
    Signature sig = (1ULL << COMPONENT_LIFETIME);
    
    Entity entity;
    QUERY_EACH(world, sig, entity) {
        Lifetime* lifetime = getComponent(world, entity, COMPONENT_LIFETIME);
        
        lifetime->remaining -= dt;
        
        if (lifetime->remaining <= 0.0f && lifetime->destroyOnExpire) {
            destroyEntity(world, entity);
        }
    }
}

// Input System
void inputSystem(World* world, float dt) {
    Signature sig = (1ULL << COMPONENT_INPUT);
    
    // Get keyboard state
    const uint8_t* keys = SDL_GetKeyboardState(NULL);
    
    Entity entity;
    QUERY_EACH(world, sig, entity) {
        Input* input = getComponent(world, entity, COMPONENT_INPUT);
        
        // Reset input
        input->moveX = 0;
        input->moveY = 0;
        input->shoot = false;
        input->jump = false;
        input->action = false;
        
        // Read input
        if (keys[SDL_SCANCODE_LEFT] || keys[SDL_SCANCODE_A]) input->moveX = -1.0f;
        if (keys[SDL_SCANCODE_RIGHT] || keys[SDL_SCANCODE_D]) input->moveX = 1.0f;
        if (keys[SDL_SCANCODE_UP] || keys[SDL_SCANCODE_W]) input->moveY = -1.0f;
        if (keys[SDL_SCANCODE_DOWN] || keys[SDL_SCANCODE_S]) input->moveY = 1.0f;
        if (keys[SDL_SCANCODE_SPACE]) input->shoot = true;
        if (keys[SDL_SCANCODE_Z]) input->jump = true;
        if (keys[SDL_SCANCODE_X]) input->action = true;
    }
}

// Rendering System (Immediate Mode)
void renderingSystem(World* world, IMRenderContext* imCtx) {
    Signature sig = (1ULL << COMPONENT_POSITION) | (1ULL << COMPONENT_SPRITE);
    
    Entity entity;
    QUERY_EACH(world, sig, entity) {
        Position* pos = getComponent(world, entity, COMPONENT_POSITION);
        Sprite* sprite = getComponent(world, entity, COMPONENT_SPRITE);
        
        // Set layer
        imSetLayer(imCtx, sprite->layer);
        
        // Set tint
        imSetColor(imCtx, sprite->tint.r, sprite->tint.g, 
                  sprite->tint.b, sprite->tint.a);
        
        // Draw sprite (immediate mode - no state retained)
        imDrawTextureRegion(imCtx, sprite->texture, &sprite->srcRect,
                          pos->x - sprite->width/2,
                          pos->y - sprite->height/2,
                          sprite->width, sprite->height,
                          pos->rotation);
    }
}

// Collision System
void collisionSystem(World* world, float dt) {
    Signature sig = (1ULL << COMPONENT_POSITION) | (1ULL << COMPONENT_COLLISION);
    
    // O(n²) but simple - we'll optimize later
    Entity entity1;
    QUERY_EACH(world, sig, entity1) {
        Position* pos1 = getComponent(world, entity1, COMPONENT_POSITION);
        Collision* col1 = getComponent(world, entity1, COMPONENT_COLLISION);
        
        Entity entity2;
        QUERY_EACH(world, sig, entity2) {
            if (entity1 >= entity2) continue; // Avoid double-checking
            
            Position* pos2 = getComponent(world, entity2, COMPONENT_POSITION);
            Collision* col2 = getComponent(world, entity2, COMPONENT_COLLISION);
            
            // Check layer mask
            if ((col1->layer & col2->layer) == 0) continue;
            
            // Circle collision
            float dx = pos2->x - pos1->x;
            float dy = pos2->y - pos1->y;
            float distSq = dx*dx + dy*dy;
            float radiusSum = col1->radius + col2->radius;
            
            if (distSq < radiusSum * radiusSum) {
                // Collision detected!
                onCollision(world, entity1, entity2);
            }
        }
    }
}

void onCollision(World* world, Entity a, Entity b) {
    // Check bullet vs enemy
    if (hasComponent(world, a, COMPONENT_BULLET) && 
        hasComponent(world, b, COMPONENT_ENEMY)) {
        
        Bullet* bullet = getComponent(world, a, COMPONENT_BULLET);
        Health* health = getComponent(world, b, COMPONENT_HEALTH);
        
        if (health && !health->invulnerable) {
            health->current -= bullet->damage;
            if (health->current <= 0) {
                destroyEntity(world, b); // Enemy dies
            }
        }
        
        destroyEntity(world, a); // Bullet destroyed
    }
    // Mirror check
    else if (hasComponent(world, b, COMPONENT_BULLET) && 
             hasComponent(world, a, COMPONENT_ENEMY)) {
        onCollision(world, b, a);
    }
}

5.4 Main Game Loop

// main.c

int main(int argc, char* argv[]) {
    Engine* engine = createEngine(800, 600, "ECS + Immediate Mode Demo");
    
    // Register systems
    Signature moveSig = (1ULL << COMPONENT_POSITION) | (1ULL << COMPONENT_VELOCITY);
    registerSystem(engine->world, "Movement", movementSystem, moveSig, 10);
    
    Signature inputSig = (1ULL << COMPONENT_INPUT);
    registerSystem(engine->world, "Input", inputSystem, inputSig, 0);
    
    Signature lifeSig = (1ULL << COMPONENT_LIFETIME);
    registerSystem(engine->world, "Lifetime", lifetimeSystem, lifeSig, 100);
    
    Signature collSig = (1ULL << COMPONENT_POSITION) | (1ULL << COMPONENT_COLLISION);
    registerSystem(engine->world, "Collision", collisionSystem, collSig, 50);
    
    sortSystems(engine->world);
    
    // Create player entity
    Entity player = createEntity(engine->world);
    Position playerPos = {400, 300, 0};
    Velocity playerVel = {0, 0, 0};
    Sprite playerSprite = {
        .texture = loadTexture(engine, "assets/player.bmp"),
        .srcRect = {0, 0, 32, 32},
        .width = 32,
        .height = 32,
        .tint = {255, 255, 255, 255},
        .layer = 10
    };
    Input playerInput = {0};
    Health playerHealth = {100, 100, false, 0};
    
    addComponent(engine->world, player, COMPONENT_POSITION, &playerPos);
    addComponent(engine->world, player, COMPONENT_VELOCITY, &playerVel);
    addComponent(engine->world, player, COMPONENT_SPRITE, &playerSprite);
    addComponent(engine->world, player, COMPONENT_INPUT, &playerInput);
    addComponent(engine->world, player, COMPONENT_HEALTH, &playerHealth);
    addComponent(engine->world, player, COMPONENT_PLAYER, &(Player){0});
    
    // Timing
    Uint64 lastTime = SDL_GetPerformanceCounter();
    float deltaTime = 0.0f;
    
    // Main loop
    while (engine->running) {
        // Calculate delta time
        Uint64 currentTime = SDL_GetPerformanceCounter();
        deltaTime = (float)(currentTime - lastTime) / SDL_GetPerformanceFrequency();
        lastTime = currentTime;
        if (deltaTime > 0.05f) deltaTime = 0.05f;
        
        // Handle events
        SDL_Event event;
        while (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT) {
                engine->running = false;
            }
        }
        
        // Update systems
        updateSystems(engine->world, deltaTime);
        
        // Render (immediate mode)
        SDL_SetRenderDrawColor(engine->renderer, 0, 0, 0, 255);
        SDL_RenderClear(engine->renderer);
        
        imBeginFrame(engine->imContext);
        
        // Render system adds draw commands
        renderingSystem(engine->world, engine->imContext);
        
        // Execute all draw commands
        imEndFrame(engine->imContext);
        
        SDL_RenderPresent(engine->renderer);
    }
    
    destroyEngine(engine);
    return 0;
}

Chapter 6: Component Design Patterns

6.1 Composition Patterns

// Common entity compositions

// Simple moving sprite
Entity createMovingSprite(World* world, float x, float y, float vx, float vy) {
    Entity e = createEntity(world);
    
    Position pos = {x, y, 0};
    Velocity vel = {vx, vy, 0};
    
    addComponent(world, e, COMPONENT_POSITION, &pos);
    addComponent(world, e, COMPONENT_VELOCITY, &vel);
    
    return e;
}

// Projectile
Entity createProjectile(World* world, float x, float y, float vx, float vy,
                       float damage, Entity owner, float lifetime) {
    Entity e = createEntity(world);
    
    Position pos = {x, y, 0};
    Velocity vel = {vx, vy, 0};
    Bullet bullet = {damage, owner};
    Collision col = {5.0f, false, 0xFFFF};
    Lifetime life = {lifetime, true};
    
    addComponent(world, e, COMPONENT_POSITION, &pos);
    addComponent(world, e, COMPONENT_VELOCITY, &vel);
    addComponent(world, e, COMPONENT_BULLET, &bullet);
    addComponent(world, e, COMPONENT_COLLISION, &col);
    addComponent(world, e, COMPONENT_LIFETIME, &life);
    
    return e;
}

// Particle (minimal)
Entity createParticle(World* world, float x, float y, float vx, float vy,
                     SDL_Color color, float lifetime) {
    Entity e = createEntity(world);
    
    Position pos = {x, y, 0};
    Velocity vel = {vx, vy, 0};
    Sprite sprite = {
        .texture = NULL, // Could use a white pixel texture
        .srcRect = {0, 0, 2, 2},
        .width = 2, .height = 2,
        .tint = color,
        .layer = 0
    };
    Lifetime life = {lifetime, true};
    
    addComponent(world, e, COMPONENT_POSITION, &pos);
    addComponent(world, e, COMPONENT_VELOCITY, &vel);
    addComponent(world, e, COMPONENT_SPRITE, &sprite);
    addComponent(world, e, COMPONENT_LIFETIME, &life);
    
    return e;
}

6.2 Data-Oriented Component Design

// BAD: Object-oriented thinking in components
typedef struct {
    float x, y;
    void (*update)(struct BadComponent* self, float dt);  // NO!
    void (*draw)(struct BadComponent* self);               // NO!
} BadComponent;

// GOOD: Pure data
typedef struct {
    float x, y;
} GoodComponent;
// Logic lives in systems, not components

// BAD: Mixing concerns
typedef struct {
    float x, y;
    float velocityX, velocityY;
    SDL_Texture* texture;
    int health;
    bool isPlayer;
    // Too much in one component!
} BadMonolithicComponent;

// GOOD: Single responsibility
typedef struct { float x, y; } Position;
typedef struct { float x, y; } Velocity;
typedef struct { SDL_Texture* tex; } Sprite;
typedef struct { int current, max; } Health;
typedef struct { int playerID; } PlayerTag;

6.3 Tag Components

// Tag components are empty - they just mark entities
typedef struct {
    // Empty! Just a marker
} PlayerTag;

typedef struct {
    // Empty!
} EnemyTag;

typedef struct {
    // Empty!
} StaticTag;  // Doesn't move

// Usage
Entity player = createEntity(world);
PlayerTag tag = {0}; // Zero-size in practice
addComponent(world, player, COMPONENT_PLAYER_TAG, &tag);

// Query for all players
Signature playerSig = (1ULL << COMPONENT_PLAYER_TAG);
Entity entity;
QUERY_EACH(world, playerSig, entity) {
    // Process player entities
}

6.4 Prefab System

// prefab.h - Entity templates

typedef struct {
    const char* name;
    void (*create)(World* world, Entity entity, void* params);
} Prefab;

// Player prefab
void createPlayerPrefab(World* world, Entity entity, void* params) {
    float* pos = (float*)params; // [x, y]
    
    Position p = {pos[0], pos[1], 0};
    Velocity v = {0, 0, 0};
    Health h = {100, 100, false, 0};
    Input input = {0};
    Collision col = {16.0f, false, 0x0001};
    PlayerTag tag = {0};
    
    addComponent(world, entity, COMPONENT_POSITION, &p);
    addComponent(world, entity, COMPONENT_VELOCITY, &v);
    addComponent(world, entity, COMPONENT_HEALTH, &h);
    addComponent(world, entity, COMPONENT_INPUT, &input);
    addComponent(world, entity, COMPONENT_COLLISION, &col);
    addComponent(world, entity, COMPONENT_PLAYER_TAG, &tag);
}

// Enemy prefab
void createEnemyPrefab(World* world, Entity entity, void* params) {
    float* data = (float*)params; // [x, y, health]
    
    Position p = {data[0], data[1], 0};
    Velocity v = {0, 0, 0};
    Health h = {data[2], data[2], false, 0};
    Collision col = {12.0f, false, 0x0002};
    EnemyTag tag = {0};
    
    addComponent(world, entity, COMPONENT_POSITION, &p);
    addComponent(world, entity, COMPONENT_VELOCITY, &v);
    addComponent(world, entity, COMPONENT_HEALTH, &h);
    addComponent(world, entity, COMPONENT_COLLISION, &col);
    addComponent(world, entity, COMPONENT_ENEMY_TAG, &tag);
}

// Prefab registry
Prefab prefabs[] = {
    {"player", createPlayerPrefab},
    {"enemy", createEnemyPrefab},
    // ...
};

Entity instantiatePrefab(World* world, const char* name, void* params) {
    Entity entity = createEntity(world);
    
    for (int i = 0; i < sizeof(prefabs)/sizeof(Prefab); i++) {
        if (strcmp(prefabs[i].name, name) == 0) {
            prefabs[i].create(world, entity, params);
            return entity;
        }
    }
    
    return ENTITY_NULL;
}

// Usage
float playerPos[] = {400, 300};
Entity player = instantiatePrefab(world, "player", playerPos);

float enemyData[] = {200, 150, 50}; // x, y, health
Entity enemy = instantiatePrefab(world, "enemy", enemyData);

Chapter 7: System Architecture

7.1 System Design Principles

Systems should:

  1. Be stateless (data lives in components)
  2. Operate on component arrays (not individual entities)
  3. Be independent (minimize system dependencies)
  4. Have clear, single responsibilities
  5. Be easily parallelizable
// GOOD System Design
void physicsSystem(World* world, float dt) {
    // 1. Query for relevant entities
    Signature sig = (1ULL << COMPONENT_POSITION) | 
                   (1ULL << COMPONENT_VELOCITY);
    
    // 2. Process in bulk
    ComponentArray* positions = world->componentArrays[COMPONENT_POSITION];
    ComponentArray* velocities = world->componentArrays[COMPONENT_VELOCITY];
    
    // 3. Direct array access for performance
    Position* posArray = (Position*)positions->data;
    Velocity* velArray = (Velocity*)velocities->data;
    
    // 4. Process all matching entities
    for (uint32_t i = 0; i < positions->size; i++) {
        Entity entity = positions->indexToEntity[i];
        
        // Check if entity has velocity
        if (!hasComponent(world, entity, COMPONENT_VELOCITY)) continue;
        
        uint32_t velIdx = velocities->entityToIndex[entity];
        
        posArray[i].x += velArray[velIdx].x * dt;
        posArray[i].y += velArray[velIdx].y * dt;
    }
}

7.2 System Update Order

// System priorities control update order
typedef enum {
    PRIORITY_INPUT = 0,       // Read input first
    PRIORITY_MOVEMENT = 10,   // Then update positions
    PRIORITY_PHYSICS = 20,    // Then physics
    PRIORITY_COLLISION = 30,  // Then collision detection
    PRIORITY_RESPONSE = 40,   // Then collision response
    PRIORITY_ANIMATION = 50,  // Then animations
    PRIORITY_CAMERA = 60,     // Then camera
    PRIORITY_PARTICLES = 70,  // Then particles
    PRIORITY_RENDER = 100     // Finally render
} SystemPriority;

void setupSystems(Engine* engine) {
    registerSystem(engine->world, "Input", inputSystem, 
                  inputSig, PRIORITY_INPUT);
    registerSystem(engine->world, "Movement", movementSystem,
                  moveSig, PRIORITY_MOVEMENT);
    registerSystem(engine->world, "Collision", collisionSystem,
                  collSig, PRIORITY_COLLISION);
    // ...
    
    sortSystems(engine->world);
}

7.3 System Communication via Events

// event_system.h

typedef enum {
    EVENT_ENTITY_DIED,
    EVENT_ENTITY_SPAWNED,
    EVENT_COLLISION,
    EVENT_DAMAGE_DEALT,
    EVENT_LEVEL_COMPLETE,
    EVENT_COUNT
} EventType;

typedef struct {
    EventType type;
    Entity entity;
    Entity other;  // For collisions, etc.
    void* data;
} Event;

typedef struct {
    Event queue[1024];
    int count;
} EventQueue;

void initEventQueue(EventQueue* eq) {
    eq->count = 0;
}

void pushEvent(EventQueue* eq, Event event) {
    if (eq->count < 1024) {
        eq->queue[eq->count++] = event;
    }
}

// Systems can emit events
void someSystem(World* world, EventQueue* events, float dt) {
    // ... do work ...
    
    // Emit event
    Event evt = {
        .type = EVENT_ENTITY_DIED,
        .entity = deadEntity,
        .other = ENTITY_NULL,
        .data = NULL
    };
    pushEvent(events, evt);
}

// Process events after all systems update
void processEvents(World* world, EventQueue* events) {
    for (int i = 0; i < events->count; i++) {
        Event* evt = &events->queue[i];
        
        switch (evt->type) {
            case EVENT_ENTITY_DIED:
                // Spawn particles, play sound, etc.
                spawnDeathEffect(world, evt->entity);
                break;
            case EVENT_COLLISION:
                // Handle collision
                break;
            // ...
        }
    }
    
    events->count = 0; // Clear queue
}

7.4 Parallel System Execution

// parallel_systems.h

#include <pthread.h>

typedef struct {
    World* world;
    System* system;
    float deltaTime;
    pthread_t thread;
} SystemThread;

typedef struct {
    SystemThread threads[16];
    int threadCount;
} ParallelScheduler;

void* systemThreadFunc(void* arg) {
    SystemThread* st = (SystemThread*)arg;
    st->system->update(st->world, st->deltaTime);
    return NULL;
}

void executeSystemsParallel(World* world, float dt) {
    ParallelScheduler scheduler = {0};
    
    // Find independent systems (no shared components)
    for (uint32_t i = 0; i < world->systemCount; i++) {
        System* sys = &world->systems[i];
        
        bool canParallelize = true;
        
        // Check if this system conflicts with running systems
        for (int j = 0; j < scheduler.threadCount; j++) {
            Signature overlap = sys->signature & 
                               scheduler.threads[j].system->signature;
            if (overlap != 0) {
                // Systems share components, can't run in parallel
                canParallelize = false;
                break;
            }
        }
        
        if (canParallelize && scheduler.threadCount < 16) {
            // Launch in parallel
            SystemThread* st = &scheduler.threads[scheduler.threadCount++];
            st->world = world;
            st->system = sys;
            st->deltaTime = dt;
            pthread_create(&st->thread, NULL, systemThreadFunc, st);
        } else {
            // Wait for current batch to finish
            for (int j = 0; j < scheduler.threadCount; j++) {
                pthread_join(scheduler.threads[j].thread, NULL);
            }
            scheduler.threadCount = 0;
            
            // Run this system
            sys->update(world, dt);
        }
    }
    
    // Wait for remaining threads
    for (int j = 0; j < scheduler.threadCount; j++) {
        pthread_join(scheduler.threads[j].thread, NULL);
    }
}

Chapter 8: Immediate Mode Rendering

8.1 Advanced Immediate Rendering

// im_render_advanced.c

// Batch rendering for performance
typedef struct {
    SDL_Texture* texture;
    SDL_Rect srcRects[1000];
    SDL_Rect destRects[1000];
    float rotations[1000];
    SDL_Color tints[1000];
    int count;
} SpriteBatch;

void imBeginBatch(IMRenderContext* ctx, SDL_Texture* texture) {
    // Implementation for batching same-texture sprites
}

void imAddSpriteToBatch(IMRenderContext* ctx, SDL_Rect* src, SDL_Rect* dest,
                        float rotation, SDL_Color tint) {
    // Add to current batch
}

void imFlushBatch(IMRenderContext* ctx) {
    // Render all batched sprites in one call
}

// Custom shader support (if using OpenGL backend)
typedef struct {
    GLuint program;
    // Uniform locations
} Shader;

void imUseShader(IMRenderContext* ctx, Shader* shader) {
    // Switch to custom shader
}

// Post-processing effects
void imBeginRenderTarget(IMRenderContext* ctx, SDL_Texture* target) {
    SDL_SetRenderTarget(ctx->renderer, target);
}

void imEndRenderTarget(IMRenderContext* ctx) {
    SDL_SetRenderTarget(ctx->renderer, NULL);
}

void imApplyPostProcess(IMRenderContext* ctx, SDL_Texture* source,
                       void (*process)(SDL_Texture*, void*), void* params) {
    // Apply post-process effect
}

8.2 Debug Visualization (Immediate)

// im_debug.c - Immediate mode debug drawing

void imDebugDrawCollider(IMRenderContext* ctx, Position* pos, Collision* col) {
    imSetColor(ctx, 0, 255, 0, 128);
    imSetBlendMode(ctx, SDL_BLENDMODE_BLEND);
    imDrawCircle(ctx, pos->x, pos->y, col->radius, false);
}

void imDebugDrawVelocity(IMRenderContext* ctx, Position* pos, Velocity* vel) {
    imSetColor(ctx, 255, 255, 0, 255);
    float scale = 10.0f;
    imDrawLine(ctx, pos->x, pos->y,
              pos->x + vel->x * scale,
              pos->y + vel->y * scale, 2.0f);
}

void imDebugDrawGrid(IMRenderContext* ctx, int gridSize, int screenW, int screenH) {
    imSetColor(ctx, 50, 50, 50, 255);
    
    for (int x = 0; x < screenW; x += gridSize) {
        imDrawLine(ctx, x, 0, x, screenH, 1.0f);
    }
    for (int y = 0; y < screenH; y += gridSize) {
        imDrawLine(ctx, 0, y, screenW, y, 1.0f);
    }
}

// Debug system
void debugRenderSystem(World* world, IMRenderContext* imCtx, bool showColliders,
                      bool showVelocities) {
    if (showColliders) {
        Signature sig = (1ULL << COMPONENT_POSITION) | (1ULL << COMPONENT_COLLISION);
        Entity entity;
        QUERY_EACH(world, sig, entity) {
            Position* pos = getComponent(world, entity, COMPONENT_POSITION);
            Collision* col = getComponent(world, entity, COMPONENT_COLLISION);
            imDebugDrawCollider(imCtx, pos, col);
        }
    }
    
    if (showVelocities) {
        Signature sig = (1ULL << COMPONENT_POSITION) | (1ULL << COMPONENT_VELOCITY);
        Entity entity;
        QUERY_EACH(world, sig, entity) {
            Position* pos = getComponent(world, entity, COMPONENT_POSITION);
            Velocity* vel = getComponent(world, entity, COMPONENT_VELOCITY);
            imDebugDrawVelocity(imCtx, pos, vel);
        }
    }
}

8.3 Particle Rendering (Immediate)

// Particle system with immediate rendering
typedef struct {
    Position pos;
    Velocity vel;
    SDL_Color color;
    float life;
    float size;
} Particle;

typedef struct {
    Particle particles[10000];
    int count;
} ParticleSystem;

void updateParticles(ParticleSystem* ps, float dt) {
    for (int i = 0; i < ps->count; i++) {
        Particle* p = &ps->particles[i];
        
        // Update
        p->pos.x += p->vel.x * dt;
        p->pos.y += p->vel.y * dt;
        p->vel.y += 100.0f * dt; // Gravity
        p->life -= dt;
        
        // Remove dead particles (swap with last)
        if (p->life <= 0) {
            ps->particles[i] = ps->particles[--ps->count];
            i--;
        }
    }
}

void renderParticles(ParticleSystem* ps, IMRenderContext* imCtx) {
    imSetBlendMode(imCtx, SDL_BLENDMODE_ADD); // Additive blending
    imSetLayer(imCtx, 100); // Top layer
    
    for (int i = 0; i < ps->count; i++) {
        Particle* p = &ps->particles[i];
        
        float alpha = p->life / 1.0f; // Fade based on lifetime
        imSetColor(imCtx, p->color.r, p->color.g, p->color.b,
                  (uint8_t)(alpha * 255));
        
        // Draw particle (immediate - no state)
        imDrawCircle(imCtx, p->pos.x, p->pos.y, p->size, true);
    }
}

---

<a name="imgui"></a>
## Chapter 9: Immediate Mode GUI Integration

### 9.1 Building an Immediate Mode GUI System

```c
// imgui_simple.h - Minimal immediate mode GUI
#ifndef IMGUI_SIMPLE_H
#define IMGUI_SIMPLE_H

#include <SDL2/SDL.h>
#include <stdbool.h>

typedef struct {
    SDL_Renderer* renderer;
    
    // Mouse state
    int mouseX, mouseY;
    bool mouseDown;
    bool mousePressed;
    bool mouseReleased;
    
    // Keyboard state
    char textInput[64];
    int textLength;
    
    // Hot (mouse over) and active (being interacted with) items
    int hotItem;
    int activeItem;
    
    // ID counter for widgets
    int nextID;
    
    // Style
    SDL_Color bgColor;
    SDL_Color fgColor;
    SDL_Color hotColor;
    SDL_Color activeColor;
    SDL_Color textColor;
} IMGUIContext;

// Lifecycle
IMGUIContext* imguiCreate(SDL_Renderer* renderer);
void imguiDestroy(IMGUIContext* ctx);
void imguiBeginFrame(IMGUIContext* ctx, int mouseX, int mouseY, bool mouseDown);
void imguiEndFrame(IMGUIContext* ctx);

// Widgets (return true if interacted)
bool imguiButton(IMGUIContext* ctx, const char* label, int x, int y, int w, int h);
bool imguiCheckbox(IMGUIContext* ctx, const char* label, bool* checked, int x, int y);
bool imguiSlider(IMGUIContext* ctx, const char* label, float* value,
                float min, float max, int x, int y, int w);
void imguiLabel(IMGUIContext* ctx, const char* text, int x, int y);
bool imguiTextInput(IMGUIContext* ctx, char* buffer, int bufferSize,
                    int x, int y, int w, int h);

#endif // IMGUI_SIMPLE_H

9.2 Implementing the GUI System

// imgui_simple.c

IMGUIContext* imguiCreate(SDL_Renderer* renderer) {
    IMGUIContext* ctx = calloc(1, sizeof(IMGUIContext));
    ctx->renderer = renderer;
    ctx->hotItem = 0;
    ctx->activeItem = 0;
    
    // Default colors
    ctx->bgColor = (SDL_Color){60, 60, 60, 255};
    ctx->fgColor = (SDL_Color){100, 100, 100, 255};
    ctx->hotColor = (SDL_Color){120, 120, 120, 255};
    ctx->activeColor = (SDL_Color){140, 140, 140, 255};
    ctx->textColor = (SDL_Color){255, 255, 255, 255};
    
    return ctx;
}

void imguiBeginFrame(IMGUIContext* ctx, int mouseX, int mouseY, bool mouseDown) {
    ctx->mouseX = mouseX;
    ctx->mouseY = mouseY;
    
    // Track press/release
    ctx->mousePressed = mouseDown && !ctx->mouseDown;
    ctx->mouseReleased = !mouseDown && ctx->mouseDown;
    ctx->mouseDown = mouseDown;
    
    // Reset hot item
    ctx->hotItem = 0;
    
    // Reset ID counter
    ctx->nextID = 1;
}

void imguiEndFrame(IMGUIContext* ctx) {
    // If mouse released, deactivate
    if (ctx->mouseReleased) {
        ctx->activeItem = 0;
    }
}

// Generate unique ID
int imguiGetID(IMGUIContext* ctx) {
    return ctx->nextID++;
}

bool imguiButton(IMGUIContext* ctx, const char* label, int x, int y, int w, int h) {
    int id = imguiGetID(ctx);
    
    // Check if mouse is over button
    bool mouseOver = ctx->mouseX >= x && ctx->mouseX < x + w &&
                    ctx->mouseY >= y && ctx->mouseY < y + h;
    
    if (mouseOver) {
        ctx->hotItem = id;
        if (ctx->activeItem == 0 && ctx->mousePressed) {
            ctx->activeItem = id;
        }
    }
    
    // Render button
    SDL_Rect rect = {x, y, w, h};
    SDL_Color color;
    
    if (ctx->activeItem == id) {
        color = ctx->activeColor;
    } else if (ctx->hotItem == id) {
        color = ctx->hotColor;
    } else {
        color = ctx->fgColor;
    }
    
    SDL_SetRenderDrawColor(ctx->renderer, color.r, color.g, color.b, color.a);
    SDL_RenderFillRect(ctx->renderer, &rect);
    
    // Draw border
    SDL_SetRenderDrawColor(ctx->renderer, 200, 200, 200, 255);
    SDL_RenderDrawRect(ctx->renderer, &rect);
    
    // Return true if button was clicked
    return ctx->hotItem == id && ctx->activeItem == id && ctx->mouseReleased;
}

bool imguiCheckbox(IMGUIContext* ctx, const char* label, bool* checked, int x, int y) {
    int id = imguiGetID(ctx);
    int size = 20;
    
    // Check if mouse is over checkbox
    bool mouseOver = ctx->mouseX >= x && ctx->mouseX < x + size &&
                    ctx->mouseY >= y && ctx->mouseY < y + size;
    
    if (mouseOver) {
        ctx->hotItem = id;
        if (ctx->activeItem == 0 && ctx->mousePressed) {
            ctx->activeItem = id;
        }
    }
    
    // Render checkbox
    SDL_Rect rect = {x, y, size, size};
    SDL_Color color = (ctx->hotItem == id) ? ctx->hotColor : ctx->fgColor;
    
    SDL_SetRenderDrawColor(ctx->renderer, color.r, color.g, color.b, color.a);
    SDL_RenderFillRect(ctx->renderer, &rect);
    
    SDL_SetRenderDrawColor(ctx->renderer, 200, 200, 200, 255);
    SDL_RenderDrawRect(ctx->renderer, &rect);
    
    // Draw check if checked
    if (*checked) {
        SDL_Rect check = {x + 4, y + 4, size - 8, size - 8};
        SDL_SetRenderDrawColor(ctx->renderer, 255, 255, 255, 255);
        SDL_RenderFillRect(ctx->renderer, &check);
    }
    
    // Toggle on click
    if (ctx->hotItem == id && ctx->activeItem == id && ctx->mouseReleased) {
        *checked = !*checked;
        return true;
    }
    
    return false;
}

bool imguiSlider(IMGUIContext* ctx, const char* label, float* value,
                float min, float max, int x, int y, int w) {
    int id = imguiGetID(ctx);
    int h = 20;
    
    // Check if mouse is over slider
    bool mouseOver = ctx->mouseX >= x && ctx->mouseX < x + w &&
                    ctx->mouseY >= y && ctx->mouseY < y + h;
    
    if (mouseOver) {
        ctx->hotItem = id;
        if (ctx->activeItem == 0 && ctx->mouseDown) {
            ctx->activeItem = id;
        }
    }
    
    // Update value if active
    if (ctx->activeItem == id && ctx->mouseDown) {
        float t = (float)(ctx->mouseX - x) / w;
        if (t < 0) t = 0;
        if (t > 1) t = 1;
        *value = min + t * (max - min);
    }
    
    // Render slider track
    SDL_Rect track = {x, y + h/2 - 2, w, 4};
    SDL_SetRenderDrawColor(ctx->renderer, 
                          ctx->bgColor.r, ctx->bgColor.g, ctx->bgColor.b, 255);
    SDL_RenderFillRect(ctx->renderer, &track);
    
    // Render slider handle
    float t = (*value - min) / (max - min);
    int handleX = x + (int)(t * w);
    SDL_Rect handle = {handleX - 5, y, 10, h};
    
    SDL_Color color;
    if (ctx->activeItem == id) {
        color = ctx->activeColor;
    } else if (ctx->hotItem == id) {
        color = ctx->hotColor;
    } else {
        color = ctx->fgColor;
    }
    
    SDL_SetRenderDrawColor(ctx->renderer, color.r, color.g, color.b, color.a);
    SDL_RenderFillRect(ctx->renderer, &handle);
    
    return ctx->activeItem == id;
}

void imguiLabel(IMGUIContext* ctx, const char* text, int x, int y) {
    // In production, would use SDL_ttf for text rendering
    // For now, draw a marker representing text
    SDL_Rect marker = {x, y, 4, 4};
    SDL_SetRenderDrawColor(ctx->renderer, 
                          ctx->textColor.r, ctx->textColor.g,
                          ctx->textColor.b, ctx->textColor.a);
    SDL_RenderFillRect(ctx->renderer, &marker);
}

9.3 Using Immediate Mode GUI in Game

// game_with_gui.c

typedef struct {
    Engine* engine;
    IMGUIContext* imgui;
    
    // Debug options
    bool showDebug;
    bool showColliders;
    bool showVelocities;
    float timeScale;
    bool paused;
} GameWithGUI;

void gameUpdate(GameWithGUI* game, float dt) {
    // Update GUI
    int mx, my;
    Uint32 buttons = SDL_GetMouseState(&mx, &my);
    bool mouseDown = (buttons & SDL_BUTTON(SDL_BUTTON_LEFT)) != 0;
    
    imguiBeginFrame(game->imgui, mx, my, mouseDown);
    
    // Debug panel
    if (game->showDebug) {
        // Background panel
        SDL_Rect panel = {10, 10, 200, 300};
        SDL_SetRenderDrawColor(game->engine->renderer, 0, 0, 0, 180);
        SDL_SetRenderDrawBlendMode(game->engine->renderer, SDL_BLENDMODE_BLEND);
        SDL_RenderFillRect(game->engine->renderer, &panel);
        
        int yPos = 20;
        
        imguiLabel(game->imgui, "Debug Options", 20, yPos);
        yPos += 30;
        
        imguiCheckbox(game->imgui, "Show Colliders", &game->showColliders, 20, yPos);
        yPos += 30;
        
        imguiCheckbox(game->imgui, "Show Velocities", &game->showVelocities, 20, yPos);
        yPos += 30;
        
        imguiLabel(game->imgui, "Time Scale", 20, yPos);
        yPos += 20;
        imguiSlider(game->imgui, "TimeScale", &game->timeScale, 0.0f, 2.0f, 20, yPos, 180);
        yPos += 30;
        
        if (imguiButton(game->imgui, "Pause", 20, yPos, 80, 25)) {
            game->paused = !game->paused;
        }
        
        if (imguiButton(game->imgui, "Reset", 110, yPos, 80, 25)) {
            // Reset game
        }
    }
    
    imguiEndFrame(game->imgui);
    
    // Update game systems
    if (!game->paused) {
        updateSystems(game->engine->world, dt * game->timeScale);
    }
}

9.4 Advanced GUI Widgets

// imgui_advanced.c

// List box
int imguiListBox(IMGUIContext* ctx, const char** items, int itemCount,
                 int* selectedIndex, int x, int y, int w, int h) {
    int id = imguiGetID(ctx);
    int itemHeight = 25;
    
    // Background
    SDL_Rect bg = {x, y, w, h};
    SDL_SetRenderDrawColor(ctx->renderer, 
                          ctx->bgColor.r, ctx->bgColor.g, ctx->bgColor.b, 255);
    SDL_RenderFillRect(ctx->renderer, &bg);
    
    // Items
    for (int i = 0; i < itemCount; i++) {
        int itemY = y + i * itemHeight;
        
        if (itemY + itemHeight > y + h) break; // Clipping
        
        int itemID = id + i + 1;
        SDL_Rect itemRect = {x, itemY, w, itemHeight};
        
        bool mouseOver = ctx->mouseX >= x && ctx->mouseX < x + w &&
                        ctx->mouseY >= itemY && ctx->mouseY < itemY + itemHeight;
        
        if (mouseOver) {
            ctx->hotItem = itemID;
            if (ctx->mousePressed) {
                *selectedIndex = i;
                return i;
            }
        }
        
        // Render item
        SDL_Color color;
        if (*selectedIndex == i) {
            color = ctx->activeColor;
        } else if (ctx->hotItem == itemID) {
            color = ctx->hotColor;
        } else {
            color = ctx->fgColor;
        }
        
        SDL_SetRenderDrawColor(ctx->renderer, color.r, color.g, color.b, color.a);
        SDL_RenderFillRect(ctx->renderer, &itemRect);
    }
    
    return -1;
}

// Window with title bar
typedef struct {
    int x, y, w, h;
    bool dragging;
    int dragOffsetX, dragOffsetY;
} Window;

bool imguiWindow(IMGUIContext* ctx, Window* window, const char* title) {
    int id = imguiGetID(ctx);
    int titleHeight = 25;
    
    // Title bar
    SDL_Rect titleBar = {window->x, window->y, window->w, titleHeight};
    bool mouseOverTitle = ctx->mouseX >= titleBar.x && 
                         ctx->mouseX < titleBar.x + titleBar.w &&
                         ctx->mouseY >= titleBar.y && 
                         ctx->mouseY < titleBar.y + titleBar.h;
    
    if (mouseOverTitle) {
        ctx->hotItem = id;
        if (ctx->mousePressed) {
            window->dragging = true;
            window->dragOffsetX = ctx->mouseX - window->x;
            window->dragOffsetY = ctx->mouseY - window->y;
        }
    }
    
    if (window->dragging) {
        window->x = ctx->mouseX - window->dragOffsetX;
        window->y = ctx->mouseY - window->dragOffsetY;
        
        if (ctx->mouseReleased) {
            window->dragging = false;
        }
    }
    
    // Draw window
    SDL_Rect bg = {window->x, window->y + titleHeight, window->w, window->h - titleHeight};
    SDL_SetRenderDrawColor(ctx->renderer, 40, 40, 40, 230);
    SDL_SetRenderDrawBlendMode(ctx->renderer, SDL_BLENDMODE_BLEND);
    SDL_RenderFillRect(ctx->renderer, &bg);
    
    SDL_SetRenderDrawColor(ctx->renderer, 60, 60, 60, 230);
    SDL_RenderFillRect(ctx->renderer, &titleBar);
    
    // Border
    SDL_Rect border = {window->x, window->y, window->w, window->h};
    SDL_SetRenderDrawColor(ctx->renderer, 100, 100, 100, 255);
    SDL_RenderDrawRect(ctx->renderer, &border);
    
    return true;
}

Chapter 10: Memory Management in ECS

10.1 Component Pool Allocator

// pool_allocator.h

typedef struct {
    void* memory;
    uint32_t* freeList;
    uint32_t freeCount;
    uint32_t capacity;
    size_t elementSize;
} PoolAllocator;

PoolAllocator* createPoolAllocator(size_t elementSize, uint32_t capacity) {
    PoolAllocator* pool = malloc(sizeof(PoolAllocator));
    pool->elementSize = elementSize;
    pool->capacity = capacity;
    
    // Allocate memory block
    pool->memory = malloc(elementSize * capacity);
    
    // Initialize free list
    pool->freeList = malloc(sizeof(uint32_t) * capacity);
    pool->freeCount = capacity;
    
    for (uint32_t i = 0; i < capacity; i++) {
        pool->freeList[i] = i;
    }
    
    return pool;
}

void* poolAlloc(PoolAllocator* pool) {
    if (pool->freeCount == 0) {
        return NULL; // Pool exhausted
    }
    
    uint32_t index = pool->freeList[--pool->freeCount];
    return (char*)pool->memory + (index * pool->elementSize);
}

void poolFree(PoolAllocator* pool, void* ptr) {
    // Calculate index
    size_t offset = (char*)ptr - (char*)pool->memory;
    uint32_t index = offset / pool->elementSize;
    
    // Add to free list
    pool->freeList[pool->freeCount++] = index;
}

void destroyPoolAllocator(PoolAllocator* pool) {
    free(pool->memory);
    free(pool->freeList);
    free(pool);
}

10.2 Archetype-Based Storage

// archetype.h - Group entities by component signature

typedef struct {
    Signature signature;
    void** componentArrays;  // One array per component type
    Entity* entities;
    uint32_t count;
    uint32_t capacity;
} Archetype;

typedef struct {
    Archetype* archetypes;
    uint32_t archetypeCount;
    uint32_t archetypeCapacity;
    
    // Map entity to archetype
    uint32_t* entityToArchetype;
    uint32_t* entityToIndex;
} ArchetypeStorage;

ArchetypeStorage* createArchetypeStorage() {
    ArchetypeStorage* storage = calloc(1, sizeof(ArchetypeStorage));
    storage->archetypeCapacity = 64;
    storage->archetypes = malloc(sizeof(Archetype) * storage->archetypeCapacity);
    storage->archetypeCount = 0;
    
    storage->entityToArchetype = malloc(sizeof(uint32_t) * MAX_ENTITIES);
    storage->entityToIndex = malloc(sizeof(uint32_t) * MAX_ENTITIES);
    
    for (uint32_t i = 0; i < MAX_ENTITIES; i++) {
        storage->entityToArchetype[i] = UINT32_MAX;
        storage->entityToIndex[i] = UINT32_MAX;
    }
    
    return storage;
}

Archetype* findOrCreateArchetype(ArchetypeStorage* storage, Signature sig) {
    // Find existing archetype
    for (uint32_t i = 0; i < storage->archetypeCount; i++) {
        if (storage->archetypes[i].signature == sig) {
            return &storage->archetypes[i];
        }
    }
    
    // Create new archetype
    if (storage->archetypeCount >= storage->archetypeCapacity) {
        storage->archetypeCapacity *= 2;
        storage->archetypes = realloc(storage->archetypes,
            sizeof(Archetype) * storage->archetypeCapacity);
    }
    
    Archetype* archetype = &storage->archetypes[storage->archetypeCount++];
    archetype->signature = sig;
    archetype->capacity = 100;
    archetype->count = 0;
    archetype->entities = malloc(sizeof(Entity) * archetype->capacity);
    
    // Allocate component arrays based on signature
    int componentCount = __builtin_popcountll(sig);
    archetype->componentArrays = malloc(sizeof(void*) * componentCount);
    
    return archetype;
}

10.3 Memory Layout Optimization

// struct_of_arrays.h - SoA for cache efficiency

// BAD: Array of Structures (AoS)
typedef struct {
    float x, y, z;     // 12 bytes
    float vx, vy, vz;  // 12 bytes
    float mass;        // 4 bytes
    int id;            // 4 bytes
} ParticleAoS;        // 32 bytes total

ParticleAoS particles[1000];

// When updating positions, we load ALL fields (wasteful)
for (int i = 0; i < 1000; i++) {
    particles[i].x += particles[i].vx * dt;
    particles[i].y += particles[i].vy * dt;
    particles[i].z += particles[i].vz * dt;
    // We're loading mass and id too, even though we don't use them!
}

// GOOD: Structure of Arrays (SoA)
typedef struct {
    float* x;    // Array of all x positions
    float* y;    // Array of all y positions
    float* z;    // Array of all z positions
    float* vx;   // Array of all x velocities
    float* vy;
    float* vz;
    float* mass;
    int* id;
    int count;
} ParticlesSoA;

void initParticlesSoA(ParticlesSoA* particles, int capacity) {
    particles->x = malloc(sizeof(float) * capacity);
    particles->y = malloc(sizeof(float) * capacity);
    particles->z = malloc(sizeof(float) * capacity);
    particles->vx = malloc(sizeof(float) * capacity);
    particles->vy = malloc(sizeof(float) * capacity);
    particles->vz = malloc(sizeof(float) * capacity);
    particles->mass = malloc(sizeof(float) * capacity);
    particles->id = malloc(sizeof(int) * capacity);
    particles->count = 0;
}

// Now we only touch what we need - perfect cache utilization!
void updatePositions(ParticlesSoA* particles, float dt) {
    for (int i = 0; i < particles->count; i++) {
        particles->x[i] += particles->vx[i] * dt;
        particles->y[i] += particles->vy[i] * dt;
        particles->z[i] += particles->vz[i] * dt;
    }
    // Only 6 arrays touched instead of loading entire structs!
}

10.4 Custom Allocators for ECS

// ecs_allocator.c

typedef struct {
    PoolAllocator* entityPool;
    PoolAllocator* componentPools[MAX_COMPONENT_TYPES];
    
    // Linear allocator for temporary data
    void* tempMemory;
    size_t tempSize;
    size_t tempUsed;
} ECSAllocator;

ECSAllocator* createECSAllocator() {
    ECSAllocator* alloc = malloc(sizeof(ECSAllocator));
    
    alloc->entityPool = createPoolAllocator(sizeof(Entity), MAX_ENTITIES);
    
    for (int i = 0; i < MAX_COMPONENT_TYPES; i++) {
        alloc->componentPools[i] = NULL;
    }
    
    // 16MB temporary memory
    alloc->tempSize = 16 * 1024 * 1024;
    alloc->tempMemory = malloc(alloc->tempSize);
    alloc->tempUsed = 0;
    
    return alloc;
}

void* allocTemp(ECSAllocator* alloc, size_t size) {
    // Align to 16 bytes
    size = (size + 15) & ~15;
    
    if (alloc->tempUsed + size > alloc->tempSize) {
        return NULL; // Out of temp memory
    }
    
    void* ptr = (char*)alloc->tempMemory + alloc->tempUsed;
    alloc->tempUsed += size;
    
    return ptr;
}

void resetTempMemory(ECSAllocator* alloc) {
    alloc->tempUsed = 0;
    // All temp allocations become invalid
    // Perfect for per-frame allocations
}

// Usage
void someSystem(World* world, ECSAllocator* alloc, float dt) {
    // Allocate temporary array for this frame
    int* tempData = allocTemp(alloc, sizeof(int) * 1000);
    
    // Use temp data...
    
    // No need to free - resetTempMemory() at end of frame
}

Chapter 11: Advanced ECS Patterns

11.1 Entity Relationships

// relationships.h - Parent-child hierarchies in ECS

typedef struct {
    Entity parent;
    Entity firstChild;
    Entity nextSibling;
    Entity prevSibling;
} Hierarchy;

ComponentType COMPONENT_HIERARCHY;

void attachChild(World* world, Entity parent, Entity child) {
    Hierarchy* parentHier = getComponent(world, parent, COMPONENT_HIERARCHY);
    Hierarchy* childHier = getComponent(world, child, COMPONENT_HIERARCHY);
    
    if (!parentHier) {
        Hierarchy newHier = {ENTITY_NULL, ENTITY_NULL, ENTITY_NULL, ENTITY_NULL};
        parentHier = addComponent(world, parent, COMPONENT_HIERARCHY, &newHier);
    }
    
    if (!childHier) {
        Hierarchy newHier = {ENTITY_NULL, ENTITY_NULL, ENTITY_NULL, ENTITY_NULL};
        childHier = addComponent(world, child, COMPONENT_HIERARCHY, &newHier);
    }
    
    // Detach from old parent
    if (childHier->parent != ENTITY_NULL) {
        detachChild(world, child);
    }
    
    childHier->parent = parent;
    
    // Add to parent's child list
    if (parentHier->firstChild == ENTITY_NULL) {
        parentHier->firstChild = child;
    } else {
        // Add to end of sibling list
        Entity lastChild = parentHier->firstChild;
        Hierarchy* lastHier = getComponent(world, lastChild, COMPONENT_HIERARCHY);
        
        while (lastHier->nextSibling != ENTITY_NULL) {
            lastChild = lastHier->nextSibling;
            lastHier = getComponent(world, lastChild, COMPONENT_HIERARCHY);
        }
        
        lastHier->nextSibling = child;
        childHier->prevSibling = lastChild;
    }
}

// Transform system with hierarchy
void hierarchyTransformSystem(World* world, float dt) {
    Signature sig = (1ULL << COMPONENT_POSITION) | (1ULL << COMPONENT_HIERARCHY);
    
    Entity entity;
    QUERY_EACH(world, sig, entity) {
        Hierarchy* hier = getComponent(world, entity, COMPONENT_HIERARCHY);
        
        // Only process root entities
        if (hier->parent != ENTITY_NULL) continue;
        
        updateTransformRecursive(world, entity);
    }
}

void updateTransformRecursive(World* world, Entity entity) {
    Position* pos = getComponent(world, entity, COMPONENT_POSITION);
    Hierarchy* hier = getComponent(world, entity, COMPONENT_HIERARCHY);
    
    if (!hier) return;
    
    // Update children
    Entity child = hier->firstChild;
    while (child != ENTITY_NULL) {
        Position* childPos = getComponent(world, child, COMPONENT_POSITION);
        Hierarchy* childHier = getComponent(world, child, COMPONENT_HIERARCHY);
        
        // Child position is relative to parent
        childPos->x += pos->x;
        childPos->y += pos->y;
        childPos->rotation += pos->rotation;
        
        // Recurse
        updateTransformRecursive(world, child);
        
        child = childHier->nextSibling;
    }
}

11.2 Entity Commands/Deferred Operations

// entity_commands.h - Deferred entity operations

typedef enum {
    CMD_CREATE_ENTITY,
    CMD_DESTROY_ENTITY,
    CMD_ADD_COMPONENT,
    CMD_REMOVE_COMPONENT
} CommandType;

typedef struct {
    CommandType type;
    Entity entity;
    ComponentType componentType;
    void* data;
    size_t dataSize;
} EntityCommand;

typedef struct {
    EntityCommand* commands;
    int count;
    int capacity;
} CommandBuffer;

CommandBuffer* createCommandBuffer() {
    CommandBuffer* buffer = malloc(sizeof(CommandBuffer));
    buffer->capacity = 1000;
    buffer->commands = malloc(sizeof(EntityCommand) * buffer->capacity);
    buffer->count = 0;
    return buffer;
}

void cmdDestroyEntity(CommandBuffer* buffer, Entity entity) {
    if (buffer->count >= buffer->capacity) return;
    
    EntityCommand* cmd = &buffer->commands[buffer->count++];
    cmd->type = CMD_DESTROY_ENTITY;
    cmd->entity = entity;
}

void executeCommands(World* world, CommandBuffer* buffer) {
    for (int i = 0; i < buffer->count; i++) {
        EntityCommand* cmd = &buffer->commands[i];
        
        switch (cmd->type) {
            case CMD_DESTROY_ENTITY:
                destroyEntity(world, cmd->entity);
                break;
            // ... handle other commands
        }
    }
    
    buffer->count = 0;
}

Chapter 12: Data-Oriented Performance

12.1 SIMD Optimization

// simd_operations.c
#include <immintrin.h> // AVX/SSE

// Process 4 positions at once with SIMD
void simdMovementSystem(World* world, float dt) {
    ComponentArray* positions = world->componentArrays[COMPONENT_POSITION];
    ComponentArray* velocities = world->componentArrays[COMPONENT_VELOCITY];
    
    Position* posArray = (Position*)positions->data;
    Velocity* velArray = (Velocity*)velocities->data;
    
    __m128 dt4 = _mm_set1_ps(dt); // Broadcast dt to 4 floats
    
    uint32_t i = 0;
    
    // Process 4 at a time
    for (; i + 3 < positions->size; i += 4) {
        // Load 4 x positions
        __m128 x = _mm_set_ps(posArray[i+3].x, posArray[i+2].x,
                              posArray[i+1].x, posArray[i].x);
        
        // Load 4 x velocities  
        __m128 vx = _mm_set_ps(velArray[i+3].x, velArray[i+2].x,
                               velArray[i+1].x, velArray[i].x);
        
        // x = x + vx * dt (4 operations at once)
        x = _mm_add_ps(x, _mm_mul_ps(vx, dt4));
        
        // Store back
        float result[4];
        _mm_store_ps(result, x);
        posArray[i].x = result[0];
        posArray[i+1].x = result[1];
        posArray[i+2].x = result[2];
        posArray[i+3].x = result[3];
    }
    
    // Handle remaining elements
    for (; i < positions->size; i++) {
        posArray[i].x += velArray[i].x * dt;
        posArray[i].y += velArray[i].y * dt;
    }
}

12.2 Multithreading ECS Systems

// parallel_ecs.c
#include <pthread.h>

typedef struct {
    World* world;
    float deltaTime;
    uint32_t startIndex;
    uint32_t endIndex;
    ComponentArray* positions;
    ComponentArray* velocities;
} ThreadWorkData;

void* movementThreadFunc(void* arg) {
    ThreadWorkData* work = (ThreadWorkData*)arg;
    
    Position* posArray = (Position*)work->positions->data;
    Velocity* velArray = (Velocity*)work->velocities->data;
    
    // Process assigned range
    for (uint32_t i = work->startIndex; i < work->endIndex; i++) {
        posArray[i].x += velArray[i].x * work->deltaTime;
        posArray[i].y += velArray[i].y * work->deltaTime;
    }
    
    return NULL;
}

void parallelMovementSystem(World* world, float dt) {
    ComponentArray* positions = world->componentArrays[COMPONENT_POSITION];
    ComponentArray* velocities = world->componentArrays[COMPONENT_VELOCITY];
    
    int numThreads = 4;
    pthread_t threads[4];
    ThreadWorkData workData[4];
    
    uint32_t entitiesPerThread = positions->size / numThreads;
    
    // Launch threads
    for (int i = 0; i < numThreads; i++) {
        workData[i].world = world;
        workData[i].deltaTime = dt;
        workData[i].startIndex = i * entitiesPerThread;
        workData[i].endIndex = (i == numThreads - 1) ? 
                               positions->size : (i + 1) * entitiesPerThread;
        workData[i].positions = positions;
        workData[i].velocities = velocities;
        
        pthread_create(&threads[i], NULL, movementThreadFunc, &workData[i]);
    }
    
    // Wait for completion
    for (int i = 0; i < numThreads; i++) {
        pthread_join(threads[i], NULL);
    }
}

Chapter 13: Physics System with ECS

13.1 Physics Components

// physics_components.h

typedef struct {
    float x, y;
    float prevX, prevY; // For Verlet integration
} PhysicsPosition;

typedef struct {
    float x, y;
} PhysicsVelocity;

typedef struct {
    float x, y;
} PhysicsAcceleration;

typedef struct {
    float mass;
    float inverseMass; // 1/mass (store for performance)
} Mass;

typedef struct {
    float restitution;  // Bounciness (0-1)
    float friction;     // Surface friction
} Material;

typedef struct {
    bool isKinematic;  // Moved by code, not physics
    bool isStatic;     // Never moves
} PhysicsType;

typedef struct {
    float linearDamping;  // Velocity reduction
} Damping;

13.2 Physics Integration System

// physics_integration.c

void physicsIntegrationSystem(World* world, float dt) {
    // Verlet integration for stability
    Signature sig = (1ULL << COMPONENT_POSITION) | 
                   (1ULL << COMPONENT_VELOCITY) |
                   (1ULL << COMPONENT_ACCELERATION);
    
    Entity entity;
    QUERY_EACH(world, sig, entity) {
        Position* pos = getComponent(world, entity, COMPONENT_POSITION);
        Velocity* vel = getComponent(world, entity, COMPONENT_VELOCITY);
        Acceleration* accel = getComponent(world, entity, COMPONENT_ACCELERATION);
        
        // Update velocity
        vel->x += accel->x * dt;
        vel->y += accel->y * dt;
        
        // Apply damping
        Damping* damping = getComponent(world, entity, COMPONENT_DAMPING);
        if (damping) {
            vel->x *= (1.0f - damping->linearDamping * dt);
            vel->y *= (1.0f - damping->linearDamping * dt);
        }
        
        // Update position
        pos->x += vel->x * dt;
        pos->y += vel->y * dt;
        
        // Reset acceleration
        accel->x = 0;
        accel->y = 0;
    }
}

// Gravity system
void gravitySystem(World* world, float dt) {
    Signature sig = (1ULL << COMPONENT_ACCELERATION) | (1ULL << COMPONENT_MASS);
    
    float gravity = 980.0f; // pixels per second^2
    
    Entity entity;
    QUERY_EACH(world, sig, entity) {
        PhysicsType* type = getComponent(world, entity, COMPONENT_PHYSICS_TYPE);
        
        // Skip static and kinematic objects
        if (type && (type->isStatic || type->isKinematic)) continue;
        
        Acceleration* accel = getComponent(world, entity, COMPONENT_ACCELERATION);
        accel->y += gravity;
    }
}

Chapter 14: Rendering System with ECS

14.1 Render Components

// render_components.h

typedef struct {
    SDL_Texture* texture;
    SDL_Rect srcRect;
    float pivotX, pivotY; // 0-1, center of sprite
    int sortingLayer;
    int orderInLayer;
} SpriteRenderer;

typedef struct {
    float scaleX, scaleY;
} RenderScale;

typedef struct {
    bool flipX, flipY;
} RenderFlip;

typedef struct {
    bool visible;
} Visibility;

// Animated sprite
typedef struct {
    int currentFrame;
    int frameCount;
    float frameTime;
    float timer;
    bool loop;
    bool playing;
} SpriteAnimation;

14.2 Camera System

// camera_system.c

typedef struct {
    float x, y;
    float zoom;
    int viewportW, viewportH;
    SDL_Color clearColor;
    bool isMain;
} Camera;

// Transform world position to screen position
void worldToScreen(Camera* cam, float worldX, float worldY,
                   int* screenX, int* screenY) {
    float relX = (worldX - cam->x) * cam->zoom;
    float relY = (worldY - cam->y) * cam->zoom;
    
    *screenX = (int)(relX + cam->viewportW / 2);
    *screenY = (int)(relY + cam->viewportH / 2);
}

// Camera follow system
void cameraFollowSystem(World* world, float dt) {
    Signature camSig = (1ULL << COMPONENT_CAMERA);
    Signature targetSig = (1ULL << COMPONENT_POSITION) | (1ULL << COMPONENT_PLAYER);
    
    Entity camEntity;
    QUERY_EACH(world, camSig, camEntity) {
        Camera* cam = getComponent(world, camEntity, COMPONENT_CAMERA);
        
        // Find player
        Entity playerEntity;
        QUERY_EACH(world, targetSig, playerEntity) {
            Position* playerPos = getComponent(world, playerEntity, COMPONENT_POSITION);
            
            // Smooth follow
            float followSpeed = 5.0f;
            cam->x += (playerPos->x - cam->x) * followSpeed * dt;
            cam->y += (playerPos->y - cam->y) * followSpeed * dt;
            
            break;
        }
    }
}

14.3 Render System with Immediate Mode

// render_execution.c

void renderSystem(World* world, IMRenderContext* imCtx, Camera* cam) {
    // Clear
    SDL_SetRenderDrawColor(imCtx->renderer,
                          cam->clearColor.r, cam->clearColor.g,
                          cam->clearColor.b, cam->clearColor.a);
    SDL_RenderClear(imCtx->renderer);
    
    // Begin immediate rendering
    imBeginFrame(imCtx);
    
    Signature sig = (1ULL << COMPONENT_POSITION) | (1ULL << COMPONENT_SPRITE_RENDERER);
    
    Entity entity;
    QUERY_EACH(world, sig, entity) {
        Position* pos = getComponent(world, entity, COMPONENT_POSITION);
        SpriteRenderer* sprite = getComponent(world, entity, COMPONENT_SPRITE_RENDERER);
        
        // Check visibility
        Visibility* vis = getComponent(world, entity, COMPONENT_VISIBILITY);
        if (vis && !vis->visible) continue;
        
        // Transform to screen space
        int screenX, screenY;
        worldToScreen(cam, pos->x, pos->y, &screenX, &screenY);
        
        // Apply scale
        float scaleX = 1.0f, scaleY = 1.0f;
        RenderScale* scale = getComponent(world, entity, COMPONENT_RENDER_SCALE);
        if (scale) {
            scaleX = scale->scaleX;
            scaleY = scale->scaleY;
        }
        
        float w = sprite->srcRect.w * scaleX * cam->zoom;
        float h = sprite->srcRect.h * scaleY * cam->zoom;
        
        // Set layer
        imSetLayer(imCtx, sprite->sortingLayer);
        
        // Draw (immediate mode!)
        imDrawTextureRegion(imCtx, sprite->texture, &sprite->srcRect,
                          screenX - w * sprite->pivotX,
                          screenY - h * sprite->pivotY,
                          w, h, pos->rotation);
    }
    
    // Execute all draw commands
    imEndFrame(imCtx);
}

Chapter 15: Input System with ECS

15.1 Input Components

// input_components.h

typedef struct {
    float horizontal; // -1 to 1
    float vertical;
    bool jump;
    bool attack;
    bool interact;
} PlayerInput;

typedef struct {
    int playerIndex;
    SDL_GameController* controller;
} InputDevice;

15.2 Input Gathering System

// input_system.c

void inputGatheringSystem(World* world, float dt) {
    Signature sig = (1ULL << COMPONENT_PLAYER_INPUT) | (1ULL << COMPONENT_INPUT_DEVICE);
    
    const Uint8* keyboard = SDL_GetKeyboardState(NULL);
    
    Entity entity;
    QUERY_EACH(world, sig, entity) {
        PlayerInput* input = getComponent(world, entity, COMPONENT_PLAYER_INPUT);
        InputDevice* device = getComponent(world, entity, COMPONENT_INPUT_DEVICE);
        
        // Reset input
        input->horizontal = 0;
        input->vertical = 0;
        input->jump = false;
        input->attack = false;
        
        if (device->controller) {
            // Gamepad input
            Sint16 leftX = SDL_GameControllerGetAxis(device->controller,
                                                    SDL_CONTROLLER_AXIS_LEFTX);
            input->horizontal = leftX / 32767.0f;
            input->jump = SDL_GameControllerGetButton(device->controller,
                                                     SDL_CONTROLLER_BUTTON_A);
        } else {
            // Keyboard input
            if (keyboard[SDL_SCANCODE_LEFT]) input->horizontal = -1.0f;
            if (keyboard[SDL_SCANCODE_RIGHT]) input->horizontal = 1.0f;
            if (keyboard[SDL_SCANCODE_UP]) input->vertical = -1.0f;
            if (keyboard[SDL_SCANCODE_DOWN]) input->vertical = 1.0f;
            if (keyboard[SDL_SCANCODE_SPACE]) input->jump = true;
        }
    }
}

// Player movement system
void playerMovementSystem(World* world, float dt) {
    Signature sig = (1ULL << COMPONENT_POSITION) |
                   (1ULL << COMPONENT_VELOCITY) |
                   (1ULL << COMPONENT_PLAYER_INPUT);
    
    Entity entity;
    QUERY_EACH(world, sig, entity) {
        Velocity* vel = getComponent(world, entity, COMPONENT_VELOCITY);
        PlayerInput* input = getComponent(world, entity, COMPONENT_PLAYER_INPUT);
        
        float speed = 200.0f;
        vel->x = input->horizontal * speed;
        
        if (input->jump) {
            vel->y = -400.0f;
        }
    }
}

Chapter 16: Audio System with ECS

16.1 Audio Components

// audio_components.h

typedef struct {
    Mix_Chunk* sound;
    int channel;
    bool loop;
    float volume;
} AudioSource;

typedef struct {
    bool playOnStart;
    bool destroyOnFinish;
} AudioBehavior;

16.2 Audio System

// audio_system.c

void audioPlaybackSystem(World* world, float dt) {
    Signature sig = (1ULL << COMPONENT_AUDIO_SOURCE);
    
    Entity entity;
    QUERY_EACH(world, sig, entity) {
        AudioSource* source = getComponent(world, entity, COMPONENT_AUDIO_SOURCE);
        AudioBehavior* behavior = getComponent(world, entity, COMPONENT_AUDIO_BEHAVIOR);
        
        if (behavior && behavior->playOnStart && source->channel == -1) {
            int loops = source->loop ? -1 : 0;
            source->channel = Mix_PlayChannel(-1, source->sound, loops);
            
            if (source->channel >= 0) {
                Mix_Volume(source->channel, (int)(source->volume * MIX_MAX_VOLUME));
            }
        }
        
        // Check if finished
        if (source->channel >= 0 && !Mix_Playing(source->channel)) {
            source->channel = -1;
            
            if (behavior && behavior->destroyOnFinish) {
                destroyEntity(world, entity);
            }
        }
    }
}

Chapter 17: Particle System with ECS

17.1 Particle Components

// particle_components.h

typedef struct {
    float lifetime;
    float age;
} ParticleLifetime;

typedef struct {
    SDL_Color startColor;
    SDL_Color endColor;
    float startSize;
    float endSize;
} ParticleRenderer;

typedef struct {
    float emissionRate;
    float emissionTimer;
    float lifetimeMin, lifetimeMax;
    float speedMin, speedMax;
    int maxParticles;
    bool active;
} ParticleEmitter;

17.2 Particle Systems

// particle_system.c

void particleEmissionSystem(World* world, float dt) {
    Signature sig = (1ULL << COMPONENT_POSITION) | (1ULL << COMPONENT_PARTICLE_EMITTER);
    
    Entity emitterEntity;
    QUERY_EACH(world, sig, emitterEntity) {
        Position* emitterPos = getComponent(world, emitterEntity, COMPONENT_POSITION);
        ParticleEmitter* emitter = getComponent(world, emitterEntity, COMPONENT_PARTICLE_EMITTER);
        
        if (!emitter->active) continue;
        
        emitter->emissionTimer += dt;
        float emissionInterval = 1.0f / emitter->emissionRate;
        
        while (emitter->emissionTimer >= emissionInterval) {
            // Create particle entity
            Entity particle = createEntity(world);
            
            Position particlePos = {emitterPos->x, emitterPos->y, 0};
            addComponent(world, particle, COMPONENT_POSITION, &particlePos);
            
            float speed = randomFloat(emitter->speedMin, emitter->speedMax);
            float angle = randomFloat(0, 2.0f * M_PI);
            Velocity particleVel = {cosf(angle) * speed, sinf(angle) * speed, 0};
            addComponent(world, particle, COMPONENT_VELOCITY, &particleVel);
            
            ParticleLifetime lifetime = {
                randomFloat(emitter->lifetimeMin, emitter->lifetimeMax), 0
            };
            addComponent(world, particle, COMPONENT_PARTICLE_LIFETIME, &lifetime);
            
            emitter->emissionTimer -= emissionInterval;
        }
    }
}

void particleUpdateSystem(World* world, float dt) {
    Signature sig = (1ULL << COMPONENT_PARTICLE_LIFETIME);
    
    Entity entity;
    QUERY_EACH(world, sig, entity) {
        ParticleLifetime* lifetime = getComponent(world, entity, COMPONENT_PARTICLE_LIFETIME);
        
        lifetime->age += dt;
        
        if (lifetime->age >= lifetime->lifetime) {
            destroyEntity(world, entity);
        }
    }
}

void particleRenderSystem(World* world, IMRenderContext* imCtx) {
    Signature sig = (1ULL << COMPONENT_POSITION) |
                   (1ULL << COMPONENT_PARTICLE_LIFETIME) |
                   (1ULL << COMPONENT_PARTICLE_RENDERER);
    
    imSetBlendMode(imCtx, SDL_BLENDMODE_ADD);
    imSetLayer(imCtx, 100);
    
    Entity entity;
    QUERY_EACH(world, sig, entity) {
        Position* pos = getComponent(world, entity, COMPONENT_POSITION);
        ParticleLifetime* lifetime = getComponent(world, entity, COMPONENT_PARTICLE_LIFETIME);
        ParticleRenderer* renderer = getComponent(world, entity, COMPONENT_PARTICLE_RENDERER);
        
        float t = lifetime->age / lifetime->lifetime;
        
        SDL_Color color;
        color.r = (Uint8)(renderer->startColor.r + 
                         (renderer->endColor.r - renderer->startColor.r) * t);
        color.g = (Uint8)(renderer->startColor.g + 
                         (renderer->endColor.g - renderer->startColor.g) * t);
        color.b = (Uint8)(renderer->startColor.b + 
                         (renderer->endColor.b - renderer->startColor.b) * t);
        
        float size = renderer->startSize + (renderer->endSize - renderer->startSize) * t;
        
        imSetColor(imCtx, color.r, color.g, color.b, 255);
        imSetAlpha(imCtx, 1.0f - t);
        
        imDrawCircle(imCtx, pos->x, pos->y, size, true);
    }
}

Chapter 18: Collision System with Spatial Partitioning

18.1 Spatial Hash Grid

// spatial_hash.h

#define CELL_SIZE 64
#define GRID_WIDTH 128
#define GRID_HEIGHT 128

typedef struct {
    Entity entities[32];
    int count;
} GridCell;

typedef struct {
    GridCell cells[GRID_WIDTH * GRID_HEIGHT];
} SpatialHashGrid;

void initSpatialHash(SpatialHashGrid* grid) {
    memset(grid, 0, sizeof(SpatialHashGrid));
}

int hashPosition(float x, float y) {
    int cellX = ((int)x / CELL_SIZE) % GRID_WIDTH;
    int cellY = ((int)y / CELL_SIZE) % GRID_HEIGHT;
    
    if (cellX < 0) cellX += GRID_WIDTH;
    if (cellY < 0) cellY += GRID_HEIGHT;
    
    return cellY * GRID_WIDTH + cellX;
}

void insertIntoGrid(SpatialHashGrid* grid, Entity entity, float x, float y) {
    int hash = hashPosition(x, y);
    GridCell* cell = &grid->cells[hash];
    
    if (cell->count < 32) {
        cell->entities[cell->count++] = entity;
    }
}

18.2 Optimized Collision Detection

// collision_system.c

typedef struct {
    Entity entityA;
    Entity entityB;
    float penetration;
    float normalX, normalY;
} Contact;

typedef struct {
    Contact contacts[1000];
    int count;
} ContactManifold;

void collisionDetectionSystem(World* world, float dt) {
    // Build spatial hash
    SpatialHashGrid grid;
    initSpatialHash(&grid);
    
    Signature sig = (1ULL << COMPONENT_POSITION) | (1ULL << COMPONENT_COLLIDER);
    
    Entity entity;
    QUERY_EACH(world, sig, entity) {
        Position* pos = getComponent(world, entity, COMPONENT_POSITION);
        insertIntoGrid(&grid, entity, pos->x, pos->y);
    }
    
    // Check collisions
    ContactManifold manifold = {0};
    
    QUERY_EACH(world, sig, entity) {
        Position* posA = getComponent(world, entity, COMPONENT_POSITION);
        Collider* colA = getComponent(world, entity, COMPONENT_COLLIDER);
        
        // Query nearby entities
        int hash = hashPosition(posA->x, posA->y);
        GridCell* cell = &grid.cells[hash];
        
        for (int i = 0; i < cell->count; i++) {
            Entity other = cell->entities[i];
            if (entity >= other) continue;
            
            Position* posB = getComponent(world, other, COMPONENT_POSITION);
            Collider* colB = getComponent(world, other, COMPONENT_COLLIDER);
            
            // Check collision
            float dx = posB->x - posA->x;
            float dy = posB->y - posA->y;
            float distSq = dx*dx + dy*dy;
            float radiusSum = colA->radius + colB->radius;
            
            if (distSq < radiusSum * radiusSum) {
                Contact contact;
                contact.entityA = entity;
                contact.entityB = other;
                
                if (manifold.count < 1000) {
                    manifold.contacts[manifold.count++] = contact;
                }
            }
        }
    }
    
    // Resolve collisions
    resolveCollisions(world, &manifold);
}

Chapter 19: Complete Game Examples

19.1 Space Shooter Example

// space_shooter.c - Complete game using ECS + Immediate Mode

typedef struct {
    Engine* engine;
    IMGUIContext* gui;
    
    Entity player;
    int score;
    int lives;
    float spawnTimer;
    bool gameOver;
} SpaceShooter;

void initSpaceShooter(SpaceShooter* game) {
    game->engine = createEngine(800, 600, "Space Shooter ECS");
    game->gui = imguiCreate(game->engine->renderer);
    
    // Register systems
    World* world = game->engine->world;
    
    registerSystem(world, "Input", inputGatheringSystem,
                  (1ULL << COMPONENT_PLAYER_INPUT), 0);
    registerSystem(world, "Movement", movementSystem,
                  (1ULL << COMPONENT_POSITION) | (1ULL << COMPONENT_VELOCITY), 10);
    registerSystem(world, "Collision", collisionDetectionSystem,
                  (1ULL << COMPONENT_COLLIDER), 30);
    
    sortSystems(world);
    
    // Create player
    game->player = createEntity(world);
    Position playerPos = {400, 500, 0};
    Velocity playerVel = {0, 0, 0};
    PlayerInput input = {0};
    
    addComponent(world, game->player, COMPONENT_POSITION, &playerPos);
    addComponent(world, game->player, COMPONENT_VELOCITY, &playerVel);
    addComponent(world, game->player, COMPONENT_PLAYER_INPUT, &input);
    
    game->score = 0;
    game->lives = 3;
    game->gameOver = false;
}

void updateSpaceShooter(SpaceShooter* game, float dt) {
    if (game->gameOver) return;
    
    // Spawn enemies
    game->spawnTimer += dt;
    if (game->spawnTimer >= 2.0f) {
        game->spawnTimer = 0;
        
        float x = randomFloat(50, 750);
        Entity enemy = createEntity(game->engine->world);
        Position enemyPos = {x, -50, 0};
        Velocity enemyVel = {0, 100, 0};
        
        addComponent(game->engine->world, enemy, COMPONENT_POSITION, &enemyPos);
        addComponent(game->engine->world, enemy, COMPONENT_VELOCITY, &enemyVel);
    }
    
    // Update systems
    updateSystems(game->engine->world, dt);
}

void renderSpaceShooter(SpaceShooter* game) {
    SDL_SetRenderDrawColor(game->engine->renderer, 10, 10, 20, 255);
    SDL_RenderClear(game->engine->renderer);
    
    // Render game
    imBeginFrame(game->engine->imContext);
    renderingSystem(game->engine->world, game->engine->imContext);
    imEndFrame(game->engine->imContext);
    
    // Render GUI
    int mx, my;
    SDL_GetMouseState(&mx, &my);
    imguiBeginFrame(game->gui, mx, my, false);
    
    char scoreText[64];
    snprintf(scoreText, sizeof(scoreText), "Score: %d", game->score);
    imguiLabel(game->gui, scoreText, 10, 10);
    
    imguiEndFrame(game->gui);
    
    SDL_RenderPresent(game->engine->renderer);
}

19.2 Platformer Example

// platformer.c

typedef struct {
    Engine* engine;
    Entity player;
    bool onGround;
} Platformer;

void initPlatformer(Platformer* game) {
    game->engine = createEngine(800, 600, "Platformer ECS");
    
    // Create player
    game->player = createEntity(game->engine->world);
    Position pos = {400, 300, 0};
    Velocity vel = {0, 0, 0};
    
    addComponent(game->engine->world, game->player, COMPONENT_POSITION, &pos);
    addComponent(game->engine->world, game->player, COMPONENT_VELOCITY, &vel);
    
    // Create platforms
    for (int i = 0; i < 5; i++) {
        Entity platform = createEntity(game->engine->world);
        Position platformPos = {100 + i * 150, 500 - i * 80, 0};
        addComponent(game->engine->world, platform, COMPONENT_POSITION, &platformPos);
    }
}

void updatePlatformer(Platformer* game, float dt) {
    // Gravity
    gravitySystem(game->engine->world, dt);
    
    // Input
    inputGatheringSystem(game->engine->world, dt);
    playerMovementSystem(game->engine->world, dt);
    
    // Physics
    physicsIntegrationSystem(game->engine->world, dt);
    
    // Collision
    collisionDetectionSystem(game->engine->world, dt);
}

Chapter 20: Production-Ready ECS Engine

20.1 Complete Engine Architecture

// production_engine.h

typedef struct {
    // Core systems
    World* world;
    SDL_Window* window;
    SDL_Renderer* renderer;
    IMRenderContext* imContext;
    IMGUIContext* guiContext;
    
    // Timing
    float deltaTime;
    float fixedDeltaTime;
    float accumulator;
    uint64_t frameCount;
    
    // Configuration
    int screenWidth;
    int screenHeight;
    bool vsync;
    bool running;
    
    // Debug
    bool showDebug;
} ProductionEngine;

ProductionEngine* createProductionEngine(const char* title, int width, int height) {
    ProductionEngine* engine = calloc(1, sizeof(ProductionEngine));
    
    SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
    
    engine->window = SDL_CreateWindow(title,
        SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
        width, height, SDL_WINDOW_SHOWN);
    
    engine->renderer = SDL_CreateRenderer(engine->window, -1,
        SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
    
    engine->imContext = imCreateContext(engine->renderer);
    engine->guiContext = imguiCreate(engine->renderer);
    engine->world = createWorld();
    
    engine->screenWidth = width;
    engine->screenHeight = height;
    engine->running = true;
    engine->fixedDeltaTime = 1.0f / 60.0f;
    
    return engine;
}

void runEngine(ProductionEngine* engine) {
    Uint64 lastTime = SDL_GetPerformanceCounter();
    
    while (engine->running) {
        // Calculate delta time
        Uint64 currentTime = SDL_GetPerformanceCounter();
        engine->deltaTime = (float)(currentTime - lastTime) / SDL_GetPerformanceFrequency();
        lastTime = currentTime;
        
        if (engine->deltaTime > 0.1f) engine->deltaTime = 0.1f;
        
        // Handle events
        SDL_Event event;
        while (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT) {
                engine->running = false;
            }
        }
        
        // Fixed timestep update
        engine->accumulator += engine->deltaTime;
        
        while (engine->accumulator >= engine->fixedDeltaTime) {
            updateSystems(engine->world, engine->fixedDeltaTime);
            engine->accumulator -= engine->fixedDeltaTime;
        }
        
        // Render
        SDL_SetRenderDrawColor(engine->renderer, 20, 20, 30, 255);
        SDL_RenderClear(engine->renderer);
        
        imBeginFrame(engine->imContext);
        // Render systems add draw commands here
        imEndFrame(engine->imContext);
        
        SDL_RenderPresent(engine->renderer);
        
        engine->frameCount++;
    }
}

void destroyProductionEngine(ProductionEngine* engine) {
    destroyWorld(engine->world);
    imDestroyContext(engine->imContext);
    imguiDestroy(engine->guiContext);
    SDL_DestroyRenderer(engine->renderer);
    SDL_DestroyWindow(engine->window);
    SDL_Quit();
    free(engine);
}

20.2 Scene Management

// scene_manager.h

typedef struct Scene Scene;

struct Scene {
    const char* name;
    void (*onLoad)(ProductionEngine* engine, Scene* scene);
    void (*onUnload)(ProductionEngine* engine, Scene* scene);
    void (*onUpdate)(ProductionEngine* engine, Scene* scene, float dt);
    void* userData;
};

void loadScene(ProductionEngine* engine, Scene* scene) {
    if (scene->onLoad) {
        scene->onLoad(engine, scene);
    }
}

20.3 Serialization System

// serialization.h

typedef struct {
    FILE* file;
    bool writing;
} Serializer;

Serializer* createSerializer(const char* filename, bool write) {
    Serializer* s = malloc(sizeof(Serializer));
    s->file = fopen(filename, write ? "wb" : "rb");
    s->writing = write;
    return s;
}

void serialize_int(Serializer* s, int* value) {
    if (s->writing) {
        fwrite(value, sizeof(int), 1, s->file);
    } else {
        fread(value, sizeof(int), 1, s->file);
    }
}

void serialize_float(Serializer* s, float* value) {
    if (s->writing) {
        fwrite(value, sizeof(float), 1, s->file);
    } else {
        fread(value, sizeof(float), 1, s->file);
    }
}

void closeSerializer(Serializer* s) {
    fclose(s->file);
    free(s);
}

// Save entity
void saveEntity(World* world, Entity entity, Serializer* s) {
    serialize_int(s, (int*)&entity);
    
    // Save components
    for (int i = 0; i < world->componentCount; i++) {
        bool hasComp = hasComponent(world, entity, i);
        serialize_int(s, (int*)&hasComp);
        
        if (hasComp) {
            void* comp = getComponent(world, entity, i);
            ComponentArray* array = world->componentArrays[i];
            fwrite(comp, array->componentSize, 1, s->file);
        }
    }
}

Conclusion

You've Mastered:

  1. ECS Architecture - Complete understanding of Entity-Component-System design
  2. Data-Oriented Programming - Cache-friendly memory layouts and SoA patterns
  3. Immediate Mode Rendering - Stateless, declarative rendering APIs
  4. Complete Systems - Physics, rendering, input, audio, particles, collision
  5. Performance Optimization - SIMD, multithreading, spatial partitioning
  6. Production Patterns - Hierarchies, commands, reactive systems, serialization

Key Principles Learned:

  • Composition over Inheritance - Build complex entities from simple components
  • Data Locality Matters - Cache-friendly iteration is 10-100x faster
  • Stateless is Simpler - Immediate mode eliminates entire classes of bugs
  • Profile Everything - Measure before optimizing
  • Ship Games - Perfect code doesn't exist, shipped games do

Next Steps:

  1. Build a Complete Game - Start small, maybe Asteroids or Breakout
  2. Study Production ECS - EnTT (C++), Flecs (C), Unity DOTS
  3. Profile and Optimize - Use actual performance data to guide decisions
  4. Experiment - Try different architectures, find what works for you
  5. Share Your Work - Open source helps everyone learn

Resources for Further Learning:

Final Thoughts:

ECS + Immediate Mode gives you the foundation for building high-performance game engines that scale to thousands of entities. The patterns you've learned are used in AAA games and production engines worldwide.

Start simple, iterate quickly, and remember: the best architecture is the one that lets you finish your game.

You now have all the tools and knowledge to build professional-quality game engines. Go build something amazing!


Happy coding, and may your frame rates be high and your bugs be few!