fix: alot of stuff changed. Mostly bugfixxes / architechture changes

This commit is contained in:
2026-08-09 02:24:17 +02:00
parent 4b6f773c0c
commit 05384debbf
98 changed files with 5461 additions and 1751 deletions
+338 -90
View File
@@ -1,75 +1,278 @@
#include <destrum/Serialization/SceneSerializer.h>
#include <cmath>
#include <algorithm>
#include <fstream>
#include <iostream>
#include <limits>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <nlohmann/json.hpp>
#include <destrum/Scene/Scene.h>
#include <destrum/ObjectModel/GameObject.h>
#include <destrum/ObjectModel/Component.h>
#include <destrum/ObjectModel/GameObject.h>
#include <destrum/ObjectModel/ObjectId.h>
#include <destrum/ObjectModel/Transform.h>
#include <destrum/Components/Physics/Collider.h>
#include <destrum/Components/Physics/Rigidbody.h>
#include <destrum/Serialization/ComponentFactory.h>
#include <destrum/Serialization/ComponentRegistry.h>
using json = nlohmann::json;
namespace {
[[nodiscard]] bool IsFiniteNumber(const json& value) {
if (!value.is_number()) {
return false;
}
return std::isfinite(value.get<double>());
}
[[nodiscard]] bool IsFiniteArray(const json& value, std::size_t size) {
if (!value.is_array() || value.size() != size) {
return false;
}
for (const auto& element : value) {
if (!IsFiniteNumber(element)) {
return false;
}
}
return true;
}
[[nodiscard]] bool IsFiniteJson(const json& value) {
if (value.is_number()) {
return IsFiniteNumber(value);
}
if (value.is_array()) {
for (const auto& element : value) {
if (!IsFiniteJson(element)) {
return false;
}
}
return true;
}
if (value.is_object()) {
for (auto it = value.begin(); it != value.end(); ++it) {
if (!IsFiniteJson(it.value())) {
return false;
}
}
return true;
}
return true;
}
[[nodiscard]] bool ValidateTransform(const json& objectJson, int version) {
if (!objectJson.contains("transform")) {
return true;
}
const auto& transform = objectJson.at("transform");
if (!transform.is_object()) {
return false;
}
// Version 1 wrote an empty placeholder transform object.
if (version <= 1 && transform.empty()) {
return true;
}
if (!transform.contains("position") || !transform.contains("rotation") ||
!transform.contains("scale")) {
return false;
}
if (!IsFiniteArray(transform.at("position"), 3) ||
!IsFiniteArray(transform.at("scale"), 3)) {
return false;
}
const auto& rotation = transform.at("rotation");
return IsFiniteArray(rotation, 3) || IsFiniteArray(rotation, 4);
}
[[nodiscard]] bool ValidateSceneJson(const json& root) {
if (!root.is_object() || !root.contains("objects") || !root.at("objects").is_array()) {
return false;
}
if (root.contains("name") && !root.at("name").is_string()) {
return false;
}
int version = 1;
if (root.contains("version")) {
if (!root.at("version").is_number_integer()) {
return false;
}
version = root.at("version").get<int>();
if (version < 1 || version > 2) {
return false;
}
}
std::unordered_set<ObjectId> ids;
std::unordered_map<ObjectId, ObjectId> parents;
for (const auto& objectJson : root.at("objects")) {
if (!objectJson.is_object() || !objectJson.contains("id")) {
return false;
}
const ObjectId id = objectJson.at("id").get<ObjectId>();
if (id == InvalidObjectId ||
id >= std::numeric_limits<ObjectId>::max() - 1 ||
!ids.insert(id).second) {
return false;
}
if (objectJson.contains("name") && !objectJson.at("name").is_string()) {
return false;
}
if (objectJson.contains("active") && !objectJson.at("active").is_boolean()) {
return false;
}
if (!ValidateTransform(objectJson, version)) {
return false;
}
const ObjectId parent = objectJson.value("parent", InvalidObjectId);
if (parent == id) {
return false;
}
parents.emplace(id, parent);
if (objectJson.contains("components")) {
const auto& components = objectJson.at("components");
if (!components.is_array()) {
return false;
}
for (const auto& componentJson : components) {
if (!componentJson.is_object() || !componentJson.contains("type") ||
!componentJson.at("type").is_string()) {
return false;
}
if (componentJson.contains("enabled") &&
!componentJson.at("enabled").is_boolean()) {
return false;
}
if (componentJson.contains("data") &&
(!componentJson.at("data").is_object() ||
!IsFiniteJson(componentJson.at("data")))) {
return false;
}
if (!ComponentFactory::IsRegistered(componentJson.at("type").get<std::string>())) {
return false;
}
}
}
}
for (const auto& [id, parent] : parents) {
if (parent != InvalidObjectId && !ids.contains(parent)) {
return false;
}
std::unordered_set<ObjectId> visited;
ObjectId current = id;
while (current != InvalidObjectId) {
if (!visited.insert(current).second) {
return false;
}
const auto parentIt = parents.find(current);
if (parentIt == parents.end()) {
return false;
}
current = parentIt->second;
}
}
return true;
}
}
bool SceneSerializer::Save(Scene& scene, const std::filesystem::path& path) {
scene.CommitPendingAdditions();
if (scene.IsIterating()) {
std::cerr << "Cannot save a scene during an update or render phase: "
<< scene.GetName() << '\n';
return false;
}
RegisterEngineComponents();
try {
scene.CommitPendingAdditions();
} catch (const std::exception& exception) {
std::cerr << "Failed to commit scene additions before save: "
<< exception.what() << '\n';
return false;
}
json root;
root["version"] = 1;
root["version"] = 2;
root["name"] = scene.GetName();
root["objects"] = json::array();
for (const auto& objectPtr : scene.GetObjects()) {
if (!objectPtr) {
if (!objectPtr || objectPtr->IsBeingDestroyed()) {
continue;
}
const GameObject& object = *objectPtr;
if (object.GetComponent<Rigidbody>() != nullptr &&
object.GetComponent<Collider>() == nullptr) {
std::cerr << "Cannot save object with a Rigidbody but no Collider: "
<< object.GetName() << '\n';
return false;
}
const Transform& transform = object.GetTransform();
const glm::vec3& position = transform.GetLocalPosition();
const glm::quat& rotation = transform.GetLocalRotation();
const glm::vec3& scale = transform.GetLocalScale();
json objectJson;
objectJson["id"] = object.GetId();
objectJson["name"] = object.GetName();
objectJson["active"] = object.IsActive();
// Transform should be saved separately because in your engine it is not a component.
// Replace this with your actual Transform getters.
objectJson["transform"] = {
// Example:
// { "position", { object.GetTransform().GetLocalPosition().x,
// object.GetTransform().GetLocalPosition().y,
// object.GetTransform().GetLocalPosition().z } },
// { "rotation", { ... } },
// { "scale", { ... } }
{"position", {position.x, position.y, position.z}},
{"rotation", {rotation.x, rotation.y, rotation.z, rotation.w}},
{"scale", {scale.x, scale.y, scale.z}}
};
const Transform* parent = object.GetTransform().GetParent();
objectJson["parent"] = parent
const Transform* parent = transform.GetParent();
objectJson["parent"] = parent != nullptr && parent->GetOwner() != nullptr &&
parent->GetOwner()->GetScene() == &scene &&
!parent->GetOwner()->IsBeingDestroyed()
? parent->GetOwner()->GetId()
: InvalidObjectId;
objectJson["components"] = json::array();
for (const auto& componentPtr : object.GetComponents()) {
if (!componentPtr) {
if (!componentPtr || componentPtr->IsBeingDestroyed()) {
continue;
}
const Component& component = *componentPtr;
json componentJson;
componentJson["type"] = component.GetTypeName();
componentJson["enabled"] = component.isEnabled();
componentJson["data"] = component.Serialize();
objectJson["components"].push_back(componentJson);
objectJson["components"].push_back({
{"type", component.GetTypeName()},
{"enabled", component.isEnabled()},
{"data", component.Serialize()}
});
}
root["objects"].push_back(objectJson);
root["objects"].push_back(std::move(objectJson));
}
std::ofstream file(path);
@@ -79,10 +282,23 @@ bool SceneSerializer::Save(Scene& scene, const std::filesystem::path& path) {
}
file << root.dump(4);
if (!file.good()) {
std::cerr << "Failed to write scene file: " << path << '\n';
return false;
}
return true;
}
bool SceneSerializer::Load(Scene& scene, const std::filesystem::path& path) {
if (scene.IsIterating()) {
std::cerr << "Cannot load a scene during an update or render phase: "
<< scene.GetName() << '\n';
return false;
}
RegisterEngineComponents();
std::ifstream file(path);
if (!file.is_open()) {
std::cerr << "Failed to open scene file for reading: " << path << '\n';
@@ -90,134 +306,166 @@ bool SceneSerializer::Load(Scene& scene, const std::filesystem::path& path) {
}
json root;
try {
file >> root;
} catch (const std::exception& e) {
std::cerr << "Failed to parse scene file: " << e.what() << '\n';
if (!ValidateSceneJson(root)) {
std::cerr << "Invalid scene data: " << path << '\n';
return false;
}
} catch (const std::exception& exception) {
std::cerr << "Failed to parse scene file: " << exception.what() << '\n';
return false;
}
scene.RemoveAll();
if (scene.IsBeingUnloaded()) {
std::cerr << "Cannot load a scene that is being unloaded: " << scene.GetName() << '\n';
return false;
}
// Build the replacement separately. The current scene is not touched
// until all objects, transforms, and components have loaded successfully.
Scene stagingScene(scene.GetName());
std::unordered_map<ObjectId, GameObject*> idMap;
std::vector<const json*> objectJsonList;
std::vector<GameObject*> registeredObjects;
try {
const auto& objectsJson = root.at("objects");
// Pass 1:
// Create all GameObjects first.
for (const auto& objectJson : objectsJson) {
const auto id = objectJson.at("id").get<ObjectId>();
for (const auto& objectJson : root.at("objects")) {
const ObjectId id = objectJson.at("id").get<ObjectId>();
const std::string name = objectJson.value("name", "GameObject");
const bool active = objectJson.value("active", true);
GameObject* object = scene.CreateGameObject(name);
GameObject* object = stagingScene.CreateGameObject(name);
object->SetIdForDeserialization(id);
object->SetActive(active);
idMap[id] = object;
idMap.emplace(id, object);
objectJsonList.push_back(&objectJson);
}
scene.CommitPendingAdditions();
stagingScene.CommitPendingAdditions();
// Pass 2:
// Restore transforms and parent-child hierarchy.
for (const json* objectJson : objectJsonList) {
const auto id = objectJson->at("id").get<ObjectId>();
const ObjectId id = objectJson->at("id").get<ObjectId>();
GameObject* object = idMap.at(id);
// Replace this with your actual Transform deserialization.
if (objectJson->contains("transform")) {
const json& transformJson = objectJson->at("transform");
if (objectJson->contains("transform") &&
!objectJson->at("transform").empty()) {
const auto& transformJson = objectJson->at("transform");
const auto& position = transformJson.at("position");
const auto& rotation = transformJson.at("rotation");
const auto& scale = transformJson.at("scale");
auto position = transformJson.at("position");
object->GetTransform().SetLocalPosition({
position[0].get<float>(),
position[1].get<float>(),
position[2].get<float>()
position.at(0).get<float>(),
position.at(1).get<float>(),
position.at(2).get<float>()
});
auto rotation = transformJson.at("rotation");
object->GetTransform().SetLocalRotation({
rotation[0].get<float>(),
rotation[1].get<float>(),
rotation[2].get<float>()
});
if (rotation.size() == 4) {
object->GetTransform().SetLocalRotation({
rotation.at(3).get<float>(),
rotation.at(0).get<float>(),
rotation.at(1).get<float>(),
rotation.at(2).get<float>()
});
} else {
// Version 1 scene files used three Euler angles in
// degrees. Continue reading that format safely.
object->GetTransform().SetLocalRotation({
rotation.at(0).get<float>(),
rotation.at(1).get<float>(),
rotation.at(2).get<float>()
});
}
auto scale = transformJson.at("scale");
object->GetTransform().SetLocalScale({
scale[0].get<float>(),
scale[1].get<float>(),
scale[2].get<float>()
scale.at(0).get<float>(),
scale.at(1).get<float>(),
scale.at(2).get<float>()
});
}
const ObjectId parentId = objectJson->value("parent", InvalidObjectId);
if (parentId != InvalidObjectId) {
auto parentIt = idMap.find(parentId);
if (parentIt != idMap.end()) {
object->GetTransform().SetParent(&parentIt->second->GetTransform());
} else {
std::cerr << "Parent not found while loading object " << id << '\n';
}
object->GetTransform().SetParent(&idMap.at(parentId)->GetTransform(), false);
}
}
// Pass 3:
// Create and deserialize components.
for (const json* objectJson : objectJsonList) {
const auto id = objectJson->at("id").get<ObjectId>();
GameObject* object = idMap.at(id);
GameObject* object = idMap.at(objectJson->at("id").get<ObjectId>());
if (!objectJson->contains("components")) {
continue;
}
for (const auto& componentJson : objectJson->at("components")) {
const std::string type = componentJson.at("type").get<std::string>();
Component* component = ComponentFactory::Create(type, *object);
if (!component) {
std::cerr << "Unknown component type while loading scene: " << type << '\n';
continue;
if (component == nullptr) {
throw std::runtime_error("Unknown component type: " + type);
}
component->SetEnabled(componentJson.value("enabled", true));
if (componentJson.contains("data")) {
component->Deserialize(componentJson.at("data"));
}
}
}
// Pass 4:
// Resolve object references now that all objects and components exist.
for (const auto& objectPtr : scene.GetObjects()) {
for (const auto& objectPtr : stagingScene.GetObjects()) {
if (!objectPtr) {
continue;
}
for (const auto& componentPtr : objectPtr->GetComponents()) {
if (!componentPtr) {
continue;
if (componentPtr) {
componentPtr->ResolveReferences(idMap);
}
componentPtr->ResolveReferences(idMap);
}
}
} catch (const std::exception& e) {
std::cerr << "Failed to load scene: " << e.what() << '\n';
scene.RemoveAll();
// Register the replacement bodies in the live physics world before
// touching the current scene. If any body fails, the old scene and
// its physics state can remain intact.
for (const auto& objectPtr : stagingScene.GetObjects()) {
if (objectPtr) {
if (objectPtr->GetComponent<Rigidbody>() != nullptr &&
objectPtr->GetComponent<Collider>() == nullptr) {
throw std::runtime_error(
"Rigidbody requires a collider on object " + objectPtr->GetName());
}
scene.GetPhysics().RegisterGameObject(*objectPtr);
registeredObjects.push_back(objectPtr.get());
}
}
} catch (const std::exception& exception) {
std::cerr << "Failed to load scene: " << exception.what() << '\n';
for (GameObject* object : registeredObjects) {
if (object != nullptr) {
scene.GetPhysics().UnregisterGameObject(*object);
}
}
stagingScene.RemoveAll();
return false;
}
scene.RemoveAll();
scene.m_objects = std::move(stagingScene.m_objects);
scene.m_pendingAdditions = std::move(stagingScene.m_pendingAdditions);
if (root.contains("name")) {
scene.m_name = root.at("name").get<std::string>();
}
for (const auto& object : scene.m_objects) {
if (object) {
object->SetScene(&scene);
}
}
for (const auto& object : scene.m_pendingAdditions) {
if (object) {
object->SetScene(&scene);
}
}
return true;
}
}