Vulkan Schnee 0.0.1
High-performance rendering engine
Loading...
Searching...
No Matches
Data Storage

Vulkan Schnee uses EnTT for high-performance data-oriented ECS, combined with wrapper classes for game logic.

Core Concepts

Entity

entt::entity is a lightweight handle (32-bit integer). Entities own components stored in the registry.

Registry

Ecs::RegistryManager::get() provides access to the global EnTT registry. All component data lives here.

auto& registry = Ecs::RegistryManager::get();
entt::entity entity = registry.create();
registry.emplace<Ecs::LocalTransform>(entity, glm::mat4{1.0f});
static entt::registry & get()
Gets the registry for all components.

Components

Components are pure data structs in the Ecs namespace (defined in Engine/include/Engine/Ecs/EcsData.h).

namespace Ecs {
struct LocalTransform {
glm::mat4 matrix;
};
struct WorldTransform {
glm::mat4 matrix;
};
struct Tick {
EngineCore::ITickable* tickable;
};
}
Data structs for the Entity Component System.
glm::mat4 matrix
Definition EcsData.h:303
Tag for everything which wants to receive tick events.
Definition EcsData.h:146
EngineCore::ITickable * tickable
Definition EcsData.h:147
glm::mat4 matrix
Definition EcsData.h:307

Architecture Pattern

Vulkan Schnee uses composition for data, inheritance for type safety.

Data layer: EnTT components store raw data for cache-friendly iteration. Logic layer: C++ classes wrap entities and provide game logic.

Entity (Base Class)

EngineCore::Entity wraps an entt::entity handle.

Fields:

  • entt::entity data - Handle to ECS entity
  • std::vector<std::shared_ptr<LogicComponent>> components - Logic components (not ECS)
  • bool allowTicking - Tick enable flag

Usage:

class Entity {
protected:
entt::entity data = entt::null;
};

Entities register themselves for ticking:

void Entity::enableTick(bool enable) {
auto& registry = Ecs::RegistryManager::get();
if (enable) {
registry.emplace<Ecs::Tick>(data, this);
}
}

Actor (Subclass)

EngineCore::Actor extends Entity with transform and scene graph.

Additional fields:

  • std::shared_ptr<SceneNode> sceneNode - Scene graph node
  • Transform worldTransform - Cached world transform
  • std::vector<MeshComponent*> meshComponents - Rendering components

Usage:

class Actor : public Entity {
public:
glm::vec3 getActorLocation() const;
void setActorLocation(glm::vec3 newLocation);
glm::mat4 getWorldTransform() const;
};

Actors live in the scene graph. Transform changes propagate through hierarchy.

Data Flow

1. Actor Creation

// Scene spawns actor
Actor* actor = scene->spawnActor<MyActor>(spawnTransform);
// Actor constructor creates ECS entity internally
// Entity base class initializes: data = registry.create()

2. Component Attachment

// MeshComponent wraps mesh rendering data
class MeshComponent {
entt::entity componentEntity; // Separate entity for component data
Ecs::StaticMeshData* staticMeshData;
};
// Constructor creates component entity and attaches data
MeshComponent::MeshComponent(...) {
auto& registry = Ecs::RegistryManager::get();
componentEntity = registry.create();
staticMeshData = &registry.emplace<Ecs::StaticMeshData>(componentEntity);
registry.emplace<Ecs::Parent>(componentEntity, actor->data);
}
Stores parent relationships.
Definition EcsData.h:44

3. Data Synchronization

Scene graph transforms sync to ECS:

// SceneNode marks transforms dirty
registry.emplace_or_replace<Ecs::TransformDirty>(entity);
// Engine syncs hierarchy
auto view = registry.view<Ecs::TransformDirty, Ecs::WorldTransform>();
for (auto entity : view) {
// Update world transform from parent chain
}
Tag for dirty transforms (used by scene graph synchronization)
Definition EcsData.h:113

4. Rendering Query

Renderer iterates ECS components directly (no wrapper overhead):

// Query all mesh components for rendering
auto view = registry.view<Ecs::MeshComponentRef>();
for (auto entity : view) {
auto& meshRef = view.get<Ecs::MeshComponentRef>(entity);
MeshComponent* mesh = meshRef.component;
// Upload transform, draw mesh
}
Reference to a MeshComponent for ECS-based iteration. Allows efficient querying of all mesh component...
Definition EcsData.h:138
EngineCore::MeshComponent * component
Definition EcsData.h:139

