mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-07-22 00:09:18 +02:00
New Components/ Prepare to new scene loading system
This commit is contained in:
172
entitysystem/ECSPersistence.cpp
Normal file
172
entitysystem/ECSPersistence.cpp
Normal file
@@ -0,0 +1,172 @@
|
||||
#include "stdafx.h"
|
||||
#include "ECSPersistence.h"
|
||||
|
||||
#include "entitysystem/ECWorld.h"
|
||||
#include "entitysystem/components/BasicComponents.h"
|
||||
#include "entitysystem/components/RenderComponents.h"
|
||||
#include "utilities/Logs.h"
|
||||
#include "nlohmann/json.hpp"
|
||||
#include <fstream>
|
||||
|
||||
namespace ecs_persistence
|
||||
{
|
||||
|
||||
int save(ECWorld &world, const std::string &path)
|
||||
{
|
||||
nlohmann::json root = nlohmann::json::array();
|
||||
|
||||
for (auto entity : world.GetEntities()) {
|
||||
auto *idComp = world.GetComponent<ECSComponent::Identification>(entity);
|
||||
if (!idComp) continue;
|
||||
|
||||
nlohmann::json ent;
|
||||
ent["name"] = idComp->Name.ToString();
|
||||
|
||||
if (auto *t = world.GetComponent<ECSComponent::Transform>(entity)) {
|
||||
ent["Transform"] = {
|
||||
{"px", t->Position.x}, {"py", t->Position.y}, {"pz", t->Position.z},
|
||||
{"rx", t->Rotation.x}, {"ry", t->Rotation.y}, {"rz", t->Rotation.z}, {"rw", t->Rotation.w},
|
||||
{"sx", t->Scale.x}, {"sy", t->Scale.y}, {"sz", t->Scale.z}
|
||||
};
|
||||
}
|
||||
if (auto *s = world.GetComponent<ECSComponent::SpotLight>(entity)) {
|
||||
ent["SpotLight"] = {
|
||||
{"r", s->Color.x}, {"g", s->Color.y}, {"b", s->Color.z},
|
||||
{"intensity", s->Intensity}, {"range", s->Range},
|
||||
{"innerAngle", s->InnerAngle}, {"outerAngle", s->OuterAngle},
|
||||
{"enabled", s->Enabled}, {"castShadows", s->CastShadows}
|
||||
};
|
||||
}
|
||||
if (auto *p = world.GetComponent<ECSComponent::ParticleEmitter>(entity)) {
|
||||
ent["ParticleEmitter"] = {
|
||||
{"active", p->isActive}, {"spawnRate", p->spawnRate},
|
||||
{"lifetime", p->particleLifetime}, {"airResistance", p->airResistance},
|
||||
{"gx", p->gravity.x}, {"gy", p->gravity.y}, {"gz", p->gravity.z},
|
||||
{"fadex", p->colorFade.x}, {"fadey", p->colorFade.y},
|
||||
{"fadez", p->colorFade.z}, {"fadew", p->colorFade.w}
|
||||
};
|
||||
}
|
||||
if (auto *b = world.GetComponent<ECSComponent::Billboard>(entity)) {
|
||||
ent["Billboard"] = {
|
||||
{"texture", b->texturePath.ToString()},
|
||||
{"size", b->size}, {"active", b->active}
|
||||
};
|
||||
}
|
||||
if (auto *l = world.GetComponent<ECSComponent::Line>(entity)) {
|
||||
ent["Line"] = {
|
||||
{"sx", l->start.x}, {"sy", l->start.y}, {"sz", l->start.z},
|
||||
{"ex", l->end.x}, {"ey", l->end.y}, {"ez", l->end.z},
|
||||
{"r", l->color.x}, {"g", l->color.y}, {"b", l->color.z},
|
||||
{"thickness", l->thickness}, {"active", l->active}
|
||||
};
|
||||
}
|
||||
if (auto *lod = world.GetComponent<ECSComponent::LODController>(entity)) {
|
||||
ent["LODController"] = {{"min", lod->RangeMin}, {"max", lod->RangeMax}};
|
||||
}
|
||||
if (world.HasComponent<ECSComponent::Disabled>(entity))
|
||||
ent["disabled"] = true;
|
||||
|
||||
// skip entities that are purely runtime-managed (VehicleRef, MeshRenderer linked to legacy)
|
||||
bool runtimeOnly =
|
||||
world.HasComponent<ECSComponent::VehicleRef>(entity) ||
|
||||
(world.HasComponent<ECSComponent::MeshRenderer>(entity) &&
|
||||
world.GetComponent<ECSComponent::MeshRenderer>(entity)->modelInstance != nullptr);
|
||||
if (runtimeOnly) continue;
|
||||
|
||||
root.push_back(ent);
|
||||
}
|
||||
|
||||
std::ofstream f(path);
|
||||
if (!f) {
|
||||
WriteLog("[ECS] ECSPersistence: cannot write to " + path);
|
||||
return -1;
|
||||
}
|
||||
f << root.dump(2);
|
||||
WriteLog("[ECS] ECSPersistence: saved " + std::to_string(root.size()) + " entities to " + path);
|
||||
return static_cast<int>(root.size());
|
||||
}
|
||||
|
||||
int load(ECWorld &world, const std::string &path)
|
||||
{
|
||||
std::ifstream f(path);
|
||||
if (!f) return -1; // file doesn't exist — silently skip
|
||||
|
||||
nlohmann::json root;
|
||||
try { root = nlohmann::json::parse(f); }
|
||||
catch (const std::exception &e) {
|
||||
WriteLog("[ECS] ECSPersistence: JSON parse error in " + path + ": " + e.what());
|
||||
return -1;
|
||||
}
|
||||
|
||||
int created = 0;
|
||||
for (auto const &ent : root) {
|
||||
if (!ent.contains("name")) continue;
|
||||
std::string name = ent["name"].get<std::string>();
|
||||
if (world.FindEntityByName(name) != entt::null) continue;
|
||||
|
||||
auto entity = world.CreateEntity();
|
||||
auto &id = world.AddComponent<ECSComponent::Identification>(entity);
|
||||
id.Name = name;
|
||||
|
||||
if (ent.contains("Transform")) {
|
||||
auto &j = ent["Transform"];
|
||||
auto &t = world.AddComponent<ECSComponent::Transform>(entity);
|
||||
t.Position = glm::dvec3(double(j["px"]), double(j["py"]), double(j["pz"]));
|
||||
t.Rotation = glm::quat(float(j["rw"]), float(j["rx"]), float(j["ry"]), float(j["rz"]));
|
||||
t.Scale = glm::vec3(float(j["sx"]), float(j["sy"]), float(j["sz"]));
|
||||
}
|
||||
if (ent.contains("SpotLight")) {
|
||||
auto &j = ent["SpotLight"];
|
||||
auto &s = world.AddComponent<ECSComponent::SpotLight>(entity);
|
||||
s.Color = glm::vec3(float(j["r"]), float(j["g"]), float(j["b"]));
|
||||
s.Intensity = j["intensity"];
|
||||
s.Range = j["range"];
|
||||
s.InnerAngle = j["innerAngle"];
|
||||
s.OuterAngle = j["outerAngle"];
|
||||
s.Enabled = j["enabled"];
|
||||
s.CastShadows = j["castShadows"];
|
||||
}
|
||||
if (ent.contains("ParticleEmitter")) {
|
||||
auto &j = ent["ParticleEmitter"];
|
||||
auto &p = world.AddComponent<ECSComponent::ParticleEmitter>(entity);
|
||||
p.isActive = j["active"];
|
||||
p.spawnRate = j["spawnRate"];
|
||||
p.particleLifetime = j["lifetime"];
|
||||
p.airResistance = j["airResistance"];
|
||||
p.gravity = glm::vec3(float(j["gx"]), float(j["gy"]), float(j["gz"]));
|
||||
p.colorFade = glm::vec4(float(j["fadex"]), float(j["fadey"]),
|
||||
float(j["fadez"]), float(j["fadew"]));
|
||||
}
|
||||
if (ent.contains("Billboard")) {
|
||||
auto &j = ent["Billboard"];
|
||||
auto &b = world.AddComponent<ECSComponent::Billboard>(entity);
|
||||
b.texturePath = FName(j["texture"].get<std::string>());
|
||||
b.size = j["size"];
|
||||
b.active = j["active"];
|
||||
}
|
||||
if (ent.contains("Line")) {
|
||||
auto &j = ent["Line"];
|
||||
auto &l = world.AddComponent<ECSComponent::Line>(entity);
|
||||
l.start = glm::vec3(float(j["sx"]), float(j["sy"]), float(j["sz"]));
|
||||
l.end = glm::vec3(float(j["ex"]), float(j["ey"]), float(j["ez"]));
|
||||
l.color = glm::vec3(float(j["r"]), float(j["g"]), float(j["b"]));
|
||||
l.thickness = j["thickness"];
|
||||
l.active = j["active"];
|
||||
}
|
||||
if (ent.contains("LODController")) {
|
||||
auto &j = ent["LODController"];
|
||||
auto &lod = world.AddComponent<ECSComponent::LODController>(entity);
|
||||
lod.RangeMin = j["min"];
|
||||
lod.RangeMax = j["max"];
|
||||
}
|
||||
if (ent.value("disabled", false))
|
||||
world.AddComponent<ECSComponent::Disabled>(entity);
|
||||
|
||||
++created;
|
||||
}
|
||||
|
||||
WriteLog("[ECS] ECSPersistence: loaded " + std::to_string(created) + " entities from " + path);
|
||||
return created;
|
||||
}
|
||||
|
||||
} // namespace ecs_persistence
|
||||
12
entitysystem/ECSPersistence.h
Normal file
12
entitysystem/ECSPersistence.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
|
||||
class ECWorld;
|
||||
|
||||
namespace ecs_persistence
|
||||
{
|
||||
// Save all named entities to a JSON file. Returns number of entities saved, or -1 on error.
|
||||
int save(ECWorld &world, const std::string &path);
|
||||
// Load entities from a JSON file. Skips entities whose name already exists. Returns count loaded, or -1 on error.
|
||||
int load(ECWorld &world, const std::string &path);
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "ECScene.h"
|
||||
#include "utilities/Logs.h"
|
||||
#include "simulation/simulation.h"
|
||||
|
||||
ECScene::ECScene()
|
||||
: m_loaded(false)
|
||||
@@ -39,6 +40,9 @@ void ECScene::Update(float dt)
|
||||
if (!m_loaded)
|
||||
return;
|
||||
|
||||
if (!simulation::is_ready)
|
||||
return;
|
||||
|
||||
m_systems.Update(m_world, dt);
|
||||
OnUpdate(dt);
|
||||
}
|
||||
|
||||
@@ -12,15 +12,24 @@ entt::entity ECWorld::CreateEntity()
|
||||
|
||||
void ECWorld::DestroyEntity(entt::entity entity)
|
||||
{
|
||||
if (m_registry.valid(entity))
|
||||
if (m_registry.valid(entity)) {
|
||||
if (auto *id = m_registry.try_get<ECSComponent::Identification>(entity))
|
||||
m_nameIndex.erase(id->Name.ToString());
|
||||
m_registry.destroy(entity);
|
||||
}
|
||||
}
|
||||
|
||||
void ECWorld::Clear()
|
||||
{
|
||||
m_nameIndex.clear();
|
||||
m_registry.clear();
|
||||
}
|
||||
|
||||
void ECWorld::UpdateNameIndex(entt::entity entity, const std::string &name)
|
||||
{
|
||||
m_nameIndex[name] = entity;
|
||||
}
|
||||
|
||||
bool ECWorld::IsAlive(entt::entity entity) const
|
||||
{
|
||||
return m_registry.valid(entity);
|
||||
@@ -29,18 +38,8 @@ bool ECWorld::IsAlive(entt::entity entity) const
|
||||
|
||||
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;
|
||||
auto it = m_nameIndex.find(name);
|
||||
return (it != m_nameIndex.end()) ? it->second : entt::null;
|
||||
}
|
||||
|
||||
entt::registry& ECWorld::Registry()
|
||||
|
||||
@@ -12,6 +12,14 @@
|
||||
#include <entt/entt.hpp>
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
|
||||
// Forward declaration so the name-index branch in AddComponent<T> compiles in
|
||||
// translation units that include ECWorld.h without BasicComponents.h.
|
||||
// if constexpr discards the branch for non-Identification T, but the name must
|
||||
// still resolve at parse time.
|
||||
namespace ECSComponent { struct Identification; }
|
||||
|
||||
class ECWorld
|
||||
{
|
||||
@@ -22,6 +30,8 @@ public:
|
||||
entt::entity CreateEntity();
|
||||
void DestroyEntity(entt::entity entity);
|
||||
void Clear();
|
||||
// Updates the name→entity index after an Identification component's Name is changed at runtime.
|
||||
void UpdateNameIndex(entt::entity entity, const std::string &name);
|
||||
|
||||
bool IsAlive(entt::entity entity) const;
|
||||
|
||||
@@ -105,6 +115,7 @@ public:
|
||||
|
||||
private:
|
||||
entt::registry m_registry;
|
||||
std::unordered_map<std::string, entt::entity> m_nameIndex;
|
||||
};
|
||||
|
||||
template<typename T, typename... Args>
|
||||
@@ -122,7 +133,10 @@ T& ECWorld::AddComponent(entt::entity entity, Args&&... args)
|
||||
static T dummy{};
|
||||
return dummy;
|
||||
} else {
|
||||
return m_registry.get<T>(entity);
|
||||
auto &comp = m_registry.get<T>(entity);
|
||||
if constexpr (std::is_same_v<T, ECSComponent::Identification>)
|
||||
m_nameIndex[comp.Name.ToString()] = entity;
|
||||
return comp;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "entitysystem/systems/LineSystem.h"
|
||||
#include "entitysystem/systems/VehicleSyncSystem.h"
|
||||
#include "entitysystem/systems/BillboardSystem.h"
|
||||
#include "entitysystem/ECSPersistence.h"
|
||||
#include "utilities/Logs.h"
|
||||
|
||||
GameScene::GameScene() = default;
|
||||
@@ -37,6 +38,8 @@ void GameScene::OnLoad()
|
||||
#ifdef _DEBUG
|
||||
CreateTestEntities();
|
||||
#endif
|
||||
|
||||
ecs_persistence::load(World(), "ecs_save.json");
|
||||
}
|
||||
|
||||
void GameScene::OnUpdate(float dt)
|
||||
@@ -45,6 +48,7 @@ void GameScene::OnUpdate(float dt)
|
||||
|
||||
void GameScene::OnUnload()
|
||||
{
|
||||
ecs_persistence::save(World(), "ecs_save.json");
|
||||
World().Clear();
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ void LightSystem::Update(ECWorld& world, float dt)
|
||||
|
||||
light_array::free_light_record rec;
|
||||
rec.position = transform.Position;
|
||||
rec.direction = glm::vec3(0.f, -1.f, 0.f); // default downward; rotation support can be added later
|
||||
rec.direction = glm::normalize(glm::mat3_cast(transform.Rotation) * glm::vec3(0.f, -1.f, 0.f));
|
||||
rec.color = spot.Color;
|
||||
rec.intensity = spot.Intensity;
|
||||
rec.range = spot.Range;
|
||||
|
||||
Reference in New Issue
Block a user