Repository navigation
How SparkEngine Works
This page explains SparkEngine in two layers:
- High level: plain-English explanation for absolute beginners.
- Low level: concrete technical flow (systems, frame order, ownership, threading).
If you only read one section, read "The 60-Second Mental Model" and "One Frame, Step-by-Step".
Think of SparkEngine like a restaurant kitchen:
- EngineContext is the head chef who knows where every station is.
- Subsystems (graphics, physics, audio, input, networking, scripting) are stations.
- Game modules are the menu for your game (FPS, RPG, MMO, etc.).
- Main loop is the repeating rhythm: take orders -> cook -> plate -> repeat every frame.
Every frame, SparkEngine:
- Reads player/network input.
- Updates simulation (physics, AI, gameplay logic).
- Updates visuals/audio from latest simulation state.
- Renders the frame.
- Repeats.
When the executable starts, it creates core systems and wires them into EngineContext.
The engine loads one or more game modules (.dll/.so) that contain game-specific rules.
The engine runs a continuous loop until quit.
Modules unload first, then engine systems shut down in reverse dependency order.
That is the whole lifecycle.
This is the practical per-frame sequence used by SparkEngine's ECS/system orchestration:
-
Input phase
- Keyboard/mouse/gamepad state gathered.
- Network packets consumed.
-
Simulation phase
- Physics integration and collision resolution.
- Animation updates from movement/state.
- AI decision updates from world state.
- Audio/gameplay lifecycle updates.
-
Render phase
- Renderer consumes final world/component state.
- Post-processing chain runs.
- UI composed.
-
Present / end-of-frame work
- Frame metrics/profiling.
- Deferred work queues.
SparkEngine's documented ECS execution order is:
Physics -> Animation -> AI -> Audio -> Lifecycle -> Render
This order avoids common dependency bugs (for example, animation reading stale physics data).
- Engine systems are owned centrally (RAII; mostly
std::unique_ptr). -
EngineContextexposes non-owning pointers for access. - Named getters (
GetGraphics(),GetPhysics(), etc.) are convenience APIs. - Generic registry (
RegisterSystem<T>()/GetSystem<T>()) supports extensibility.
From anywhere in engine or game-module code, the service locator is:
#include "Core/EngineContext.h"
auto* ctx = Spark::EngineContext::Get();
auto* physics = ctx->GetPhysics(); // non-owning, never delete
auto* graphics = ctx->GetGraphics();
// Extension: register a game-defined subsystem and retrieve it by type:
ctx->RegisterSystem<MyCombatDirector>(std::make_unique<MyCombatDirector>());
auto* combat = ctx->GetSystem<MyCombatDirector>();Why this matters:
- Keeps ownership clear (fewer leaks/double frees).
- Allows game modules to access subsystems without singletons everywhere.
SparkEngine supports dependency-aware subsystem registration.
- Each subsystem can declare dependencies (
DependsOn<...>). -
InitializeAll()performs ordered startup (topological dependency order). -
ShutdownAll()runs reverse order.
Why this matters:
- Prevents "system used before initialized" crashes.
- Makes large engine startup predictable and testable.
Game code lives in dynamic modules implementing IModule.
Typical lifecycle:
CreateModule()GetModuleInfo()OnLoad(IEngineContext*)- Repeated
OnUpdate(deltaTime)and optionalOnRender() OnUnload()DestroyModule()
A minimal module looks like this — SPARK_IMPLEMENT_MODULE emits the
CreateModule/GetModuleInfo/DestroyModule exports, and
Spark/ModuleDllMain.h emits the Windows DllMain that calls
DisableThreadLibraryCalls:
// MyGame/Source/MyGameModule.h
#pragma once
#include <Spark/IModule.h>
class MyGameModule : public Spark::IModule
{
public:
void OnLoad(Spark::IEngineContext* ctx) override;
void OnUpdate(float dt) override;
void OnUnload() override;
};
// MyGame/Source/MyGameModule.cpp
#include "MyGameModule.h"
#include <Spark/ModuleDllMain.h> // emits DllMain on Windows, no-op elsewhere
void MyGameModule::OnLoad(Spark::IEngineContext* ctx) { /* subscribe, spawn */ }
void MyGameModule::OnUpdate(float dt) { /* per-frame tick */ }
void MyGameModule::OnUnload() { /* unsubscribe */ }
SPARK_IMPLEMENT_MODULE(MyGameModule) // CreateModule / GetModuleInfo / DestroyModuleWhy this matters:
- Engine and game logic are decoupled.
- You can ship multiple gameplay modules with one engine core.
SparkEngine uses an RHI abstraction with multiple backends (D3D11 primary, plus experimental D3D12/Vulkan/Metal/OpenGL and NullRHI fallback).
Render path at a high level:
- Collect visible scene state.
- Build/execute render passes.
- Run post-processing stack.
- Composite UI.
- Present.
If no GPU backend is available, NullRHI/headless fallback keeps engine logic running.
- Main/game thread: orchestration, gameplay state ownership.
- Physics jobs: multithread-capable via physics job dispatch.
- Network message queues: mutex-protected handoff.
- Render submission: generally main-thread coordinated, backend-dependent internals.
Rule of thumb for contributors:
- If a system owns gameplay state, treat it as game-thread authoritative unless explicitly designed otherwise.
- Use queues/events for cross-thread boundaries, not shared mutable state without synchronization.
Imagine pressing W to move forward:
- Input system marks W as pressed.
- Player movement system requests velocity change.
- Physics system moves the character capsule and resolves collisions.
- Animation system chooses run/walk blend based on resulting speed.
- Camera system updates final camera transform.
- Audio system plays footstep events if movement state changed.
- Renderer draws world from the new camera/player position.
- You see movement on screen this frame.
Same idea applies to shooting, interacting, opening doors, etc.
When reading a new subsystem, answer these 6 questions:
-
Who owns it? (
unique_ptrowner or external owner?) - Who initializes it? (EngineContext/manual lifecycle?)
- When is it updated? (which phase/order?)
- What data does it read/write? (components, events, resources)
- Thread affinity? (game thread only, worker-safe, or mixed?)
- How does it fail? (assert/log/expected return path?)
If these are clear, the subsystem is usually easy to reason about.
If this page made sense, continue in this order:
Published from 2b03dc797148. Edit the canonical source in wiki/.
- Documentation
- Docs route
- Wiki index
- Guides
- Tutorials
- Samples
- Examples
- API Reference
- API route
- Reference
- Build Guide
- Dependencies
- FAQ
- Changelog
- Roadmap
- Contributing
- Code of Conduct
- Home
- FAQ
- Getting Started
- Quick-Start Tutorial
- Making Your First Game
- Making Your First Multiplayer Game
- Artist Workflow Guide
- Editor Walkthrough
- Migration Guide
- How SparkEngine Works
- Architecture Overview
- Engine Architecture Flowchart
- Creating a Game Module
- Game Modules (catalog)
- Entity Component System
- Rendering and Graphics
- Physics
- Cloth Simulation
- Audio
- Input System
- Camera System
- Scripting with AngelScript
- Visual Scripting
- AI and Navigation
- Animation
- 2D Systems
- Networking
- Dedicated Server
- Multiplayer Quick Start
- Area Server Architecture
- Scene Management
- Large World Support
- Collaborative Editing
- Coroutine System
- Event System
- Event Response System
- Job System
- UI System
- UI Layout Extensions
- Localization
- Dialogue System
- Destruction System
- Replay System
- Achievement System
- Loading System
- Mod System
- Content Delivery
- Tween System
- Memory Integrity
- Gameplay Systems
- Terrain and Procedural Generation
- Save System
- Persistence System
- Day Night Cycle and Weather
- Cinematic Sequencer
- Runtime Prefabs
- SparkEditor
- Editor Tutorials
- SparkConsole
- SparkDaemon
- Shader Pipeline
- Asset Pipeline
- Asset Validation
- Asset Migration
- Game Packaging
- Online Services
- DataTable System
- Loot and Crafting System
- CSG System
- Font System
- Timer Manager
- Movie Render Pipeline
- HLOD and World Partition
- Remote Debug System
- Selection Manager
- Asset Dependency Graph
- Editor Automation
- File Watcher
- Project Templates
- System Requirements
- VR Support
- Mobile Platform
- Accessibility
- Platform Input
- Platform Certification
- Cross-Compilation: Wine Testing
- RHI Abstraction Layer
- D3D11 Backend
- D3D12 Backend
- Vulkan Backend
- OpenGL Backend
- Metal Backend
- DXR Raytracing
- Hybrid Ray Tracing
- Upscaling (DLSS/FSR)
- Render Graph
- Shader Graph
- GPU Particles
- GPU-Driven Rendering
- Volumetric Fog
- Volumetric Clouds
- Global Illumination
- Virtual Texturing
- Water Rendering
- Clustered Lighting
- Material System
- Post-Processing
- Shadow System
- Particle System
- Decal System
- Sky and Atmosphere
- Foliage System
- Mesh Shaders
- Neural Rendering
- Configuration Reference
- Performance Tips
- Benchmark Framework
- Threading Model
- Fuzz Policy and Parser Security
- Memory Safety
- Memory Management Patterns
- Build System and CMake Modules
- Profiler and Debugging
- Performance Profiling Guide
- Telemetry System
- Crash Reporting
- Golden Image Testing
- Utilities
- Testing
- Fuzz Policy and Parser Security
- Codebase Statistics
- Codebase Health
- Error Handling Patterns
- Hot Reload Overview
- Troubleshooting
- Contributing
- Workflow Patterns
- Build Optimizations
- CI Reproducible Builds
- GitHub API and PR Checks
- Git Rebase Conflicts
- Clang-Format
- Code Quality Violations
- AI Bloat Pattern
- MinGW + Wine Cross-Compilation
- Live Editor Testing
- Engine & Renderer Landscape
- DuetOS Portability Catalog
- Five-Engine Analysis
- Eleven-Engine Analysis
- ThorVG / Unity Graphics Analysis
- Advanced Techniques Catalog
- Third-Party Library Evaluation
- Engine Viability Evaluation
- Engine Feature Recommendations
- Project Recommendations
- Mac Compatibility Analysis
- Codebase Observations
- Codebase Bloat Audit
- Test Suite Audit
- Documentation Coverage Audit
- ThirdParty Dependencies Audit
- Load Test Baseline
- Gameplay Systems Status
- SparkGame Module Status
- Stub and Abandoned Features
- Memory Integrity System
- Memory Safety Evaluation
- Hardware Acceleration Systems
- Jolt Physics Integration
- GPU/CPU Separation Plan
- Daemon Services Architecture
- Reflection & Polymorphism Refactoring Plan
- SparkBuild In-Tree
- Wine No-JobSystem Breakthrough
- Wine Role and Fallback Tiers