Wrapper vs ECS Storage

Wrapper Classes (Slow Path)

Game logic uses wrappers: Actor, MeshComponent, Scene.

Purpose:

  • Type-safe API
  • Virtual functions (beginPlay, tick, endPlay)
  • Complex operations (physics, input, animation)

Access pattern:

actor->setActorLocation(newPos); // Wrapper method
// → Updates scene graph
// → Marks ECS transform dirty

ECS Storage (Fast Path)

Renderer uses ECS directly for hot loops.

Purpose:

  • Cache-friendly iteration
  • SIMD-friendly data layout
  • Parallel processing

Access pattern:

auto view = registry.view<Ecs::WorldTransform, Ecs::MeshPrimitiveData>();
for (auto entity : view) {
// Process thousands of entities per frame
}

Common Patterns

Registry Queries

auto& registry = Ecs::RegistryManager::get();
// Single component
if (registry.any_of<Ecs::TransformDirty>(entity)) {
auto& transform = registry.get<Ecs::WorldTransform>(entity);
}
// Multiple components
auto view = registry.view<Ecs::StaticMeshData, Ecs::Parent>();
for (auto entity : view) {
auto [mesh, parent] = view.get(entity);
}
// Exclude components
auto view = registry.view<Ecs::Tick>(entt::exclude<Ecs::SimulatesPhysics>);

Dirty Flags

Transform changes use two-stage dirty tracking:

Stage 1: Scene graph sync

registry.emplace<Ecs::TransformDirty>(entity);
// Engine propagates hierarchy
// Clears TransformDirty after sync

Stage 2: GPU upload

registry.emplace<Ecs::TransformDirtyRenderer>(entity);
// RenderingDataManager uploads to GPU
// Clears TransformDirtyRenderer after upload
Tag for transforms that need GPU upload. Separate from TransformDirty because the renderer clears thi...
Definition EcsData.h:123

Separation prevents race conditions when transforms change during tick.

Asset Loading

Asset pipeline uses staged components:

// 1. Request asset load
registry.emplace<Ecs::AssetRequested>(entity);
// 2. Pipeline loads async, replaces with data
auto& meshData = registry.emplace<Ecs::MeshPrimitiveData>(entity);
registry.remove<Ecs::AssetRequested>(entity);
// 3. Renderer queries loaded data
auto view = registry.view<Ecs::MeshPrimitiveData>(entt::exclude<Ecs::AssetRequested>);
Is a tag for asset loading. When an asset has been rquested for loading but isnt yet created this tag...
Definition EcsData.h:53

Example: Tick System

// Game code: Actor ticking (wrapper)
void ExampleScene::tick(double dt) {
rat->rotateActor(glm::vec3(10.0f * dt, 90.0f * dt, 0.0f));
}
// Behind the scenes: ECS query
auto& registry = Ecs::RegistryManager::get();
auto tickView = registry.view<Ecs::Tick>();
for (auto entity : tickView) {
auto& tickComponent = tickView.get<Ecs::Tick>(entity);
tickComponent.tickable->tick(deltaTime);
}
virtual void tick(double deltaTimeSeconds)
Function called each frame when a tick component has been registered with EnTT Ecs::Tick.
Definition Tick.h:32

Key Files

File Purpose
Engine/include/Engine/Ecs/EcsData.h All ECS component definitions
Engine/include/Engine/Ecs/RegistryManager.h Registry singleton
Engine/include/Engine/Entity/Entity.h Base wrapper class
Engine/include/Engine/Entity/Actor.h Transform wrapper
Engine/src/Engine/Ecs/FrameProcessing.cpp Asset loading pipeline
Engine/src/Engine/Renderer/RenderingDataManager.cpp Rendering queries

Performance Notes

EnTT provides O(1) component access and cache-friendly iteration. Use wrappers for game logic, ECS for hot loops.

Wrapper overhead: Acceptable for tick/input (runs once per entity per frame). ECS direct access: Required for rendering (processes thousands of primitives per frame).

Transform hierarchy sync runs once per frame before rendering. Dirty flags minimize work.