A work‑in‑progress, modular C++20 engine focused on foundational architecture, technical design, and clean systems engineering rather than commercial features or production‑ready tooling.
XGE is an exploration of modern engine architecture: reflection‑driven data, modular runtime systems, dual‑runtime scripting (CoreCLR + NativeAOT), and strict layering. The goal is to build a solid, extensible foundation that can grow into a full engine over time.
The project emphasizes:
- strict layering
- reflection‑driven data
- modular runtime systems
- dual scripting runtimes (CoreCLR + NativeAOT, extendable to support other language like Squirrel, Lua, etc)
- editor integration
- clean, STL‑free public API surfaces
- Overview
- Design Philosophy
- Architecture
- Current Engine Capabilities
- Current Work‑In‑Progress
- Rendering Architecture
- Network Serialization (Planned)
- Scripting Architecture
- Repository Structure
- Reflection System
- JSON Serialization
- Runtime Flow
- Roadmap
- License
XGE is a modular, reflection‑driven, cross‑platform engine foundation designed for long‑term scalability.
It prioritizes clean architecture, strict layering, and runtime modularity over monolithic engine design.
XGE is built to support:
- multiple scripting runtimes
- multiple rendering backends
- editor/runtime separation
- deterministic data pipelines
- future network replication
- future visual scripting
- future hot‑reload workflows
No platform code in core.
No renderer code in scripting.
No STL in public headers.
Stable ABI.
Minimal implementation in headers.
Tree‑sitter generates metadata used for:
- JSON serialization
- editor property panels
- network replication (future)
- deterministic data pipelines
Everything is a module:
- renderer
- scripting runtime
- editor kernel
- future physics/audio/gameplay
Load modules at runtime.
Swap renderer or scripting backend without recompiling the engine.
xgBase → Base types, macros, platform detection
xgPlatform → Platform backends (Win32, SDL)
xgCore → Engine runtime
Renderers → DX12, Vulkan (standard modules)
Script Hosts → CoreCLR, NativeAOT (script module facilitator)
Script Modules→ Loaded in the HSM tree (Editor, Game01, PhysicsEngine, etc.)
Editor → Native editor kernel + managed editor runtime
Kits → Shared libraries (editor, runtime, runtime-managed)
- Modular engine with dynamic module loading
- Reflection‑driven configuration
- JSON serialization via reflection metadata
- Strict layering
- STL‑free public API surface
- Hierarchical system scoping
- SDL3 window backend
- Win32 file backend
- Cross‑platform module loader
- Event system (SDL →
xgEvent)
- DirectX 12 renderer module
- Swap chain, command queue, render targets
- Clear + present rendering loop
- Dual‑runtime scripting: CoreCLR + NativeAOT
- Unified
ScriptModuleinterface - Managed editor runtime
- Native editor kernel module
- Full ImGui integration
- Docking + multi‑viewport
- Viewport, Outliner, Properties, Timeline, Assets panels
XGE’s runtime is built around HSM (Hierarchical Script Modules) — a structured tree of ScriptModules that defines ownership, routing scope, and execution domains.
The system consists of:
ScriptTree — hierarchical tree of ScriptModules (HSM)(DONE)System Tree - hierarchical scoping system(DONE - 2026/07/31)SceneGraph — Scoped, Multi‑Purpose Graph System(DONE - 2026/07/31)Messenger — message routing backbone(DONE)Route Traversal — parent/child/sibling routing rules(DONE)Codec Registry — dynamic message encoding/decoding(DONE)- Dispatch Groups — thread‑affinity execution domains (IN PROGRESS)
Together, these form XGE’s core runtime architecture.
ScriptTree stores every ScriptModule in a strict hierarchy:
- parent → child relationships
- sibling traversal
- domain grouping (Main, Worker, Pinned, Dedicated)
- deterministic update order
- scoped message routing
Each ScriptModule is a node in the tree, enabling:
- hierarchical updates
- scoped messaging
- clean separation of systems
- future hot‑reload boundaries
- system scoping
- Systems are registered per ScriptModule
- Lookup walks upward through parents
- Closest ancestor wins
- Children inherit unless they override
- No global systems; all systems are scoped
- A
Systemis any overridable runtime service - Declared with
XG_DECLARE_BASE_SYSTEM(Type) - Implemented with
XG_DECLARE_SYSTEM(Implementation, Type) - Resolved hierarchically through the ScriptTree
struct SceneGraphFactory : public System {
XG_DECLARE_BASE_SYSTEM(SceneGraphFactory)
};class SceneGraphFactoryImpl : public SceneGraphFactory {
XG_DECLARE_SYSTEM(SceneGraphFactoryImpl, SceneGraphFactory)
};engine->RegisterSystem(module, new SceneGraphFactoryImpl());auto* factory = engine->GetSystem<SceneGraphFactory>(module);engine->RegisterSystem(childModule, new SceneGraphFactoryImpl());A SceneGraph is a first‑class System, which means:
- It is scope‑based
- It is registered per ScriptModule
- It is resolved hierarchically through the ScriptTree
- The closest ancestor SceneGraph is used when requested
- A ScriptNode can override its parent’s SceneGraph by registering its own
This makes SceneGraphs modular, replaceable, and context‑specific.
ActorIDs are generated globally by the engine’s ActorRegistry.
- ActorIDs are unique across the entire engine
- ActorIDs are not owned by any SceneGraph
- ActorIDs allow the same logical actor to appear in multiple graphs
This enables multi‑representation and multi‑domain processing.
An ActorID can have multiple representations, one per SceneGraph.
Examples of representations:
- Transform hierarchy
- Physics bodies
- ECS entities
- Rendering nodes
- AI nodes
- Metadata nodes
Each SceneGraph stores its own representation of the same ActorID.
A SceneGraph is not a single “scene tree.”
It is a domain‑specific graph with a specialized purpose.
Examples of specialized SceneGraphs:
- Physics SceneGraph — rigid bodies, colliders, constraints
- ECS SceneGraph — entity/component relationships
- Render SceneGraph — drawable objects, materials, LODs
- Transform SceneGraph — parent/child transforms
- Metadata SceneGraph — editor properties, tags, names
Each SceneGraph defines its own storage, traversal, and rules.
A ScriptNode may register multiple SceneGraphs, each serving a different domain.
For example, a ScriptNode may contain:
- A Transform SceneGraph
- A Physics SceneGraph
- A Render SceneGraph
- A Metadata SceneGraph
All scoped to that ScriptNode and inherited by its children unless overridden.
Messenger delivers ScriptMessage objects across the ScriptTree using explicit Route objects.
Routing is deterministic and handled by ScriptTree; encoding/decoding is handled by the Codec Registry.
Messages are delivered by constructing a Route and passing it to Messenger::Send.
ScriptMessage msg{ PlayerDamaged{ .Amount = 10 } };
// Direct message to a specific ScriptModule
Messenger->Send(msg, Route::Direct("Player"));
// Broadcast to all children of a ScriptModule
Messenger->Send(msg, Route::ChildrenOf("UIRoot"));
// Bubble upward to parents
Messenger->Send(msg, Route::ParentOf("Inventory"));
// Send to siblings
Messenger->Send(msg, Route::SiblingsOf("EnemyAI"));
// Filtered routing (predicate receives ScriptModule*)
Messenger->Send(msg, Route::Filtered([](ScriptModule* m) {
return m->GetId() == "HUD";
}));
// Global broadcast
Messenger->SendToAll(msg);Messages can be routed using simple rules:
- ToParent — bubble upward
- ToChildren — broadcast downward
- ToSiblings — lateral routing
- ToDomain — cross‑thread dispatch
- ToSpecificNode — direct targeting
Example:
Messenger->Send(msg, Route::ToParent);
Messenger->Broadcast(msg, Route::ToChildren);
Messenger->Send(msg, Route::ToDomain(Thread::Worker));The CodecRegistry is responsible for encoding and decoding all ScriptMessage payloads.
It supports both native C++ types and dynamic types (DynamicObject), using reflection metadata (TypeSchema) generated by the TypeRegistry.
CodecRegistry provides:
- native encoders/decoders (registered per typeName)
- dynamic JSON encode/decode for
DynamicObject - lookup of encoder/decoder functions
- unified public
Encode/Decodeentry points - integration with Messenger and ScriptTree
Native types register their encoder/decoder functions:
registry.RegisterEncoder("PlayerDamaged", Encode_PlayerDamaged);
registry.RegisterDecoder("PlayerDamaged", Decode_PlayerDamaged);When Messenger delivers a message, CodecRegistry selects the correct encoder based on the message’s typeName.
Dynamic types (DynamicObject) do not register encoders.
CodecRegistry automatically falls back to JSON‑based dynamic encoding:
EncodeDynamic(obj, outMessage)DecodeDynamic(message, schema, outObj)
This allows:
- editor inspection
- flexible message formats
- runtime‑generated message types
- future network serialization
All message encoding flows through two entry points:
bool Encode(const char* typeName,
const void* src,
const TypeSchema* schema,
ScriptMessage& outMessage) const;
bool Decode(const char* typeName,
const ScriptMessage& message,
const TypeSchema* schema,
void* dst) const;These functions:
- select native or dynamic codec
- serialize the payload into
ScriptMessage - deserialize payload into native struct or
DynamicObject
Messenger does not serialize messages itself.
Instead, before delivery:
- Messenger calls
CodecRegistry::Encode - ScriptTree routes the encoded message
- Receiving ScriptModule calls
CodecRegistry::Decode - The decoded object is passed to the handler method
This ensures:
- consistent message formats
- reflection‑driven serialization
- safe cross‑module communication
- future network replication compatibility
Dispatch groups define execution domains:
- Input
- Gameplay
- Animation
- RenderPrep
- Async
- Custom
Modules declare dispatch groups via attributes (C#) or macros (C++).
The engine handles scheduling and thread affinity.
The HSM + ScriptTree + Messenger + Route Traversal + Codec Registry + Dispatch Groups form XGE’s core runtime architecture.
They provide:
- deterministic module updates
- safe multi‑threaded messaging
- structured message encoding
- hierarchical routing
- clean separation of systems
- future‑proof foundations for AI, gameplay, editor tooling, and networking
Validates:
- command queue + allocator
- descriptor heaps
- swap chain
- GPU/CPU sync
- render targets
Will validate:
- cross‑platform rendering
- explicit sync
- descriptor sets
- pipeline layout abstraction
- multi‑queue support
Both backends will share:
- unified renderer interface
- FrameGraph
- resource abstractions
- enumerate fields
- identify replicated fields
- serialize only required fields
- apply deltas deterministically
- compare previous vs current state
- emit only changed fields
- primitive packing
- boolean compression
- enum packing
- optional string hashing
XG_FIELD(meta=Replicated)
XG_FIELD(meta=Replicated | Interpolated)- method lookup
- parameter serialization
- stub generation
- hot reload
- reflection
- dynamic assembly loading
- debugging
- full .NET ecosystem
- zero dependencies
- instant startup
- no JIT
- native debugging
- predictable performance
Both runtimes implement:
class ScriptModule {
public:
virtual bool Init(ScriptEngine* engine) = 0;
virtual void Update(float dt) = 0;
virtual void Shutdown() = 0;
virtual bool IsValid() const = 0;
};XGE/
├── docs/ # Documentation, diagrams, design notes
├── gameroot/ # Runtime deployment root
│ ├── bin/ # Native binaries (engine + modules)
│ ├── editor/ # Editor assets (fonts, UI resources)
│ └── coreclr/ # CoreCLR runtime + managed assemblies
│ └── Microsoft.NETCore.App/
│ └── 10.0.8/
├── xgEngine/ # Engine source tree
│ ├── 3rdparty/ # External libraries (SDL, ImGui, etc.)
│ ├── tools/ # Engine tools (reflection generator, etc.)
│ │ └── xgReflectGen/
│ ├── xgBase/ # Base types, macros, platform detection
│ │ └── math/
│ ├── xgCore/ # Core engine runtime
│ ├── xgEditor.CoreCLR/ # Editor module
│ ├── xgEditorKit/ # Shared Editor utilities
│ ├── xgPlatform/ # Platform backends (Win32, SDL)
│ ├── xgRendererDX12/ # DirectX 12 renderer module
│ ├── xgScriptCoreCLR/ # CoreCLR scripting host module
│ ├── xgScriptNative/ # NativeAOT scripting host module
│ │ └── interop/
│ ├── xgSharedKit/ # Shared Editor/Runtime library
│ ├── xgSharedKit.Managed/ # Managed Shared Editor/Runtime library
│ ├── xgEditorKernel/ # Native editor kernel module
│ ├── xgUtility/ # Utility layer (platform helpers, etc.)
│ │ └── platform/
│ └── xge/ # Engine executable (launcher)
- Tree‑sitter C++
- generates
.generated.h - produces
RawFieldInfoarrays - supports bool/int/float/const char*
- uses
offsetof()
Example:
XG_SERIALIZABLE()
struct EngineConfig {
XG_FIELD()
const char* RendererModule = nullptr;
};- custom wrapper over nlohmann::json
- STL only in
.cpp - reflection‑driven serialization
- load config
- load renderer module
- load scripting module
- initialize systems
- fixed update
- variable rendering
- ScriptHost update
- module updates
- ScriptHost shutdown
- renderer shutdown
- platform shutdown
- Dispatch Groups
- HSM tooling
- messaging inspector
- editor integration
- hot reload boundaries
- scene graph
- component system
- property inspector
- enum/array reflection
- FrameGraph
- GPU‑driven rendering
- full editor
- shader graph
- hot‑reloadable C# gameplay
- network replication
- RPC system
- job system
- Vulkan parity
MIT License.

