From b9358166ba8e2e79044bd5a027f7282ed09f9ba6 Mon Sep 17 00:00:00 2001 From: infirit89 Date: Wed, 4 Feb 2026 23:10:31 +0200 Subject: [PATCH 01/51] chore(Asset): add concrete namespace for declare asset type macro --- Libraries/LibAsset/Asset.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Libraries/LibAsset/Asset.h b/Libraries/LibAsset/Asset.h index 64ef7f00..9e924077 100644 --- a/Libraries/LibAsset/Asset.h +++ b/Libraries/LibAsset/Asset.h @@ -43,8 +43,8 @@ concept HasStaticType = requires(T) { }; #define TR_DECLARE_ASSET_TYPE(type_name) \ - static AssetTypeId static_type() { return Terran::Core::Hash::fnv1a_64(#type_name); } \ - virtual AssetTypeId type() const override { return static_type(); } + static Terran::Asset::AssetTypeId static_type() { return Terran::Core::Hash::fnv1a_64(#type_name); } \ + virtual Terran::Asset::AssetTypeId type() const override { return static_type(); } class TextAsset final : public Asset { public: From b8dfd7a08eaeb85c362bfc60324dda68de6f02fa Mon Sep 17 00:00:00 2001 From: infirit89 Date: Wed, 4 Feb 2026 23:11:14 +0200 Subject: [PATCH 02/51] chore(Core): start from unnamed namespace for log macros --- Libraries/LibCore/Log.h | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Libraries/LibCore/Log.h b/Libraries/LibCore/Log.h index 89fd7566..5454a6a3 100644 --- a/Libraries/LibCore/Log.h +++ b/Libraries/LibCore/Log.h @@ -48,36 +48,36 @@ class Log final { }; #define TR_CORE_TRACE(LoggerName, ...) \ - if (Terran::Core::Log::contains(LoggerName)) \ - Terran::Core::Log::logger(LoggerName)->trace(__VA_ARGS__) + if (::Terran::Core::Log::contains(LoggerName)) \ + ::Terran::Core::Log::logger(LoggerName)->trace(__VA_ARGS__) #define TR_CORE_INFO(LoggerName, ...) \ - if (Terran::Core::Log::contains(LoggerName)) \ - Terran::Core::Log::logger(LoggerName)->info(__VA_ARGS__) + if (::Terran::Core::Log::contains(LoggerName)) \ + ::Terran::Core::Log::logger(LoggerName)->info(__VA_ARGS__) #define TR_CORE_WARN(LoggerName, ...) \ - if (Terran::Core::Log::contains(LoggerName)) \ - Terran::Core::Log::logger(LoggerName)->warn(__VA_ARGS__) + if (::Terran::Core::Log::contains(LoggerName)) \ + ::Terran::Core::Log::logger(LoggerName)->warn(__VA_ARGS__) #define TR_CORE_ERROR(LoggerName, ...) \ - if (Terran::Core::Log::contains(LoggerName)) \ - Terran::Core::Log::logger(LoggerName)->error(__VA_ARGS__) + if (::Terran::Core::Log::contains(LoggerName)) \ + ::Terran::Core::Log::logger(LoggerName)->error(__VA_ARGS__) #define TR_CLIENT_TRACE(...) \ - if (Terran::Core::Log::contains(TR_LOG_CLIENT)) \ - Terran::Core::Log::logger(TR_LOG_CLIENT)->trace(__VA_ARGS__) + if (::Terran::Core::Log::contains(TR_LOG_CLIENT)) \ + ::Terran::Core::Log::logger(TR_LOG_CLIENT)->trace(__VA_ARGS__) #define TR_CLIENT_INFO(...) \ - if (Terran::Core::Log::contains(TR_LOG_CLIENT)) \ - Terran::Core::Log::logger(TR_LOG_CLIENT)->info(__VA_ARGS__) + if (::Terran::Core::Log::contains(TR_LOG_CLIENT)) \ + ::Terran::Core::Log::logger(TR_LOG_CLIENT)->info(__VA_ARGS__) #define TR_CLIENT_WARN(...) \ - if (Terran::Core::Log::contains(TR_LOG_CLIENT)) \ - Terran::Core::Log::logger(TR_LOG_CLIENT)->warn(__VA_ARGS__) + if (::Terran::Core::Log::contains(TR_LOG_CLIENT)) \ + ::Terran::Core::Log::logger(TR_LOG_CLIENT)->warn(__VA_ARGS__) #define TR_CLIENT_ERROR(...) \ - if (Terran::Core::Log::contains(TR_LOG_CLIENT)) \ - Terran::Core::Log::logger(TR_LOG_CLIENT)->error(__VA_ARGS__) + if (::Terran::Core::Log::contains(TR_LOG_CLIENT)) \ + ::Terran::Core::Log::logger(TR_LOG_CLIENT)->error(__VA_ARGS__) } From 11b995e45af6290b77bd93e399e85ab58dc4689f Mon Sep 17 00:00:00 2001 From: infirit89 Date: Wed, 4 Feb 2026 23:12:28 +0200 Subject: [PATCH 03/51] chore(Core): move math utilities into core --- .../src/Math => Libraries/LibCore}/Math.cpp | 7 +++---- Libraries/LibCore/Math.h | 11 +++++++++++ TerranEngine/src/Math/Math.h | 13 ------------- 3 files changed, 14 insertions(+), 17 deletions(-) rename {TerranEngine/src/Math => Libraries/LibCore}/Math.cpp (88%) create mode 100644 Libraries/LibCore/Math.h delete mode 100644 TerranEngine/src/Math/Math.h diff --git a/TerranEngine/src/Math/Math.cpp b/Libraries/LibCore/Math.cpp similarity index 88% rename from TerranEngine/src/Math/Math.cpp rename to Libraries/LibCore/Math.cpp index 40591421..9a8cd755 100644 --- a/TerranEngine/src/Math/Math.cpp +++ b/Libraries/LibCore/Math.cpp @@ -1,4 +1,3 @@ -#include "trpch.h" #include "Math.h" #include @@ -6,9 +5,9 @@ #include #include -namespace TerranEngine +namespace Terran::Core { - bool Math::Decompose(const glm::mat4& modelMatrix, glm::vec3& translation, glm::vec3& rotation, glm::vec3& scale) + bool Math::decompose_transform_matrix(const glm::mat4& modelMatrix, glm::vec3& translation, glm::vec3& rotation, glm::vec3& scale) { glm::mat4 LocalMatrix(modelMatrix); @@ -75,7 +74,7 @@ namespace TerranEngine return true; } - glm::mat4 Math::ComposeTransformationMatrix(const glm::vec3& translation, const glm::vec3& rotation, const glm::vec3& scale) + glm::mat4 Math::compose_transform_matrix(const glm::vec3& translation, const glm::vec3& rotation, const glm::vec3& scale) { return glm::translate(glm::mat4(1.0f), translation) * glm::toMat4(glm::quat(rotation)) * diff --git a/Libraries/LibCore/Math.h b/Libraries/LibCore/Math.h new file mode 100644 index 00000000..57e1610b --- /dev/null +++ b/Libraries/LibCore/Math.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +namespace Terran::Core::Math { + +bool decompose_transform_matrix(glm::mat4 const& modelMatrix, glm::vec3& translation, glm::vec3& rotation, glm::vec3& scale); +glm::mat4 compose_transform_matrix(glm::vec3 const& translation, glm::vec3 const& rotation, glm::vec3 const& scale); + +} + diff --git a/TerranEngine/src/Math/Math.h b/TerranEngine/src/Math/Math.h deleted file mode 100644 index a9455f8d..00000000 --- a/TerranEngine/src/Math/Math.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include - -namespace TerranEngine -{ - namespace Math - { - bool Decompose(const glm::mat4& modelMatrix, glm::vec3& translation, glm::vec3& rotation, glm::vec3& scale); - glm::mat4 ComposeTransformationMatrix(const glm::vec3& translation, const glm::vec3& rotation, const glm::vec3& scale); - } - -} \ No newline at end of file From a0fb9283a57bf6d077f722e1ed7994af757bc16b Mon Sep 17 00:00:00 2001 From: infirit89 Date: Wed, 4 Feb 2026 23:13:04 +0200 Subject: [PATCH 04/51] chore(Scene): initial work for LibScene separation from engine library --- .../LibCore/SerializerExtensions.cpp | 11 +- .../LibCore/SerializerExtensions.h | 24 +- Libraries/LibScene/Components.h | 176 +++++ Libraries/LibScene/Entity.cpp | 72 ++ Libraries/LibScene/Entity.h | 145 ++++ Libraries/LibScene/Entity.inl | 64 ++ Libraries/LibScene/Scene.cpp | 677 +++++++++++++++++ .../src/Scene => Libraries/LibScene}/Scene.h | 32 +- .../LibScene}/SceneManager.cpp | 21 +- .../LibScene}/SceneManager.h | 2 +- Libraries/LibScene/SceneSerializer.cpp | 583 +++++++++++++++ .../LibScene}/SceneSerializer.h | 14 +- Libraries/LibScene/premake5.lua | 63 ++ TerranEngine/premake5.lua | 2 +- TerranEngine/src/Scene/Components.h | 177 ----- TerranEngine/src/Scene/Entity.h | 236 ------ TerranEngine/src/Scene/Scene.cpp | 692 ------------------ TerranEngine/src/Scene/SceneSerializer.cpp | 576 --------------- .../src/Scene/Systems/SceneRenderer.cpp | 178 ----- .../src/Scene/Systems/SceneRenderer.h | 52 -- dependecies.lua | 4 +- premake-native.lua | 1 + {TerranEngine/vendor => vendor}/entt/LICENSE | 0 .../vendor => vendor}/entt/include/entt.hpp | 0 24 files changed, 1826 insertions(+), 1976 deletions(-) rename TerranEngine/src/Utils/SerializerUtils.cpp => Libraries/LibCore/SerializerExtensions.cpp (79%) rename TerranEngine/src/Utils/SerializerUtils.h => Libraries/LibCore/SerializerExtensions.h (82%) create mode 100644 Libraries/LibScene/Components.h create mode 100644 Libraries/LibScene/Entity.cpp create mode 100644 Libraries/LibScene/Entity.h create mode 100644 Libraries/LibScene/Entity.inl create mode 100644 Libraries/LibScene/Scene.cpp rename {TerranEngine/src/Scene => Libraries/LibScene}/Scene.h (87%) rename {TerranEngine/src/Scene => Libraries/LibScene}/SceneManager.cpp (70%) rename {TerranEngine/src/Scene => Libraries/LibScene}/SceneManager.h (96%) create mode 100644 Libraries/LibScene/SceneSerializer.cpp rename {TerranEngine/src/Scene => Libraries/LibScene}/SceneSerializer.h (55%) create mode 100644 Libraries/LibScene/premake5.lua delete mode 100644 TerranEngine/src/Scene/Components.h delete mode 100644 TerranEngine/src/Scene/Entity.h delete mode 100644 TerranEngine/src/Scene/Scene.cpp delete mode 100644 TerranEngine/src/Scene/SceneSerializer.cpp delete mode 100644 TerranEngine/src/Scene/Systems/SceneRenderer.cpp delete mode 100644 TerranEngine/src/Scene/Systems/SceneRenderer.h rename {TerranEngine/vendor => vendor}/entt/LICENSE (100%) rename {TerranEngine/vendor => vendor}/entt/include/entt.hpp (100%) diff --git a/TerranEngine/src/Utils/SerializerUtils.cpp b/Libraries/LibCore/SerializerExtensions.cpp similarity index 79% rename from TerranEngine/src/Utils/SerializerUtils.cpp rename to Libraries/LibCore/SerializerExtensions.cpp index 955be29b..054e93c2 100644 --- a/TerranEngine/src/Utils/SerializerUtils.cpp +++ b/Libraries/LibCore/SerializerExtensions.cpp @@ -1,7 +1,6 @@ -#include "trpch.h" -#include "SerializerUtils.h" +#include "SerializerExtensions.h" -namespace TerranEngine { +namespace Terran::Core { YAML::Emitter& operator<<(YAML::Emitter& out, glm::vec2 const& v) { @@ -30,12 +29,6 @@ YAML::Emitter& operator<<(YAML::Emitter& out, Terran::Core::UUID const& v) return out; } -YAML::Emitter& operator<<(YAML::Emitter& out, TextureFilter const& v) -{ - out << TextureFilterToString(v); - return out; -} - YAML::Emitter& operator<<(YAML::Emitter& out, std::byte v) { out << static_cast(v); diff --git a/TerranEngine/src/Utils/SerializerUtils.h b/Libraries/LibCore/SerializerExtensions.h similarity index 82% rename from TerranEngine/src/Utils/SerializerUtils.h rename to Libraries/LibCore/SerializerExtensions.h index b141704b..85f04817 100644 --- a/TerranEngine/src/Utils/SerializerUtils.h +++ b/Libraries/LibCore/SerializerExtensions.h @@ -1,20 +1,17 @@ #pragma once -#include "LibCore/UUID.h" - -#include "Graphics/Texture.h" +#include "UUID.h" #include #include -namespace TerranEngine { +namespace Terran::Core { YAML::Emitter& operator<<(YAML::Emitter& out, glm::vec2 const& v); YAML::Emitter& operator<<(YAML::Emitter& out, glm::vec3 const& v); YAML::Emitter& operator<<(YAML::Emitter& out, glm::vec4 const& v); YAML::Emitter& operator<<(YAML::Emitter& out, Terran::Core::UUID const& v); -YAML::Emitter& operator<<(YAML::Emitter& out, TextureFilter const& v); YAML::Emitter& operator<<(YAML::Emitter& out, std::byte v); } @@ -113,23 +110,6 @@ struct convert { } }; -template<> -struct convert { - static Node encode(TerranEngine::TextureFilter const& rhs) - { - Node node; - node.push_back(TerranEngine::TextureFilterToString(rhs)); - return node; - } - - static bool decode(Node const& node, TerranEngine::TextureFilter& rhs) - { - - rhs = TerranEngine::TextureFilterFromString(node.as()); - return true; - } -}; - template<> struct convert { static Node encode(std::byte rhs) diff --git a/Libraries/LibScene/Components.h b/Libraries/LibScene/Components.h new file mode 100644 index 00000000..d39b5580 --- /dev/null +++ b/Libraries/LibScene/Components.h @@ -0,0 +1,176 @@ +#pragma once + +#include +#include + +// #include "Graphics/Font.h" +// #include "Graphics/OrthographicCamera.h" +// +// #include "Physics/PhysicsStates.h" + +#include +#define GLM_ENABLE_EXPERIMENTAL +#include + +#include +#include + +namespace Terran::World { + +struct TagComponent { + TagComponent() = default; + + TagComponent(std::string const& name, Terran::Core::UUID const& id) + : Name(name) + , ID(id) + { + } + + TagComponent(std::string const& name) + : Name(name) + { + } + + std::string Name; + Terran::Core::UUID ID; +}; + +struct TransformComponent { + TransformComponent() = default; + + glm::vec3 Position = { 0.0f, 0.0f, 0.0f }; + glm::vec3 Rotation = { 0.0f, 0.0f, 0.0f }; + glm::vec3 Scale = { 1.0f, 1.0f, 1.0f }; + + glm::vec3 Forward = { 0.0f, 0.0f, 1.0f }; + glm::vec3 Up = { 0.0f, 1.0f, 0.0f }; + glm::vec3 Right = { 1.0f, 0.0f, 0.0f }; + + // dirty flag used for optimization + bool IsDirty = true; + + // cached transform matrices + glm::mat4 WorldSpaceTransformMatrix = glm::mat4(1.0f); + glm::mat4 LocalSpaceTransformMatrix = glm::mat4(1.0f); +}; + +// struct CameraComponent { +// CameraComponent() = default; +// +// OrthographicCamera Camera; +// bool Primary = true; +// +// glm::vec4 BackgroundColor = { 0.1f, 0.1f, 0.1f, 1.0f }; +// }; +// +// struct SpriteRendererComponent { +// SpriteRendererComponent() = default; +// +// glm::vec4 Color = { 1.0f, 1.0f, 1.0f, 1.0f }; +// Terran::Core::UUID TextureHandle = Terran::Core::UUID::invalid(); +// +// int ZIndex = 0; +// }; +// +// struct CircleRendererComponent { +// CircleRendererComponent() = default; +// +// glm::vec4 Color = { 1.0f, 1.0f, 1.0f, 1.0f }; +// float Thickness = 1.0f; +// }; +// +// // bullshit; fix +// struct LineRendererComponent { +// LineRendererComponent() = default; +// +// glm::vec4 Color = { 1.0f, 1.0f, 1.0f, 1.0f }; +// float Thickness = 1.0f; +// +// glm::vec3 StartPoint = { 0.0f, 0.0f, 0.0f }; +// glm::vec3 EndPoint = { 0.0f, 0.0f, 1.0f }; +// }; +// +// struct TextRendererComponent { +// TextRendererComponent() = default; +// ~TextRendererComponent() = default; +// +// Terran::Core::Shared FontAtlas; +// glm::vec4 TextColor = { 1.0f, 1.0f, 1.0f, 1.0f }; +// std::string Text = ""; +// float LineSpacing = 1.0f; +// float LineWidth = 10.0f; +// }; + +struct RelationshipComponent { + RelationshipComponent() + : Parent({ 0 }) + { + } + + Terran::Core::UUID Parent; + std::vector Children; +}; + +// struct ScriptComponent { +// ScriptComponent() = default; +// +// ScriptComponent(std::string const& moduleName) +// : ModuleName(moduleName) +// { +// } +// +// // NOTE: think about having an array of scripts so that one entity +// // "can" have more than one script (because of the 1 component of a type per entity) +// +// std::string ModuleName; +// std::vector FieldHandles; +// +// Terran::Core::UUID ScriptSourceHandle = Terran::Core::UUID::invalid(); +// bool ClassExists = true; +// }; +// +// struct Rigidbody2DComponent { +// Rigidbody2DComponent() = default; +// +// PhysicsBodyType BodyType = PhysicsBodyType::Dynamic; +// PhysicsBodySleepState SleepState = PhysicsBodySleepState::Awake; +// +// bool FixedRotation = false; +// float GravityScale = 1.0f; +// bool Enabled = true; +// int LayerIndex = 0; +// +// Terran::Core::UUID PhysicsMaterialHandle = Terran::Core::UUID::invalid(); +// }; +// +// struct BoxCollider2DComponent { +// BoxCollider2DComponent() = default; +// +// glm::vec2 Offset = { 0.0f, 0.0f }; +// glm::vec2 Size = { 1.0f, 1.0f }; +// bool Sensor = false; +// +// uint32_t ColliderIndex = 0; +// }; +// +// struct CircleCollider2DComponent { +// CircleCollider2DComponent() = default; +// +// glm::vec2 Offset = { 0.0f, 0.0f }; +// float Radius = 0.5f; +// bool Sensor = false; +// +// uint32_t ColliderIndex = 0; +// }; +// +// struct CapsuleCollider2DComponent { +// CapsuleCollider2DComponent() = default; +// +// glm::vec2 Offset = { 0.0f, 0.0f }; +// glm::vec2 Size = { 0.5f, 1.0f }; +// bool Sensor = false; +// +// uint32_t ColliderIndex = 0; +// }; + +} diff --git a/Libraries/LibScene/Entity.cpp b/Libraries/LibScene/Entity.cpp new file mode 100644 index 00000000..4afa9d85 --- /dev/null +++ b/Libraries/LibScene/Entity.cpp @@ -0,0 +1,72 @@ +#include "Scene.h" + +#include "LibCore/UUID.h" + +namespace Terran::World { + +bool Entity::Valid() const { return m_Scene->m_Registry.valid(m_Handle); } +bool Entity::operator==(Entity const& other) const { return m_Handle == other.m_Handle && m_Scene == other.m_Scene; } +bool Entity::HasParent() const { return HasComponent() ? m_Scene->FindEntityWithUUID(GetComponent().Parent) : false; } +Core::UUID const& Entity::GetSceneId() const { return m_Scene->handle(); } + +Entity Entity::GetChild(uint32_t index) const +{ + if (!HasComponent()) + return {}; + + return m_Scene->FindEntityWithUUID(GetChildren()[index]); +} + +Entity Entity::GetParent() const +{ + if (!HasComponent()) + return {}; + + return m_Scene->FindEntityWithUUID(GetParentID()); +} +void Entity::SetParent(Entity parent, bool forceTransformUpdate) +{ + if (!HasComponent()) + AddComponent(); + + if (!parent.HasComponent()) + parent.AddComponent(); + + if (IsChildOf(parent)) + return; + if (parent.IsChildOf(*this)) + return; + + if (HasParent()) + Unparent(); + + auto& relComp = GetComponent(); + relComp.Parent = parent.GetID(); + parent.GetChildren().emplace_back(GetID()); + + m_Scene->ConvertToLocalSpace(*this); +} +void Entity::Unparent() +{ + if (!HasComponent()) + return; + + Core::UUID parentID = GetComponent().Parent; + Entity parent = m_Scene->FindEntityWithUUID(parentID); + + if (!parent) + return; + + m_Scene->ConvertToWorldSpace(*this); + + auto const& it = std::ranges::find(parent.GetChildren(), GetID()); + + if (it != parent.GetChildren().end()) + parent.GetChildren().erase(it); + + SetParentID(Core::UUID({ 0 })); + + // TODO: if the relationship component is no longer necessary than remove it +} + +} diff --git a/Libraries/LibScene/Entity.h b/Libraries/LibScene/Entity.h new file mode 100644 index 00000000..3531b360 --- /dev/null +++ b/Libraries/LibScene/Entity.h @@ -0,0 +1,145 @@ +#pragma once + +#include "Components.h" + +#include +#include +#include + +#pragma warning(push) +#pragma warning(disable : 4834) + +#include +#include +#include +#include +#include + +namespace Terran::World { + +class Scene; + +class Entity final { +public: + Entity() = default; + + Entity(entt::entity const& handle, Scene* scene) + : m_Handle(handle) + , m_Scene(scene) + { + } + + ~Entity() = default; + + template + Component& AddComponent(Args&&... parameters); + + template + Component& AddOrReplaceComponent(Args&&... parameters); + + template + Component& GetComponent() const; + + template + void RemoveComponent(); + + template + Component& TryGetComponent() const; + + template + bool HasComponent() const; + + // visit all the components of an entity + // the signiture of Func should be void(const entt::type_info) + template + void Visit(Entity entity, Func func) const; + + // base stuffs + Terran::Core::UUID const& GetID() const { return GetComponent().ID; } + TransformComponent& GetTransform() const { return GetComponent(); } + bool Valid() const; + std::string const& GetName() const { return GetComponent().Name; } + + // operators + operator entt::entity() const { return m_Handle; } + bool operator!=(Entity const& other) const { return !(*this == other); } + operator uint32_t() const { return static_cast(m_Handle); } + operator bool() const { return m_Handle != entt::null; } + bool operator==(Entity const& other) const; + + // relationship component stuffs + std::vector& GetChildren() const { return GetComponent().Children; } + size_t GetChildCount() const { return HasComponent() ? GetComponent().Children.size() : 0; } + Terran::Core::UUID GetParentID() const { return HasComponent() ? GetComponent().Parent : Terran::Core::UUID::invalid(); } + bool HasParent() const; + + Terran::Core::UUID const& GetSceneId() const; + + Entity GetChild(uint32_t index) const; + + void SetParentID(Terran::Core::UUID const& id) + { + if (!HasComponent()) + return; + + auto& relComp = GetComponent(); + relComp.Parent = id; + } + + Entity GetParent() const; + + bool IsChildOf(Entity entity) const + { + if (!HasComponent()) + return false; + + if (!entity.HasComponent()) + return false; + + return GetParentID() == entity.GetID(); + } + + void SetParent(Entity parent, bool forceTransformUpdate = false); + + void Unparent(); + + /*void Unparent(Entity parent, Entity child, bool removeRelationship) + { + if (!parent.HasComponent()) + return; + + if (!child.HasComponent()) + return; + + const auto& it = std::find(parent.GetChildren().begin(), parent.GetChildren().end(), child.GetID()); + + if (it != parent.GetChildren().end()) + parent.GetChildren().erase(it); + + if (removeRelationship) + child.RemoveComponent(); + else + { + RelationshipComponent& rc = child.GetComponent(); + rc.ParentID = UUID({ 0 }); + } + }*/ + + void RemoveChild(Entity child, bool removeRelationship) + { + child.Unparent(); + } + + void Reparent(Entity previousParent, Entity newParent) + { + Unparent(); + SetParent(newParent); + } + +private: + entt::entity m_Handle { entt::null }; + Scene* m_Scene = nullptr; +}; + +} +#pragma warning(pop) diff --git a/Libraries/LibScene/Entity.inl b/Libraries/LibScene/Entity.inl new file mode 100644 index 00000000..785300ca --- /dev/null +++ b/Libraries/LibScene/Entity.inl @@ -0,0 +1,64 @@ +#pragma once +namespace Terran::World { + +template +inline Component& Entity::AddComponent(Args&&... parameters) +{ + TR_ASSERT(m_Handle != entt::null, "Ivalid entity"); + + TR_ASSERT(!HasComponent(), "Entity already has component"); + + Component& component = m_Scene->m_Registry.emplace(m_Handle, std::forward(parameters)...); + return component; +} + +template +inline Component& Entity::AddOrReplaceComponent(Args&&... parameters) +{ + TR_ASSERT(m_Handle != entt::null, "Ivalid entity"); + Component& component = m_Scene->m_Registry.emplace_or_replace(m_Handle, std::forward(parameters)...); + return component; +} + +template +inline Component& Entity::GetComponent() const +{ + TR_ASSERT(m_Handle != entt::null, "Ivalid entity"); + + TR_ASSERT(HasComponent(), "Entity doesn't have the component"); + return m_Scene->m_Registry.get(m_Handle); +} + +template +inline void Entity::RemoveComponent() +{ + TR_ASSERT(m_Handle != entt::null, "Ivalid entity"); + + TR_ASSERT(HasComponent(), "Entity doesn't have component"); + + m_Scene->m_Registry.remove(m_Handle); +} + +template +inline Component& Entity::TryGetComponent() const +{ + TR_ASSERT(m_Handle != entt::null, "Ivalid entity"); + + return m_Scene->m_Registry.try_get(m_Handle); +} + +template +inline bool Entity::HasComponent() const +{ + TR_ASSERT(m_Handle != entt::null, "Ivalid entity"); + + return m_Scene->m_Registry.all_of(m_Handle); +} + +template +inline void Entity::Visit(Entity entity, Func func) const +{ + m_Scene->m_Registry.visit(entity, std::forward(func)); +} + +} diff --git a/Libraries/LibScene/Scene.cpp b/Libraries/LibScene/Scene.cpp new file mode 100644 index 00000000..db19d220 --- /dev/null +++ b/Libraries/LibScene/Scene.cpp @@ -0,0 +1,677 @@ +#include "Scene.h" + +#include "Components.h" +#include "Entity.h" +#include "SceneManager.h" + +#include + +// #include "Utils/Debug/OptickProfiler.h" +// #include "Utils/Debug/Profiler.h" + +#include +#include +#define GLM_ENABLE_EXPERIMENTAL +#include + +namespace Terran::World { + +struct SceneComponent final { + Terran::Core::UUID SceneID; +}; + +namespace { + +template +void CopyComponent(entt::entity srcHandle, entt::entity dstHandle, entt::registry& srcRegistry, entt::registry& dstRegistry) +{ + if (!srcRegistry.all_of(srcHandle)) + return; + + dstRegistry.emplace_or_replace(dstHandle, srcRegistry.get(srcHandle)); +} + +// template<> +// void CopyComponent(entt::entity srcHandle, entt::entity dstHandle, entt::registry& srcRegistry, entt::registry& dstRegistry) +// { +// if (!srcRegistry.all_of(srcHandle)) +// return; +// +// entt::entity const srcSceneEntity = srcRegistry.view().front(); +// Terran::Core::UUID const& srcSceneID = srcRegistry.get(srcSceneEntity).SceneID; +// Terran::Core::UUID const& srcEntityID = srcRegistry.get(srcHandle).ID; +// +// Terran::Core::Shared srcScriptInstance = ScriptEngine::GetScriptInstance(srcSceneID, srcEntityID); +// if (!srcScriptInstance) { +// TR_CORE_ERROR(TR_LOG_SCRIPT, "The script instance from the source scene was null"); +// return; +// } +// dstRegistry.emplace_or_replace(dstHandle, srcRegistry.get(srcHandle)); +// +// entt::entity const dstSceneEntity = dstRegistry.view().front(); +// Terran::Core::UUID const& dstSceneID = dstRegistry.get(dstSceneEntity).SceneID; +// +// Terran::Core::UUID const& dstEntityID = dstRegistry.get(dstHandle).ID; +// +// Terran::Core::Shared dstScriptInstance = ScriptEngine::GetScriptInstance(dstSceneID, dstEntityID); +// if (!dstScriptInstance) { +// TR_CORE_ERROR(TR_LOG_SCRIPT, "The script instance from the destination scene was null"); +// return; +// } +// dstScriptInstance->CopyAllFieldsFrom(srcScriptInstance); +// } + +template +void CopyComponent(entt::entity srcHandle, entt::entity dstHandle, entt::registry& srcRegistry) +{ + CopyComponent(srcHandle, dstHandle, srcRegistry, srcRegistry); +} + +static glm::mat4 CalculateTransformMatrix(TransformComponent const& transform) +{ + return glm::translate(glm::mat4(1.0f), transform.Position) * glm::toMat4(glm::quat(transform.Rotation)) * glm::scale(glm::mat4(1.0f), transform.Scale); +} + +} + +Scene::Scene() + : Scene(Terran::Core::UUID()) +{ +} + +Scene::Scene(Terran::Core::UUID const& handle) + : Asset(handle) +{ + auto const sceneEntity = m_Registry.create(); + m_Registry.emplace(sceneEntity, m_handle); + + // m_Registry.on_construct().connect<&Scene::OnScriptComponentConstructed>(this); + // m_Registry.on_destroy().connect<&Scene::OnScriptComponentDestroyed>(this); + // + // m_Registry.on_construct().connect<&Scene::OnRigidbody2DComponentConstructed>(this); + // m_Registry.on_destroy().connect<&Scene::OnRigidbody2DComponentDestroyed>(this); + // + // m_Registry.on_construct().connect<&Scene::OnBoxCollider2DComponentConstructed>(this); + // m_Registry.on_destroy().connect<&Scene::OnBoxCollider2DComponentDestroyed>(this); + // + // m_Registry.on_construct().connect<&Scene::OnCircleCollider2DComponentConstructed>(this); + // m_Registry.on_destroy().connect<&Scene::OnCircleCollider2DComponentDestroyed>(this); + // + // m_Registry.on_construct().connect<&Scene::OnCapsuleCollider2DComponentConstructed>(this); + // m_Registry.on_destroy().connect<&Scene::OnCapsuleCollider2DComponentDestroyed>(this); + // + // m_Registry.on_construct().connect<&Scene::OnTextComponentConstructed>(this); +} + +Scene::~Scene() +{ + // auto scriptableComponentView = m_Registry.view(); + // + // for (auto e : scriptableComponentView) { + // Entity entity(e, this); + // ScriptEngine::DestroyScriptInstance(entity); + // } + + m_Registry.clear(); + + // m_Registry.on_construct().disconnect<&Scene::OnScriptComponentConstructed>(this); + // m_Registry.on_destroy().disconnect<&Scene::OnScriptComponentDestroyed>(this); + // + // m_Registry.on_construct().disconnect<&Scene::OnRigidbody2DComponentConstructed>(this); + // m_Registry.on_destroy().disconnect<&Scene::OnRigidbody2DComponentDestroyed>(this); +} + +Entity Scene::CreateEntity(std::string const& name) +{ + return CreateEntityWithUUID(name, Terran::Core::UUID()); +} + +Entity Scene::CreateEntityWithUUID(std::string const& name, Terran::Core::UUID const& uuid) +{ + entt::entity e = m_Registry.create(); + + Entity entity(e, this); + entity.AddComponent(name.empty() ? "Entity" : name, uuid); + entity.AddComponent(); + + m_EntityMap[uuid] = e; + + SortEntities(); + return entity; +} + +Entity Scene::CreateEmptyEntity() +{ + entt::entity e = m_Registry.create(); + Entity entity(e, this); + return entity; +} + +void Scene::DestroyEntity(Entity entity, bool first) +{ + // ScriptEngine::DestroyScriptInstance(entity); + // + // if (entity.HasComponent()) + // Physics2D::DestroyPhysicsBody(entity); + + if (entity.HasComponent()) { + if (first) { + if (entity.HasParent()) + entity.GetParent().RemoveChild(entity, false); + } + + for (auto eID : entity.GetChildren()) + DestroyEntity(FindEntityWithUUID(eID), false); + } + + auto entityIt = m_EntityMap.find(entity.GetID()); + if (entityIt != m_EntityMap.end()) + m_EntityMap.erase(entityIt); + + m_Registry.destroy(entity); + + SortEntities(); +} + +void Scene::StartRuntime() +{ + if (m_IsPlaying) + return; + + m_IsPlaying = true; + + // Physics2D::CreatePhysicsWorld(Project::GetPhysicsSettings()); + // Physics2D::CratePhysicsBodies(this); + // + // auto scriptableComponentView = m_Registry.view(); + // + // for (auto e : scriptableComponentView) { + // Entity entity(e, this); + // ScriptEngine::OnStart(entity); + // } +} + +void Scene::StopRuntime() +{ + if (!m_IsPlaying) + return; + + m_IsPlaying = false; + // Physics2D::CleanUpPhysicsWorld(); +} + +void Scene::Update(Terran::Core::Time time) +{ + // TR_PROFILE_FUNCN("Scene::Update"); + // TR_PROFILE_FUNCTION(); + + UpdateTransformHierarchy(); + + // Physics2D::Update(time); + // + // auto scriptableComponentView = m_Registry.view(); + // for (auto e : scriptableComponentView) { + // Entity entity(e, this); + // ScriptEngine::OnUpdate(entity, time.GetDeltaTime()); +} + +// void Scene::UpdateEditor() +// { +// // TR_PROFILE_FUNCTION(); +// UpdateTransformHierarchy(); +// } + +// void Scene::OnResize(float width, float height) +// { +// if (m_ViewportWidth != width || m_ViewportHeight != height) { +// m_ViewportWidth = width; +// m_ViewportHeight = height; +// +// auto cameraView = m_Registry.view(); +// +// for (auto e : cameraView) { +// Entity entity(e, this); +// auto& cameraComponent = entity.GetComponent(); +// +// cameraComponent.Camera.SetViewport(width, height); +// } +// } +// } + +// void Scene::OnRender(Terran::Core::Shared const& sceneRenderer) +// { +// // TR_PROFILE_FUNCTION(); +// +// if (Entity primaryCamera = GetPrimaryCamera()) { +// glm::vec4 backgroundColor = primaryCamera.GetComponent().BackgroundColor; +// sceneRenderer->SetScene(this); +// sceneRenderer->SetClearColor(backgroundColor); +// +// Camera& camera = primaryCamera.GetComponent().Camera; +// glm::mat4& cameraTransform = primaryCamera.GetTransform().WorldSpaceTransformMatrix; +// +// sceneRenderer->BeginScene(camera, cameraTransform, true); +// +// // submit sprites +// { +// auto spriteRendererView = m_Registry.view(); +// for (auto e : spriteRendererView) { +// Entity entity(e, this); +// auto& spriteRenderer = entity.GetComponent(); +// auto& transform = entity.GetTransform(); +// +// sceneRenderer->SubmitSprite(spriteRenderer, transform.WorldSpaceTransformMatrix, (uint32_t)entity); +// } +// } +// +// // submit circles +// { +// auto circleRendererView = m_Registry.view(); +// for (auto e : circleRendererView) { +// Entity entity(e, this); +// auto& circleRenderer = entity.GetComponent(); +// auto& transform = entity.GetTransform(); +// +// sceneRenderer->SubmitCircle(circleRenderer, transform.WorldSpaceTransformMatrix, (uint32_t)(entity)); +// } +// } +// +// // submit text +// { +// auto textRendererView = m_Registry.view(); +// for (auto e : textRendererView) { +// Entity entity(e, this); +// auto& textRenderer = entity.GetComponent(); +// auto& transform = entity.GetTransform(); +// +// sceneRenderer->SubmitText(textRenderer, transform.WorldSpaceTransformMatrix, entity); +// } +// } +// +// // submit lines +// { +// auto lineRendererView = m_Registry.view(); +// +// for (auto e : lineRendererView) { +// Entity entity(e, this); +// auto& lineRenderer = entity.GetComponent(); +// +// sceneRenderer->SubmitLine(lineRenderer, (uint32_t)entity); +// } +// } +// +// sceneRenderer->EndScene(); +// } +// } + +// void Scene::OnRenderEditor(Terran::Core::Shared const& sceneRenderer, Camera& camera, glm::mat4& cameraView) +// { +// // TR_PROFILE_FUNCTION(); +// sceneRenderer->SetScene(this); +// sceneRenderer->BeginScene(camera, cameraView, false); +// +// sceneRenderer->GetFramebuffer()->SetColorAttachmentValue(1, -1); +// +// // submit sprites +// { +// auto spriteRendererView = m_Registry.view(); +// for (auto e : spriteRendererView) { +// Entity entity(e, this); +// auto& spriteRenderer = entity.GetComponent(); +// auto& transform = entity.GetTransform(); +// +// sceneRenderer->SubmitSprite(spriteRenderer, transform.WorldSpaceTransformMatrix, (uint32_t)entity); +// } +// } +// +// // submit circles +// { +// auto circleRendererView = m_Registry.view(); +// for (auto e : circleRendererView) { +// Entity entity(e, this); +// auto& circleRenderer = entity.GetComponent(); +// auto& transform = entity.GetTransform(); +// +// sceneRenderer->SubmitCircle(circleRenderer, transform.WorldSpaceTransformMatrix, (uint32_t)entity); +// } +// } +// +// // submit text +// { +// auto textRendererView = m_Registry.view(); +// for (auto e : textRendererView) { +// Entity entity(e, this); +// auto& textRenderer = entity.GetComponent(); +// auto& transform = entity.GetTransform(); +// +// sceneRenderer->SubmitText(textRenderer, transform.WorldSpaceTransformMatrix, (uint32_t)entity); +// } +// } +// +// // submit lines +// { +// auto lineRendererView = m_Registry.view(); +// for (auto e : lineRendererView) { +// Entity entity(e, this); +// auto& lineRenderer = entity.GetComponent(); +// +// sceneRenderer->SubmitLine(lineRenderer, (uint32_t)entity); +// } +// } +// +// sceneRenderer->EndScene(); +// } + +Entity Scene::FindEntityWithUUID(Terran::Core::UUID uuid) +{ + // TR_PROFILE_FUNCTION(); + if (m_EntityMap.contains(uuid)) + return Entity(m_EntityMap.at(uuid), this); + + return {}; +} + +Entity Scene::FindEntityWithName(std::string const& name) +{ + // TR_PROFILE_FUNCTION(); + auto const tagView = m_Registry.view(); + + for (auto e : tagView) { + Entity entity(e, this); + if (entity.GetName() == name) + return entity; + } + + return {}; +} + +Entity Scene::GetPrimaryCamera() +{ + // auto const cameraView = m_Registry.view(); + // for (auto e : cameraView) { + // Entity entity(e, this); + // auto& cameraComponent = entity.GetComponent(); + // + // if (cameraComponent.Primary) + // return entity; + // } + + return {}; +} + +Entity Scene::DuplicateEntity(Entity srcEntity, Entity parent) +{ + Entity dstEntity = CreateEntity(srcEntity.GetName() + " Copy"); + + CopyComponent(srcEntity, dstEntity, m_Registry); + // CopyComponent(srcEntity, dstEntity, m_Registry); + // CopyComponent(srcEntity, dstEntity, m_Registry); + // CopyComponent(srcEntity, dstEntity, m_Registry); + // CopyComponent(srcEntity, dstEntity, m_Registry); + // CopyComponent(srcEntity, dstEntity, m_Registry); + // CopyComponent(srcEntity, dstEntity, m_Registry); + // CopyComponent(srcEntity, dstEntity, m_Registry); + // CopyComponent(srcEntity, dstEntity, m_Registry); + // CopyComponent(srcEntity, dstEntity, m_Registry); + + if (srcEntity.HasComponent()) { + for (int i = 0; i < srcEntity.GetChildCount(); i++) { + Entity childEntity = srcEntity.GetChild(i); + DuplicateEntity(childEntity, dstEntity); + } + + if (!parent) + parent = srcEntity.GetParent(); + + if (parent) + dstEntity.SetParent(parent); + } + + return dstEntity; +} + +Entity Scene::DuplicateEntity(Entity srcEntity) +{ + return DuplicateEntity(srcEntity, {}); +} + +Terran::Core::Shared Scene::CopyScene(Terran::Core::Shared const& srcScene) +{ + Terran::Core::Shared scene = SceneManager::CreateEmptyScene(); + + auto tagView = srcScene->GetEntitiesWith(); + + for (auto e : tagView) { + Entity srcEntity(e, srcScene.get()); + Entity dstEntity = scene->CreateEntityWithUUID(srcEntity.GetName(), srcEntity.GetID()); + } + + for (auto e : tagView) { + Entity srcEntity(e, srcScene.get()); + Entity dstEntity = scene->FindEntityWithUUID(srcEntity.GetID()); + + CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); + // CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); + // CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); + // CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); + // CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); + // CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); + // CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); + // CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); + // CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); + // CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); + // CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); + } + + scene->SortEntities(); + + return scene; +} + +void Scene::UpdateTransformHierarchy() +{ + // TR_PROFILE_FUNCN("Scene::UpdateTransformHierarchy"); + // TR_PROFILE_FUNCTION(); + + auto transformView = GetEntitiesWith(); + + for (auto e : transformView) { + Entity entity(e, this); + auto& transform = entity.GetTransform(); + + if (!entity.HasParent()) + UpdateEntityTransform(entity); + } +} + +void Scene::UpdateEntityTransform(Entity entity) +{ + TransformComponent& tc = entity.GetComponent(); + + if (tc.IsDirty) { + if (Entity parent = entity.GetParent()) { + glm::mat4 parentTransform = parent.GetTransform().WorldSpaceTransformMatrix; + tc.WorldSpaceTransformMatrix = parentTransform * CalculateTransformMatrix(tc); + tc.LocalSpaceTransformMatrix = glm::inverse(parentTransform) * tc.WorldSpaceTransformMatrix; + } else { + tc.WorldSpaceTransformMatrix = CalculateTransformMatrix(tc); + tc.LocalSpaceTransformMatrix = tc.WorldSpaceTransformMatrix; + } + + glm::quat rotation = tc.Rotation; + + tc.Forward = glm::normalize(glm::rotate(rotation, glm::vec3(0.0f, 0.0f, 1.0f))); + tc.Up = glm::normalize(glm::rotate(rotation, glm::vec3(0.0f, 1.0f, 0.0f))); + tc.Right = glm::normalize(glm::rotate(rotation, glm::vec3(1.0f, 0.0f, 0.0f))); + } + + for (size_t i = 0; i < entity.GetChildCount(); i++) { + Entity currEntity = entity.GetChild(static_cast(i)); + + if (tc.IsDirty) + currEntity.GetTransform().IsDirty = true; + + UpdateEntityTransform(currEntity); + } + + tc.IsDirty = false; +} + +void Scene::ConvertToLocalSpace(Entity entity) +{ + auto& tc = entity.GetComponent(); + + if (!entity.HasParent()) + return; + + if (tc.IsDirty) + UpdateEntityTransform(entity); + + Entity parent = entity.GetParent(); + auto& parentTransform = parent.GetTransform(); + + // NOTE: have to calculate it because at this point the local space + // and world space transform matrices are equal + glm::mat4 parentWorldMatrix = parentTransform.WorldSpaceTransformMatrix; + glm::mat4 localMat = glm::inverse(parentWorldMatrix) * tc.WorldSpaceTransformMatrix; + + Core::Math::decompose_transform_matrix(localMat, tc.Position, tc.Rotation, tc.Scale); + + tc.IsDirty = true; +} + +void Scene::ConvertToWorldSpace(Entity entity) +{ + auto& tc = entity.GetComponent(); + + if (!entity.HasParent()) + return; + + if (tc.IsDirty) + UpdateEntityTransform(entity); + + Core::Math::decompose_transform_matrix(tc.WorldSpaceTransformMatrix, tc.Position, tc.Rotation, tc.Scale); + + tc.IsDirty = true; +} + +void Scene::SortEntities() +{ + m_Registry.sort([](entt::entity const& lEntity, entt::entity const& rEntity) { return lEntity < rEntity; }); +} + +// void Scene::OnScriptComponentConstructed(entt::registry& registry, entt::entity entityHandle) +// { +// Entity entity(entityHandle, this); +// ScriptEngine::CreateScriptInstance(entity); +// +// if (m_IsPlaying) +// ScriptEngine::OnStart(entity); +// } +// +// void Scene::OnScriptComponentDestroyed(entt::registry& registry, entt::entity entityHandle) +// { +// Entity entity(entityHandle, this); +// ScriptEngine::DestroyScriptInstance(entity); +// } +// +// void Scene::OnRigidbody2DComponentConstructed(entt::registry& registry, entt::entity entityHandle) +// { +// if (m_IsPlaying) { +// Entity entity(entityHandle, this); +// Terran::Core::Shared physicsBody = Physics2D::CreatePhysicsBody(entity); +// physicsBody->AttachColliders(); +// } +// } +// +// void Scene::OnRigidbody2DComponentDestroyed(entt::registry& registry, entt::entity entityHandle) +// { +// if (m_IsPlaying) { +// Entity entity(entityHandle, this); +// Physics2D::DestroyPhysicsBody(entity); +// } +// } +// +// void Scene::OnBoxCollider2DComponentConstructed(entt::registry& registry, entt::entity entityHandle) +// { +// Entity entity(entityHandle, this); +// if (!entity.HasComponent()) +// entity.AddComponent(); +// +// if (!m_IsPlaying) +// return; +// +// if (Terran::Core::Shared physicsBody = Physics2D::GetPhysicsBody(entity)) +// physicsBody->AddCollider(entity); +// } +// void Scene::OnBoxCollider2DComponentDestroyed(entt::registry& registry, entt::entity entityHandle) +// { +// if (m_IsPlaying) { +// Entity entity(entityHandle, this); +// Terran::Core::Shared physicsBody = Physics2D::GetPhysicsBody(entity); +// auto& bcComponent = entity.GetComponent(); +// +// if (physicsBody) +// physicsBody->RemoveCollider(bcComponent.ColliderIndex); +// } +// } +// +// void Scene::OnCircleCollider2DComponentConstructed(entt::registry& registry, entt::entity entityHandle) +// { +// Entity entity(entityHandle, this); +// if (!entity.HasComponent()) +// entity.AddComponent(); +// +// if (!m_IsPlaying) +// return; +// +// if (Terran::Core::Shared physicsBody = Physics2D::GetPhysicsBody(entity)) +// physicsBody->AddCollider(entity); +// } +// void Scene::OnCircleCollider2DComponentDestroyed(entt::registry& registry, entt::entity entityHandle) +// { +// if (m_IsPlaying) { +// Entity entity(entityHandle, this); +// auto& ccComponent = entity.GetComponent(); +// +// if (Terran::Core::Shared physicsBody = Physics2D::GetPhysicsBody(entity)) +// physicsBody->RemoveCollider(ccComponent.ColliderIndex); +// } +// } +// +// void Scene::OnCapsuleCollider2DComponentConstructed(entt::registry& registry, entt::entity entityHandle) +// { +// Entity entity(entityHandle, this); +// if (!entity.HasComponent()) +// entity.AddComponent(); +// +// if (!m_IsPlaying) +// return; +// +// if (Terran::Core::Shared physicsBody = Physics2D::GetPhysicsBody(entity)) +// physicsBody->AddCollider(entity); +// } +// void Scene::OnCapsuleCollider2DComponentDestroyed(entt::registry& registry, entt::entity entityHandle) +// { +// if (m_IsPlaying) { +// Entity entity(entityHandle, this); +// Terran::Core::Shared physicsBody = Physics2D::GetPhysicsBody(entity); +// auto& ccComponent = entity.GetComponent(); +// +// if (physicsBody) +// physicsBody->RemoveCollider(ccComponent.ColliderIndex); +// } +// } +// +// void Scene::OnTextComponentConstructed(entt::registry& registry, entt::entity entityHandle) +// { +// Entity entity(entityHandle, this); +// auto& trc = entity.GetComponent(); +// if (!trc.FontAtlas) +// trc.FontAtlas = Font::DefaultFont; +// } +// +// void Scene::OnTextComponentDestroyed(entt::registry& registry, entt::entity entityHandle) +// { +// } +} diff --git a/TerranEngine/src/Scene/Scene.h b/Libraries/LibScene/Scene.h similarity index 87% rename from TerranEngine/src/Scene/Scene.h rename to Libraries/LibScene/Scene.h index cafe6996..f60c11ad 100644 --- a/TerranEngine/src/Scene/Scene.h +++ b/Libraries/LibScene/Scene.h @@ -1,35 +1,35 @@ #pragma once -#include "LibCore/Base.h" -#include "LibCore/Time.h" -#include "LibCore/UUID.h" +#include +#include +#include -#include "Asset/Asset.h" +#include -#include "Graphics/Camera.h" +#include "Entity.h" + +// #include "Graphics/Camera.h" #pragma warning(push) #pragma warning(disable : 26439) #include #pragma warning(pop) +#include #include #include #include #include -namespace TerranEngine { - -class Entity; -class SceneRenderer; +namespace Terran::World { -class Scene final : public Asset { +class Scene final : public Asset::Asset { public: Scene(); Scene(Terran::Core::UUID const& handle); ~Scene() override; - ASSET_CLASS_TYPE(Scene) + TR_DECLARE_ASSET_TYPE(Scene) Entity CreateEntity(std::string const& name = std::string()); Entity CreateEntityWithUUID(std::string const& name, Terran::Core::UUID const& uuid); @@ -41,11 +41,11 @@ class Scene final : public Asset { void StopRuntime(); void Update(Terran::Core::Time time); - void UpdateEditor(); - void OnResize(float width, float height); + // void UpdateEditor(); + // void OnResize(float width, float height); - void OnRender(Terran::Core::Shared const& sceneRenderer); - void OnRenderEditor(Terran::Core::Shared const& sceneRenderer, Camera& camera, glm::mat4& cameraView); + // void OnRender(Terran::Core::Shared const& sceneRenderer); + // void OnRenderEditor(Terran::Core::Shared const& sceneRenderer, Camera& camera, glm::mat4& cameraView); Entity FindEntityWithUUID(Terran::Core::UUID uuid); Entity FindEntityWithName(std::string const& name); @@ -126,10 +126,10 @@ class Scene final : public Asset { glm::vec2 m_ViewportPosition = { 0.0f, 0.0f }; float m_ViewportWidth = 1080, m_ViewportHeight = 720; - friend class SceneRenderer; friend class Entity; friend class SceneSerializer; friend class SceneAssetLoader; }; } +#include "Entity.inl" diff --git a/TerranEngine/src/Scene/SceneManager.cpp b/Libraries/LibScene/SceneManager.cpp similarity index 70% rename from TerranEngine/src/Scene/SceneManager.cpp rename to Libraries/LibScene/SceneManager.cpp index d61d161d..3c37083e 100644 --- a/TerranEngine/src/Scene/SceneManager.cpp +++ b/Libraries/LibScene/SceneManager.cpp @@ -1,11 +1,8 @@ -#include "trpch.h" #include "SceneManager.h" -#include "Core/Application.h" +// #include "Events/SceneEvent.h" -#include "Events/SceneEvent.h" - -namespace TerranEngine { +namespace Terran::World { Terran::Core::Shared SceneManager::s_CurrentScene; std::unordered_map> SceneManager::s_ActiveScenes; @@ -14,7 +11,7 @@ Terran::Core::Shared SceneManager::CreateEmptyScene() { // TODO: create memory asset??? Terran::Core::Shared scene = Terran::Core::CreateShared(); - s_ActiveScenes[scene->GetHandle()] = scene; + s_ActiveScenes[scene->handle()] = scene; return scene; } @@ -23,7 +20,7 @@ void SceneManager::RemoveScene(const Terran::Core::UUID& id) if (s_ActiveScenes.contains(id)) s_ActiveScenes.erase(id); - if (s_CurrentScene->GetHandle() == id) + if (s_CurrentScene->handle() == id) s_CurrentScene = nullptr; } @@ -39,13 +36,15 @@ void SceneManager::SetCurrentScene(Terran::Core::Shared newScene) { Terran::Core::UUID id({ 0 }); if (s_CurrentScene) - id = s_CurrentScene->GetHandle(); + id = s_CurrentScene->handle(); - SceneTransitionEvent sceneTransitionEvent(s_CurrentScene, newScene); - Application::Get()->DispatchEvent(sceneTransitionEvent); + // SceneTransitionEvent sceneTransitionEvent(s_CurrentScene, newScene); + + // TODO: dispatch events properly once this becomes a layer + // Application::Get()->DispatchEvent(sceneTransitionEvent); s_CurrentScene = newScene; - s_ActiveScenes[newScene->GetHandle()] = newScene; + s_ActiveScenes[newScene->handle()] = newScene; if (!id) return; diff --git a/TerranEngine/src/Scene/SceneManager.h b/Libraries/LibScene/SceneManager.h similarity index 96% rename from TerranEngine/src/Scene/SceneManager.h rename to Libraries/LibScene/SceneManager.h index d7e391ea..8b14cd88 100644 --- a/TerranEngine/src/Scene/SceneManager.h +++ b/Libraries/LibScene/SceneManager.h @@ -6,7 +6,7 @@ #include -namespace TerranEngine { +namespace Terran::World { class SceneManager final { public: diff --git a/Libraries/LibScene/SceneSerializer.cpp b/Libraries/LibScene/SceneSerializer.cpp new file mode 100644 index 00000000..502baefc --- /dev/null +++ b/Libraries/LibScene/SceneSerializer.cpp @@ -0,0 +1,583 @@ +#include "SceneSerializer.h" +#include "Entity.h" +#include "Components.h" + +// #include "Scripting/ScriptEngine.h" + +#include +#include +#include +#include +#include + +#include + +#include +#define GLM_ENABLE_EXPERIMENTAL +#include + +#include +#include + +using namespace Terran::Core; +namespace Terran::World { + +char const* SceneSerializer::SceneFilter = "Terran Scene\0*.terran\0"; +static char const* SerializerVersion = "yml1.0"; + +SceneSerializer::SceneSerializer(Core::Shared const& scene) + : m_Scene(scene) +{ +} + +#define WRITE_SCRIPT_FIELD(FieldType, Type) \ + case ScriptFieldType::FieldType: \ + out << YAML::Key << field.Name << YAML::Value << scriptInstance->GetFieldValue(fieldID); \ + break + +#define WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(FieldType, Type) \ + case ScriptFieldType::FieldType: { \ + Type value = scriptInstance->GetFieldArrayValue(array, i); \ + out << value; \ + } break + +// TODO: n dimensional arrays maybe someday in the future? +// static void SerializeScriptArray(YAML::Emitter& out, Shared scriptInstance, const ScriptArray& array, const ScriptField& field, int32_t* indices, int dimension = 0) +//{ +// int32_t length = scriptInstance->GetFieldArrayLength(array, dimension); +// if (dimension == array.Rank - 1) +// { +// out << YAML::BeginSeq; +// for (int32_t i = 0; i < length; i++) +// { +// indices[dimension] = i; +// switch (field.Type) +// { +// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Bool, bool); +// // NOTE: maybe wchar_t? +// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Char, char); +// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Int8, int8_t); +// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Int16, int16_t); +// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Int32, int32_t); +// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Int64, int64_t); +// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(UInt8, std::byte); +// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(UInt16, uint16_t); +// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(UInt32, uint32_t); +// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(UInt64, uint64_t); +// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Float, float); +// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Double, double); +// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(String, std::string); +// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Vector2, glm::vec2); +// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Vector3, glm::vec3); +// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Color, glm::vec4); +// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Entity, UUID); +// default: TR_ASSERT(false, "Unsupported field type"); break; +// } +// } +// out << YAML::EndSeq; +// } +// else +// { +// out << YAML::BeginSeq; +// for (int32_t i = 0; i < length; i++) +// { +// indices[dimension] = i; +// SerializeScriptArray(out, scriptInstance, array, field, indices, dimension + 1); +// } +// out << YAML::EndSeq; +// } +//} + +// static void SerializeScriptFields(YAML::Emitter& out, Entity entity) +// { +// ScriptComponent& sc = entity.GetComponent(); +// Terran::Core::Shared scriptInstance = ScriptEngine::GetScriptInstance(entity); +// +// for (auto& fieldID : sc.FieldHandles) { +// ScriptField field = scriptInstance->GetScriptField(fieldID); +// +// if (field.IsArray) { +// ScriptArray array = scriptInstance->GetScriptArray(fieldID); +// if (array.Rank > 1) +// continue; +// +// out << YAML::BeginMap; +// out << YAML::Key << field.Name << YAML::Value << YAML::Flow; +// out << YAML::BeginSeq; +// +// int32_t length = scriptInstance->GetFieldArrayLength(array, 0); +// for (int32_t i = 0; i < length; i++) { +// switch (field.Type) { +// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Bool, bool); +// // NOTE: maybe wchar_t? +// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Char, char); +// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Int8, int8_t); +// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Int16, int16_t); +// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Int32, int32_t); +// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Int64, int64_t); +// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(UInt8, std::byte); +// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(UInt16, uint16_t); +// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(UInt32, uint32_t); +// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(UInt64, uint64_t); +// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Float, float); +// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Double, double); +// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(String, std::string); +// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Vector2, glm::vec2); +// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Vector3, glm::vec3); +// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Color, glm::vec4); +// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Entity, Terran::Core::UUID); +// default: +// TR_ASSERT(false, "Unsupported field type"); +// break; +// } +// } +// out << YAML::EndSeq; +// out << YAML::EndMap; +// } else { +// out << YAML::BeginMap; +// switch (field.Type) { +// WRITE_SCRIPT_FIELD(Bool, bool); +// // TODO: wchar +// WRITE_SCRIPT_FIELD(Char, char); +// WRITE_SCRIPT_FIELD(Int8, int8_t); +// WRITE_SCRIPT_FIELD(Int16, int16_t); +// WRITE_SCRIPT_FIELD(Int32, int32_t); +// WRITE_SCRIPT_FIELD(Int64, int64_t); +// WRITE_SCRIPT_FIELD(UInt8, std::byte); +// WRITE_SCRIPT_FIELD(UInt16, uint16_t); +// WRITE_SCRIPT_FIELD(UInt32, uint32_t); +// WRITE_SCRIPT_FIELD(UInt64, uint64_t); +// WRITE_SCRIPT_FIELD(Float, float); +// WRITE_SCRIPT_FIELD(Double, double); +// WRITE_SCRIPT_FIELD(String, std::string); +// WRITE_SCRIPT_FIELD(Vector2, glm::vec2); +// WRITE_SCRIPT_FIELD(Vector3, glm::vec3); +// WRITE_SCRIPT_FIELD(Color, glm::vec4); +// WRITE_SCRIPT_FIELD(Entity, Terran::Core::UUID); +// default: +// TR_ASSERT(false, "Unsupported field type"); +// break; +// } +// out << YAML::EndMap; +// } +// } +// } + +#define BEGIN_COMPONENT_MAP(componentKey) \ + out << YAML::Key << componentKey; \ + out << YAML::BeginMap + +#define END_COMPONENT_MAP() \ + out << YAML::EndMap + +#define WRITE_COMPONENT_PROPERY(propertyName, property) \ + out << YAML::Key << propertyName << YAML::Value << property + +static void SerializeEntity(YAML::Emitter& out, Entity entity) +{ + // TODO: convert to a verify assert + TR_ASSERT(entity.HasComponent(), "Can't serialize an entity that doesn't have a tag component"); + out << YAML::BeginMap; + + WRITE_COMPONENT_PROPERY("Entity", entity.GetID()); + + BEGIN_COMPONENT_MAP("TagComponent"); + auto& tagComponent = entity.GetComponent(); + WRITE_COMPONENT_PROPERY("Tag", tagComponent.Name); + END_COMPONENT_MAP(); + + if (entity.HasComponent()) { + auto& transformComponent = entity.GetTransform(); + + BEGIN_COMPONENT_MAP("TransformComponent"); + WRITE_COMPONENT_PROPERY("Position", transformComponent.Position); + WRITE_COMPONENT_PROPERY("Scale", transformComponent.Scale); + WRITE_COMPONENT_PROPERY("Rotation", transformComponent.Rotation); + END_COMPONENT_MAP(); + } + + // if (entity.HasComponent()) { + // auto& cameraComponent = entity.GetComponent(); + // + // BEGIN_COMPONENT_MAP("CameraComponent"); + // WRITE_COMPONENT_PROPERY("Camera", YAML::BeginMap); + // WRITE_COMPONENT_PROPERY("Size", cameraComponent.Camera.GetOrthographicSize()); + // WRITE_COMPONENT_PROPERY("Near", cameraComponent.Camera.GetOrthographicNear()); + // WRITE_COMPONENT_PROPERY("Far", cameraComponent.Camera.GetOrthographicFar()); + // out << YAML::EndMap; + // + // WRITE_COMPONENT_PROPERY("Primary", cameraComponent.Primary); + // WRITE_COMPONENT_PROPERY("ClearColor", cameraComponent.BackgroundColor); + // END_COMPONENT_MAP(); + // } + // + // if (entity.HasComponent()) { + // auto& spriteRendererComponent = entity.GetComponent(); + // + // BEGIN_COMPONENT_MAP("SpriteRendererComponent"); + // WRITE_COMPONENT_PROPERY("Color", spriteRendererComponent.Color); + // WRITE_COMPONENT_PROPERY("Texture", spriteRendererComponent.TextureHandle); + // END_COMPONENT_MAP(); + // } + // + // if (entity.HasComponent()) { + // auto& circleRendererComponent = entity.GetComponent(); + // + // BEGIN_COMPONENT_MAP("CircleRendererComponent"); + // WRITE_COMPONENT_PROPERY("Color", circleRendererComponent.Color); + // WRITE_COMPONENT_PROPERY("Thickness", circleRendererComponent.Thickness); + // END_COMPONENT_MAP(); + // } + // + // if (entity.HasComponent()) { + // auto& textRendererComponent = entity.GetComponent(); + // + // BEGIN_COMPONENT_MAP("TextRendererCompoent"); + // WRITE_COMPONENT_PROPERY("Color", textRendererComponent.TextColor); + // WRITE_COMPONENT_PROPERY("Text", textRendererComponent.Text); + // // TODO: save font handle + // END_COMPONENT_MAP(); + // } + + if (entity.HasComponent()) { + auto& relationshipComponent = entity.GetComponent(); + + BEGIN_COMPONENT_MAP("RelationshipComponent"); + WRITE_COMPONENT_PROPERY("Children", YAML::BeginSeq); + for (auto child : relationshipComponent.Children) + out << child; + out << YAML::EndSeq; + WRITE_COMPONENT_PROPERY("Parent", relationshipComponent.Parent); + END_COMPONENT_MAP(); + } + + // if (entity.HasComponent()) { + // auto& scriptComponent = entity.GetComponent(); + // + // BEGIN_COMPONENT_MAP("ScriptComponent"); + // WRITE_COMPONENT_PROPERY("ModuleName", scriptComponent.ModuleName); + // if (!scriptComponent.FieldHandles.empty()) { + // out << YAML::Key << "Fields" << YAML::Value; + // out << YAML::BeginSeq; + // SerializeScriptFields(out, entity); + // out << YAML::EndSeq; + // } + // END_COMPONENT_MAP(); + // } + // + // if (entity.HasComponent()) { + // auto& rigidbodyComponent = entity.GetComponent(); + // + // BEGIN_COMPONENT_MAP("Rigidbody2DComponent"); + // WRITE_COMPONENT_PROPERY("BodyType", PhysicsBodyTypeToString(rigidbodyComponent.BodyType).data()); + // WRITE_COMPONENT_PROPERY("FixedRotation", rigidbodyComponent.FixedRotation); + // WRITE_COMPONENT_PROPERY("SleepState", PhysicsBodySleepStateToString(rigidbodyComponent.SleepState).data()); + // WRITE_COMPONENT_PROPERY("GravityScale", rigidbodyComponent.GravityScale); + // WRITE_COMPONENT_PROPERY("Material", rigidbodyComponent.PhysicsMaterialHandle); + // END_COMPONENT_MAP(); + // } + // + // if (entity.HasComponent()) { + // auto& boxCollider2DComponent = entity.GetComponent(); + // + // BEGIN_COMPONENT_MAP("BoxCollider2DComponent"); + // WRITE_COMPONENT_PROPERY("Offset", boxCollider2DComponent.Offset); + // WRITE_COMPONENT_PROPERY("Size", boxCollider2DComponent.Size); + // WRITE_COMPONENT_PROPERY("Sensor", boxCollider2DComponent.Sensor); + // END_COMPONENT_MAP(); + // } + // + // if (entity.HasComponent()) { + // auto& circleCollider2DComponent = entity.GetComponent(); + // + // BEGIN_COMPONENT_MAP("CircleCollider2DComponent"); + // WRITE_COMPONENT_PROPERY("Offset", circleCollider2DComponent.Offset); + // WRITE_COMPONENT_PROPERY("Radius", circleCollider2DComponent.Radius); + // WRITE_COMPONENT_PROPERY("Sensor", circleCollider2DComponent.Sensor); + // END_COMPONENT_MAP(); + // } + // + // if (entity.HasComponent()) { + // auto& capsuleColliderComponenet = entity.GetComponent(); + // + // BEGIN_COMPONENT_MAP("CapsuleCollider2DComponent"); + // WRITE_COMPONENT_PROPERY("Offset", capsuleColliderComponenet.Offset); + // WRITE_COMPONENT_PROPERY("Size", capsuleColliderComponenet.Size); + // WRITE_COMPONENT_PROPERY("Sensor", capsuleColliderComponenet.Sensor); + // END_COMPONENT_MAP(); + // } + + out << YAML::EndMap; +} + +void SceneSerializer::SerializeEditor(std::filesystem::path const& scenePath) +{ + YAML::Emitter out; + + out << YAML::BeginMap; + + out << YAML::Key << "SerializerVersion" << YAML::Value << SerializerVersion; + out << YAML::Key << "Entities" << YAML::Value << YAML::BeginSeq; + + auto const tagComponentView = m_Scene->GetEntitiesWith(); + for (auto e : tagComponentView) { + Entity entity(e, m_Scene.get()); + SerializeEntity(out, entity); + } + + out << YAML::EndSeq; + out << YAML::EndMap; + + std::ofstream ofs(scenePath); + ofs << out.c_str(); +} + +#define READ_SCRIPT_FIELD(FieldType, Type) \ + case ScriptFieldType::FieldType: { \ + Type value = scriptFieldNode.as(); \ + scriptInstance->SetFieldValue(fieldID, value); \ + } break + +#define READ_SCRIPT_FIELD_ARRAY_ELEMENT(FieldType, Type) \ + case ScriptFieldType::FieldType: { \ + Type value = scriptFieldNode[i].as(); \ + scriptInstance->SetFieldArrayValue(array, value, i); \ + } break + +// static void DeserializeScriptFields(Terran::Core::Shared scriptInstance, ScriptComponent& scriptComponent, const YAML::Node& scriptFieldsNode) +// { +// for (auto const& fieldID : scriptComponent.FieldHandles) { +// ScriptField scriptField = scriptInstance->GetScriptField(fieldID); +// YAML::Node scriptFieldNode; +// bool valid = false; +// +// for (auto field : scriptFieldsNode) { +// if (field[scriptField.Name]) { +// scriptFieldNode = field[scriptField.Name]; +// valid = true; +// break; +// } +// } +// +// if (!valid) +// continue; +// +// if (scriptField.IsArray) { +// ScriptArray array = scriptInstance->GetScriptArray(fieldID); +// if (array.Rank > 1) +// continue; +// +// // TODO: n dimensional arrays someday in the future? +// int32_t length = scriptInstance->GetFieldArrayLength(array); +// for (int32_t i = 0; i < length && i < scriptFieldNode.size(); i++) { +// switch (scriptField.Type) { +// READ_SCRIPT_FIELD_ARRAY_ELEMENT(Bool, bool); +// READ_SCRIPT_FIELD_ARRAY_ELEMENT(Char, char); +// READ_SCRIPT_FIELD_ARRAY_ELEMENT(Int64, int64_t); +// READ_SCRIPT_FIELD_ARRAY_ELEMENT(Int32, int32_t); +// READ_SCRIPT_FIELD_ARRAY_ELEMENT(Int16, int16_t); +// READ_SCRIPT_FIELD_ARRAY_ELEMENT(Int8, int8_t); +// READ_SCRIPT_FIELD_ARRAY_ELEMENT(UInt64, uint64_t); +// READ_SCRIPT_FIELD_ARRAY_ELEMENT(UInt32, uint32_t); +// READ_SCRIPT_FIELD_ARRAY_ELEMENT(UInt16, uint16_t); +// READ_SCRIPT_FIELD_ARRAY_ELEMENT(UInt8, uint8_t); +// READ_SCRIPT_FIELD_ARRAY_ELEMENT(Float, float); +// READ_SCRIPT_FIELD_ARRAY_ELEMENT(Double, double); +// READ_SCRIPT_FIELD_ARRAY_ELEMENT(String, std::string); +// READ_SCRIPT_FIELD_ARRAY_ELEMENT(Vector2, glm::vec2); +// READ_SCRIPT_FIELD_ARRAY_ELEMENT(Vector3, glm::vec3); +// READ_SCRIPT_FIELD_ARRAY_ELEMENT(Color, glm::vec4); +// READ_SCRIPT_FIELD_ARRAY_ELEMENT(Entity, Terran::Core::UUID); +// default: +// TR_ASSERT(false, "Invalid script type"); +// } +// } +// } else { +// switch (scriptField.Type) { +// READ_SCRIPT_FIELD(Bool, bool); +// READ_SCRIPT_FIELD(Char, char); +// READ_SCRIPT_FIELD(Int64, int64_t); +// READ_SCRIPT_FIELD(Int32, int32_t); +// READ_SCRIPT_FIELD(Int16, int16_t); +// READ_SCRIPT_FIELD(Int8, int8_t); +// READ_SCRIPT_FIELD(UInt64, uint64_t); +// READ_SCRIPT_FIELD(UInt32, uint32_t); +// READ_SCRIPT_FIELD(UInt16, uint16_t); +// READ_SCRIPT_FIELD(UInt8, uint8_t); +// READ_SCRIPT_FIELD(Float, float); +// READ_SCRIPT_FIELD(Double, double); +// READ_SCRIPT_FIELD(String, std::string); +// READ_SCRIPT_FIELD(Vector2, glm::vec2); +// READ_SCRIPT_FIELD(Vector3, glm::vec3); +// READ_SCRIPT_FIELD(Color, glm::vec4); +// READ_SCRIPT_FIELD(Entity, Terran::Core::UUID); +// default: +// TR_ASSERT(false, "Invalid script type"); +// } +// } +// } +// } + +static YAML::Node FindEntity(YAML::Node scene, Core::UUID const& entityID) +{ + for (auto entity : scene) { + TR_CORE_TRACE(TR_LOG_CORE, entity); + Core::UUID id = entity["Entity"].as(); + if (id == entityID) + return entity; + } + + return {}; +} + +static Entity DeserializeEntity(YAML::Node data, YAML::Node scene, Core::Shared deserializedScene) +{ + try { + Core::UUID id = data["Entity"].as(); + if (!id) { + TR_ASSERT(false, "Invalid id"); + return {}; + } + + Entity entity = deserializedScene->FindEntityWithUUID(id); + if (entity) + return entity; + + auto tagComponent = data["TagComponent"]; + if (!tagComponent) { + TR_ASSERT(false, "Invalid tag component"); + return {}; + } + + std::string name = tagComponent["Tag"].as(); + Entity deserializedEntity = deserializedScene->CreateEntityWithUUID(name, id); + + auto transformComponent = data["TransformComponent"]; + if (transformComponent) { + auto& tc = deserializedEntity.GetTransform(); + tc.Position = transformComponent["Position"].as(glm::vec3(0.0f, 0.0f, 0.0f)); + tc.Rotation = transformComponent["Rotation"].as(glm::vec3(0.0f, 0.0f, 0.0f)); + tc.Scale = transformComponent["Scale"].as(glm::vec3(1.0f, 1.0f, 1.0f)); + } + + // auto cameraComponent = data["CameraComponent"]; + // if (cameraComponent) { + // auto& cc = deserializedEntity.AddComponent(); + // auto camera = cameraComponent["Camera"]; + // cc.Camera.SetOrthographicSize(camera["Size"].as(10.0f)); + // cc.Camera.SetOrthographicNear(camera["Near"].as(-10.0f)); + // cc.Camera.SetOrthographicFar(camera["Far"].as(10.0f)); + // + // cc.Primary = cameraComponent["Primary"].as(false); + // cc.BackgroundColor = cameraComponent["ClearColor"].as(glm::vec4(0.1f, 0.1f, 0.1f, 1.0f)); + // } + // + // auto spriteRendererComponent = data["SpriteRendererComponent"]; + // if (spriteRendererComponent) { + // auto& src = deserializedEntity.AddComponent(); + // src.Color = spriteRendererComponent["Color"].as(glm::vec4(1.0f, 1.0f, 1.0f, 1.0f)); + // src.TextureHandle = spriteRendererComponent["Texture"].as(Terran::Core::UUID::Invalid()); + // } + // + // auto circleRendererComponent = data["CircleRendererComponent"]; + // if (circleRendererComponent) { + // auto& crc = deserializedEntity.AddComponent(); + // crc.Color = circleRendererComponent["Color"].as(glm::vec4(1.0f, 1.0f, 1.0f, 1.0f)); + // crc.Thickness = circleRendererComponent["Thickness"].as(1.0f); + // } + + auto relationshipComponent = data["RelationshipComponent"]; + if (relationshipComponent) { + auto& rc = deserializedEntity.AddComponent(); + for (auto childID : relationshipComponent["Children"]) { + Core::UUID deserializedChildID = childID.as(); + Entity child = deserializedScene->FindEntityWithUUID(deserializedChildID); + if (!child) { + YAML::Node childNode = FindEntity(scene, deserializedChildID); + child = DeserializeEntity(childNode, scene, deserializedScene); + } + + if (child) + child.SetParent(deserializedEntity, true); + } + } + + // auto scriptComponent = data["ScriptComponent"]; + // if (scriptComponent) { + // auto& sc = deserializedEntity.AddComponent(); + // sc.ModuleName = scriptComponent["ModuleName"].as(); + // + // Terran::Core::Shared scriptInstance = ScriptEngine::CreateScriptInstance(deserializedEntity); + // auto scriptFields = scriptComponent["Fields"]; + // if (scriptFields) + // DeserializeScriptFields(scriptInstance, sc, scriptFields); + // } + // + // auto boxColliderComponent = data["BoxCollider2DComponent"]; + // if (boxColliderComponent) { + // auto& bcc = deserializedEntity.AddComponent(); + // bcc.Offset = boxColliderComponent["Offset"].as(glm::vec2(0.0f, 0.0f)); + // bcc.Size = boxColliderComponent["Size"].as(glm::vec2(1.0f, 1.0f)); + // bcc.Sensor = boxColliderComponent["Sensor"].as(false); + // } + // + // auto circleColliderComponent = data["CircleCollider2DComponent"]; + // if (circleRendererComponent) { + // auto& ccc = deserializedEntity.AddComponent(); + // ccc.Offset = circleColliderComponent["Offset"].as(glm::vec2(0.0f, 0.0f)); + // ccc.Radius = circleColliderComponent["Radius"].as(0.5f); + // ccc.Sensor = circleColliderComponent["Sensor"].as(false); + // } + // + // auto rigidbodyComponent = data["Rigidbody2DComponent"]; + // if (rigidbodyComponent) { + // auto& rbc = deserializedEntity.AddComponent(); + // rbc.BodyType = PhysicsBodyTypeFromString(rigidbodyComponent["BodyType"].as()); + // rbc.FixedRotation = rigidbodyComponent["FixedRotation"].as(false); + // rbc.SleepState = PhysicsBodySleepStateFromString(rigidbodyComponent["SleepState"].as()); + // rbc.GravityScale = rigidbodyComponent["GravityScale"].as(1.0f); + // + // if (rigidbodyComponent["Material"]) + // rbc.PhysicsMaterialHandle = rigidbodyComponent["Material"].as(Terran::Core::UUID::Invalid()); + // } + // + // auto capsuleColliderComponent = data["CapsuleCollider2DComponent"]; + // if (capsuleColliderComponent) { + // auto& ccc = deserializedEntity.AddComponent(); + // ccc.Offset = capsuleColliderComponent["Offset"].as(glm::vec2(0.0f, 0.0f)); + // ccc.Size = capsuleColliderComponent["Size"].as(glm::vec2(0.5f, 1.0f)); + // ccc.Sensor = capsuleColliderComponent["Sensor"].as(false); + // } + + return deserializedEntity; + } catch (YAML::InvalidNode const& ex) { + TR_CORE_ERROR(TR_LOG_ASSET, ex.what()); + return Entity(); + } +} + +Core::Result SceneSerializer::DeserializeEditor(std::filesystem::path const& scenePath) +{ + YAML::Node data; + try { + data = YAML::LoadFile(scenePath.string()); + } catch (YAML::ParserException const& ex) { + TR_CORE_ERROR(TR_LOG_ASSET, ex.what()); + return { SceneSerializationError::InvalidFormat }; + } catch (YAML::BadFile const& ex) { + TR_CORE_ERROR(TR_LOG_ASSET, ex.what()); + return { SceneSerializationError::NotFound }; + } + + auto entities = data["Entities"]; + if (entities) { + for (auto entity : entities) { + if (!DeserializeEntity(entity, entities, m_Scene)) + return { SceneSerializationError::InvalidFormat }; + } + } + + return {}; +} + +} diff --git a/TerranEngine/src/Scene/SceneSerializer.h b/Libraries/LibScene/SceneSerializer.h similarity index 55% rename from TerranEngine/src/Scene/SceneSerializer.h rename to Libraries/LibScene/SceneSerializer.h index 4a570052..d5df49df 100644 --- a/TerranEngine/src/Scene/SceneSerializer.h +++ b/Libraries/LibScene/SceneSerializer.h @@ -2,12 +2,18 @@ #include "Scene.h" -#include "Core/Result.h" -#include "LibCore/Base.h" +// #include "Core/Result.h" +#include +#include #include -namespace TerranEngine { +namespace Terran::World { + +enum class SceneSerializationError { + InvalidFormat, + NotFound, +}; class SceneSerializer final { public: @@ -15,7 +21,7 @@ class SceneSerializer final { SceneSerializer(Terran::Core::Shared const& scene); void SerializeEditor(std::filesystem::path const& scenePath); - Result DeserializeEditor(std::filesystem::path const& scenePath); + Core::Result DeserializeEditor(std::filesystem::path const& scenePath); public: static char const* SceneFilter; diff --git a/Libraries/LibScene/premake5.lua b/Libraries/LibScene/premake5.lua new file mode 100644 index 00000000..7829e517 --- /dev/null +++ b/Libraries/LibScene/premake5.lua @@ -0,0 +1,63 @@ +project "LibScene" +pic "On" +kind "StaticLib" +language "C++" +cppdialect "C++20" +staticruntime "Off" + +targetdir("%{prj.location}/bin/" .. outputdir) +objdir("%{prj.location}/bin-int/" .. outputdir) + +files { + "**.h", + "**.cpp", + + "vendor/glm/glm/**.hpp", + "vendor/glm/glm/**.inl", +} + +externalincludedirs { + "%{wks.location}/Libraries", + "%{Dependencies.spdlog.include}", + "%{Dependencies.glm.include}", + "%{Dependencies.yaml.include}", + "%{Dependencies.entt.include}", +} + +linkoptions { + "-IGNORE:4006", +} + +filter "system:macosx" +architecture "ARM64" + +libdirs { "/usr/local/lib" } +links { + "CoreFoundation.framework", -- no path needed for system frameworks +} + +filter "system:windows" +architecture "x86_64" +systemversion "latest" + +defines { + "_CRT_SECURE_NO_WARNINGS", +} + +includedirs { + "%{IncludeDirectories.optick}", +} + +links { + "OptickCore", +} + +filter "configurations:Debug" +defines "TR_DEBUG" +runtime "Debug" +symbols "on" + +filter "configurations:Release" +defines "TR_RELEASE" +runtime "Release" +optimize "on" diff --git a/TerranEngine/premake5.lua b/TerranEngine/premake5.lua index 70cae9ec..ccfb5e34 100644 --- a/TerranEngine/premake5.lua +++ b/TerranEngine/premake5.lua @@ -67,7 +67,7 @@ includedirs { "%{IncludeDirectories.imgui}", "%{IncludeDirectories.glad}", "%{IncludeDirectories.stb}", - "%{IncludeDirectories.entt}", + "%{Dependencies.entt.include}", "%{IncludeDirectories.msdfgen}", "%{IncludeDirectories.msdf_atlas_gen}", "%{IncludeDirectories.box2d}", diff --git a/TerranEngine/src/Scene/Components.h b/TerranEngine/src/Scene/Components.h deleted file mode 100644 index 6af04a55..00000000 --- a/TerranEngine/src/Scene/Components.h +++ /dev/null @@ -1,177 +0,0 @@ -#pragma once - -#include "LibCore/Base.h" -#include "LibCore/UUID.h" - -#include "Graphics/Font.h" -#include "Graphics/OrthographicCamera.h" - -#include "Physics/PhysicsStates.h" - -#include -#define GLM_ENABLE_EXPERIMENTAL -#include - -#include -#include -#include - -namespace TerranEngine { - -struct TagComponent { - TagComponent() = default; - - TagComponent(std::string const& name, Terran::Core::UUID const& id) - : Name(name) - , ID(id) - { - } - - TagComponent(std::string const& name) - : Name(name) - { - } - - std::string Name; - Terran::Core::UUID ID; -}; - -struct TransformComponent { - TransformComponent() = default; - - glm::vec3 Position = { 0.0f, 0.0f, 0.0f }; - glm::vec3 Rotation = { 0.0f, 0.0f, 0.0f }; - glm::vec3 Scale = { 1.0f, 1.0f, 1.0f }; - - glm::vec3 Forward = { 0.0f, 0.0f, 1.0f }; - glm::vec3 Up = { 0.0f, 1.0f, 0.0f }; - glm::vec3 Right = { 1.0f, 0.0f, 0.0f }; - - // dirty flag used for optimization - bool IsDirty = true; - - // cached transform matrices - glm::mat4 WorldSpaceTransformMatrix = glm::mat4(1.0f); - glm::mat4 LocalSpaceTransformMatrix = glm::mat4(1.0f); -}; - -struct CameraComponent { - CameraComponent() = default; - - OrthographicCamera Camera; - bool Primary = true; - - glm::vec4 BackgroundColor = { 0.1f, 0.1f, 0.1f, 1.0f }; -}; - -struct SpriteRendererComponent { - SpriteRendererComponent() = default; - - glm::vec4 Color = { 1.0f, 1.0f, 1.0f, 1.0f }; - Terran::Core::UUID TextureHandle = Terran::Core::UUID::invalid(); - - int ZIndex = 0; -}; - -struct CircleRendererComponent { - CircleRendererComponent() = default; - - glm::vec4 Color = { 1.0f, 1.0f, 1.0f, 1.0f }; - float Thickness = 1.0f; -}; - -// bullshit; fix -struct LineRendererComponent { - LineRendererComponent() = default; - - glm::vec4 Color = { 1.0f, 1.0f, 1.0f, 1.0f }; - float Thickness = 1.0f; - - glm::vec3 StartPoint = { 0.0f, 0.0f, 0.0f }; - glm::vec3 EndPoint = { 0.0f, 0.0f, 1.0f }; -}; - -struct TextRendererComponent { - TextRendererComponent() = default; - ~TextRendererComponent() = default; - - Terran::Core::Shared FontAtlas; - glm::vec4 TextColor = { 1.0f, 1.0f, 1.0f, 1.0f }; - std::string Text = ""; - float LineSpacing = 1.0f; - float LineWidth = 10.0f; -}; - -struct RelationshipComponent { - RelationshipComponent() - : Parent({ 0 }) - { - } - - Terran::Core::UUID Parent; - std::vector Children; -}; - -struct ScriptComponent { - ScriptComponent() = default; - - ScriptComponent(std::string const& moduleName) - : ModuleName(moduleName) - { - } - - // NOTE: think about having an array of scripts so that one entity - // "can" have more than one script (because of the 1 component of a type per entity) - - std::string ModuleName; - std::vector FieldHandles; - - Terran::Core::UUID ScriptSourceHandle = Terran::Core::UUID::invalid(); - bool ClassExists = true; -}; - -struct Rigidbody2DComponent { - Rigidbody2DComponent() = default; - - PhysicsBodyType BodyType = PhysicsBodyType::Dynamic; - PhysicsBodySleepState SleepState = PhysicsBodySleepState::Awake; - - bool FixedRotation = false; - float GravityScale = 1.0f; - bool Enabled = true; - int LayerIndex = 0; - - Terran::Core::UUID PhysicsMaterialHandle = Terran::Core::UUID::invalid(); -}; - -struct BoxCollider2DComponent { - BoxCollider2DComponent() = default; - - glm::vec2 Offset = { 0.0f, 0.0f }; - glm::vec2 Size = { 1.0f, 1.0f }; - bool Sensor = false; - - uint32_t ColliderIndex = 0; -}; - -struct CircleCollider2DComponent { - CircleCollider2DComponent() = default; - - glm::vec2 Offset = { 0.0f, 0.0f }; - float Radius = 0.5f; - bool Sensor = false; - - uint32_t ColliderIndex = 0; -}; - -struct CapsuleCollider2DComponent { - CapsuleCollider2DComponent() = default; - - glm::vec2 Offset = { 0.0f, 0.0f }; - glm::vec2 Size = { 0.5f, 1.0f }; - bool Sensor = false; - - uint32_t ColliderIndex = 0; -}; - -} diff --git a/TerranEngine/src/Scene/Entity.h b/TerranEngine/src/Scene/Entity.h deleted file mode 100644 index 564ac0d4..00000000 --- a/TerranEngine/src/Scene/Entity.h +++ /dev/null @@ -1,236 +0,0 @@ -#pragma once - -#include "Components.h" -#include "Scene.h" - -#include "LibCore/Assert.h" -#include "LibCore/UUID.h" - -#pragma warning(push) -#pragma warning(disable : 4834) - -#include -#include -#include -#include -#include - -namespace TerranEngine { - -class Entity final { -public: - Entity() = default; - - Entity(entt::entity const& handle, Scene* scene) - : m_Handle(handle) - , m_Scene(scene) - { - } - - ~Entity() = default; - - template - Component& AddComponent(Args&&... parameters) - { - TR_ASSERT(m_Handle != entt::null, "Ivalid entity"); - - TR_ASSERT(!HasComponent(), "Entity already has component"); - - Component& component = m_Scene->m_Registry.emplace(m_Handle, std::forward(parameters)...); - return component; - } - - template - Component& AddOrReplaceComponent(Args&&... parameters) - { - TR_ASSERT(m_Handle != entt::null, "Ivalid entity"); - Component& component = m_Scene->m_Registry.emplace_or_replace(m_Handle, std::forward(parameters)...); - return component; - } - - template - Component& GetComponent() const - { - TR_ASSERT(m_Handle != entt::null, "Ivalid entity"); - - TR_ASSERT(HasComponent(), "Entity doesn't have the component"); - return m_Scene->m_Registry.get(m_Handle); - } - - template - void RemoveComponent() - { - TR_ASSERT(m_Handle != entt::null, "Ivalid entity"); - - TR_ASSERT(HasComponent(), "Entity doesn't have component"); - - m_Scene->m_Registry.remove(m_Handle); - } - - template - Component& TryGetComponent() const - { - TR_ASSERT(m_Handle != entt::null, "Ivalid entity"); - - return m_Scene->m_Registry.try_get(m_Handle); - } - - template - bool HasComponent() const - { - TR_ASSERT(m_Handle != entt::null, "Ivalid entity"); - - return m_Scene->m_Registry.all_of(m_Handle); - } - - // visit all the components of an entity - // the signiture of Func should be void(const entt::type_info) - template - void Visit(Entity entity, Func func) const - { - m_Scene->m_Registry.visit(entity, std::forward(func)); - } - - // base stuffs - Terran::Core::UUID const& GetID() const { return GetComponent().ID; } - TransformComponent& GetTransform() const { return GetComponent(); } - bool Valid() const { return m_Scene->m_Registry.valid(m_Handle); } - std::string const& GetName() const { return GetComponent().Name; } - - // operators - operator entt::entity() const { return m_Handle; } - bool operator!=(Entity const& other) const { return !(*this == other); } - operator uint32_t() const { return static_cast(m_Handle); } - operator bool() const { return m_Handle != entt::null; } - bool operator==(Entity const& other) const { return m_Handle == other.m_Handle && m_Scene == other.m_Scene; } - - // relationship component stuffs - std::vector& GetChildren() const { return GetComponent().Children; } - size_t GetChildCount() const { return HasComponent() ? GetComponent().Children.size() : 0; } - Terran::Core::UUID GetParentID() const { return HasComponent() ? GetComponent().Parent : Terran::Core::UUID::invalid(); } - bool HasParent() const { return HasComponent() ? m_Scene->FindEntityWithUUID(GetComponent().Parent) : false; } - - Terran::Core::UUID const& GetSceneId() const { return m_Scene->GetHandle(); } - - Entity GetChild(uint32_t index) const - { - if (!HasComponent()) - return {}; - - return m_Scene->FindEntityWithUUID(GetChildren()[index]); - } - - void SetParentID(Terran::Core::UUID const& id) - { - if (!HasComponent()) - return; - - auto& relComp = GetComponent(); - relComp.Parent = id; - } - - Entity GetParent() const - { - if (!HasComponent()) - return {}; - - return m_Scene->FindEntityWithUUID(GetParentID()); - } - - bool IsChildOf(Entity entity) const - { - if (!HasComponent()) - return false; - - if (!entity.HasComponent()) - return false; - - return GetParentID() == entity.GetID(); - } - - void SetParent(Entity parent, bool forceTransformUpdate = false) - { - if (!HasComponent()) - AddComponent(); - - if (!parent.HasComponent()) - parent.AddComponent(); - - if (IsChildOf(parent)) - return; - if (parent.IsChildOf(*this)) - return; - - if (HasParent()) - Unparent(); - - auto& relComp = GetComponent(); - relComp.Parent = parent.GetID(); - parent.GetChildren().emplace_back(GetID()); - - m_Scene->ConvertToLocalSpace(*this); - } - - void Unparent() - { - if (!HasComponent()) - return; - - Terran::Core::UUID parentID = GetComponent().Parent; - Entity parent = m_Scene->FindEntityWithUUID(parentID); - - if (!parent) - return; - - m_Scene->ConvertToWorldSpace(*this); - - auto const& it = std::ranges::find(parent.GetChildren(), GetID()); - - if (it != parent.GetChildren().end()) - parent.GetChildren().erase(it); - - SetParentID(Terran::Core::UUID({ 0 })); - - // TODO: if the relationship component is no longer necessary than remove it - } - - /*void Unparent(Entity parent, Entity child, bool removeRelationship) - { - if (!parent.HasComponent()) - return; - - if (!child.HasComponent()) - return; - - const auto& it = std::find(parent.GetChildren().begin(), parent.GetChildren().end(), child.GetID()); - - if (it != parent.GetChildren().end()) - parent.GetChildren().erase(it); - - if (removeRelationship) - child.RemoveComponent(); - else - { - RelationshipComponent& rc = child.GetComponent(); - rc.ParentID = UUID({ 0 }); - } - }*/ - - void RemoveChild(Entity child, bool removeRelationship) - { - child.Unparent(); - } - - void Reparent(Entity previousParent, Entity newParent) - { - Unparent(); - SetParent(newParent); - } - -private: - entt::entity m_Handle { entt::null }; - Scene* m_Scene = nullptr; -}; - -} -#pragma warning(pop) diff --git a/TerranEngine/src/Scene/Scene.cpp b/TerranEngine/src/Scene/Scene.cpp deleted file mode 100644 index f9ee800d..00000000 --- a/TerranEngine/src/Scene/Scene.cpp +++ /dev/null @@ -1,692 +0,0 @@ -#include "Scene.h" -#include "trpch.h" - -#include "Components.h" -#include "Entity.h" -#include "SceneManager.h" - -#include "Graphics/BatchRenderer2D.h" - -#include "Systems/SceneRenderer.h" - -#include "Scripting/ScriptEngine.h" -// #include "Scripting/ScriptCache.h" - -#include "Physics/Physics.h" -#include "Physics/PhysicsBody.h" - -#include "Project/Project.h" - -#include "Math/Math.h" - -#include "Utils/Debug/OptickProfiler.h" -#include "Utils/Debug/Profiler.h" - -#include -#include -#define GLM_ENABLE_EXPERIMENTAL -#include - -namespace TerranEngine { - -struct SceneComponent final { - Terran::Core::UUID SceneID; -}; - -namespace { - -template -void CopyComponent(entt::entity srcHandle, entt::entity dstHandle, entt::registry& srcRegistry, entt::registry& dstRegistry) -{ - if (!srcRegistry.all_of(srcHandle)) - return; - - dstRegistry.emplace_or_replace(dstHandle, srcRegistry.get(srcHandle)); -} - -template<> -void CopyComponent(entt::entity srcHandle, entt::entity dstHandle, entt::registry& srcRegistry, entt::registry& dstRegistry) -{ - if (!srcRegistry.all_of(srcHandle)) - return; - - entt::entity const srcSceneEntity = srcRegistry.view().front(); - Terran::Core::UUID const& srcSceneID = srcRegistry.get(srcSceneEntity).SceneID; - Terran::Core::UUID const& srcEntityID = srcRegistry.get(srcHandle).ID; - - Terran::Core::Shared srcScriptInstance = ScriptEngine::GetScriptInstance(srcSceneID, srcEntityID); - if (!srcScriptInstance) { - TR_CORE_ERROR(TR_LOG_SCRIPT, "The script instance from the source scene was null"); - return; - } - dstRegistry.emplace_or_replace(dstHandle, srcRegistry.get(srcHandle)); - - entt::entity const dstSceneEntity = dstRegistry.view().front(); - Terran::Core::UUID const& dstSceneID = dstRegistry.get(dstSceneEntity).SceneID; - - Terran::Core::UUID const& dstEntityID = dstRegistry.get(dstHandle).ID; - - Terran::Core::Shared dstScriptInstance = ScriptEngine::GetScriptInstance(dstSceneID, dstEntityID); - if (!dstScriptInstance) { - TR_CORE_ERROR(TR_LOG_SCRIPT, "The script instance from the destination scene was null"); - return; - } - dstScriptInstance->CopyAllFieldsFrom(srcScriptInstance); -} - -template -void CopyComponent(entt::entity srcHandle, entt::entity dstHandle, entt::registry& srcRegistry) -{ - CopyComponent(srcHandle, dstHandle, srcRegistry, srcRegistry); -} - -static glm::mat4 CalculateTransformMatrix(TransformComponent const& transform) -{ - return glm::translate(glm::mat4(1.0f), transform.Position) * glm::toMat4(glm::quat(transform.Rotation)) * glm::scale(glm::mat4(1.0f), transform.Scale); -} - -} - -Scene::Scene() - : Scene(Terran::Core::UUID()) -{ -} - -Scene::Scene(Terran::Core::UUID const& handle) - : Asset(handle) -{ - auto const sceneEntity = m_Registry.create(); - m_Registry.emplace(sceneEntity, m_Handle); - - m_Registry.on_construct().connect<&Scene::OnScriptComponentConstructed>(this); - m_Registry.on_destroy().connect<&Scene::OnScriptComponentDestroyed>(this); - - m_Registry.on_construct().connect<&Scene::OnRigidbody2DComponentConstructed>(this); - m_Registry.on_destroy().connect<&Scene::OnRigidbody2DComponentDestroyed>(this); - - m_Registry.on_construct().connect<&Scene::OnBoxCollider2DComponentConstructed>(this); - m_Registry.on_destroy().connect<&Scene::OnBoxCollider2DComponentDestroyed>(this); - - m_Registry.on_construct().connect<&Scene::OnCircleCollider2DComponentConstructed>(this); - m_Registry.on_destroy().connect<&Scene::OnCircleCollider2DComponentDestroyed>(this); - - m_Registry.on_construct().connect<&Scene::OnCapsuleCollider2DComponentConstructed>(this); - m_Registry.on_destroy().connect<&Scene::OnCapsuleCollider2DComponentDestroyed>(this); - - m_Registry.on_construct().connect<&Scene::OnTextComponentConstructed>(this); -} - -Scene::~Scene() -{ - auto scriptableComponentView = m_Registry.view(); - - for (auto e : scriptableComponentView) { - Entity entity(e, this); - ScriptEngine::DestroyScriptInstance(entity); - } - - m_Registry.clear(); - - m_Registry.on_construct().disconnect<&Scene::OnScriptComponentConstructed>(this); - m_Registry.on_destroy().disconnect<&Scene::OnScriptComponentDestroyed>(this); - - m_Registry.on_construct().disconnect<&Scene::OnRigidbody2DComponentConstructed>(this); - m_Registry.on_destroy().disconnect<&Scene::OnRigidbody2DComponentDestroyed>(this); -} - -Entity Scene::CreateEntity(std::string const& name) -{ - return CreateEntityWithUUID(name, Terran::Core::UUID()); -} - -Entity Scene::CreateEntityWithUUID(std::string const& name, Terran::Core::UUID const& uuid) -{ - entt::entity e = m_Registry.create(); - - Entity entity(e, this); - entity.AddComponent(name.empty() ? "Entity" : name, uuid); - entity.AddComponent(); - - m_EntityMap[uuid] = e; - - SortEntities(); - return entity; -} - -Entity Scene::CreateEmptyEntity() -{ - entt::entity e = m_Registry.create(); - Entity entity(e, this); - return entity; -} - -void Scene::DestroyEntity(Entity entity, bool first) -{ - ScriptEngine::DestroyScriptInstance(entity); - - if (entity.HasComponent()) - Physics2D::DestroyPhysicsBody(entity); - - if (entity.HasComponent()) { - if (first) { - if (entity.HasParent()) - entity.GetParent().RemoveChild(entity, false); - } - - for (auto eID : entity.GetChildren()) - DestroyEntity(FindEntityWithUUID(eID), false); - } - - auto entityIt = m_EntityMap.find(entity.GetID()); - if (entityIt != m_EntityMap.end()) - m_EntityMap.erase(entityIt); - - m_Registry.destroy(entity); - - SortEntities(); -} - -void Scene::StartRuntime() -{ - if (m_IsPlaying) - return; - - m_IsPlaying = true; - - Physics2D::CreatePhysicsWorld(Project::GetPhysicsSettings()); - Physics2D::CratePhysicsBodies(this); - - auto scriptableComponentView = m_Registry.view(); - - for (auto e : scriptableComponentView) { - Entity entity(e, this); - ScriptEngine::OnStart(entity); - } -} - -void Scene::StopRuntime() -{ - if (!m_IsPlaying) - return; - - m_IsPlaying = false; - Physics2D::CleanUpPhysicsWorld(); -} - -void Scene::Update(Terran::Core::Time time) -{ - TR_PROFILE_FUNCN("Scene::Update"); - TR_PROFILE_FUNCTION(); - - UpdateTransformHierarchy(); - - Physics2D::Update(time); - - auto scriptableComponentView = m_Registry.view(); - for (auto e : scriptableComponentView) { - Entity entity(e, this); - ScriptEngine::OnUpdate(entity, time.GetDeltaTime()); - } -} - -void Scene::UpdateEditor() -{ - TR_PROFILE_FUNCTION(); - UpdateTransformHierarchy(); -} - -void Scene::OnResize(float width, float height) -{ - if (m_ViewportWidth != width || m_ViewportHeight != height) { - m_ViewportWidth = width; - m_ViewportHeight = height; - - auto cameraView = m_Registry.view(); - - for (auto e : cameraView) { - Entity entity(e, this); - auto& cameraComponent = entity.GetComponent(); - - cameraComponent.Camera.SetViewport(width, height); - } - } -} - -void Scene::OnRender(Terran::Core::Shared const& sceneRenderer) -{ - TR_PROFILE_FUNCTION(); - - if (Entity primaryCamera = GetPrimaryCamera()) { - glm::vec4 backgroundColor = primaryCamera.GetComponent().BackgroundColor; - sceneRenderer->SetScene(this); - sceneRenderer->SetClearColor(backgroundColor); - - Camera& camera = primaryCamera.GetComponent().Camera; - glm::mat4& cameraTransform = primaryCamera.GetTransform().WorldSpaceTransformMatrix; - - sceneRenderer->BeginScene(camera, cameraTransform, true); - - // submit sprites - { - auto spriteRendererView = m_Registry.view(); - for (auto e : spriteRendererView) { - Entity entity(e, this); - auto& spriteRenderer = entity.GetComponent(); - auto& transform = entity.GetTransform(); - - sceneRenderer->SubmitSprite(spriteRenderer, transform.WorldSpaceTransformMatrix, (uint32_t)entity); - } - } - - // submit circles - { - auto circleRendererView = m_Registry.view(); - for (auto e : circleRendererView) { - Entity entity(e, this); - auto& circleRenderer = entity.GetComponent(); - auto& transform = entity.GetTransform(); - - sceneRenderer->SubmitCircle(circleRenderer, transform.WorldSpaceTransformMatrix, (uint32_t)(entity)); - } - } - - // submit text - { - auto textRendererView = m_Registry.view(); - for (auto e : textRendererView) { - Entity entity(e, this); - auto& textRenderer = entity.GetComponent(); - auto& transform = entity.GetTransform(); - - sceneRenderer->SubmitText(textRenderer, transform.WorldSpaceTransformMatrix, entity); - } - } - - // submit lines - { - auto lineRendererView = m_Registry.view(); - - for (auto e : lineRendererView) { - Entity entity(e, this); - auto& lineRenderer = entity.GetComponent(); - - sceneRenderer->SubmitLine(lineRenderer, (uint32_t)entity); - } - } - - sceneRenderer->EndScene(); - } -} - -void Scene::OnRenderEditor(Terran::Core::Shared const& sceneRenderer, Camera& camera, glm::mat4& cameraView) -{ - TR_PROFILE_FUNCTION(); - sceneRenderer->SetScene(this); - sceneRenderer->BeginScene(camera, cameraView, false); - - sceneRenderer->GetFramebuffer()->SetColorAttachmentValue(1, -1); - - // submit sprites - { - auto spriteRendererView = m_Registry.view(); - for (auto e : spriteRendererView) { - Entity entity(e, this); - auto& spriteRenderer = entity.GetComponent(); - auto& transform = entity.GetTransform(); - - sceneRenderer->SubmitSprite(spriteRenderer, transform.WorldSpaceTransformMatrix, (uint32_t)entity); - } - } - - // submit circles - { - auto circleRendererView = m_Registry.view(); - for (auto e : circleRendererView) { - Entity entity(e, this); - auto& circleRenderer = entity.GetComponent(); - auto& transform = entity.GetTransform(); - - sceneRenderer->SubmitCircle(circleRenderer, transform.WorldSpaceTransformMatrix, (uint32_t)entity); - } - } - - // submit text - { - auto textRendererView = m_Registry.view(); - for (auto e : textRendererView) { - Entity entity(e, this); - auto& textRenderer = entity.GetComponent(); - auto& transform = entity.GetTransform(); - - sceneRenderer->SubmitText(textRenderer, transform.WorldSpaceTransformMatrix, (uint32_t)entity); - } - } - - // submit lines - { - auto lineRendererView = m_Registry.view(); - for (auto e : lineRendererView) { - Entity entity(e, this); - auto& lineRenderer = entity.GetComponent(); - - sceneRenderer->SubmitLine(lineRenderer, (uint32_t)entity); - } - } - - sceneRenderer->EndScene(); -} - -Entity Scene::FindEntityWithUUID(Terran::Core::UUID uuid) -{ - TR_PROFILE_FUNCTION(); - if (m_EntityMap.contains(uuid)) - return Entity(m_EntityMap.at(uuid), this); - - return {}; -} - -Entity Scene::FindEntityWithName(std::string const& name) -{ - TR_PROFILE_FUNCTION(); - auto const tagView = m_Registry.view(); - - for (auto e : tagView) { - Entity entity(e, this); - if (entity.GetName() == name) - return entity; - } - - return {}; -} - -Entity Scene::GetPrimaryCamera() -{ - auto const cameraView = m_Registry.view(); - for (auto e : cameraView) { - Entity entity(e, this); - auto& cameraComponent = entity.GetComponent(); - - if (cameraComponent.Primary) - return entity; - } - - return {}; -} - -Entity Scene::DuplicateEntity(Entity srcEntity, Entity parent) -{ - Entity dstEntity = CreateEntity(srcEntity.GetName() + " Copy"); - - CopyComponent(srcEntity, dstEntity, m_Registry); - CopyComponent(srcEntity, dstEntity, m_Registry); - CopyComponent(srcEntity, dstEntity, m_Registry); - CopyComponent(srcEntity, dstEntity, m_Registry); - CopyComponent(srcEntity, dstEntity, m_Registry); - CopyComponent(srcEntity, dstEntity, m_Registry); - CopyComponent(srcEntity, dstEntity, m_Registry); - CopyComponent(srcEntity, dstEntity, m_Registry); - CopyComponent(srcEntity, dstEntity, m_Registry); - CopyComponent(srcEntity, dstEntity, m_Registry); - - if (srcEntity.HasComponent()) { - for (int i = 0; i < srcEntity.GetChildCount(); i++) { - Entity childEntity = srcEntity.GetChild(i); - DuplicateEntity(childEntity, dstEntity); - } - - if (!parent) - parent = srcEntity.GetParent(); - - if (parent) - dstEntity.SetParent(parent); - } - - return dstEntity; -} - -Entity Scene::DuplicateEntity(Entity srcEntity) -{ - return DuplicateEntity(srcEntity, {}); -} - -Terran::Core::Shared Scene::CopyScene(Terran::Core::Shared const& srcScene) -{ - Terran::Core::Shared scene = SceneManager::CreateEmptyScene(); - - auto tagView = srcScene->GetEntitiesWith(); - - for (auto e : tagView) { - Entity srcEntity(e, srcScene.get()); - Entity dstEntity = scene->CreateEntityWithUUID(srcEntity.GetName(), srcEntity.GetID()); - } - - for (auto e : tagView) { - Entity srcEntity(e, srcScene.get()); - Entity dstEntity = scene->FindEntityWithUUID(srcEntity.GetID()); - - CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); - CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); - CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); - CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); - CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); - CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); - CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); - CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); - CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); - CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); - CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); - } - - scene->SortEntities(); - - return scene; -} - -void Scene::UpdateTransformHierarchy() -{ - TR_PROFILE_FUNCN("Scene::UpdateTransformHierarchy"); - TR_PROFILE_FUNCTION(); - - auto transformView = GetEntitiesWith(); - - for (auto e : transformView) { - Entity entity(e, this); - auto& transform = entity.GetTransform(); - - if (!entity.HasParent()) - UpdateEntityTransform(entity); - } -} - -void Scene::UpdateEntityTransform(Entity entity) -{ - TransformComponent& tc = entity.GetComponent(); - - if (tc.IsDirty) { - if (Entity parent = entity.GetParent()) { - glm::mat4 parentTransform = parent.GetTransform().WorldSpaceTransformMatrix; - tc.WorldSpaceTransformMatrix = parentTransform * CalculateTransformMatrix(tc); - tc.LocalSpaceTransformMatrix = glm::inverse(parentTransform) * tc.WorldSpaceTransformMatrix; - } else { - tc.WorldSpaceTransformMatrix = CalculateTransformMatrix(tc); - tc.LocalSpaceTransformMatrix = tc.WorldSpaceTransformMatrix; - } - - glm::quat rotation = tc.Rotation; - - tc.Forward = glm::normalize(glm::rotate(rotation, glm::vec3(0.0f, 0.0f, 1.0f))); - tc.Up = glm::normalize(glm::rotate(rotation, glm::vec3(0.0f, 1.0f, 0.0f))); - tc.Right = glm::normalize(glm::rotate(rotation, glm::vec3(1.0f, 0.0f, 0.0f))); - } - - for (size_t i = 0; i < entity.GetChildCount(); i++) { - Entity currEntity = entity.GetChild(static_cast(i)); - - if (tc.IsDirty) - currEntity.GetTransform().IsDirty = true; - - UpdateEntityTransform(currEntity); - } - - tc.IsDirty = false; -} - -void Scene::ConvertToLocalSpace(Entity entity) -{ - auto& tc = entity.GetComponent(); - - if (!entity.HasParent()) - return; - - if (tc.IsDirty) - UpdateEntityTransform(entity); - - Entity parent = entity.GetParent(); - auto& parentTransform = parent.GetTransform(); - - // NOTE: have to calculate it because at this point the local space - // and world space transform matrices are equal - glm::mat4 parentWorldMatrix = parentTransform.WorldSpaceTransformMatrix; - glm::mat4 localMat = glm::inverse(parentWorldMatrix) * tc.WorldSpaceTransformMatrix; - - Math::Decompose(localMat, tc.Position, tc.Rotation, tc.Scale); - - tc.IsDirty = true; -} - -void Scene::ConvertToWorldSpace(Entity entity) -{ - auto& tc = entity.GetComponent(); - - if (!entity.HasParent()) - return; - - if (tc.IsDirty) - UpdateEntityTransform(entity); - - Math::Decompose(tc.WorldSpaceTransformMatrix, tc.Position, tc.Rotation, tc.Scale); - - tc.IsDirty = true; -} - -void Scene::SortEntities() -{ - m_Registry.sort([](entt::entity const& lEntity, entt::entity const& rEntity) { return lEntity < rEntity; }); -} - -void Scene::OnScriptComponentConstructed(entt::registry& registry, entt::entity entityHandle) -{ - Entity entity(entityHandle, this); - ScriptEngine::CreateScriptInstance(entity); - - if (m_IsPlaying) - ScriptEngine::OnStart(entity); -} - -void Scene::OnScriptComponentDestroyed(entt::registry& registry, entt::entity entityHandle) -{ - Entity entity(entityHandle, this); - ScriptEngine::DestroyScriptInstance(entity); -} - -void Scene::OnRigidbody2DComponentConstructed(entt::registry& registry, entt::entity entityHandle) -{ - if (m_IsPlaying) { - Entity entity(entityHandle, this); - Terran::Core::Shared physicsBody = Physics2D::CreatePhysicsBody(entity); - physicsBody->AttachColliders(); - } -} - -void Scene::OnRigidbody2DComponentDestroyed(entt::registry& registry, entt::entity entityHandle) -{ - if (m_IsPlaying) { - Entity entity(entityHandle, this); - Physics2D::DestroyPhysicsBody(entity); - } -} - -void Scene::OnBoxCollider2DComponentConstructed(entt::registry& registry, entt::entity entityHandle) -{ - Entity entity(entityHandle, this); - if (!entity.HasComponent()) - entity.AddComponent(); - - if (!m_IsPlaying) - return; - - if (Terran::Core::Shared physicsBody = Physics2D::GetPhysicsBody(entity)) - physicsBody->AddCollider(entity); -} -void Scene::OnBoxCollider2DComponentDestroyed(entt::registry& registry, entt::entity entityHandle) -{ - if (m_IsPlaying) { - Entity entity(entityHandle, this); - Terran::Core::Shared physicsBody = Physics2D::GetPhysicsBody(entity); - auto& bcComponent = entity.GetComponent(); - - if (physicsBody) - physicsBody->RemoveCollider(bcComponent.ColliderIndex); - } -} - -void Scene::OnCircleCollider2DComponentConstructed(entt::registry& registry, entt::entity entityHandle) -{ - Entity entity(entityHandle, this); - if (!entity.HasComponent()) - entity.AddComponent(); - - if (!m_IsPlaying) - return; - - if (Terran::Core::Shared physicsBody = Physics2D::GetPhysicsBody(entity)) - physicsBody->AddCollider(entity); -} -void Scene::OnCircleCollider2DComponentDestroyed(entt::registry& registry, entt::entity entityHandle) -{ - if (m_IsPlaying) { - Entity entity(entityHandle, this); - auto& ccComponent = entity.GetComponent(); - - if (Terran::Core::Shared physicsBody = Physics2D::GetPhysicsBody(entity)) - physicsBody->RemoveCollider(ccComponent.ColliderIndex); - } -} - -void Scene::OnCapsuleCollider2DComponentConstructed(entt::registry& registry, entt::entity entityHandle) -{ - Entity entity(entityHandle, this); - if (!entity.HasComponent()) - entity.AddComponent(); - - if (!m_IsPlaying) - return; - - if (Terran::Core::Shared physicsBody = Physics2D::GetPhysicsBody(entity)) - physicsBody->AddCollider(entity); -} -void Scene::OnCapsuleCollider2DComponentDestroyed(entt::registry& registry, entt::entity entityHandle) -{ - if (m_IsPlaying) { - Entity entity(entityHandle, this); - Terran::Core::Shared physicsBody = Physics2D::GetPhysicsBody(entity); - auto& ccComponent = entity.GetComponent(); - - if (physicsBody) - physicsBody->RemoveCollider(ccComponent.ColliderIndex); - } -} - -void Scene::OnTextComponentConstructed(entt::registry& registry, entt::entity entityHandle) -{ - Entity entity(entityHandle, this); - auto& trc = entity.GetComponent(); - if (!trc.FontAtlas) - trc.FontAtlas = Font::DefaultFont; -} - -void Scene::OnTextComponentDestroyed(entt::registry& registry, entt::entity entityHandle) -{ -} - -} diff --git a/TerranEngine/src/Scene/SceneSerializer.cpp b/TerranEngine/src/Scene/SceneSerializer.cpp deleted file mode 100644 index d0d9bdf5..00000000 --- a/TerranEngine/src/Scene/SceneSerializer.cpp +++ /dev/null @@ -1,576 +0,0 @@ -#include "trpch.h" - -#include "Entity.h" -#include "SceneSerializer.h" - -#include "Scripting/ScriptEngine.h" - -#include "Utils/SerializerUtils.h" - -#include - -#include -#define GLM_ENABLE_EXPERIMENTAL -#include - -namespace TerranEngine { - -char const* SceneSerializer::SceneFilter = "Terran Scene\0*.terran\0"; -static char const* SerializerVersion = "yml1.0"; - -SceneSerializer::SceneSerializer(Terran::Core::Shared const& scene) - : m_Scene(scene) -{ -} - -#define WRITE_SCRIPT_FIELD(FieldType, Type) \ - case ScriptFieldType::FieldType: \ - out << YAML::Key << field.Name << YAML::Value << scriptInstance->GetFieldValue(fieldID); \ - break - -#define WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(FieldType, Type) \ - case ScriptFieldType::FieldType: { \ - Type value = scriptInstance->GetFieldArrayValue(array, i); \ - out << value; \ - } break - -// TODO: n dimensional arrays maybe someday in the future? -// static void SerializeScriptArray(YAML::Emitter& out, Shared scriptInstance, const ScriptArray& array, const ScriptField& field, int32_t* indices, int dimension = 0) -//{ -// int32_t length = scriptInstance->GetFieldArrayLength(array, dimension); -// if (dimension == array.Rank - 1) -// { -// out << YAML::BeginSeq; -// for (int32_t i = 0; i < length; i++) -// { -// indices[dimension] = i; -// switch (field.Type) -// { -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Bool, bool); -// // NOTE: maybe wchar_t? -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Char, char); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Int8, int8_t); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Int16, int16_t); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Int32, int32_t); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Int64, int64_t); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(UInt8, std::byte); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(UInt16, uint16_t); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(UInt32, uint32_t); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(UInt64, uint64_t); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Float, float); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Double, double); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(String, std::string); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Vector2, glm::vec2); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Vector3, glm::vec3); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Color, glm::vec4); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Entity, UUID); -// default: TR_ASSERT(false, "Unsupported field type"); break; -// } -// } -// out << YAML::EndSeq; -// } -// else -// { -// out << YAML::BeginSeq; -// for (int32_t i = 0; i < length; i++) -// { -// indices[dimension] = i; -// SerializeScriptArray(out, scriptInstance, array, field, indices, dimension + 1); -// } -// out << YAML::EndSeq; -// } -//} - -static void SerializeScriptFields(YAML::Emitter& out, Entity entity) -{ - ScriptComponent& sc = entity.GetComponent(); - Terran::Core::Shared scriptInstance = ScriptEngine::GetScriptInstance(entity); - - for (auto& fieldID : sc.FieldHandles) { - ScriptField field = scriptInstance->GetScriptField(fieldID); - - if (field.IsArray) { - ScriptArray array = scriptInstance->GetScriptArray(fieldID); - if (array.Rank > 1) - continue; - - out << YAML::BeginMap; - out << YAML::Key << field.Name << YAML::Value << YAML::Flow; - out << YAML::BeginSeq; - - int32_t length = scriptInstance->GetFieldArrayLength(array, 0); - for (int32_t i = 0; i < length; i++) { - switch (field.Type) { - WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Bool, bool); - // NOTE: maybe wchar_t? - WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Char, char); - WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Int8, int8_t); - WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Int16, int16_t); - WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Int32, int32_t); - WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Int64, int64_t); - WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(UInt8, std::byte); - WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(UInt16, uint16_t); - WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(UInt32, uint32_t); - WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(UInt64, uint64_t); - WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Float, float); - WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Double, double); - WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(String, std::string); - WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Vector2, glm::vec2); - WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Vector3, glm::vec3); - WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Color, glm::vec4); - WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Entity, Terran::Core::UUID); - default: - TR_ASSERT(false, "Unsupported field type"); - break; - } - } - out << YAML::EndSeq; - out << YAML::EndMap; - } else { - out << YAML::BeginMap; - switch (field.Type) { - WRITE_SCRIPT_FIELD(Bool, bool); - // TODO: wchar - WRITE_SCRIPT_FIELD(Char, char); - WRITE_SCRIPT_FIELD(Int8, int8_t); - WRITE_SCRIPT_FIELD(Int16, int16_t); - WRITE_SCRIPT_FIELD(Int32, int32_t); - WRITE_SCRIPT_FIELD(Int64, int64_t); - WRITE_SCRIPT_FIELD(UInt8, std::byte); - WRITE_SCRIPT_FIELD(UInt16, uint16_t); - WRITE_SCRIPT_FIELD(UInt32, uint32_t); - WRITE_SCRIPT_FIELD(UInt64, uint64_t); - WRITE_SCRIPT_FIELD(Float, float); - WRITE_SCRIPT_FIELD(Double, double); - WRITE_SCRIPT_FIELD(String, std::string); - WRITE_SCRIPT_FIELD(Vector2, glm::vec2); - WRITE_SCRIPT_FIELD(Vector3, glm::vec3); - WRITE_SCRIPT_FIELD(Color, glm::vec4); - WRITE_SCRIPT_FIELD(Entity, Terran::Core::UUID); - default: - TR_ASSERT(false, "Unsupported field type"); - break; - } - out << YAML::EndMap; - } - } -} - -#define BEGIN_COMPONENT_MAP(componentKey) \ - out << YAML::Key << componentKey; \ - out << YAML::BeginMap - -#define END_COMPONENT_MAP() \ - out << YAML::EndMap - -#define WRITE_COMPONENT_PROPERY(propertyName, property) \ - out << YAML::Key << propertyName << YAML::Value << property - -static void SerializeEntity(YAML::Emitter& out, Entity entity) -{ - // TODO: convert to a verify assert - TR_ASSERT(entity.HasComponent(), "Can't serialize an entity that doesn't have a tag component"); - out << YAML::BeginMap; - - WRITE_COMPONENT_PROPERY("Entity", entity.GetID()); - - BEGIN_COMPONENT_MAP("TagComponent"); - auto& tagComponent = entity.GetComponent(); - WRITE_COMPONENT_PROPERY("Tag", tagComponent.Name); - END_COMPONENT_MAP(); - - if (entity.HasComponent()) { - auto& transformComponent = entity.GetTransform(); - - BEGIN_COMPONENT_MAP("TransformComponent"); - WRITE_COMPONENT_PROPERY("Position", transformComponent.Position); - WRITE_COMPONENT_PROPERY("Scale", transformComponent.Scale); - WRITE_COMPONENT_PROPERY("Rotation", transformComponent.Rotation); - END_COMPONENT_MAP(); - } - - if (entity.HasComponent()) { - auto& cameraComponent = entity.GetComponent(); - - BEGIN_COMPONENT_MAP("CameraComponent"); - WRITE_COMPONENT_PROPERY("Camera", YAML::BeginMap); - WRITE_COMPONENT_PROPERY("Size", cameraComponent.Camera.GetOrthographicSize()); - WRITE_COMPONENT_PROPERY("Near", cameraComponent.Camera.GetOrthographicNear()); - WRITE_COMPONENT_PROPERY("Far", cameraComponent.Camera.GetOrthographicFar()); - out << YAML::EndMap; - - WRITE_COMPONENT_PROPERY("Primary", cameraComponent.Primary); - WRITE_COMPONENT_PROPERY("ClearColor", cameraComponent.BackgroundColor); - END_COMPONENT_MAP(); - } - - if (entity.HasComponent()) { - auto& spriteRendererComponent = entity.GetComponent(); - - BEGIN_COMPONENT_MAP("SpriteRendererComponent"); - WRITE_COMPONENT_PROPERY("Color", spriteRendererComponent.Color); - WRITE_COMPONENT_PROPERY("Texture", spriteRendererComponent.TextureHandle); - END_COMPONENT_MAP(); - } - - if (entity.HasComponent()) { - auto& circleRendererComponent = entity.GetComponent(); - - BEGIN_COMPONENT_MAP("CircleRendererComponent"); - WRITE_COMPONENT_PROPERY("Color", circleRendererComponent.Color); - WRITE_COMPONENT_PROPERY("Thickness", circleRendererComponent.Thickness); - END_COMPONENT_MAP(); - } - - if (entity.HasComponent()) { - auto& textRendererComponent = entity.GetComponent(); - - BEGIN_COMPONENT_MAP("TextRendererCompoent"); - WRITE_COMPONENT_PROPERY("Color", textRendererComponent.TextColor); - WRITE_COMPONENT_PROPERY("Text", textRendererComponent.Text); - // TODO: save font handle - END_COMPONENT_MAP(); - } - - if (entity.HasComponent()) { - auto& relationshipComponent = entity.GetComponent(); - - BEGIN_COMPONENT_MAP("RelationshipComponent"); - WRITE_COMPONENT_PROPERY("Children", YAML::BeginSeq); - for (auto child : relationshipComponent.Children) - out << child; - out << YAML::EndSeq; - WRITE_COMPONENT_PROPERY("Parent", relationshipComponent.Parent); - END_COMPONENT_MAP(); - } - - if (entity.HasComponent()) { - auto& scriptComponent = entity.GetComponent(); - - BEGIN_COMPONENT_MAP("ScriptComponent"); - WRITE_COMPONENT_PROPERY("ModuleName", scriptComponent.ModuleName); - if (!scriptComponent.FieldHandles.empty()) { - out << YAML::Key << "Fields" << YAML::Value; - out << YAML::BeginSeq; - SerializeScriptFields(out, entity); - out << YAML::EndSeq; - } - END_COMPONENT_MAP(); - } - - if (entity.HasComponent()) { - auto& rigidbodyComponent = entity.GetComponent(); - - BEGIN_COMPONENT_MAP("Rigidbody2DComponent"); - WRITE_COMPONENT_PROPERY("BodyType", PhysicsBodyTypeToString(rigidbodyComponent.BodyType).data()); - WRITE_COMPONENT_PROPERY("FixedRotation", rigidbodyComponent.FixedRotation); - WRITE_COMPONENT_PROPERY("SleepState", PhysicsBodySleepStateToString(rigidbodyComponent.SleepState).data()); - WRITE_COMPONENT_PROPERY("GravityScale", rigidbodyComponent.GravityScale); - WRITE_COMPONENT_PROPERY("Material", rigidbodyComponent.PhysicsMaterialHandle); - END_COMPONENT_MAP(); - } - - if (entity.HasComponent()) { - auto& boxCollider2DComponent = entity.GetComponent(); - - BEGIN_COMPONENT_MAP("BoxCollider2DComponent"); - WRITE_COMPONENT_PROPERY("Offset", boxCollider2DComponent.Offset); - WRITE_COMPONENT_PROPERY("Size", boxCollider2DComponent.Size); - WRITE_COMPONENT_PROPERY("Sensor", boxCollider2DComponent.Sensor); - END_COMPONENT_MAP(); - } - - if (entity.HasComponent()) { - auto& circleCollider2DComponent = entity.GetComponent(); - - BEGIN_COMPONENT_MAP("CircleCollider2DComponent"); - WRITE_COMPONENT_PROPERY("Offset", circleCollider2DComponent.Offset); - WRITE_COMPONENT_PROPERY("Radius", circleCollider2DComponent.Radius); - WRITE_COMPONENT_PROPERY("Sensor", circleCollider2DComponent.Sensor); - END_COMPONENT_MAP(); - } - - if (entity.HasComponent()) { - auto& capsuleColliderComponenet = entity.GetComponent(); - - BEGIN_COMPONENT_MAP("CapsuleCollider2DComponent"); - WRITE_COMPONENT_PROPERY("Offset", capsuleColliderComponenet.Offset); - WRITE_COMPONENT_PROPERY("Size", capsuleColliderComponenet.Size); - WRITE_COMPONENT_PROPERY("Sensor", capsuleColliderComponenet.Sensor); - END_COMPONENT_MAP(); - } - - out << YAML::EndMap; -} - -void SceneSerializer::SerializeEditor(std::filesystem::path const& scenePath) -{ - YAML::Emitter out; - - out << YAML::BeginMap; - - out << YAML::Key << "SerializerVersion" << YAML::Value << SerializerVersion; - out << YAML::Key << "Entities" << YAML::Value << YAML::BeginSeq; - - auto const tagComponentView = m_Scene->GetEntitiesWith(); - for (auto e : tagComponentView) { - Entity entity(e, m_Scene.get()); - SerializeEntity(out, entity); - } - - out << YAML::EndSeq; - out << YAML::EndMap; - - std::ofstream ofs(scenePath); - ofs << out.c_str(); -} - -#define READ_SCRIPT_FIELD(FieldType, Type) \ - case ScriptFieldType::FieldType: { \ - Type value = scriptFieldNode.as(); \ - scriptInstance->SetFieldValue(fieldID, value); \ - } break - -#define READ_SCRIPT_FIELD_ARRAY_ELEMENT(FieldType, Type) \ - case ScriptFieldType::FieldType: { \ - Type value = scriptFieldNode[i].as(); \ - scriptInstance->SetFieldArrayValue(array, value, i); \ - } break - -static void DeserializeScriptFields(Terran::Core::Shared scriptInstance, ScriptComponent& scriptComponent, const YAML::Node& scriptFieldsNode) -{ - for (auto const& fieldID : scriptComponent.FieldHandles) { - ScriptField scriptField = scriptInstance->GetScriptField(fieldID); - YAML::Node scriptFieldNode; - bool valid = false; - - for (auto field : scriptFieldsNode) { - if (field[scriptField.Name]) { - scriptFieldNode = field[scriptField.Name]; - valid = true; - break; - } - } - - if (!valid) - continue; - - if (scriptField.IsArray) { - ScriptArray array = scriptInstance->GetScriptArray(fieldID); - if (array.Rank > 1) - continue; - - // TODO: n dimensional arrays someday in the future? - int32_t length = scriptInstance->GetFieldArrayLength(array); - for (int32_t i = 0; i < length && i < scriptFieldNode.size(); i++) { - switch (scriptField.Type) { - READ_SCRIPT_FIELD_ARRAY_ELEMENT(Bool, bool); - READ_SCRIPT_FIELD_ARRAY_ELEMENT(Char, char); - READ_SCRIPT_FIELD_ARRAY_ELEMENT(Int64, int64_t); - READ_SCRIPT_FIELD_ARRAY_ELEMENT(Int32, int32_t); - READ_SCRIPT_FIELD_ARRAY_ELEMENT(Int16, int16_t); - READ_SCRIPT_FIELD_ARRAY_ELEMENT(Int8, int8_t); - READ_SCRIPT_FIELD_ARRAY_ELEMENT(UInt64, uint64_t); - READ_SCRIPT_FIELD_ARRAY_ELEMENT(UInt32, uint32_t); - READ_SCRIPT_FIELD_ARRAY_ELEMENT(UInt16, uint16_t); - READ_SCRIPT_FIELD_ARRAY_ELEMENT(UInt8, uint8_t); - READ_SCRIPT_FIELD_ARRAY_ELEMENT(Float, float); - READ_SCRIPT_FIELD_ARRAY_ELEMENT(Double, double); - READ_SCRIPT_FIELD_ARRAY_ELEMENT(String, std::string); - READ_SCRIPT_FIELD_ARRAY_ELEMENT(Vector2, glm::vec2); - READ_SCRIPT_FIELD_ARRAY_ELEMENT(Vector3, glm::vec3); - READ_SCRIPT_FIELD_ARRAY_ELEMENT(Color, glm::vec4); - READ_SCRIPT_FIELD_ARRAY_ELEMENT(Entity, Terran::Core::UUID); - default: - TR_ASSERT(false, "Invalid script type"); - } - } - } else { - switch (scriptField.Type) { - READ_SCRIPT_FIELD(Bool, bool); - READ_SCRIPT_FIELD(Char, char); - READ_SCRIPT_FIELD(Int64, int64_t); - READ_SCRIPT_FIELD(Int32, int32_t); - READ_SCRIPT_FIELD(Int16, int16_t); - READ_SCRIPT_FIELD(Int8, int8_t); - READ_SCRIPT_FIELD(UInt64, uint64_t); - READ_SCRIPT_FIELD(UInt32, uint32_t); - READ_SCRIPT_FIELD(UInt16, uint16_t); - READ_SCRIPT_FIELD(UInt8, uint8_t); - READ_SCRIPT_FIELD(Float, float); - READ_SCRIPT_FIELD(Double, double); - READ_SCRIPT_FIELD(String, std::string); - READ_SCRIPT_FIELD(Vector2, glm::vec2); - READ_SCRIPT_FIELD(Vector3, glm::vec3); - READ_SCRIPT_FIELD(Color, glm::vec4); - READ_SCRIPT_FIELD(Entity, Terran::Core::UUID); - default: - TR_ASSERT(false, "Invalid script type"); - } - } - } -} - -static YAML::Node FindEntity(YAML::Node scene, Terran::Core::UUID const& entityID) -{ - for (auto entity : scene) { - TR_CORE_TRACE(TR_LOG_CORE, entity); - Terran::Core::UUID id = entity["Entity"].as(); - if (id == entityID) - return entity; - } - - return {}; -} - -static Entity DeserializeEntity(YAML::Node data, YAML::Node scene, Terran::Core::Shared deserializedScene) -{ - try { - Terran::Core::UUID id = data["Entity"].as(); - if (!id) { - TR_ASSERT(false, "Invalid id"); - return {}; - } - - Entity entity = deserializedScene->FindEntityWithUUID(id); - if (entity) - return entity; - - auto tagComponent = data["TagComponent"]; - if (!tagComponent) { - TR_ASSERT(false, "Invalid tag component"); - return {}; - } - - std::string name = tagComponent["Tag"].as(); - Entity deserializedEntity = deserializedScene->CreateEntityWithUUID(name, id); - - auto transformComponent = data["TransformComponent"]; - if (transformComponent) { - auto& tc = deserializedEntity.GetTransform(); - tc.Position = transformComponent["Position"].as(glm::vec3(0.0f, 0.0f, 0.0f)); - tc.Rotation = transformComponent["Rotation"].as(glm::vec3(0.0f, 0.0f, 0.0f)); - tc.Scale = transformComponent["Scale"].as(glm::vec3(1.0f, 1.0f, 1.0f)); - } - - auto cameraComponent = data["CameraComponent"]; - if (cameraComponent) { - auto& cc = deserializedEntity.AddComponent(); - auto camera = cameraComponent["Camera"]; - cc.Camera.SetOrthographicSize(camera["Size"].as(10.0f)); - cc.Camera.SetOrthographicNear(camera["Near"].as(-10.0f)); - cc.Camera.SetOrthographicFar(camera["Far"].as(10.0f)); - - cc.Primary = cameraComponent["Primary"].as(false); - cc.BackgroundColor = cameraComponent["ClearColor"].as(glm::vec4(0.1f, 0.1f, 0.1f, 1.0f)); - } - - auto spriteRendererComponent = data["SpriteRendererComponent"]; - if (spriteRendererComponent) { - auto& src = deserializedEntity.AddComponent(); - src.Color = spriteRendererComponent["Color"].as(glm::vec4(1.0f, 1.0f, 1.0f, 1.0f)); - src.TextureHandle = spriteRendererComponent["Texture"].as(Terran::Core::UUID::Invalid()); - } - - auto circleRendererComponent = data["CircleRendererComponent"]; - if (circleRendererComponent) { - auto& crc = deserializedEntity.AddComponent(); - crc.Color = circleRendererComponent["Color"].as(glm::vec4(1.0f, 1.0f, 1.0f, 1.0f)); - crc.Thickness = circleRendererComponent["Thickness"].as(1.0f); - } - - auto relationshipComponent = data["RelationshipComponent"]; - if (relationshipComponent) { - auto& rc = deserializedEntity.AddComponent(); - for (auto childID : relationshipComponent["Children"]) { - Terran::Core::UUID deserializedChildID = childID.as(); - Entity child = deserializedScene->FindEntityWithUUID(deserializedChildID); - if (!child) { - YAML::Node childNode = FindEntity(scene, deserializedChildID); - child = DeserializeEntity(childNode, scene, deserializedScene); - } - - if (child) - child.SetParent(deserializedEntity, true); - } - } - - auto scriptComponent = data["ScriptComponent"]; - if (scriptComponent) { - auto& sc = deserializedEntity.AddComponent(); - sc.ModuleName = scriptComponent["ModuleName"].as(); - - Terran::Core::Shared scriptInstance = ScriptEngine::CreateScriptInstance(deserializedEntity); - auto scriptFields = scriptComponent["Fields"]; - if (scriptFields) - DeserializeScriptFields(scriptInstance, sc, scriptFields); - } - - auto boxColliderComponent = data["BoxCollider2DComponent"]; - if (boxColliderComponent) { - auto& bcc = deserializedEntity.AddComponent(); - bcc.Offset = boxColliderComponent["Offset"].as(glm::vec2(0.0f, 0.0f)); - bcc.Size = boxColliderComponent["Size"].as(glm::vec2(1.0f, 1.0f)); - bcc.Sensor = boxColliderComponent["Sensor"].as(false); - } - - auto circleColliderComponent = data["CircleCollider2DComponent"]; - if (circleRendererComponent) { - auto& ccc = deserializedEntity.AddComponent(); - ccc.Offset = circleColliderComponent["Offset"].as(glm::vec2(0.0f, 0.0f)); - ccc.Radius = circleColliderComponent["Radius"].as(0.5f); - ccc.Sensor = circleColliderComponent["Sensor"].as(false); - } - - auto rigidbodyComponent = data["Rigidbody2DComponent"]; - if (rigidbodyComponent) { - auto& rbc = deserializedEntity.AddComponent(); - rbc.BodyType = PhysicsBodyTypeFromString(rigidbodyComponent["BodyType"].as()); - rbc.FixedRotation = rigidbodyComponent["FixedRotation"].as(false); - rbc.SleepState = PhysicsBodySleepStateFromString(rigidbodyComponent["SleepState"].as()); - rbc.GravityScale = rigidbodyComponent["GravityScale"].as(1.0f); - - if (rigidbodyComponent["Material"]) - rbc.PhysicsMaterialHandle = rigidbodyComponent["Material"].as(Terran::Core::UUID::Invalid()); - } - - auto capsuleColliderComponent = data["CapsuleCollider2DComponent"]; - if (capsuleColliderComponent) { - auto& ccc = deserializedEntity.AddComponent(); - ccc.Offset = capsuleColliderComponent["Offset"].as(glm::vec2(0.0f, 0.0f)); - ccc.Size = capsuleColliderComponent["Size"].as(glm::vec2(0.5f, 1.0f)); - ccc.Sensor = capsuleColliderComponent["Sensor"].as(false); - } - - return deserializedEntity; - } catch (YAML::InvalidNode const& ex) { - TR_CORE_ERROR(TR_LOG_ASSET, ex.what()); - return Entity(); - } -} - -Result SceneSerializer::DeserializeEditor(std::filesystem::path const& scenePath) -{ - YAML::Node data; - try { - data = YAML::LoadFile(scenePath.string()); - } catch (YAML::ParserException const& ex) { - TR_CORE_ERROR(TR_LOG_ASSET, ex.what()); - return Result::PARSE_ERROR; - } catch (YAML::BadFile const& ex) { - TR_CORE_ERROR(TR_LOG_ASSET, ex.what()); - return Result::NOT_FOUND; - } - - auto entities = data["Entities"]; - if (entities) { - for (auto entity : entities) { - if (!DeserializeEntity(entity, entities, m_Scene)) - return Result::PARSE_ERROR; - } - } - - return Result::OK; -} - -} diff --git a/TerranEngine/src/Scene/Systems/SceneRenderer.cpp b/TerranEngine/src/Scene/Systems/SceneRenderer.cpp deleted file mode 100644 index 035ee581..00000000 --- a/TerranEngine/src/Scene/Systems/SceneRenderer.cpp +++ /dev/null @@ -1,178 +0,0 @@ -#include "SceneRenderer.h" -#include "trpch.h" - -#include "Scene/Entity.h" - -#include "Core/Input.h" - -#include "Graphics/BatchRenderer2D.h" -#include "Graphics/Renderer.h" - -#include "Asset/AssetManager.h" - -#include "Math/Math.h" - -#include - -namespace TerranEngine { - -SceneRenderer::SceneRenderer(FramebufferParameters const& params) - : m_Scene(nullptr) -{ - m_Framebuffer = Terran::Core::CreateShared(params); -} - -void SceneRenderer::SetScene(Scene* scene) -{ - m_Scene = scene; -} - -void SceneRenderer::BeginScene(Camera& camera, glm::mat4 const& cameraTransform, bool inverseTransform) -{ - m_Framebuffer->Bind(); - - Renderer::SetClearColor(m_ClearColor.r, m_ClearColor.g, m_ClearColor.b, 1.0f); - Renderer::Clear(); - - BatchRenderer2D::BeginFrame(camera, cameraTransform, inverseTransform); - m_BegunScene = true; - - /* TODO: better sorting - * also add circle sorting - */ - - m_Scene->m_Registry.sort([](auto const& lEntity, auto const& rEntity) { return lEntity.ZIndex < rEntity.ZIndex; }); -} - -void SceneRenderer::SubmitSprite(SpriteRendererComponent const& spriteRenderer, glm::mat4& transform, int entityID) -{ - // TODO: frustum culling - Terran::Core::Shared texture = AssetManager::GetAssetByHandle(spriteRenderer.TextureHandle); - BatchRenderer2D::AddQuad(transform, spriteRenderer.Color, texture, entityID); -} - -void SceneRenderer::SubmitCircle(CircleRendererComponent const& circleRenderer, glm::mat4& transform, int entityID) -{ - // TODO: frustum culling - BatchRenderer2D::AddCircle(transform, circleRenderer.Color, circleRenderer.Thickness, entityID); -} - -void SceneRenderer::SubmitLine(LineRendererComponent const& lineRenderer, int entityID) -{ - BatchRenderer2D::AddLine(lineRenderer.StartPoint, lineRenderer.EndPoint, lineRenderer.Color, lineRenderer.Thickness, entityID); -} - -void SceneRenderer::SubmitText(TextRendererComponent const& textRenderer, glm::mat4& transform, int entityID) -{ - BatchRenderer2D::AddString(transform, textRenderer.Text, textRenderer.TextColor, textRenderer.FontAtlas, - textRenderer.LineSpacing, textRenderer.LineWidth, entityID); -} - -void SceneRenderer::EndScene() -{ - // TODO: draw grid - if (m_ShowColliders) - SubmitColliderBounds(); - - TR_ASSERT(m_BegunScene, "BeginScene has to be called before EndScene!"); - - BatchRenderer2D::EndFrame(); - m_Framebuffer->Unbind(); - - m_BegunScene = false; -} - -void SceneRenderer::OnResize(uint32_t width, uint32_t height) -{ - if (m_Framebuffer->GetWidth() != width || m_Framebuffer->GetHeight() != height) - m_Framebuffer->Resize(width, height); -} - -void SceneRenderer::SubmitColliderBounds() -{ - // submit box collider bounds - { - auto boxColliderView = m_Scene->GetEntitiesWith(); - for (auto e : boxColliderView) { - Entity entity(e, m_Scene); - - BoxCollider2DComponent& boxCollider = entity.GetComponent(); - auto& transform = entity.GetTransform(); - - constexpr glm::vec4 color = { 0.0f, 1.0f, 0.0f, 1.0f }; - constexpr float thickness = 0.05f; - - glm::vec3 size = { boxCollider.Size.x, boxCollider.Size.y, 1.0f }; - - glm::vec3 position = { boxCollider.Offset.x, boxCollider.Offset.y, 1.0f }; - - glm::mat4 worldTransformMatrix = transform.WorldSpaceTransformMatrix; - glm::mat4 transformMatrix = worldTransformMatrix * glm::translate(glm::mat4(1.0f), position) * glm::scale(glm::mat4(1.0f), size); - - /*const glm::vec3 size = { transform.Scale.x * boxCollider.Size.x, transform.Scale.y * boxCollider.Size.y, 1.0f }; - - const glm::vec3 postition = { transform.Position.x + boxCollider.Offset.x, transform.Position.y + boxCollider.Offset.y, 1.0f }; - - glm::mat4 transformMatrix = glm::translate(glm::mat4(1.0f), postition) * - glm::rotate(glm::mat4(1.0f), transform.Rotation.z, glm::vec3(0.0f, 0.0f, 1.0f)) * - glm::scale(glm::mat4(1.0f), size);*/ - - BatchRenderer2D::AddDebugRect(transformMatrix, color); - } - } - - // submit circle collider bounds - { - auto circleColliderView = m_Scene->GetEntitiesWith(); - - for (auto e : circleColliderView) { - Entity entity(e, m_Scene); - - auto& circleCollider = entity.GetComponent(); - auto& transform = entity.GetTransform(); - - constexpr glm::vec4 color = { 0.0f, 1.0f, 0.0f, 1.0f }; - constexpr float thickness = 0.02f; - - glm::vec3 position, rotation, scale; - - Math::Decompose(transform.WorldSpaceTransformMatrix, position, rotation, scale); - - // choose which component of the scale to apply - float scalingFactor = scale.x > scale.y ? scale.x : scale.y; - - glm::vec3 colliderSize = scalingFactor * glm::vec3(circleCollider.Radius * 2.0f); - glm::vec3 colliderPosition = { position.x + circleCollider.Offset.x, - position.y + circleCollider.Offset.y, 1.0f }; - - glm::mat4 transformMatrix = glm::translate(glm::mat4(1.0f), colliderPosition) * glm::rotate(glm::mat4(1.0f), rotation.z, glm::vec3(0.0f, 0.0f, 1.0f)) * glm::scale(glm::mat4(1.0f), colliderSize); - - BatchRenderer2D::AddCircle(transformMatrix, color, thickness, -1); - } - } - - // submit capsule collider bounds - { - auto capsuleColliderView = m_Scene->GetEntitiesWith(); - - for (auto e : capsuleColliderView) { - Entity entity(e, m_Scene); - - auto& capsuleCollider = entity.GetComponent(); - auto& transform = entity.GetTransform(); - - constexpr glm::vec4 color = { 0.0f, 1.0f, 0.0f, 1.0f }; - constexpr float thickness = 0.02f; - - float ySize = capsuleCollider.Size.x > capsuleCollider.Size.y ? capsuleCollider.Size.x : capsuleCollider.Size.y; - glm::vec3 const size = { capsuleCollider.Size.x, ySize, 1.0f }; - glm::vec3 const position = { capsuleCollider.Offset.x, capsuleCollider.Offset.y, 1.0f }; - - glm::mat4 transformMatrix = transform.WorldSpaceTransformMatrix * glm::translate(glm::mat4(1.0f), position) * glm::scale(glm::mat4(1.0f), size); - - BatchRenderer2D::AddCircle(transformMatrix, color, thickness, -1); - } - } -} - -} diff --git a/TerranEngine/src/Scene/Systems/SceneRenderer.h b/TerranEngine/src/Scene/Systems/SceneRenderer.h deleted file mode 100644 index c5b0618b..00000000 --- a/TerranEngine/src/Scene/Systems/SceneRenderer.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -#include "LibCore/Base.h" - -#include "Graphics/Camera.h" -#include "Graphics/Framebuffer.h" -#include "Graphics/Texture.h" - -#include "Scene/Components.h" -#include "Scene/Scene.h" - -namespace TerranEngine { - -class SceneRenderer final { -public: - SceneRenderer(FramebufferParameters const& params); - - void SetClearColor(glm::vec4 color) { m_ClearColor = color; } - - void SetScene(Scene* scene); - - void BeginScene(Camera& camera, glm::mat4 const& cameraTransform, bool inverseTransform); - - void SubmitSprite(SpriteRendererComponent const& spriteRenderer, glm::mat4& transform, int entityID); - void SubmitCircle(CircleRendererComponent const& circleRenderer, glm::mat4& transform, int entityID); - void SubmitLine(LineRendererComponent const& lineRenderer, int entityID); - - void SubmitText(TextRendererComponent const& textRenderer, glm::mat4& transform, int entityID); - - void EndScene(); - - Terran::Core::Shared& GetFramebuffer() { return m_Framebuffer; } - - void OnResize(uint32_t width, uint32_t height); - - void SetShowColliders(bool show) { m_ShowColliders = show; } - bool AreCollidersShowing() const { return m_ShowColliders; } - -private: - void SubmitColliderBounds(); - - Scene* m_Scene; - bool m_BegunScene = false; - bool m_ShowColliders = false; - - glm::vec4 m_ClearColor = { 0.1f, 0.1f, 0.1f, 1.0f }; - - Terran::Core::Shared m_Framebuffer; -}; - -} - diff --git a/dependecies.lua b/dependecies.lua index e8f094c4..7f2a689a 100644 --- a/dependecies.lua +++ b/dependecies.lua @@ -15,13 +15,15 @@ Dependencies = { include = "%{wks.location}/vendor/GLFW/include", link = "glfw", }, + entt = { + include = "%{wks.location}/vendor/entt/include", + }, } IncludeDirectories = {} IncludeDirectories["imgui"] = "%{wks.location}/TerranEngine/vendor/ImGui/" IncludeDirectories["glad"] = "%{wks.location}/TerranEngine/vendor/GLAD/include/" IncludeDirectories["stb"] = "%{wks.location}/TerranEngine/vendor/stb/" -IncludeDirectories["entt"] = "%{wks.location}/TerranEngine/vendor/entt/include/" IncludeDirectories["msdfgen"] = "%{wks.location}/TerranEngine/vendor/msdf-atlas-gen/msdfgen/" IncludeDirectories["msdf_atlas_gen"] = "%{wks.location}/TerranEngine/vendor/msdf-atlas-gen/" IncludeDirectories["box2d"] = "%{wks.location}/TerranEngine/vendor/Box2D/include/" diff --git a/premake-native.lua b/premake-native.lua index 5ca6f51c..5cb2d8ff 100644 --- a/premake-native.lua +++ b/premake-native.lua @@ -35,6 +35,7 @@ group "Core" include "Libraries/LibMain" include "Libraries/LibCore" include "Libraries/LibAsset" +include "Libraries/LibScene" include "TerranEngine" group "Graphics" diff --git a/TerranEngine/vendor/entt/LICENSE b/vendor/entt/LICENSE similarity index 100% rename from TerranEngine/vendor/entt/LICENSE rename to vendor/entt/LICENSE diff --git a/TerranEngine/vendor/entt/include/entt.hpp b/vendor/entt/include/entt.hpp similarity index 100% rename from TerranEngine/vendor/entt/include/entt.hpp rename to vendor/entt/include/entt.hpp From 7dae78aa7db65d70b7282a852d281e37335b5aa0 Mon Sep 17 00:00:00 2001 From: infirit89 Date: Sun, 15 Feb 2026 15:40:13 +0200 Subject: [PATCH 05/51] chore(Asset): add AssetError type to signal errors during asset loading --- Libraries/LibAsset/AssetError.h | 14 ++++++++ Libraries/LibAsset/AssetImporter.h | 15 +++++--- Libraries/LibAsset/AssetImporterError.h | 37 ++++++++++++++++++++ Libraries/LibAsset/AssetImporterRegistry.cpp | 7 ++-- Libraries/LibAsset/AssetImporterRegistry.h | 2 +- Libraries/LibAsset/AssetManager.cpp | 23 ++++++------ Libraries/LibAsset/AssetManager.h | 23 ++++++------ 7 files changed, 89 insertions(+), 32 deletions(-) create mode 100644 Libraries/LibAsset/AssetError.h create mode 100644 Libraries/LibAsset/AssetImporterError.h diff --git a/Libraries/LibAsset/AssetError.h b/Libraries/LibAsset/AssetError.h new file mode 100644 index 00000000..57d7b4b6 --- /dev/null +++ b/Libraries/LibAsset/AssetError.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +namespace Terran::Asset { + +class AssetError { +public: + virtual ~AssetError() = default; + [[nodiscard]] virtual std::string_view message() const = 0; + [[nodiscard]] virtual std::string_view source() const = 0; +}; + +} diff --git a/Libraries/LibAsset/AssetImporter.h b/Libraries/LibAsset/AssetImporter.h index ff302859..6b9e7dae 100644 --- a/Libraries/LibAsset/AssetImporter.h +++ b/Libraries/LibAsset/AssetImporter.h @@ -2,20 +2,25 @@ #include "AssetMetadata.h" #include "Asset.h" +#include "AssetTypes.h" +#include "AssetError.h" -#include #include +#include + #include namespace Terran::Asset { +using AssetLoadResult = Core::Result, Core::Shared>; + class AssetImporter { public: virtual ~AssetImporter() = default; - virtual void load(AssetMetadata const& assetInfo, Terran::Core::Shared& asset) = 0; - virtual bool save(AssetMetadata const& assetInfo, Terran::Core::Shared const& asset) = 0; - virtual bool can_handle(std::filesystem::path const& assetPath) = 0; - virtual AssetTypeId asset_type() = 0; + [[nodiscard]] virtual AssetLoadResult load(AssetMetadata const& assetMetadata) = 0; + virtual bool save(AssetMetadata const& assetMetadata, Core::Shared const& asset) = 0; + [[nodiscard]] virtual bool can_handle(std::filesystem::path const& assetPath) = 0; + [[nodiscard]] virtual AssetTypeId asset_type() = 0; }; } diff --git a/Libraries/LibAsset/AssetImporterError.h b/Libraries/LibAsset/AssetImporterError.h new file mode 100644 index 00000000..8c378e86 --- /dev/null +++ b/Libraries/LibAsset/AssetImporterError.h @@ -0,0 +1,37 @@ +#pragma once + +#include "AssetError.h" + +#include +#include + +namespace Terran::Asset { + +class AssetImporterError final : public AssetError { +public: + enum Code : uint8_t { + ImporterNotFound, + }; + + AssetImporterError(Code code) + : m_code(code) + { + } + + ~AssetImporterError() override = default; + + [[nodiscard]] std::string_view message() const override { + switch(m_code) { + case Code::ImporterNotFound: return "No importer registered for asset type"; + } + } + + [[nodiscard]] std::string_view source() const override { + return "Asset"; + } + +private: + Code m_code; +}; + +} diff --git a/Libraries/LibAsset/AssetImporterRegistry.cpp b/Libraries/LibAsset/AssetImporterRegistry.cpp index 0c027797..94a089b6 100644 --- a/Libraries/LibAsset/AssetImporterRegistry.cpp +++ b/Libraries/LibAsset/AssetImporterRegistry.cpp @@ -3,6 +3,7 @@ #include "AssetImporter.h" #include "AssetMetadata.h" #include "AssetTypes.h" +#include "AssetImporterError.h" #include #include @@ -13,15 +14,15 @@ namespace Terran::Asset { std::unordered_map> AssetImporterRegistry::s_loaders; -void AssetImporterRegistry::load(AssetMetadata const& assetMetadata, Terran::Core::Shared& asset) +AssetLoadResult AssetImporterRegistry::load(AssetMetadata const& assetMetadata) { AssetTypeId const type_id = assetMetadata.Type; if (s_loaders.contains(type_id)) { - s_loaders[type_id]->load(assetMetadata, asset); - return; + return s_loaders[type_id]->load(assetMetadata); } TR_CORE_ERROR(TR_LOG_ASSET, "Invalid asset type for asset: {0}", assetMetadata.Path); + return { Core::CreateShared(AssetImporterError::ImporterNotFound) }; } bool AssetImporterRegistry::save(AssetMetadata const& assetMetadata, Terran::Core::Shared const& asset) diff --git a/Libraries/LibAsset/AssetImporterRegistry.h b/Libraries/LibAsset/AssetImporterRegistry.h index 2e3375ed..a3626ae3 100644 --- a/Libraries/LibAsset/AssetImporterRegistry.h +++ b/Libraries/LibAsset/AssetImporterRegistry.h @@ -24,7 +24,7 @@ class AssetImporterRegistry final { s_loaders[TAsset::static_type()] = loader; } - static void load(AssetMetadata const& assetMetadata, Terran::Core::Shared& asset); + static AssetLoadResult load(AssetMetadata const& assetMetadata); static bool save(AssetMetadata const& assetMetadata, Terran::Core::Shared const& asset); static bool exists_for_path(std::filesystem::path const& path) diff --git a/Libraries/LibAsset/AssetManager.cpp b/Libraries/LibAsset/AssetManager.cpp index 95ed6ab5..68127e18 100644 --- a/Libraries/LibAsset/AssetManager.cpp +++ b/Libraries/LibAsset/AssetManager.cpp @@ -4,13 +4,13 @@ #include "AssetMetadata.h" #include "AssetMetadataRegistry.h" #include "AssetTypes.h" +#include "AssetEvents.h" +#include "AssetImporter.h" -#include "LibCore/Base.h" -#include "LibCore/FileUtils.h" -#include "LibCore/Log.h" - -#include +#include +#include #include + #include #include #include @@ -60,14 +60,15 @@ AssetHandle AssetManager::import_asset(std::filesystem::path const& assetPath) void AssetManager::reload_asset_by_handle(AssetHandle const& handle) { AssetMetadata const& info = AssetMetadataRegistry::asset_metadata_by_handle(handle); - if (!m_loaded_assets.contains(handle)) { + + if (!m_loaded_assets.contains(handle)) TR_CORE_WARN(TR_LOG_ASSET, "Trying to reload an asset that was never loaded"); - Terran::Core::Shared asset; - AssetImporterRegistry::load(info, asset); + + AssetLoadResult assetResult = AssetImporterRegistry::load(info); + if (!assetResult) return; - } - Terran::Core::Shared& asset = m_loaded_assets.at(handle); - AssetImporterRegistry::load(info, asset); + + m_loaded_assets[handle] = assetResult.value(); } void AssetManager::on_filesystem_changed(std::vector const& fileSystemEvents) diff --git a/Libraries/LibAsset/AssetManager.h b/Libraries/LibAsset/AssetManager.h index 94a6d417..d468ae8b 100644 --- a/Libraries/LibAsset/AssetManager.h +++ b/Libraries/LibAsset/AssetManager.h @@ -5,6 +5,7 @@ #include "AssetMetadata.h" #include "AssetTypes.h" +#include #include #include #include @@ -26,12 +27,12 @@ class AssetManager final { AssetManager(Core::EventDispatcher& event_dispatcher); ~AssetManager(); - std::filesystem::path filesystem_path(std::filesystem::path const& path); + std::filesystem::path filesystem_path(std::filesystem::path const& path); - AssetHandle import_asset(std::filesystem::path const& assetPath); - void reload_asset_by_handle(AssetHandle const& handle); + AssetHandle import_asset(std::filesystem::path const& assetPath); + void reload_asset_by_handle(AssetHandle const& handle); - void SetAssetChangedCallback(AssetChangeCallbackFn const& callback) { m_asset_change_callback = callback; } + void SetAssetChangedCallback(AssetChangeCallbackFn const& callback) { m_asset_change_callback = callback; } template requires(std::is_base_of_v) @@ -45,15 +46,14 @@ class AssetManager final { if (!info) return nullptr; - // NOTE: poc code - Terran::Core::Shared asset = nullptr; - AssetImporterRegistry::load(info, asset); + AssetLoadResult assetResult = AssetImporterRegistry::load(info); - if (!asset) { + if (!assetResult) { TR_CORE_ERROR(TR_LOG_ASSET, "Failed to load asset with path: {0}", info.Path); return nullptr; } + Core::Shared const& asset = assetResult.value(); asset->m_handle = assetHandle; m_loaded_assets[assetHandle] = asset; return Terran::Core::DynamicCast(m_loaded_assets[assetHandle]); @@ -66,15 +66,14 @@ class AssetManager final { if (m_loaded_assets.contains(assetMetadata.Handle)) return Terran::Core::DynamicCast(m_loaded_assets.at(assetMetadata.Handle)); - // NOTE: poc code - Terran::Core::Shared asset = nullptr; - AssetImporterRegistry::load(assetMetadata, asset); + AssetLoadResult assetResult = AssetImporterRegistry::load(assetMetadata); - if (!asset) { + if (!assetResult) { TR_CORE_ERROR(TR_LOG_ASSET, "Failed to load asset with path: {0}", assetMetadata.Path); return nullptr; } + Core::Shared const& asset = assetResult.value(); asset->m_handle = assetMetadata.Handle; m_loaded_assets[assetMetadata.Handle] = asset; return Terran::Core::DynamicCast(m_loaded_assets[assetMetadata.Handle]); From f85853f83dc6d4df4a9300ace79c281452f0081d Mon Sep 17 00:00:00 2001 From: infirit89 Date: Sun, 15 Feb 2026 15:41:24 +0200 Subject: [PATCH 06/51] chore(Scene): remove commented code --- Libraries/LibScene/Scene.cpp | 369 +---------------------------------- Libraries/LibScene/Scene.h | 54 ----- 2 files changed, 1 insertion(+), 422 deletions(-) diff --git a/Libraries/LibScene/Scene.cpp b/Libraries/LibScene/Scene.cpp index db19d220..85e7cef0 100644 --- a/Libraries/LibScene/Scene.cpp +++ b/Libraries/LibScene/Scene.cpp @@ -31,36 +31,6 @@ void CopyComponent(entt::entity srcHandle, entt::entity dstHandle, entt::registr dstRegistry.emplace_or_replace(dstHandle, srcRegistry.get(srcHandle)); } -// template<> -// void CopyComponent(entt::entity srcHandle, entt::entity dstHandle, entt::registry& srcRegistry, entt::registry& dstRegistry) -// { -// if (!srcRegistry.all_of(srcHandle)) -// return; -// -// entt::entity const srcSceneEntity = srcRegistry.view().front(); -// Terran::Core::UUID const& srcSceneID = srcRegistry.get(srcSceneEntity).SceneID; -// Terran::Core::UUID const& srcEntityID = srcRegistry.get(srcHandle).ID; -// -// Terran::Core::Shared srcScriptInstance = ScriptEngine::GetScriptInstance(srcSceneID, srcEntityID); -// if (!srcScriptInstance) { -// TR_CORE_ERROR(TR_LOG_SCRIPT, "The script instance from the source scene was null"); -// return; -// } -// dstRegistry.emplace_or_replace(dstHandle, srcRegistry.get(srcHandle)); -// -// entt::entity const dstSceneEntity = dstRegistry.view().front(); -// Terran::Core::UUID const& dstSceneID = dstRegistry.get(dstSceneEntity).SceneID; -// -// Terran::Core::UUID const& dstEntityID = dstRegistry.get(dstHandle).ID; -// -// Terran::Core::Shared dstScriptInstance = ScriptEngine::GetScriptInstance(dstSceneID, dstEntityID); -// if (!dstScriptInstance) { -// TR_CORE_ERROR(TR_LOG_SCRIPT, "The script instance from the destination scene was null"); -// return; -// } -// dstScriptInstance->CopyAllFieldsFrom(srcScriptInstance); -// } - template void CopyComponent(entt::entity srcHandle, entt::entity dstHandle, entt::registry& srcRegistry) { @@ -84,41 +54,11 @@ Scene::Scene(Terran::Core::UUID const& handle) { auto const sceneEntity = m_Registry.create(); m_Registry.emplace(sceneEntity, m_handle); - - // m_Registry.on_construct().connect<&Scene::OnScriptComponentConstructed>(this); - // m_Registry.on_destroy().connect<&Scene::OnScriptComponentDestroyed>(this); - // - // m_Registry.on_construct().connect<&Scene::OnRigidbody2DComponentConstructed>(this); - // m_Registry.on_destroy().connect<&Scene::OnRigidbody2DComponentDestroyed>(this); - // - // m_Registry.on_construct().connect<&Scene::OnBoxCollider2DComponentConstructed>(this); - // m_Registry.on_destroy().connect<&Scene::OnBoxCollider2DComponentDestroyed>(this); - // - // m_Registry.on_construct().connect<&Scene::OnCircleCollider2DComponentConstructed>(this); - // m_Registry.on_destroy().connect<&Scene::OnCircleCollider2DComponentDestroyed>(this); - // - // m_Registry.on_construct().connect<&Scene::OnCapsuleCollider2DComponentConstructed>(this); - // m_Registry.on_destroy().connect<&Scene::OnCapsuleCollider2DComponentDestroyed>(this); - // - // m_Registry.on_construct().connect<&Scene::OnTextComponentConstructed>(this); } Scene::~Scene() { - // auto scriptableComponentView = m_Registry.view(); - // - // for (auto e : scriptableComponentView) { - // Entity entity(e, this); - // ScriptEngine::DestroyScriptInstance(entity); - // } - m_Registry.clear(); - - // m_Registry.on_construct().disconnect<&Scene::OnScriptComponentConstructed>(this); - // m_Registry.on_destroy().disconnect<&Scene::OnScriptComponentDestroyed>(this); - // - // m_Registry.on_construct().disconnect<&Scene::OnRigidbody2DComponentConstructed>(this); - // m_Registry.on_destroy().disconnect<&Scene::OnRigidbody2DComponentDestroyed>(this); } Entity Scene::CreateEntity(std::string const& name) @@ -202,169 +142,12 @@ void Scene::StopRuntime() void Scene::Update(Terran::Core::Time time) { - // TR_PROFILE_FUNCN("Scene::Update"); - // TR_PROFILE_FUNCTION(); - UpdateTransformHierarchy(); - - // Physics2D::Update(time); - // - // auto scriptableComponentView = m_Registry.view(); - // for (auto e : scriptableComponentView) { - // Entity entity(e, this); - // ScriptEngine::OnUpdate(entity, time.GetDeltaTime()); } -// void Scene::UpdateEditor() -// { -// // TR_PROFILE_FUNCTION(); -// UpdateTransformHierarchy(); -// } - -// void Scene::OnResize(float width, float height) -// { -// if (m_ViewportWidth != width || m_ViewportHeight != height) { -// m_ViewportWidth = width; -// m_ViewportHeight = height; -// -// auto cameraView = m_Registry.view(); -// -// for (auto e : cameraView) { -// Entity entity(e, this); -// auto& cameraComponent = entity.GetComponent(); -// -// cameraComponent.Camera.SetViewport(width, height); -// } -// } -// } - -// void Scene::OnRender(Terran::Core::Shared const& sceneRenderer) -// { -// // TR_PROFILE_FUNCTION(); -// -// if (Entity primaryCamera = GetPrimaryCamera()) { -// glm::vec4 backgroundColor = primaryCamera.GetComponent().BackgroundColor; -// sceneRenderer->SetScene(this); -// sceneRenderer->SetClearColor(backgroundColor); -// -// Camera& camera = primaryCamera.GetComponent().Camera; -// glm::mat4& cameraTransform = primaryCamera.GetTransform().WorldSpaceTransformMatrix; -// -// sceneRenderer->BeginScene(camera, cameraTransform, true); -// -// // submit sprites -// { -// auto spriteRendererView = m_Registry.view(); -// for (auto e : spriteRendererView) { -// Entity entity(e, this); -// auto& spriteRenderer = entity.GetComponent(); -// auto& transform = entity.GetTransform(); -// -// sceneRenderer->SubmitSprite(spriteRenderer, transform.WorldSpaceTransformMatrix, (uint32_t)entity); -// } -// } -// -// // submit circles -// { -// auto circleRendererView = m_Registry.view(); -// for (auto e : circleRendererView) { -// Entity entity(e, this); -// auto& circleRenderer = entity.GetComponent(); -// auto& transform = entity.GetTransform(); -// -// sceneRenderer->SubmitCircle(circleRenderer, transform.WorldSpaceTransformMatrix, (uint32_t)(entity)); -// } -// } -// -// // submit text -// { -// auto textRendererView = m_Registry.view(); -// for (auto e : textRendererView) { -// Entity entity(e, this); -// auto& textRenderer = entity.GetComponent(); -// auto& transform = entity.GetTransform(); -// -// sceneRenderer->SubmitText(textRenderer, transform.WorldSpaceTransformMatrix, entity); -// } -// } -// -// // submit lines -// { -// auto lineRendererView = m_Registry.view(); -// -// for (auto e : lineRendererView) { -// Entity entity(e, this); -// auto& lineRenderer = entity.GetComponent(); -// -// sceneRenderer->SubmitLine(lineRenderer, (uint32_t)entity); -// } -// } -// -// sceneRenderer->EndScene(); -// } -// } - -// void Scene::OnRenderEditor(Terran::Core::Shared const& sceneRenderer, Camera& camera, glm::mat4& cameraView) -// { -// // TR_PROFILE_FUNCTION(); -// sceneRenderer->SetScene(this); -// sceneRenderer->BeginScene(camera, cameraView, false); -// -// sceneRenderer->GetFramebuffer()->SetColorAttachmentValue(1, -1); -// -// // submit sprites -// { -// auto spriteRendererView = m_Registry.view(); -// for (auto e : spriteRendererView) { -// Entity entity(e, this); -// auto& spriteRenderer = entity.GetComponent(); -// auto& transform = entity.GetTransform(); -// -// sceneRenderer->SubmitSprite(spriteRenderer, transform.WorldSpaceTransformMatrix, (uint32_t)entity); -// } -// } -// -// // submit circles -// { -// auto circleRendererView = m_Registry.view(); -// for (auto e : circleRendererView) { -// Entity entity(e, this); -// auto& circleRenderer = entity.GetComponent(); -// auto& transform = entity.GetTransform(); -// -// sceneRenderer->SubmitCircle(circleRenderer, transform.WorldSpaceTransformMatrix, (uint32_t)entity); -// } -// } -// -// // submit text -// { -// auto textRendererView = m_Registry.view(); -// for (auto e : textRendererView) { -// Entity entity(e, this); -// auto& textRenderer = entity.GetComponent(); -// auto& transform = entity.GetTransform(); -// -// sceneRenderer->SubmitText(textRenderer, transform.WorldSpaceTransformMatrix, (uint32_t)entity); -// } -// } -// -// // submit lines -// { -// auto lineRendererView = m_Registry.view(); -// for (auto e : lineRendererView) { -// Entity entity(e, this); -// auto& lineRenderer = entity.GetComponent(); -// -// sceneRenderer->SubmitLine(lineRenderer, (uint32_t)entity); -// } -// } -// -// sceneRenderer->EndScene(); -// } Entity Scene::FindEntityWithUUID(Terran::Core::UUID uuid) { - // TR_PROFILE_FUNCTION(); if (m_EntityMap.contains(uuid)) return Entity(m_EntityMap.at(uuid), this); @@ -373,7 +156,6 @@ Entity Scene::FindEntityWithUUID(Terran::Core::UUID uuid) Entity Scene::FindEntityWithName(std::string const& name) { - // TR_PROFILE_FUNCTION(); auto const tagView = m_Registry.view(); for (auto e : tagView) { @@ -385,34 +167,11 @@ Entity Scene::FindEntityWithName(std::string const& name) return {}; } -Entity Scene::GetPrimaryCamera() -{ - // auto const cameraView = m_Registry.view(); - // for (auto e : cameraView) { - // Entity entity(e, this); - // auto& cameraComponent = entity.GetComponent(); - // - // if (cameraComponent.Primary) - // return entity; - // } - - return {}; -} - Entity Scene::DuplicateEntity(Entity srcEntity, Entity parent) { Entity dstEntity = CreateEntity(srcEntity.GetName() + " Copy"); CopyComponent(srcEntity, dstEntity, m_Registry); - // CopyComponent(srcEntity, dstEntity, m_Registry); - // CopyComponent(srcEntity, dstEntity, m_Registry); - // CopyComponent(srcEntity, dstEntity, m_Registry); - // CopyComponent(srcEntity, dstEntity, m_Registry); - // CopyComponent(srcEntity, dstEntity, m_Registry); - // CopyComponent(srcEntity, dstEntity, m_Registry); - // CopyComponent(srcEntity, dstEntity, m_Registry); - // CopyComponent(srcEntity, dstEntity, m_Registry); - // CopyComponent(srcEntity, dstEntity, m_Registry); if (srcEntity.HasComponent()) { for (int i = 0; i < srcEntity.GetChildCount(); i++) { @@ -451,16 +210,7 @@ Terran::Core::Shared Scene::CopyScene(Terran::Core::Shared const& Entity dstEntity = scene->FindEntityWithUUID(srcEntity.GetID()); CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); - // CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); - // CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); - // CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); - // CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); - // CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); - // CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); - // CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); - // CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); - // CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); - // CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); + CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); } scene->SortEntities(); @@ -470,9 +220,6 @@ Terran::Core::Shared Scene::CopyScene(Terran::Core::Shared const& void Scene::UpdateTransformHierarchy() { - // TR_PROFILE_FUNCN("Scene::UpdateTransformHierarchy"); - // TR_PROFILE_FUNCTION(); - auto transformView = GetEntitiesWith(); for (auto e : transformView) { @@ -560,118 +307,4 @@ void Scene::SortEntities() m_Registry.sort([](entt::entity const& lEntity, entt::entity const& rEntity) { return lEntity < rEntity; }); } -// void Scene::OnScriptComponentConstructed(entt::registry& registry, entt::entity entityHandle) -// { -// Entity entity(entityHandle, this); -// ScriptEngine::CreateScriptInstance(entity); -// -// if (m_IsPlaying) -// ScriptEngine::OnStart(entity); -// } -// -// void Scene::OnScriptComponentDestroyed(entt::registry& registry, entt::entity entityHandle) -// { -// Entity entity(entityHandle, this); -// ScriptEngine::DestroyScriptInstance(entity); -// } -// -// void Scene::OnRigidbody2DComponentConstructed(entt::registry& registry, entt::entity entityHandle) -// { -// if (m_IsPlaying) { -// Entity entity(entityHandle, this); -// Terran::Core::Shared physicsBody = Physics2D::CreatePhysicsBody(entity); -// physicsBody->AttachColliders(); -// } -// } -// -// void Scene::OnRigidbody2DComponentDestroyed(entt::registry& registry, entt::entity entityHandle) -// { -// if (m_IsPlaying) { -// Entity entity(entityHandle, this); -// Physics2D::DestroyPhysicsBody(entity); -// } -// } -// -// void Scene::OnBoxCollider2DComponentConstructed(entt::registry& registry, entt::entity entityHandle) -// { -// Entity entity(entityHandle, this); -// if (!entity.HasComponent()) -// entity.AddComponent(); -// -// if (!m_IsPlaying) -// return; -// -// if (Terran::Core::Shared physicsBody = Physics2D::GetPhysicsBody(entity)) -// physicsBody->AddCollider(entity); -// } -// void Scene::OnBoxCollider2DComponentDestroyed(entt::registry& registry, entt::entity entityHandle) -// { -// if (m_IsPlaying) { -// Entity entity(entityHandle, this); -// Terran::Core::Shared physicsBody = Physics2D::GetPhysicsBody(entity); -// auto& bcComponent = entity.GetComponent(); -// -// if (physicsBody) -// physicsBody->RemoveCollider(bcComponent.ColliderIndex); -// } -// } -// -// void Scene::OnCircleCollider2DComponentConstructed(entt::registry& registry, entt::entity entityHandle) -// { -// Entity entity(entityHandle, this); -// if (!entity.HasComponent()) -// entity.AddComponent(); -// -// if (!m_IsPlaying) -// return; -// -// if (Terran::Core::Shared physicsBody = Physics2D::GetPhysicsBody(entity)) -// physicsBody->AddCollider(entity); -// } -// void Scene::OnCircleCollider2DComponentDestroyed(entt::registry& registry, entt::entity entityHandle) -// { -// if (m_IsPlaying) { -// Entity entity(entityHandle, this); -// auto& ccComponent = entity.GetComponent(); -// -// if (Terran::Core::Shared physicsBody = Physics2D::GetPhysicsBody(entity)) -// physicsBody->RemoveCollider(ccComponent.ColliderIndex); -// } -// } -// -// void Scene::OnCapsuleCollider2DComponentConstructed(entt::registry& registry, entt::entity entityHandle) -// { -// Entity entity(entityHandle, this); -// if (!entity.HasComponent()) -// entity.AddComponent(); -// -// if (!m_IsPlaying) -// return; -// -// if (Terran::Core::Shared physicsBody = Physics2D::GetPhysicsBody(entity)) -// physicsBody->AddCollider(entity); -// } -// void Scene::OnCapsuleCollider2DComponentDestroyed(entt::registry& registry, entt::entity entityHandle) -// { -// if (m_IsPlaying) { -// Entity entity(entityHandle, this); -// Terran::Core::Shared physicsBody = Physics2D::GetPhysicsBody(entity); -// auto& ccComponent = entity.GetComponent(); -// -// if (physicsBody) -// physicsBody->RemoveCollider(ccComponent.ColliderIndex); -// } -// } -// -// void Scene::OnTextComponentConstructed(entt::registry& registry, entt::entity entityHandle) -// { -// Entity entity(entityHandle, this); -// auto& trc = entity.GetComponent(); -// if (!trc.FontAtlas) -// trc.FontAtlas = Font::DefaultFont; -// } -// -// void Scene::OnTextComponentDestroyed(entt::registry& registry, entt::entity entityHandle) -// { -// } } diff --git a/Libraries/LibScene/Scene.h b/Libraries/LibScene/Scene.h index f60c11ad..dfd737af 100644 --- a/Libraries/LibScene/Scene.h +++ b/Libraries/LibScene/Scene.h @@ -8,8 +8,6 @@ #include "Entity.h" -// #include "Graphics/Camera.h" - #pragma warning(push) #pragma warning(disable : 26439) #include @@ -41,11 +39,6 @@ class Scene final : public Asset::Asset { void StopRuntime(); void Update(Terran::Core::Time time); - // void UpdateEditor(); - // void OnResize(float width, float height); - - // void OnRender(Terran::Core::Shared const& sceneRenderer); - // void OnRenderEditor(Terran::Core::Shared const& sceneRenderer, Camera& camera, glm::mat4& cameraView); Entity FindEntityWithUUID(Terran::Core::UUID uuid); Entity FindEntityWithName(std::string const& name); @@ -55,23 +48,6 @@ class Scene final : public Asset::Asset { std::unordered_map& GetEntityMap() { return m_EntityMap; } - // template - // std::vector Filter(Predicate&& predicate) - // { - // std::vector entities; - // auto view = m_Registry.view...>(); - // entities.reserve(view.size()); - // - // for (auto e : view) - // { - // if (predicate(view.template get>(e)...)) - // entities.push_back({ e, this }); - // } - // - // return entities; - // } - - Entity GetPrimaryCamera(); Entity DuplicateEntity(Entity srcEntity, Entity parent); Entity DuplicateEntity(Entity srcEntity); @@ -89,33 +65,6 @@ class Scene final : public Asset::Asset { void ConvertToWorldSpace(Entity entity); void SortEntities(); - glm::vec2 const& GetViewportPosition() const { return m_ViewportPosition; } - void SetViewportPosition(glm::vec2 const& viewportPosition) { m_ViewportPosition = viewportPosition; } - - float GetViewportWidth() const { return m_ViewportWidth; } - float GetViewportHeight() const { return m_ViewportHeight; } - -private: - // scripting components - void OnScriptComponentConstructed(entt::registry& registry, entt::entity entityHandle); - void OnScriptComponentDestroyed(entt::registry& registry, entt::entity entityHandle); - - // physics components - void OnRigidbody2DComponentConstructed(entt::registry& registry, entt::entity entityHandle); - void OnRigidbody2DComponentDestroyed(entt::registry& registry, entt::entity entityHandle); - - void OnBoxCollider2DComponentConstructed(entt::registry& registry, entt::entity entityHandle); - void OnBoxCollider2DComponentDestroyed(entt::registry& registry, entt::entity entityHandle); - - void OnCircleCollider2DComponentConstructed(entt::registry& registry, entt::entity entityHandle); - void OnCircleCollider2DComponentDestroyed(entt::registry& registry, entt::entity entityHandle); - - void OnCapsuleCollider2DComponentConstructed(entt::registry& registry, entt::entity entityHandle); - void OnCapsuleCollider2DComponentDestroyed(entt::registry& registry, entt::entity entityHandle); - - // text component - void OnTextComponentConstructed(entt::registry& registry, entt::entity entityHandle); - void OnTextComponentDestroyed(entt::registry& registry, entt::entity entityHandle); private: bool m_IsPlaying = false; @@ -123,12 +72,9 @@ class Scene final : public Asset::Asset { std::unordered_map m_EntityMap; entt::registry m_Registry; - glm::vec2 m_ViewportPosition = { 0.0f, 0.0f }; - float m_ViewportWidth = 1080, m_ViewportHeight = 720; friend class Entity; friend class SceneSerializer; - friend class SceneAssetLoader; }; } From 324f611063f59417907f0ab0b22151cdb78aee69 Mon Sep 17 00:00:00 2001 From: infirit89 Date: Sun, 15 Feb 2026 15:41:55 +0200 Subject: [PATCH 07/51] chore(Scene): refactor scene serializer --- Libraries/LibScene/ComponentSerializer.h | 15 + Libraries/LibScene/SceneSerializer.cpp | 500 +++------------------- Libraries/LibScene/SceneSerializer.h | 31 +- Libraries/LibScene/SceneSerializerError.h | 44 ++ 4 files changed, 128 insertions(+), 462 deletions(-) create mode 100644 Libraries/LibScene/ComponentSerializer.h create mode 100644 Libraries/LibScene/SceneSerializerError.h diff --git a/Libraries/LibScene/ComponentSerializer.h b/Libraries/LibScene/ComponentSerializer.h new file mode 100644 index 00000000..04a220f2 --- /dev/null +++ b/Libraries/LibScene/ComponentSerializer.h @@ -0,0 +1,15 @@ +#pragma once + +#include "Entity.h" +#include +#include +namespace Terran::World { + +template +concept ComponentSerializer = requires( + T serializer, YAML::Emitter& out, YAML::Node& node, Entity entity) { + { serializer.serialize(out, entity) } -> std::same_as; + { serializer.deserialize(node, entity) } -> std::same_as; +}; + +} diff --git a/Libraries/LibScene/SceneSerializer.cpp b/Libraries/LibScene/SceneSerializer.cpp index 502baefc..8df0df4c 100644 --- a/Libraries/LibScene/SceneSerializer.cpp +++ b/Libraries/LibScene/SceneSerializer.cpp @@ -1,428 +1,109 @@ #include "SceneSerializer.h" -#include "Entity.h" #include "Components.h" +#include "Entity.h" +#include "SceneSerializerError.h" -// #include "Scripting/ScriptEngine.h" - +#include #include -#include -#include #include +#include #include +#include +#include +#include #include #include #define GLM_ENABLE_EXPERIMENTAL #include -#include #include +#include using namespace Terran::Core; namespace Terran::World { -char const* SceneSerializer::SceneFilter = "Terran Scene\0*.terran\0"; -static char const* SerializerVersion = "yml1.0"; - -SceneSerializer::SceneSerializer(Core::Shared const& scene) - : m_Scene(scene) -{ -} - -#define WRITE_SCRIPT_FIELD(FieldType, Type) \ - case ScriptFieldType::FieldType: \ - out << YAML::Key << field.Name << YAML::Value << scriptInstance->GetFieldValue(fieldID); \ - break - -#define WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(FieldType, Type) \ - case ScriptFieldType::FieldType: { \ - Type value = scriptInstance->GetFieldArrayValue(array, i); \ - out << value; \ - } break - -// TODO: n dimensional arrays maybe someday in the future? -// static void SerializeScriptArray(YAML::Emitter& out, Shared scriptInstance, const ScriptArray& array, const ScriptField& field, int32_t* indices, int dimension = 0) -//{ -// int32_t length = scriptInstance->GetFieldArrayLength(array, dimension); -// if (dimension == array.Rank - 1) -// { -// out << YAML::BeginSeq; -// for (int32_t i = 0; i < length; i++) -// { -// indices[dimension] = i; -// switch (field.Type) -// { -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Bool, bool); -// // NOTE: maybe wchar_t? -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Char, char); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Int8, int8_t); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Int16, int16_t); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Int32, int32_t); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Int64, int64_t); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(UInt8, std::byte); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(UInt16, uint16_t); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(UInt32, uint32_t); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(UInt64, uint64_t); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Float, float); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Double, double); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(String, std::string); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Vector2, glm::vec2); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Vector3, glm::vec3); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Color, glm::vec4); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Entity, UUID); -// default: TR_ASSERT(false, "Unsupported field type"); break; -// } -// } -// out << YAML::EndSeq; -// } -// else -// { -// out << YAML::BeginSeq; -// for (int32_t i = 0; i < length; i++) -// { -// indices[dimension] = i; -// SerializeScriptArray(out, scriptInstance, array, field, indices, dimension + 1); -// } -// out << YAML::EndSeq; -// } -//} - -// static void SerializeScriptFields(YAML::Emitter& out, Entity entity) -// { -// ScriptComponent& sc = entity.GetComponent(); -// Terran::Core::Shared scriptInstance = ScriptEngine::GetScriptInstance(entity); -// -// for (auto& fieldID : sc.FieldHandles) { -// ScriptField field = scriptInstance->GetScriptField(fieldID); -// -// if (field.IsArray) { -// ScriptArray array = scriptInstance->GetScriptArray(fieldID); -// if (array.Rank > 1) -// continue; -// -// out << YAML::BeginMap; -// out << YAML::Key << field.Name << YAML::Value << YAML::Flow; -// out << YAML::BeginSeq; -// -// int32_t length = scriptInstance->GetFieldArrayLength(array, 0); -// for (int32_t i = 0; i < length; i++) { -// switch (field.Type) { -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Bool, bool); -// // NOTE: maybe wchar_t? -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Char, char); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Int8, int8_t); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Int16, int16_t); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Int32, int32_t); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Int64, int64_t); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(UInt8, std::byte); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(UInt16, uint16_t); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(UInt32, uint32_t); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(UInt64, uint64_t); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Float, float); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Double, double); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(String, std::string); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Vector2, glm::vec2); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Vector3, glm::vec3); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Color, glm::vec4); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Entity, Terran::Core::UUID); -// default: -// TR_ASSERT(false, "Unsupported field type"); -// break; -// } -// } -// out << YAML::EndSeq; -// out << YAML::EndMap; -// } else { -// out << YAML::BeginMap; -// switch (field.Type) { -// WRITE_SCRIPT_FIELD(Bool, bool); -// // TODO: wchar -// WRITE_SCRIPT_FIELD(Char, char); -// WRITE_SCRIPT_FIELD(Int8, int8_t); -// WRITE_SCRIPT_FIELD(Int16, int16_t); -// WRITE_SCRIPT_FIELD(Int32, int32_t); -// WRITE_SCRIPT_FIELD(Int64, int64_t); -// WRITE_SCRIPT_FIELD(UInt8, std::byte); -// WRITE_SCRIPT_FIELD(UInt16, uint16_t); -// WRITE_SCRIPT_FIELD(UInt32, uint32_t); -// WRITE_SCRIPT_FIELD(UInt64, uint64_t); -// WRITE_SCRIPT_FIELD(Float, float); -// WRITE_SCRIPT_FIELD(Double, double); -// WRITE_SCRIPT_FIELD(String, std::string); -// WRITE_SCRIPT_FIELD(Vector2, glm::vec2); -// WRITE_SCRIPT_FIELD(Vector3, glm::vec3); -// WRITE_SCRIPT_FIELD(Color, glm::vec4); -// WRITE_SCRIPT_FIELD(Entity, Terran::Core::UUID); -// default: -// TR_ASSERT(false, "Unsupported field type"); -// break; -// } -// out << YAML::EndMap; -// } -// } -// } - -#define BEGIN_COMPONENT_MAP(componentKey) \ - out << YAML::Key << componentKey; \ - out << YAML::BeginMap - -#define END_COMPONENT_MAP() \ - out << YAML::EndMap - -#define WRITE_COMPONENT_PROPERY(propertyName, property) \ - out << YAML::Key << propertyName << YAML::Value << property +static char const* SerializerVersion = "tr-0.1"; static void SerializeEntity(YAML::Emitter& out, Entity entity) { // TODO: convert to a verify assert TR_ASSERT(entity.HasComponent(), "Can't serialize an entity that doesn't have a tag component"); - out << YAML::BeginMap; - - WRITE_COMPONENT_PROPERY("Entity", entity.GetID()); - BEGIN_COMPONENT_MAP("TagComponent"); + out << YAML::BeginMap; + out << YAML::Key << "Entity" << YAML::Value << entity.GetID(); auto& tagComponent = entity.GetComponent(); - WRITE_COMPONENT_PROPERY("Tag", tagComponent.Name); - END_COMPONENT_MAP(); + out << YAML::Key << "TagComponent"; + out << YAML::BeginMap; + out << YAML::Key << "Tag" << YAML::Value << tagComponent.Name; + out << YAML::EndMap; if (entity.HasComponent()) { auto& transformComponent = entity.GetTransform(); - - BEGIN_COMPONENT_MAP("TransformComponent"); - WRITE_COMPONENT_PROPERY("Position", transformComponent.Position); - WRITE_COMPONENT_PROPERY("Scale", transformComponent.Scale); - WRITE_COMPONENT_PROPERY("Rotation", transformComponent.Rotation); - END_COMPONENT_MAP(); + out << YAML::Key << "TransformComponent"; + out << YAML::BeginMap; + out << YAML::Key << "Position" << YAML::Value << transformComponent.Position; + out << YAML::Key << "Scale" << YAML::Value << transformComponent.Scale; + out << YAML::Key << "Rotation" << YAML::Value << transformComponent.Rotation; + out << YAML::EndMap; } - // if (entity.HasComponent()) { - // auto& cameraComponent = entity.GetComponent(); - // - // BEGIN_COMPONENT_MAP("CameraComponent"); - // WRITE_COMPONENT_PROPERY("Camera", YAML::BeginMap); - // WRITE_COMPONENT_PROPERY("Size", cameraComponent.Camera.GetOrthographicSize()); - // WRITE_COMPONENT_PROPERY("Near", cameraComponent.Camera.GetOrthographicNear()); - // WRITE_COMPONENT_PROPERY("Far", cameraComponent.Camera.GetOrthographicFar()); - // out << YAML::EndMap; - // - // WRITE_COMPONENT_PROPERY("Primary", cameraComponent.Primary); - // WRITE_COMPONENT_PROPERY("ClearColor", cameraComponent.BackgroundColor); - // END_COMPONENT_MAP(); - // } - // - // if (entity.HasComponent()) { - // auto& spriteRendererComponent = entity.GetComponent(); - // - // BEGIN_COMPONENT_MAP("SpriteRendererComponent"); - // WRITE_COMPONENT_PROPERY("Color", spriteRendererComponent.Color); - // WRITE_COMPONENT_PROPERY("Texture", spriteRendererComponent.TextureHandle); - // END_COMPONENT_MAP(); - // } - // - // if (entity.HasComponent()) { - // auto& circleRendererComponent = entity.GetComponent(); - // - // BEGIN_COMPONENT_MAP("CircleRendererComponent"); - // WRITE_COMPONENT_PROPERY("Color", circleRendererComponent.Color); - // WRITE_COMPONENT_PROPERY("Thickness", circleRendererComponent.Thickness); - // END_COMPONENT_MAP(); - // } - // - // if (entity.HasComponent()) { - // auto& textRendererComponent = entity.GetComponent(); - // - // BEGIN_COMPONENT_MAP("TextRendererCompoent"); - // WRITE_COMPONENT_PROPERY("Color", textRendererComponent.TextColor); - // WRITE_COMPONENT_PROPERY("Text", textRendererComponent.Text); - // // TODO: save font handle - // END_COMPONENT_MAP(); - // } - if (entity.HasComponent()) { auto& relationshipComponent = entity.GetComponent(); - BEGIN_COMPONENT_MAP("RelationshipComponent"); - WRITE_COMPONENT_PROPERY("Children", YAML::BeginSeq); + out << YAML::Key << "RelationshipComponent"; + out << YAML::BeginMap; + + out << YAML::Key << "Children" << YAML::Value << YAML::BeginSeq; for (auto child : relationshipComponent.Children) out << child; out << YAML::EndSeq; - WRITE_COMPONENT_PROPERY("Parent", relationshipComponent.Parent); - END_COMPONENT_MAP(); - } - // if (entity.HasComponent()) { - // auto& scriptComponent = entity.GetComponent(); - // - // BEGIN_COMPONENT_MAP("ScriptComponent"); - // WRITE_COMPONENT_PROPERY("ModuleName", scriptComponent.ModuleName); - // if (!scriptComponent.FieldHandles.empty()) { - // out << YAML::Key << "Fields" << YAML::Value; - // out << YAML::BeginSeq; - // SerializeScriptFields(out, entity); - // out << YAML::EndSeq; - // } - // END_COMPONENT_MAP(); - // } - // - // if (entity.HasComponent()) { - // auto& rigidbodyComponent = entity.GetComponent(); - // - // BEGIN_COMPONENT_MAP("Rigidbody2DComponent"); - // WRITE_COMPONENT_PROPERY("BodyType", PhysicsBodyTypeToString(rigidbodyComponent.BodyType).data()); - // WRITE_COMPONENT_PROPERY("FixedRotation", rigidbodyComponent.FixedRotation); - // WRITE_COMPONENT_PROPERY("SleepState", PhysicsBodySleepStateToString(rigidbodyComponent.SleepState).data()); - // WRITE_COMPONENT_PROPERY("GravityScale", rigidbodyComponent.GravityScale); - // WRITE_COMPONENT_PROPERY("Material", rigidbodyComponent.PhysicsMaterialHandle); - // END_COMPONENT_MAP(); - // } - // - // if (entity.HasComponent()) { - // auto& boxCollider2DComponent = entity.GetComponent(); - // - // BEGIN_COMPONENT_MAP("BoxCollider2DComponent"); - // WRITE_COMPONENT_PROPERY("Offset", boxCollider2DComponent.Offset); - // WRITE_COMPONENT_PROPERY("Size", boxCollider2DComponent.Size); - // WRITE_COMPONENT_PROPERY("Sensor", boxCollider2DComponent.Sensor); - // END_COMPONENT_MAP(); - // } - // - // if (entity.HasComponent()) { - // auto& circleCollider2DComponent = entity.GetComponent(); - // - // BEGIN_COMPONENT_MAP("CircleCollider2DComponent"); - // WRITE_COMPONENT_PROPERY("Offset", circleCollider2DComponent.Offset); - // WRITE_COMPONENT_PROPERY("Radius", circleCollider2DComponent.Radius); - // WRITE_COMPONENT_PROPERY("Sensor", circleCollider2DComponent.Sensor); - // END_COMPONENT_MAP(); - // } - // - // if (entity.HasComponent()) { - // auto& capsuleColliderComponenet = entity.GetComponent(); - // - // BEGIN_COMPONENT_MAP("CapsuleCollider2DComponent"); - // WRITE_COMPONENT_PROPERY("Offset", capsuleColliderComponenet.Offset); - // WRITE_COMPONENT_PROPERY("Size", capsuleColliderComponenet.Size); - // WRITE_COMPONENT_PROPERY("Sensor", capsuleColliderComponenet.Sensor); - // END_COMPONENT_MAP(); - // } + out << YAML::Key << "Parent"; + if (relationshipComponent.Parent) + out << YAML::Value << relationshipComponent.Parent; + else + out << YAML::Null; + out << YAML::EndMap; + } out << YAML::EndMap; } -void SceneSerializer::SerializeEditor(std::filesystem::path const& scenePath) +bool SceneSerializer::save(Asset::AssetMetadata const& assetMetadata, Core::Shared const& asset) { + Core::Shared scene = Core::DynamicCast(asset); + if (!scene) + return false; + YAML::Emitter out; out << YAML::BeginMap; out << YAML::Key << "SerializerVersion" << YAML::Value << SerializerVersion; - out << YAML::Key << "Entities" << YAML::Value << YAML::BeginSeq; + out << YAML::Key << "Entities"; + out << YAML::BeginSeq; - auto const tagComponentView = m_Scene->GetEntitiesWith(); + auto const tagComponentView = scene->GetEntitiesWith(); for (auto e : tagComponentView) { - Entity entity(e, m_Scene.get()); + Entity entity(e, scene.get()); SerializeEntity(out, entity); } out << YAML::EndSeq; out << YAML::EndMap; - std::ofstream ofs(scenePath); + std::ofstream ofs(assetMetadata.Path); ofs << out.c_str(); + return true; } -#define READ_SCRIPT_FIELD(FieldType, Type) \ - case ScriptFieldType::FieldType: { \ - Type value = scriptFieldNode.as(); \ - scriptInstance->SetFieldValue(fieldID, value); \ - } break - -#define READ_SCRIPT_FIELD_ARRAY_ELEMENT(FieldType, Type) \ - case ScriptFieldType::FieldType: { \ - Type value = scriptFieldNode[i].as(); \ - scriptInstance->SetFieldArrayValue(array, value, i); \ - } break - -// static void DeserializeScriptFields(Terran::Core::Shared scriptInstance, ScriptComponent& scriptComponent, const YAML::Node& scriptFieldsNode) -// { -// for (auto const& fieldID : scriptComponent.FieldHandles) { -// ScriptField scriptField = scriptInstance->GetScriptField(fieldID); -// YAML::Node scriptFieldNode; -// bool valid = false; -// -// for (auto field : scriptFieldsNode) { -// if (field[scriptField.Name]) { -// scriptFieldNode = field[scriptField.Name]; -// valid = true; -// break; -// } -// } -// -// if (!valid) -// continue; -// -// if (scriptField.IsArray) { -// ScriptArray array = scriptInstance->GetScriptArray(fieldID); -// if (array.Rank > 1) -// continue; -// -// // TODO: n dimensional arrays someday in the future? -// int32_t length = scriptInstance->GetFieldArrayLength(array); -// for (int32_t i = 0; i < length && i < scriptFieldNode.size(); i++) { -// switch (scriptField.Type) { -// READ_SCRIPT_FIELD_ARRAY_ELEMENT(Bool, bool); -// READ_SCRIPT_FIELD_ARRAY_ELEMENT(Char, char); -// READ_SCRIPT_FIELD_ARRAY_ELEMENT(Int64, int64_t); -// READ_SCRIPT_FIELD_ARRAY_ELEMENT(Int32, int32_t); -// READ_SCRIPT_FIELD_ARRAY_ELEMENT(Int16, int16_t); -// READ_SCRIPT_FIELD_ARRAY_ELEMENT(Int8, int8_t); -// READ_SCRIPT_FIELD_ARRAY_ELEMENT(UInt64, uint64_t); -// READ_SCRIPT_FIELD_ARRAY_ELEMENT(UInt32, uint32_t); -// READ_SCRIPT_FIELD_ARRAY_ELEMENT(UInt16, uint16_t); -// READ_SCRIPT_FIELD_ARRAY_ELEMENT(UInt8, uint8_t); -// READ_SCRIPT_FIELD_ARRAY_ELEMENT(Float, float); -// READ_SCRIPT_FIELD_ARRAY_ELEMENT(Double, double); -// READ_SCRIPT_FIELD_ARRAY_ELEMENT(String, std::string); -// READ_SCRIPT_FIELD_ARRAY_ELEMENT(Vector2, glm::vec2); -// READ_SCRIPT_FIELD_ARRAY_ELEMENT(Vector3, glm::vec3); -// READ_SCRIPT_FIELD_ARRAY_ELEMENT(Color, glm::vec4); -// READ_SCRIPT_FIELD_ARRAY_ELEMENT(Entity, Terran::Core::UUID); -// default: -// TR_ASSERT(false, "Invalid script type"); -// } -// } -// } else { -// switch (scriptField.Type) { -// READ_SCRIPT_FIELD(Bool, bool); -// READ_SCRIPT_FIELD(Char, char); -// READ_SCRIPT_FIELD(Int64, int64_t); -// READ_SCRIPT_FIELD(Int32, int32_t); -// READ_SCRIPT_FIELD(Int16, int16_t); -// READ_SCRIPT_FIELD(Int8, int8_t); -// READ_SCRIPT_FIELD(UInt64, uint64_t); -// READ_SCRIPT_FIELD(UInt32, uint32_t); -// READ_SCRIPT_FIELD(UInt16, uint16_t); -// READ_SCRIPT_FIELD(UInt8, uint8_t); -// READ_SCRIPT_FIELD(Float, float); -// READ_SCRIPT_FIELD(Double, double); -// READ_SCRIPT_FIELD(String, std::string); -// READ_SCRIPT_FIELD(Vector2, glm::vec2); -// READ_SCRIPT_FIELD(Vector3, glm::vec3); -// READ_SCRIPT_FIELD(Color, glm::vec4); -// READ_SCRIPT_FIELD(Entity, Terran::Core::UUID); -// default: -// TR_ASSERT(false, "Invalid script type"); -// } -// } -// } -// } - -static YAML::Node FindEntity(YAML::Node scene, Core::UUID const& entityID) +static YAML::Node FindEntity(YAML::Node const& scene, Terran::Core::UUID const& entityID) { for (auto entity : scene) { TR_CORE_TRACE(TR_LOG_CORE, entity); - Core::UUID id = entity["Entity"].as(); + Terran::Core::UUID id = entity["Entity"].as(); if (id == entityID) return entity; } @@ -460,32 +141,6 @@ static Entity DeserializeEntity(YAML::Node data, YAML::Node scene, Core::Shared< tc.Scale = transformComponent["Scale"].as(glm::vec3(1.0f, 1.0f, 1.0f)); } - // auto cameraComponent = data["CameraComponent"]; - // if (cameraComponent) { - // auto& cc = deserializedEntity.AddComponent(); - // auto camera = cameraComponent["Camera"]; - // cc.Camera.SetOrthographicSize(camera["Size"].as(10.0f)); - // cc.Camera.SetOrthographicNear(camera["Near"].as(-10.0f)); - // cc.Camera.SetOrthographicFar(camera["Far"].as(10.0f)); - // - // cc.Primary = cameraComponent["Primary"].as(false); - // cc.BackgroundColor = cameraComponent["ClearColor"].as(glm::vec4(0.1f, 0.1f, 0.1f, 1.0f)); - // } - // - // auto spriteRendererComponent = data["SpriteRendererComponent"]; - // if (spriteRendererComponent) { - // auto& src = deserializedEntity.AddComponent(); - // src.Color = spriteRendererComponent["Color"].as(glm::vec4(1.0f, 1.0f, 1.0f, 1.0f)); - // src.TextureHandle = spriteRendererComponent["Texture"].as(Terran::Core::UUID::Invalid()); - // } - // - // auto circleRendererComponent = data["CircleRendererComponent"]; - // if (circleRendererComponent) { - // auto& crc = deserializedEntity.AddComponent(); - // crc.Color = circleRendererComponent["Color"].as(glm::vec4(1.0f, 1.0f, 1.0f, 1.0f)); - // crc.Thickness = circleRendererComponent["Thickness"].as(1.0f); - // } - auto relationshipComponent = data["RelationshipComponent"]; if (relationshipComponent) { auto& rc = deserializedEntity.AddComponent(); @@ -502,53 +157,6 @@ static Entity DeserializeEntity(YAML::Node data, YAML::Node scene, Core::Shared< } } - // auto scriptComponent = data["ScriptComponent"]; - // if (scriptComponent) { - // auto& sc = deserializedEntity.AddComponent(); - // sc.ModuleName = scriptComponent["ModuleName"].as(); - // - // Terran::Core::Shared scriptInstance = ScriptEngine::CreateScriptInstance(deserializedEntity); - // auto scriptFields = scriptComponent["Fields"]; - // if (scriptFields) - // DeserializeScriptFields(scriptInstance, sc, scriptFields); - // } - // - // auto boxColliderComponent = data["BoxCollider2DComponent"]; - // if (boxColliderComponent) { - // auto& bcc = deserializedEntity.AddComponent(); - // bcc.Offset = boxColliderComponent["Offset"].as(glm::vec2(0.0f, 0.0f)); - // bcc.Size = boxColliderComponent["Size"].as(glm::vec2(1.0f, 1.0f)); - // bcc.Sensor = boxColliderComponent["Sensor"].as(false); - // } - // - // auto circleColliderComponent = data["CircleCollider2DComponent"]; - // if (circleRendererComponent) { - // auto& ccc = deserializedEntity.AddComponent(); - // ccc.Offset = circleColliderComponent["Offset"].as(glm::vec2(0.0f, 0.0f)); - // ccc.Radius = circleColliderComponent["Radius"].as(0.5f); - // ccc.Sensor = circleColliderComponent["Sensor"].as(false); - // } - // - // auto rigidbodyComponent = data["Rigidbody2DComponent"]; - // if (rigidbodyComponent) { - // auto& rbc = deserializedEntity.AddComponent(); - // rbc.BodyType = PhysicsBodyTypeFromString(rigidbodyComponent["BodyType"].as()); - // rbc.FixedRotation = rigidbodyComponent["FixedRotation"].as(false); - // rbc.SleepState = PhysicsBodySleepStateFromString(rigidbodyComponent["SleepState"].as()); - // rbc.GravityScale = rigidbodyComponent["GravityScale"].as(1.0f); - // - // if (rigidbodyComponent["Material"]) - // rbc.PhysicsMaterialHandle = rigidbodyComponent["Material"].as(Terran::Core::UUID::Invalid()); - // } - // - // auto capsuleColliderComponent = data["CapsuleCollider2DComponent"]; - // if (capsuleColliderComponent) { - // auto& ccc = deserializedEntity.AddComponent(); - // ccc.Offset = capsuleColliderComponent["Offset"].as(glm::vec2(0.0f, 0.0f)); - // ccc.Size = capsuleColliderComponent["Size"].as(glm::vec2(0.5f, 1.0f)); - // ccc.Sensor = capsuleColliderComponent["Sensor"].as(false); - // } - return deserializedEntity; } catch (YAML::InvalidNode const& ex) { TR_CORE_ERROR(TR_LOG_ASSET, ex.what()); @@ -556,28 +164,28 @@ static Entity DeserializeEntity(YAML::Node data, YAML::Node scene, Core::Shared< } } -Core::Result SceneSerializer::DeserializeEditor(std::filesystem::path const& scenePath) +Asset::AssetLoadResult SceneSerializer::load(Asset::AssetMetadata const& assetMetadata) { YAML::Node data; try { - data = YAML::LoadFile(scenePath.string()); + data = YAML::LoadFile(assetMetadata.Path); } catch (YAML::ParserException const& ex) { - TR_CORE_ERROR(TR_LOG_ASSET, ex.what()); - return { SceneSerializationError::InvalidFormat }; + return { Core::CreateShared(SceneSerializerError::InvalidFormat, ex.what()) }; } catch (YAML::BadFile const& ex) { - TR_CORE_ERROR(TR_LOG_ASSET, ex.what()); - return { SceneSerializationError::NotFound }; + TR_CORE_ERROR(TR_LOG_CORE, ex.what()); + return { Core::CreateShared(SceneSerializerError::NotFound, ex.what()) }; } + Core::Shared scene = Core::CreateShared(assetMetadata.Handle); auto entities = data["Entities"]; if (entities) { for (auto entity : entities) { - if (!DeserializeEntity(entity, entities, m_Scene)) - return { SceneSerializationError::InvalidFormat }; + if (!DeserializeEntity(entity, entities, scene)) + return { Core::CreateShared(SceneSerializerError::InvalidFormat) }; } } - return {}; + return scene; } } diff --git a/Libraries/LibScene/SceneSerializer.h b/Libraries/LibScene/SceneSerializer.h index d5df49df..23b27b10 100644 --- a/Libraries/LibScene/SceneSerializer.h +++ b/Libraries/LibScene/SceneSerializer.h @@ -2,32 +2,31 @@ #include "Scene.h" -// #include "Core/Result.h" #include #include +#include +#include +#include +#include + #include namespace Terran::World { -enum class SceneSerializationError { - InvalidFormat, - NotFound, -}; - -class SceneSerializer final { +class SceneSerializer final : Asset::AssetImporter { public: SceneSerializer() = default; - SceneSerializer(Terran::Core::Shared const& scene); - - void SerializeEditor(std::filesystem::path const& scenePath); - Core::Result DeserializeEditor(std::filesystem::path const& scenePath); - -public: - static char const* SceneFilter; + ~SceneSerializer() override = default; + bool save(Asset::AssetMetadata const& assetMetadata, Core::Shared const& asset) override; + Asset::AssetLoadResult load(Asset::AssetMetadata const& assetMetadata) override; + [[nodiscard]] bool can_handle(std::filesystem::path const& assetPath) override { + return assetPath.extension() == ".terran"; + } + [[nodiscard]] Asset::AssetTypeId asset_type() override { + return Scene::static_type(); + } -private: - Terran::Core::Shared m_Scene; }; } diff --git a/Libraries/LibScene/SceneSerializerError.h b/Libraries/LibScene/SceneSerializerError.h new file mode 100644 index 00000000..05f21227 --- /dev/null +++ b/Libraries/LibScene/SceneSerializerError.h @@ -0,0 +1,44 @@ +#pragma once + +#include + +#include +#include +namespace Terran::World { + +class SceneSerializerError final : public Asset::AssetError { +public: + enum Code : uint8_t { + InvalidFormat, + NotFound, + }; + + SceneSerializerError(Code code, std::string_view details = "") + : m_code(code) + , m_details(details) + { + } + + ~SceneSerializerError() override = default; + + [[nodiscard]] std::string_view message() const override + { + switch (m_code) { + case InvalidFormat: + return "Scene file has invalid format"; + case NotFound: + return "Scene file was not found"; + } + } + + [[nodiscard]] std::string_view source() const override + { + return m_details; + } + +private: + Code m_code; + std::string_view m_details; +}; + +} From f81e60a96540d1356c6c92828d9f0f32a41720bf Mon Sep 17 00:00:00 2001 From: infirit89 Date: Sun, 15 Feb 2026 15:42:43 +0200 Subject: [PATCH 08/51] chore(Sandbox): add scene and asset systems as dependencies --- Sandbox/premake5.lua | 4 +++- Sandbox/src/SandboxLayer.cpp | 22 ++++++++-------------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/Sandbox/premake5.lua b/Sandbox/premake5.lua index cc20dc94..cfe84170 100644 --- a/Sandbox/premake5.lua +++ b/Sandbox/premake5.lua @@ -20,8 +20,8 @@ externalincludedirs { "%{Dependencies.spdlog.include}", "%{IncludeDirectories.imgui}", "%{Dependencies.glm.include}", - "%{IncludeDirectories.entt}", "%{IncludeDirectories.imguizmo}", + "%{Dependencies.entt.include}", "%{IncludeDirectories.optick}", "%{wks.location}/TerranEditor/vendor/FontAwesome", } @@ -30,6 +30,8 @@ links { "LibCore", "LibMain", "LibWindow", + "LibScene", + "LibAsset", } defines { diff --git a/Sandbox/src/SandboxLayer.cpp b/Sandbox/src/SandboxLayer.cpp index 20274530..ad399456 100644 --- a/Sandbox/src/SandboxLayer.cpp +++ b/Sandbox/src/SandboxLayer.cpp @@ -1,17 +1,22 @@ #include "SandboxLayer.h" +#include "LibAsset/AssetMetadata.h" +#include "LibCore/Log.h" +#include "LibScene/Components.h" #include +#include #include #include #include -#include #include +#include +#include #define GLM_ENABLE_EXPERIMENTAL #include -#include #include +#include #include #include @@ -34,18 +39,6 @@ static void TestFunc() TR_CLIENT_WARN("Some warning"); } -struct TestStruct { - virtual void test() { - TR_CLIENT_INFO("It works!"); - } -}; - -struct TestStruct2 : public TestStruct { - virtual void test() override { - TR_CLIENT_WARN("It doesn't work!!!!!"); - } -}; - SandboxLayer::SandboxLayer(Terran::Core::EventDispatcher& event_dispatcher, Terran::Core::RawPtr windowSystem) : Layer("Sandbox Layer", event_dispatcher) , m_windowSystem(windowSystem) @@ -59,6 +52,7 @@ SandboxLayer::SandboxLayer(Terran::Core::EventDispatcher& event_dispatcher, Terr auto const& window = m_windowSystem->window(windowId); event_dispatcher.handlers().connect<&SandboxLayer::on_window_close>(this); + } Core::Result SandboxLayer::on_attach() From cd8b489058f4f4a24eb5adae08feba40b041708e Mon Sep 17 00:00:00 2001 From: infirit89 Date: Sun, 15 Feb 2026 16:44:46 +0200 Subject: [PATCH 09/51] chore(Scene): add scene transition event --- Libraries/LibScene/Entity.cpp | 82 +++++--- Libraries/LibScene/Entity.h | 79 ++++--- Libraries/LibScene/Entity.inl | 46 ++-- Libraries/LibScene/Scene.cpp | 245 ++++++++++------------ Libraries/LibScene/Scene.h | 53 +++-- Libraries/LibScene/SceneEvent.h | 26 +++ Libraries/LibScene/SceneManager.cpp | 8 +- Libraries/LibScene/SceneManager.h | 12 +- Libraries/LibScene/SceneSerializer.cpp | 86 ++++---- Libraries/LibScene/SceneSerializer.h | 2 +- Libraries/LibScene/SceneSerializerError.h | 10 +- TerranEngine/src/Events/SceneEvent.h | 29 --- 12 files changed, 334 insertions(+), 344 deletions(-) create mode 100644 Libraries/LibScene/SceneEvent.h delete mode 100644 TerranEngine/src/Events/SceneEvent.h diff --git a/Libraries/LibScene/Entity.cpp b/Libraries/LibScene/Entity.cpp index 4afa9d85..4d8b9b56 100644 --- a/Libraries/LibScene/Entity.cpp +++ b/Libraries/LibScene/Entity.cpp @@ -1,70 +1,86 @@ +#include "Entity.h" #include "Scene.h" +#include "Components.h" -#include "LibCore/UUID.h" +#include + +#include namespace Terran::World { -bool Entity::Valid() const { return m_Scene->m_Registry.valid(m_Handle); } -bool Entity::operator==(Entity const& other) const { return m_Handle == other.m_Handle && m_Scene == other.m_Scene; } -bool Entity::HasParent() const { return HasComponent() ? m_Scene->FindEntityWithUUID(GetComponent().Parent) : false; } -Core::UUID const& Entity::GetSceneId() const { return m_Scene->handle(); } +bool Entity::valid() const +{ + return m_scene->m_registry.valid(m_handle); +} +bool Entity::operator==(Entity const& other) const +{ + return m_handle == other.m_handle && m_scene == other.m_scene; +} +bool Entity::has_parent() const +{ + return has_component() ? m_scene->find_entity(get_component().Parent) : false; +} +Core::UUID const& Entity::scene_id() const +{ + return m_scene->handle(); +} -Entity Entity::GetChild(uint32_t index) const +Entity Entity::child_at(uint32_t index) const { - if (!HasComponent()) + if (!has_component()) return {}; - return m_Scene->FindEntityWithUUID(GetChildren()[index]); + return m_scene->find_entity(children()[index]); } -Entity Entity::GetParent() const +Entity Entity::parent() const { - if (!HasComponent()) + if (!has_component()) return {}; - return m_Scene->FindEntityWithUUID(GetParentID()); + return m_scene->find_entity(parent_id()); } -void Entity::SetParent(Entity parent, bool forceTransformUpdate) +void Entity::set_parent(Entity parent, bool forceTransformUpdate) { - if (!HasComponent()) - AddComponent(); + if (!has_component()) + add_component(); - if (!parent.HasComponent()) - parent.AddComponent(); + if (!parent.has_component()) + parent.add_component(); - if (IsChildOf(parent)) + if (is_child_of(parent)) return; - if (parent.IsChildOf(*this)) + if (parent.is_child_of(*this)) return; - if (HasParent()) - Unparent(); + if (has_parent()) + unparent(); - auto& relComp = GetComponent(); - relComp.Parent = parent.GetID(); - parent.GetChildren().emplace_back(GetID()); + auto& relComp = get_component(); + relComp.Parent = parent.id(); + parent.children().emplace_back(id()); - m_Scene->ConvertToLocalSpace(*this); + m_scene->convert_to_local_space(*this); } -void Entity::Unparent() +void Entity::unparent() { - if (!HasComponent()) + if (!has_component()) return; - Core::UUID parentID = GetComponent().Parent; - Entity parent = m_Scene->FindEntityWithUUID(parentID); + Core::UUID parentID = get_component().Parent; + Entity parent = m_scene->find_entity(parentID); if (!parent) return; - m_Scene->ConvertToWorldSpace(*this); + m_scene->convert_to_world_space(*this); - auto const& it = std::ranges::find(parent.GetChildren(), GetID()); + auto const& it = std::ranges::find(parent.children(), id()); - if (it != parent.GetChildren().end()) - parent.GetChildren().erase(it); + if (it != parent.children().end()) + parent.children().erase(it); - SetParentID(Core::UUID({ 0 })); + set_parent_id(Core::UUID({ 0 })); // TODO: if the relationship component is no longer necessary than remove it } diff --git a/Libraries/LibScene/Entity.h b/Libraries/LibScene/Entity.h index 3531b360..929cf84a 100644 --- a/Libraries/LibScene/Entity.h +++ b/Libraries/LibScene/Entity.h @@ -9,7 +9,6 @@ #pragma warning(push) #pragma warning(disable : 4834) -#include #include #include #include @@ -24,84 +23,84 @@ class Entity final { Entity() = default; Entity(entt::entity const& handle, Scene* scene) - : m_Handle(handle) - , m_Scene(scene) + : m_handle(handle) + , m_scene(scene) { } ~Entity() = default; template - Component& AddComponent(Args&&... parameters); + Component& add_component(Args&&... parameters); template - Component& AddOrReplaceComponent(Args&&... parameters); + Component& add_or_replace_component(Args&&... parameters); template - Component& GetComponent() const; + Component& get_component() const; template - void RemoveComponent(); + void remove_component(); template - Component& TryGetComponent() const; + Component& try_get_component() const; template - bool HasComponent() const; + bool has_component() const; // visit all the components of an entity // the signiture of Func should be void(const entt::type_info) template - void Visit(Entity entity, Func func) const; + void visit(Entity entity, Func func) const; // base stuffs - Terran::Core::UUID const& GetID() const { return GetComponent().ID; } - TransformComponent& GetTransform() const { return GetComponent(); } - bool Valid() const; - std::string const& GetName() const { return GetComponent().Name; } + Terran::Core::UUID const& id() const { return get_component().ID; } + TransformComponent& transform() const { return get_component(); } + bool valid() const; + std::string const& name() const { return get_component().Name; } // operators - operator entt::entity() const { return m_Handle; } + operator entt::entity() const { return m_handle; } bool operator!=(Entity const& other) const { return !(*this == other); } - operator uint32_t() const { return static_cast(m_Handle); } - operator bool() const { return m_Handle != entt::null; } + operator uint32_t() const { return static_cast(m_handle); } + operator bool() const { return m_handle != entt::null; } bool operator==(Entity const& other) const; // relationship component stuffs - std::vector& GetChildren() const { return GetComponent().Children; } - size_t GetChildCount() const { return HasComponent() ? GetComponent().Children.size() : 0; } - Terran::Core::UUID GetParentID() const { return HasComponent() ? GetComponent().Parent : Terran::Core::UUID::invalid(); } - bool HasParent() const; + std::vector& children() const { return get_component().Children; } + size_t children_count() const { return has_component() ? get_component().Children.size() : 0; } + Terran::Core::UUID parent_id() const { return has_component() ? get_component().Parent : Terran::Core::UUID::invalid(); } + bool has_parent() const; - Terran::Core::UUID const& GetSceneId() const; + Terran::Core::UUID const& scene_id() const; - Entity GetChild(uint32_t index) const; + Entity child_at(uint32_t index) const; - void SetParentID(Terran::Core::UUID const& id) + void set_parent_id(Terran::Core::UUID const& id) { - if (!HasComponent()) + if (!has_component()) return; - auto& relComp = GetComponent(); + auto& relComp = get_component(); relComp.Parent = id; } - Entity GetParent() const; + Entity parent() const; - bool IsChildOf(Entity entity) const + bool is_child_of(Entity entity) const { - if (!HasComponent()) + if (!has_component()) return false; - if (!entity.HasComponent()) + if (!entity.has_component()) return false; - return GetParentID() == entity.GetID(); + return parent_id() == entity.id(); } - void SetParent(Entity parent, bool forceTransformUpdate = false); + void set_parent(Entity parent, bool forceTransformUpdate = false); - void Unparent(); + void unparent(); /*void Unparent(Entity parent, Entity child, bool removeRelationship) { @@ -125,20 +124,20 @@ class Entity final { } }*/ - void RemoveChild(Entity child, bool removeRelationship) + void remove_child(Entity child, bool removeRelationship) { - child.Unparent(); + child.unparent(); } - void Reparent(Entity previousParent, Entity newParent) + void reparent(Entity previousParent, Entity newParent) { - Unparent(); - SetParent(newParent); + unparent(); + set_parent(newParent); } private: - entt::entity m_Handle { entt::null }; - Scene* m_Scene = nullptr; + entt::entity m_handle { entt::null }; + Scene* m_scene = nullptr; }; } diff --git a/Libraries/LibScene/Entity.inl b/Libraries/LibScene/Entity.inl index 785300ca..a4d09878 100644 --- a/Libraries/LibScene/Entity.inl +++ b/Libraries/LibScene/Entity.inl @@ -2,63 +2,63 @@ namespace Terran::World { template -inline Component& Entity::AddComponent(Args&&... parameters) +inline Component& Entity::add_component(Args&&... parameters) { - TR_ASSERT(m_Handle != entt::null, "Ivalid entity"); + TR_ASSERT(m_handle != entt::null, "Ivalid entity"); - TR_ASSERT(!HasComponent(), "Entity already has component"); + TR_ASSERT(!has_component(), "Entity already has component"); - Component& component = m_Scene->m_Registry.emplace(m_Handle, std::forward(parameters)...); + Component& component = m_scene->m_registry.emplace(m_handle, std::forward(parameters)...); return component; } template -inline Component& Entity::AddOrReplaceComponent(Args&&... parameters) +inline Component& Entity::add_or_replace_component(Args&&... parameters) { - TR_ASSERT(m_Handle != entt::null, "Ivalid entity"); - Component& component = m_Scene->m_Registry.emplace_or_replace(m_Handle, std::forward(parameters)...); + TR_ASSERT(m_handle != entt::null, "Ivalid entity"); + Component& component = m_scene->m_registry.emplace_or_replace(m_handle, std::forward(parameters)...); return component; } template -inline Component& Entity::GetComponent() const +inline Component& Entity::get_component() const { - TR_ASSERT(m_Handle != entt::null, "Ivalid entity"); + TR_ASSERT(m_handle != entt::null, "Ivalid entity"); - TR_ASSERT(HasComponent(), "Entity doesn't have the component"); - return m_Scene->m_Registry.get(m_Handle); + TR_ASSERT(has_component(), "Entity doesn't have the component"); + return m_scene->m_registry.get(m_handle); } template -inline void Entity::RemoveComponent() +inline void Entity::remove_component() { - TR_ASSERT(m_Handle != entt::null, "Ivalid entity"); + TR_ASSERT(m_handle != entt::null, "Ivalid entity"); - TR_ASSERT(HasComponent(), "Entity doesn't have component"); + TR_ASSERT(has_component(), "Entity doesn't have component"); - m_Scene->m_Registry.remove(m_Handle); + m_scene->m_registry.remove(m_handle); } template -inline Component& Entity::TryGetComponent() const +inline Component& Entity::try_get_component() const { - TR_ASSERT(m_Handle != entt::null, "Ivalid entity"); + TR_ASSERT(m_handle != entt::null, "Ivalid entity"); - return m_Scene->m_Registry.try_get(m_Handle); + return m_scene->m_registry.try_get(m_handle); } template -inline bool Entity::HasComponent() const +inline bool Entity::has_component() const { - TR_ASSERT(m_Handle != entt::null, "Ivalid entity"); + TR_ASSERT(m_handle != entt::null, "Ivalid entity"); - return m_Scene->m_Registry.all_of(m_Handle); + return m_scene->m_registry.all_of(m_handle); } template -inline void Entity::Visit(Entity entity, Func func) const +inline void Entity::visit(Entity entity, Func func) const { - m_Scene->m_Registry.visit(entity, std::forward(func)); + m_scene->m_registry.visit(entity, std::forward(func)); } } diff --git a/Libraries/LibScene/Scene.cpp b/Libraries/LibScene/Scene.cpp index 85e7cef0..9cbe4621 100644 --- a/Libraries/LibScene/Scene.cpp +++ b/Libraries/LibScene/Scene.cpp @@ -5,6 +5,7 @@ #include "SceneManager.h" #include +#include // #include "Utils/Debug/OptickProfiler.h" // #include "Utils/Debug/Profiler.h" @@ -14,6 +15,8 @@ #define GLM_ENABLE_EXPERIMENTAL #include +#include + namespace Terran::World { struct SceneComponent final { @@ -23,7 +26,7 @@ struct SceneComponent final { namespace { template -void CopyComponent(entt::entity srcHandle, entt::entity dstHandle, entt::registry& srcRegistry, entt::registry& dstRegistry) +void copy_component(entt::entity srcHandle, entt::entity dstHandle, entt::registry& srcRegistry, entt::registry& dstRegistry) { if (!srcRegistry.all_of(srcHandle)) return; @@ -32,12 +35,12 @@ void CopyComponent(entt::entity srcHandle, entt::entity dstHandle, entt::registr } template -void CopyComponent(entt::entity srcHandle, entt::entity dstHandle, entt::registry& srcRegistry) +void copy_component(entt::entity srcHandle, entt::entity dstHandle, entt::registry& srcRegistry) { - CopyComponent(srcHandle, dstHandle, srcRegistry, srcRegistry); + copy_component(srcHandle, dstHandle, srcRegistry, srcRegistry); } -static glm::mat4 CalculateTransformMatrix(TransformComponent const& transform) +glm::mat4 calculate_transform_matrix(TransformComponent const& transform) { return glm::translate(glm::mat4(1.0f), transform.Position) * glm::toMat4(glm::quat(transform.Rotation)) * glm::scale(glm::mat4(1.0f), transform.Scale); } @@ -52,259 +55,237 @@ Scene::Scene() Scene::Scene(Terran::Core::UUID const& handle) : Asset(handle) { - auto const sceneEntity = m_Registry.create(); - m_Registry.emplace(sceneEntity, m_handle); + auto const sceneEntity = m_registry.create(); + m_registry.emplace(sceneEntity, m_handle); } Scene::~Scene() { - m_Registry.clear(); + m_registry.clear(); } -Entity Scene::CreateEntity(std::string const& name) +Entity Scene::create_entity(std::string const& name) { - return CreateEntityWithUUID(name, Terran::Core::UUID()); + return create_entity(name, Terran::Core::UUID()); } -Entity Scene::CreateEntityWithUUID(std::string const& name, Terran::Core::UUID const& uuid) +Entity Scene::create_entity(std::string const& name, Terran::Core::UUID const& uuid) { - entt::entity e = m_Registry.create(); + entt::entity e = m_registry.create(); Entity entity(e, this); - entity.AddComponent(name.empty() ? "Entity" : name, uuid); - entity.AddComponent(); + entity.add_component(name.empty() ? "Entity" : name, uuid); + entity.add_component(); - m_EntityMap[uuid] = e; + m_entity_map[uuid] = e; - SortEntities(); + sort_entities(); return entity; } -Entity Scene::CreateEmptyEntity() +Entity Scene::create_entity() { - entt::entity e = m_Registry.create(); + entt::entity e = m_registry.create(); Entity entity(e, this); return entity; } -void Scene::DestroyEntity(Entity entity, bool first) +void Scene::destrory_entity(Entity entity, bool first) { - // ScriptEngine::DestroyScriptInstance(entity); - // - // if (entity.HasComponent()) - // Physics2D::DestroyPhysicsBody(entity); - - if (entity.HasComponent()) { - if (first) { - if (entity.HasParent()) - entity.GetParent().RemoveChild(entity, false); + if (entity.has_component()) { + if (first && entity.has_parent()) { + entity.parent().remove_child(entity, false); } - for (auto eID : entity.GetChildren()) - DestroyEntity(FindEntityWithUUID(eID), false); + for (auto const& child_id : entity.children()) + destrory_entity(find_entity(child_id), false); } - auto entityIt = m_EntityMap.find(entity.GetID()); - if (entityIt != m_EntityMap.end()) - m_EntityMap.erase(entityIt); + if (auto entity_iterator = m_entity_map.find(entity.id()); entity_iterator != m_entity_map.end()) + m_entity_map.erase(entity_iterator); - m_Registry.destroy(entity); + m_registry.destroy(entity); - SortEntities(); + sort_entities(); } -void Scene::StartRuntime() +void Scene::start_runtime() { - if (m_IsPlaying) + if (m_is_playing) return; - m_IsPlaying = true; - - // Physics2D::CreatePhysicsWorld(Project::GetPhysicsSettings()); - // Physics2D::CratePhysicsBodies(this); - // - // auto scriptableComponentView = m_Registry.view(); - // - // for (auto e : scriptableComponentView) { - // Entity entity(e, this); - // ScriptEngine::OnStart(entity); - // } + m_is_playing = true; + + // TODO: fire an on start playing/start simulation event } -void Scene::StopRuntime() +void Scene::stop_runtime() { - if (!m_IsPlaying) + if (!m_is_playing) return; - m_IsPlaying = false; - // Physics2D::CleanUpPhysicsWorld(); + m_is_playing = false; + // TODO: fire an on stop playing/stop simulation event } -void Scene::Update(Terran::Core::Time time) +void Scene::update(Terran::Core::Time) { - UpdateTransformHierarchy(); + update_transform_hierarchy(); } - -Entity Scene::FindEntityWithUUID(Terran::Core::UUID uuid) +Entity Scene::find_entity(Terran::Core::UUID uuid) { - if (m_EntityMap.contains(uuid)) - return Entity(m_EntityMap.at(uuid), this); + if (m_entity_map.contains(uuid)) + return Entity(m_entity_map.at(uuid), this); return {}; } -Entity Scene::FindEntityWithName(std::string const& name) +Entity Scene::find_entity(std::string_view name) { - auto const tagView = m_Registry.view(); + auto const tag_view = m_registry.view(); - for (auto e : tagView) { + for (auto e : tag_view) { Entity entity(e, this); - if (entity.GetName() == name) + if (entity.name() == name) return entity; } return {}; } -Entity Scene::DuplicateEntity(Entity srcEntity, Entity parent) +Entity Scene::duplicate_entity(Entity source_entity, Entity parent) { - Entity dstEntity = CreateEntity(srcEntity.GetName() + " Copy"); + Entity destination_entity = create_entity(source_entity.name() + " Copy"); - CopyComponent(srcEntity, dstEntity, m_Registry); + copy_component(source_entity, destination_entity, m_registry); - if (srcEntity.HasComponent()) { - for (int i = 0; i < srcEntity.GetChildCount(); i++) { - Entity childEntity = srcEntity.GetChild(i); - DuplicateEntity(childEntity, dstEntity); + if (source_entity.has_component()) { + for (int i = 0; i < source_entity.children_count(); i++) { + Entity childEntity = source_entity.child_at(i); + duplicate_entity(childEntity, destination_entity); } if (!parent) - parent = srcEntity.GetParent(); + parent = source_entity.parent(); if (parent) - dstEntity.SetParent(parent); + destination_entity.set_parent(parent); } - return dstEntity; + return destination_entity; } -Entity Scene::DuplicateEntity(Entity srcEntity) +Entity Scene::duplicate_entity(Entity srcEntity) { - return DuplicateEntity(srcEntity, {}); + return duplicate_entity(srcEntity, {}); } -Terran::Core::Shared Scene::CopyScene(Terran::Core::Shared const& srcScene) +Terran::Core::Shared Scene::copy_scene(Terran::Core::Shared const& source_scene) { - Terran::Core::Shared scene = SceneManager::CreateEmptyScene(); + Terran::Core::Shared scene = SceneManager::create_empty_scene(); - auto tagView = srcScene->GetEntitiesWith(); - - for (auto e : tagView) { - Entity srcEntity(e, srcScene.get()); - Entity dstEntity = scene->CreateEntityWithUUID(srcEntity.GetName(), srcEntity.GetID()); - } + auto tag_view = source_scene->entities_with(); - for (auto e : tagView) { - Entity srcEntity(e, srcScene.get()); - Entity dstEntity = scene->FindEntityWithUUID(srcEntity.GetID()); + for (auto e : tag_view) { + Entity source_entity(e, source_scene.get()); + Entity destination_entity = scene->find_entity(source_entity.id()); - CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); - CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); + copy_component(source_entity, destination_entity, source_scene->m_registry, scene->m_registry); + copy_component(source_entity, destination_entity, source_scene->m_registry, scene->m_registry); } - scene->SortEntities(); + scene->sort_entities(); return scene; } -void Scene::UpdateTransformHierarchy() +void Scene::update_transform_hierarchy() { - auto transformView = GetEntitiesWith(); + auto transform_view = entities_with(); - for (auto e : transformView) { + for (auto e : transform_view) { Entity entity(e, this); - auto& transform = entity.GetTransform(); - if (!entity.HasParent()) - UpdateEntityTransform(entity); + if (!entity.has_parent()) + update_entity_transform(entity); } } -void Scene::UpdateEntityTransform(Entity entity) +void Scene::update_entity_transform(Entity entity) { - TransformComponent& tc = entity.GetComponent(); + TransformComponent& transform_component = entity.get_component(); - if (tc.IsDirty) { - if (Entity parent = entity.GetParent()) { - glm::mat4 parentTransform = parent.GetTransform().WorldSpaceTransformMatrix; - tc.WorldSpaceTransformMatrix = parentTransform * CalculateTransformMatrix(tc); - tc.LocalSpaceTransformMatrix = glm::inverse(parentTransform) * tc.WorldSpaceTransformMatrix; + if (transform_component.IsDirty) { + if (Entity parent = entity.parent()) { + glm::mat4 parentTransform = parent.transform().WorldSpaceTransformMatrix; + transform_component.WorldSpaceTransformMatrix = parentTransform * calculate_transform_matrix(transform_component); + transform_component.LocalSpaceTransformMatrix = glm::inverse(parentTransform) * transform_component.WorldSpaceTransformMatrix; } else { - tc.WorldSpaceTransformMatrix = CalculateTransformMatrix(tc); - tc.LocalSpaceTransformMatrix = tc.WorldSpaceTransformMatrix; + transform_component.WorldSpaceTransformMatrix = calculate_transform_matrix(transform_component); + transform_component.LocalSpaceTransformMatrix = transform_component.WorldSpaceTransformMatrix; } - glm::quat rotation = tc.Rotation; + glm::quat rotation = transform_component.Rotation; - tc.Forward = glm::normalize(glm::rotate(rotation, glm::vec3(0.0f, 0.0f, 1.0f))); - tc.Up = glm::normalize(glm::rotate(rotation, glm::vec3(0.0f, 1.0f, 0.0f))); - tc.Right = glm::normalize(glm::rotate(rotation, glm::vec3(1.0f, 0.0f, 0.0f))); + transform_component.Forward = glm::normalize(glm::rotate(rotation, glm::vec3(0.0f, 0.0f, 1.0f))); + transform_component.Up = glm::normalize(glm::rotate(rotation, glm::vec3(0.0f, 1.0f, 0.0f))); + transform_component.Right = glm::normalize(glm::rotate(rotation, glm::vec3(1.0f, 0.0f, 0.0f))); } - for (size_t i = 0; i < entity.GetChildCount(); i++) { - Entity currEntity = entity.GetChild(static_cast(i)); + for (size_t i = 0; i < entity.children_count(); i++) { + Entity currEntity = entity.child_at(static_cast(i)); - if (tc.IsDirty) - currEntity.GetTransform().IsDirty = true; + if (transform_component.IsDirty) + currEntity.transform().IsDirty = true; - UpdateEntityTransform(currEntity); + update_entity_transform(currEntity); } - tc.IsDirty = false; + transform_component.IsDirty = false; } -void Scene::ConvertToLocalSpace(Entity entity) +void Scene::convert_to_local_space(Entity entity) { - auto& tc = entity.GetComponent(); + auto& transform_component = entity.get_component(); - if (!entity.HasParent()) + if (!entity.has_parent()) return; - if (tc.IsDirty) - UpdateEntityTransform(entity); + if (transform_component.IsDirty) + update_entity_transform(entity); - Entity parent = entity.GetParent(); - auto& parentTransform = parent.GetTransform(); + Entity parent = entity.parent(); + auto const& parent_transform_component = parent.transform(); // NOTE: have to calculate it because at this point the local space // and world space transform matrices are equal - glm::mat4 parentWorldMatrix = parentTransform.WorldSpaceTransformMatrix; - glm::mat4 localMat = glm::inverse(parentWorldMatrix) * tc.WorldSpaceTransformMatrix; + glm::mat4 parent_world_matrix = parent_transform_component.WorldSpaceTransformMatrix; + glm::mat4 local_matrix = glm::inverse(parent_world_matrix) * transform_component.WorldSpaceTransformMatrix; - Core::Math::decompose_transform_matrix(localMat, tc.Position, tc.Rotation, tc.Scale); + Core::Math::decompose_transform_matrix(local_matrix, transform_component.Position, transform_component.Rotation, transform_component.Scale); - tc.IsDirty = true; + transform_component.IsDirty = true; } -void Scene::ConvertToWorldSpace(Entity entity) +void Scene::convert_to_world_space(Entity entity) { - auto& tc = entity.GetComponent(); + auto& transform_component = entity.get_component(); - if (!entity.HasParent()) + if (!entity.has_parent()) return; - if (tc.IsDirty) - UpdateEntityTransform(entity); + if (transform_component.IsDirty) + update_entity_transform(entity); - Core::Math::decompose_transform_matrix(tc.WorldSpaceTransformMatrix, tc.Position, tc.Rotation, tc.Scale); + Core::Math::decompose_transform_matrix(transform_component.WorldSpaceTransformMatrix, transform_component.Position, transform_component.Rotation, transform_component.Scale); - tc.IsDirty = true; + transform_component.IsDirty = true; } -void Scene::SortEntities() +void Scene::sort_entities() { - m_Registry.sort([](entt::entity const& lEntity, entt::entity const& rEntity) { return lEntity < rEntity; }); + m_registry.sort([](entt::entity const& lEntity, entt::entity const& rEntity) { return lEntity < rEntity; }); } } diff --git a/Libraries/LibScene/Scene.h b/Libraries/LibScene/Scene.h index dfd737af..3c2b100a 100644 --- a/Libraries/LibScene/Scene.h +++ b/Libraries/LibScene/Scene.h @@ -15,63 +15,62 @@ #include #include -#include #include -#include +#include namespace Terran::World { class Scene final : public Asset::Asset { public: Scene(); - Scene(Terran::Core::UUID const& handle); + explicit Scene(Terran::Core::UUID const& handle); ~Scene() override; TR_DECLARE_ASSET_TYPE(Scene) - Entity CreateEntity(std::string const& name = std::string()); - Entity CreateEntityWithUUID(std::string const& name, Terran::Core::UUID const& uuid); - Entity CreateEmptyEntity(); + Entity create_entity(std::string const& name = std::string()); + Entity create_entity(std::string const& name, Terran::Core::UUID const& uuid); + Entity create_entity(); - void DestroyEntity(Entity entity, bool first); + void destrory_entity(Entity entity, bool first); - void StartRuntime(); - void StopRuntime(); + void start_runtime(); + void stop_runtime(); - void Update(Terran::Core::Time time); + void update(Terran::Core::Time time); - Entity FindEntityWithUUID(Terran::Core::UUID uuid); - Entity FindEntityWithName(std::string const& name); + Entity find_entity(Terran::Core::UUID uuid); + Entity find_entity(std::string_view name); template - auto GetEntitiesWith(entt::exclude_t exclude = {}) { return m_Registry.view(exclude); } + auto entities_with(entt::exclude_t exclude = {}) { return m_registry.view(exclude); } - std::unordered_map& GetEntityMap() { return m_EntityMap; } + std::unordered_map& GetEntityMap() { return m_entity_map; } - Entity DuplicateEntity(Entity srcEntity, Entity parent); - Entity DuplicateEntity(Entity srcEntity); + Entity duplicate_entity(Entity srcEntity, Entity parent); + Entity duplicate_entity(Entity srcEntity); - static Terran::Core::Shared CopyScene(Terran::Core::Shared const& srcScene); + static Terran::Core::Shared copy_scene(Terran::Core::Shared const& srcScene); - bool IsPlaying() const { return m_IsPlaying; } + bool playing() const { return m_is_playing; } - Scene* GetRaw() { return this; } + Scene* raw() { return this; } - void UpdateTransformHierarchy(); - void UpdateEntityTransform(Entity entity); + void update_transform_hierarchy(); + void update_entity_transform(Entity entity); - void ConvertToLocalSpace(Entity entity); - void ConvertToWorldSpace(Entity entity); + void convert_to_local_space(Entity entity); + void convert_to_world_space(Entity entity); - void SortEntities(); + void sort_entities(); private: - bool m_IsPlaying = false; + bool m_is_playing = false; - std::unordered_map m_EntityMap; + std::unordered_map m_entity_map; - entt::registry m_Registry; + entt::registry m_registry; friend class Entity; friend class SceneSerializer; diff --git a/Libraries/LibScene/SceneEvent.h b/Libraries/LibScene/SceneEvent.h new file mode 100644 index 00000000..29fda6d1 --- /dev/null +++ b/Libraries/LibScene/SceneEvent.h @@ -0,0 +1,26 @@ +#pragma once + +#include "Scene.h" + +#include +#include + +namespace Terran::World { + +class SceneTransitionEvent { +public: + SceneTransitionEvent(Terran::Core::Weak const& oldScene, Terran::Core::Shared const& newScene) + : m_old_scene(oldScene) + , m_new_scene(newScene) + { + } + + Terran::Core::Weak old_scene() { return m_old_scene; } + Terran::Core::Shared new_scene() { return m_new_scene; } + +private: + Terran::Core::Weak m_old_scene; + Terran::Core::Shared m_new_scene; +}; + +} diff --git a/Libraries/LibScene/SceneManager.cpp b/Libraries/LibScene/SceneManager.cpp index 3c37083e..dca1c5e8 100644 --- a/Libraries/LibScene/SceneManager.cpp +++ b/Libraries/LibScene/SceneManager.cpp @@ -7,7 +7,7 @@ namespace Terran::World { Terran::Core::Shared SceneManager::s_CurrentScene; std::unordered_map> SceneManager::s_ActiveScenes; -Terran::Core::Shared SceneManager::CreateEmptyScene() +Terran::Core::Shared SceneManager::create_empty_scene() { // TODO: create memory asset??? Terran::Core::Shared scene = Terran::Core::CreateShared(); @@ -15,7 +15,7 @@ Terran::Core::Shared SceneManager::CreateEmptyScene() return scene; } -void SceneManager::RemoveScene(const Terran::Core::UUID& id) +void SceneManager::remove_scene(const Terran::Core::UUID& id) { if (s_ActiveScenes.contains(id)) s_ActiveScenes.erase(id); @@ -24,7 +24,7 @@ void SceneManager::RemoveScene(const Terran::Core::UUID& id) s_CurrentScene = nullptr; } -Terran::Core::Shared SceneManager::GetScene(const Terran::Core::UUID& id) +Terran::Core::Shared SceneManager::scene(const Terran::Core::UUID& id) { if (s_ActiveScenes.contains(id)) return s_ActiveScenes.at(id); @@ -32,7 +32,7 @@ Terran::Core::Shared SceneManager::GetScene(const Terran::Core::UUID& id) return nullptr; } -void SceneManager::SetCurrentScene(Terran::Core::Shared newScene) +void SceneManager::set_current_scene(Terran::Core::Shared newScene) { Terran::Core::UUID id({ 0 }); if (s_CurrentScene) diff --git a/Libraries/LibScene/SceneManager.h b/Libraries/LibScene/SceneManager.h index 8b14cd88..bc409b5b 100644 --- a/Libraries/LibScene/SceneManager.h +++ b/Libraries/LibScene/SceneManager.h @@ -10,15 +10,15 @@ namespace Terran::World { class SceneManager final { public: - static Terran::Core::Shared CreateEmptyScene(); - static void RemoveScene(Terran::Core::UUID const& id); + static Terran::Core::Shared create_empty_scene(); + static void remove_scene(Terran::Core::UUID const& id); - static Terran::Core::Shared GetScene(Terran::Core::UUID const& id); + static Terran::Core::Shared scene(Terran::Core::UUID const& id); - static Terran::Core::Shared const& GetCurrentScene() { return s_CurrentScene; } - static void SetCurrentScene(Terran::Core::Shared newScene); + static Terran::Core::Shared const& current_scene() { return s_CurrentScene; } + static void set_current_scene(Terran::Core::Shared newScene); - static std::unordered_map>& GetActiveScenes() { return s_ActiveScenes; } + static std::unordered_map>& active_scenes() { return s_ActiveScenes; } private: static std::unordered_map> s_ActiveScenes; diff --git a/Libraries/LibScene/SceneSerializer.cpp b/Libraries/LibScene/SceneSerializer.cpp index 8df0df4c..cf802490 100644 --- a/Libraries/LibScene/SceneSerializer.cpp +++ b/Libraries/LibScene/SceneSerializer.cpp @@ -18,51 +18,49 @@ #define GLM_ENABLE_EXPERIMENTAL #include -#include #include using namespace Terran::Core; namespace Terran::World { -static char const* SerializerVersion = "tr-0.1"; +static char const* const SerializerVersion = "tr-0.1"; -static void SerializeEntity(YAML::Emitter& out, Entity entity) +static void serialize_entity(YAML::Emitter& out, Entity entity) { - // TODO: convert to a verify assert - TR_ASSERT(entity.HasComponent(), "Can't serialize an entity that doesn't have a tag component"); + TR_ASSERT(entity.has_component(), "Can't serialize an entity that doesn't have a tag component"); out << YAML::BeginMap; - out << YAML::Key << "Entity" << YAML::Value << entity.GetID(); - auto& tagComponent = entity.GetComponent(); + out << YAML::Key << "Entity" << YAML::Value << entity.id(); + auto const& tag_component = entity.get_component(); out << YAML::Key << "TagComponent"; out << YAML::BeginMap; - out << YAML::Key << "Tag" << YAML::Value << tagComponent.Name; + out << YAML::Key << "Tag" << YAML::Value << tag_component.Name; out << YAML::EndMap; - if (entity.HasComponent()) { - auto& transformComponent = entity.GetTransform(); + if (entity.has_component()) { + auto const& transform_component = entity.transform(); out << YAML::Key << "TransformComponent"; out << YAML::BeginMap; - out << YAML::Key << "Position" << YAML::Value << transformComponent.Position; - out << YAML::Key << "Scale" << YAML::Value << transformComponent.Scale; - out << YAML::Key << "Rotation" << YAML::Value << transformComponent.Rotation; + out << YAML::Key << "Position" << YAML::Value << transform_component.Position; + out << YAML::Key << "Scale" << YAML::Value << transform_component.Scale; + out << YAML::Key << "Rotation" << YAML::Value << transform_component.Rotation; out << YAML::EndMap; } - if (entity.HasComponent()) { - auto& relationshipComponent = entity.GetComponent(); + if (entity.has_component()) { + auto const& relationship_component = entity.get_component(); out << YAML::Key << "RelationshipComponent"; out << YAML::BeginMap; out << YAML::Key << "Children" << YAML::Value << YAML::BeginSeq; - for (auto child : relationshipComponent.Children) + for (auto child : relationship_component.Children) out << child; out << YAML::EndSeq; out << YAML::Key << "Parent"; - if (relationshipComponent.Parent) - out << YAML::Value << relationshipComponent.Parent; + if (relationship_component.Parent) + out << YAML::Value << relationship_component.Parent; else out << YAML::Null; @@ -85,10 +83,10 @@ bool SceneSerializer::save(Asset::AssetMetadata const& assetMetadata, Core::Shar out << YAML::Key << "Entities"; out << YAML::BeginSeq; - auto const tagComponentView = scene->GetEntitiesWith(); + auto const tagComponentView = scene->entities_with(); for (auto e : tagComponentView) { Entity entity(e, scene.get()); - SerializeEntity(out, entity); + serialize_entity(out, entity); } out << YAML::EndSeq; @@ -99,7 +97,7 @@ bool SceneSerializer::save(Asset::AssetMetadata const& assetMetadata, Core::Shar return true; } -static YAML::Node FindEntity(YAML::Node const& scene, Terran::Core::UUID const& entityID) +static YAML::Node find_entity(YAML::Node const& scene, Terran::Core::UUID const& entityID) { for (auto entity : scene) { TR_CORE_TRACE(TR_LOG_CORE, entity); @@ -111,7 +109,7 @@ static YAML::Node FindEntity(YAML::Node const& scene, Terran::Core::UUID const& return {}; } -static Entity DeserializeEntity(YAML::Node data, YAML::Node scene, Core::Shared deserializedScene) +static Entity deserialize_entity(YAML::Node const& data, YAML::Node const& scene, Core::Shared deserializedScene) { try { Core::UUID id = data["Entity"].as(); @@ -120,7 +118,7 @@ static Entity DeserializeEntity(YAML::Node data, YAML::Node scene, Core::Shared< return {}; } - Entity entity = deserializedScene->FindEntityWithUUID(id); + Entity entity = deserializedScene->find_entity(id); if (entity) return entity; @@ -131,29 +129,27 @@ static Entity DeserializeEntity(YAML::Node data, YAML::Node scene, Core::Shared< } std::string name = tagComponent["Tag"].as(); - Entity deserializedEntity = deserializedScene->CreateEntityWithUUID(name, id); - - auto transformComponent = data["TransformComponent"]; - if (transformComponent) { - auto& tc = deserializedEntity.GetTransform(); - tc.Position = transformComponent["Position"].as(glm::vec3(0.0f, 0.0f, 0.0f)); - tc.Rotation = transformComponent["Rotation"].as(glm::vec3(0.0f, 0.0f, 0.0f)); - tc.Scale = transformComponent["Scale"].as(glm::vec3(1.0f, 1.0f, 1.0f)); + Entity deserializedEntity = deserializedScene->create_entity(name, id); + + if (auto transform_component_node = data["TransformComponent"]; transform_component_node) { + auto& transform_component = deserializedEntity.transform(); + transform_component.Position = transform_component_node["Position"].as(glm::vec3(0.0f, 0.0f, 0.0f)); + transform_component.Rotation = transform_component_node["Rotation"].as(glm::vec3(0.0f, 0.0f, 0.0f)); + transform_component.Scale = transform_component_node["Scale"].as(glm::vec3(1.0f, 1.0f, 1.0f)); } - auto relationshipComponent = data["RelationshipComponent"]; - if (relationshipComponent) { - auto& rc = deserializedEntity.AddComponent(); - for (auto childID : relationshipComponent["Children"]) { + if (auto relationship_component_node = data["RelationshipComponent"]; relationship_component_node) { + deserializedEntity.add_component(); + for (auto childID : relationship_component_node["Children"]) { Core::UUID deserializedChildID = childID.as(); - Entity child = deserializedScene->FindEntityWithUUID(deserializedChildID); + Entity child = deserializedScene->find_entity(deserializedChildID); if (!child) { - YAML::Node childNode = FindEntity(scene, deserializedChildID); - child = DeserializeEntity(childNode, scene, deserializedScene); + YAML::Node childNode = find_entity(scene, deserializedChildID); + child = deserialize_entity(childNode, scene, deserializedScene); } if (child) - child.SetParent(deserializedEntity, true); + child.set_parent(deserializedEntity, true); } } @@ -170,18 +166,18 @@ Asset::AssetLoadResult SceneSerializer::load(Asset::AssetMetadata const& assetMe try { data = YAML::LoadFile(assetMetadata.Path); } catch (YAML::ParserException const& ex) { - return { Core::CreateShared(SceneSerializerError::InvalidFormat, ex.what()) }; + return { Core::CreateShared(SceneSerializerError::Code::InvalidFormat, ex.what()) }; } catch (YAML::BadFile const& ex) { TR_CORE_ERROR(TR_LOG_CORE, ex.what()); - return { Core::CreateShared(SceneSerializerError::NotFound, ex.what()) }; + return { Core::CreateShared(SceneSerializerError::Code::NotFound, ex.what()) }; } Core::Shared scene = Core::CreateShared(assetMetadata.Handle); - auto entities = data["Entities"]; - if (entities) { + + if (auto entities = data["Entities"]; entities) { for (auto entity : entities) { - if (!DeserializeEntity(entity, entities, scene)) - return { Core::CreateShared(SceneSerializerError::InvalidFormat) }; + if (!deserialize_entity(entity, entities, scene)) + return { Core::CreateShared(SceneSerializerError::Code::InvalidFormat) }; } } diff --git a/Libraries/LibScene/SceneSerializer.h b/Libraries/LibScene/SceneSerializer.h index 23b27b10..9ee581cf 100644 --- a/Libraries/LibScene/SceneSerializer.h +++ b/Libraries/LibScene/SceneSerializer.h @@ -14,7 +14,7 @@ namespace Terran::World { -class SceneSerializer final : Asset::AssetImporter { +class SceneSerializer final : public Asset::AssetImporter { public: SceneSerializer() = default; ~SceneSerializer() override = default; diff --git a/Libraries/LibScene/SceneSerializerError.h b/Libraries/LibScene/SceneSerializerError.h index 05f21227..cd74accc 100644 --- a/Libraries/LibScene/SceneSerializerError.h +++ b/Libraries/LibScene/SceneSerializerError.h @@ -8,12 +8,12 @@ namespace Terran::World { class SceneSerializerError final : public Asset::AssetError { public: - enum Code : uint8_t { + enum class Code : uint8_t { InvalidFormat, NotFound, }; - SceneSerializerError(Code code, std::string_view details = "") + explicit SceneSerializerError(Code code, std::string_view details = "") : m_code(code) , m_details(details) { @@ -24,11 +24,13 @@ class SceneSerializerError final : public Asset::AssetError { [[nodiscard]] std::string_view message() const override { switch (m_code) { - case InvalidFormat: + case Code::InvalidFormat: return "Scene file has invalid format"; - case NotFound: + case Code::NotFound: return "Scene file was not found"; } + + return "Unknown error"; } [[nodiscard]] std::string_view source() const override diff --git a/TerranEngine/src/Events/SceneEvent.h b/TerranEngine/src/Events/SceneEvent.h deleted file mode 100644 index d8c63c6f..00000000 --- a/TerranEngine/src/Events/SceneEvent.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include "LibCore/Event.h" -#include "LibCore/Base.h" - -#include "Scene/Scene.h" - -namespace TerranEngine { - -class SceneTransitionEvent final : public Terran::Core::Event { -public: - SceneTransitionEvent(Terran::Core::Weak const& oldScene, Terran::Core::Shared const& newScene) - : m_OldScene(oldScene) - , m_NewScene(newScene) - { - } - - EVENT_CLASS_TYPE(SceneTransitionEvent) - EVENT_CLASS_CATEGORY(EventCategoryApplication) - - Terran::Core::Weak GetOldScene() { return m_OldScene; } - Terran::Core::Shared GetNewScene() { return m_NewScene; } - -private: - Terran::Core::Weak m_OldScene; - Terran::Core::Shared m_NewScene; -}; - -} From dac51954e5cf8532107cbc5883254c90d971c8c8 Mon Sep 17 00:00:00 2001 From: infirit89 Date: Sun, 15 Feb 2026 23:27:45 +0200 Subject: [PATCH 10/51] chore(Scene): fix sonarcloud issues --- Libraries/LibScene/Components.h | 125 +------------------------------- Libraries/LibScene/Entity.cpp | 14 ++-- Libraries/LibScene/Entity.h | 83 ++++++++++++--------- 3 files changed, 59 insertions(+), 163 deletions(-) diff --git a/Libraries/LibScene/Components.h b/Libraries/LibScene/Components.h index d39b5580..78cfac45 100644 --- a/Libraries/LibScene/Components.h +++ b/Libraries/LibScene/Components.h @@ -3,11 +3,6 @@ #include #include -// #include "Graphics/Font.h" -// #include "Graphics/OrthographicCamera.h" -// -// #include "Physics/PhysicsStates.h" - #include #define GLM_ENABLE_EXPERIMENTAL #include @@ -26,7 +21,7 @@ struct TagComponent { { } - TagComponent(std::string const& name) + explicit TagComponent(std::string const& name) : Name(name) { } @@ -54,123 +49,11 @@ struct TransformComponent { glm::mat4 LocalSpaceTransformMatrix = glm::mat4(1.0f); }; -// struct CameraComponent { -// CameraComponent() = default; -// -// OrthographicCamera Camera; -// bool Primary = true; -// -// glm::vec4 BackgroundColor = { 0.1f, 0.1f, 0.1f, 1.0f }; -// }; -// -// struct SpriteRendererComponent { -// SpriteRendererComponent() = default; -// -// glm::vec4 Color = { 1.0f, 1.0f, 1.0f, 1.0f }; -// Terran::Core::UUID TextureHandle = Terran::Core::UUID::invalid(); -// -// int ZIndex = 0; -// }; -// -// struct CircleRendererComponent { -// CircleRendererComponent() = default; -// -// glm::vec4 Color = { 1.0f, 1.0f, 1.0f, 1.0f }; -// float Thickness = 1.0f; -// }; -// -// // bullshit; fix -// struct LineRendererComponent { -// LineRendererComponent() = default; -// -// glm::vec4 Color = { 1.0f, 1.0f, 1.0f, 1.0f }; -// float Thickness = 1.0f; -// -// glm::vec3 StartPoint = { 0.0f, 0.0f, 0.0f }; -// glm::vec3 EndPoint = { 0.0f, 0.0f, 1.0f }; -// }; -// -// struct TextRendererComponent { -// TextRendererComponent() = default; -// ~TextRendererComponent() = default; -// -// Terran::Core::Shared FontAtlas; -// glm::vec4 TextColor = { 1.0f, 1.0f, 1.0f, 1.0f }; -// std::string Text = ""; -// float LineSpacing = 1.0f; -// float LineWidth = 10.0f; -// }; - struct RelationshipComponent { - RelationshipComponent() - : Parent({ 0 }) - { - } + RelationshipComponent() = default; - Terran::Core::UUID Parent; - std::vector Children; + Core::UUID Parent = Core::UUID::invalid(); + std::vector Children; }; -// struct ScriptComponent { -// ScriptComponent() = default; -// -// ScriptComponent(std::string const& moduleName) -// : ModuleName(moduleName) -// { -// } -// -// // NOTE: think about having an array of scripts so that one entity -// // "can" have more than one script (because of the 1 component of a type per entity) -// -// std::string ModuleName; -// std::vector FieldHandles; -// -// Terran::Core::UUID ScriptSourceHandle = Terran::Core::UUID::invalid(); -// bool ClassExists = true; -// }; -// -// struct Rigidbody2DComponent { -// Rigidbody2DComponent() = default; -// -// PhysicsBodyType BodyType = PhysicsBodyType::Dynamic; -// PhysicsBodySleepState SleepState = PhysicsBodySleepState::Awake; -// -// bool FixedRotation = false; -// float GravityScale = 1.0f; -// bool Enabled = true; -// int LayerIndex = 0; -// -// Terran::Core::UUID PhysicsMaterialHandle = Terran::Core::UUID::invalid(); -// }; -// -// struct BoxCollider2DComponent { -// BoxCollider2DComponent() = default; -// -// glm::vec2 Offset = { 0.0f, 0.0f }; -// glm::vec2 Size = { 1.0f, 1.0f }; -// bool Sensor = false; -// -// uint32_t ColliderIndex = 0; -// }; -// -// struct CircleCollider2DComponent { -// CircleCollider2DComponent() = default; -// -// glm::vec2 Offset = { 0.0f, 0.0f }; -// float Radius = 0.5f; -// bool Sensor = false; -// -// uint32_t ColliderIndex = 0; -// }; -// -// struct CapsuleCollider2DComponent { -// CapsuleCollider2DComponent() = default; -// -// glm::vec2 Offset = { 0.0f, 0.0f }; -// glm::vec2 Size = { 0.5f, 1.0f }; -// bool Sensor = false; -// -// uint32_t ColliderIndex = 0; -// }; - } diff --git a/Libraries/LibScene/Entity.cpp b/Libraries/LibScene/Entity.cpp index 4d8b9b56..a2a897ab 100644 --- a/Libraries/LibScene/Entity.cpp +++ b/Libraries/LibScene/Entity.cpp @@ -40,7 +40,7 @@ Entity Entity::parent() const return m_scene->find_entity(parent_id()); } -void Entity::set_parent(Entity parent, bool forceTransformUpdate) +void Entity::set_parent(Entity parent) { if (!has_component()) add_component(); @@ -56,8 +56,8 @@ void Entity::set_parent(Entity parent, bool forceTransformUpdate) if (has_parent()) unparent(); - auto& relComp = get_component(); - relComp.Parent = parent.id(); + auto& relationship_component = get_component(); + relationship_component.Parent = parent.id(); parent.children().emplace_back(id()); m_scene->convert_to_local_space(*this); @@ -75,10 +75,10 @@ void Entity::unparent() m_scene->convert_to_world_space(*this); - auto const& it = std::ranges::find(parent.children(), id()); - - if (it != parent.children().end()) - parent.children().erase(it); + if (auto const& iterator = std::ranges::find(parent.children(), id()); + iterator != parent.children().end()) { + parent.children().erase(iterator); + } set_parent_id(Core::UUID({ 0 })); diff --git a/Libraries/LibScene/Entity.h b/Libraries/LibScene/Entity.h index 929cf84a..236a0fdc 100644 --- a/Libraries/LibScene/Entity.h +++ b/Libraries/LibScene/Entity.h @@ -54,22 +54,57 @@ class Entity final { void visit(Entity entity, Func func) const; // base stuffs - Terran::Core::UUID const& id() const { return get_component().ID; } - TransformComponent& transform() const { return get_component(); } + Terran::Core::UUID const& id() const + { + return get_component().ID; + } + + TransformComponent& transform() const + { + return get_component(); + } + bool valid() const; - std::string const& name() const { return get_component().Name; } + + std::string const& name() const + { + return get_component().Name; + } // operators - operator entt::entity() const { return m_handle; } - bool operator!=(Entity const& other) const { return !(*this == other); } - operator uint32_t() const { return static_cast(m_handle); } - operator bool() const { return m_handle != entt::null; } + operator entt::entity() const + { + return m_handle; + } + + operator uint32_t() const + { + return static_cast(m_handle); + } + + operator bool() const + { + return m_handle != entt::null; + } + bool operator==(Entity const& other) const; // relationship component stuffs - std::vector& children() const { return get_component().Children; } - size_t children_count() const { return has_component() ? get_component().Children.size() : 0; } - Terran::Core::UUID parent_id() const { return has_component() ? get_component().Parent : Terran::Core::UUID::invalid(); } + std::vector& children() const + { + return get_component().Children; + } + + size_t children_count() const + { + return has_component() ? get_component().Children.size() : 0; + } + + Terran::Core::UUID parent_id() const + { + return has_component() ? get_component().Parent : Terran::Core::UUID::invalid(); + } + bool has_parent() const; Terran::Core::UUID const& scene_id() const; @@ -98,38 +133,16 @@ class Entity final { return parent_id() == entity.id(); } - void set_parent(Entity parent, bool forceTransformUpdate = false); + void set_parent(Entity parent); void unparent(); - /*void Unparent(Entity parent, Entity child, bool removeRelationship) - { - if (!parent.HasComponent()) - return; - - if (!child.HasComponent()) - return; - - const auto& it = std::find(parent.GetChildren().begin(), parent.GetChildren().end(), child.GetID()); - - if (it != parent.GetChildren().end()) - parent.GetChildren().erase(it); - - if (removeRelationship) - child.RemoveComponent(); - else - { - RelationshipComponent& rc = child.GetComponent(); - rc.ParentID = UUID({ 0 }); - } - }*/ - - void remove_child(Entity child, bool removeRelationship) + void remove_child(Entity child) { child.unparent(); } - void reparent(Entity previousParent, Entity newParent) + void reparent(Entity newParent) { unparent(); set_parent(newParent); From 7f703d43c7b5b5effdd0da5c71fe2410b3fd4be0 Mon Sep 17 00:00:00 2001 From: infirit89 Date: Sun, 15 Feb 2026 23:33:13 +0200 Subject: [PATCH 11/51] chore(Core): fix sonarcloud issues --- Libraries/LibCore/SerializerExtensions.cpp | 38 ---------------------- Libraries/LibCore/SerializerExtensions.h | 37 ++++++++++++++++++--- 2 files changed, 32 insertions(+), 43 deletions(-) delete mode 100644 Libraries/LibCore/SerializerExtensions.cpp diff --git a/Libraries/LibCore/SerializerExtensions.cpp b/Libraries/LibCore/SerializerExtensions.cpp deleted file mode 100644 index 054e93c2..00000000 --- a/Libraries/LibCore/SerializerExtensions.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "SerializerExtensions.h" - -namespace Terran::Core { - -YAML::Emitter& operator<<(YAML::Emitter& out, glm::vec2 const& v) -{ - out << YAML::Flow; - out << YAML::BeginSeq << v.x << v.y << YAML::EndSeq; - return out; -} - -YAML::Emitter& operator<<(YAML::Emitter& out, glm::vec3 const& v) -{ - out << YAML::Flow; - out << YAML::BeginSeq << v.x << v.y << v.z << YAML::EndSeq; - return out; -} - -YAML::Emitter& operator<<(YAML::Emitter& out, glm::vec4 const& v) -{ - out << YAML::Flow; - out << YAML::BeginSeq << v.x << v.y << v.z << v.w << YAML::EndSeq; - return out; -} - -YAML::Emitter& operator<<(YAML::Emitter& out, Terran::Core::UUID const& v) -{ - out << std::to_string(v); - return out; -} - -YAML::Emitter& operator<<(YAML::Emitter& out, std::byte v) -{ - out << static_cast(v); - return out; -} - -} diff --git a/Libraries/LibCore/SerializerExtensions.h b/Libraries/LibCore/SerializerExtensions.h index 85f04817..929669ec 100644 --- a/Libraries/LibCore/SerializerExtensions.h +++ b/Libraries/LibCore/SerializerExtensions.h @@ -8,11 +8,38 @@ namespace Terran::Core { -YAML::Emitter& operator<<(YAML::Emitter& out, glm::vec2 const& v); -YAML::Emitter& operator<<(YAML::Emitter& out, glm::vec3 const& v); -YAML::Emitter& operator<<(YAML::Emitter& out, glm::vec4 const& v); -YAML::Emitter& operator<<(YAML::Emitter& out, Terran::Core::UUID const& v); -YAML::Emitter& operator<<(YAML::Emitter& out, std::byte v); +struct CustomConversionOperators { + friend YAML::Emitter& operator<<(YAML::Emitter& out, glm::vec2 const& v) + { + out << YAML::Flow; + out << YAML::BeginSeq << v.x << v.y << YAML::EndSeq; + return out; + } + + friend YAML::Emitter& operator<<(YAML::Emitter& out, glm::vec3 const& v) + { + out << YAML::Flow; + out << YAML::BeginSeq << v.x << v.y << v.z << YAML::EndSeq; + return out; + } + + friend YAML::Emitter& operator<<(YAML::Emitter& out, glm::vec4 const& v) + { + out << YAML::Flow; + out << YAML::BeginSeq << v.x << v.y << v.z << v.w << YAML::EndSeq; + return out; + } + friend YAML::Emitter& operator<<(YAML::Emitter& out, Terran::Core::UUID const& v) + { + out << std::to_string(v); + return out; + } + friend YAML::Emitter& operator<<(YAML::Emitter& out, std::byte v) + { + out << static_cast(v); + return out; + } +}; } From c2247734521f89999d4b4ae9aad0dbfe75eb9268 Mon Sep 17 00:00:00 2001 From: infirit89 Date: Sun, 15 Feb 2026 23:57:16 +0200 Subject: [PATCH 12/51] chore(Core): fix serializer build errors --- Libraries/LibCore/SerializerExtensions.h | 66 ++++++++++++++++++++---- Libraries/LibScene/Scene.cpp | 2 +- Libraries/LibScene/SceneSerializer.cpp | 8 +-- 3 files changed, 60 insertions(+), 16 deletions(-) diff --git a/Libraries/LibCore/SerializerExtensions.h b/Libraries/LibCore/SerializerExtensions.h index 929669ec..0de8d1fe 100644 --- a/Libraries/LibCore/SerializerExtensions.h +++ b/Libraries/LibCore/SerializerExtensions.h @@ -8,37 +8,81 @@ namespace Terran::Core { -struct CustomConversionOperators { - friend YAML::Emitter& operator<<(YAML::Emitter& out, glm::vec2 const& v) +struct Vec2ToYaml { + explicit Vec2ToYaml(glm::vec2 const& vec) + : data(vec) + { + } + friend YAML::Emitter& operator<<(YAML::Emitter& out, Vec2ToYaml const& v) { out << YAML::Flow; - out << YAML::BeginSeq << v.x << v.y << YAML::EndSeq; + out << YAML::BeginSeq << v.data.x << v.data.y << YAML::EndSeq; return out; } - friend YAML::Emitter& operator<<(YAML::Emitter& out, glm::vec3 const& v) + glm::vec2 const& data; +}; + +struct Vec3ToYaml { + + explicit Vec3ToYaml(glm::vec3 const& vec) + : data(vec) + { + } + friend YAML::Emitter& operator<<(YAML::Emitter& out, Vec3ToYaml const& v) { out << YAML::Flow; - out << YAML::BeginSeq << v.x << v.y << v.z << YAML::EndSeq; + out << YAML::BeginSeq << v.data.x << v.data.y << v.data.z << YAML::EndSeq; return out; } - friend YAML::Emitter& operator<<(YAML::Emitter& out, glm::vec4 const& v) + glm::vec3 const& data; +}; + +struct Vec4ToYaml { + + explicit Vec4ToYaml(glm::vec4 const& vec) + : data(vec) + { + } + friend YAML::Emitter& operator<<(YAML::Emitter& out, Vec4ToYaml const& v) { out << YAML::Flow; - out << YAML::BeginSeq << v.x << v.y << v.z << v.w << YAML::EndSeq; + out << YAML::BeginSeq << v.data.x << v.data.y << v.data.z << v.data.w << YAML::EndSeq; return out; } - friend YAML::Emitter& operator<<(YAML::Emitter& out, Terran::Core::UUID const& v) + + glm::vec4 data; +}; + +struct UUIDToYaml { + + explicit UUIDToYaml(Core::UUID const& id) + : data(id) { - out << std::to_string(v); + } + friend YAML::Emitter& operator<<(YAML::Emitter& out, UUIDToYaml const& id) + { + out << std::to_string(id.data); return out; } - friend YAML::Emitter& operator<<(YAML::Emitter& out, std::byte v) + + Core::UUID const& data; +}; +struct ByteToYaml { + + explicit ByteToYaml(std::byte const& byte) + : data(byte) + { + } + + friend YAML::Emitter& operator<<(YAML::Emitter& out, ByteToYaml const& byte) { - out << static_cast(v); + out << static_cast(byte.data); return out; } + + std::byte const& data; }; } diff --git a/Libraries/LibScene/Scene.cpp b/Libraries/LibScene/Scene.cpp index 9cbe4621..c65a6e7b 100644 --- a/Libraries/LibScene/Scene.cpp +++ b/Libraries/LibScene/Scene.cpp @@ -94,7 +94,7 @@ void Scene::destrory_entity(Entity entity, bool first) { if (entity.has_component()) { if (first && entity.has_parent()) { - entity.parent().remove_child(entity, false); + entity.parent().remove_child(entity); } for (auto const& child_id : entity.children()) diff --git a/Libraries/LibScene/SceneSerializer.cpp b/Libraries/LibScene/SceneSerializer.cpp index cf802490..266b7e88 100644 --- a/Libraries/LibScene/SceneSerializer.cpp +++ b/Libraries/LibScene/SceneSerializer.cpp @@ -41,9 +41,9 @@ static void serialize_entity(YAML::Emitter& out, Entity entity) auto const& transform_component = entity.transform(); out << YAML::Key << "TransformComponent"; out << YAML::BeginMap; - out << YAML::Key << "Position" << YAML::Value << transform_component.Position; - out << YAML::Key << "Scale" << YAML::Value << transform_component.Scale; - out << YAML::Key << "Rotation" << YAML::Value << transform_component.Rotation; + out << YAML::Key << "Position" << YAML::Value << Vec3ToYaml(transform_component.Position); + out << YAML::Key << "Scale" << YAML::Value << Vec3ToYaml(transform_component.Scale); + out << YAML::Key << "Rotation" << YAML::Value << Vec3ToYaml(transform_component.Rotation); out << YAML::EndMap; } @@ -149,7 +149,7 @@ static Entity deserialize_entity(YAML::Node const& data, YAML::Node const& scene } if (child) - child.set_parent(deserializedEntity, true); + child.set_parent(deserializedEntity); } } From 8b647752b4a9e371216dc252f1ee63b0e9a6d2d7 Mon Sep 17 00:00:00 2001 From: infirit89 Date: Mon, 16 Feb 2026 00:11:21 +0200 Subject: [PATCH 13/51] chore(Scene): fix more sonarcloud issues --- Libraries/LibScene/Entity.cpp | 13 +++++-------- Libraries/LibScene/Entity.h | 12 ++++++------ Libraries/LibScene/Scene.cpp | 22 +++++++++++++++------- Libraries/LibScene/SceneSerializer.cpp | 17 +++++++++-------- 4 files changed, 35 insertions(+), 29 deletions(-) diff --git a/Libraries/LibScene/Entity.cpp b/Libraries/LibScene/Entity.cpp index a2a897ab..4d15dff1 100644 --- a/Libraries/LibScene/Entity.cpp +++ b/Libraries/LibScene/Entity.cpp @@ -1,6 +1,6 @@ #include "Entity.h" -#include "Scene.h" #include "Components.h" +#include "Scene.h" #include @@ -12,13 +12,12 @@ bool Entity::valid() const { return m_scene->m_registry.valid(m_handle); } -bool Entity::operator==(Entity const& other) const -{ - return m_handle == other.m_handle && m_scene == other.m_scene; -} + bool Entity::has_parent() const { - return has_component() ? m_scene->find_entity(get_component().Parent) : false; + return has_component() + ? (bool)m_scene->find_entity(get_component().Parent) + : false; } Core::UUID const& Entity::scene_id() const { @@ -81,8 +80,6 @@ void Entity::unparent() } set_parent_id(Core::UUID({ 0 })); - - // TODO: if the relationship component is no longer necessary than remove it } } diff --git a/Libraries/LibScene/Entity.h b/Libraries/LibScene/Entity.h index 236a0fdc..2b4ba582 100644 --- a/Libraries/LibScene/Entity.h +++ b/Libraries/LibScene/Entity.h @@ -72,22 +72,22 @@ class Entity final { } // operators - operator entt::entity() const + explicit operator entt::entity() const { return m_handle; } - operator uint32_t() const + explicit operator uint32_t() const { return static_cast(m_handle); } - operator bool() const + explicit operator bool() const { return m_handle != entt::null; } - bool operator==(Entity const& other) const; + bool operator==(Entity const& other) const = default; // relationship component stuffs std::vector& children() const @@ -111,7 +111,7 @@ class Entity final { Entity child_at(uint32_t index) const; - void set_parent_id(Terran::Core::UUID const& id) + void set_parent_id(Terran::Core::UUID const& id) const { if (!has_component()) return; @@ -137,7 +137,7 @@ class Entity final { void unparent(); - void remove_child(Entity child) + void remove_child(Entity child) const { child.unparent(); } diff --git a/Libraries/LibScene/Scene.cpp b/Libraries/LibScene/Scene.cpp index c65a6e7b..3069c5ed 100644 --- a/Libraries/LibScene/Scene.cpp +++ b/Libraries/LibScene/Scene.cpp @@ -7,9 +7,6 @@ #include #include -// #include "Utils/Debug/OptickProfiler.h" -// #include "Utils/Debug/Profiler.h" - #include #include #define GLM_ENABLE_EXPERIMENTAL @@ -104,7 +101,7 @@ void Scene::destrory_entity(Entity entity, bool first) if (auto entity_iterator = m_entity_map.find(entity.id()); entity_iterator != m_entity_map.end()) m_entity_map.erase(entity_iterator); - m_registry.destroy(entity); + m_registry.destroy((entt::entity)entity); sort_entities(); } @@ -158,7 +155,10 @@ Entity Scene::duplicate_entity(Entity source_entity, Entity parent) { Entity destination_entity = create_entity(source_entity.name() + " Copy"); - copy_component(source_entity, destination_entity, m_registry); + copy_component( + (entt::entity)source_entity, + (entt::entity)destination_entity, + m_registry); if (source_entity.has_component()) { for (int i = 0; i < source_entity.children_count(); i++) { @@ -191,8 +191,16 @@ Terran::Core::Shared Scene::copy_scene(Terran::Core::Shared const& Entity source_entity(e, source_scene.get()); Entity destination_entity = scene->find_entity(source_entity.id()); - copy_component(source_entity, destination_entity, source_scene->m_registry, scene->m_registry); - copy_component(source_entity, destination_entity, source_scene->m_registry, scene->m_registry); + copy_component( + (entt::entity)source_entity, + (entt::entity)destination_entity, + source_scene->m_registry, + scene->m_registry); + copy_component( + (entt::entity)source_entity, + (entt::entity)destination_entity, + source_scene->m_registry, + scene->m_registry); } scene->sort_entities(); diff --git a/Libraries/LibScene/SceneSerializer.cpp b/Libraries/LibScene/SceneSerializer.cpp index 266b7e88..503e0ab9 100644 --- a/Libraries/LibScene/SceneSerializer.cpp +++ b/Libraries/LibScene/SceneSerializer.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -97,16 +98,17 @@ bool SceneSerializer::save(Asset::AssetMetadata const& assetMetadata, Core::Shar return true; } -static YAML::Node find_entity(YAML::Node const& scene, Terran::Core::UUID const& entityID) +static YAML::const_iterator find_entity(YAML::Node const& scene, Terran::Core::UUID const& entityID) { - for (auto entity : scene) { + for (YAML::const_iterator it = scene.begin(); it != scene.end(); ++it) { + auto const& entity = *it; TR_CORE_TRACE(TR_LOG_CORE, entity); Terran::Core::UUID id = entity["Entity"].as(); if (id == entityID) - return entity; + return it; } - return {}; + return scene.end(); } static Entity deserialize_entity(YAML::Node const& data, YAML::Node const& scene, Core::Shared deserializedScene) @@ -118,8 +120,7 @@ static Entity deserialize_entity(YAML::Node const& data, YAML::Node const& scene return {}; } - Entity entity = deserializedScene->find_entity(id); - if (entity) + if (Entity entity = deserializedScene->find_entity(id); entity) return entity; auto tagComponent = data["TagComponent"]; @@ -144,8 +145,8 @@ static Entity deserialize_entity(YAML::Node const& data, YAML::Node const& scene Core::UUID deserializedChildID = childID.as(); Entity child = deserializedScene->find_entity(deserializedChildID); if (!child) { - YAML::Node childNode = find_entity(scene, deserializedChildID); - child = deserialize_entity(childNode, scene, deserializedScene); + YAML::const_iterator childNode = find_entity(scene, deserializedChildID); + child = deserialize_entity(*childNode, scene, deserializedScene); } if (child) From 03b9eff59269056ddc42d87a78bf935ace814bb8 Mon Sep 17 00:00:00 2001 From: infirit89 Date: Mon, 16 Feb 2026 00:14:28 +0200 Subject: [PATCH 14/51] chore(Core): fix more sonarcloud errors --- Libraries/LibCore/Math.cpp | 144 ++++++++++++++++++------------------- 1 file changed, 70 insertions(+), 74 deletions(-) diff --git a/Libraries/LibCore/Math.cpp b/Libraries/LibCore/Math.cpp index 9a8cd755..d562f847 100644 --- a/Libraries/LibCore/Math.cpp +++ b/Libraries/LibCore/Math.cpp @@ -5,79 +5,75 @@ #include #include -namespace Terran::Core +#include + +namespace Terran::Core { + +bool Math::decompose_transform_matrix(glm::mat4 const& modelMatrix, glm::vec3& translation, glm::vec3& rotation, glm::vec3& scale) { - bool Math::decompose_transform_matrix(const glm::mat4& modelMatrix, glm::vec3& translation, glm::vec3& rotation, glm::vec3& scale) - { - glm::mat4 LocalMatrix(modelMatrix); - - // Normalize the matrix. - if (glm::epsilonEqual(LocalMatrix[3][3], static_cast(0), glm::epsilon())) - return false; - - for (glm::length_t i = 0; i < 4; ++i) - for (glm::length_t j = 0; j < 4; ++j) - LocalMatrix[i][j] /= LocalMatrix[3][3]; - - if ( - glm::epsilonNotEqual(LocalMatrix[0][3], static_cast(0), glm::epsilon()) || - glm::epsilonNotEqual(LocalMatrix[1][3], static_cast(0), glm::epsilon()) || - glm::epsilonNotEqual(LocalMatrix[2][3], static_cast(0), glm::epsilon())) - { - LocalMatrix[0][3] = LocalMatrix[1][3] = LocalMatrix[2][3] = static_cast(0); - LocalMatrix[3][3] = static_cast(1); - } - - translation = glm::vec3(LocalMatrix[3]); - LocalMatrix[3] = glm::vec4(0, 0, 0, LocalMatrix[3].w); - - glm::vec3 Row[3], Pdum3; - - for (glm::length_t i = 0; i < 3; ++i) - for (glm::length_t j = 0; j < 3; ++j) - Row[i][j] = LocalMatrix[i][j]; - - // get X scale factor and normalize first row. - scale.x = glm::length(Row[0]); - - Row[0] = glm::detail::scale(Row[0], static_cast(1)); - - // get Y scale and normalize 2nd row. - scale.y = glm::length(Row[1]); - Row[1] = glm::detail::scale(Row[1], static_cast(1)); - - // get Z scale and normalize 3rd row. - scale.z = glm::length(Row[2]); - Row[2] = glm::detail::scale(Row[2], static_cast(1)); - - Pdum3 = cross(Row[1], Row[2]); - if (dot(Row[0], Pdum3) < 0) - { - for (glm::length_t i = 0; i < 3; i++) - { - scale[i] *= static_cast(-1); - Row[i] *= static_cast(-1); - } - } - - // get the rotations out. - rotation.y = asin(-Row[0][2]); - if (cos(rotation.y) != 0) { - rotation.x = atan2(Row[1][2], Row[2][2]); - rotation.z = atan2(Row[0][1], Row[0][0]); - } - else { - rotation.x = atan2(-Row[2][0], Row[1][1]); - rotation.z = 0; - } - - return true; - } - - glm::mat4 Math::compose_transform_matrix(const glm::vec3& translation, const glm::vec3& rotation, const glm::vec3& scale) - { - return glm::translate(glm::mat4(1.0f), translation) * - glm::toMat4(glm::quat(rotation)) * - glm::scale(glm::mat4(1.0f), scale); - } + glm::mat4 LocalMatrix(modelMatrix); + + // Normalize the matrix. + if (glm::epsilonEqual(LocalMatrix[3][3], static_cast(0), glm::epsilon())) + return false; + + for (glm::length_t i = 0; i < 4; ++i) + for (glm::length_t j = 0; j < 4; ++j) + LocalMatrix[i][j] /= LocalMatrix[3][3]; + + if ( + glm::epsilonNotEqual(LocalMatrix[0][3], static_cast(0), glm::epsilon()) || glm::epsilonNotEqual(LocalMatrix[1][3], static_cast(0), glm::epsilon()) || glm::epsilonNotEqual(LocalMatrix[2][3], static_cast(0), glm::epsilon())) { + LocalMatrix[0][3] = LocalMatrix[1][3] = LocalMatrix[2][3] = static_cast(0); + LocalMatrix[3][3] = static_cast(1); + } + + translation = glm::vec3(LocalMatrix[3]); + LocalMatrix[3] = glm::vec4(0, 0, 0, LocalMatrix[3].w); + + std::array Row; + glm::vec3 Pdum3; + + for (glm::length_t i = 0; i < 3; ++i) + for (glm::length_t j = 0; j < 3; ++j) + Row[i][j] = LocalMatrix[i][j]; + + // get X scale factor and normalize first row. + scale.x = glm::length(Row[0]); + + Row[0] = glm::detail::scale(Row[0], static_cast(1)); + + // get Y scale and normalize 2nd row. + scale.y = glm::length(Row[1]); + Row[1] = glm::detail::scale(Row[1], static_cast(1)); + + // get Z scale and normalize 3rd row. + scale.z = glm::length(Row[2]); + Row[2] = glm::detail::scale(Row[2], static_cast(1)); + + Pdum3 = cross(Row[1], Row[2]); + if (dot(Row[0], Pdum3) < 0) { + for (glm::length_t i = 0; i < 3; i++) { + scale[i] *= static_cast(-1); + Row[i] *= static_cast(-1); + } + } + + // get the rotations out. + rotation.y = asin(-Row[0][2]); + if (cos(rotation.y) != 0) { + rotation.x = atan2(Row[1][2], Row[2][2]); + rotation.z = atan2(Row[0][1], Row[0][0]); + } else { + rotation.x = atan2(-Row[2][0], Row[1][1]); + rotation.z = 0; + } + + return true; +} + +glm::mat4 Math::compose_transform_matrix(glm::vec3 const& translation, glm::vec3 const& rotation, glm::vec3 const& scale) +{ + return glm::translate(glm::mat4(1.0f), translation) * glm::toMat4(glm::quat(rotation)) * glm::scale(glm::mat4(1.0f), scale); +} + } From b7e664f8e86e8131a7d0557c2717c017ece041d9 Mon Sep 17 00:00:00 2001 From: infirit89 Date: Mon, 16 Feb 2026 00:14:46 +0200 Subject: [PATCH 15/51] chore(Asset): fix more sonarcloud issues --- Libraries/LibAsset/AssetImporterError.h | 16 ++++++++++------ Libraries/LibAsset/AssetImporterRegistry.cpp | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Libraries/LibAsset/AssetImporterError.h b/Libraries/LibAsset/AssetImporterError.h index 8c378e86..7454cd91 100644 --- a/Libraries/LibAsset/AssetImporterError.h +++ b/Libraries/LibAsset/AssetImporterError.h @@ -9,24 +9,28 @@ namespace Terran::Asset { class AssetImporterError final : public AssetError { public: - enum Code : uint8_t { + enum class Code : uint8_t { ImporterNotFound, }; - AssetImporterError(Code code) + explicit AssetImporterError(Code code) : m_code(code) { } ~AssetImporterError() override = default; - [[nodiscard]] std::string_view message() const override { - switch(m_code) { - case Code::ImporterNotFound: return "No importer registered for asset type"; + [[nodiscard]] std::string_view message() const override + { + if (m_code == Code::ImporterNotFound) { + return "No importer registered for asset type"; } + + return "Unknown error"; } - [[nodiscard]] std::string_view source() const override { + [[nodiscard]] std::string_view source() const override + { return "Asset"; } diff --git a/Libraries/LibAsset/AssetImporterRegistry.cpp b/Libraries/LibAsset/AssetImporterRegistry.cpp index 94a089b6..0ae6117b 100644 --- a/Libraries/LibAsset/AssetImporterRegistry.cpp +++ b/Libraries/LibAsset/AssetImporterRegistry.cpp @@ -22,7 +22,7 @@ AssetLoadResult AssetImporterRegistry::load(AssetMetadata const& assetMetadata) } TR_CORE_ERROR(TR_LOG_ASSET, "Invalid asset type for asset: {0}", assetMetadata.Path); - return { Core::CreateShared(AssetImporterError::ImporterNotFound) }; + return { Core::CreateShared(AssetImporterError::Code::ImporterNotFound) }; } bool AssetImporterRegistry::save(AssetMetadata const& assetMetadata, Terran::Core::Shared const& asset) From b9a016547557e54e1291f1ca57cfaadbe948054b Mon Sep 17 00:00:00 2001 From: infirit89 Date: Mon, 16 Feb 2026 00:55:25 +0200 Subject: [PATCH 16/51] chore(Scene): dispatch scene transition event on scene transition --- Libraries/LibScene/Scene.cpp | 5 ++-- Libraries/LibScene/SceneEvent.h | 10 +++++++ Libraries/LibScene/SceneManager.cpp | 44 ++++++++++++++--------------- Libraries/LibScene/SceneManager.h | 40 ++++++++++++++++++-------- 4 files changed, 63 insertions(+), 36 deletions(-) diff --git a/Libraries/LibScene/Scene.cpp b/Libraries/LibScene/Scene.cpp index 3069c5ed..a0bf271e 100644 --- a/Libraries/LibScene/Scene.cpp +++ b/Libraries/LibScene/Scene.cpp @@ -2,10 +2,11 @@ #include "Components.h" #include "Entity.h" -#include "SceneManager.h" +#include #include #include +#include #include #include @@ -183,7 +184,7 @@ Entity Scene::duplicate_entity(Entity srcEntity) Terran::Core::Shared Scene::copy_scene(Terran::Core::Shared const& source_scene) { - Terran::Core::Shared scene = SceneManager::create_empty_scene(); + Core::Shared scene = Core::CreateShared(); auto tag_view = source_scene->entities_with(); diff --git a/Libraries/LibScene/SceneEvent.h b/Libraries/LibScene/SceneEvent.h index 29fda6d1..4a4571f3 100644 --- a/Libraries/LibScene/SceneEvent.h +++ b/Libraries/LibScene/SceneEvent.h @@ -23,4 +23,14 @@ class SceneTransitionEvent { Terran::Core::Shared m_new_scene; }; +class SceneStartSimulationEvent { +public: + SceneStartSimulationEvent() = default; +}; + +class SceneStopSimulationEvent { +public: + SceneStopSimulationEvent() = default; +}; + } diff --git a/Libraries/LibScene/SceneManager.cpp b/Libraries/LibScene/SceneManager.cpp index dca1c5e8..9e581a3d 100644 --- a/Libraries/LibScene/SceneManager.cpp +++ b/Libraries/LibScene/SceneManager.cpp @@ -1,33 +1,33 @@ #include "SceneManager.h" +#include "SceneEvent.h" +#include "Scene.h" -// #include "Events/SceneEvent.h" +#include +#include namespace Terran::World { -Terran::Core::Shared SceneManager::s_CurrentScene; -std::unordered_map> SceneManager::s_ActiveScenes; - -Terran::Core::Shared SceneManager::create_empty_scene() +Core::Shared SceneManager::create_empty_scene() { // TODO: create memory asset??? Terran::Core::Shared scene = Terran::Core::CreateShared(); - s_ActiveScenes[scene->handle()] = scene; + m_active_scenes[scene->handle()] = scene; return scene; } void SceneManager::remove_scene(const Terran::Core::UUID& id) { - if (s_ActiveScenes.contains(id)) - s_ActiveScenes.erase(id); + if (m_active_scenes.contains(id)) + m_active_scenes.erase(id); - if (s_CurrentScene->handle() == id) - s_CurrentScene = nullptr; + if (m_current_scene->handle() == id) + m_current_scene = nullptr; } Terran::Core::Shared SceneManager::scene(const Terran::Core::UUID& id) { - if (s_ActiveScenes.contains(id)) - return s_ActiveScenes.at(id); + if (m_active_scenes.contains(id)) + return m_active_scenes.at(id); return nullptr; } @@ -35,25 +35,23 @@ Terran::Core::Shared SceneManager::scene(const Terran::Core::UUID& id) void SceneManager::set_current_scene(Terran::Core::Shared newScene) { Terran::Core::UUID id({ 0 }); - if (s_CurrentScene) - id = s_CurrentScene->handle(); + if (m_current_scene) + id = m_current_scene->handle(); - // SceneTransitionEvent sceneTransitionEvent(s_CurrentScene, newScene); - - // TODO: dispatch events properly once this becomes a layer - // Application::Get()->DispatchEvent(sceneTransitionEvent); + SceneTransitionEvent sceneTransitionEvent(m_current_scene, newScene); + event_dispatcher.trigger(sceneTransitionEvent); - s_CurrentScene = newScene; - s_ActiveScenes[newScene->handle()] = newScene; + m_current_scene = newScene; + m_active_scenes[newScene->handle()] = newScene; if (!id) return; - if (s_ActiveScenes.contains(id)) { - if (s_ActiveScenes[id].use_count() > 1) + if (m_active_scenes.contains(id)) { + if (m_active_scenes[id].use_count() > 1) return; - s_ActiveScenes.erase(id); + m_active_scenes.erase(id); } } diff --git a/Libraries/LibScene/SceneManager.h b/Libraries/LibScene/SceneManager.h index bc409b5b..4537a5c1 100644 --- a/Libraries/LibScene/SceneManager.h +++ b/Libraries/LibScene/SceneManager.h @@ -1,28 +1,46 @@ #pragma once -#include "LibCore/Base.h" -#include "LibCore/UUID.h" #include "Scene.h" +#include +#include +#include +#include + #include namespace Terran::World { -class SceneManager final { +class SceneManager final : public Core::Layer { + using active_scenes_container = std::unordered_map>; + public: - static Terran::Core::Shared create_empty_scene(); - static void remove_scene(Terran::Core::UUID const& id); + SceneManager(Core::EventDispatcher& dispatcher) + : Core::Layer("Scene", dispatcher) + { + } + + Core::Shared create_empty_scene(); + + void remove_scene(Terran::Core::UUID const& id); + + Core::Shared scene(Terran::Core::UUID const& id); - static Terran::Core::Shared scene(Terran::Core::UUID const& id); + Core::Shared const& current_scene() + { + return m_current_scene; + } - static Terran::Core::Shared const& current_scene() { return s_CurrentScene; } - static void set_current_scene(Terran::Core::Shared newScene); + void set_current_scene(Terran::Core::Shared newScene); - static std::unordered_map>& active_scenes() { return s_ActiveScenes; } + active_scenes_container& active_scenes() + { + return m_active_scenes; + } private: - static std::unordered_map> s_ActiveScenes; - static Terran::Core::Shared s_CurrentScene; + active_scenes_container m_active_scenes; + Core::Shared m_current_scene; }; } From d070aef83aa30ba834cd1eec54cbb45c4b717d56 Mon Sep 17 00:00:00 2001 From: infirit89 Date: Mon, 16 Feb 2026 12:07:30 +0200 Subject: [PATCH 17/51] chore(Scene): move copy scene method to scene manager --- Libraries/LibScene/Scene.cpp | 62 +++++++++----------------- Libraries/LibScene/Scene.h | 62 ++++++++++++++++++-------- Libraries/LibScene/SceneManager.cpp | 42 ++++++++++++++--- Libraries/LibScene/SceneManager.h | 1 + Libraries/LibScene/SceneSerializer.cpp | 3 +- Libraries/LibScene/SceneSerializer.h | 17 +++++-- 6 files changed, 115 insertions(+), 72 deletions(-) diff --git a/Libraries/LibScene/Scene.cpp b/Libraries/LibScene/Scene.cpp index a0bf271e..0e2a3278 100644 --- a/Libraries/LibScene/Scene.cpp +++ b/Libraries/LibScene/Scene.cpp @@ -4,15 +4,21 @@ #include "Entity.h" #include +#include #include #include #include +#include + +#include #include #include #define GLM_ENABLE_EXPERIMENTAL #include +#include +#include #include namespace Terran::World { @@ -24,7 +30,7 @@ struct SceneComponent final { namespace { template -void copy_component(entt::entity srcHandle, entt::entity dstHandle, entt::registry& srcRegistry, entt::registry& dstRegistry) +void copy_component_internal(entt::entity srcHandle, entt::entity dstHandle, entt::registry& srcRegistry, entt::registry& dstRegistry) { if (!srcRegistry.all_of(srcHandle)) return; @@ -33,9 +39,9 @@ void copy_component(entt::entity srcHandle, entt::entity dstHandle, entt::regist } template -void copy_component(entt::entity srcHandle, entt::entity dstHandle, entt::registry& srcRegistry) +void copy_component_internal(entt::entity srcHandle, entt::entity dstHandle, entt::registry& srcRegistry) { - copy_component(srcHandle, dstHandle, srcRegistry, srcRegistry); + copy_component_internal(srcHandle, dstHandle, srcRegistry, srcRegistry); } glm::mat4 calculate_transform_matrix(TransformComponent const& transform) @@ -45,13 +51,14 @@ glm::mat4 calculate_transform_matrix(TransformComponent const& transform) } -Scene::Scene() - : Scene(Terran::Core::UUID()) +Scene::Scene(Core::EventDispatcher& event_dispatcher) + : Scene(event_dispatcher, Core::UUID()) { } -Scene::Scene(Terran::Core::UUID const& handle) +Scene::Scene(Core::EventDispatcher& event_dispatcher, Core::UUID const& handle) : Asset(handle) + , m_event_dispatcher(event_dispatcher) { auto const sceneEntity = m_registry.create(); m_registry.emplace(sceneEntity, m_handle); @@ -114,7 +121,8 @@ void Scene::start_runtime() m_is_playing = true; - // TODO: fire an on start playing/start simulation event + SceneStartSimulationEvent start_simulation_event; + m_event_dispatcher.trigger(start_simulation_event); } void Scene::stop_runtime() @@ -123,7 +131,9 @@ void Scene::stop_runtime() return; m_is_playing = false; - // TODO: fire an on stop playing/stop simulation event + + SceneStopSimulationEvent stop_simulation_event; + m_event_dispatcher.trigger(stop_simulation_event); } void Scene::update(Terran::Core::Time) @@ -131,7 +141,7 @@ void Scene::update(Terran::Core::Time) update_transform_hierarchy(); } -Entity Scene::find_entity(Terran::Core::UUID uuid) +Entity Scene::find_entity(Core::UUID const& uuid) { if (m_entity_map.contains(uuid)) return Entity(m_entity_map.at(uuid), this); @@ -156,7 +166,7 @@ Entity Scene::duplicate_entity(Entity source_entity, Entity parent) { Entity destination_entity = create_entity(source_entity.name() + " Copy"); - copy_component( + copy_component_internal( (entt::entity)source_entity, (entt::entity)destination_entity, m_registry); @@ -177,38 +187,6 @@ Entity Scene::duplicate_entity(Entity source_entity, Entity parent) return destination_entity; } -Entity Scene::duplicate_entity(Entity srcEntity) -{ - return duplicate_entity(srcEntity, {}); -} - -Terran::Core::Shared Scene::copy_scene(Terran::Core::Shared const& source_scene) -{ - Core::Shared scene = Core::CreateShared(); - - auto tag_view = source_scene->entities_with(); - - for (auto e : tag_view) { - Entity source_entity(e, source_scene.get()); - Entity destination_entity = scene->find_entity(source_entity.id()); - - copy_component( - (entt::entity)source_entity, - (entt::entity)destination_entity, - source_scene->m_registry, - scene->m_registry); - copy_component( - (entt::entity)source_entity, - (entt::entity)destination_entity, - source_scene->m_registry, - scene->m_registry); - } - - scene->sort_entities(); - - return scene; -} - void Scene::update_transform_hierarchy() { auto transform_view = entities_with(); diff --git a/Libraries/LibScene/Scene.h b/Libraries/LibScene/Scene.h index 3c2b100a..2af0f5d1 100644 --- a/Libraries/LibScene/Scene.h +++ b/Libraries/LibScene/Scene.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -15,15 +16,15 @@ #include #include -#include #include +#include namespace Terran::World { class Scene final : public Asset::Asset { public: - Scene(); - explicit Scene(Terran::Core::UUID const& handle); + Scene(Core::EventDispatcher& event_dispatcher); + Scene(Core::EventDispatcher& event_dispatcher, Core::UUID const& handle); ~Scene() override; TR_DECLARE_ASSET_TYPE(Scene) @@ -39,23 +40,46 @@ class Scene final : public Asset::Asset { void update(Terran::Core::Time time); - Entity find_entity(Terran::Core::UUID uuid); + Entity find_entity(Core::UUID const& uuid); Entity find_entity(std::string_view name); template - auto entities_with(entt::exclude_t exclude = {}) { return m_registry.view(exclude); } - - std::unordered_map& GetEntityMap() { return m_entity_map; } - - - Entity duplicate_entity(Entity srcEntity, Entity parent); - Entity duplicate_entity(Entity srcEntity); - - static Terran::Core::Shared copy_scene(Terran::Core::Shared const& srcScene); - - bool playing() const { return m_is_playing; } - - Scene* raw() { return this; } + auto entities_with(entt::exclude_t exclude = {}) + { + return m_registry.view(exclude); + } + + std::unordered_map& GetEntityMap() + { + return m_entity_map; + } + + Entity duplicate_entity(Entity source_entity, Entity parent = {}); + + bool playing() const + { + return m_is_playing; + } + + template + static void copy_component(Entity source_entity, Entity destination_entity, Core::Shared source_scene, Core::Shared destination_scene) + { + entt::registry& source_registry = source_scene->m_registry; + entt::registry& destination_registry = destination_scene->m_registry; + + if (!source_registry.all_of((entt::entity)source_entity)) { + return; + } + + destination_registry.emplace_or_replace( + (entt::entity)destination_entity, + source_registry.get((entt::entity)source_entity)); + } + + Scene* raw() + { + return this; + } void update_transform_hierarchy(); void update_entity_transform(Entity entity); @@ -68,12 +92,14 @@ class Scene final : public Asset::Asset { private: bool m_is_playing = false; - std::unordered_map m_entity_map; + std::unordered_map m_entity_map; + Core::EventDispatcher& m_event_dispatcher; entt::registry m_registry; friend class Entity; friend class SceneSerializer; + friend class SceneManager; }; } diff --git a/Libraries/LibScene/SceneManager.cpp b/Libraries/LibScene/SceneManager.cpp index 9e581a3d..e10cbf25 100644 --- a/Libraries/LibScene/SceneManager.cpp +++ b/Libraries/LibScene/SceneManager.cpp @@ -1,21 +1,23 @@ #include "SceneManager.h" -#include "SceneEvent.h" #include "Scene.h" +#include "SceneEvent.h" +#include "Components.h" +#include "Entity.h" -#include #include +#include namespace Terran::World { Core::Shared SceneManager::create_empty_scene() { // TODO: create memory asset??? - Terran::Core::Shared scene = Terran::Core::CreateShared(); + Core::Shared scene = Core::CreateShared(event_dispatcher); m_active_scenes[scene->handle()] = scene; return scene; } -void SceneManager::remove_scene(const Terran::Core::UUID& id) +void SceneManager::remove_scene(Core::UUID const& id) { if (m_active_scenes.contains(id)) m_active_scenes.erase(id); @@ -24,17 +26,43 @@ void SceneManager::remove_scene(const Terran::Core::UUID& id) m_current_scene = nullptr; } -Terran::Core::Shared SceneManager::scene(const Terran::Core::UUID& id) +Core::Shared SceneManager::scene(Core::UUID const& id) { if (m_active_scenes.contains(id)) return m_active_scenes.at(id); return nullptr; } +Core::Shared SceneManager::copy_scene(Core::Shared const& source_scene) +{ + Core::Shared scene = Core::CreateShared(event_dispatcher); + + auto tag_view = source_scene->entities_with(); + + for (auto e : tag_view) { + Entity source_entity(e, source_scene.get()); + Entity destination_entity = scene->find_entity(source_entity.id()); + + Scene::copy_component( + source_entity, + destination_entity, + source_scene, + scene); + Scene::copy_component( + source_entity, + destination_entity, + source_scene, + scene); + } + + scene->sort_entities(); + + return scene; +} -void SceneManager::set_current_scene(Terran::Core::Shared newScene) +void SceneManager::set_current_scene(Core::Shared newScene) { - Terran::Core::UUID id({ 0 }); + Core::UUID id({ 0 }); if (m_current_scene) id = m_current_scene->handle(); diff --git a/Libraries/LibScene/SceneManager.h b/Libraries/LibScene/SceneManager.h index 4537a5c1..61c10939 100644 --- a/Libraries/LibScene/SceneManager.h +++ b/Libraries/LibScene/SceneManager.h @@ -32,6 +32,7 @@ class SceneManager final : public Core::Layer { } void set_current_scene(Terran::Core::Shared newScene); + Core::Shared copy_scene(Core::Shared const& source_scene); active_scenes_container& active_scenes() { diff --git a/Libraries/LibScene/SceneSerializer.cpp b/Libraries/LibScene/SceneSerializer.cpp index 503e0ab9..dd9290b2 100644 --- a/Libraries/LibScene/SceneSerializer.cpp +++ b/Libraries/LibScene/SceneSerializer.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -173,7 +174,7 @@ Asset::AssetLoadResult SceneSerializer::load(Asset::AssetMetadata const& assetMe return { Core::CreateShared(SceneSerializerError::Code::NotFound, ex.what()) }; } - Core::Shared scene = Core::CreateShared(assetMetadata.Handle); + Core::Shared scene = m_scene_system.create_empty_scene(); if (auto entities = data["Entities"]; entities) { for (auto entity : entities) { diff --git a/Libraries/LibScene/SceneSerializer.h b/Libraries/LibScene/SceneSerializer.h index 9ee581cf..42455b76 100644 --- a/Libraries/LibScene/SceneSerializer.h +++ b/Libraries/LibScene/SceneSerializer.h @@ -5,28 +5,37 @@ #include #include +#include #include #include -#include #include +#include #include namespace Terran::World { class SceneSerializer final : public Asset::AssetImporter { public: - SceneSerializer() = default; + SceneSerializer(SceneManager& scene_system) + : m_scene_system(scene_system) + { + } + ~SceneSerializer() override = default; bool save(Asset::AssetMetadata const& assetMetadata, Core::Shared const& asset) override; Asset::AssetLoadResult load(Asset::AssetMetadata const& assetMetadata) override; - [[nodiscard]] bool can_handle(std::filesystem::path const& assetPath) override { + [[nodiscard]] bool can_handle(std::filesystem::path const& assetPath) override + { return assetPath.extension() == ".terran"; } - [[nodiscard]] Asset::AssetTypeId asset_type() override { + [[nodiscard]] Asset::AssetTypeId asset_type() override + { return Scene::static_type(); } +private: + SceneManager& m_scene_system; }; } From 578b9a232deeed952c4b9d66d6ee6038326b5872 Mon Sep 17 00:00:00 2001 From: infirit89 Date: Mon, 16 Feb 2026 20:22:53 +0200 Subject: [PATCH 18/51] chore(Asset): add remove_asset to AssetManager --- Libraries/LibAsset/AssetManager.cpp | 104 +++++++++++++++------------- Libraries/LibAsset/AssetManager.h | 69 ++++++++++-------- 2 files changed, 97 insertions(+), 76 deletions(-) diff --git a/Libraries/LibAsset/AssetManager.cpp b/Libraries/LibAsset/AssetManager.cpp index 68127e18..cb5b474c 100644 --- a/Libraries/LibAsset/AssetManager.cpp +++ b/Libraries/LibAsset/AssetManager.cpp @@ -1,15 +1,16 @@ #include "AssetManager.h" #include "Asset.h" +#include "AssetEvents.h" +#include "AssetImporter.h" #include "AssetImporterRegistry.h" #include "AssetMetadata.h" #include "AssetMetadataRegistry.h" #include "AssetTypes.h" -#include "AssetEvents.h" -#include "AssetImporter.h" +#include #include #include -#include +#include #include #include @@ -31,80 +32,91 @@ AssetManager::~AssetManager() TR_CORE_INFO(TR_LOG_ASSET, "Shutdown asset manager"); } -AssetHandle AssetManager::import_asset(std::filesystem::path const& assetPath) +AssetHandle AssetManager::import_asset(std::filesystem::path const& asset_path) { - AssetHandle assetHandle = AssetMetadataRegistry::asset_handle_from_path(assetPath); + AssetHandle asset_handle = AssetMetadataRegistry::asset_handle_from_path(asset_path); - if (assetHandle) - return assetHandle; + if (asset_handle) + return asset_handle; - assetHandle = AssetHandle(); + asset_handle = AssetHandle(); - if (assetPath.empty()) + if (asset_path.empty()) return AssetHandle::invalid(); - auto const assetLoader = AssetImporterRegistry::find_for_path(assetPath); - if (assetLoader) + auto const asset_loader = AssetImporterRegistry::find_for_path(asset_path); + if (asset_loader) return AssetHandle::invalid(); - AssetMetadata assetMetadata; - assetMetadata.Path = assetPath; - assetMetadata.Type = assetLoader->asset_type(); - assetMetadata.Handle = assetHandle; + AssetMetadata asset_metadata; + asset_metadata.Path = asset_path; + asset_metadata.Type = asset_loader->asset_type(); + asset_metadata.Handle = asset_handle; - AssetMetadataRegistry::add_asset_metadata(assetMetadata); + AssetMetadataRegistry::add_asset_metadata(asset_metadata); - return assetHandle; + return asset_handle; } void AssetManager::reload_asset_by_handle(AssetHandle const& handle) { - AssetMetadata const& info = AssetMetadataRegistry::asset_metadata_by_handle(handle); + AssetMetadata const& metadata = AssetMetadataRegistry::asset_metadata_by_handle(handle); if (!m_loaded_assets.contains(handle)) TR_CORE_WARN(TR_LOG_ASSET, "Trying to reload an asset that was never loaded"); - AssetLoadResult assetResult = AssetImporterRegistry::load(info); - if (!assetResult) + AssetLoadResult asset_result = AssetImporterRegistry::load(metadata); + if (!asset_result) return; - m_loaded_assets[handle] = assetResult.value(); + m_loaded_assets[handle] = asset_result.value(); } -void AssetManager::on_filesystem_changed(std::vector const& fileSystemEvents) +void AssetManager::remove_asset(Core::UUID const& handle, RemoveAssetMetadata remove_metadata) +{ + if (m_loaded_assets.contains(handle)) { + m_loaded_assets.erase(handle); + } + + if (remove_metadata == RemoveAssetMetadata::Yes && AssetMetadataRegistry::contains(handle)) { + AssetMetadataRegistry::erase(handle); + } +} + +void AssetManager::on_filesystem_changed(std::vector const& file_system_events) { if (m_asset_change_callback) - m_asset_change_callback(fileSystemEvents); + m_asset_change_callback(file_system_events); - for (auto const& e : fileSystemEvents) { - if (std::filesystem::is_directory(e.FileName)) + for (auto const& event : file_system_events) { + if (std::filesystem::is_directory(event.FileName)) continue; - switch (e.Action) { - case Terran::Core::FileAction::Removed: { - on_asset_removed(AssetMetadataRegistry::asset_handle_from_path(e.FileName)); + switch (event.Action) { + case Core::FileAction::Removed: { + on_asset_removed(AssetMetadataRegistry::asset_handle_from_path(event.FileName)); break; } - case Terran::Core::FileAction::Renamed: { - bool was_asset = AssetImporterRegistry::exists_for_path(e.OldFileName); - bool is_asset = AssetImporterRegistry::exists_for_path(e.FileName); + case Core::FileAction::Renamed: { + bool was_asset = AssetImporterRegistry::exists_for_path(event.OldFileName); + bool is_asset = AssetImporterRegistry::exists_for_path(event.FileName); if (!was_asset && is_asset) { - import_asset(e.FileName); + import_asset(event.FileName); } else if (was_asset && !is_asset) { on_asset_removed( - AssetMetadataRegistry::asset_handle_from_path(e.OldFileName)); + AssetMetadataRegistry::asset_handle_from_path(event.OldFileName)); } else { on_asset_renamed( - AssetMetadataRegistry::asset_handle_from_path(e.OldFileName), e.FileName); + AssetMetadataRegistry::asset_handle_from_path(event.OldFileName), event.FileName); } break; } - case Terran::Core::FileAction::Modified: { - AssetMetadata info = AssetMetadataRegistry::asset_metadata_by_path(e.FileName); + case Core::FileAction::Modified: { + AssetMetadata metadata = AssetMetadataRegistry::asset_metadata_by_path(event.FileName); - if (info) - reload_asset_by_handle(info.Handle); + if (metadata) + reload_asset_by_handle(metadata.Handle); break; } @@ -114,24 +126,20 @@ void AssetManager::on_filesystem_changed(std::vector -#include #include #include #include #include +#include +#include #include +#include #include +#include #include #include #include namespace Terran::Asset { +enum class RemoveAssetMetadata : uint8_t { + No = 0, + Yes = 1 +}; + class AssetManager final { - using AssetChangeCallbackFn = std::function const&)>; + using AssetChangeCallbackFn = std::function const&)>; + using asset_container = std::unordered_map>; public: AssetManager(Core::EventDispatcher& event_dispatcher); @@ -29,17 +39,17 @@ class AssetManager final { std::filesystem::path filesystem_path(std::filesystem::path const& path); - AssetHandle import_asset(std::filesystem::path const& assetPath); + AssetHandle import_asset(std::filesystem::path const& asset_path); void reload_asset_by_handle(AssetHandle const& handle); void SetAssetChangedCallback(AssetChangeCallbackFn const& callback) { m_asset_change_callback = callback; } template requires(std::is_base_of_v) - Terran::Core::Shared asset_by_handle(AssetHandle const& assetHandle) + Core::Shared asset_by_handle(AssetHandle const& assetHandle) { if (m_loaded_assets.contains(assetHandle)) - return Terran::Core::DynamicCast(m_loaded_assets.at(assetHandle)); + return Core::DynamicCast(m_loaded_assets.at(assetHandle)); AssetMetadata& info = AssetMetadataRegistry::asset_metadata_by_handle__internal(assetHandle); @@ -56,15 +66,15 @@ class AssetManager final { Core::Shared const& asset = assetResult.value(); asset->m_handle = assetHandle; m_loaded_assets[assetHandle] = asset; - return Terran::Core::DynamicCast(m_loaded_assets[assetHandle]); + return Core::DynamicCast(m_loaded_assets[assetHandle]); } template requires(std::is_base_of_v) - Terran::Core::Shared asset_by_metadata(AssetMetadata const& assetMetadata) + Core::Shared asset_by_metadata(AssetMetadata const& assetMetadata) { if (m_loaded_assets.contains(assetMetadata.Handle)) - return Terran::Core::DynamicCast(m_loaded_assets.at(assetMetadata.Handle)); + return Core::DynamicCast(m_loaded_assets.at(assetMetadata.Handle)); AssetLoadResult assetResult = AssetImporterRegistry::load(assetMetadata); @@ -76,60 +86,63 @@ class AssetManager final { Core::Shared const& asset = assetResult.value(); asset->m_handle = assetMetadata.Handle; m_loaded_assets[assetMetadata.Handle] = asset; - return Terran::Core::DynamicCast(m_loaded_assets[assetMetadata.Handle]); + return Core::DynamicCast(m_loaded_assets[assetMetadata.Handle]); } - template + template requires( std::is_base_of_v, HasStaticType) - Terran::Core::Shared create_asset(std::filesystem::path const& filePath) + Core::Shared create_asset(std::filesystem::path const& file_path, TArgs... args) { AssetMetadata metadata; metadata.Handle = AssetHandle(); - metadata.Path = filePath; + metadata.Path = file_path; metadata.Type = TAsset::static_type(); - int currentFileNumber = 2; + int file_occurrence_count = 2; while (file_exists(metadata.Path)) { - metadata.Path = filePath.parent_path() / filePath.stem(); + // Format: parent_directory/file_name (occurrence_count).extension + metadata.Path = std::format("{0} ({1}){2}", (file_path.parent_path() / file_path.stem()).string(), std::to_string(file_occurrence_count), file_path.extension().string()); - metadata.Path = metadata.Path.string() + " (" + std::to_string(currentFileNumber) + ")" + filePath.extension().string(); - - currentFileNumber++; + file_occurrence_count++; } AssetMetadataRegistry::add_asset_metadata(metadata); - // TODO: add parameter options - Terran::Core::Shared asset = Terran::Core::CreateShared(); + Core::Shared asset = Core::CreateShared(args...); m_loaded_assets[metadata.Handle] = asset; AssetImporterRegistry::save(metadata, asset); - return Terran::Core::DynamicCast(m_loaded_assets[metadata.Handle]); + return Core::DynamicCast(m_loaded_assets[metadata.Handle]); } - template + // NOTE: maybe we should take in a parameter that signifies + // whether to create metadata for this asset + // something like create_memory_asset(CreateAssetMetadata::Yes) + template requires(std::is_base_of_v) - Terran::Core::Shared create_memory_asset() + Core::Shared create_memory_asset(TArgs... args) { - Terran::Core::Shared asset = Terran::Core::CreateShared(); + Core::Shared asset = Core::CreateShared(args...); m_loaded_assets[asset->m_handle] = asset; - return Terran::Core::DynamicCast(m_loaded_assets[asset->m_handle]); + return Core::DynamicCast(m_loaded_assets[asset->m_handle]); } + void remove_asset(Core::UUID const& handle, RemoveAssetMetadata remove_metadata = RemoveAssetMetadata::Yes); + static bool file_exists(std::filesystem::path const& path); private: - void on_filesystem_changed(std::vector const& fileSystemEvents); + void on_filesystem_changed(std::vector const& file_system_events); void on_asset_removed(AssetHandle const& handle); - void on_asset_renamed(AssetHandle const& handle, std::filesystem::path const& newFileName); + void on_asset_renamed(AssetHandle const& handle, std::filesystem::path const& new_file_name); std::filesystem::path relative_path(std::filesystem::path const& path); private: - std::unordered_map> m_loaded_assets; + asset_container m_loaded_assets; AssetChangeCallbackFn m_asset_change_callback; Core::EventDispatcher& m_event_dispatcher; }; From ef9a48fd9e08cb9683299888c4c3d6c4ff705447 Mon Sep 17 00:00:00 2001 From: infirit89 Date: Mon, 16 Feb 2026 20:23:25 +0200 Subject: [PATCH 19/51] chore(Core): add intrusive reference counting smart pointer --- Libraries/LibCore/RefPtr.h | 212 +++++++++++++++++++++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 Libraries/LibCore/RefPtr.h diff --git a/Libraries/LibCore/RefPtr.h b/Libraries/LibCore/RefPtr.h new file mode 100644 index 00000000..79793190 --- /dev/null +++ b/Libraries/LibCore/RefPtr.h @@ -0,0 +1,212 @@ +#pragma once + +#include +#include +#include +#include +#include +namespace Terran::Core { + +class RefCounted { +public: + virtual ~RefCounted() = default; + + void increment_count() + { + m_reference_count++; + } + + void decrement_count() + { + m_reference_count--; + } + + size_t ref_count() const + { + return m_reference_count.load(); + } + +private: + std::atomic_size_t m_reference_count; +}; + +template +concept IsRefCounted = std::is_base_of_v; + +template +class RefPtr final { + using value_type = TValue; + +public: + constexpr RefPtr() noexcept = default; + constexpr RefPtr(std::nullptr_t) noexcept + : m_data(nullptr) + { + } + + constexpr RefPtr(TValue* data) noexcept + : m_data(data) + { + increment_ref(); + } + + template + requires(std::is_convertible_v) + constexpr RefPtr(TYValue* data) noexcept + : m_data(data) + { + increment_ref(); + } + + template + requires(std::is_convertible_v) + constexpr RefPtr(RefPtr const& other) noexcept + : m_data(other.m_data) + { + increment_ref(); + } + + template + requires(std::is_convertible_v) + constexpr RefPtr(RefPtr&& other) noexcept + : m_data(other.release()) + { + } + + constexpr RefPtr(RefPtr const& other) noexcept + : m_data(other.m_data) + { + increment_ref(); + } + + constexpr RefPtr(RefPtr&& other) noexcept + : m_data(other.release()) + { + } + + constexpr ~RefPtr() + { + reset(); + } + + constexpr void reset() noexcept + { + decrement_ref(); + } + + [[nodiscard]] constexpr value_type* release() noexcept + { + return std::exchange(m_data, nullptr); + } + + [[nodiscard]] constexpr value_type* data() noexcept + { + return m_data; + } + + constexpr bool operator==(RefPtr const& other) const noexcept + { + return m_data == other.m_data; + } + + constexpr value_type* operator->() const noexcept + { + return m_data; + } + + constexpr RefPtr& operator=(std::nullptr_t) noexcept + { + decrement_ref(); + m_data = nullptr; + return *this; + } + + constexpr RefPtr& operator=(RefPtr const& other) noexcept + { + if (this == other) + return *this; + + other.increment_ref(); + decrement_ref(); + m_data = other.m_data; + return *this; + } + + template + requires(std::is_convertible_v) + constexpr RefPtr& operator=(RefPtr const& other) noexcept + { + if (this == other) + return *this; + + other.increment_ref(); + decrement_ref(); + m_data = other.m_data; + return *this; + } + + constexpr RefPtr& operator=(RefPtr&& other) noexcept + { + decrement_ref(); + m_data = other.m_data; + other.m_data = nullptr; + return *this; + } + template + requires(std::is_convertible_v) + constexpr RefPtr& operator=(RefPtr&& other) noexcept + { + decrement_ref(); + m_data = other.m_data; + other.m_data = nullptr; + return *this; + } + + constexpr RefPtr& operator=(TValue* data) noexcept { + m_data = data; + increment_ref(); + return *this; + } + + constexpr operator bool() const noexcept + { + return !is_null(); + } + + constexpr bool is_null() const noexcept + { + return !m_data; + } + + template + static constexpr RefPtr create(TArgs&&... args) { + return RefPtr(new TValue(std::forward(args)...)); + } + +private: + void increment_ref() + { + if (m_data) { + m_data->increment_count(); + } + } + + void decrement_ref() + { + if (!m_data) { + return; + } + + m_data->decrement_count(); + + if (m_data->ref_count() == 0) { + delete m_data; + m_data = nullptr; + } + } + +private: + value_type* m_data = nullptr; +}; + +} From f6d9d7600550d993bc96a40125bb02921e875d6e Mon Sep 17 00:00:00 2001 From: infirit89 Date: Mon, 16 Feb 2026 20:24:18 +0200 Subject: [PATCH 20/51] chore(Scene): fix more sonarcloud errors --- Libraries/LibScene/Scene.h | 6 +-- Libraries/LibScene/SceneEvent.h | 17 ++++++--- Libraries/LibScene/SceneManager.cpp | 52 ++++++-------------------- Libraries/LibScene/SceneManager.h | 23 +++++------- Libraries/LibScene/SceneSerializer.cpp | 2 +- Libraries/LibScene/SceneSerializer.h | 2 +- 6 files changed, 37 insertions(+), 65 deletions(-) diff --git a/Libraries/LibScene/Scene.h b/Libraries/LibScene/Scene.h index 2af0f5d1..8c8331e8 100644 --- a/Libraries/LibScene/Scene.h +++ b/Libraries/LibScene/Scene.h @@ -23,14 +23,14 @@ namespace Terran::World { class Scene final : public Asset::Asset { public: - Scene(Core::EventDispatcher& event_dispatcher); + explicit Scene(Core::EventDispatcher& event_dispatcher); Scene(Core::EventDispatcher& event_dispatcher, Core::UUID const& handle); ~Scene() override; TR_DECLARE_ASSET_TYPE(Scene) Entity create_entity(std::string const& name = std::string()); - Entity create_entity(std::string const& name, Terran::Core::UUID const& uuid); + Entity create_entity(std::string const& name, Core::UUID const& uuid); Entity create_entity(); void destrory_entity(Entity entity, bool first); @@ -38,7 +38,7 @@ class Scene final : public Asset::Asset { void start_runtime(); void stop_runtime(); - void update(Terran::Core::Time time); + void update(Core::Time time); Entity find_entity(Core::UUID const& uuid); Entity find_entity(std::string_view name); diff --git a/Libraries/LibScene/SceneEvent.h b/Libraries/LibScene/SceneEvent.h index 4a4571f3..5cc18fad 100644 --- a/Libraries/LibScene/SceneEvent.h +++ b/Libraries/LibScene/SceneEvent.h @@ -9,18 +9,25 @@ namespace Terran::World { class SceneTransitionEvent { public: - SceneTransitionEvent(Terran::Core::Weak const& oldScene, Terran::Core::Shared const& newScene) + SceneTransitionEvent(Core::Weak const& oldScene, Core::Shared const& newScene) : m_old_scene(oldScene) , m_new_scene(newScene) { } - Terran::Core::Weak old_scene() { return m_old_scene; } - Terran::Core::Shared new_scene() { return m_new_scene; } + Core::Weak old_scene() const + { + return m_old_scene; + } + + Core::Shared new_scene() const + { + return m_new_scene; + } private: - Terran::Core::Weak m_old_scene; - Terran::Core::Shared m_new_scene; + Core::Weak m_old_scene; + Core::Shared m_new_scene; }; class SceneStartSimulationEvent { diff --git a/Libraries/LibScene/SceneManager.cpp b/Libraries/LibScene/SceneManager.cpp index e10cbf25..049bbfd6 100644 --- a/Libraries/LibScene/SceneManager.cpp +++ b/Libraries/LibScene/SceneManager.cpp @@ -1,8 +1,8 @@ #include "SceneManager.h" -#include "Scene.h" -#include "SceneEvent.h" #include "Components.h" #include "Entity.h" +#include "Scene.h" +#include "SceneEvent.h" #include #include @@ -11,31 +11,12 @@ namespace Terran::World { Core::Shared SceneManager::create_empty_scene() { - // TODO: create memory asset??? - Core::Shared scene = Core::CreateShared(event_dispatcher); - m_active_scenes[scene->handle()] = scene; - return scene; -} - -void SceneManager::remove_scene(Core::UUID const& id) -{ - if (m_active_scenes.contains(id)) - m_active_scenes.erase(id); - - if (m_current_scene->handle() == id) - m_current_scene = nullptr; + return m_asset_system->asset_manager()->create_memory_asset(event_dispatcher); } -Core::Shared SceneManager::scene(Core::UUID const& id) -{ - if (m_active_scenes.contains(id)) - return m_active_scenes.at(id); - - return nullptr; -} Core::Shared SceneManager::copy_scene(Core::Shared const& source_scene) -{ - Core::Shared scene = Core::CreateShared(event_dispatcher); +{ + Core::Shared scene = m_asset_system->asset_manager()->create_memory_asset(event_dispatcher); auto tag_view = source_scene->entities_with(); @@ -60,27 +41,16 @@ Core::Shared SceneManager::copy_scene(Core::Shared const& source_s return scene; } -void SceneManager::set_current_scene(Core::Shared newScene) +void SceneManager::set_current_scene(Core::Shared new_scene) { - Core::UUID id({ 0 }); - if (m_current_scene) - id = m_current_scene->handle(); - - SceneTransitionEvent sceneTransitionEvent(m_current_scene, newScene); - event_dispatcher.trigger(sceneTransitionEvent); - - m_current_scene = newScene; - m_active_scenes[newScene->handle()] = newScene; - - if (!id) + if (!m_current_scene) { return; + } - if (m_active_scenes.contains(id)) { - if (m_active_scenes[id].use_count() > 1) - return; + SceneTransitionEvent scene_transition_event(m_current_scene, new_scene); + event_dispatcher.trigger(scene_transition_event); - m_active_scenes.erase(id); - } + m_current_scene = new_scene; } } diff --git a/Libraries/LibScene/SceneManager.h b/Libraries/LibScene/SceneManager.h index 61c10939..130dbb10 100644 --- a/Libraries/LibScene/SceneManager.h +++ b/Libraries/LibScene/SceneManager.h @@ -2,46 +2,41 @@ #include "Scene.h" +#include #include #include #include #include -#include - namespace Terran::World { class SceneManager final : public Core::Layer { - using active_scenes_container = std::unordered_map>; public: - SceneManager(Core::EventDispatcher& dispatcher) + SceneManager(Core::EventDispatcher& dispatcher, Core::RawPtr asset_system) : Core::Layer("Scene", dispatcher) + , m_asset_system(asset_system) { } Core::Shared create_empty_scene(); - void remove_scene(Terran::Core::UUID const& id); - - Core::Shared scene(Terran::Core::UUID const& id); - Core::Shared const& current_scene() { return m_current_scene; } - void set_current_scene(Terran::Core::Shared newScene); - Core::Shared copy_scene(Core::Shared const& source_scene); - - active_scenes_container& active_scenes() + void reset_current_scene() { - return m_active_scenes; + m_current_scene.reset(); } + void set_current_scene(Core::Shared new_scene); + Core::Shared copy_scene(Core::Shared const& source_scene); + private: - active_scenes_container m_active_scenes; Core::Shared m_current_scene; + Core::RawPtr m_asset_system; }; } diff --git a/Libraries/LibScene/SceneSerializer.cpp b/Libraries/LibScene/SceneSerializer.cpp index dd9290b2..7ebd0a1e 100644 --- a/Libraries/LibScene/SceneSerializer.cpp +++ b/Libraries/LibScene/SceneSerializer.cpp @@ -101,7 +101,7 @@ bool SceneSerializer::save(Asset::AssetMetadata const& assetMetadata, Core::Shar static YAML::const_iterator find_entity(YAML::Node const& scene, Terran::Core::UUID const& entityID) { - for (YAML::const_iterator it = scene.begin(); it != scene.end(); ++it) { + for (auto it = scene.begin(); it != scene.end(); ++it) { auto const& entity = *it; TR_CORE_TRACE(TR_LOG_CORE, entity); Terran::Core::UUID id = entity["Entity"].as(); diff --git a/Libraries/LibScene/SceneSerializer.h b/Libraries/LibScene/SceneSerializer.h index 42455b76..c3f5c47d 100644 --- a/Libraries/LibScene/SceneSerializer.h +++ b/Libraries/LibScene/SceneSerializer.h @@ -17,7 +17,7 @@ namespace Terran::World { class SceneSerializer final : public Asset::AssetImporter { public: - SceneSerializer(SceneManager& scene_system) + explicit SceneSerializer(SceneManager& scene_system) : m_scene_system(scene_system) { } From 7782a62b3a0b6be96fe44911e936cd7ae3b4f200 Mon Sep 17 00:00:00 2001 From: infirit89 Date: Mon, 16 Feb 2026 20:44:26 +0200 Subject: [PATCH 21/51] build(Sandbox): add yaml-cpp as dependency --- Sandbox/premake5.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/Sandbox/premake5.lua b/Sandbox/premake5.lua index cfe84170..eae64d86 100644 --- a/Sandbox/premake5.lua +++ b/Sandbox/premake5.lua @@ -23,6 +23,7 @@ externalincludedirs { "%{IncludeDirectories.imguizmo}", "%{Dependencies.entt.include}", "%{IncludeDirectories.optick}", + "%{Dependencies.yaml.include}", "%{wks.location}/TerranEditor/vendor/FontAwesome", } From 776da01070574ed3e22e8c741e7c0703cc4a64fd Mon Sep 17 00:00:00 2001 From: infirit89 Date: Mon, 16 Feb 2026 21:46:31 +0200 Subject: [PATCH 22/51] test(Core): add unit tests for RefPtr --- Libraries/LibCore/RefPtr.h | 2 +- Tests/LibCore/RefPtrTests.cpp | 169 ++++++++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 Tests/LibCore/RefPtrTests.cpp diff --git a/Libraries/LibCore/RefPtr.h b/Libraries/LibCore/RefPtr.h index 79793190..206d70f3 100644 --- a/Libraries/LibCore/RefPtr.h +++ b/Libraries/LibCore/RefPtr.h @@ -27,7 +27,7 @@ class RefCounted { } private: - std::atomic_size_t m_reference_count; + std::atomic_size_t m_reference_count = 0; }; template diff --git a/Tests/LibCore/RefPtrTests.cpp b/Tests/LibCore/RefPtrTests.cpp new file mode 100644 index 00000000..64a612dc --- /dev/null +++ b/Tests/LibCore/RefPtrTests.cpp @@ -0,0 +1,169 @@ +#include + +#include + +namespace Terran::Tests { + +class MockRefCounted : public Core::RefCounted { +}; + +class RefPtrTest : public testing::Test { + +protected: + RefPtrTest() = default; + virtual ~RefPtrTest() override = default; +}; + +TEST_F(RefPtrTest, default_constructor_initializes_null_data) +{ + Core::RefPtr ref_ptr; + ASSERT_EQ(ref_ptr.data(), nullptr); +} + +TEST_F(RefPtrTest, nullptr_t_constructor_initializes_null_data) +{ + Core::RefPtr ref_ptr(nullptr); + ASSERT_EQ(ref_ptr.data(), nullptr); +} + +TEST_F(RefPtrTest, value_pointer_constructor_sets_data) +{ + MockRefCounted* mock_object = new MockRefCounted(); + Core::RefPtr ref_ptr(mock_object); + ASSERT_EQ(ref_ptr.data(), mock_object); +} + +TEST_F(RefPtrTest, value_pointer_constructor_increments_ref_count) +{ + MockRefCounted* mock_object = new MockRefCounted(); + Core::RefPtr ref_ptr(mock_object); + ASSERT_EQ(mock_object->ref_count(), 1); +} + +TEST_F(RefPtrTest, reset_sets_null_if_ref_count_is_zero) +{ + MockRefCounted* mock_object = new MockRefCounted(); + Core::RefPtr ref_ptr(mock_object); + + ref_ptr.reset(); + ASSERT_EQ(ref_ptr.data(), nullptr); +} + +TEST_F(RefPtrTest, destructor_decrements_ref_count) +{ + MockRefCounted* mock_object = new MockRefCounted(); + Core::RefPtr ref_ptr(mock_object); + + { + Core::RefPtr temp_ref_ptr = ref_ptr; + ASSERT_EQ(mock_object->ref_count(), 2); + } + + ASSERT_EQ(mock_object->ref_count(), 1); +} + +TEST_F(RefPtrTest, release_exchanges_data_for_nullptr) +{ + MockRefCounted* mock_object = new MockRefCounted(); + Core::RefPtr ref_ptr(mock_object); + MockRefCounted* data = ref_ptr.release(); + EXPECT_EQ(mock_object, data); + EXPECT_EQ(ref_ptr.data(), nullptr); + delete mock_object; +} + +TEST_F(RefPtrTest, copy_constructor_sets_data) +{ + MockRefCounted* mock_object = new MockRefCounted(); + Core::RefPtr ref_ptr(mock_object); + Core::RefPtr temp_ref_ptr(ref_ptr); + ASSERT_EQ(temp_ref_ptr.data(), mock_object); +} + +TEST_F(RefPtrTest, copy_constructor_increments_ref_count) +{ + MockRefCounted* mock_object = new MockRefCounted(); + Core::RefPtr ref_ptr(mock_object); + Core::RefPtr temp_ref_ptr(ref_ptr); + ASSERT_EQ(mock_object->ref_count(), 2); +} + +TEST_F(RefPtrTest, move_constructor_sets_data) +{ + MockRefCounted* mock_object = new MockRefCounted(); + Core::RefPtr ref_ptr(mock_object); + Core::RefPtr temp_ref_ptr(std::move(ref_ptr)); + ASSERT_EQ(temp_ref_ptr.data(), mock_object); +} + +TEST_F(RefPtrTest, move_constructor_releases_moved_data) +{ + MockRefCounted* mock_object = new MockRefCounted(); + Core::RefPtr ref_ptr(mock_object); + Core::RefPtr temp_ref_ptr(std::move(ref_ptr)); + ASSERT_EQ(ref_ptr, nullptr); +} + +TEST_F(RefPtrTest, move_constructor_doesnt_increment_ref_count) +{ + MockRefCounted* mock_object = new MockRefCounted(); + Core::RefPtr ref_ptr(mock_object); + Core::RefPtr temp_ref_ptr(std::move(ref_ptr)); + ASSERT_EQ(mock_object->ref_count(), 1); +} + +TEST_F(RefPtrTest, comparison_operator_returns_true_when_both_refptrs_point_to_same_object) +{ + MockRefCounted* mock_object = new MockRefCounted(); + Core::RefPtr ref_ptr(mock_object); + Core::RefPtr temp_ref_ptr(ref_ptr); + ASSERT_TRUE(ref_ptr == temp_ref_ptr); +} + +TEST_F(RefPtrTest, comparison_operator_returns_false_when_both_refptrs_dont_point_to_same_object) +{ + MockRefCounted* mock_object = new MockRefCounted(); + Core::RefPtr ref_ptr(mock_object); + Core::RefPtr temp_ref_ptr; + ASSERT_FALSE(ref_ptr == temp_ref_ptr); +} + +TEST_F(RefPtrTest, nullptr_assignment_operator_sets_data_to_nullptr) +{ + Core::RefPtr ref_ptr = nullptr; + ASSERT_EQ(ref_ptr.data(), nullptr); +} + +TEST_F(RefPtrTest, nullptr_assignment_operator_decrements_refcount_when_previous_data_is_not_null) +{ + MockRefCounted* mock_object = new MockRefCounted(); + Core::RefPtr ref_ptr(mock_object); + Core::RefPtr temp_ref_ptr(ref_ptr); + ASSERT_EQ(mock_object->ref_count(), 2); + ref_ptr = nullptr; + ASSERT_EQ(mock_object->ref_count(), 1); +} +TEST_F(RefPtrTest, copy_assignment_operator_increments_refcount) +{ + MockRefCounted* mock_object = new MockRefCounted(); + Core::RefPtr ref_ptr(mock_object); + Core::RefPtr temp_ref_ptr = ref_ptr; + ASSERT_EQ(mock_object->ref_count(), 2); +} + +TEST_F(RefPtrTest, move_assignment_operator_doesnt_increase_refcount) +{ + MockRefCounted* mock_object = new MockRefCounted(); + Core::RefPtr ref_ptr(mock_object); + Core::RefPtr temp_ref_ptr = std::move(ref_ptr); + ASSERT_EQ(mock_object->ref_count(), 1); +} + +TEST_F(RefPtrTest, value_pointer_assignment_operator_doesnt_increase_refcount) +{ + MockRefCounted* mock_object = new MockRefCounted(); + Core::RefPtr ref_ptr = mock_object; + ASSERT_EQ(mock_object->ref_count(), 1); +} + +} From bf3b89c92063bb1aede0f76055d9eca4c057eb65 Mon Sep 17 00:00:00 2001 From: infirit89 Date: Mon, 16 Feb 2026 23:13:40 +0200 Subject: [PATCH 23/51] chore(Core): add smart pointer casts --- Libraries/LibCore/RefPtr.h | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Libraries/LibCore/RefPtr.h b/Libraries/LibCore/RefPtr.h index 206d70f3..20c7c78b 100644 --- a/Libraries/LibCore/RefPtr.h +++ b/Libraries/LibCore/RefPtr.h @@ -99,7 +99,7 @@ class RefPtr final { return std::exchange(m_data, nullptr); } - [[nodiscard]] constexpr value_type* data() noexcept + [[nodiscard]] constexpr value_type* data() const noexcept { return m_data; } @@ -209,4 +209,14 @@ class RefPtr final { value_type* m_data = nullptr; }; +template +constexpr RefPtr static_pointer_cast(RefPtr const& other) { + return RefPtr(static_cast(other.data())); +} + +template +constexpr RefPtr dynamic_pointer_cast(RefPtr const& other) { + return RefPtr(dynamic_cast(other.data())); +} + } From d761f83c00a79abe3e38837c6089d20b10ae101e Mon Sep 17 00:00:00 2001 From: infirit89 Date: Mon, 16 Feb 2026 23:14:03 +0200 Subject: [PATCH 24/51] chore(Core): add nonowning smart pointer for intrusive ref counted objects --- Libraries/LibCore/WeakRef.h | 119 ++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 Libraries/LibCore/WeakRef.h diff --git a/Libraries/LibCore/WeakRef.h b/Libraries/LibCore/WeakRef.h new file mode 100644 index 00000000..ef408b7c --- /dev/null +++ b/Libraries/LibCore/WeakRef.h @@ -0,0 +1,119 @@ +#pragma once +#include "RefPtr.h" +#include +#include +#include + +namespace Terran::Core { + +template +class WeakPtr final { + using value_type = TValue; + +public: + constexpr WeakPtr() noexcept = default; + constexpr WeakPtr(WeakPtr const& other) noexcept + : m_data(other.m_data) + { + } + + template + requires(std::is_convertible_v) + constexpr WeakPtr(WeakPtr const& other) noexcept + : m_data(other.m_data) + { + } + + template + requires(std::is_convertible_v) + constexpr WeakPtr(RefPtr const& other) noexcept + : m_data(other.data()) + { + } + + constexpr WeakPtr(WeakPtr&& other) noexcept + : m_data(other.release()) + { + } + + template + requires(std::is_convertible_v) + constexpr WeakPtr(WeakPtr&& other) noexcept + : m_data(other.release()) + { + } + + constexpr ~WeakPtr() noexcept = default; + + [[nodiscard]] constexpr value_type* release() noexcept + { + return std::exchange(m_data, nullptr); + } + + [[nodiscard]] constexpr RefPtr lock() noexcept + { + return RefPtr(m_data); + } + + constexpr bool operator==(WeakPtr const& other) const noexcept + { + return m_data == other.m_data; + } + + constexpr operator bool() const noexcept + { + return !is_null(); + } + + constexpr bool is_null() const noexcept + { + return !m_data; + } + + constexpr WeakPtr& operator=(WeakPtr const& other) noexcept + { + if (this == other) + return *this; + + m_data = other.m_data; + return *this; + } + + template + requires(std::is_convertible_v) + constexpr WeakPtr& operator=(WeakPtr const& other) noexcept + { + if (this == other) + return *this; + + m_data = other.m_data; + return *this; + } + + template + requires(std::is_convertible_v) + constexpr WeakPtr& operator=(RefPtr const& other) noexcept + { + m_data = other.data(); + return *this; + } + + constexpr WeakPtr& operator=(WeakPtr&& other) noexcept + { + m_data = other.release(); + return *this; + } + + template + requires(std::is_convertible_v) + constexpr WeakPtr& operator=(WeakPtr&& other) noexcept + { + m_data = other.release(); + return *this; + } + +private: + value_type* m_data = nullptr; +}; + +} From 4923a795b6867e3bf5571e312262647b66be044a Mon Sep 17 00:00:00 2001 From: infirit89 Date: Mon, 16 Feb 2026 23:19:47 +0200 Subject: [PATCH 25/51] chore(Core): make std::forward be called on a forwarding reference --- Libraries/LibCore/LayerStack.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Libraries/LibCore/LayerStack.h b/Libraries/LibCore/LayerStack.h index 14a5993c..5dd04824 100644 --- a/Libraries/LibCore/LayerStack.h +++ b/Libraries/LibCore/LayerStack.h @@ -45,11 +45,11 @@ class LayerStack final { * * @param args The arguments passed to the constructor of the layer */ - template + template requires(std::is_base_of_v) - Result push(Args... args) + Result push(TArgs&&... args) { - Unique layer = Unique::create(*m_dispatcher, std::forward(args)...); + Unique layer = Unique::create(*m_dispatcher, std::forward(args)...); auto result = layer->on_attach(); if (!result.is_ok()) From 1d99b9ddb4182687e17a574bcca26c0ef72f7be9 Mon Sep 17 00:00:00 2001 From: infirit89 Date: Mon, 16 Feb 2026 23:21:24 +0200 Subject: [PATCH 26/51] chore(Asset): make create asset methods accept forwarding reference to asset parameters --- Libraries/LibAsset/AssetManager.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Libraries/LibAsset/AssetManager.h b/Libraries/LibAsset/AssetManager.h index efe243af..9767a20f 100644 --- a/Libraries/LibAsset/AssetManager.h +++ b/Libraries/LibAsset/AssetManager.h @@ -93,7 +93,7 @@ class AssetManager final { requires( std::is_base_of_v, HasStaticType) - Core::Shared create_asset(std::filesystem::path const& file_path, TArgs... args) + Core::Shared create_asset(std::filesystem::path const& file_path, TArgs&&... args) { AssetMetadata metadata; metadata.Handle = AssetHandle(); @@ -110,7 +110,7 @@ class AssetManager final { AssetMetadataRegistry::add_asset_metadata(metadata); - Core::Shared asset = Core::CreateShared(args...); + Core::Shared asset = Core::CreateShared(std::forward(args)...); m_loaded_assets[metadata.Handle] = asset; AssetImporterRegistry::save(metadata, asset); @@ -123,9 +123,9 @@ class AssetManager final { // something like create_memory_asset(CreateAssetMetadata::Yes) template requires(std::is_base_of_v) - Core::Shared create_memory_asset(TArgs... args) + Core::Shared create_memory_asset(TArgs&&... args) { - Core::Shared asset = Core::CreateShared(args...); + Core::Shared asset = Core::CreateShared(std::forward(args)...); m_loaded_assets[asset->m_handle] = asset; return Core::DynamicCast(m_loaded_assets[asset->m_handle]); From 3c96a470d76b46335742d860f05953688e8ab079 Mon Sep 17 00:00:00 2001 From: infirit89 Date: Mon, 16 Feb 2026 23:22:23 +0200 Subject: [PATCH 27/51] test(Core): remove unnecessary header guard in ByteBufferTests --- Tests/LibCore/ByteBufferTests.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/Tests/LibCore/ByteBufferTests.cpp b/Tests/LibCore/ByteBufferTests.cpp index e14b9219..2af64cc5 100644 --- a/Tests/LibCore/ByteBufferTests.cpp +++ b/Tests/LibCore/ByteBufferTests.cpp @@ -1,5 +1,3 @@ -#pragma once - #include #include From d8e8e06ce7f3e11dae4a16ab8a252a92e5c999cb Mon Sep 17 00:00:00 2001 From: infirit89 Date: Wed, 18 Feb 2026 00:28:47 +0200 Subject: [PATCH 28/51] chore(Core): add some missing stuff from first pass of intrusive refs --- Libraries/LibCore/RefPtr.h | 31 +++++++++++++++++++------------ Libraries/LibCore/WeakRef.h | 12 ++++++------ 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/Libraries/LibCore/RefPtr.h b/Libraries/LibCore/RefPtr.h index 20c7c78b..5272af74 100644 --- a/Libraries/LibCore/RefPtr.h +++ b/Libraries/LibCore/RefPtr.h @@ -11,12 +11,12 @@ class RefCounted { public: virtual ~RefCounted() = default; - void increment_count() + void increment_count() const { m_reference_count++; } - void decrement_count() + void decrement_count() const { m_reference_count--; } @@ -27,7 +27,7 @@ class RefCounted { } private: - std::atomic_size_t m_reference_count = 0; + mutable std::atomic_size_t m_reference_count = 0; }; template @@ -123,7 +123,7 @@ class RefPtr final { constexpr RefPtr& operator=(RefPtr const& other) noexcept { - if (this == other) + if (this == &other) return *this; other.increment_ref(); @@ -136,7 +136,7 @@ class RefPtr final { requires(std::is_convertible_v) constexpr RefPtr& operator=(RefPtr const& other) noexcept { - if (this == other) + if (this == &other) return *this; other.increment_ref(); @@ -162,7 +162,8 @@ class RefPtr final { return *this; } - constexpr RefPtr& operator=(TValue* data) noexcept { + constexpr RefPtr& operator=(TValue* data) noexcept + { m_data = data; increment_ref(); return *this; @@ -179,19 +180,20 @@ class RefPtr final { } template - static constexpr RefPtr create(TArgs&&... args) { + static constexpr RefPtr create(TArgs&&... args) + { return RefPtr(new TValue(std::forward(args)...)); } private: - void increment_ref() + void increment_ref() const { if (m_data) { m_data->increment_count(); } } - void decrement_ref() + void decrement_ref() const { if (!m_data) { return; @@ -206,16 +208,21 @@ class RefPtr final { } private: - value_type* m_data = nullptr; + mutable value_type* m_data = nullptr; + + template + friend class RefPtr; }; template -constexpr RefPtr static_pointer_cast(RefPtr const& other) { +constexpr RefPtr static_pointer_cast(RefPtr const& other) +{ return RefPtr(static_cast(other.data())); } template -constexpr RefPtr dynamic_pointer_cast(RefPtr const& other) { +constexpr RefPtr dynamic_pointer_cast(RefPtr const& other) +{ return RefPtr(dynamic_cast(other.data())); } diff --git a/Libraries/LibCore/WeakRef.h b/Libraries/LibCore/WeakRef.h index ef408b7c..eb5578eb 100644 --- a/Libraries/LibCore/WeakRef.h +++ b/Libraries/LibCore/WeakRef.h @@ -18,14 +18,14 @@ class WeakPtr final { } template - requires(std::is_convertible_v) + requires(std::is_convertible_v) constexpr WeakPtr(WeakPtr const& other) noexcept : m_data(other.m_data) { } template - requires(std::is_convertible_v) + requires(std::is_convertible_v) constexpr WeakPtr(RefPtr const& other) noexcept : m_data(other.data()) { @@ -37,7 +37,7 @@ class WeakPtr final { } template - requires(std::is_convertible_v) + requires(std::is_convertible_v) constexpr WeakPtr(WeakPtr&& other) noexcept : m_data(other.release()) { @@ -80,7 +80,7 @@ class WeakPtr final { } template - requires(std::is_convertible_v) + requires(std::is_convertible_v) constexpr WeakPtr& operator=(WeakPtr const& other) noexcept { if (this == other) @@ -91,7 +91,7 @@ class WeakPtr final { } template - requires(std::is_convertible_v) + requires(std::is_convertible_v) constexpr WeakPtr& operator=(RefPtr const& other) noexcept { m_data = other.data(); @@ -105,7 +105,7 @@ class WeakPtr final { } template - requires(std::is_convertible_v) + requires(std::is_convertible_v) constexpr WeakPtr& operator=(WeakPtr&& other) noexcept { m_data = other.release(); From fa03f8f523cfa30f04782064c9bb28e9003d84fc Mon Sep 17 00:00:00 2001 From: infirit89 Date: Wed, 18 Feb 2026 00:29:42 +0200 Subject: [PATCH 29/51] chore(Asset): make Asset be intrusive ref counted --- Libraries/LibAsset/Asset.h | 3 ++- Libraries/LibAsset/AssetImporter.h | 5 ++-- Libraries/LibAsset/AssetImporterRegistry.cpp | 2 +- Libraries/LibAsset/AssetImporterRegistry.h | 3 ++- Libraries/LibAsset/AssetManager.h | 27 ++++++++++---------- 5 files changed, 22 insertions(+), 18 deletions(-) diff --git a/Libraries/LibAsset/Asset.h b/Libraries/LibAsset/Asset.h index 9e924077..8c0b6e2e 100644 --- a/Libraries/LibAsset/Asset.h +++ b/Libraries/LibAsset/Asset.h @@ -4,13 +4,14 @@ #include +#include #include #include namespace Terran { namespace Asset { -class Asset { +class Asset : public Core::RefCounted { public: Asset() = default; virtual ~Asset() = default; diff --git a/Libraries/LibAsset/AssetImporter.h b/Libraries/LibAsset/AssetImporter.h index 6b9e7dae..a4ce970d 100644 --- a/Libraries/LibAsset/AssetImporter.h +++ b/Libraries/LibAsset/AssetImporter.h @@ -7,18 +7,19 @@ #include #include +#include #include namespace Terran::Asset { -using AssetLoadResult = Core::Result, Core::Shared>; +using AssetLoadResult = Core::Result, Core::Shared>; class AssetImporter { public: virtual ~AssetImporter() = default; [[nodiscard]] virtual AssetLoadResult load(AssetMetadata const& assetMetadata) = 0; - virtual bool save(AssetMetadata const& assetMetadata, Core::Shared const& asset) = 0; + virtual bool save(AssetMetadata const& assetMetadata, Core::RefPtr const& asset) = 0; [[nodiscard]] virtual bool can_handle(std::filesystem::path const& assetPath) = 0; [[nodiscard]] virtual AssetTypeId asset_type() = 0; }; diff --git a/Libraries/LibAsset/AssetImporterRegistry.cpp b/Libraries/LibAsset/AssetImporterRegistry.cpp index 0ae6117b..63214664 100644 --- a/Libraries/LibAsset/AssetImporterRegistry.cpp +++ b/Libraries/LibAsset/AssetImporterRegistry.cpp @@ -25,7 +25,7 @@ AssetLoadResult AssetImporterRegistry::load(AssetMetadata const& assetMetadata) return { Core::CreateShared(AssetImporterError::Code::ImporterNotFound) }; } -bool AssetImporterRegistry::save(AssetMetadata const& assetMetadata, Terran::Core::Shared const& asset) +bool AssetImporterRegistry::save(AssetMetadata const& assetMetadata, Core::RefPtr const& asset) { AssetTypeId const type_id = assetMetadata.Type; if (s_loaders.contains(type_id)) diff --git a/Libraries/LibAsset/AssetImporterRegistry.h b/Libraries/LibAsset/AssetImporterRegistry.h index a3626ae3..541bb855 100644 --- a/Libraries/LibAsset/AssetImporterRegistry.h +++ b/Libraries/LibAsset/AssetImporterRegistry.h @@ -6,6 +6,7 @@ #include "AssetTypes.h" #include +#include #include #include @@ -25,7 +26,7 @@ class AssetImporterRegistry final { } static AssetLoadResult load(AssetMetadata const& assetMetadata); - static bool save(AssetMetadata const& assetMetadata, Terran::Core::Shared const& asset); + static bool save(AssetMetadata const& assetMetadata, Terran::Core::RefPtr const& asset); static bool exists_for_path(std::filesystem::path const& path) { diff --git a/Libraries/LibAsset/AssetManager.h b/Libraries/LibAsset/AssetManager.h index 9767a20f..1343539a 100644 --- a/Libraries/LibAsset/AssetManager.h +++ b/Libraries/LibAsset/AssetManager.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -31,7 +32,7 @@ enum class RemoveAssetMetadata : uint8_t { class AssetManager final { using AssetChangeCallbackFn = std::function const&)>; - using asset_container = std::unordered_map>; + using asset_container = std::unordered_map>; public: AssetManager(Core::EventDispatcher& event_dispatcher); @@ -46,10 +47,10 @@ class AssetManager final { template requires(std::is_base_of_v) - Core::Shared asset_by_handle(AssetHandle const& assetHandle) + Core::RefPtr asset_by_handle(AssetHandle const& assetHandle) { if (m_loaded_assets.contains(assetHandle)) - return Core::DynamicCast(m_loaded_assets.at(assetHandle)); + return Core::dynamic_pointer_cast(m_loaded_assets.at(assetHandle)); AssetMetadata& info = AssetMetadataRegistry::asset_metadata_by_handle__internal(assetHandle); @@ -63,7 +64,7 @@ class AssetManager final { return nullptr; } - Core::Shared const& asset = assetResult.value(); + Core::RefPtr const& asset = assetResult.value(); asset->m_handle = assetHandle; m_loaded_assets[assetHandle] = asset; return Core::DynamicCast(m_loaded_assets[assetHandle]); @@ -71,10 +72,10 @@ class AssetManager final { template requires(std::is_base_of_v) - Core::Shared asset_by_metadata(AssetMetadata const& assetMetadata) + Core::RefPtr asset_by_metadata(AssetMetadata const& assetMetadata) { if (m_loaded_assets.contains(assetMetadata.Handle)) - return Core::DynamicCast(m_loaded_assets.at(assetMetadata.Handle)); + return Core::dynamic_pointer_cast(m_loaded_assets.at(assetMetadata.Handle)); AssetLoadResult assetResult = AssetImporterRegistry::load(assetMetadata); @@ -83,7 +84,7 @@ class AssetManager final { return nullptr; } - Core::Shared const& asset = assetResult.value(); + Core::RefPtr const& asset = assetResult.value(); asset->m_handle = assetMetadata.Handle; m_loaded_assets[assetMetadata.Handle] = asset; return Core::DynamicCast(m_loaded_assets[assetMetadata.Handle]); @@ -93,7 +94,7 @@ class AssetManager final { requires( std::is_base_of_v, HasStaticType) - Core::Shared create_asset(std::filesystem::path const& file_path, TArgs&&... args) + Core::RefPtr create_asset(std::filesystem::path const& file_path, TArgs&&... args) { AssetMetadata metadata; metadata.Handle = AssetHandle(); @@ -110,12 +111,12 @@ class AssetManager final { AssetMetadataRegistry::add_asset_metadata(metadata); - Core::Shared asset = Core::CreateShared(std::forward(args)...); + Core::RefPtr asset = Core::RefPtr::create(std::forward(args)...); m_loaded_assets[metadata.Handle] = asset; AssetImporterRegistry::save(metadata, asset); - return Core::DynamicCast(m_loaded_assets[metadata.Handle]); + return Core::dynamic_pointer_cast(m_loaded_assets[metadata.Handle]); } // NOTE: maybe we should take in a parameter that signifies @@ -123,12 +124,12 @@ class AssetManager final { // something like create_memory_asset(CreateAssetMetadata::Yes) template requires(std::is_base_of_v) - Core::Shared create_memory_asset(TArgs&&... args) + Core::RefPtr create_memory_asset(TArgs&&... args) { - Core::Shared asset = Core::CreateShared(std::forward(args)...); + Core::RefPtr asset = Core::RefPtr::create(std::forward(args)...); m_loaded_assets[asset->m_handle] = asset; - return Core::DynamicCast(m_loaded_assets[asset->m_handle]); + return Core::dynamic_pointer_cast(m_loaded_assets[asset->m_handle]); } void remove_asset(Core::UUID const& handle, RemoveAssetMetadata remove_metadata = RemoveAssetMetadata::Yes); From f9e0d8041e9133cf31d5c3ce10fd6f3bdb093a1c Mon Sep 17 00:00:00 2001 From: infirit89 Date: Wed, 18 Feb 2026 00:30:23 +0200 Subject: [PATCH 30/51] chore(Scene): change scene to reflect intrusive ref counting in Asset Asset is now intrusive reference counted, thus there were some changes needed in LibScene, e.g. changing Core::Shared to Core::RefPtr --- Libraries/LibScene/Scene.h | 30 +++++++++++--------------- Libraries/LibScene/SceneEvent.h | 12 ++++++----- Libraries/LibScene/SceneManager.cpp | 19 ++++++++-------- Libraries/LibScene/SceneManager.h | 14 ++++++------ Libraries/LibScene/SceneSerializer.cpp | 17 +++++++++------ Libraries/LibScene/SceneSerializer.h | 5 +++-- 6 files changed, 50 insertions(+), 47 deletions(-) diff --git a/Libraries/LibScene/Scene.h b/Libraries/LibScene/Scene.h index 8c8331e8..76174c83 100644 --- a/Libraries/LibScene/Scene.h +++ b/Libraries/LibScene/Scene.h @@ -61,12 +61,20 @@ class Scene final : public Asset::Asset { return m_is_playing; } + + void update_transform_hierarchy(); + void update_entity_transform(Entity entity); + + void convert_to_local_space(Entity entity); + void convert_to_world_space(Entity entity); + + void sort_entities(); + +private: + template - static void copy_component(Entity source_entity, Entity destination_entity, Core::Shared source_scene, Core::Shared destination_scene) + static void copy_component(Entity source_entity, Entity destination_entity, entt::registry& source_registry, entt::registry& destination_registry) { - entt::registry& source_registry = source_scene->m_registry; - entt::registry& destination_registry = destination_scene->m_registry; - if (!source_registry.all_of((entt::entity)source_entity)) { return; } @@ -76,20 +84,6 @@ class Scene final : public Asset::Asset { source_registry.get((entt::entity)source_entity)); } - Scene* raw() - { - return this; - } - - void update_transform_hierarchy(); - void update_entity_transform(Entity entity); - - void convert_to_local_space(Entity entity); - void convert_to_world_space(Entity entity); - - void sort_entities(); - -private: bool m_is_playing = false; std::unordered_map m_entity_map; diff --git a/Libraries/LibScene/SceneEvent.h b/Libraries/LibScene/SceneEvent.h index 5cc18fad..1e491fd0 100644 --- a/Libraries/LibScene/SceneEvent.h +++ b/Libraries/LibScene/SceneEvent.h @@ -4,30 +4,32 @@ #include #include +#include +#include namespace Terran::World { class SceneTransitionEvent { public: - SceneTransitionEvent(Core::Weak const& oldScene, Core::Shared const& newScene) + SceneTransitionEvent(Core::WeakPtr const& oldScene, Core::RefPtr const& newScene) : m_old_scene(oldScene) , m_new_scene(newScene) { } - Core::Weak old_scene() const + Core::WeakPtr old_scene() const { return m_old_scene; } - Core::Shared new_scene() const + Core::RefPtr new_scene() const { return m_new_scene; } private: - Core::Weak m_old_scene; - Core::Shared m_new_scene; + Core::WeakPtr m_old_scene; + Core::RefPtr m_new_scene; }; class SceneStartSimulationEvent { diff --git a/Libraries/LibScene/SceneManager.cpp b/Libraries/LibScene/SceneManager.cpp index 049bbfd6..49cb90b9 100644 --- a/Libraries/LibScene/SceneManager.cpp +++ b/Libraries/LibScene/SceneManager.cpp @@ -6,34 +6,35 @@ #include #include +#include namespace Terran::World { -Core::Shared SceneManager::create_empty_scene() +Core::RefPtr SceneManager::create_empty_scene() { return m_asset_system->asset_manager()->create_memory_asset(event_dispatcher); } -Core::Shared SceneManager::copy_scene(Core::Shared const& source_scene) +Core::RefPtr SceneManager::copy_scene(Core::RefPtr const& source_scene) { - Core::Shared scene = m_asset_system->asset_manager()->create_memory_asset(event_dispatcher); + Core::RefPtr scene = m_asset_system->asset_manager()->create_memory_asset(event_dispatcher); auto tag_view = source_scene->entities_with(); for (auto e : tag_view) { - Entity source_entity(e, source_scene.get()); + Entity source_entity(e, source_scene.data()); Entity destination_entity = scene->find_entity(source_entity.id()); Scene::copy_component( source_entity, destination_entity, - source_scene, - scene); + source_scene->m_registry, + scene->m_registry); Scene::copy_component( source_entity, destination_entity, - source_scene, - scene); + source_scene->m_registry, + scene->m_registry); } scene->sort_entities(); @@ -41,7 +42,7 @@ Core::Shared SceneManager::copy_scene(Core::Shared const& source_s return scene; } -void SceneManager::set_current_scene(Core::Shared new_scene) +void SceneManager::set_current_scene(Core::RefPtr new_scene) { if (!m_current_scene) { return; diff --git a/Libraries/LibScene/SceneManager.h b/Libraries/LibScene/SceneManager.h index 130dbb10..943687a7 100644 --- a/Libraries/LibScene/SceneManager.h +++ b/Libraries/LibScene/SceneManager.h @@ -2,11 +2,13 @@ #include "Scene.h" -#include #include #include #include #include +#include + +#include namespace Terran::World { @@ -19,9 +21,9 @@ class SceneManager final : public Core::Layer { { } - Core::Shared create_empty_scene(); + Core::RefPtr create_empty_scene(); - Core::Shared const& current_scene() + Core::RefPtr const& current_scene() { return m_current_scene; } @@ -31,11 +33,11 @@ class SceneManager final : public Core::Layer { m_current_scene.reset(); } - void set_current_scene(Core::Shared new_scene); - Core::Shared copy_scene(Core::Shared const& source_scene); + void set_current_scene(Core::RefPtr new_scene); + Core::RefPtr copy_scene(Core::RefPtr const& source_scene); private: - Core::Shared m_current_scene; + Core::RefPtr m_current_scene; Core::RawPtr m_asset_system; }; diff --git a/Libraries/LibScene/SceneSerializer.cpp b/Libraries/LibScene/SceneSerializer.cpp index 7ebd0a1e..14d238a6 100644 --- a/Libraries/LibScene/SceneSerializer.cpp +++ b/Libraries/LibScene/SceneSerializer.cpp @@ -2,15 +2,18 @@ #include "Components.h" #include "Entity.h" #include "SceneSerializerError.h" +#include "SceneManager.h" -#include #include #include #include #include #include +#include + +#include +#include -#include #include #include #include @@ -71,9 +74,9 @@ static void serialize_entity(YAML::Emitter& out, Entity entity) out << YAML::EndMap; } -bool SceneSerializer::save(Asset::AssetMetadata const& assetMetadata, Core::Shared const& asset) +bool SceneSerializer::save(Asset::AssetMetadata const& assetMetadata, Core::RefPtr const& asset) { - Core::Shared scene = Core::DynamicCast(asset); + Core::RefPtr scene = Core::dynamic_pointer_cast(asset); if (!scene) return false; @@ -87,7 +90,7 @@ bool SceneSerializer::save(Asset::AssetMetadata const& assetMetadata, Core::Shar auto const tagComponentView = scene->entities_with(); for (auto e : tagComponentView) { - Entity entity(e, scene.get()); + Entity entity(e, scene.data()); serialize_entity(out, entity); } @@ -112,7 +115,7 @@ static YAML::const_iterator find_entity(YAML::Node const& scene, Terran::Core::U return scene.end(); } -static Entity deserialize_entity(YAML::Node const& data, YAML::Node const& scene, Core::Shared deserializedScene) +static Entity deserialize_entity(YAML::Node const& data, YAML::Node const& scene, Core::RefPtr deserializedScene) { try { Core::UUID id = data["Entity"].as(); @@ -174,7 +177,7 @@ Asset::AssetLoadResult SceneSerializer::load(Asset::AssetMetadata const& assetMe return { Core::CreateShared(SceneSerializerError::Code::NotFound, ex.what()) }; } - Core::Shared scene = m_scene_system.create_empty_scene(); + Core::RefPtr scene = m_scene_system.create_empty_scene(); if (auto entities = data["Entities"]; entities) { for (auto entity : entities) { diff --git a/Libraries/LibScene/SceneSerializer.h b/Libraries/LibScene/SceneSerializer.h index c3f5c47d..c4f246e1 100644 --- a/Libraries/LibScene/SceneSerializer.h +++ b/Libraries/LibScene/SceneSerializer.h @@ -1,16 +1,17 @@ #pragma once #include "Scene.h" +#include "SceneManager.h" #include #include +#include #include #include #include #include -#include #include namespace Terran::World { @@ -23,7 +24,7 @@ class SceneSerializer final : public Asset::AssetImporter { } ~SceneSerializer() override = default; - bool save(Asset::AssetMetadata const& assetMetadata, Core::Shared const& asset) override; + bool save(Asset::AssetMetadata const& assetMetadata, Core::RefPtr const& asset) override; Asset::AssetLoadResult load(Asset::AssetMetadata const& assetMetadata) override; [[nodiscard]] bool can_handle(std::filesystem::path const& assetPath) override { From e1dd7083a02466586d889dad4ed10a12fced8256 Mon Sep 17 00:00:00 2001 From: infirit89 Date: Wed, 18 Feb 2026 13:38:31 +0200 Subject: [PATCH 31/51] chore(Core): fix sonarcloud errors regarding RefPtr and WeakPtr --- Libraries/LibCore/RefPtr.h | 36 ++++++++++++++++++++++++++++-------- Libraries/LibCore/WeakRef.h | 6 ++++-- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/Libraries/LibCore/RefPtr.h b/Libraries/LibCore/RefPtr.h index 5272af74..05cb1933 100644 --- a/Libraries/LibCore/RefPtr.h +++ b/Libraries/LibCore/RefPtr.h @@ -1,10 +1,13 @@ #pragma once #include +#include #include #include +#include #include #include + namespace Terran::Core { class RefCounted { @@ -40,7 +43,6 @@ class RefPtr final { public: constexpr RefPtr() noexcept = default; constexpr RefPtr(std::nullptr_t) noexcept - : m_data(nullptr) { } @@ -84,6 +86,9 @@ class RefPtr final { { } + template + RefPtr(Unique const& other) = delete; + constexpr ~RefPtr() { reset(); @@ -104,6 +109,18 @@ class RefPtr final { return m_data; } + void swap(RefPtr& other) noexcept + { + std::swap(m_data, other.m_data); + } + + template + requires(std::is_convertible_v) + void swap(RefPtr& other) noexcept + { + std::swap(m_data, other.m_data); + } + constexpr bool operator==(RefPtr const& other) const noexcept { return m_data == other.m_data; @@ -147,18 +164,17 @@ class RefPtr final { constexpr RefPtr& operator=(RefPtr&& other) noexcept { - decrement_ref(); - m_data = other.m_data; - other.m_data = nullptr; + // NOTE: this cleans up memory because temp gets destroyed at the end of the scope + RefPtr temp { std::move(other) }; + swap(temp); return *this; } template requires(std::is_convertible_v) constexpr RefPtr& operator=(RefPtr&& other) noexcept { - decrement_ref(); - m_data = other.m_data; - other.m_data = nullptr; + RefPtr temp { std::move(other) }; + swap(temp); return *this; } @@ -169,6 +185,9 @@ class RefPtr final { return *this; } + template + RefPtr& operator=(Unique const& other) = delete; + constexpr operator bool() const noexcept { return !is_null(); @@ -202,7 +221,8 @@ class RefPtr final { m_data->decrement_count(); if (m_data->ref_count() == 0) { - delete m_data; + DefaultDelete deleter; + deleter(m_data); m_data = nullptr; } } diff --git a/Libraries/LibCore/WeakRef.h b/Libraries/LibCore/WeakRef.h index eb5578eb..02311907 100644 --- a/Libraries/LibCore/WeakRef.h +++ b/Libraries/LibCore/WeakRef.h @@ -100,7 +100,8 @@ class WeakPtr final { constexpr WeakPtr& operator=(WeakPtr&& other) noexcept { - m_data = other.release(); + WeakPtr temp { std::move(other) }; + m_data = temp.m_data; return *this; } @@ -108,7 +109,8 @@ class WeakPtr final { requires(std::is_convertible_v) constexpr WeakPtr& operator=(WeakPtr&& other) noexcept { - m_data = other.release(); + WeakPtr temp { std::move(other) }; + m_data = temp.m_data; return *this; } From 1f1ad4041f4ebfed48acdd58f04af1f194f517d9 Mon Sep 17 00:00:00 2001 From: infirit89 Date: Wed, 18 Feb 2026 13:43:16 +0200 Subject: [PATCH 32/51] chore(Asset): move designed to be private member data into private specifier --- Libraries/LibAsset/Asset.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/LibAsset/Asset.h b/Libraries/LibAsset/Asset.h index 8c0b6e2e..81f07f3d 100644 --- a/Libraries/LibAsset/Asset.h +++ b/Libraries/LibAsset/Asset.h @@ -32,7 +32,7 @@ class Asset : public Core::RefCounted { virtual AssetTypeId type() const = 0; -protected: +private: AssetHandle m_handle; AssetTypeId m_type_id; friend class AssetManager; From b979dfd8a5a668af7df388770f121f2549979840 Mon Sep 17 00:00:00 2001 From: infirit89 Date: Wed, 18 Feb 2026 13:44:01 +0200 Subject: [PATCH 33/51] chore(Scene): mark current_scene getter as const --- Libraries/LibScene/Scene.cpp | 2 +- Libraries/LibScene/SceneManager.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Libraries/LibScene/Scene.cpp b/Libraries/LibScene/Scene.cpp index 0e2a3278..c55235a2 100644 --- a/Libraries/LibScene/Scene.cpp +++ b/Libraries/LibScene/Scene.cpp @@ -61,7 +61,7 @@ Scene::Scene(Core::EventDispatcher& event_dispatcher, Core::UUID const& handle) , m_event_dispatcher(event_dispatcher) { auto const sceneEntity = m_registry.create(); - m_registry.emplace(sceneEntity, m_handle); + m_registry.emplace(sceneEntity, handle); } Scene::~Scene() diff --git a/Libraries/LibScene/SceneManager.h b/Libraries/LibScene/SceneManager.h index 943687a7..7c987d06 100644 --- a/Libraries/LibScene/SceneManager.h +++ b/Libraries/LibScene/SceneManager.h @@ -23,7 +23,7 @@ class SceneManager final : public Core::Layer { Core::RefPtr create_empty_scene(); - Core::RefPtr const& current_scene() + Core::RefPtr const& current_scene() const { return m_current_scene; } From 9a6adf166158dcd6bf659e04eeb7dc0c0e6f9298 Mon Sep 17 00:00:00 2001 From: infirit89 Date: Wed, 18 Feb 2026 17:09:06 +0200 Subject: [PATCH 34/51] chore(Core): add explicit keyword to RefPtr and WeakPtr Add the explicit keyword to explicit about the explicity of single parameter constructors and boolean conversion operators --- Libraries/LibCore/RefPtr.h | 16 ++++++++-------- Libraries/LibCore/WeakRef.h | 12 ++++++------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Libraries/LibCore/RefPtr.h b/Libraries/LibCore/RefPtr.h index 05cb1933..c54cccbf 100644 --- a/Libraries/LibCore/RefPtr.h +++ b/Libraries/LibCore/RefPtr.h @@ -42,11 +42,11 @@ class RefPtr final { public: constexpr RefPtr() noexcept = default; - constexpr RefPtr(std::nullptr_t) noexcept + explicit(false) constexpr RefPtr(std::nullptr_t) noexcept { } - constexpr RefPtr(TValue* data) noexcept + explicit(false) constexpr RefPtr(TValue* data) noexcept : m_data(data) { increment_ref(); @@ -54,7 +54,7 @@ class RefPtr final { template requires(std::is_convertible_v) - constexpr RefPtr(TYValue* data) noexcept + explicit(false) constexpr RefPtr(TYValue* data) noexcept : m_data(data) { increment_ref(); @@ -62,7 +62,7 @@ class RefPtr final { template requires(std::is_convertible_v) - constexpr RefPtr(RefPtr const& other) noexcept + explicit(false) constexpr RefPtr(RefPtr const& other) noexcept : m_data(other.m_data) { increment_ref(); @@ -70,18 +70,18 @@ class RefPtr final { template requires(std::is_convertible_v) - constexpr RefPtr(RefPtr&& other) noexcept + explicit(false) constexpr RefPtr(RefPtr&& other) noexcept : m_data(other.release()) { } - constexpr RefPtr(RefPtr const& other) noexcept + explicit(false) constexpr RefPtr(RefPtr const& other) noexcept : m_data(other.m_data) { increment_ref(); } - constexpr RefPtr(RefPtr&& other) noexcept + explicit(false) constexpr RefPtr(RefPtr&& other) noexcept : m_data(other.release()) { } @@ -188,7 +188,7 @@ class RefPtr final { template RefPtr& operator=(Unique const& other) = delete; - constexpr operator bool() const noexcept + explicit(false) constexpr operator bool() const noexcept { return !is_null(); } diff --git a/Libraries/LibCore/WeakRef.h b/Libraries/LibCore/WeakRef.h index 02311907..cde9023e 100644 --- a/Libraries/LibCore/WeakRef.h +++ b/Libraries/LibCore/WeakRef.h @@ -12,33 +12,33 @@ class WeakPtr final { public: constexpr WeakPtr() noexcept = default; - constexpr WeakPtr(WeakPtr const& other) noexcept + explicit(false) constexpr WeakPtr(WeakPtr const& other) noexcept : m_data(other.m_data) { } template requires(std::is_convertible_v) - constexpr WeakPtr(WeakPtr const& other) noexcept + explicit(false) constexpr WeakPtr(WeakPtr const& other) noexcept : m_data(other.m_data) { } template requires(std::is_convertible_v) - constexpr WeakPtr(RefPtr const& other) noexcept + explicit(false) constexpr WeakPtr(RefPtr const& other) noexcept : m_data(other.data()) { } - constexpr WeakPtr(WeakPtr&& other) noexcept + explicit(false) constexpr WeakPtr(WeakPtr&& other) noexcept : m_data(other.release()) { } template requires(std::is_convertible_v) - constexpr WeakPtr(WeakPtr&& other) noexcept + explicit(false) constexpr WeakPtr(WeakPtr&& other) noexcept : m_data(other.release()) { } @@ -60,7 +60,7 @@ class WeakPtr final { return m_data == other.m_data; } - constexpr operator bool() const noexcept + explicit(false) constexpr operator bool() const noexcept { return !is_null(); } From a7d168015e71674af5c1f304e0da2867dc1f003f Mon Sep 17 00:00:00 2001 From: infirit89 Date: Wed, 18 Feb 2026 20:33:40 +0200 Subject: [PATCH 35/51] chore(Asset): add asset free queue in Asset Manager --- Libraries/LibAsset/Asset.cpp | 10 ++++++++- Libraries/LibAsset/Asset.h | 5 ++++- Libraries/LibAsset/AssetEvents.h | 5 +++-- Libraries/LibAsset/AssetManager.cpp | 24 ++++++++++++++++---- Libraries/LibAsset/AssetManager.h | 34 +++++++++++++++++++++-------- Libraries/LibCore/RefPtr.h | 1 - 6 files changed, 61 insertions(+), 18 deletions(-) diff --git a/Libraries/LibAsset/Asset.cpp b/Libraries/LibAsset/Asset.cpp index c2445efe..aba56464 100644 --- a/Libraries/LibAsset/Asset.cpp +++ b/Libraries/LibAsset/Asset.cpp @@ -1,5 +1,13 @@ #include "Asset.h" +#include "AssetManager.h" -namespace TerranEngine +namespace Terran::Asset { + +Asset::~Asset() { + if (m_asset_manager) { + m_asset_manager->enqueue_asset_for_deletion(m_handle); + } +} + } diff --git a/Libraries/LibAsset/Asset.h b/Libraries/LibAsset/Asset.h index 81f07f3d..ecbabde7 100644 --- a/Libraries/LibAsset/Asset.h +++ b/Libraries/LibAsset/Asset.h @@ -2,6 +2,7 @@ #include "AssetTypes.h" +#include #include #include @@ -11,10 +12,11 @@ namespace Terran { namespace Asset { +class AssetManager; class Asset : public Core::RefCounted { public: Asset() = default; - virtual ~Asset() = default; + virtual ~Asset() override; Asset(AssetHandle const& handle) : m_handle(handle) { @@ -35,6 +37,7 @@ class Asset : public Core::RefCounted { private: AssetHandle m_handle; AssetTypeId m_type_id; + Core::RawPtr m_asset_manager; friend class AssetManager; }; diff --git a/Libraries/LibAsset/AssetEvents.h b/Libraries/LibAsset/AssetEvents.h index b9105840..0ffdb961 100644 --- a/Libraries/LibAsset/AssetEvents.h +++ b/Libraries/LibAsset/AssetEvents.h @@ -40,8 +40,9 @@ class AssetRenamedEvent { { return m_old_file_name; } - - [[nodiscard]] constexpr std::filesystem::path const& new_file_name() const noexcept { + + [[nodiscard]] constexpr std::filesystem::path const& new_file_name() const noexcept + { return m_new_file_name; } diff --git a/Libraries/LibAsset/AssetManager.cpp b/Libraries/LibAsset/AssetManager.cpp index cb5b474c..74a2cde9 100644 --- a/Libraries/LibAsset/AssetManager.cpp +++ b/Libraries/LibAsset/AssetManager.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -32,7 +33,7 @@ AssetManager::~AssetManager() TR_CORE_INFO(TR_LOG_ASSET, "Shutdown asset manager"); } -AssetHandle AssetManager::import_asset(std::filesystem::path const& asset_path) +AssetHandle AssetManager::import_asset(std::filesystem::path const& asset_path) const { AssetHandle asset_handle = AssetMetadataRegistry::asset_handle_from_path(asset_path); @@ -72,15 +73,26 @@ void AssetManager::reload_asset_by_handle(AssetHandle const& handle) m_loaded_assets[handle] = asset_result.value(); } -void AssetManager::remove_asset(Core::UUID const& handle, RemoveAssetMetadata remove_metadata) +Core::Result AssetManager::remove_asset(Core::UUID const& handle, RemoveAssetImmediately remove_immediately, RemoveAssetMetadata remove_metadata) { - if (m_loaded_assets.contains(handle)) { + if(!m_loaded_assets.contains(handle)) + return { AssetRemoveError::AssetNotFound }; + + if (remove_metadata == RemoveAssetMetadata::Yes && !AssetMetadataRegistry::contains(handle)) { + return { AssetRemoveError::MetadatNotFound }; + } + + if (remove_immediately == RemoveAssetImmediately::Yes) { m_loaded_assets.erase(handle); + } else { + m_free_queue.emplace_back(handle); } - if (remove_metadata == RemoveAssetMetadata::Yes && AssetMetadataRegistry::contains(handle)) { + if (remove_metadata == RemoveAssetMetadata::Yes) { AssetMetadataRegistry::erase(handle); } + + return {}; } void AssetManager::on_filesystem_changed(std::vector const& file_system_events) @@ -144,4 +156,8 @@ void AssetManager::on_asset_renamed(AssetHandle const& handle, std::filesystem:: m_event_dispatcher.trigger(renamed_event); } +void AssetManager::enqueue_asset_for_deletion(AssetHandle const& handle) { + m_free_queue.emplace_back(handle); +} + } diff --git a/Libraries/LibAsset/AssetManager.h b/Libraries/LibAsset/AssetManager.h index 1343539a..a6f7eaa3 100644 --- a/Libraries/LibAsset/AssetManager.h +++ b/Libraries/LibAsset/AssetManager.h @@ -13,8 +13,10 @@ #include #include #include +#include #include +#include #include #include #include @@ -30,9 +32,20 @@ enum class RemoveAssetMetadata : uint8_t { Yes = 1 }; +enum class RemoveAssetImmediately : uint8_t { + No = 0, + Yes = 1, +}; + +enum class AssetRemoveError : uint8_t { + AssetNotFound, + MetadatNotFound +}; + class AssetManager final { using AssetChangeCallbackFn = std::function const&)>; - using asset_container = std::unordered_map>; + using asset_container_type = std::unordered_map>; + using free_queue_type = std::deque; public: AssetManager(Core::EventDispatcher& event_dispatcher); @@ -40,7 +53,7 @@ class AssetManager final { std::filesystem::path filesystem_path(std::filesystem::path const& path); - AssetHandle import_asset(std::filesystem::path const& asset_path); + AssetHandle import_asset(std::filesystem::path const& asset_path) const; void reload_asset_by_handle(AssetHandle const& handle); void SetAssetChangedCallback(AssetChangeCallbackFn const& callback) { m_asset_change_callback = callback; } @@ -102,9 +115,9 @@ class AssetManager final { metadata.Type = TAsset::static_type(); int file_occurrence_count = 2; - while (file_exists(metadata.Path)) { + while (std::filesystem::exists(metadata.Path)) { // Format: parent_directory/file_name (occurrence_count).extension - metadata.Path = std::format("{0} ({1}){2}", (file_path.parent_path() / file_path.stem()).string(), std::to_string(file_occurrence_count), file_path.extension().string()); + metadata.Path = std::format("{} ({}){}", (file_path.parent_path() / file_path.stem()).string(), std::to_string(file_occurrence_count), file_path.extension().string()); file_occurrence_count++; } @@ -132,20 +145,23 @@ class AssetManager final { return Core::dynamic_pointer_cast(m_loaded_assets[asset->m_handle]); } - void remove_asset(Core::UUID const& handle, RemoveAssetMetadata remove_metadata = RemoveAssetMetadata::Yes); - - static bool file_exists(std::filesystem::path const& path); + Core::Result remove_asset(Core::UUID const& handle, RemoveAssetImmediately remove_immediately = RemoveAssetImmediately::Yes, RemoveAssetMetadata remove_metadata = RemoveAssetMetadata::Yes); private: void on_filesystem_changed(std::vector const& file_system_events); void on_asset_removed(AssetHandle const& handle); void on_asset_renamed(AssetHandle const& handle, std::filesystem::path const& new_file_name); - std::filesystem::path relative_path(std::filesystem::path const& path); + void enqueue_asset_for_deletion(AssetHandle const& handle); private: - asset_container m_loaded_assets; + asset_container_type m_loaded_assets; AssetChangeCallbackFn m_asset_change_callback; Core::EventDispatcher& m_event_dispatcher; + //NOTE: this needs to be a multithreaded queue, either with or without a lock!!! + //This works for now because the engine is not multithreaded + //Will cause a bunch of problems if not changed and the engine goes multithreaded!!! + free_queue_type m_free_queue; + friend class Asset; }; } diff --git a/Libraries/LibCore/RefPtr.h b/Libraries/LibCore/RefPtr.h index c54cccbf..81caf6b2 100644 --- a/Libraries/LibCore/RefPtr.h +++ b/Libraries/LibCore/RefPtr.h @@ -4,7 +4,6 @@ #include #include #include -#include #include #include From cd532af3196914a4f588e4181f7b54ea277eb6db Mon Sep 17 00:00:00 2001 From: infirit89 Date: Thu, 19 Feb 2026 00:56:00 +0200 Subject: [PATCH 36/51] chore(Asset): add implementation for add_asset_metadata --- Libraries/LibAsset/AssetMetadataRegistry.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Libraries/LibAsset/AssetMetadataRegistry.cpp b/Libraries/LibAsset/AssetMetadataRegistry.cpp index ae75e948..07bea557 100644 --- a/Libraries/LibAsset/AssetMetadataRegistry.cpp +++ b/Libraries/LibAsset/AssetMetadataRegistry.cpp @@ -132,5 +132,9 @@ bool AssetMetadataRegistry::contains(AssetHandle const& handle) { return s_asset_metadata.contains(handle); } +void AssetMetadataRegistry::add_asset_metadata(const AssetMetadata &metadata) { + s_asset_metadata.emplace(metadata.Handle, metadata); +} + } } From 1ef23d1d52cdf2e59b17d84e2fe0ef1be0ed27d7 Mon Sep 17 00:00:00 2001 From: infirit89 Date: Thu, 19 Feb 2026 00:57:17 +0200 Subject: [PATCH 37/51] test(Scene): add unit tests for Scene class --- Libraries/LibScene/Scene.cpp | 2 +- Libraries/LibScene/Scene.h | 6 +- Tests/LibScene/Main.cpp | 6 + Tests/LibScene/SceneTests.cpp | 313 ++++++++++++++++++++++++++++++++++ Tests/LibScene/premake5.lua | 50 ++++++ premake-native.lua | 1 + 6 files changed, 374 insertions(+), 4 deletions(-) create mode 100644 Tests/LibScene/Main.cpp create mode 100644 Tests/LibScene/SceneTests.cpp create mode 100644 Tests/LibScene/premake5.lua diff --git a/Libraries/LibScene/Scene.cpp b/Libraries/LibScene/Scene.cpp index c55235a2..bdd005ee 100644 --- a/Libraries/LibScene/Scene.cpp +++ b/Libraries/LibScene/Scene.cpp @@ -88,7 +88,7 @@ Entity Scene::create_entity(std::string const& name, Terran::Core::UUID const& u return entity; } -Entity Scene::create_entity() +Entity Scene::create_empty_entity() { entt::entity e = m_registry.create(); Entity entity(e, this); diff --git a/Libraries/LibScene/Scene.h b/Libraries/LibScene/Scene.h index 76174c83..0808f567 100644 --- a/Libraries/LibScene/Scene.h +++ b/Libraries/LibScene/Scene.h @@ -31,9 +31,9 @@ class Scene final : public Asset::Asset { Entity create_entity(std::string const& name = std::string()); Entity create_entity(std::string const& name, Core::UUID const& uuid); - Entity create_entity(); + Entity create_empty_entity(); - void destrory_entity(Entity entity, bool first); + void destrory_entity(Entity entity, bool first = true); void start_runtime(); void stop_runtime(); @@ -49,7 +49,7 @@ class Scene final : public Asset::Asset { return m_registry.view(exclude); } - std::unordered_map& GetEntityMap() + std::unordered_map& entity_map() { return m_entity_map; } diff --git a/Tests/LibScene/Main.cpp b/Tests/LibScene/Main.cpp new file mode 100644 index 00000000..64becff4 --- /dev/null +++ b/Tests/LibScene/Main.cpp @@ -0,0 +1,6 @@ +#include + +int main(int argc, char **argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/Tests/LibScene/SceneTests.cpp b/Tests/LibScene/SceneTests.cpp new file mode 100644 index 00000000..e7c90919 --- /dev/null +++ b/Tests/LibScene/SceneTests.cpp @@ -0,0 +1,313 @@ +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace Terran::World::Tests { + +class SceneTest : public testing::Test { + +protected: + + void on_scene_start(SceneStartSimulationEvent&) { + m_is_scene_start_event_triggered = true; + } + + void on_scene_stop(SceneStopSimulationEvent&) { + m_is_scene_stop_event_triggered = true; + } + + SceneTest() + : m_scene(Core::RefPtr::create(m_event_dispatcher)) + { + m_event_dispatcher.handlers().connect<&SceneTest::on_scene_start>(this); + m_event_dispatcher.handlers().connect<&SceneTest::on_scene_stop>(this); + } + virtual ~SceneTest() override = default; + + Core::EventDispatcher m_event_dispatcher; + Core::RefPtr m_scene; + bool m_is_scene_start_event_triggered = false; + bool m_is_scene_stop_event_triggered = false; +}; + +TEST_F(SceneTest, create_empty_entity_returns_valid_entity) +{ + Entity entity = m_scene->create_empty_entity(); + ASSERT_TRUE((bool)entity); +} + +TEST_F(SceneTest, create_empty_entity_doesnt_add_tag_component) +{ + Entity entity = m_scene->create_empty_entity(); + ASSERT_FALSE(entity.has_component()); +} + +TEST_F(SceneTest, create_empty_entity_doesnt_add_transform_component) +{ + Entity entity = m_scene->create_empty_entity(); + ASSERT_FALSE(entity.has_component()); +} + +TEST_F(SceneTest, create_empty_entity_doesnt_add_entity_to_scene_map) +{ + Entity entity = m_scene->create_empty_entity(); + ASSERT_TRUE(m_scene->entity_map().empty()); +} + +TEST_F(SceneTest, create_entity_without_name_returns_valid_entity) +{ + Entity entity = m_scene->create_entity(); + ASSERT_TRUE((bool)entity); +} + +TEST_F(SceneTest, create_entity_with_empty_name_defaults_to_entity) +{ + Entity entity = m_scene->create_entity(); + ASSERT_EQ(entity.name(), "Entity"); +} + +TEST_F(SceneTest, create_entity_with_name_returns_valid_entity) +{ + Entity entity = m_scene->create_entity("TestEntity"); + ASSERT_TRUE((bool)entity); +} + +TEST_F(SceneTest, create_entity_with_adds_tag_component) +{ + Entity entity = m_scene->create_entity("Tagged"); + ASSERT_TRUE(entity.has_component()); +} + +TEST_F(SceneTest, create_entity_adds_transform_component) +{ + Entity entity = m_scene->create_entity("WithTransform"); + ASSERT_TRUE(entity.has_component()); +} + +TEST_F(SceneTest, create_entity_with_name_stores_correct_name) +{ + Entity entity = m_scene->create_entity("TestEntity"); + ASSERT_EQ(entity.name(), "TestEntity"); +} + +TEST_F(SceneTest, create_entity_with_explicit_uuid_sets_uuid) +{ + Core::UUID uuid; + Entity entity = m_scene->create_entity("Identified", uuid); + ASSERT_EQ(entity.id(), uuid); +} + +TEST_F(SceneTest, create_entity_adds_entity_to_scene_map) +{ + Core::UUID uuid; + m_scene->create_entity("Identified", uuid); + Entity found = m_scene->find_entity(uuid); + ASSERT_TRUE((bool)found); +} + +TEST_F(SceneTest, destroy_entity_removes_it_from_entity_map) +{ + Entity entity = m_scene->create_entity("ToDestroy"); + Core::UUID uuid = entity.id(); + m_scene->destrory_entity(entity); + Entity found = m_scene->find_entity(uuid); + ASSERT_FALSE((bool)found); +} + +TEST_F(SceneTest, destroy_entity_makes_it_invalid) +{ + Entity entity = m_scene->create_entity("ToDestroy"); + m_scene->destrory_entity(entity); + ASSERT_FALSE(entity.valid()); +} + +TEST_F(SceneTest, two_entities_have_different_uuids) +{ + Entity a = m_scene->create_entity("A"); + Entity b = m_scene->create_entity("B"); + ASSERT_NE(a.id(), b.id()); +} + +TEST_F(SceneTest, find_entity_by_uuid_returns_correct_entity) +{ + Entity entity = m_scene->create_entity("FindMe"); + Entity found = m_scene->find_entity(entity.id()); + ASSERT_EQ(entity, found); +} + +TEST_F(SceneTest, find_entity_by_name_returns_correct_entity) +{ + Entity entity = m_scene->create_entity("FindByName"); + Entity found = m_scene->find_entity("FindByName"); + ASSERT_EQ(entity, found); +} + +TEST_F(SceneTest, find_entity_by_invalid_uuid_returns_null_entity) +{ + Core::UUID nonexistent; + Entity found = m_scene->find_entity(nonexistent); + ASSERT_FALSE((bool)found); +} + +TEST_F(SceneTest, find_entity_by_nonexistent_name_returns_null_entity) +{ + Entity found = m_scene->find_entity("DoesNotExist"); + ASSERT_FALSE((bool)found); +} + +// TEST_F(SceneTest, set_parent_makes_child_report_correct_parent) +// { +// Entity parent = m_scene->create_entity("Parent"); +// Entity child = m_scene->create_entity("Child"); +// child.set_parent(parent); +// ASSERT_EQ(child.parent(), parent); +// } +// +// TEST_F(SceneTest, set_parent_adds_child_to_parents_children_list) +// { +// Entity parent = m_scene->create_entity("Parent"); +// Entity child = m_scene->create_entity("Child"); +// child.set_parent(parent); +// ASSERT_EQ(parent.children_count(), 1u); +// } +// +// TEST_F(SceneTest, child_is_child_of_parent) +// { +// Entity parent = m_scene->create_entity("Parent"); +// Entity child = m_scene->create_entity("Child"); +// child.set_parent(parent); +// ASSERT_TRUE(child.is_child_of(parent)); +// } +// +// TEST_F(SceneTest, unparent_removes_child_from_parent) +// { +// Entity parent = m_scene->create_entity("Parent"); +// Entity child = m_scene->create_entity("Child"); +// child.set_parent(parent); +// child.unparent(); +// ASSERT_EQ(parent.children_count(), 0u); +// } +// +// TEST_F(SceneTest, unparent_clears_childs_parent_reference) +// { +// Entity parent = m_scene->create_entity("Parent"); +// Entity child = m_scene->create_entity("Child"); +// child.set_parent(parent); +// child.unparent(); +// ASSERT_FALSE(child.has_parent()); +// } +// +// TEST_F(SceneTest, set_parent_does_not_create_cycle_when_parent_is_already_child) +// { +// Entity a = m_scene->create_entity("A"); +// Entity b = m_scene->create_entity("B"); +// a.set_parent(b); +// // Attempting to make b a child of a should be a no-op (cycle guard) +// b.set_parent(a); +// ASSERT_FALSE(b.is_child_of(a)); +// } +// +// TEST_F(SceneTest, reparent_moves_child_to_new_parent) +// { +// Entity parent1 = m_scene->create_entity("Parent1"); +// Entity parent2 = m_scene->create_entity("Parent2"); +// Entity child = m_scene->create_entity("Child"); +// child.set_parent(parent1); +// child.reparent(parent2); +// ASSERT_TRUE(child.is_child_of(parent2)); +// ASSERT_EQ(parent1.children_count(), 0u); +// } +// +TEST_F(SceneTest, destroy_parent_also_destroys_children) +{ + Entity parent = m_scene->create_entity("Parent"); + Entity child = m_scene->create_entity("Child"); + child.set_parent(parent); + Core::UUID child_uuid = child.id(); + m_scene->destrory_entity(parent); + Entity found_child = m_scene->find_entity(child_uuid); + ASSERT_FALSE((bool)found_child); +} + +TEST_F(SceneTest, duplicate_entity_creates_new_entity) +{ + Entity original = m_scene->create_entity("Original"); + Entity copy = m_scene->duplicate_entity(original); + ASSERT_NE(original, copy); +} + +TEST_F(SceneTest, duplicate_entity_copy_has_different_uuid) +{ + Entity original = m_scene->create_entity("Original"); + Entity copy = m_scene->duplicate_entity(original); + ASSERT_NE(original.id(), copy.id()); +} + +TEST_F(SceneTest, duplicate_entity_copy_name_is_original_name_with_copy_suffix) +{ + Entity original = m_scene->create_entity("MyEntity"); + Entity copy = m_scene->duplicate_entity(original); + ASSERT_EQ(copy.name(), "MyEntity Copy"); +} + +TEST_F(SceneTest, duplicate_entity_with_children_duplicates_children) +{ + Entity parent = m_scene->create_entity("Parent"); + Entity child = m_scene->create_entity("Child"); + child.set_parent(parent); + Entity copy = m_scene->duplicate_entity(parent); + ASSERT_EQ(copy.children_count(), 1u); +} + +TEST_F(SceneTest, scene_is_not_playing_by_default) +{ + ASSERT_FALSE(m_scene->playing()); +} + +TEST_F(SceneTest, start_runtime_sets_playing_to_true) +{ + m_scene->start_runtime(); + ASSERT_TRUE(m_scene->playing()); +} + +TEST_F(SceneTest, start_runtime_triggers_scene_start_simulation_event) +{ + m_scene->start_runtime(); + ASSERT_TRUE(m_is_scene_start_event_triggered); +} + +TEST_F(SceneTest, stop_runtime_sets_playing_to_false) +{ + m_scene->start_runtime(); + m_scene->stop_runtime(); + ASSERT_FALSE(m_scene->playing()); +} + +TEST_F(SceneTest, stop_runtime_triggers_scene_stop_simulation_event) +{ + m_scene->start_runtime(); + m_scene->stop_runtime(); + ASSERT_TRUE(m_is_scene_stop_event_triggered); +} + +TEST_F(SceneTest, calling_start_runtime_twice_keeps_playing_true) +{ + m_scene->start_runtime(); + m_scene->start_runtime(); + ASSERT_TRUE(m_scene->playing()); +} + +TEST_F(SceneTest, calling_stop_runtime_without_starting_keeps_playing_false) +{ + m_scene->stop_runtime(); + ASSERT_FALSE(m_scene->playing()); +} + +} diff --git a/Tests/LibScene/premake5.lua b/Tests/LibScene/premake5.lua new file mode 100644 index 00000000..4f07cc8b --- /dev/null +++ b/Tests/LibScene/premake5.lua @@ -0,0 +1,50 @@ +project "Test.LibScene" +language "C++" +cppdialect "C++20" +kind "ConsoleApp" +staticruntime "Off" + +files { + "**.cpp", + "**.hpp", +} + +externalincludedirs { + "%{wks.location}/Libraries", + "%{Dependencies.spdlog.include}", + "%{Dependencies.glm.include}", + "%{Dependencies.yaml.include}", + "%{Dependencies.entt.include}", + "../../vendor/gtest/googletest/include/", +} + +links { + "LibCore", + "LibAsset", + "LibScene", + "yaml-cpp", + "googletest", + "CoreFoundation.framework", -- no path needed for system frameworks + "Cocoa.framework", + "IOKit.framework", + "QuartzCore.framework", +} + +filter { "configurations:Debug" } +runtime "Debug" +symbols "On" +defines { "TERRAN_TESTING_DEBUG" } +defines "TR_DEBUG" +filter {} + +filter { "configurations:Release" } +runtime "Release" +defines { "TERRAN_TESTING_RELEASE" } + +filter { "system:macosx" } +architecture "ARM64" + +libdirs { "/usr/local/lib" } +links { + "CoreFoundation.framework", -- no path needed for system frameworks +} diff --git a/premake-native.lua b/premake-native.lua index 5cb2d8ff..1830950e 100644 --- a/premake-native.lua +++ b/premake-native.lua @@ -51,3 +51,4 @@ include "Sandbox" group "Tests" include "Tests/LibCore" +include "Tests/LibScene" From 9725c10c0b6aefb1779fc1c2f5bf8a74963e6e86 Mon Sep 17 00:00:00 2001 From: infirit89 Date: Thu, 19 Feb 2026 16:53:07 +0200 Subject: [PATCH 38/51] build(Sonarcloud): fix test coverage gathering --- .github/workflows/sonarcloud.yml | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 6b5cef21..3c7a1847 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -55,14 +55,32 @@ jobs: run: | /opt/homebrew/opt/llvm/bin/llvm-profdata merge -sparse coverage/*.profraw -o coverage/coverage.profdata - SOURCE_FILES=$(find Libraries Sandbox -type f \( -name "*.cpp" -o -name "*.h" -o -name "*.hpp" \) | tr '\n' ' ') - TEST_BINS=$(find Tests -type f -name "Test.*" -perm +111 | tr '\n' ' ') + # Collect all test binaries into an array + TEST_BINS=() + while IFS= read -r bin; do + TEST_BINS+=("$bin") + done < <(find Tests -type f -name "Test.*" -perm +111) - /opt/homebrew/opt/llvm/bin/llvm-cov show $TEST_BINS \ - --show-branches=count \ - -instr-profile=coverage/coverage.profdata \ - --ignore-filename-regex='.*(/usr/|/opt/|/Library/|\.h$)' \ - > coverage/coverage.txt + if [ ${#TEST_BINS[@]} -eq 0 ]; then + echo "No test binaries found!" + exit 1 + fi + + # Build -object flags for every binary after the first + OBJECT_FLAGS=() + for bin in "${TEST_BINS[@]:1}"; do + OBJECT_FLAGS+=(-object "$bin") + done + + /opt/homebrew/opt/llvm/bin/llvm-cov show "${TEST_BINS[0]}" \ + "${OBJECT_FLAGS[@]}" \ + --show-branches=count \ + -instr-profile=coverage/coverage.profdata \ + --ignore-filename-regex='.*(/usr/|/opt/|/Library/)' \ + > coverage/coverage.txt + + # Sanity check — print coverage size so you can see it in logs + wc -l coverage/coverage.txt # ---- MANAGED BUILD ---- - name: Generate managed solution From 56ce3bb919b6d4eeebf204dbac40a291b3937e5b Mon Sep 17 00:00:00 2001 From: infirit89 Date: Thu, 19 Feb 2026 17:07:20 +0200 Subject: [PATCH 39/51] test(Scene): add unit test for destroying child entity --- Libraries/LibScene/Scene.cpp | 4 ++-- Tests/LibScene/SceneTests.cpp | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Libraries/LibScene/Scene.cpp b/Libraries/LibScene/Scene.cpp index bdd005ee..73ed4b88 100644 --- a/Libraries/LibScene/Scene.cpp +++ b/Libraries/LibScene/Scene.cpp @@ -106,8 +106,8 @@ void Scene::destrory_entity(Entity entity, bool first) destrory_entity(find_entity(child_id), false); } - if (auto entity_iterator = m_entity_map.find(entity.id()); entity_iterator != m_entity_map.end()) - m_entity_map.erase(entity_iterator); + if (m_entity_map.contains(entity.id())) + m_entity_map.erase(entity.id()); m_registry.destroy((entt::entity)entity); diff --git a/Tests/LibScene/SceneTests.cpp b/Tests/LibScene/SceneTests.cpp index e7c90919..80f23eb4 100644 --- a/Tests/LibScene/SceneTests.cpp +++ b/Tests/LibScene/SceneTests.cpp @@ -236,6 +236,15 @@ TEST_F(SceneTest, destroy_parent_also_destroys_children) ASSERT_FALSE((bool)found_child); } +TEST_F(SceneTest, destroy_child_removes_itself_from_parent_children) +{ + Entity parent = m_scene->create_entity("Parent"); + Entity child = m_scene->create_entity("Child"); + child.set_parent(parent); + m_scene->destrory_entity(child); + ASSERT_TRUE(parent.children().empty()); +} + TEST_F(SceneTest, duplicate_entity_creates_new_entity) { Entity original = m_scene->create_entity("Original"); From 9ebeb10ba0bf8ff25ad042459d986b6cc5428eac Mon Sep 17 00:00:00 2001 From: infirit89 Date: Thu, 19 Feb 2026 17:18:02 +0200 Subject: [PATCH 40/51] test(Scene): add entity unit tests --- Tests/LibScene/EntityTests.cpp | 111 +++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 Tests/LibScene/EntityTests.cpp diff --git a/Tests/LibScene/EntityTests.cpp b/Tests/LibScene/EntityTests.cpp new file mode 100644 index 00000000..efd98ce6 --- /dev/null +++ b/Tests/LibScene/EntityTests.cpp @@ -0,0 +1,111 @@ +#include +#include + +#include +#include + +#include +namespace Terran::World::Tests { + +class EntityTest : public testing::Test { + +protected: + EntityTest() + : m_scene(Core::RefPtr::create(m_event_dispatcher)) + { + } + + virtual ~EntityTest() override = default; + + Core::EventDispatcher m_event_dispatcher; + Core::RefPtr m_scene; +}; + +TEST_F(EntityTest, set_parent_makes_child_report_correct_parent) +{ + Entity parent = m_scene->create_entity("Parent"); + Entity child = m_scene->create_entity("Child"); + child.set_parent(parent); + ASSERT_EQ(child.parent(), parent); +} + +TEST_F(EntityTest, has_parent_returns_false_when_entity_doesnt_have_relationship_component) +{ + Entity entity = m_scene->create_entity("Parent"); + ASSERT_FALSE(entity.has_parent()); +} + +TEST_F(EntityTest, entity_reports_correct_scene_id) +{ + Entity entity = m_scene->create_entity("Parent"); + ASSERT_EQ(entity.scene_id(), m_scene->handle()); +} + +TEST_F(EntityTest, set_parent_adds_child_to_parents_children_list) +{ + Entity parent = m_scene->create_entity("Parent"); + Entity child = m_scene->create_entity("Child"); + child.set_parent(parent); + ASSERT_EQ(parent.children_count(), 1u); +} + +TEST_F(EntityTest, child_at_returns_invalid_when_entity_doesnt_have_relationship_component) +{ + Entity entity = m_scene->create_entity("Parent"); + ASSERT_FALSE((bool)entity.child_at(0)); +} + +TEST_F(EntityTest, parent_getter_returns_invalid_when_entity_doesnt_have_relationship_component) +{ + Entity entity = m_scene->create_entity("Parent"); + ASSERT_FALSE((bool)entity.parent()); +} + +TEST_F(EntityTest, child_is_child_of_parent) +{ + Entity parent = m_scene->create_entity("Parent"); + Entity child = m_scene->create_entity("Child"); + child.set_parent(parent); + ASSERT_TRUE(child.is_child_of(parent)); +} + +TEST_F(EntityTest, unparent_removes_child_from_parent) +{ + Entity parent = m_scene->create_entity("Parent"); + Entity child = m_scene->create_entity("Child"); + child.set_parent(parent); + child.unparent(); + ASSERT_EQ(parent.children_count(), 0u); +} + +TEST_F(EntityTest, unparent_clears_childs_parent_reference) +{ + Entity parent = m_scene->create_entity("Parent"); + Entity child = m_scene->create_entity("Child"); + child.set_parent(parent); + child.unparent(); + ASSERT_FALSE(child.has_parent()); +} + +TEST_F(EntityTest, set_parent_does_not_create_cycle_when_parent_is_already_child) +{ + Entity a = m_scene->create_entity("A"); + Entity b = m_scene->create_entity("B"); + a.set_parent(b); + // Attempting to make b a child of a should be a no-op (cycle guard) + b.set_parent(a); + ASSERT_FALSE(b.is_child_of(a)); +} + +TEST_F(EntityTest, reparent_moves_child_to_new_parent) +{ + Entity parent1 = m_scene->create_entity("Parent1"); + Entity parent2 = m_scene->create_entity("Parent2"); + Entity child = m_scene->create_entity("Child"); + child.set_parent(parent1); + child.reparent(parent2); + ASSERT_TRUE(child.is_child_of(parent2)); + ASSERT_EQ(parent1.children_count(), 0u); +} + +} From b96c0b1ee23d4b332a4fe71db5066766f9ddda2b Mon Sep 17 00:00:00 2001 From: infirit89 Date: Thu, 19 Feb 2026 18:56:56 +0200 Subject: [PATCH 41/51] test(Scene): add more unit tests for the Entity and Scene classes --- Libraries/LibScene/Components.h | 6 +-- Libraries/LibScene/Entity.cpp | 46 +++++++++++------ Libraries/LibScene/Entity.h | 34 +++++++----- Libraries/LibScene/Entity.inl | 2 +- Libraries/LibScene/Scene.cpp | 4 +- Tests/LibScene/EntityTests.cpp | 91 +++++++++++++++++++++++++++++++-- Tests/LibScene/SceneTests.cpp | 74 ++++----------------------- 7 files changed, 156 insertions(+), 101 deletions(-) diff --git a/Libraries/LibScene/Components.h b/Libraries/LibScene/Components.h index 78cfac45..a9fb5d4c 100644 --- a/Libraries/LibScene/Components.h +++ b/Libraries/LibScene/Components.h @@ -15,9 +15,9 @@ namespace Terran::World { struct TagComponent { TagComponent() = default; - TagComponent(std::string const& name, Terran::Core::UUID const& id) + TagComponent(std::string const& name, Core::UUID const& id) : Name(name) - , ID(id) + , Id(id) { } @@ -27,7 +27,7 @@ struct TagComponent { } std::string Name; - Terran::Core::UUID ID; + Core::UUID Id; }; struct TransformComponent { diff --git a/Libraries/LibScene/Entity.cpp b/Libraries/LibScene/Entity.cpp index 4d15dff1..61590042 100644 --- a/Libraries/LibScene/Entity.cpp +++ b/Libraries/LibScene/Entity.cpp @@ -2,8 +2,10 @@ #include "Components.h" #include "Scene.h" +#include #include +#include #include namespace Terran::World { @@ -39,7 +41,8 @@ Entity Entity::parent() const return m_scene->find_entity(parent_id()); } -void Entity::set_parent(Entity parent) + +Core::Result Entity::set_parent(Entity parent) { if (!has_component()) add_component(); @@ -48,38 +51,51 @@ void Entity::set_parent(Entity parent) parent.add_component(); if (is_child_of(parent)) - return; + return { EntityErrors::AlreadyAChildOfThisParent }; if (parent.is_child_of(*this)) - return; + return { EntityErrors::TargetParentIsAlreadyAChildOfThisEntity }; if (has_parent()) unparent(); auto& relationship_component = get_component(); relationship_component.Parent = parent.id(); - parent.children().emplace_back(id()); + + auto& parent_relationship_component = parent.get_component(); + parent_relationship_component.Children.emplace_back(id()); m_scene->convert_to_local_space(*this); + return {}; } -void Entity::unparent() +Core::Result Entity::unparent() { - if (!has_component()) - return; - - Core::UUID parentID = get_component().Parent; - Entity parent = m_scene->find_entity(parentID); + RelationshipComponent* relationship_component = try_get_component(); + if (!relationship_component) { + return { EntityErrors::DoesntHaveRelationshipComponent }; + } + Core::UUID parent_id = relationship_component->Parent; + Entity parent = m_scene->find_entity(parent_id); if (!parent) - return; + return { EntityErrors::ParentNotFound }; - m_scene->convert_to_world_space(*this); + RelationshipComponent* parent_relationship_component = parent.try_get_component(); + + if (!parent_relationship_component) { + return { EntityErrors::DoesntHaveRelationshipComponent }; + } - if (auto const& iterator = std::ranges::find(parent.children(), id()); - iterator != parent.children().end()) { - parent.children().erase(iterator); + auto& children = parent_relationship_component->Children; + auto const& iterator = std::ranges::find(children, id()); + if (iterator == parent.children().end()) { + return { EntityErrors::ParentDoesntContainChild }; } + children.erase(iterator); set_parent_id(Core::UUID({ 0 })); + + m_scene->convert_to_world_space(*this); + return {}; } } diff --git a/Libraries/LibScene/Entity.h b/Libraries/LibScene/Entity.h index 2b4ba582..a5ac94c1 100644 --- a/Libraries/LibScene/Entity.h +++ b/Libraries/LibScene/Entity.h @@ -3,7 +3,9 @@ #include "Components.h" #include +#include #include + #include #pragma warning(push) @@ -11,6 +13,7 @@ #include #include +#include #include #include @@ -18,6 +21,14 @@ namespace Terran::World { class Scene; +enum class EntityErrors : uint8_t { + AlreadyAChildOfThisParent, + DoesntHaveRelationshipComponent, + TargetParentIsAlreadyAChildOfThisEntity, + ParentNotFound, + ParentDoesntContainChild +}; + class Entity final { public: Entity() = default; @@ -43,7 +54,7 @@ class Entity final { void remove_component(); template - Component& try_get_component() const; + auto try_get_component() const; template bool has_component() const; @@ -54,9 +65,11 @@ class Entity final { void visit(Entity entity, Func func) const; // base stuffs - Terran::Core::UUID const& id() const + Core::UUID const& id() const { - return get_component().ID; + return has_component() + ? get_component().Id + : Core::UUID::invalid(); } TransformComponent& transform() const @@ -90,14 +103,11 @@ class Entity final { bool operator==(Entity const& other) const = default; // relationship component stuffs - std::vector& children() const - { - return get_component().Children; - } - - size_t children_count() const + std::span children() const { - return has_component() ? get_component().Children.size() : 0; + return has_component() + ? get_component().Children + : std::span(); } Terran::Core::UUID parent_id() const @@ -133,9 +143,9 @@ class Entity final { return parent_id() == entity.id(); } - void set_parent(Entity parent); + Core::Result set_parent(Entity parent); - void unparent(); + Core::Result unparent(); void remove_child(Entity child) const { diff --git a/Libraries/LibScene/Entity.inl b/Libraries/LibScene/Entity.inl index a4d09878..4312b3a5 100644 --- a/Libraries/LibScene/Entity.inl +++ b/Libraries/LibScene/Entity.inl @@ -40,7 +40,7 @@ inline void Entity::remove_component() } template -inline Component& Entity::try_get_component() const +inline auto Entity::try_get_component() const { TR_ASSERT(m_handle != entt::null, "Ivalid entity"); diff --git a/Libraries/LibScene/Scene.cpp b/Libraries/LibScene/Scene.cpp index 73ed4b88..2fd24bf0 100644 --- a/Libraries/LibScene/Scene.cpp +++ b/Libraries/LibScene/Scene.cpp @@ -172,7 +172,7 @@ Entity Scene::duplicate_entity(Entity source_entity, Entity parent) m_registry); if (source_entity.has_component()) { - for (int i = 0; i < source_entity.children_count(); i++) { + for (int i = 0; i < source_entity.children().size(); i++) { Entity childEntity = source_entity.child_at(i); duplicate_entity(childEntity, destination_entity); } @@ -220,7 +220,7 @@ void Scene::update_entity_transform(Entity entity) transform_component.Right = glm::normalize(glm::rotate(rotation, glm::vec3(1.0f, 0.0f, 0.0f))); } - for (size_t i = 0; i < entity.children_count(); i++) { + for (size_t i = 0; i < entity.children().size(); i++) { Entity currEntity = entity.child_at(static_cast(i)); if (transform_component.IsDirty) diff --git a/Tests/LibScene/EntityTests.cpp b/Tests/LibScene/EntityTests.cpp index efd98ce6..04114a99 100644 --- a/Tests/LibScene/EntityTests.cpp +++ b/Tests/LibScene/EntityTests.cpp @@ -1,8 +1,10 @@ #include #include +#include #include #include +#include #include namespace Terran::World::Tests { @@ -21,6 +23,18 @@ class EntityTest : public testing::Test { Core::RefPtr m_scene; }; +TEST_F(EntityTest, id_getter_returns_invalid_if_entity_doesnt_have_tag_component) +{ + Entity entity = m_scene->create_empty_entity(); + ASSERT_FALSE((bool)entity.id()); +} + +TEST_F(EntityTest, id_getter_returns_valid_if_entity_has_tag_component) +{ + Entity entity = m_scene->create_entity(); + ASSERT_TRUE((bool)entity.id()); +} + TEST_F(EntityTest, set_parent_makes_child_report_correct_parent) { Entity parent = m_scene->create_entity("Parent"); @@ -46,7 +60,7 @@ TEST_F(EntityTest, set_parent_adds_child_to_parents_children_list) Entity parent = m_scene->create_entity("Parent"); Entity child = m_scene->create_entity("Child"); child.set_parent(parent); - ASSERT_EQ(parent.children_count(), 1u); + ASSERT_EQ(parent.children().size(), 1u); } TEST_F(EntityTest, child_at_returns_invalid_when_entity_doesnt_have_relationship_component) @@ -75,7 +89,7 @@ TEST_F(EntityTest, unparent_removes_child_from_parent) Entity child = m_scene->create_entity("Child"); child.set_parent(parent); child.unparent(); - ASSERT_EQ(parent.children_count(), 0u); + ASSERT_EQ(parent.children().size(), 0u); } TEST_F(EntityTest, unparent_clears_childs_parent_reference) @@ -87,14 +101,83 @@ TEST_F(EntityTest, unparent_clears_childs_parent_reference) ASSERT_FALSE(child.has_parent()); } +TEST_F(EntityTest, unparent_returns_error_if_entity_doesnt_have_relationship_component) +{ + Entity child = m_scene->create_entity("Child"); + auto unparentRes = child.unparent(); + ASSERT_FALSE(unparentRes.is_ok()); + ASSERT_EQ(unparentRes.error(), EntityErrors::DoesntHaveRelationshipComponent); +} + +TEST_F(EntityTest, unparent_returns_error_if_entity_parent_isnt_found) +{ + Entity child = m_scene->create_entity("Child"); + auto& relationshipComponent = child.add_component(); + relationshipComponent.Parent = Core::UUID(); + auto unparentRes = child.unparent(); + ASSERT_FALSE(unparentRes.is_ok()); + ASSERT_EQ(unparentRes.error(), EntityErrors::ParentNotFound); +} + +// NOTE: This will happen in a very wierd case, +// you'd have to mess with the Relationship Component directly, +// which is not really recommended, +// still though lets check if the logic to deal with it is correct + +TEST_F(EntityTest, unparent_returns_error_if_parent_doesnt_contain_child) +{ + Entity parent = m_scene->create_entity("Parent"); + parent.add_component(); + Entity child = m_scene->create_entity("Child"); + auto& relationshipComponent = child.add_component(); + relationshipComponent.Parent = parent.id(); + auto unparentRes = child.unparent(); + ASSERT_FALSE(unparentRes.is_ok()); + ASSERT_EQ(unparentRes.error(), EntityErrors::ParentDoesntContainChild); +} + +TEST_F(EntityTest, unparent_returns_error_if_parent_doesnt_have_relationship_component) +{ + Entity parent = m_scene->create_entity("Parent"); + Entity child = m_scene->create_entity("Child"); + auto& relationshipComponent = child.add_component(); + relationshipComponent.Parent = parent.id(); + auto unparentRes = child.unparent(); + ASSERT_FALSE(unparentRes.is_ok()); + ASSERT_EQ(unparentRes.error(), EntityErrors::DoesntHaveRelationshipComponent); +} + TEST_F(EntityTest, set_parent_does_not_create_cycle_when_parent_is_already_child) { Entity a = m_scene->create_entity("A"); Entity b = m_scene->create_entity("B"); a.set_parent(b); // Attempting to make b a child of a should be a no-op (cycle guard) - b.set_parent(a); + auto parentRes = b.set_parent(a); ASSERT_FALSE(b.is_child_of(a)); + ASSERT_FALSE(parentRes.is_ok()); + ASSERT_EQ(parentRes.error(), EntityErrors::TargetParentIsAlreadyAChildOfThisEntity); +} + +TEST_F(EntityTest, set_parent_returns_error_if_entity_is_already_a_child_of_this_parent) +{ + Entity a = m_scene->create_entity("A"); + Entity b = m_scene->create_entity("B"); + a.set_parent(b); + auto parentRes = a.set_parent(b); + ASSERT_FALSE(parentRes.is_ok()); + ASSERT_EQ(parentRes.error(), EntityErrors::AlreadyAChildOfThisParent); +} + +TEST_F(EntityTest, set_parent_unparents_if_entity_alread_has_another_parent) +{ + Entity a = m_scene->create_entity("A"); + Entity b = m_scene->create_entity("B"); + Entity c = m_scene->create_entity("C"); + a.set_parent(b); + a.set_parent(c); + ASSERT_TRUE(b.children().empty()); + ASSERT_TRUE(a.is_child_of(c)); } TEST_F(EntityTest, reparent_moves_child_to_new_parent) @@ -105,7 +188,7 @@ TEST_F(EntityTest, reparent_moves_child_to_new_parent) child.set_parent(parent1); child.reparent(parent2); ASSERT_TRUE(child.is_child_of(parent2)); - ASSERT_EQ(parent1.children_count(), 0u); + ASSERT_EQ(parent1.children().size(), 0u); } } diff --git a/Tests/LibScene/SceneTests.cpp b/Tests/LibScene/SceneTests.cpp index 80f23eb4..78ca5668 100644 --- a/Tests/LibScene/SceneTests.cpp +++ b/Tests/LibScene/SceneTests.cpp @@ -128,6 +128,15 @@ TEST_F(SceneTest, destroy_entity_makes_it_invalid) ASSERT_FALSE(entity.valid()); } +TEST_F(SceneTest, destroy_entity_doesnt_erase_from_entity_map_if_entity_wasnt_registered_in_it) +{ + Entity entity = m_scene->create_entity("ToDestroy"); + Entity empty_entity = m_scene->create_empty_entity(); + ASSERT_FALSE(m_scene->entity_map().empty()); + m_scene->destrory_entity(empty_entity); + ASSERT_FALSE(m_scene->entity_map().empty()); +} + TEST_F(SceneTest, two_entities_have_different_uuids) { Entity a = m_scene->create_entity("A"); @@ -162,69 +171,6 @@ TEST_F(SceneTest, find_entity_by_nonexistent_name_returns_null_entity) ASSERT_FALSE((bool)found); } -// TEST_F(SceneTest, set_parent_makes_child_report_correct_parent) -// { -// Entity parent = m_scene->create_entity("Parent"); -// Entity child = m_scene->create_entity("Child"); -// child.set_parent(parent); -// ASSERT_EQ(child.parent(), parent); -// } -// -// TEST_F(SceneTest, set_parent_adds_child_to_parents_children_list) -// { -// Entity parent = m_scene->create_entity("Parent"); -// Entity child = m_scene->create_entity("Child"); -// child.set_parent(parent); -// ASSERT_EQ(parent.children_count(), 1u); -// } -// -// TEST_F(SceneTest, child_is_child_of_parent) -// { -// Entity parent = m_scene->create_entity("Parent"); -// Entity child = m_scene->create_entity("Child"); -// child.set_parent(parent); -// ASSERT_TRUE(child.is_child_of(parent)); -// } -// -// TEST_F(SceneTest, unparent_removes_child_from_parent) -// { -// Entity parent = m_scene->create_entity("Parent"); -// Entity child = m_scene->create_entity("Child"); -// child.set_parent(parent); -// child.unparent(); -// ASSERT_EQ(parent.children_count(), 0u); -// } -// -// TEST_F(SceneTest, unparent_clears_childs_parent_reference) -// { -// Entity parent = m_scene->create_entity("Parent"); -// Entity child = m_scene->create_entity("Child"); -// child.set_parent(parent); -// child.unparent(); -// ASSERT_FALSE(child.has_parent()); -// } -// -// TEST_F(SceneTest, set_parent_does_not_create_cycle_when_parent_is_already_child) -// { -// Entity a = m_scene->create_entity("A"); -// Entity b = m_scene->create_entity("B"); -// a.set_parent(b); -// // Attempting to make b a child of a should be a no-op (cycle guard) -// b.set_parent(a); -// ASSERT_FALSE(b.is_child_of(a)); -// } -// -// TEST_F(SceneTest, reparent_moves_child_to_new_parent) -// { -// Entity parent1 = m_scene->create_entity("Parent1"); -// Entity parent2 = m_scene->create_entity("Parent2"); -// Entity child = m_scene->create_entity("Child"); -// child.set_parent(parent1); -// child.reparent(parent2); -// ASSERT_TRUE(child.is_child_of(parent2)); -// ASSERT_EQ(parent1.children_count(), 0u); -// } -// TEST_F(SceneTest, destroy_parent_also_destroys_children) { Entity parent = m_scene->create_entity("Parent"); @@ -272,7 +218,7 @@ TEST_F(SceneTest, duplicate_entity_with_children_duplicates_children) Entity child = m_scene->create_entity("Child"); child.set_parent(parent); Entity copy = m_scene->duplicate_entity(parent); - ASSERT_EQ(copy.children_count(), 1u); + ASSERT_EQ(copy.children().size(), 1u); } TEST_F(SceneTest, scene_is_not_playing_by_default) From cb11131c49c80cde42201f5cf1cd5c73178cfde3 Mon Sep 17 00:00:00 2001 From: infirit89 Date: Fri, 20 Feb 2026 12:24:09 +0200 Subject: [PATCH 42/51] chore(Core): fix ub in Result type --- Libraries/LibCore/Result.h | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/Libraries/LibCore/Result.h b/Libraries/LibCore/Result.h index 45959080..d977fe7a 100644 --- a/Libraries/LibCore/Result.h +++ b/Libraries/LibCore/Result.h @@ -58,9 +58,11 @@ class Result { template constexpr Result(U const& data) noexcept requires(std::is_convertible_v) - : m_storage(data) - , m_isError(false) + : m_isError(false) { + // NOTE: use placement new to make "data" the "active" + // union member to prevent UB + new (&m_storage.data) TValue(data); } /** @@ -73,10 +75,18 @@ class Result { requires(std::is_convertible_v) : m_isError(true) { - m_storage.error = error; + // NOTE: use placement new to make "error" the "active" + // union memver to prevent UB + new (&m_storage.error) TError(error); } - constexpr ~Result() noexcept = default; + constexpr ~Result() noexcept { + // NOTE: manually call destructors to prevent UB + if(m_isError) + m_storage.error.~TError(); + else + m_storage.data.~TValue(); + } [[nodiscard]] constexpr bool is_ok() const { From dbad457c357d15aabdb25c43a6fbf886f6d702d3 Mon Sep 17 00:00:00 2001 From: infirit89 Date: Fri, 20 Feb 2026 12:25:02 +0200 Subject: [PATCH 43/51] chore(Scene): fix sonarcloud error --- Libraries/LibScene/Entity.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Libraries/LibScene/Entity.cpp b/Libraries/LibScene/Entity.cpp index 61590042..f1351925 100644 --- a/Libraries/LibScene/Entity.cpp +++ b/Libraries/LibScene/Entity.cpp @@ -69,10 +69,12 @@ Core::Result Entity::set_parent(Entity parent) } Core::Result Entity::unparent() { - RelationshipComponent* relationship_component = try_get_component(); + RelationshipComponent const* relationship_component = try_get_component(); + if (!relationship_component) { return { EntityErrors::DoesntHaveRelationshipComponent }; } + Core::UUID parent_id = relationship_component->Parent; Entity parent = m_scene->find_entity(parent_id); From 6bb1ea191987d316d523b733d5c44653e023c902 Mon Sep 17 00:00:00 2001 From: infirit89 Date: Fri, 20 Feb 2026 12:26:09 +0200 Subject: [PATCH 44/51] test(Asset): add AssetImporterRegistry unit tests --- Libraries/LibAsset/AssetImporterRegistry.cpp | 8 +- Libraries/LibAsset/AssetImporterRegistry.h | 6 +- Tests/LibAsset/AssetImporterRegistryTests.cpp | 122 ++++++++++++++++++ Tests/LibAsset/Main.cpp | 6 + Tests/LibAsset/premake5.lua | 48 +++++++ premake-native.lua | 1 + 6 files changed, 189 insertions(+), 2 deletions(-) create mode 100644 Tests/LibAsset/AssetImporterRegistryTests.cpp create mode 100644 Tests/LibAsset/Main.cpp create mode 100644 Tests/LibAsset/premake5.lua diff --git a/Libraries/LibAsset/AssetImporterRegistry.cpp b/Libraries/LibAsset/AssetImporterRegistry.cpp index 63214664..e53916cc 100644 --- a/Libraries/LibAsset/AssetImporterRegistry.cpp +++ b/Libraries/LibAsset/AssetImporterRegistry.cpp @@ -1,13 +1,15 @@ #include "AssetImporterRegistry.h" #include "Asset.h" #include "AssetImporter.h" +#include "AssetImporterError.h" #include "AssetMetadata.h" #include "AssetTypes.h" -#include "AssetImporterError.h" #include #include +#include +#include #include namespace Terran::Asset { @@ -27,6 +29,10 @@ AssetLoadResult AssetImporterRegistry::load(AssetMetadata const& assetMetadata) bool AssetImporterRegistry::save(AssetMetadata const& assetMetadata, Core::RefPtr const& asset) { + if(assetMetadata.Type != asset->type()) { + return false; + } + AssetTypeId const type_id = assetMetadata.Type; if (s_loaders.contains(type_id)) return s_loaders[type_id]->save(assetMetadata, asset); diff --git a/Libraries/LibAsset/AssetImporterRegistry.h b/Libraries/LibAsset/AssetImporterRegistry.h index 541bb855..96df64a1 100644 --- a/Libraries/LibAsset/AssetImporterRegistry.h +++ b/Libraries/LibAsset/AssetImporterRegistry.h @@ -20,7 +20,7 @@ class AssetImporterRegistry final { requires( std::is_base_of_v, HasStaticType) - static void register_asset(Terran::Core::Shared loader) + static void register_asset(Terran::Core::Shared const& loader) { s_loaders[TAsset::static_type()] = loader; } @@ -48,6 +48,10 @@ class AssetImporterRegistry final { return nullptr; } + static void clear() noexcept { + s_loaders.clear(); + } + private: static std::unordered_map> s_loaders; }; diff --git a/Tests/LibAsset/AssetImporterRegistryTests.cpp b/Tests/LibAsset/AssetImporterRegistryTests.cpp new file mode 100644 index 00000000..b975803f --- /dev/null +++ b/Tests/LibAsset/AssetImporterRegistryTests.cpp @@ -0,0 +1,122 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include + +namespace Terran::Asset::Tests { + +class MockAsset final : public Asset { +public: + MockAsset(AssetHandle const& handle) + : Asset(handle) + { + } + TR_DECLARE_ASSET_TYPE(MockAsset); +}; + +class MockAsset2 final : public Asset { +public: + MockAsset2(AssetHandle const& handle) + : Asset(handle) + { + } + TR_DECLARE_ASSET_TYPE(MockAsset2); +}; + +class MockAssetImporter final : public AssetImporter { +public: + virtual ~MockAssetImporter() override = default; + [[nodiscard]] virtual AssetLoadResult load(AssetMetadata const& assetMetadata) override + { + Core::RefPtr asset = Core::RefPtr::create(assetMetadata.Handle); + return { asset }; + } + virtual bool save(AssetMetadata const&, Core::RefPtr const&) override + { + return true; + } + [[nodiscard]] virtual bool can_handle(std::filesystem::path const&) override + { + return true; + } + [[nodiscard]] virtual AssetTypeId asset_type() override + { + return MockAsset::static_type(); + } +}; +class AssetImporterRegistryTest : public testing::Test { +protected: + AssetImporterRegistryTest() + { + m_asset_metadata.Handle = Core::UUID(); + m_asset_metadata.Path = ""; + m_asset_metadata.Type = MockAsset::static_type(); + + m_asset_importer = Core::CreateShared(); + AssetImporterRegistry::register_asset(m_asset_importer); + } + virtual ~AssetImporterRegistryTest() override + { + AssetImporterRegistry::clear(); + } + + AssetMetadata m_asset_metadata; + Core::Shared m_asset_importer; +}; + +TEST_F(AssetImporterRegistryTest, load_calls_load_for_a_given_asset_importer_when_it_is_registered) +{ + auto assetRes = AssetImporterRegistry::load(m_asset_metadata); + ASSERT_TRUE(assetRes.is_ok()); + ASSERT_TRUE(assetRes.value()->is_valid()); + ASSERT_EQ(assetRes.value()->handle(), m_asset_metadata.Handle); +} + +TEST_F(AssetImporterRegistryTest, load_returns_error_when_asset_importer_of_a_given_type_is_not_registered) +{ + AssetMetadata metadata; + metadata.Type = MockAsset2::static_type(); + auto assetRes = AssetImporterRegistry::load(metadata); + ASSERT_FALSE(assetRes.is_ok()); + Core::Shared importerError = Core::CreateShared(AssetImporterError::Code::ImporterNotFound); + ASSERT_EQ(assetRes.error()->message(), importerError->message()); +} + +TEST_F(AssetImporterRegistryTest, save_calls_save_for_a_given_asset_importer_when_it_is_registered) +{ + Core::RefPtr mockAsset = Core::RefPtr::create(m_asset_metadata.Handle); + bool result = AssetImporterRegistry::save(m_asset_metadata, mockAsset); + ASSERT_TRUE(result); +} + + +TEST_F(AssetImporterRegistryTest, save_returns_false_when_asset_importer_of_a_given_type_is_not_registered) +{ + Core::RefPtr mockAsset = Core::RefPtr::create(Core::UUID()); + AssetMetadata metadata; + metadata.Type = mockAsset->type(); + bool result = AssetImporterRegistry::save(metadata, mockAsset); + ASSERT_FALSE(result); +} + +TEST_F(AssetImporterRegistryTest, save_returns_false_when_assetmetadata_type_and_asset_type_dont_match) +{ + Core::RefPtr mockAsset = Core::RefPtr::create(Core::UUID()); + AssetMetadata metadata; + metadata.Type = MockAsset::static_type(); + bool result = AssetImporterRegistry::save(metadata, mockAsset); + ASSERT_FALSE(result); +} + +} diff --git a/Tests/LibAsset/Main.cpp b/Tests/LibAsset/Main.cpp new file mode 100644 index 00000000..64becff4 --- /dev/null +++ b/Tests/LibAsset/Main.cpp @@ -0,0 +1,6 @@ +#include + +int main(int argc, char **argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/Tests/LibAsset/premake5.lua b/Tests/LibAsset/premake5.lua new file mode 100644 index 00000000..dcddd247 --- /dev/null +++ b/Tests/LibAsset/premake5.lua @@ -0,0 +1,48 @@ +project "Test.LibAsset" +language "C++" +cppdialect "C++20" +kind "ConsoleApp" +staticruntime "Off" + +files { + "**.cpp", + "**.hpp", +} + +externalincludedirs { + "%{wks.location}/Libraries", + "%{Dependencies.spdlog.include}", + "%{Dependencies.glm.include}", + "%{Dependencies.yaml.include}", + "../../vendor/gtest/googletest/include/", +} + +links { + "LibCore", + "LibAsset", + "yaml-cpp", + "googletest", + "CoreFoundation.framework", -- no path needed for system frameworks + "Cocoa.framework", + "IOKit.framework", + "QuartzCore.framework", +} + +filter { "configurations:Debug" } +runtime "Debug" +symbols "On" +defines { "TERRAN_TESTING_DEBUG" } +defines "TR_DEBUG" +filter {} + +filter { "configurations:Release" } +runtime "Release" +defines { "TERRAN_TESTING_RELEASE" } + +filter { "system:macosx" } +architecture "ARM64" + +libdirs { "/usr/local/lib" } +links { + "CoreFoundation.framework", -- no path needed for system frameworks +} diff --git a/premake-native.lua b/premake-native.lua index 1830950e..02a78293 100644 --- a/premake-native.lua +++ b/premake-native.lua @@ -52,3 +52,4 @@ include "Sandbox" group "Tests" include "Tests/LibCore" include "Tests/LibScene" +include "Tests/LibAsset" From 02be68093a45c8c6d0ad30fffa5cfa44687b49f6 Mon Sep 17 00:00:00 2001 From: infirit89 Date: Mon, 23 Feb 2026 17:46:18 +0200 Subject: [PATCH 45/51] chore(Core): change base logging macro names Before the macros names were imported from the Terran Engine mono library and included the CORE keyword in their name (e.g. TR_CORE_TRACE) this no longer makes sense as these would be called from other libraries --- Libraries/LibAsset/AssetImporterRegistry.cpp | 4 +-- Libraries/LibAsset/AssetManager.cpp | 28 +++++++++++++------- Libraries/LibAsset/AssetManager.h | 4 +-- Libraries/LibAsset/AssetMetadataRegistry.cpp | 8 ++++-- Libraries/LibAsset/AssetTypes.h | 2 +- Libraries/LibCore/Assert.h | 8 +++--- Libraries/LibCore/Log.cpp | 9 ++++--- Libraries/LibCore/Log.h | 8 +++--- Libraries/LibScene/Entity.cpp | 18 ++++++++++--- Libraries/LibScene/SceneSerializer.cpp | 8 +++--- Libraries/LibScene/SceneTypes.h | 3 +++ Libraries/LibWindow/GLFWWindow.cpp | 28 +++++++++++--------- Libraries/LibWindow/Input.h | 3 ++- Libraries/LibWindow/WindowSystem.cpp | 26 ++++++++++++++++-- Libraries/LibWindow/WindowSystem.h | 5 ++++ Libraries/LibWindow/WindowTypes.h | 3 +++ 16 files changed, 116 insertions(+), 49 deletions(-) create mode 100644 Libraries/LibScene/SceneTypes.h create mode 100644 Libraries/LibWindow/WindowTypes.h diff --git a/Libraries/LibAsset/AssetImporterRegistry.cpp b/Libraries/LibAsset/AssetImporterRegistry.cpp index e53916cc..a0da1cf2 100644 --- a/Libraries/LibAsset/AssetImporterRegistry.cpp +++ b/Libraries/LibAsset/AssetImporterRegistry.cpp @@ -23,7 +23,7 @@ AssetLoadResult AssetImporterRegistry::load(AssetMetadata const& assetMetadata) return s_loaders[type_id]->load(assetMetadata); } - TR_CORE_ERROR(TR_LOG_ASSET, "Invalid asset type for asset: {0}", assetMetadata.Path); + TR_ERROR(TR_LOG_ASSET, "Invalid asset type for asset: {0}", assetMetadata.Path); return { Core::CreateShared(AssetImporterError::Code::ImporterNotFound) }; } @@ -37,7 +37,7 @@ bool AssetImporterRegistry::save(AssetMetadata const& assetMetadata, Core::RefPt if (s_loaders.contains(type_id)) return s_loaders[type_id]->save(assetMetadata, asset); - TR_CORE_ERROR(TR_LOG_ASSET, "Invalid asset type for asset: {0}", assetMetadata.Path); + TR_ERROR(TR_LOG_ASSET, "Invalid asset type for asset: {0}", assetMetadata.Path); return false; } diff --git a/Libraries/LibAsset/AssetManager.cpp b/Libraries/LibAsset/AssetManager.cpp index 74a2cde9..20bb0a21 100644 --- a/Libraries/LibAsset/AssetManager.cpp +++ b/Libraries/LibAsset/AssetManager.cpp @@ -10,8 +10,8 @@ #include #include #include -#include #include +#include #include #include @@ -23,14 +23,14 @@ AssetManager::AssetManager(Core::EventDispatcher& event_dispatcher) : m_event_dispatcher(event_dispatcher) { m_loaded_assets.clear(); - TR_CORE_INFO(TR_LOG_ASSET, "Initialized asset manager"); + TR_INFO(TR_LOG_ASSET, "Initialized asset manager"); } AssetManager::~AssetManager() { m_loaded_assets.clear(); - TR_CORE_INFO(TR_LOG_ASSET, "Shutdown asset manager"); + TR_INFO(TR_LOG_ASSET, "Shutdown asset manager"); } AssetHandle AssetManager::import_asset(std::filesystem::path const& asset_path) const @@ -64,21 +64,26 @@ void AssetManager::reload_asset_by_handle(AssetHandle const& handle) AssetMetadata const& metadata = AssetMetadataRegistry::asset_metadata_by_handle(handle); if (!m_loaded_assets.contains(handle)) - TR_CORE_WARN(TR_LOG_ASSET, "Trying to reload an asset that was never loaded"); + TR_WARN(TR_LOG_ASSET, "Trying to reload an asset that was never loaded"); AssetLoadResult asset_result = AssetImporterRegistry::load(metadata); - if (!asset_result) + if (!asset_result) { + TR_ERROR(TR_LOG_ASSET, "Failed to load asset with Id: {} and Path: {}", handle, metadata.Handle); return; + } m_loaded_assets[handle] = asset_result.value(); } Core::Result AssetManager::remove_asset(Core::UUID const& handle, RemoveAssetImmediately remove_immediately, RemoveAssetMetadata remove_metadata) { - if(!m_loaded_assets.contains(handle)) + if (!m_loaded_assets.contains(handle)) { + TR_ERROR(TR_LOG_ASSET, "Asset with id {} wasn't found!", handle); return { AssetRemoveError::AssetNotFound }; + } if (remove_metadata == RemoveAssetMetadata::Yes && !AssetMetadataRegistry::contains(handle)) { + TR_ERROR(TR_LOG_ASSET, "Asset metadata with id {} wasn't found!", handle); return { AssetRemoveError::MetadatNotFound }; } @@ -127,8 +132,12 @@ void AssetManager::on_filesystem_changed(std::vector #include #include +#include namespace Terran::Core { @@ -41,7 +42,7 @@ static spdlog::level::level_enum GetLogLevel(std::string const& logLevel) if (logLevel == "off") return spdlog::level::level_enum::off; - TR_CORE_WARN(TR_LOG_CORE, "Invalid log level, defaulting to trace"); + TR_WARN(TR_LOG_CORE, "Invalid log level, defaulting to trace"); return spdlog::level::level_enum::trace; } @@ -64,7 +65,7 @@ static std::string LogLevelToString(spdlog::level::level_enum logLevel) return "trace"; } - TR_CORE_WARN(TR_LOG_CORE, "Invalid log level, defaulting to trace"); + TR_WARN(TR_LOG_CORE, "Invalid log level, defaulting to trace"); return "trace"; } @@ -75,7 +76,7 @@ static void load_log_settings() try { node = YAML::LoadFile(s_SettingsPath.string()); } catch (YAML::Exception const& e) { - TR_CORE_ERROR(TR_LOG_CORE, e.what()); + TR_ERROR(TR_LOG_CORE, e.what()); return; } @@ -96,7 +97,7 @@ static void load_log_settings() index++; } } catch (YAML::BadSubscript const& e) { - TR_CORE_ERROR(TR_LOG_CORE, e.what()); + TR_ERROR(TR_LOG_CORE, e.what()); return; } } diff --git a/Libraries/LibCore/Log.h b/Libraries/LibCore/Log.h index 5454a6a3..67de0670 100644 --- a/Libraries/LibCore/Log.h +++ b/Libraries/LibCore/Log.h @@ -47,19 +47,19 @@ class Log final { static void add_logger(std::string const& logger_name); }; -#define TR_CORE_TRACE(LoggerName, ...) \ +#define TR_TRACE(LoggerName, ...) \ if (::Terran::Core::Log::contains(LoggerName)) \ ::Terran::Core::Log::logger(LoggerName)->trace(__VA_ARGS__) -#define TR_CORE_INFO(LoggerName, ...) \ +#define TR_INFO(LoggerName, ...) \ if (::Terran::Core::Log::contains(LoggerName)) \ ::Terran::Core::Log::logger(LoggerName)->info(__VA_ARGS__) -#define TR_CORE_WARN(LoggerName, ...) \ +#define TR_WARN(LoggerName, ...) \ if (::Terran::Core::Log::contains(LoggerName)) \ ::Terran::Core::Log::logger(LoggerName)->warn(__VA_ARGS__) -#define TR_CORE_ERROR(LoggerName, ...) \ +#define TR_ERROR(LoggerName, ...) \ if (::Terran::Core::Log::contains(LoggerName)) \ ::Terran::Core::Log::logger(LoggerName)->error(__VA_ARGS__) diff --git a/Libraries/LibScene/Entity.cpp b/Libraries/LibScene/Entity.cpp index f1351925..f537e5d6 100644 --- a/Libraries/LibScene/Entity.cpp +++ b/Libraries/LibScene/Entity.cpp @@ -4,7 +4,9 @@ #include #include +#include +#include #include #include @@ -50,10 +52,15 @@ Core::Result Entity::set_parent(Entity parent) if (!parent.has_component()) parent.add_component(); - if (is_child_of(parent)) + if (is_child_of(parent)) { + TR_WARN(TR_LOG_SCENE, "Trying to parent an entity with Id: {} which is already a child of {}", id(), parent.id()); return { EntityErrors::AlreadyAChildOfThisParent }; - if (parent.is_child_of(*this)) + } + if (parent.is_child_of(*this)) { + // NOTE: this is a terrible error message, couldn't think of a better one right now though + TR_WARN(TR_LOG_SCENE, "Trying to parent to an entity {} which is a child of the current entity", parent.id(), id()); return { EntityErrors::TargetParentIsAlreadyAChildOfThisEntity }; + } if (has_parent()) unparent(); @@ -72,24 +79,29 @@ Core::Result Entity::unparent() RelationshipComponent const* relationship_component = try_get_component(); if (!relationship_component) { + TR_WARN(TR_LOG_SCENE, "Trying to unparent an enity with Id: {} which doesn't have a relationship component", id()); return { EntityErrors::DoesntHaveRelationshipComponent }; } Core::UUID parent_id = relationship_component->Parent; Entity parent = m_scene->find_entity(parent_id); - if (!parent) + if (!parent) { + TR_WARN(TR_LOG_SCENE, "Failed to find parent {} while trying to unparent", parent_id); return { EntityErrors::ParentNotFound }; + } RelationshipComponent* parent_relationship_component = parent.try_get_component(); if (!parent_relationship_component) { + TR_WARN(TR_LOG_SCENE, "Parent {} doesn't have relationship component! Failed to unparent", parent_id); return { EntityErrors::DoesntHaveRelationshipComponent }; } auto& children = parent_relationship_component->Children; auto const& iterator = std::ranges::find(children, id()); if (iterator == parent.children().end()) { + TR_WARN(TR_LOG_SCENE, "Parent {} doesn't contain entity: {}! Failed to unparent", parent_id, id()); return { EntityErrors::ParentDoesntContainChild }; } diff --git a/Libraries/LibScene/SceneSerializer.cpp b/Libraries/LibScene/SceneSerializer.cpp index 14d238a6..2833a3d7 100644 --- a/Libraries/LibScene/SceneSerializer.cpp +++ b/Libraries/LibScene/SceneSerializer.cpp @@ -3,6 +3,7 @@ #include "Entity.h" #include "SceneSerializerError.h" #include "SceneManager.h" +#include "SceneTypes.h" #include #include @@ -106,7 +107,6 @@ static YAML::const_iterator find_entity(YAML::Node const& scene, Terran::Core::U { for (auto it = scene.begin(); it != scene.end(); ++it) { auto const& entity = *it; - TR_CORE_TRACE(TR_LOG_CORE, entity); Terran::Core::UUID id = entity["Entity"].as(); if (id == entityID) return it; @@ -120,6 +120,7 @@ static Entity deserialize_entity(YAML::Node const& data, YAML::Node const& scene try { Core::UUID id = data["Entity"].as(); if (!id) { + TR_ERROR(TR_LOG_SCENE, "Invalid id"); TR_ASSERT(false, "Invalid id"); return {}; } @@ -160,7 +161,7 @@ static Entity deserialize_entity(YAML::Node const& data, YAML::Node const& scene return deserializedEntity; } catch (YAML::InvalidNode const& ex) { - TR_CORE_ERROR(TR_LOG_ASSET, ex.what()); + TR_ERROR(TR_LOG_CORE, ex.what()); return Entity(); } } @@ -171,9 +172,10 @@ Asset::AssetLoadResult SceneSerializer::load(Asset::AssetMetadata const& assetMe try { data = YAML::LoadFile(assetMetadata.Path); } catch (YAML::ParserException const& ex) { + TR_ERROR(TR_LOG_SCENE, ex.what()); return { Core::CreateShared(SceneSerializerError::Code::InvalidFormat, ex.what()) }; } catch (YAML::BadFile const& ex) { - TR_CORE_ERROR(TR_LOG_CORE, ex.what()); + TR_ERROR(TR_LOG_SCENE, ex.what()); return { Core::CreateShared(SceneSerializerError::Code::NotFound, ex.what()) }; } diff --git a/Libraries/LibScene/SceneTypes.h b/Libraries/LibScene/SceneTypes.h new file mode 100644 index 00000000..af93f2dd --- /dev/null +++ b/Libraries/LibScene/SceneTypes.h @@ -0,0 +1,3 @@ +#pragma once + +constexpr char const* const TR_LOG_SCENE = "Scene"; diff --git a/Libraries/LibWindow/GLFWWindow.cpp b/Libraries/LibWindow/GLFWWindow.cpp index bbf6f0a7..060edd72 100644 --- a/Libraries/LibWindow/GLFWWindow.cpp +++ b/Libraries/LibWindow/GLFWWindow.cpp @@ -1,26 +1,30 @@ #include "GLFWWindow.h" -#include "LibCore/Assert.h" - -#include "GamepadEvent.h" #include "KeyboardEvent.h" #include "MouseEvent.h" #include "WindowEvent.h" +#include "Window.h" +#include "WindowTypes.h" +#include "MouseButtons.h" +#include "KeyCodes.h" + +#include #include -#include +#include + +#include + +#include namespace Terran { namespace Window { namespace Implementation { -static int s_glfw_init_result = glfwInit(); static GLFWwindow* s_current_window = nullptr; GLFWWindow::GLFWWindow(Core::EventDispatcher& event_dispatcher, WindowData const& data) : Window(event_dispatcher) { - TR_ASSERT(s_glfw_init_result, "GFLW couldn't initialze!"); - init(data); } GLFWWindow::~GLFWWindow() @@ -71,16 +75,17 @@ void GLFWWindow::init(WindowData data) m_window_data_ptr.video_mode = vidMode; m_window = glfwCreateWindow(data.Width, data.Height, data.Name.data(), nullptr, nullptr); - s_current_window = m_window; TR_ASSERT(m_window, "Couldn't create a GLFW window!"); + s_current_window = m_window; glfwSetWindowUserPointer(m_window, &m_window_data_ptr); glfwGetWindowContentScale(m_window, &m_window_data_ptr.scale_x, &m_window_data_ptr.scale_y); - TR_CORE_INFO(TR_LOG_CORE, "Created window"); + TR_INFO(TR_LOG_WINDOW, "Created window {} {}x{}", data.Name, data.Width, data.Height); + TR_TRACE(TR_LOG_WINDOW, "Setting up window event handlers"); setup_callbacks(); - TR_CORE_INFO(TR_LOG_CORE, "Setup window events"); + TR_INFO(TR_LOG_WINDOW, "Window event handlers successfuly setup"); glfwMakeContextCurrent(m_window); set_vsync(data.VSync); @@ -190,8 +195,7 @@ void GLFWWindow::setup_callbacks() void GLFWWindow::destroy() { glfwDestroyWindow(m_window); - glfwTerminate(); - TR_CORE_INFO(TR_LOG_CORE, "Destroyed window and opengl context"); + TR_INFO(TR_LOG_WINDOW, "Destroyed window"); } } diff --git a/Libraries/LibWindow/Input.h b/Libraries/LibWindow/Input.h index 03b9a592..fd67f23e 100644 --- a/Libraries/LibWindow/Input.h +++ b/Libraries/LibWindow/Input.h @@ -4,6 +4,7 @@ #include "InputState.h" #include "KeyCodes.h" #include "MouseButtons.h" +#include "WindowTypes.h" #include #include @@ -109,7 +110,7 @@ class Input final { for (auto const& key : InputUtils::Keys) m_keyStates.emplace(key, InputState()); - TR_CORE_INFO(TR_LOG_CORE, "Initialized input system"); + TR_INFO(TR_LOG_WINDOW, "Initialized input system"); } key_state_map m_keyStates; diff --git a/Libraries/LibWindow/WindowSystem.cpp b/Libraries/LibWindow/WindowSystem.cpp index b93e2bc7..732dc75b 100644 --- a/Libraries/LibWindow/WindowSystem.cpp +++ b/Libraries/LibWindow/WindowSystem.cpp @@ -1,10 +1,12 @@ #include "WindowSystem.h" -#include "GamepadEvent.h" #include "ControllerInput.h" +#include "GamepadEvent.h" +#include "WindowTypes.h" #include #include #include +#include #include #include @@ -21,22 +23,38 @@ WindowSystem::WindowSystem(Core::EventDispatcher& dispatcher) : Core::Layer("WindowLayer", dispatcher) { s_event_dispatcher = &dispatcher; + Core::Log::add_logger(TR_LOG_WINDOW); } WindowSystem::~WindowSystem() { } +static void window_error_callback(int error, char const* description) +{ + TR_ERROR(TR_LOG_WINDOW, "{}: {}", error, description); +} Core::Result WindowSystem::on_attach() { // we want hats and buttons to be queried using different functions // without this querying a joystick buttons' state will also give us // its hats' state + TR_TRACE(TR_LOG_WINDOW, "Intializing GLFW..."); + glfwSetErrorCallback(window_error_callback); glfwInitHint(GLFW_JOYSTICK_HAT_BUTTONS, GLFW_FALSE); - if (glfwInit() != GLFW_TRUE) + if (glfwInit() != GLFW_TRUE) { + char const* error_description; + int error_code = glfwGetError(&error_description); + if (error_description) { + TR_ERROR(TR_LOG_WINDOW, "Couldn't initialize GLFW! {}: {}", error_code, error_description); + } else { + TR_ERROR(TR_LOG_WINDOW, "Couldn't initialize GLFW! {}", error_code); + } return Core::Bad(); + } m_controllerInput.initialize(); + TR_INFO(TR_LOG_WINDOW, "GLFW successfuly initialized!"); // maybe not the greatest solution??? // for now though i still want the users to be able to access the controller input through the WindowSystem @@ -61,7 +79,11 @@ Core::Result WindowSystem::on_dettach() { // if glfwInit was unsuccessful running this function does nothing // thus there is no need to conditionally run it + m_windows.clear(); + + TR_TRACE(TR_LOG_WINDOW, "Terminating GLFW..."); glfwTerminate(); + TR_INFO(TR_LOG_WINDOW, "GLFW successfuly terminated!"); return {}; } diff --git a/Libraries/LibWindow/WindowSystem.h b/Libraries/LibWindow/WindowSystem.h index 9676d66f..dc19301c 100644 --- a/Libraries/LibWindow/WindowSystem.h +++ b/Libraries/LibWindow/WindowSystem.h @@ -37,6 +37,11 @@ class WindowSystem : public Core::Layer { return m_windows.at(id); } + void destroy(Core::UUID const& id) + { + m_windows.erase(id); + } + constexpr ControllerInput const& controllers() const { return m_controllerInput; diff --git a/Libraries/LibWindow/WindowTypes.h b/Libraries/LibWindow/WindowTypes.h new file mode 100644 index 00000000..f168798f --- /dev/null +++ b/Libraries/LibWindow/WindowTypes.h @@ -0,0 +1,3 @@ +#pragma once + +constexpr char const* const TR_LOG_WINDOW = "Window"; From d8543a40cb716e825d3c44f43ec53e95674d99f1 Mon Sep 17 00:00:00 2001 From: infirit89 Date: Mon, 23 Feb 2026 22:00:50 +0200 Subject: [PATCH 46/51] chore(Asset): make AssetHandle the way to access loaded assets The idea behind this change is to have AssetHandle control the lifetime of an Asset, i.e. when an asset is added/imported the AssetManager creates a strong AssetHandle. When all of the references to the AssetHandle drop, the destructor enqueues the corresponding Asset to be freed --- Libraries/LibAsset/Asset.cpp | 13 -- Libraries/LibAsset/Asset.h | 14 +- Libraries/LibAsset/AssetEvents.h | 12 +- Libraries/LibAsset/AssetHandle.cpp | 16 +++ Libraries/LibAsset/AssetHandle.h | 49 +++++++ Libraries/LibAsset/AssetManager.cpp | 60 +++++--- Libraries/LibAsset/AssetManager.h | 135 +++++++++++------- Libraries/LibAsset/AssetMetadata.h | 4 +- Libraries/LibAsset/AssetMetadataRegistry.cpp | 22 +-- Libraries/LibAsset/AssetMetadataRegistry.h | 12 +- Libraries/LibAsset/AssetSystem.h | 3 + Libraries/LibAsset/AssetTypes.h | 3 +- Libraries/LibScene/Entity.cpp | 2 +- Libraries/LibScene/SceneManager.cpp | 4 +- Libraries/LibScene/SceneManager.h | 3 + Tests/LibAsset/AssetImporterRegistryTests.cpp | 12 +- Tests/LibScene/EntityTests.cpp | 2 +- 17 files changed, 240 insertions(+), 126 deletions(-) delete mode 100644 Libraries/LibAsset/Asset.cpp create mode 100644 Libraries/LibAsset/AssetHandle.cpp create mode 100644 Libraries/LibAsset/AssetHandle.h diff --git a/Libraries/LibAsset/Asset.cpp b/Libraries/LibAsset/Asset.cpp deleted file mode 100644 index aba56464..00000000 --- a/Libraries/LibAsset/Asset.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include "Asset.h" -#include "AssetManager.h" - -namespace Terran::Asset { - -Asset::~Asset() -{ - if (m_asset_manager) { - m_asset_manager->enqueue_asset_for_deletion(m_handle); - } -} - -} diff --git a/Libraries/LibAsset/Asset.h b/Libraries/LibAsset/Asset.h index ecbabde7..3aeeb763 100644 --- a/Libraries/LibAsset/Asset.h +++ b/Libraries/LibAsset/Asset.h @@ -16,28 +16,26 @@ class AssetManager; class Asset : public Core::RefCounted { public: Asset() = default; - virtual ~Asset() override; - Asset(AssetHandle const& handle) - : m_handle(handle) + Asset(AssetId const& handle) + : m_id(handle) { } bool is_valid() { - return m_handle.is_valid(); + return m_id.is_valid(); } - AssetHandle const& handle() + AssetId const& id() { - return m_handle; + return m_id; } virtual AssetTypeId type() const = 0; private: - AssetHandle m_handle; + AssetId m_id; AssetTypeId m_type_id; - Core::RawPtr m_asset_manager; friend class AssetManager; }; diff --git a/Libraries/LibAsset/AssetEvents.h b/Libraries/LibAsset/AssetEvents.h index 0ffdb961..2f799c1f 100644 --- a/Libraries/LibAsset/AssetEvents.h +++ b/Libraries/LibAsset/AssetEvents.h @@ -8,30 +8,30 @@ namespace Terran::Asset { class AssetRemovedEvent { public: - constexpr AssetRemovedEvent(AssetHandle const& handle) noexcept + constexpr AssetRemovedEvent(AssetId const& handle) noexcept : m_handle(handle) { } - [[nodiscard]] constexpr AssetHandle const& handle() const noexcept + [[nodiscard]] constexpr AssetId const& handle() const noexcept { return m_handle; } private: - AssetHandle m_handle; + AssetId m_handle; }; class AssetRenamedEvent { public: - AssetRenamedEvent(AssetHandle const& handle, std::filesystem::path const& new_file_name, std::filesystem::path const& old_file_name) + AssetRenamedEvent(AssetId const& handle, std::filesystem::path const& new_file_name, std::filesystem::path const& old_file_name) : m_handle(handle) , m_new_file_name(new_file_name) , m_old_file_name(old_file_name) { } - [[nodiscard]] constexpr AssetHandle const& handle() const noexcept + [[nodiscard]] constexpr AssetId const& handle() const noexcept { return m_handle; } @@ -47,7 +47,7 @@ class AssetRenamedEvent { } private: - AssetHandle m_handle; + AssetId m_handle; std::filesystem::path m_new_file_name; std::filesystem::path m_old_file_name; }; diff --git a/Libraries/LibAsset/AssetHandle.cpp b/Libraries/LibAsset/AssetHandle.cpp new file mode 100644 index 00000000..3e02841b --- /dev/null +++ b/Libraries/LibAsset/AssetHandle.cpp @@ -0,0 +1,16 @@ +#include "AssetHandle.h" +#include "AssetManager.h" + +namespace Terran::Asset { + +AssetHandle::~AssetHandle() +{ + m_asset_manager->enqueue_asset_for_deletion(m_id); +} + +bool AssetHandle::is_valid() const +{ + return m_asset_manager->is_asset_loaded(m_id); +} + +} diff --git a/Libraries/LibAsset/AssetHandle.h b/Libraries/LibAsset/AssetHandle.h new file mode 100644 index 00000000..175bd0d0 --- /dev/null +++ b/Libraries/LibAsset/AssetHandle.h @@ -0,0 +1,49 @@ +#pragma once +#include "AssetTypes.h" + +#include +#include +#include +#include +#include + +namespace Terran::Asset { + +class AssetManager; +class AssetHandle : public Core::RefCounted { +public: + virtual ~AssetHandle() override; + + constexpr AssetId const& id() const + { + return m_id; + } + + bool is_valid() const; + constexpr operator bool() const { + return is_valid(); + } + +private: + // A nifty trick to ensure AssetHandle is only used with RefPtr or WeakPtr + AssetHandle(AssetId const& id, Core::RawPtr asset_manager) + : m_id(id) + , m_asset_manager(asset_manager) + { + } + +private: + AssetId m_id; + Core::RawPtr m_asset_manager; + template + friend class Core::RefPtr; + + template + friend class Core::WeakPtr; +}; + +using StrongAssetHandle = Core::RefPtr; +using WeakAssetHandle = Core::WeakPtr; + +} + diff --git a/Libraries/LibAsset/AssetManager.cpp b/Libraries/LibAsset/AssetManager.cpp index 20bb0a21..c38cae14 100644 --- a/Libraries/LibAsset/AssetManager.cpp +++ b/Libraries/LibAsset/AssetManager.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -33,46 +34,51 @@ AssetManager::~AssetManager() TR_INFO(TR_LOG_ASSET, "Shutdown asset manager"); } -AssetHandle AssetManager::import_asset(std::filesystem::path const& asset_path) const +Core::RefPtr AssetManager::import_asset(std::filesystem::path const& asset_path) const { - AssetHandle asset_handle = AssetMetadataRegistry::asset_handle_from_path(asset_path); + AssetId id = AssetMetadataRegistry::asset_handle_from_path(asset_path); - if (asset_handle) - return asset_handle; + if (id) { + return create_asset_handle(id); + } - asset_handle = AssetHandle(); + id = AssetId(); - if (asset_path.empty()) - return AssetHandle::invalid(); + if (asset_path.empty()) { + TR_ERROR(TR_LOG_ASSET, "Failed to import asset, due to empty asset path!"); + return nullptr; + } auto const asset_loader = AssetImporterRegistry::find_for_path(asset_path); - if (asset_loader) - return AssetHandle::invalid(); + if (asset_loader) { + TR_ERROR(TR_LOG_ASSET, "No registered loaders for asset with path {}", asset_path); + return nullptr; + } AssetMetadata asset_metadata; asset_metadata.Path = asset_path; asset_metadata.Type = asset_loader->asset_type(); - asset_metadata.Handle = asset_handle; + asset_metadata.AssetId = id; AssetMetadataRegistry::add_asset_metadata(asset_metadata); - return asset_handle; + return create_asset_handle(id); } -void AssetManager::reload_asset_by_handle(AssetHandle const& handle) +void AssetManager::reload_asset_by_id(AssetId const& asset_id) { - AssetMetadata const& metadata = AssetMetadataRegistry::asset_metadata_by_handle(handle); + AssetMetadata const& metadata = AssetMetadataRegistry::asset_metadata_by_handle(asset_id); - if (!m_loaded_assets.contains(handle)) + if (!m_loaded_assets.contains(asset_id)) TR_WARN(TR_LOG_ASSET, "Trying to reload an asset that was never loaded"); AssetLoadResult asset_result = AssetImporterRegistry::load(metadata); if (!asset_result) { - TR_ERROR(TR_LOG_ASSET, "Failed to load asset with Id: {} and Path: {}", handle, metadata.Handle); + TR_ERROR(TR_LOG_ASSET, "Failed to load asset with Id: {} and Path: {}", asset_id, metadata.AssetId); return; } - m_loaded_assets[handle] = asset_result.value(); + m_loaded_assets[asset_id] = asset_result.value(); } Core::Result AssetManager::remove_asset(Core::UUID const& handle, RemoveAssetImmediately remove_immediately, RemoveAssetMetadata remove_metadata) @@ -137,7 +143,7 @@ void AssetManager::on_filesystem_changed(std::vector #include #include #include #include -#include #include +#include #include #include @@ -44,28 +45,30 @@ enum class AssetRemoveError : uint8_t { class AssetManager final { using AssetChangeCallbackFn = std::function const&)>; - using asset_container_type = std::unordered_map>; - using free_queue_type = std::deque; + using asset_container_type = std::unordered_map>; + using free_queue_type = std::deque; public: AssetManager(Core::EventDispatcher& event_dispatcher); ~AssetManager(); - std::filesystem::path filesystem_path(std::filesystem::path const& path); + StrongAssetHandle import_asset(std::filesystem::path const& asset_path) const; - AssetHandle import_asset(std::filesystem::path const& asset_path) const; - void reload_asset_by_handle(AssetHandle const& handle); + void reload_asset_by_id(AssetId const& asset_id); + void reload_asset_by_handle(StrongAssetHandle const& asset_handle) { + reload_asset_by_id(asset_handle->id()); + } void SetAssetChangedCallback(AssetChangeCallbackFn const& callback) { m_asset_change_callback = callback; } template requires(std::is_base_of_v) - Core::RefPtr asset_by_handle(AssetHandle const& assetHandle) + Core::RefPtr asset_by_handle(StrongAssetHandle const& asset_handle) { - if (m_loaded_assets.contains(assetHandle)) - return Core::dynamic_pointer_cast(m_loaded_assets.at(assetHandle)); + if (m_loaded_assets.contains(asset_handle->id())) + return asset_by_id(asset_handle->id()); - AssetMetadata& info = AssetMetadataRegistry::asset_metadata_by_handle__internal(assetHandle); + AssetMetadata& info = AssetMetadataRegistry::asset_metadata_by_handle__internal(asset_handle->id()); if (!info) return nullptr; @@ -78,90 +81,124 @@ class AssetManager final { } Core::RefPtr const& asset = assetResult.value(); - asset->m_handle = assetHandle; - m_loaded_assets[assetHandle] = asset; - return Core::DynamicCast(m_loaded_assets[assetHandle]); + asset->m_id = asset_handle->id(); + m_loaded_assets[asset_handle->id()] = asset; + return Core::dynamic_pointer_cast(asset); } template requires(std::is_base_of_v) - Core::RefPtr asset_by_metadata(AssetMetadata const& assetMetadata) + Core::RefPtr asset_by_metadata(AssetMetadata const& asset_metadata) { - if (m_loaded_assets.contains(assetMetadata.Handle)) - return Core::dynamic_pointer_cast(m_loaded_assets.at(assetMetadata.Handle)); + if (m_loaded_assets.contains(asset_metadata.AssetId)) + return asset_by_id(asset_metadata.AssetId); - AssetLoadResult assetResult = AssetImporterRegistry::load(assetMetadata); + AssetLoadResult asset_result = AssetImporterRegistry::load(asset_metadata); - if (!assetResult) { - TR_ERROR(TR_LOG_ASSET, "Failed to load asset with path: {0}", assetMetadata.Path); + if (!asset_result) { + TR_ERROR(TR_LOG_ASSET, "Failed to load asset with path: {0}", asset_metadata.Path); return nullptr; } - Core::RefPtr const& asset = assetResult.value(); - asset->m_handle = assetMetadata.Handle; - m_loaded_assets[assetMetadata.Handle] = asset; - return Core::DynamicCast(m_loaded_assets[assetMetadata.Handle]); + Core::RefPtr const& asset = asset_result.value(); + asset->m_id = asset_metadata.AssetId; + m_loaded_assets[asset_metadata.AssetId] = asset; + return Core::dynamic_pointer_cast(asset); } template requires( std::is_base_of_v, HasStaticType) - Core::RefPtr create_asset(std::filesystem::path const& file_path, TArgs&&... args) + void save_asset(Core::RefPtr asset) { - AssetMetadata metadata; - metadata.Handle = AssetHandle(); - metadata.Path = file_path; - metadata.Type = TAsset::static_type(); + if (!AssetMetadataRegistry::contains(asset->id())) { + TR_ERROR(TR_LOG_ASSET, "Failed to save asset {} as there is no metadata associated with it", asset->id()); + return; + } + AssetMetadata& metadata = AssetMetadataRegistry::asset_metadata_by_handle__internal(asset->id()); int file_occurrence_count = 2; while (std::filesystem::exists(metadata.Path)) { // Format: parent_directory/file_name (occurrence_count).extension - metadata.Path = std::format("{} ({}){}", (file_path.parent_path() / file_path.stem()).string(), std::to_string(file_occurrence_count), file_path.extension().string()); + std::filesystem::path file_path_without_extension = metadata.Path.parent_path() / metadata.Path.stem(); + std::filesystem::path file_extension = metadata.Path.extension(); + metadata.Path = std::format("{} ({}){}", + file_path_without_extension.string(), + file_occurrence_count, + file_extension.string()); file_occurrence_count++; } - AssetMetadataRegistry::add_asset_metadata(metadata); - - Core::RefPtr asset = Core::RefPtr::create(std::forward(args)...); - - m_loaded_assets[metadata.Handle] = asset; AssetImporterRegistry::save(metadata, asset); - - return Core::dynamic_pointer_cast(m_loaded_assets[metadata.Handle]); } - // NOTE: maybe we should take in a parameter that signifies + // NOTE: maybe we should take in a parameter that signifies // whether to create metadata for this asset // something like create_memory_asset(CreateAssetMetadata::Yes) + StrongAssetHandle add_asset(Core::RefPtr const& asset) + { + m_loaded_assets[asset->id()] = asset; + return create_asset_handle(asset->id()); + } + template requires(std::is_base_of_v) - Core::RefPtr create_memory_asset(TArgs&&... args) + Core::RefPtr create_asset(TArgs&&... args) { Core::RefPtr asset = Core::RefPtr::create(std::forward(args)...); - m_loaded_assets[asset->m_handle] = asset; + return Core::dynamic_pointer_cast(asset); + } - return Core::dynamic_pointer_cast(m_loaded_assets[asset->m_handle]); + template + requires( + std::is_base_of_v, + HasStaticType) + AssetMetadata create_asset_metadata(AssetId const& asset_id, std::filesystem::path const& file_path) + { + AssetMetadata metadata; + metadata.AssetId = asset_id; + metadata.Path = file_path; + metadata.Type = TAsset::static_type(); } Core::Result remove_asset(Core::UUID const& handle, RemoveAssetImmediately remove_immediately = RemoveAssetImmediately::Yes, RemoveAssetMetadata remove_metadata = RemoveAssetMetadata::Yes); + constexpr bool is_asset_loaded(AssetId const& id) const { + return m_loaded_assets.contains(id); + } + + void purge_stale(); + private: void on_filesystem_changed(std::vector const& file_system_events); - void on_asset_removed(AssetHandle const& handle); - void on_asset_renamed(AssetHandle const& handle, std::filesystem::path const& new_file_name); - void enqueue_asset_for_deletion(AssetHandle const& handle); + void on_asset_removed(AssetId const& handle); + void on_asset_renamed(AssetId const& handle, std::filesystem::path const& new_file_name); + void enqueue_asset_for_deletion(AssetId const& handle) const; + + template + requires(std::is_base_of_v) + inline void asset_by_id(AssetId const& id) + { + return Core::dynamic_pointer_cast(m_loaded_assets.at(id)); + } + + inline StrongAssetHandle create_asset_handle(AssetId const& asset_id) const + { + return StrongAssetHandle::create(asset_id, this); + } private: asset_container_type m_loaded_assets; AssetChangeCallbackFn m_asset_change_callback; Core::EventDispatcher& m_event_dispatcher; - //NOTE: this needs to be a multithreaded queue, either with or without a lock!!! - //This works for now because the engine is not multithreaded - //Will cause a bunch of problems if not changed and the engine goes multithreaded!!! - free_queue_type m_free_queue; + // NOTE: this needs to be a multithreaded queue, either with or without a lock!!! + // This works for now because the engine is not multithreaded + // Will cause a bunch of problems if not changed and the engine goes multithreaded!!! + mutable free_queue_type m_free_queue; friend class Asset; + friend class AssetHandle; }; } diff --git a/Libraries/LibAsset/AssetMetadata.h b/Libraries/LibAsset/AssetMetadata.h index aac10348..8f31d4d1 100644 --- a/Libraries/LibAsset/AssetMetadata.h +++ b/Libraries/LibAsset/AssetMetadata.h @@ -23,12 +23,12 @@ struct AssetMetadata final { operator bool() const { - return Path != "" && Handle.is_valid(); + return Path != "" && AssetId.is_valid(); } std::filesystem::path Path = ""; AssetTypeId Type; - AssetHandle Handle = AssetHandle::invalid(); + AssetId AssetId = AssetId::invalid(); }; } diff --git a/Libraries/LibAsset/AssetMetadataRegistry.cpp b/Libraries/LibAsset/AssetMetadataRegistry.cpp index 1baa1420..96358c05 100644 --- a/Libraries/LibAsset/AssetMetadataRegistry.cpp +++ b/Libraries/LibAsset/AssetMetadataRegistry.cpp @@ -14,10 +14,10 @@ namespace Terran { namespace Asset { -std::map AssetMetadataRegistry::s_asset_metadata; +std::map AssetMetadataRegistry::s_asset_metadata; static AssetMetadata s_invalid_asset_info; -AssetMetadata& AssetMetadataRegistry::asset_metadata_by_handle__internal(AssetHandle const& handle) +AssetMetadata& AssetMetadataRegistry::asset_metadata_by_handle__internal(AssetId const& handle) { if (s_asset_metadata.contains(handle)) return s_asset_metadata.at(handle); @@ -31,7 +31,7 @@ AssetMetadata& AssetMetadataRegistry::asset_metadata_by_handle__internal(AssetHa // return std::filesystem::relative(path, Project::GetAssetPath()); // } -AssetMetadata const& AssetMetadataRegistry::asset_metadata_by_handle(AssetHandle const& handle) +AssetMetadata const& AssetMetadataRegistry::asset_metadata_by_handle(AssetId const& handle) { if (s_asset_metadata.contains(handle)) return s_asset_metadata.at(handle); @@ -51,7 +51,7 @@ AssetMetadata const& AssetMetadataRegistry::asset_metadata_by_path(std::filesyst return s_invalid_asset_info; } -AssetHandle AssetMetadataRegistry::asset_handle_from_path(std::filesystem::path const& assetPath) +AssetId AssetMetadataRegistry::asset_handle_from_path(std::filesystem::path const& assetPath) { for (auto const& [handle, asset_metadata] : s_asset_metadata) { if (asset_metadata.Path == assetPath) @@ -59,7 +59,7 @@ AssetHandle AssetMetadataRegistry::asset_handle_from_path(std::filesystem::path } TR_ERROR(TR_LOG_ASSET, "Failed to find asset handle from path {}", assetPath); - return AssetHandle::invalid(); + return AssetId::invalid(); } void AssetMetadataRegistry::serialize_asset_metadata(YAML::Emitter& out) @@ -69,7 +69,7 @@ void AssetMetadataRegistry::serialize_asset_metadata(YAML::Emitter& out) for (auto const& [assetID, assetInfo] : s_asset_metadata) { out << YAML::BeginMap; - out << YAML::Key << "Asset" << YAML::Value << std::to_string(assetInfo.Handle); + out << YAML::Key << "Asset" << YAML::Value << std::to_string(assetInfo.AssetId); out << YAML::Key << "Type" << YAML::Value << assetInfo.Type; out << YAML::Key << "Path" << YAML::Value << assetInfo.Path.string(); out << YAML::EndMap; @@ -88,11 +88,11 @@ void AssetMetadataRegistry::deserialize_asset_metadata(YAML::Node const& node) for (auto const& assetInfo : assetInfos) { AssetMetadata info; - AssetHandle assetHandle = AssetHandle::from_string(assetInfo["Asset"].as()); + AssetId assetHandle = AssetId::from_string(assetInfo["Asset"].as()); info.Type = assetInfo["Type"].as(); info.Path = assetInfo["Path"].as(); - info.Handle = assetHandle; + info.AssetId = assetHandle; if (info.Path.empty()) continue; @@ -128,16 +128,16 @@ void AssetMetadataRegistry::load_asset_metadata_from_file(std::filesystem::path deserialize_asset_metadata(node); } -void AssetMetadataRegistry::erase(AssetHandle const& handle) { +void AssetMetadataRegistry::erase(AssetId const& handle) { s_asset_metadata.erase(handle); } -bool AssetMetadataRegistry::contains(AssetHandle const& handle) { +bool AssetMetadataRegistry::contains(AssetId const& handle) { return s_asset_metadata.contains(handle); } void AssetMetadataRegistry::add_asset_metadata(const AssetMetadata &metadata) { - s_asset_metadata.emplace(metadata.Handle, metadata); + s_asset_metadata.emplace(metadata.AssetId, metadata); } } diff --git a/Libraries/LibAsset/AssetMetadataRegistry.h b/Libraries/LibAsset/AssetMetadataRegistry.h index 5003eb34..97da3e83 100644 --- a/Libraries/LibAsset/AssetMetadataRegistry.h +++ b/Libraries/LibAsset/AssetMetadataRegistry.h @@ -12,14 +12,14 @@ namespace Terran { namespace Asset { class AssetMetadataRegistry final { - using AssetMetadataMap = std::map; + using AssetMetadataMap = std::map; public: - static AssetMetadata const& asset_metadata_by_handle(AssetHandle const& assetHandle); + static AssetMetadata const& asset_metadata_by_handle(AssetId const& assetHandle); static AssetMetadata const& asset_metadata_by_path(std::filesystem::path const& assetPath); - static AssetHandle asset_handle_from_path(std::filesystem::path const& assetPath); + static AssetId asset_handle_from_path(std::filesystem::path const& assetPath); static void add_asset_metadata(AssetMetadata const& metadata); @@ -36,9 +36,9 @@ class AssetMetadataRegistry final { static void deserialize_asset_metadata(YAML::Node const& node); - static void erase(AssetHandle const& handle); + static void erase(AssetId const& handle); - static bool contains(AssetHandle const& handle); + static bool contains(AssetId const& handle); static void clear() { @@ -46,7 +46,7 @@ class AssetMetadataRegistry final { } private: - static AssetMetadata& asset_metadata_by_handle__internal(AssetHandle const& handle); + static AssetMetadata& asset_metadata_by_handle__internal(AssetId const& handle); static AssetMetadataMap s_asset_metadata; diff --git a/Libraries/LibAsset/AssetSystem.h b/Libraries/LibAsset/AssetSystem.h index 42b79037..453063e7 100644 --- a/Libraries/LibAsset/AssetSystem.h +++ b/Libraries/LibAsset/AssetSystem.h @@ -1,10 +1,12 @@ #pragma once #include "AssetManager.h" +#include "AssetTypes.h" #include #include #include +#include namespace Terran::Asset { @@ -14,6 +16,7 @@ class AssetSystem final : public Core::Layer { AssetSystem(Core::EventDispatcher& dispatcher) : Core::Layer("Asset", dispatcher) { + Core::Log::add_logger(TR_LOG_ASSET); m_asset_manager = Core::CreateShared(dispatcher); } diff --git a/Libraries/LibAsset/AssetTypes.h b/Libraries/LibAsset/AssetTypes.h index 3c7a48da..29fe1b51 100644 --- a/Libraries/LibAsset/AssetTypes.h +++ b/Libraries/LibAsset/AssetTypes.h @@ -7,7 +7,8 @@ namespace Terran { namespace Asset { -using AssetHandle = Terran::Core::UUID; +using AssetId = Terran::Core::UUID; + using AssetTypeId = uint64_t; constexpr char const* const TR_LOG_ASSET = "Asset"; diff --git a/Libraries/LibScene/Entity.cpp b/Libraries/LibScene/Entity.cpp index f537e5d6..21e237fb 100644 --- a/Libraries/LibScene/Entity.cpp +++ b/Libraries/LibScene/Entity.cpp @@ -25,7 +25,7 @@ bool Entity::has_parent() const } Core::UUID const& Entity::scene_id() const { - return m_scene->handle(); + return m_scene->id(); } Entity Entity::child_at(uint32_t index) const diff --git a/Libraries/LibScene/SceneManager.cpp b/Libraries/LibScene/SceneManager.cpp index 49cb90b9..d5779be3 100644 --- a/Libraries/LibScene/SceneManager.cpp +++ b/Libraries/LibScene/SceneManager.cpp @@ -12,12 +12,12 @@ namespace Terran::World { Core::RefPtr SceneManager::create_empty_scene() { - return m_asset_system->asset_manager()->create_memory_asset(event_dispatcher); + return m_asset_system->asset_manager()->create_asset(event_dispatcher); } Core::RefPtr SceneManager::copy_scene(Core::RefPtr const& source_scene) { - Core::RefPtr scene = m_asset_system->asset_manager()->create_memory_asset(event_dispatcher); + Core::RefPtr scene = m_asset_system->asset_manager()->create_asset(event_dispatcher); auto tag_view = source_scene->entities_with(); diff --git a/Libraries/LibScene/SceneManager.h b/Libraries/LibScene/SceneManager.h index 7c987d06..40e673e3 100644 --- a/Libraries/LibScene/SceneManager.h +++ b/Libraries/LibScene/SceneManager.h @@ -1,10 +1,12 @@ #pragma once #include "Scene.h" +#include "SceneTypes.h" #include #include #include +#include #include #include @@ -19,6 +21,7 @@ class SceneManager final : public Core::Layer { : Core::Layer("Scene", dispatcher) , m_asset_system(asset_system) { + Core::Log::add_logger(TR_LOG_SCENE); } Core::RefPtr create_empty_scene(); diff --git a/Tests/LibAsset/AssetImporterRegistryTests.cpp b/Tests/LibAsset/AssetImporterRegistryTests.cpp index b975803f..982f51f0 100644 --- a/Tests/LibAsset/AssetImporterRegistryTests.cpp +++ b/Tests/LibAsset/AssetImporterRegistryTests.cpp @@ -18,7 +18,7 @@ namespace Terran::Asset::Tests { class MockAsset final : public Asset { public: - MockAsset(AssetHandle const& handle) + MockAsset(AssetId const& handle) : Asset(handle) { } @@ -27,7 +27,7 @@ class MockAsset final : public Asset { class MockAsset2 final : public Asset { public: - MockAsset2(AssetHandle const& handle) + MockAsset2(AssetId const& handle) : Asset(handle) { } @@ -39,7 +39,7 @@ class MockAssetImporter final : public AssetImporter { virtual ~MockAssetImporter() override = default; [[nodiscard]] virtual AssetLoadResult load(AssetMetadata const& assetMetadata) override { - Core::RefPtr asset = Core::RefPtr::create(assetMetadata.Handle); + Core::RefPtr asset = Core::RefPtr::create(assetMetadata.AssetId); return { asset }; } virtual bool save(AssetMetadata const&, Core::RefPtr const&) override @@ -59,7 +59,7 @@ class AssetImporterRegistryTest : public testing::Test { protected: AssetImporterRegistryTest() { - m_asset_metadata.Handle = Core::UUID(); + m_asset_metadata.AssetId = Core::UUID(); m_asset_metadata.Path = ""; m_asset_metadata.Type = MockAsset::static_type(); @@ -80,7 +80,7 @@ TEST_F(AssetImporterRegistryTest, load_calls_load_for_a_given_asset_importer_whe auto assetRes = AssetImporterRegistry::load(m_asset_metadata); ASSERT_TRUE(assetRes.is_ok()); ASSERT_TRUE(assetRes.value()->is_valid()); - ASSERT_EQ(assetRes.value()->handle(), m_asset_metadata.Handle); + ASSERT_EQ(assetRes.value()->id(), m_asset_metadata.AssetId); } TEST_F(AssetImporterRegistryTest, load_returns_error_when_asset_importer_of_a_given_type_is_not_registered) @@ -95,7 +95,7 @@ TEST_F(AssetImporterRegistryTest, load_returns_error_when_asset_importer_of_a_gi TEST_F(AssetImporterRegistryTest, save_calls_save_for_a_given_asset_importer_when_it_is_registered) { - Core::RefPtr mockAsset = Core::RefPtr::create(m_asset_metadata.Handle); + Core::RefPtr mockAsset = Core::RefPtr::create(m_asset_metadata.AssetId); bool result = AssetImporterRegistry::save(m_asset_metadata, mockAsset); ASSERT_TRUE(result); } diff --git a/Tests/LibScene/EntityTests.cpp b/Tests/LibScene/EntityTests.cpp index 04114a99..928f7112 100644 --- a/Tests/LibScene/EntityTests.cpp +++ b/Tests/LibScene/EntityTests.cpp @@ -52,7 +52,7 @@ TEST_F(EntityTest, has_parent_returns_false_when_entity_doesnt_have_relationship TEST_F(EntityTest, entity_reports_correct_scene_id) { Entity entity = m_scene->create_entity("Parent"); - ASSERT_EQ(entity.scene_id(), m_scene->handle()); + ASSERT_EQ(entity.scene_id(), m_scene->id()); } TEST_F(EntityTest, set_parent_adds_child_to_parents_children_list) From cb26b9600c9e7707975c16e10302a9ce534c9d8e Mon Sep 17 00:00:00 2001 From: infirit89 Date: Wed, 25 Feb 2026 23:02:21 +0200 Subject: [PATCH 47/51] chore(Asset): fix logical errors introduced by reworking --- Libraries/LibAsset/AssetManager.cpp | 5 +++-- Libraries/LibAsset/AssetManager.h | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Libraries/LibAsset/AssetManager.cpp b/Libraries/LibAsset/AssetManager.cpp index c38cae14..9a9b07e9 100644 --- a/Libraries/LibAsset/AssetManager.cpp +++ b/Libraries/LibAsset/AssetManager.cpp @@ -6,6 +6,7 @@ #include "AssetMetadata.h" #include "AssetMetadataRegistry.h" #include "AssetTypes.h" +#include "AssetHandle.h" #include #include @@ -50,7 +51,7 @@ Core::RefPtr AssetManager::import_asset(std::filesystem::path const } auto const asset_loader = AssetImporterRegistry::find_for_path(asset_path); - if (asset_loader) { + if (!asset_loader) { TR_ERROR(TR_LOG_ASSET, "No registered loaders for asset with path {}", asset_path); return nullptr; } @@ -74,7 +75,7 @@ void AssetManager::reload_asset_by_id(AssetId const& asset_id) AssetLoadResult asset_result = AssetImporterRegistry::load(metadata); if (!asset_result) { - TR_ERROR(TR_LOG_ASSET, "Failed to load asset with Id: {} and Path: {}", asset_id, metadata.AssetId); + TR_ERROR(TR_LOG_ASSET, "Failed to load asset with Id: {} and Path: {}", asset_id, metadata.Path); return; } diff --git a/Libraries/LibAsset/AssetManager.h b/Libraries/LibAsset/AssetManager.h index fd85c293..efe96053 100644 --- a/Libraries/LibAsset/AssetManager.h +++ b/Libraries/LibAsset/AssetManager.h @@ -161,6 +161,7 @@ class AssetManager final { metadata.AssetId = asset_id; metadata.Path = file_path; metadata.Type = TAsset::static_type(); + return metadata; } Core::Result remove_asset(Core::UUID const& handle, RemoveAssetImmediately remove_immediately = RemoveAssetImmediately::Yes, RemoveAssetMetadata remove_metadata = RemoveAssetMetadata::Yes); @@ -179,7 +180,7 @@ class AssetManager final { template requires(std::is_base_of_v) - inline void asset_by_id(AssetId const& id) + inline Core::RefPtr asset_by_id(AssetId const& id) { return Core::dynamic_pointer_cast(m_loaded_assets.at(id)); } From 307fb0c200520d026a2c85174ab39347f0701ce6 Mon Sep 17 00:00:00 2001 From: infirit89 Date: Wed, 25 Feb 2026 23:55:33 +0200 Subject: [PATCH 48/51] chore(Scene): fix logical errors introduced by refactoring --- Libraries/LibScene/Entity.cpp | 4 +-- Libraries/LibScene/SceneManager.cpp | 9 +++++-- Libraries/LibScene/SceneSerializer.cpp | 36 ++++++++++++++------------ 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/Libraries/LibScene/Entity.cpp b/Libraries/LibScene/Entity.cpp index 21e237fb..7ebe4b60 100644 --- a/Libraries/LibScene/Entity.cpp +++ b/Libraries/LibScene/Entity.cpp @@ -58,7 +58,7 @@ Core::Result Entity::set_parent(Entity parent) } if (parent.is_child_of(*this)) { // NOTE: this is a terrible error message, couldn't think of a better one right now though - TR_WARN(TR_LOG_SCENE, "Trying to parent to an entity {} which is a child of the current entity", parent.id(), id()); + TR_WARN(TR_LOG_SCENE, "Trying to parent to an entity {} which is a child of the current entity with Id: {}", parent.id(), id()); return { EntityErrors::TargetParentIsAlreadyAChildOfThisEntity }; } @@ -100,7 +100,7 @@ Core::Result Entity::unparent() auto& children = parent_relationship_component->Children; auto const& iterator = std::ranges::find(children, id()); - if (iterator == parent.children().end()) { + if (iterator == children.end()) { TR_WARN(TR_LOG_SCENE, "Parent {} doesn't contain entity: {}! Failed to unparent", parent_id, id()); return { EntityErrors::ParentDoesntContainChild }; } diff --git a/Libraries/LibScene/SceneManager.cpp b/Libraries/LibScene/SceneManager.cpp index d5779be3..f8aef4f0 100644 --- a/Libraries/LibScene/SceneManager.cpp +++ b/Libraries/LibScene/SceneManager.cpp @@ -5,8 +5,8 @@ #include "SceneEvent.h" #include -#include #include +#include namespace Terran::World { @@ -22,6 +22,11 @@ Core::RefPtr SceneManager::copy_scene(Core::RefPtr const& source_s auto tag_view = source_scene->entities_with(); for (auto e : tag_view) { + Entity source_entity(e, source_scene.data()); + scene->create_entity(source_entity.name(), source_entity.id()); + } + for (auto e : tag_view) { + Entity source_entity(e, source_scene.data()); Entity destination_entity = scene->find_entity(source_entity.id()); @@ -44,7 +49,7 @@ Core::RefPtr SceneManager::copy_scene(Core::RefPtr const& source_s void SceneManager::set_current_scene(Core::RefPtr new_scene) { - if (!m_current_scene) { + if (m_current_scene == new_scene) { return; } diff --git a/Libraries/LibScene/SceneSerializer.cpp b/Libraries/LibScene/SceneSerializer.cpp index 2833a3d7..116b3d13 100644 --- a/Libraries/LibScene/SceneSerializer.cpp +++ b/Libraries/LibScene/SceneSerializer.cpp @@ -1,16 +1,16 @@ #include "SceneSerializer.h" #include "Components.h" #include "Entity.h" -#include "SceneSerializerError.h" #include "SceneManager.h" +#include "SceneSerializerError.h" #include "SceneTypes.h" #include #include +#include #include #include #include -#include #include #include @@ -103,7 +103,7 @@ bool SceneSerializer::save(Asset::AssetMetadata const& assetMetadata, Core::RefP return true; } -static YAML::const_iterator find_entity(YAML::Node const& scene, Terran::Core::UUID const& entityID) +static YAML::const_iterator find_entity_node(YAML::Node const& scene, Terran::Core::UUID const& entityID) { for (auto it = scene.begin(); it != scene.end(); ++it) { auto const& entity = *it; @@ -128,38 +128,42 @@ static Entity deserialize_entity(YAML::Node const& data, YAML::Node const& scene if (Entity entity = deserializedScene->find_entity(id); entity) return entity; - auto tagComponent = data["TagComponent"]; - if (!tagComponent) { + auto tag_component_node = data["TagComponent"]; + if (!tag_component_node) { TR_ASSERT(false, "Invalid tag component"); return {}; } - std::string name = tagComponent["Tag"].as(); - Entity deserializedEntity = deserializedScene->create_entity(name, id); + std::string name = tag_component_node["Tag"].as(); + Entity deserialized_entity = deserializedScene->create_entity(name, id); if (auto transform_component_node = data["TransformComponent"]; transform_component_node) { - auto& transform_component = deserializedEntity.transform(); + auto& transform_component = deserialized_entity.transform(); transform_component.Position = transform_component_node["Position"].as(glm::vec3(0.0f, 0.0f, 0.0f)); transform_component.Rotation = transform_component_node["Rotation"].as(glm::vec3(0.0f, 0.0f, 0.0f)); transform_component.Scale = transform_component_node["Scale"].as(glm::vec3(1.0f, 1.0f, 1.0f)); } if (auto relationship_component_node = data["RelationshipComponent"]; relationship_component_node) { - deserializedEntity.add_component(); - for (auto childID : relationship_component_node["Children"]) { - Core::UUID deserializedChildID = childID.as(); - Entity child = deserializedScene->find_entity(deserializedChildID); + deserialized_entity.add_component(); + for (auto child_id_node : relationship_component_node["Children"]) { + Core::UUID deserialized_child_id = child_id_node.as(); + Entity child = deserializedScene->find_entity(deserialized_child_id); if (!child) { - YAML::const_iterator childNode = find_entity(scene, deserializedChildID); - child = deserialize_entity(*childNode, scene, deserializedScene); + YAML::const_iterator child_node = find_entity_node(scene, deserialized_child_id); + if (child_node == scene.end()) { + TR_WARN(TR_LOG_SCENE, "Entity {} references child entity with Id: {} could not be found! Child is skipped!", id, deserialized_child_id); + } else { + child = deserialize_entity(*child_node, scene, deserializedScene); + } } if (child) - child.set_parent(deserializedEntity); + child.set_parent(deserialized_entity); } } - return deserializedEntity; + return deserialized_entity; } catch (YAML::InvalidNode const& ex) { TR_ERROR(TR_LOG_CORE, ex.what()); return Entity(); From 32fdce455cc48eea5e35c74acd3a2b7267220f13 Mon Sep 17 00:00:00 2001 From: infirit89 Date: Wed, 4 Mar 2026 19:09:07 +0200 Subject: [PATCH 49/51] chore(Core): imporve WeakPtr WeakPtr was essentially broken, not checks for whether the actual object is still alive, and the lock tried to create a RefPtr without any regard, essentially doing an use-after-free is the original object was dead --- Libraries/LibCore/DefaultDelete.h | 14 +++++ Libraries/LibCore/RefCounted.h | 91 +++++++++++++++++++++++++++++++ Libraries/LibCore/RefPtr.h | 32 ++--------- Libraries/LibCore/WeakRef.h | 40 +++++++++++++- 4 files changed, 148 insertions(+), 29 deletions(-) create mode 100644 Libraries/LibCore/RefCounted.h diff --git a/Libraries/LibCore/DefaultDelete.h b/Libraries/LibCore/DefaultDelete.h index a34028b0..89773d70 100644 --- a/Libraries/LibCore/DefaultDelete.h +++ b/Libraries/LibCore/DefaultDelete.h @@ -5,6 +5,7 @@ */ #pragma once +#include namespace Terran { namespace Core { @@ -17,6 +18,12 @@ namespace Core { template struct DefaultDelete { constexpr DefaultDelete() noexcept = default; + + constexpr void operator()(T const* value) noexcept + { + delete value; + } + constexpr void operator()(T* value) noexcept { delete value; @@ -39,5 +46,12 @@ struct DefaultDelete { } }; +template +concept IsDefaultDelete = requires(T t, T* ptr, T const* const_ptr) { + { t(ptr) } -> std::same_as ; + { t(const_ptr) } -> std::same_as ; + +}; + } } diff --git a/Libraries/LibCore/RefCounted.h b/Libraries/LibCore/RefCounted.h new file mode 100644 index 00000000..26ad4e96 --- /dev/null +++ b/Libraries/LibCore/RefCounted.h @@ -0,0 +1,91 @@ +#pragma once + +#include "DefaultDelete.h" + +#include +#include +#include + +namespace Terran::Core { + +// NOTE: meant to be used ONLY internally by RefCounted, RefPtr and WeakPtr +// meant to be allocated on the heap +class WeakData { + +public: + void increment_weak_count() const + { + m_weak_count++; + } + + void decrement_weak_count() const + { + m_weak_count--; + if (weak_count() == 0) { + DefaultDelete deleter; + deleter(this); + } + } + + size_t weak_count() const + { + return m_weak_count.load(); + } + + bool is_alive() const noexcept + { + return m_is_alive.load(); + } + + void mark_dead() noexcept + { + m_is_alive.store(false); + } + +private: + mutable std::atomic_size_t m_weak_count = 0; + mutable std::atomic_bool m_is_alive; +}; + +class RefCounted { +public: + virtual ~RefCounted() + { + if (m_weak_data) { + m_weak_data->mark_dead(); + m_weak_data->decrement_weak_count(); + } + } + + void increment_count() const + { + m_reference_count++; + } + + void decrement_count() const + { + m_reference_count--; + } + + size_t ref_count() const + { + return m_reference_count.load(); + } + + WeakData* acquire_weak_data() const + { + if (!m_weak_data) { + m_weak_data = new WeakData(); + } + return m_weak_data; + } + +private: + mutable std::atomic_size_t m_reference_count = 0; + mutable WeakData* m_weak_data = nullptr; +}; + +template +concept IsRefCounted = std::is_base_of_v; + +} diff --git a/Libraries/LibCore/RefPtr.h b/Libraries/LibCore/RefPtr.h index 81caf6b2..19ba7a34 100644 --- a/Libraries/LibCore/RefPtr.h +++ b/Libraries/LibCore/RefPtr.h @@ -1,39 +1,15 @@ #pragma once -#include -#include -#include +#include "DefaultDelete.h" +#include "RefCounted.h" +#include "Unique.h" + #include #include #include namespace Terran::Core { -class RefCounted { -public: - virtual ~RefCounted() = default; - - void increment_count() const - { - m_reference_count++; - } - - void decrement_count() const - { - m_reference_count--; - } - - size_t ref_count() const - { - return m_reference_count.load(); - } - -private: - mutable std::atomic_size_t m_reference_count = 0; -}; - -template -concept IsRefCounted = std::is_base_of_v; template class RefPtr final { diff --git a/Libraries/LibCore/WeakRef.h b/Libraries/LibCore/WeakRef.h index cde9023e..b9451645 100644 --- a/Libraries/LibCore/WeakRef.h +++ b/Libraries/LibCore/WeakRef.h @@ -1,5 +1,6 @@ #pragma once #include "RefPtr.h" +#include #include #include #include @@ -14,44 +15,65 @@ class WeakPtr final { constexpr WeakPtr() noexcept = default; explicit(false) constexpr WeakPtr(WeakPtr const& other) noexcept : m_data(other.m_data) + , m_weak_data(other.m_weak_data) { + increment_weak_count(); } template requires(std::is_convertible_v) explicit(false) constexpr WeakPtr(WeakPtr const& other) noexcept : m_data(other.m_data) + , m_weak_data(other.m_weak_data) { + increment_weak_count(); } template requires(std::is_convertible_v) explicit(false) constexpr WeakPtr(RefPtr const& other) noexcept : m_data(other.data()) + , m_weak_data(other.data() ? other.data()->acquire_weak_data() : nullptr) { } explicit(false) constexpr WeakPtr(WeakPtr&& other) noexcept : m_data(other.release()) + , m_weak_data(std::exchange(other.m_weak_data, nullptr)) { + increment_weak_count(); } template requires(std::is_convertible_v) explicit(false) constexpr WeakPtr(WeakPtr&& other) noexcept : m_data(other.release()) + , m_weak_data(std::exchange(other.m_weak_data, nullptr)) { + increment_weak_count(); } - constexpr ~WeakPtr() noexcept = default; + constexpr ~WeakPtr() noexcept + { + decrement_weak_count(); + } [[nodiscard]] constexpr value_type* release() noexcept { return std::exchange(m_data, nullptr); } + [[nodiscard]] constexpr bool expired() const noexcept + { + return !m_weak_data || !m_weak_data->is_alive(); + } + [[nodiscard]] constexpr RefPtr lock() noexcept { + if (expired()) { + return RefPtr(nullptr); + } + return RefPtr(m_data); } @@ -114,8 +136,24 @@ class WeakPtr final { return *this; } +private: + void increment_weak_count() const noexcept + { + if (m_weak_data) { + m_weak_data->increment_weak_count(); + } + } + + void decrement_weak_count() const noexcept + { + if (m_weak_data) { + m_weak_data->decrement_weak_count(); + } + } + private: value_type* m_data = nullptr; + WeakData* m_weak_data = nullptr; }; } From ae4c54564a7d4f7f41fdf002ebd6569d146b9fd1 Mon Sep 17 00:00:00 2001 From: infirit89 Date: Wed, 4 Mar 2026 19:44:33 +0200 Subject: [PATCH 50/51] chore(Asset): address sonarqube errors --- Libraries/LibAsset/Asset.h | 6 +++--- Libraries/LibAsset/AssetHandle.h | 3 ++- Libraries/LibAsset/AssetManager.h | 16 ++++++++-------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/Libraries/LibAsset/Asset.h b/Libraries/LibAsset/Asset.h index 3aeeb763..c8bd3e86 100644 --- a/Libraries/LibAsset/Asset.h +++ b/Libraries/LibAsset/Asset.h @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -16,7 +17,7 @@ class AssetManager; class Asset : public Core::RefCounted { public: Asset() = default; - Asset(AssetId const& handle) + explicit Asset(AssetId const& handle) : m_id(handle) { } @@ -26,7 +27,7 @@ class Asset : public Core::RefCounted { return m_id.is_valid(); } - AssetId const& id() + AssetId const& id() const { return m_id; } @@ -35,7 +36,6 @@ class Asset : public Core::RefCounted { private: AssetId m_id; - AssetTypeId m_type_id; friend class AssetManager; }; diff --git a/Libraries/LibAsset/AssetHandle.h b/Libraries/LibAsset/AssetHandle.h index 175bd0d0..7f8f6425 100644 --- a/Libraries/LibAsset/AssetHandle.h +++ b/Libraries/LibAsset/AssetHandle.h @@ -5,6 +5,7 @@ #include #include #include +#include #include namespace Terran::Asset { @@ -20,7 +21,7 @@ class AssetHandle : public Core::RefCounted { } bool is_valid() const; - constexpr operator bool() const { + explicit(false) constexpr operator bool() const { return is_valid(); } diff --git a/Libraries/LibAsset/AssetManager.h b/Libraries/LibAsset/AssetManager.h index efe96053..7e8081f4 100644 --- a/Libraries/LibAsset/AssetManager.h +++ b/Libraries/LibAsset/AssetManager.h @@ -1,12 +1,12 @@ #pragma once #include "Asset.h" +#include "AssetHandle.h" #include "AssetImporter.h" #include "AssetImporterRegistry.h" #include "AssetMetadata.h" #include "AssetMetadataRegistry.h" #include "AssetTypes.h" -#include "AssetHandle.h" #include #include @@ -55,7 +55,8 @@ class AssetManager final { StrongAssetHandle import_asset(std::filesystem::path const& asset_path) const; void reload_asset_by_id(AssetId const& asset_id); - void reload_asset_by_handle(StrongAssetHandle const& asset_handle) { + void reload_asset_by_handle(StrongAssetHandle const& asset_handle) + { reload_asset_by_id(asset_handle->id()); } @@ -108,8 +109,7 @@ class AssetManager final { template requires( - std::is_base_of_v, - HasStaticType) + std::is_base_of_v && HasStaticType) void save_asset(Core::RefPtr asset) { if (!AssetMetadataRegistry::contains(asset->id())) { @@ -153,9 +153,8 @@ class AssetManager final { template requires( - std::is_base_of_v, - HasStaticType) - AssetMetadata create_asset_metadata(AssetId const& asset_id, std::filesystem::path const& file_path) + std::is_base_of_v && HasStaticType) + AssetMetadata create_asset_metadata(AssetId const& asset_id, std::filesystem::path const& file_path) const { AssetMetadata metadata; metadata.AssetId = asset_id; @@ -166,7 +165,8 @@ class AssetManager final { Core::Result remove_asset(Core::UUID const& handle, RemoveAssetImmediately remove_immediately = RemoveAssetImmediately::Yes, RemoveAssetMetadata remove_metadata = RemoveAssetMetadata::Yes); - constexpr bool is_asset_loaded(AssetId const& id) const { + constexpr bool is_asset_loaded(AssetId const& id) const + { return m_loaded_assets.contains(id); } From a91ec260671ca69e2598d98b947f31349c7540fb Mon Sep 17 00:00:00 2001 From: infirit89 Date: Wed, 4 Mar 2026 19:44:50 +0200 Subject: [PATCH 51/51] chore(Scene): address sonarqube errors --- Libraries/LibScene/SceneSerializer.cpp | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/Libraries/LibScene/SceneSerializer.cpp b/Libraries/LibScene/SceneSerializer.cpp index 116b3d13..e1dcae04 100644 --- a/Libraries/LibScene/SceneSerializer.cpp +++ b/Libraries/LibScene/SceneSerializer.cpp @@ -149,17 +149,20 @@ static Entity deserialize_entity(YAML::Node const& data, YAML::Node const& scene for (auto child_id_node : relationship_component_node["Children"]) { Core::UUID deserialized_child_id = child_id_node.as(); Entity child = deserializedScene->find_entity(deserialized_child_id); - if (!child) { - YAML::const_iterator child_node = find_entity_node(scene, deserialized_child_id); - if (child_node == scene.end()) { - TR_WARN(TR_LOG_SCENE, "Entity {} references child entity with Id: {} could not be found! Child is skipped!", id, deserialized_child_id); - } else { - child = deserialize_entity(*child_node, scene, deserializedScene); - } - } - if (child) + if (child) { child.set_parent(deserialized_entity); + continue; + } + + YAML::const_iterator child_node = find_entity_node(scene, deserialized_child_id); + if (child_node == scene.end()) { + TR_WARN(TR_LOG_SCENE, "Entity {} references child entity with Id: {} could not be found! Child is skipped!", id, deserialized_child_id); + } else { + child = deserialize_entity(*child_node, scene, deserializedScene); + } + + child.set_parent(deserialized_entity); } }