The Complete Guide to ECS-Driven and Immediate Mode Graphics Programming
Table of Contents
- Introduction to Data-Oriented Design
- ECS Architecture Fundamentals
- Setting Up the ECS Framework
- Immediate Mode Concepts
- Building Your First ECS Engine
- Component Design Patterns
- System Architecture
- Immediate Mode Rendering
- Immediate Mode GUI Integration
- Memory Management in ECS
- Advanced ECS Patterns
- Data-Oriented Performance
- Physics System with ECS
- Rendering System with ECS
- Input System with ECS
- Audio System with ECS
- Particle System with ECS
- Collision System with ECS
- Complete Game Examples
- 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:
- Be stateless (data lives in components)
- Operate on component arrays (not individual entities)
- Be independent (minimize system dependencies)
- Have clear, single responsibilities
- 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:
- ECS Architecture - Complete understanding of Entity-Component-System design
- Data-Oriented Programming - Cache-friendly memory layouts and SoA patterns
- Immediate Mode Rendering - Stateless, declarative rendering APIs
- Complete Systems - Physics, rendering, input, audio, particles, collision
- Performance Optimization - SIMD, multithreading, spatial partitioning
- 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:
- Build a Complete Game - Start small, maybe Asteroids or Breakout
- Study Production ECS - EnTT (C++), Flecs (C), Unity DOTS
- Profile and Optimize - Use actual performance data to guide decisions
- Experiment - Try different architectures, find what works for you
- Share Your Work - Open source helps everyone learn
Resources for Further Learning:
- EnTT: https://github.com/skypjack/entt (C++ ECS library)
- Flecs: https://github.com/SanderMertens/flecs (C ECS with excellent docs)
- Data-Oriented Design: Book by Richard Fabian
- Game Programming Patterns: Robert Nystrom's free online book
- Unity DOTS: Modern data-oriented ECS documentation
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!