fix: alot of stuff changed. Mostly bugfixxes / architechture changes
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user