16
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-07-18 00:49:19 +02:00

ECS System WIP

This commit is contained in:
2026-04-07 23:40:03 +02:00
parent f6db9978d2
commit 8e9fd9225f
30 changed files with 1133 additions and 242 deletions

1
.gitignore vendored
View File

@@ -10,6 +10,7 @@ bin/
#Visual Studio [Code] folders
.vscode/
.vs/
.cache/
# JetBrains CLion folders
cmake-build-*/

View File

@@ -181,6 +181,7 @@ set(SOURCES
"scripting/pythonscreenviewer.cpp"
"utilities/dictionary.cpp"
"rendering/particles.cpp"
"registry/NameRegistry.cpp"
"utilities/headtrack.cpp"
"scripting/ladderlogic.cpp"
"rendering/geometrybank.cpp"
@@ -236,8 +237,6 @@ set(SOURCES
"vr/vr_interface.cpp"
entitysystem/components/BasicComponents.h
entitysystem/ecs.cpp
entitysystem/ecs.h
entitysystem/components/RenderComponents.h
)
@@ -397,7 +396,21 @@ configure_file(${CMAKE_SOURCE_DIR}/eu07.rc.in
if (${CMAKE_CXX_COMPILER_ID} STREQUAL MSVC)
set(SOURCES ${SOURCES} ${CMAKE_BINARY_DIR}/eu07.rc)
set(SOURCES ${SOURCES} eu07.ico)
set(SOURCES ${SOURCES} eu07.ico
entitysystem/ECScene.cpp
entitysystem/ECScene.h
entitysystem/ECWorld.cpp
entitysystem/ECWorld.h
entitysystem/scenes/GameScene.cpp
entitysystem/scenes/GameScene.h
entitysystem/SceneManager.cpp
entitysystem/SceneManager.h
entitysystem/systems/SystemManager.cpp
entitysystem/systems/SystemManager.h
entitysystem/systems/BaseSystem.cpp
entitysystem/systems/BaseSystem.h
entitysystem/systems/MovementSystem.cpp
entitysystem/systems/MovementSystem.h)
add_compile_options("/utf-8")
endif()

View File

@@ -37,6 +37,9 @@ http://mozilla.org/MPL/2.0/.
#include <discord_rpc.h>
#endif
#include "entitysystem/SceneManager.h"
#include "entitysystem/scenes/GameScene.h"
#include <chrono>
#include "utilities/translation.h"
@@ -452,6 +455,8 @@ bool eu07_application::is_client() const
int eu07_application::run()
{
auto frame{0};
sceneManager.SetScene(std::make_unique<GameScene>());
// main application loop
while (!glfwWindowShouldClose(m_windows.front()) && !m_modestack.empty())
{
@@ -474,6 +479,9 @@ int eu07_application::run()
double frameStartTime = Timer::GetTime();
float dt = (float) Timer::GetDeltaTime();
sceneManager.Update(dt);
if (m_modes[m_modestack.top()]->is_command_processor())
{
// active mode is doing real calculations (e.g. drivermode)

View File

@@ -10,6 +10,7 @@ http://mozilla.org/MPL/2.0/.
#pragma once
#include "application/applicationmode.h"
#include "entitysystem/SceneManager.h"
#include "scripting/PyInt.h"
#include "network/manager.h"
#include "utilities/headtrack.h"
@@ -38,6 +39,7 @@ public:
run();
// issues request for a worker thread to perform specified task. returns: true if task was scheduled
SceneManager sceneManager;
#ifdef WITH_DISCORD_RPC
void DiscordRPCService(); // discord rich presence service function (runs as separate thread)
#endif

View File

@@ -19,6 +19,9 @@ http://mozilla.org/MPL/2.0/.
#include "utilities/translation.h"
#include "application/application.h"
#include "application/editormode.h"
#include "entitysystem/ECScene.h"
#include "entitysystem/components/BasicComponents.h"
#include "entitysystem/components/RenderComponents.h"
#include "imgui/imgui_impl_glfw.h"
@@ -383,6 +386,10 @@ void ui_layer::render()
render_menu();
render_quit_widget();
render_hierarchy();
if (auto* scene = Application.sceneManager.CurrentScene())
{
render_entity_hierarchy(scene->World());
}
// template method implementation
render_();
@@ -470,7 +477,171 @@ void ui_layer::render_hierarchy(){
ImGui::End();
}
void ui_layer::render_entity_hierarchy(ECWorld& world)
{
ImGui::SetNextWindowSize(ImVec2(900, 500), ImGuiCond_FirstUseEver);
if (!ImGui::Begin(STR_C("Entity Hierarchy"), &m_entity_hierarchy))
{
ImGui::End();
return;
}
const float left_panel_width = 280.0f;
ImGui::BeginChild("entity_list_panel", ImVec2(left_panel_width, 0), true);
ImGui::Text("Registered entities: %zu", world.GetEntityCount());
ImGui::Separator();
for (auto entity : world.GetEntities())
{
char buf[64];
std::snprintf(buf, sizeof(buf), "Entity %u", static_cast<unsigned>(entity));
const bool selected = (m_selected_entity == entity);
if (ImGui::Selectable(buf, selected))
{
m_selected_entity = entity;
}
if (ImGui::IsItemHovered())
{
ImGui::SetTooltip("Entity ID: %u", static_cast<unsigned>(entity));
}
}
ImGui::EndChild();
ImGui::SameLine();
ImGui::BeginChild("entity_inspector_panel", ImVec2(0, 0), true);
if (m_selected_entity == entt::null)
{
ImGui::TextDisabled("No entity selected");
ImGui::EndChild();
ImGui::End();
return;
}
if (!world.IsAlive(m_selected_entity))
{
ImGui::TextDisabled("Selected entity is no longer valid");
ImGui::EndChild();
ImGui::End();
return;
}
ImGui::Text("Selected entity: %u", static_cast<unsigned>(m_selected_entity));
ImGui::Separator();
// Identification
if (auto* id = world.GetComponent<ECSComponent::Identification>(m_selected_entity))
{
if (ImGui::CollapsingHeader("Identification", ImGuiTreeNodeFlags_DefaultOpen))
{
char nameBuffer[256];
std::snprintf(nameBuffer, sizeof(nameBuffer), "%s", id->Name.ToString().c_str());
if (ImGui::InputText("Name", nameBuffer, sizeof(nameBuffer)))
{
id->Name = nameBuffer;
}
}
}
// Transform
if (auto* transform = world.GetComponent<ECSComponent::Transform>(m_selected_entity))
{
if (ImGui::CollapsingHeader("Transform", ImGuiTreeNodeFlags_DefaultOpen))
{
float pos[3] = { static_cast<float>(transform->Position.x), static_cast<float>(transform->Position.y), static_cast<float>(transform->Position.z) };
if(ImGui::DragFloat3("Position", pos, 0.1f)){
}
ImGui::DragFloat3("Rotation",
&transform->Rotation.x, 0.1, -360.0, 360.0);
ImGui::DragFloat3("Scale",
&transform->Scale.x, 0.01, 0.0, 100000.0);
}
}
// Velocity
if (auto* velocity = world.GetComponent<ECSComponent::Velocity>(m_selected_entity))
{
if (ImGui::CollapsingHeader("Velocity", ImGuiTreeNodeFlags_DefaultOpen))
{
ImGui::DragFloat3("Value",
&velocity->Value.x, 0.1);
if (ImGui::Button("Stop"))
{
velocity->Value = glm::dvec3(0.0);
}
}
}
// LODController
if (auto* lod = world.GetComponent<ECSComponent::LODController>(m_selected_entity))
{
if (ImGui::CollapsingHeader("LODController", ImGuiTreeNodeFlags_DefaultOpen))
{
float range_min = lod->RangeMin;
float range_max = lod->RangeMax;
ImGui::DragFloat("Range Min", &range_min, 0.1, 0.0, 100000.0);
ImGui::DragFloat("Range Max", &range_max, 0.1, 0.0, 100000.0);
}
}
// Sound
if(auto* sound = world.GetComponent<ECSComponent::SoundComponent>(m_selected_entity)){
if(ImGui::CollapsingHeader("Sound", ImGuiTreeNodeFlags_DefaultOpen)){
ImGui::Text("Sound file: %s", sound->sound.name().c_str());
ImGui::DragFloat("Volume", &sound->volume, 0.01f, 0.0f, 1.0f);
ImGui::DragFloat("Pitch", &sound->pitch, 0.01f, 0.0f, 2.0f);
ImGui::Checkbox("Looping", &sound->loop);
ImGui::Checkbox("Playing", &sound->isPlaying);
ImGui::Checkbox("Play On Start", &sound->playOnStart);
}
}
// MeshRenderer
if(auto* mesh_renderer = world.GetComponent<ECSComponent::MeshRenderer>(m_selected_entity))
{
if (ImGui::CollapsingHeader("MeshRenderer", ImGuiTreeNodeFlags_DefaultOpen))
{
//ImGui::Text("Mesh: %s", mesh_renderer->meshHandle.ToString().c_str());
ImGui::Checkbox("Visible", &mesh_renderer->visible);
}
}
// SpotLight
if (auto* light = world.GetComponent<ECSComponent::SpotLight>(m_selected_entity))
{
if (ImGui::CollapsingHeader("SpotLight", ImGuiTreeNodeFlags_DefaultOpen))
{
ImGui::Checkbox("Enabled", &light->Enabled);
ImGui::ColorEdit3("Color", &light->Color.x);
ImGui::DragFloat("Intensity", &light->Intensity, 0.1f, 0.0f, 100.0f);
ImGui::DragFloat("Range", &light->Range, 0.5f, 0.0f, 500.0f);
ImGui::DragFloat("Inner Angle", &light->InnerAngle, 0.1f, 0.0f, light->OuterAngle);
ImGui::DragFloat("Outer Angle", &light->OuterAngle, 0.1f, light->InnerAngle, 90.0f);
ImGui::Checkbox("Cast Shadows", &light->CastShadows);
}
}
ImGui::EndChild();
ImGui::End();
}
void ui_layer::set_cursor(int const Mode)
{
glfwSetInputMode(m_window, GLFW_CURSOR, Mode);

View File

@@ -12,6 +12,7 @@ http://mozilla.org/MPL/2.0/.
#include <string>
#include "model/Texture.h"
#include "widgets/popup.h"
#include "entitysystem/ECWorld.h"
// GuiLayer -- basic user interface class. draws requested information on top of openGL screen
@@ -148,6 +149,7 @@ protected:
void render_menu();
void render_quit_widget();
void render_hierarchy();
void render_entity_hierarchy(ECWorld& world);
// draws a quad between coordinates x,y and z,w with uv-coordinates spanning 0-1
void quad( glm::vec4 const &Coordinates, glm::vec4 const &Color );
// members
@@ -161,4 +163,6 @@ protected:
bool m_imgui_demo = false;
bool m_editor_hierarchy = false;
bool m_editor_change_history = false;
entt::entity m_selected_entity = entt::null;
bool m_entity_hierarchy = true;
};

69
entitysystem/ECScene.cpp Normal file
View File

@@ -0,0 +1,69 @@
//
// Created by Daniu
//
#include "ECScene.h"
#include "utilities/Logs.h"
ECScene::ECScene()
: m_loaded(false)
{
}
ECScene::~ECScene()
{
Unload();
}
void ECScene::Load()
{
if (m_loaded)
return;
m_systems.Create(m_world);
OnLoad();
m_loaded = true;
}
void ECScene::Unload()
{
if (!m_loaded)
return;
OnUnload();
m_world.Clear();
m_loaded = false;
}
void ECScene::Update(float dt)
{
if (!m_loaded){
return;
}
m_systems.Update(m_world, dt);
OnUpdate(dt);
}
ECWorld& ECScene::World()
{
return m_world;
}
const ECWorld& ECScene::World() const
{
return m_world;
}
void ECScene::OnLoad()
{
}
void ECScene::OnUnload()
{;
}
void ECScene::OnUpdate(float dt)
{
(void)dt;
}

42
entitysystem/ECScene.h Normal file
View File

@@ -0,0 +1,42 @@
//
// Created by Daniu
//
#ifndef EU07_ECSCENE_H
#define EU07_ECSCENE_H
#pragma once
#include "ECWorld.h"
#include "systems/SystemManager.h"
class ECScene
{
public:
ECScene();
virtual ~ECScene();
void Load();
void Unload();
void Update(float dt);
ECWorld& World();
const ECWorld& World() const;
protected:
virtual void OnLoad();
virtual void OnUnload();
virtual void OnUpdate(float dt);
protected:
ECWorld m_world;
SystemManager m_systems;
private:
bool m_loaded;
};
#endif //EU07_ECSCENE_H

54
entitysystem/ECWorld.cpp Normal file
View File

@@ -0,0 +1,54 @@
//
// Created by Daniu
//
#include "ECWorld.h"
#include "entitysystem/components/BasicComponents.h"
entt::entity ECWorld::CreateEntity()
{
return m_registry.create();
}
void ECWorld::DestroyEntity(entt::entity entity)
{
if (m_registry.valid(entity))
m_registry.destroy(entity);
}
void ECWorld::Clear()
{
m_registry.clear();
}
bool ECWorld::IsAlive(entt::entity entity) const
{
return m_registry.valid(entity);
}
entt::entity ECWorld::FindEntityByName(const std::string& name) const
{
auto view = m_registry.view<ECSComponent::Identification>();
for (auto entity : view)
{
const auto& id = view.get<ECSComponent::Identification>(entity);
if (id.Name.ToString() == name)
{
return entity;
}
}
return entt::null;
}
entt::registry& ECWorld::Registry()
{
return m_registry;
}
const entt::registry& ECWorld::Registry() const
{
return m_registry;
}

135
entitysystem/ECWorld.h Normal file
View File

@@ -0,0 +1,135 @@
//
// Created by Daniu
//
#ifndef EU07_ECWORLD_H
#define EU07_ECWORLD_H
#pragma once
#include <entt/entt.hpp>
#include <utility>
#include <type_traits>
class ECWorld
{
public:
ECWorld() = default;
~ECWorld() = default;
entt::entity CreateEntity();
void DestroyEntity(entt::entity entity);
void Clear();
bool IsAlive(entt::entity entity) const;
entt::registry& Registry();
const entt::registry& Registry() const;
template<typename T, typename... Args>
T& AddComponent(entt::entity entity, Args&&... args);
template<typename T>
bool HasComponent(entt::entity entity) const;
template<typename T>
T* GetComponent(entt::entity entity);
template<typename T>
const T* GetComponent(entt::entity entity) const;
template<typename T>
void RemoveComponent(entt::entity entity);
entt::entity FindEntityByName(const std::string& name) const;
template<typename... Components>
auto View()
{
return m_registry.view<Components...>();
}
template<typename... Components, typename Func>
void Each(Func&& func)
{
auto view = m_registry.view<Components...>();
for (auto entity : view)
{
auto components = std::forward_as_tuple(view.get<Components>(entity)...);
std::apply([&](Components&... comps)
{
func(entity, comps...);
}, components);
}
}
std::vector<entt::entity> GetEntities() const
{
std::vector<entt::entity> entities;
auto *storage = m_registry.storage<entt::entity>();
for (auto entity : *storage)
{
entities.push_back(entity);
}
return entities;
}
std::size_t GetEntityCount() const
{
return m_registry.storage<entt::entity>()->size();
}
private:
entt::registry m_registry;
};
template<typename T, typename... Args>
T& ECWorld::AddComponent(entt::entity entity, Args&&... args)
{
static_assert(std::is_constructible_v<T, Args...>,
"Component must be constructible with provided arguments");
if (m_registry.all_of<T>(entity))
{
return m_registry.replace<T>(entity, std::forward<Args>(args)...);
}
return m_registry.emplace<T>(entity, std::forward<Args>(args)...);
}
template<typename T>
bool ECWorld::HasComponent(entt::entity entity) const
{
return m_registry.all_of<T>(entity);
}
template<typename T>
T* ECWorld::GetComponent(entt::entity entity)
{
return m_registry.try_get<T>(entity);
}
template<typename T>
const T* ECWorld::GetComponent(entt::entity entity) const
{
return m_registry.try_get<T>(entity);
}
template<typename T>
void ECWorld::RemoveComponent(entt::entity entity)
{
if (m_registry.all_of<T>(entity))
m_registry.remove<T>(entity);
}
extern ECWorld& GetComponentSystem();
#define ECS GetComponentSystem()
#endif //EU07_ECWORLD_H

View File

@@ -0,0 +1,59 @@
//
// Created by Daniu
//
#include "SceneManager.h"
#include "ECScene.h"
SceneManager::SceneManager() = default;
SceneManager::~SceneManager()
{
if (m_currentScene)
m_currentScene->Unload();
}
void SceneManager::SetScene(std::unique_ptr<ECScene> scene)
{
m_pendingScene = std::move(scene);
if (m_currentScene)
m_currentScene->Load();
}
void SceneManager::Update(float dt)
{
if (m_pendingScene)
SwitchScene();
if (m_currentScene){
m_currentScene->Update(dt);
}
}
void SceneManager::SwitchScene()
{
if (m_currentScene)
m_currentScene->Unload();
m_currentScene = std::move(m_pendingScene);
if (m_currentScene)
m_currentScene->Load();
}
ECScene* SceneManager::CurrentScene()
{
return m_currentScene.get();
}
const ECScene* SceneManager::CurrentScene() const
{
return m_currentScene.get();
}
bool SceneManager::HasScene() const
{
return m_currentScene != nullptr;
}

View File

@@ -0,0 +1,40 @@
//
// Created by Daniu
//
#ifndef EU07_SCENEMANAGER_H
#define EU07_SCENEMANAGER_H
#pragma once
#include <memory>
#include <string>
class ECScene;
class SceneManager
{
public:
SceneManager();
~SceneManager();
void SetScene(std::unique_ptr<ECScene> scene);
void Update(float dt);
ECScene* CurrentScene();
const ECScene* CurrentScene() const;
bool HasScene() const;
private:
void SwitchScene();
private:
std::unique_ptr<ECScene> m_currentScene;
std::unique_ptr<ECScene> m_pendingScene;
};
#endif //EU07_SCENEMANAGER_H

View File

@@ -3,11 +3,14 @@
*/
#ifndef EU07_BASICCOMPONENTS_H
#define EU07_BASICCOMPONENTS_H
#include "audio/sound.h"
#include "entt/entity/entity.hpp"
#include "glm/vec3.hpp"
#include "glm/gtc/quaternion.hpp"
#include <string>
#include "registry/FName.h"
#include "utils/uuid.hpp"
#include <cstdint>
#include <string>
namespace ECSComponent
{
@@ -16,18 +19,23 @@ namespace ECSComponent
///</summary>
struct Transform
{
glm::vec3 Position{0.f}; // object position
glm::dvec3 Position{0.f}; // object position
glm::quat Rotation{1.f, 0.f, 0.f, 0.f}; // object rotation
glm::vec3 Scale{1.f}; // object scale
};
struct Velocity
{
glm::vec3 Value{0.f}; // object velocity
};
///< summary>
/// Basic component for naming entities
/// in future for scenery hierarchy
///</summary>
struct Identification
{
std::string Name; // object name - may contain slashes for editor hierarchy "directories"
FName Name; // object name - may contain slashes for editor hierarchy "directories"
std::uint64_t Id{0}; // id in scene
};
@@ -39,6 +47,16 @@ struct Parent
entt::entity value{entt::null};
};
struct SoundComponent
{
sound_source sound; // sound source
float volume = 1.0f; // 01
float pitch = 1.0f; // speed / pitch
float range = 1.0f; // range
bool loop = false;
bool playOnStart = false;
bool isPlaying = false;
};
///< summary>
/// Empty component for entities that are disabled and should not be processed by systems
///</summary>

View File

@@ -1,6 +1,9 @@
#ifndef EU07_RENDERCOMPONENTS_H
#define EU07_RENDERCOMPONENTS_H
#include "registry/FName.h"
#include "rendering/geometrybank.h"
namespace ECSComponent
{
/// <summary>
@@ -11,6 +14,8 @@ namespace ECSComponent
/// </remarks>
struct MeshRenderer
{
gfx::geometry_handle meshHandle;
bool visible = true;
};
/// <summary>
@@ -19,7 +24,53 @@ struct MeshRenderer
/// <remarks>Currently empty
/// TODO: Add component members
/// </remarks>
struct SpotLight{};
struct SpotLight
{
glm::vec3 Color{ 1.0f, 1.0f, 1.0f };
float Intensity = 1.0f;
float Range = 25.0f;
float InnerAngle = 15.0f;
float OuterAngle = 25.0f;
bool CastShadows = false;
bool Enabled = true;
};
// TODO: AreaLight like blender
// TODO: SunLight for scene
struct ParticleEmitter
{
FName particleEffectPath = ("");
bool active = true;
};
struct Decal
{
FName decalTexturePath = ("");
float size = 1.0f;
bool active = true;
};
struct Billboard
{
FName texturePath = ("");
float size = 1.0f;
bool active = true;
};
struct Line {
glm::vec3 start{ 0.0f };
glm::vec3 end{ 1.0f, 0.0f, 0.0f };
glm::vec3 color{ 1.0f, 1.0f, 1.0f };
float thickness = 1.0f;
bool active = true;
};
/// <summary>
/// Component for entities that can be rendered with LOD

View File

@@ -1,69 +0,0 @@
//
// Created by Hirek on 3/14/2026.
//
#include "ecs.h"
void ECS::ClearWorld()
{
world_.clear();
}
entt::entity ECS::CreateObject()
{
const auto e = world_.create();
world_.emplace<ECSComponent::Transform>(e);
// add UID
auto id = world_.emplace<ECSComponent::Identification>(e);
id.Id = nextId_++;
return e;
}
void ECS::DestroyObject(entt::entity Entity)
{
if (Entity == entt::null) // check if Entity is not null
return;
if (!world_.valid(Entity)) // check if exist
return;
world_.destroy(Entity);
}
bool ECS::ValidObject(entt::entity entity) const
{
return entity != entt::null && world_.valid(entity);
}
entt::entity ECS::FindById(std::uint64_t id)
{
auto view = world_.view<const ECSComponent::Identification>();
for (auto e : view)
{
const auto &ident = view.get<ECSComponent::Identification>(e);
if (ident.Id == id)
return e;
}
return entt::null;
}
std::vector<entt::entity> ECS::FindByName(std::string_view name)
{
std::vector<entt::entity> result;
auto view = world_.view<const ECSComponent::Identification>();
for (auto e : view)
{
const auto &ident = view.get<ECSComponent::Identification>(e);
if (ident.Name == name)
{
result.push_back(e);
}
}
return result;
}
ECS &GetComponentSystem()
{
static ECS _ecs;
return _ecs;
}

View File

@@ -1,153 +0,0 @@
//
// Created by Hirek on 3/14/2026.
//
#ifndef EU07_ECS_H
#define EU07_ECS_H
#include "components/BasicComponents.h"
#include "entt/entity/registry.hpp"
#include <cstdint>
#include <vector>
#include <string_view>
#include <utility>
class ECS final
{
private:
entt::registry world_; // scene registry
std::uint64_t nextId_ = 1; // 0 is invalid/none
public:
//
// World lifecycle
//
/// <summary>
/// Clears the world, removing all entities and components
/// </summary>
void ClearWorld();
//
// Objects
//
/// <summary>
/// Creates new object with basic components (Transform, Identification) and returns its entity handle.
/// </summary>
/// <returns>Entity handle of created object</returns>
entt::entity CreateObject();
/// <summary>
/// Destroys object with it's all components
/// </summary>
/// <param name="Entity">Entity to be removed</param>
void DestroyObject(entt::entity Entity);
/// <summary>
/// Checks if object with given entity handle exists in the registry
/// </summary>
/// <param name="entity">Entity handle to check</param>
/// <returns>true if object exists, false otherwise</returns>
bool ValidObject(entt::entity entity) const;
//
// Identification lookups
//
/// <summary>
/// Finds object with given UID. Returns null entity if not found.
/// </summary>
/// <param name="id">UID of object</param>
entt::entity FindById(std::uint64_t id);
/// <summary>
/// Finds objects with given name. Returns empty vector if not found.
/// </summary>
/// <param name="name">Name of object</param>
/// <remarks>May return more than 1 as many objects can have the same name</remarks>
std::vector<entt::entity> FindByName(std::string_view name);
//
// Components
//
/// <summary>
/// Adds component of type T to entity, forwarding provided arguments to component constructor. If component already exists, it will be replaced.
/// </summary>
/// <typeparam name="T">Component type</typeparam>
/// <param name="entity">Entity to which component will be added</param>
/// <param name="args">Arguments forwarded to component constructor</param>
/// <returns>Reference to added component</returns>
template <class T, class... Args> T &AddComponent(entt::entity entity, Args &&...args)
{
return world_.emplace<T>(entity, std::forward<Args>(args)...);
}
/// <summary>
/// Returns entity's component
/// </summary>
/// <typeparam name="T"> type</typeparam>
/// <param name="entity">Entity to which component will be added</param>
/// <returns>Reference to added component</returns>
template <class T> T &GetComponent(entt::entity entity)
{
return world_.get<T>(entity);
}
/// <summary>
/// Returns entity's component
/// </summary>
/// <typeparam name="T"> type</typeparam>
/// <param name="entity">Entity to which component will be added</param>
/// <returns>Reference to added component</returns>
template <class T> const T &GetComponent(entt::entity entity) const
{
return world_.get<T>(entity);
}
/// <summary>
/// Tries to get component of type T from entity. Returns nullptr if component does not exist.
/// </summary>
/// <typeparam name="T">Component type</typeparam>
/// <param name="entity">Entity from which component will be retrieved</param>
/// <returns>Pointer to component if exists, nullptr otherwise</returns>
template <class T> T *TryGetComponent(entt::entity entity)
{
return world_.try_get<T>(entity);
}
/// <summary>
/// Tries to get component of type T from entity. Returns nullptr if component does not exist.
/// </summary>
/// <typeparam name="T">Component type</typeparam>
/// <param name="entity">Entity from which component will be retrieved</param>
/// <returns>Pointer to component if exists, nullptr otherwise</returns>
template <class T> const T *TryGetComponent(entt::entity entity) const
{
return world_.try_get<T>(entity);
}
/// <summary>
/// Checks if entity has component of type T.
/// </summary>
/// <typeparam name="T">Component type</typeparam>
/// <param name="entity">Entity to check</param>
/// <returns>true if entity has component, false otherwise</returns>
template <class... T> bool HasComponent(entt::entity entity) const
{
return world_.all_of<T...>(entity);
}
/// <summary>
/// Removes component of type T from entity. Does nothing if component does not exist.
/// </summary> <typeparam name="T">Component type</typeparam>
/// <param name="entity">Entity from which component will be removed</param>
template <class... T> void RemoveComponent(entt::entity entity)
{
world_.remove<T...>(entity);
}
};
extern ECS& GetComponentSystem();
#define CS GetComponentSystem()
#endif // EU07_ECS_H

View File

@@ -0,0 +1,35 @@
//
// Created by Daniu
//
#include "GameScene.h"
#include "entitysystem/components/BasicComponents.h"
#include "entitysystem/components/RenderComponents.h"
#include "entitysystem/systems/MovementSystem.h"
#include "utilities/Logs.h"
GameScene::GameScene() = default;
GameScene::~GameScene() = default ;
void GameScene::OnLoad()
{
m_systems.AddSystem<MovementSystem>();
}
void GameScene::OnUpdate(float dt)
{
//auto& world = World();
//auto view = world.View<ECSComponent::Transform>();
}
void GameScene::OnUnload()
{
World().Clear();
}

View File

@@ -0,0 +1,30 @@
//
// Created by Daniu
//
#ifndef EU07_GAMESCENE_H
#define EU07_GAMESCENE_H
#pragma once
#include "entitysystem/ECScene.h"
class GameScene final : public ECScene
{
public:
GameScene();
~GameScene() override;
private:
void OnLoad() override;
void OnUnload() override;
void OnUpdate(float dt) override;
private:
void CreateTestEntities();
};
#endif //EU07_GAMESCENE_H

View File

@@ -0,0 +1,5 @@
//
// Created by Daniu
//
#include "BaseSystem.h"

View File

@@ -0,0 +1,25 @@
//
// Created by Daniu
//
#ifndef EU07_BASICSYSTEM_H
#define EU07_BASICSYSTEM_H
#pragma once
class ECWorld;
class BaseSystem
{
public:
virtual ~BaseSystem() = default;
virtual void OnCreate(ECWorld& world) {}
virtual void OnDestroy(ECWorld& world) {}
virtual void Update(ECWorld& world, float dt) {}
};
#endif //EU07_BASICSYSTEM_H

View File

@@ -0,0 +1,29 @@
//
// Created by Daniu
//
#include "MovementSystem.h"
#include "entitysystem/ECWorld.h"
#include "entitysystem/components/BasicComponents.h"
#include "utilities/Logs.h"
void MovementSystem::Update(ECWorld& world, float dt)
{
constexpr double damping = 0.90; // 0.0 = instant stop, 1.0 = without damping
world.Each<ECSComponent::Transform, ECSComponent::Velocity>(
[dt](auto entity, auto& transform, auto& velocity)
{
transform.Position += velocity.Value * dt;
velocity.Value *= damping;
if (glm::length(velocity.Value) < 0.001)
{
velocity.Value = glm::dvec3(0.0);
}
}
);
}

View File

@@ -0,0 +1,25 @@
//
// Created by Daniu
//
#ifndef EU07_MOVEMENTSYSTEM_H
#define EU07_MOVEMENTSYSTEM_H
#pragma once
#include "BaseSystem.h"
namespace ECSComponent
{
struct Transform;
struct Velocity;
}
class MovementSystem : public BaseSystem
{
public:
void Update(ECWorld& world, float dt) override;
};
#endif //EU07_MOVEMENTSYSTEM_H

View File

@@ -0,0 +1,33 @@
//
// Created by Daniu
//
#include "SystemManager.h"
#include "entitysystem/systems/BaseSystem.h"
#include "entitysystem/ECWorld.h"
#include "utilities/Logs.h"
SystemManager::~SystemManager() = default;
void SystemManager::Create(ECWorld& world)
{
WriteLog("[SystemManager] Creating systems");
for (auto& system : m_systems){
system->OnCreate(world);
}
}
void SystemManager::Destroy(ECWorld& world)
{
WriteLog("[SystemManager] Destroying systems");
for (auto& system : m_systems)
system->OnDestroy(world);
}
void SystemManager::Update(ECWorld& world, float dt)
{
for (auto& system : m_systems)
system->Update(world, dt);
}

View File

@@ -0,0 +1,46 @@
//
// Created by Daniu
//
#ifndef EU07_SYSTEMMANAGER_H
#define EU07_SYSTEMMANAGER_H
#pragma once
#include "BaseSystem.h"
#include <vector>
#include <memory>
class ECWorld;
class SystemManager
{
public:
SystemManager() = default;
~SystemManager();
template<typename T, typename... Args>
T& AddSystem(Args&&... args)
{
static_assert(std::is_base_of_v<BaseSystem, T>);
auto system = std::make_unique<T>(std::forward<Args>(args)...);
T& ref = *system;
m_systems.emplace_back(std::move(system));
return ref;
}
void Create(ECWorld& world);
void Destroy(ECWorld& world);
void Update(ECWorld& world, float dt);
private:
std::vector<std::unique_ptr<BaseSystem>> m_systems;
};
#endif //EU07_SYSTEMMANAGER_H

63
registry/FName.h Normal file
View File

@@ -0,0 +1,63 @@
#pragma once
#include <cstdint>
#include <string>
#include <string_view>
#include "NameRegistry.h"
struct FName
{
using value_type = uint64_t;
static constexpr value_type INVALID_ID = 0;
value_type id = INVALID_ID;
constexpr FName() noexcept = default;
constexpr explicit FName(value_type value) noexcept
: id(value)
{
}
FName(const char* text)
: id(text ? NameRegistry::GetOrCreate(text) : INVALID_ID)
{
}
FName(const std::string& text)
: id(text.empty() ? INVALID_ID : NameRegistry::GetOrCreate(text))
{
}
FName(std::string_view text)
: id(text.empty() ? INVALID_ID : NameRegistry::GetOrCreate(text))
{
}
constexpr bool IsValid() const noexcept
{
return id != INVALID_ID;
}
constexpr value_type GetId() const noexcept
{
return id;
}
const std::string& ToString() const
{
return NameRegistry::GetString(id);
}
constexpr bool operator==(const FName& other) const noexcept
{
return id == other.id;
}
constexpr bool operator!=(const FName& other) const noexcept
{
return id != other.id;
}
};

41
registry/NameHash.h Normal file
View File

@@ -0,0 +1,41 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <string_view>
constexpr uint64_t FNV1A_OFFSET_BASIS_64 = 14695981039346656037ull;
constexpr uint64_t FNV1A_PRIME_64 = 1099511628211ull;
constexpr uint64_t HashLiteral(const char* str, std::size_t len) noexcept
{
uint64_t hash = FNV1A_OFFSET_BASIS_64;
for (std::size_t i = 0; i < len; ++i)
{
hash ^= static_cast<unsigned char>(str[i]);
hash *= FNV1A_PRIME_64;
}
return hash;
}
constexpr uint64_t HashLiteral(const char* str) noexcept
{
uint64_t hash = FNV1A_OFFSET_BASIS_64;
while (*str)
{
hash ^= static_cast<unsigned char>(*str++);
hash *= FNV1A_PRIME_64;
}
return hash;
}
inline uint64_t HashRuntime(std::string_view str) noexcept
{
uint64_t hash = FNV1A_OFFSET_BASIS_64;
for (char c : str)
{
hash ^= static_cast<unsigned char>(c);
hash *= FNV1A_PRIME_64;
}
return hash;
}

11
registry/NameLiteral.h Normal file
View File

@@ -0,0 +1,11 @@
#pragma once
#include <cstddef>
#include "FName.h"
#include "NameHash.h"
consteval FName operator"" _name(const char* str, std::size_t len)
{
return FName{ HashLiteral(str, len) };
}

52
registry/NameRegistry.cpp Normal file
View File

@@ -0,0 +1,52 @@
#include "NameRegistry.h"
#include "NameHash.h"
#include <cassert>
uint64_t NameRegistry::GetOrCreate(std::string_view name)
{
if (name.empty())
return 0;
const uint64_t hash = HashRuntime(name);
std::lock_guard<std::mutex> lock(GetMutex());
auto& map = GetMap();
auto [it, inserted] = map.emplace(hash, std::string(name));
if (!inserted)
{
assert(it->second == name && "Name hash collision detected");
}
return hash;
}
const std::string& NameRegistry::GetString(uint64_t id)
{
static const std::string Empty;
if (id == 0)
return Empty;
std::lock_guard<std::mutex> lock(GetMutex());
auto& map = GetMap();
auto it = map.find(id);
if (it != map.end())
return it->second;
return Empty;
}
std::unordered_map<uint64_t, std::string>& NameRegistry::GetMap()
{
static std::unordered_map<uint64_t, std::string> map;
return map;
}
std::mutex& NameRegistry::GetMutex()
{
static std::mutex mutex;
return mutex;
}

18
registry/NameRegistry.h Normal file
View File

@@ -0,0 +1,18 @@
#pragma once
#include <cstdint>
#include <mutex>
#include <string>
#include <string_view>
#include <unordered_map>
class NameRegistry
{
public:
static uint64_t GetOrCreate(std::string_view name);
static const std::string& GetString(uint64_t id);
private:
static std::unordered_map<uint64_t, std::string>& GetMap();
static std::mutex& GetMutex();
};

View File

@@ -25,7 +25,8 @@ http://mozilla.org/MPL/2.0/.
#include "rendering/lightarray.h"
#include "world/TractionPower.h"
#include "application/application.h"
#include "entitysystem/ecs.h"
#include "entitysystem/ECScene.h"
#include "entitysystem/components/BasicComponents.h"
#include "entitysystem/components/RenderComponents.h"
#include "rendering/renderer.h"
#include "utilities/Logs.h"
@@ -429,17 +430,18 @@ state_serializer::deserialize_node( cParser &Input, scene::scratch_data &Scratch
>> nodedata.name
>> nodedata.type;
// Add node to ComponentSystem
entt::entity CSEntity = CS.CreateObject();
auto nodeTransform = CS.GetComponent<ECSComponent::Transform>(CSEntity);
nodeTransform.Position = Scratchpad.location.offset.empty() ? glm::dvec3(0.0) : Scratchpad.location.offset.top();
nodeTransform.Rotation = Scratchpad.location.rotation;
ECWorld& world = Application.sceneManager.CurrentScene()->World();
entt::entity entity = world.CreateEntity();
auto transformComponent = world.AddComponent<ECSComponent::Transform>(entity);
transformComponent.Position = Scratchpad.location.offset.empty() ? glm::dvec3(0.0) : Scratchpad.location.offset.top();
transformComponent.Rotation = Scratchpad.location.rotation;
//nodeTransform.Scale = Scratchpad.location.scale;
auto lodController = CS.AddComponent<ECSComponent::LODController>(CSEntity);
lodController.RangeMax = nodedata.range_max;
lodController.RangeMin = nodedata.range_min;
// auto LODComponent = world.AddComponent<ECSComponent::LODController>(entity);
// LODComponent.RangeMax = nodedata.range_max;
// LODComponent.RangeMin = nodedata.range_min;
if( nodedata.name == "none" )
{
@@ -447,11 +449,12 @@ state_serializer::deserialize_node( cParser &Input, scene::scratch_data &Scratch
}
else
{
CS.GetComponent<ECSComponent::Identification>(CSEntity).Name = nodedata.name;
world.AddComponent<ECSComponent::Identification>(entity).Name = nodedata.name;
}
// type-based deserialization. not elegant but it'll do
if( nodedata.type == "dynamic" ) {
auto *vehicle { deserialize_dynamic( Input, Scratchpad, nodedata ) };
// vehicle import can potentially fail
if( vehicle == nullptr ) { return; }
@@ -471,6 +474,9 @@ state_serializer::deserialize_node( cParser &Input, scene::scratch_data &Scratch
ErrorLog( "Bad scenario: duplicate vehicle name \"" + vehicle->name() + "\" defined in file \"" + Input.Name() + "\" (line " + std::to_string( inputline ) + ")" );
}
auto velocityComponent = world.AddComponent<ECSComponent::Velocity>(entity);
velocityComponent.Value = glm::dvec3(0.0);
if( ( vehicle->MoverParameters->CategoryFlag == 1 ) // trains only
&& ( ( ( vehicle->LightList( end::front ) & ( light::headlight_left | light::headlight_right | light::headlight_upper ) ) != 0 )
|| ( ( vehicle->LightList( end::rear ) & ( light::headlight_left | light::headlight_right | light::headlight_upper ) ) != 0 ) ) ) {
@@ -572,7 +578,25 @@ state_serializer::deserialize_node( cParser &Input, scene::scratch_data &Scratch
}
scene::Groups.insert( scene::Groups.handle(), instance );
simulation::Region->insert( instance );
ECWorld& world = Application.sceneManager.CurrentScene()->World();
entt::entity entity = world.FindEntityByName(instance->name());
if(entity != entt::null) {
auto meshComponent = world.AddComponent<ECSComponent::MeshRenderer>(entity);
//meshComponent.meshHandle = instance->Model()->MeshHandle();
meshComponent.visible = true;
auto* transformComponent = world.GetComponent<ECSComponent::Transform>(entity);
transformComponent->Position = {instance->location().x, instance->location().y, instance->location().z};
transformComponent->Rotation = {instance->Angles().x, instance->Angles().y, instance->Angles().z, 1.0f};
//transformComponent->Scale = {instance->scale().x, instance->scale().y, instance->scale().z};
}
scene::basic_node *hierarchy_node = instance;
if (hierarchy_node)
{ scene::Hierarchy[hierarchy_node->uuid.to_string()] = hierarchy_node;
}
@@ -650,6 +674,15 @@ state_serializer::deserialize_node( cParser &Input, scene::scratch_data &Scratch
else if( nodedata.type == "sound" ) {
auto *sound { deserialize_sound( Input, Scratchpad, nodedata ) };
auto soundComponent = world.AddComponent<ECSComponent::SoundComponent>(entity);
soundComponent.sound = *sound;
soundComponent.volume = 1;
soundComponent.pitch = 1;
soundComponent.range = sound->range();
soundComponent.isPlaying = sound->is_playing();
if( false == simulation::Sounds.insert( sound ) ) {
ErrorLog( "Bad scenario: duplicate sound node name \"" + sound->name() + "\" defined in file \"" + Input.Name() + "\" (line " + std::to_string( inputline ) + ")" );
}