From 6a85d178993d62ac011d618092133f403c00cfb5 Mon Sep 17 00:00:00 2001 From: Bram Verhulst Date: Sun, 1 Nov 2026 23:12:00 +0100 Subject: [PATCH] feat: add new fancy reflection --- .../include/destrum/Components/AudioSource.h | 1 + .../destrum/Components/CameraComponent.h | 3 +- .../destrum/Components/LightComponent.h | 4 +- .../include/destrum/Components/OrbitAndSpin.h | 4 +- .../destrum/Components/Physics/BoxCollider.h | 5 +- .../Components/Physics/CapsuleCollider.h | 19 +-- .../destrum/Components/Physics/Collider.h | 14 +-- .../Components/Physics/ConvexHullCollider.h | 4 +- .../Components/Physics/CylinderCollider.h | 19 +-- .../destrum/Components/Physics/MeshCollider.h | 8 -- .../destrum/Components/Physics/Rigidbody.h | 3 +- .../Components/Physics/SphereCollider.h | 4 +- destrum/include/destrum/Components/Rotator.h | 4 +- destrum/include/destrum/Components/Spinner.h | 4 +- .../include/destrum/Components/UI/Canvas.h | 4 +- .../destrum/Components/UI/RectTransform.h | 3 +- .../include/destrum/Components/UI/UIImage.h | 4 +- .../include/destrum/Components/UI/UIText.h | 4 +- .../include/destrum/Serialization/Reflect.h | 44 +++++-- destrum/src/Components/AudioSource.cpp | 67 ++++------- destrum/src/Components/CameraComponent.cpp | 32 +---- destrum/src/Components/LightComponent.cpp | 101 +++------------- destrum/src/Components/OrbitAndSpin.cpp | 112 ++++------------- .../src/Components/Physics/BoxCollider.cpp | 27 +---- .../Components/Physics/CapsuleCollider.cpp | 18 +-- destrum/src/Components/Physics/Collider.cpp | 17 +-- .../Components/Physics/ConvexHullCollider.cpp | 26 +--- .../Components/Physics/CylinderCollider.cpp | 18 +-- destrum/src/Components/Physics/Rigidbody.cpp | 113 +++--------------- .../src/Components/Physics/SphereCollider.cpp | 25 +--- destrum/src/Components/Rotator.cpp | 58 ++------- destrum/src/Components/Spinner.cpp | 37 +----- destrum/src/Components/UI/Canvas.cpp | 43 ++----- destrum/src/Components/UI/RectTransform.cpp | 31 ++--- destrum/src/Components/UI/UIImage.cpp | 54 ++------- destrum/src/Components/UI/UIText.cpp | 75 ++---------- destrum/src/Serialization/Reflect.cpp | 61 ++++++++-- lightkeeper/include/components/Checkpoint.h | 4 +- lightkeeper/include/components/CrosshairUI.h | 5 +- .../components/FirstPersonController.h | 2 +- lightkeeper/include/components/LightToggle.h | 5 +- .../include/components/MovingPlatform.h | 3 +- .../include/components/PlayerRespawn.h | 1 + .../include/components/PrintComponent.h | 4 +- .../components/TriggerLoggerComponent.h | 3 +- lightkeeper/include/components/WinGoal.h | 4 +- lightkeeper/include/components/WinScreenUI.h | 5 +- lightkeeper/src/components/Checkpoint.cpp | 18 +-- lightkeeper/src/components/CrosshairUI.cpp | 57 ++------- .../src/components/FirstPersonController.cpp | 79 ++++-------- lightkeeper/src/components/LightToggle.cpp | 18 +-- lightkeeper/src/components/MovingPlatform.cpp | 26 ++-- lightkeeper/src/components/PlayerRespawn.cpp | 17 ++- lightkeeper/src/components/PrintComponent.cpp | 19 +-- .../src/components/TriggerLoggerComponent.cpp | 19 +-- lightkeeper/src/components/WinGoal.cpp | 16 +-- lightkeeper/src/components/WinScreenUI.cpp | 35 +----- tests/serialization_tests.cpp | 25 ++++ 58 files changed, 380 insertions(+), 1055 deletions(-) diff --git a/destrum/include/destrum/Components/AudioSource.h b/destrum/include/destrum/Components/AudioSource.h index 6be01de..cc1d1a9 100644 --- a/destrum/include/destrum/Components/AudioSource.h +++ b/destrum/include/destrum/Components/AudioSource.h @@ -27,6 +27,7 @@ public: std::string GetTypeName() const override { return "AudioSource"; } + void Reflect(FieldVisitor& v) override; nlohmann::json Serialize() const override; void Deserialize(const nlohmann::json& data) override; void ImGuiInspector() override; diff --git a/destrum/include/destrum/Components/CameraComponent.h b/destrum/include/destrum/Components/CameraComponent.h index 5d631df..f3c86de 100644 --- a/destrum/include/destrum/Components/CameraComponent.h +++ b/destrum/include/destrum/Components/CameraComponent.h @@ -17,8 +17,7 @@ public: std::string GetTypeName() const override { return "CameraComponent"; } - nlohmann::json Serialize() const override; - void Deserialize(const nlohmann::json& data) override; + void Reflect(FieldVisitor& v) override; void ImGuiInspector() override; // Places the camera at this object's world transform, looking down its local -Z. diff --git a/destrum/include/destrum/Components/LightComponent.h b/destrum/include/destrum/Components/LightComponent.h index d3d8eb3..0ec544b 100644 --- a/destrum/include/destrum/Components/LightComponent.h +++ b/destrum/include/destrum/Components/LightComponent.h @@ -19,9 +19,7 @@ public: std::string GetTypeName() const override { return "LightComponent"; } - nlohmann::json Serialize() const override; - void Deserialize(const nlohmann::json& data) override; - void ImGuiInspector() override; + void Reflect(FieldVisitor& v) override; void SetType(LightType type) { m_Type = type; } [[nodiscard]] LightType GetType() const { return m_Type; } diff --git a/destrum/include/destrum/Components/OrbitAndSpin.h b/destrum/include/destrum/Components/OrbitAndSpin.h index f356cd7..6ac0b3f 100644 --- a/destrum/include/destrum/Components/OrbitAndSpin.h +++ b/destrum/include/destrum/Components/OrbitAndSpin.h @@ -26,9 +26,10 @@ public: std::string GetTypeName() const override { return "OrbitAndSpin"; } - nlohmann::json Serialize() const override; + void Reflect(FieldVisitor& v) override; void Deserialize(const nlohmann::json& data) override; void ImGuiInspector() override; + void OnFieldsEdited() override; // optional setters void SetRadius(float r) { m_Radius = r; } @@ -36,6 +37,7 @@ public: private: void BuildOrbitBasis(); // builds m_U/m_V from m_OrbitAxis + void Sanitize(); float m_Radius = 5.0f; glm::vec3 m_Center{0.0f}; diff --git a/destrum/include/destrum/Components/Physics/BoxCollider.h b/destrum/include/destrum/Components/Physics/BoxCollider.h index 17fb79a..07f1863 100644 --- a/destrum/include/destrum/Components/Physics/BoxCollider.h +++ b/destrum/include/destrum/Components/Physics/BoxCollider.h @@ -11,16 +11,13 @@ public: void Update(float) override {} - nlohmann::json Serialize() const override; - void Deserialize(const nlohmann::json& data) override; - void ImGuiInspector() override; + void Reflect(FieldVisitor& v) override; std::string GetTypeName() const override { return "BoxCollider"; } - void DrawDebugLines(const glm::vec4& color) const override; [[nodiscard]] PhysicsShapeDesc BuildPhysicsShape() const override { diff --git a/destrum/include/destrum/Components/Physics/CapsuleCollider.h b/destrum/include/destrum/Components/Physics/CapsuleCollider.h index 03ea203..9505d2e 100644 --- a/destrum/include/destrum/Components/Physics/CapsuleCollider.h +++ b/destrum/include/destrum/Components/Physics/CapsuleCollider.h @@ -17,24 +17,7 @@ public: return "CapsuleCollider"; } - void ImGuiInspector() override; - - nlohmann::json Serialize() const override { - auto data = SerializeCollider(); - data["radius"] = m_Radius; - data["height"] = m_Height; - return data; - } - - void Deserialize(const nlohmann::json& data) override { - DeserializeCollider(data); - if (data.contains("radius")) { - SetRadius(data.at("radius").get()); - } - if (data.contains("height")) { - SetHeight(data.at("height").get()); - } - } + void Reflect(FieldVisitor& v) override; void DrawDebugLines(const glm::vec4& color) const override; diff --git a/destrum/include/destrum/Components/Physics/Collider.h b/destrum/include/destrum/Components/Physics/Collider.h index a7a5b7d..f61dceb 100644 --- a/destrum/include/destrum/Components/Physics/Collider.h +++ b/destrum/include/destrum/Components/Physics/Collider.h @@ -16,7 +16,7 @@ public: [[nodiscard]] bool AffectsPhysics() const override { return true; } - void ImGuiInspector() override; + void Reflect(FieldVisitor& v) override; [[nodiscard]] virtual PhysicsShapeDesc BuildPhysicsShape() const = 0; virtual void DrawDebugLines(const glm::vec4& color) const = 0; @@ -30,18 +30,6 @@ public: void SetCenterOffset(const glm::vec3& offset) { m_CenterOffset = offset; NotifyPhysicsChanged(); } protected: - [[nodiscard]] nlohmann::json SerializeCollider() const { - return { - {"centerOffset", m_CenterOffset}, - {"isTrigger", m_IsTrigger} - }; - } - - void DeserializeCollider(const nlohmann::json& data) { - m_CenterOffset = data.value("centerOffset", m_CenterOffset); - m_IsTrigger = data.value("isTrigger", m_IsTrigger); - } - bool m_IsTrigger{false}; glm::vec3 m_CenterOffset{0.0f}; }; diff --git a/destrum/include/destrum/Components/Physics/ConvexHullCollider.h b/destrum/include/destrum/Components/Physics/ConvexHullCollider.h index e3f0377..b208326 100644 --- a/destrum/include/destrum/Components/Physics/ConvexHullCollider.h +++ b/destrum/include/destrum/Components/Physics/ConvexHullCollider.h @@ -17,9 +17,7 @@ public: return "ConvexHullCollider"; } - nlohmann::json Serialize() const override; - void Deserialize(const nlohmann::json& data) override; - void ImGuiInspector() override; + void Reflect(FieldVisitor& v) override; void DrawDebugLines(const glm::vec4& color) const override; diff --git a/destrum/include/destrum/Components/Physics/CylinderCollider.h b/destrum/include/destrum/Components/Physics/CylinderCollider.h index 2c942af..5756111 100644 --- a/destrum/include/destrum/Components/Physics/CylinderCollider.h +++ b/destrum/include/destrum/Components/Physics/CylinderCollider.h @@ -17,24 +17,7 @@ public: return "CylinderCollider"; } - void ImGuiInspector() override; - - nlohmann::json Serialize() const override { - auto data = SerializeCollider(); - data["radius"] = m_Radius; - data["height"] = m_Height; - return data; - } - - void Deserialize(const nlohmann::json& data) override { - DeserializeCollider(data); - if (data.contains("radius")) { - SetRadius(data.at("radius").get()); - } - if (data.contains("height")) { - SetHeight(data.at("height").get()); - } - } + void Reflect(FieldVisitor& v) override; void DrawDebugLines(const glm::vec4& color) const override; diff --git a/destrum/include/destrum/Components/Physics/MeshCollider.h b/destrum/include/destrum/Components/Physics/MeshCollider.h index e0147fd..d8c684e 100644 --- a/destrum/include/destrum/Components/Physics/MeshCollider.h +++ b/destrum/include/destrum/Components/Physics/MeshCollider.h @@ -14,14 +14,6 @@ public: return "MeshCollider"; } - nlohmann::json Serialize() const override { - return SerializeCollider(); - } - - void Deserialize(const nlohmann::json& data) override { - DeserializeCollider(data); - } - void ImGuiInspector() override; void DrawDebugLines(const glm::vec4& color) const override; diff --git a/destrum/include/destrum/Components/Physics/Rigidbody.h b/destrum/include/destrum/Components/Physics/Rigidbody.h index 5d63d6a..e84ba8b 100644 --- a/destrum/include/destrum/Components/Physics/Rigidbody.h +++ b/destrum/include/destrum/Components/Physics/Rigidbody.h @@ -21,8 +21,7 @@ public: void Start() override; void Destroy() override; - nlohmann::json Serialize() const override; - void Deserialize(const nlohmann::json& data) override; + void Reflect(FieldVisitor& v) override; void ImGuiInspector() override; std::string GetTypeName() const override diff --git a/destrum/include/destrum/Components/Physics/SphereCollider.h b/destrum/include/destrum/Components/Physics/SphereCollider.h index 27c6a78..55096fd 100644 --- a/destrum/include/destrum/Components/Physics/SphereCollider.h +++ b/destrum/include/destrum/Components/Physics/SphereCollider.h @@ -12,9 +12,7 @@ public: void Update(float) override {} - nlohmann::json Serialize() const override; - void Deserialize(const nlohmann::json& data) override; - void ImGuiInspector() override; + void Reflect(FieldVisitor& v) override; std::string GetTypeName() const override { return "SphereCollider"; diff --git a/destrum/include/destrum/Components/Rotator.h b/destrum/include/destrum/Components/Rotator.h index 2662e19..8919552 100644 --- a/destrum/include/destrum/Components/Rotator.h +++ b/destrum/include/destrum/Components/Rotator.h @@ -20,9 +20,7 @@ public: std::string GetTypeName() const override { return "Rotator"; } - nlohmann::json Serialize() const override; - void Deserialize(const nlohmann::json& data) override; - void ImGuiInspector() override; + void Reflect(FieldVisitor& v) override; void SetDistance(float distance); void SetSpeed(float speed) { m_Speed = speed; } diff --git a/destrum/include/destrum/Components/Spinner.h b/destrum/include/destrum/Components/Spinner.h index 02aac1d..6dc257f 100644 --- a/destrum/include/destrum/Components/Spinner.h +++ b/destrum/include/destrum/Components/Spinner.h @@ -18,9 +18,7 @@ public: std::string GetTypeName() const override { return "Spinner"; } - nlohmann::json Serialize() const override; - void Deserialize(const nlohmann::json& data) override; - void ImGuiInspector() override; + void Reflect(FieldVisitor& v) override; void SetAxis(const glm::vec3& axis) { m_Axis = glm::dot(axis, axis) > 0.000001f diff --git a/destrum/include/destrum/Components/UI/Canvas.h b/destrum/include/destrum/Components/UI/Canvas.h index c20713b..e73cb0f 100644 --- a/destrum/include/destrum/Components/UI/Canvas.h +++ b/destrum/include/destrum/Components/UI/Canvas.h @@ -23,9 +23,7 @@ public: void Render(const RenderContext& ctx) override; void Draw(const glm::vec2& screenSize); - nlohmann::json Serialize() const override; - void Deserialize(const nlohmann::json& data) override; - void ImGuiInspector() override; + void Reflect(FieldVisitor& v) override; [[nodiscard]] float GetScale(const glm::vec2& screenSize) const; diff --git a/destrum/include/destrum/Components/UI/RectTransform.h b/destrum/include/destrum/Components/UI/RectTransform.h index 1f6c61e..1201273 100644 --- a/destrum/include/destrum/Components/UI/RectTransform.h +++ b/destrum/include/destrum/Components/UI/RectTransform.h @@ -22,8 +22,7 @@ public: std::string GetTypeName() const override { return "RectTransform"; } - nlohmann::json Serialize() const override; - void Deserialize(const nlohmann::json& data) override; + void Reflect(FieldVisitor& v) override; void ImGuiInspector() override; [[nodiscard]] UIRect ComputeRect(const UIRect& parentRect, float scale) const; diff --git a/destrum/include/destrum/Components/UI/UIImage.h b/destrum/include/destrum/Components/UI/UIImage.h index f0d6415..1d355f0 100644 --- a/destrum/include/destrum/Components/UI/UIImage.h +++ b/destrum/include/destrum/Components/UI/UIImage.h @@ -20,9 +20,7 @@ public: void DrawUI(const UIRect& rect, float scale) override; - nlohmann::json Serialize() const override; - void Deserialize(const nlohmann::json& data) override; - void ImGuiInspector() override; + void Reflect(FieldVisitor& v) override; void SetShape(UIImageShape shape) { m_Shape = shape; } void SetOutline(float thickness) { m_Outline = thickness; } diff --git a/destrum/include/destrum/Components/UI/UIText.h b/destrum/include/destrum/Components/UI/UIText.h index ab0d7a2..3a13a05 100644 --- a/destrum/include/destrum/Components/UI/UIText.h +++ b/destrum/include/destrum/Components/UI/UIText.h @@ -21,9 +21,7 @@ public: void DrawUI(const UIRect& rect, float scale) override; - nlohmann::json Serialize() const override; - void Deserialize(const nlohmann::json& data) override; - void ImGuiInspector() override; + void Reflect(FieldVisitor& v) override; void SetText(std::string text) { m_Text = std::move(text); } void SetFont(std::string assetPath) { m_FontPath = std::move(assetPath); } diff --git a/destrum/include/destrum/Serialization/Reflect.h b/destrum/include/destrum/Serialization/Reflect.h index 51f1020..a337897 100644 --- a/destrum/include/destrum/Serialization/Reflect.h +++ b/destrum/include/destrum/Serialization/Reflect.h @@ -29,10 +29,14 @@ struct FieldOptions { std::optional max; const char* format = nullptr; bool color = false; + bool angle = false; // radians, shown in degrees + bool multiline = false; + bool acceptAssets = false; // strings take a virtual path dropped from the asset browser bool readOnly = false; - bool hidden = false; - bool transient = false; + bool hidden = false; // saved, not shown + bool transient = false; // shown, not saved const char* label = nullptr; + const char* tooltip = nullptr; std::span enumNames; std::function filter; }; @@ -66,6 +70,7 @@ namespace reflect_detail { } bool DrawScalar(const char* label, Scalar type, void* value, int components, const void* min, const void* max, const FieldOptions& options); + bool DrawAngle(const char* label, float& radians, const FieldOptions& options); bool DrawEnum(const char* label, std::int64_t& value, const FieldOptions& options); void BeginList(const char* label); @@ -77,6 +82,9 @@ namespace reflect_detail { template concept Numeric = std::is_arithmetic_v && !std::same_as; + + template + concept FloatVec = std::same_as || std::same_as || std::same_as; } bool DrawField(const char* label, bool& value, const FieldOptions& options, const Scene* scene); @@ -90,6 +98,11 @@ bool DrawField(const char* label, std::vector& value, const FieldOpti template bool DrawField(const char* label, T& value, const FieldOptions& options, const Scene*) { + if constexpr (std::same_as) { + if (options.angle) { + return reflect_detail::DrawAngle(label, value, options); + } + } const T min = options.min ? static_cast(*options.min) : T{}; const T max = options.max ? static_cast(*options.max) : T{}; return reflect_detail::DrawScalar(label, reflect_detail::ScalarOf(), &value, 1, @@ -181,6 +194,9 @@ struct FieldCodec { if constexpr (reflect_detail::Numeric) { if (options.min && typed < static_cast(*options.min)) typed = static_cast(*options.min); if (options.max && typed > static_cast(*options.max)) typed = static_cast(*options.max); + } else if constexpr (reflect_detail::FloatVec) { + if (options.min) typed = glm::max(typed, T{static_cast(*options.min)}); + if (options.max) typed = glm::min(typed, T{static_cast(*options.max)}); } } @@ -203,14 +219,26 @@ class FieldVisitor { public: virtual ~FieldVisitor() = default; + using Commit = std::function; + template void Field(std::string_view key, T& value, const FieldOptions& options = {}) { - Visit(key, &value, options, FieldCodec::Ops); + Visit(key, &value, options, FieldCodec::Ops, {}); + } + + // Loads and edits go through the setter, so it can validate and react + template Setter> + void Property(std::string_view key, const T& value, Setter&& setter, const FieldOptions& options = {}) + { + T copy = value; + Visit(key, ©, options, FieldCodec::Ops, [&setter](void* changed) { + setter(*static_cast(changed)); + }); } protected: - virtual void Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops) = 0; + virtual void Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops, const Commit& commit) = 0; }; class JsonWriter final : public FieldVisitor { @@ -218,7 +246,7 @@ public: [[nodiscard]] nlohmann::json& Result() { return m_Result; } protected: - void Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops) override; + void Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops, const Commit& commit) override; private: nlohmann::json m_Result = nlohmann::json::object(); @@ -229,7 +257,7 @@ public: explicit JsonReader(const nlohmann::json& data) : m_Data(data) {} protected: - void Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops) override; + void Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops, const Commit& commit) override; private: const nlohmann::json& m_Data; @@ -240,7 +268,7 @@ public: ReferenceResolver(const Component& owner, const ObjectRef::ObjectMap& objects) : m_Owner(owner), m_Objects(objects) {} protected: - void Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops) override; + void Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops, const Commit& commit) override; private: const Component& m_Owner; @@ -254,7 +282,7 @@ public: [[nodiscard]] bool Changed() const { return m_Changed; } protected: - void Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops) override; + void Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops, const Commit& commit) override; private: const Scene* m_Scene; diff --git a/destrum/src/Components/AudioSource.cpp b/destrum/src/Components/AudioSource.cpp index 17a77a3..1d50ef9 100644 --- a/destrum/src/Components/AudioSource.cpp +++ b/destrum/src/Components/AudioSource.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -158,18 +159,25 @@ void AudioSource::SetDistances(float minDistance, float maxDistance) ApplyProperties(); } +void AudioSource::Reflect(FieldVisitor& v) +{ + v.Property("volume", m_Volume, [this](float volume) { SetVolume(volume); }, {.speed = 0.01f, .min = 0.0, .max = 2.0}); + v.Property("pitch", m_Pitch, [this](float pitch) { SetPitch(pitch); }, {.speed = 0.01f, .min = 0.1, .max = 3.0}); + v.Property("loop", m_Loop, [this](bool loop) { SetLoop(loop); }); + v.Field("playOnStart", m_PlayOnStart); + v.Property("spatial", m_Spatial, [this](bool spatial) { SetSpatial(spatial); }); + v.Property("minDistance", m_MinDistance, [this](float minDistance) { SetDistances(minDistance, m_MaxDistance); }, + {.speed = 0.05f, .min = 0.01, .format = "%.2f m", .hidden = !m_Spatial, .tooltip = "Full volume up to this distance"}); + v.Property("maxDistance", m_MaxDistance, [this](float maxDistance) { SetDistances(m_MinDistance, maxDistance); }, + {.speed = 0.1f, .min = 0.01, .format = "%.2f m", .hidden = !m_Spatial, + .tooltip = "The sound stops getting quieter past this distance"}); +} + nlohmann::json AudioSource::Serialize() const { - return { - {"clip", m_ClipPath.empty() ? nlohmann::json(nullptr) : AssetDatabase::GetInstance().WriteReference(m_ClipPath)}, - {"volume", m_Volume}, - {"pitch", m_Pitch}, - {"loop", m_Loop}, - {"playOnStart", m_PlayOnStart}, - {"spatial", m_Spatial}, - {"minDistance", m_MinDistance}, - {"maxDistance", m_MaxDistance}, - }; + nlohmann::json data = Component::Serialize(); + data["clip"] = m_ClipPath.empty() ? nlohmann::json(nullptr) : AssetDatabase::GetInstance().WriteReference(m_ClipPath); + return data; } void AudioSource::Deserialize(const nlohmann::json& data) @@ -183,13 +191,7 @@ void AudioSource::Deserialize(const nlohmann::json& data) SetClip({}); } } - - m_Loop = data.value("loop", m_Loop); - m_PlayOnStart = data.value("playOnStart", m_PlayOnStart); - m_Spatial = data.value("spatial", m_Spatial); - SetVolume(data.value("volume", m_Volume)); - SetPitch(data.value("pitch", m_Pitch)); - SetDistances(data.value("minDistance", m_MinDistance), data.value("maxDistance", m_MaxDistance)); + Component::Deserialize(data); } void AudioSource::ImGuiInspector() @@ -218,36 +220,7 @@ void AudioSource::ImGuiInspector() m_Clip->GetSampleRate(), m_Clip->GetDuration()); } - float volume = m_Volume; - if (ImGui::SliderFloat("Volume", &volume, 0.0f, 2.0f)) { - SetVolume(volume); - } - float pitch = m_Pitch; - if (ImGui::SliderFloat("Pitch", &pitch, 0.1f, 3.0f)) { - SetPitch(pitch); - } - - bool loop = m_Loop; - if (ImGui::Checkbox("Loop", &loop)) { - SetLoop(loop); - } - ImGui::Checkbox("Play On Start", &m_PlayOnStart); - bool spatial = m_Spatial; - if (ImGui::Checkbox("Spatial", &spatial)) { - SetSpatial(spatial); - } - - if (m_Spatial) { - float minDistance = m_MinDistance; - float maxDistance = m_MaxDistance; - bool changed = ImGui::DragFloat("Min Distance", &minDistance, 0.05f, 0.01f, 1000.0f, "%.2f m"); - ImGui::SetItemTooltip("Full volume up to this distance"); - changed |= ImGui::DragFloat("Max Distance", &maxDistance, 0.1f, 0.01f, 10000.0f, "%.2f m"); - ImGui::SetItemTooltip("The sound stops getting quieter past this distance"); - if (changed) { - SetDistances(minDistance, maxDistance); - } - } + Component::ImGuiInspector(); ImGui::BeginDisabled(m_ClipPath.empty()); if (ImGui::Button(IsPlaying() ? "Restart" : "Play")) { diff --git a/destrum/src/Components/CameraComponent.cpp b/destrum/src/Components/CameraComponent.cpp index 2c60805..d7199ae 100644 --- a/destrum/src/Components/CameraComponent.cpp +++ b/destrum/src/Components/CameraComponent.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -54,19 +55,10 @@ bool CameraComponent::IsCurrent() const return scene != nullptr && scene->GetCurrentCamera() == this; } -nlohmann::json CameraComponent::Serialize() const { - return { - {"fieldOfView", m_FieldOfView}, - {"zNear", m_ZNear}, - {"zFar", m_ZFar} - }; -} - -void CameraComponent::Deserialize(const nlohmann::json& data) { - if (data.contains("fieldOfView")) { - SetFieldOfView(data.at("fieldOfView").get()); - } - SetClipPlanes(data.value("zNear", m_ZNear), data.value("zFar", m_ZFar)); +void CameraComponent::Reflect(FieldVisitor& v) { + v.Property("fieldOfView", m_FieldOfView, [this](float fov) { SetFieldOfView(fov); }, {.speed = 0.5f, .min = 1.0, .max = 179.0}); + v.Property("zNear", m_ZNear, [this](float zNear) { SetClipPlanes(zNear, m_ZFar); }, {.speed = 0.01f, .min = 0.0001, .label = "Near"}); + v.Property("zFar", m_ZFar, [this](float zFar) { SetClipPlanes(m_ZNear, zFar); }, {.speed = 1.0f, .min = 0.1, .label = "Far"}); } void CameraComponent::ImGuiInspector() { @@ -74,17 +66,5 @@ void CameraComponent::ImGuiInspector() { if (ImGui::Checkbox("Current Camera", ¤t) && current) { MakeCurrent(); } - - float fieldOfView = m_FieldOfView; - if (ImGui::DragFloat("Field Of View", &fieldOfView, 0.5f, 1.0f, 179.0f)) { - SetFieldOfView(fieldOfView); - } - - float zNear = m_ZNear; - float zFar = m_ZFar; - const bool nearChanged = ImGui::DragFloat("Near", &zNear, 0.01f, 0.0001f, 100.0f); - const bool farChanged = ImGui::DragFloat("Far", &zFar, 1.0f, 0.1f, 100000.0f); - if (nearChanged || farChanged) { - SetClipPlanes(zNear, zFar); - } + Component::ImGuiInspector(); } diff --git a/destrum/src/Components/LightComponent.cpp b/destrum/src/Components/LightComponent.cpp index 0e23fc8..3962f73 100644 --- a/destrum/src/Components/LightComponent.cpp +++ b/destrum/src/Components/LightComponent.cpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include @@ -11,8 +11,6 @@ #include #include -#include - LightComponent::LightComponent(GameObject& parent, LightType type) : Component(parent, "LightComponent") , m_Type(type) @@ -52,86 +50,19 @@ void LightComponent::Render(const RenderContext& ctx) }); } -nlohmann::json LightComponent::Serialize() const { - return { - {"type", static_cast(m_Type)}, - {"color", m_Color}, - {"intensity", m_Intensity}, - {"range", m_Range}, - {"innerConeAngle", m_InnerConeAngle}, - {"outerConeAngle", m_OuterConeAngle}, - {"castShadows", m_CastShadows} - }; -} - -void LightComponent::Deserialize(const nlohmann::json& data) { - if (data.contains("type")) { - const auto type = data.at("type").get(); - if (type <= static_cast(LightType::Spot)) { - m_Type = static_cast(type); - } - } - m_Color = data.value("color", m_Color); - if (data.contains("intensity")) { - SetIntensity(data.at("intensity").get()); - } - if (data.contains("range")) { - SetRange(data.at("range").get()); - } - - float innerConeAngle = m_InnerConeAngle; - float outerConeAngle = m_OuterConeAngle; - if (data.contains("innerConeAngle")) { - innerConeAngle = data.at("innerConeAngle").get(); - } - if (data.contains("outerConeAngle")) { - outerConeAngle = data.at("outerConeAngle").get(); - } - SetConeAngles(innerConeAngle, outerConeAngle); - - if (data.contains("castShadows")) { - m_CastShadows = data.at("castShadows").get(); - } -} - -void LightComponent::ImGuiInspector() { - constexpr std::array typeNames{"Directional", "Point", "Spot"}; - - int type = static_cast(m_Type); - if (ImGui::Combo("Type", &type, typeNames.data(), static_cast(typeNames.size()))) { - SetType(static_cast(type)); - } - - ImGui::ColorEdit3("Color", &m_Color.x); - - float intensity = m_Intensity; - if (ImGui::DragFloat("Intensity", &intensity, 0.05f, 0.0f)) { - SetIntensity(intensity); - } - - if (m_Type == LightType::Directional) { - ImGui::Checkbox("Cast Shadows", &m_CastShadows); - ImGui::SameLine(); - ImGui::TextDisabled("(first directional light only)"); - return; - } - - if (m_Type == LightType::Spot) { - ImGui::Checkbox("Cast Shadows", &m_CastShadows); - } - - float range = m_Range; - if (ImGui::DragFloat("Range", &range, 0.05f, 0.01f)) { - SetRange(range); - } - - if (m_Type == LightType::Spot) { - float innerConeAngle = m_InnerConeAngle; - float outerConeAngle = m_OuterConeAngle; - bool changed = ImGui::SliderAngle("Inner Cone", &innerConeAngle, 0.0f, 90.0f); - changed |= ImGui::SliderAngle("Outer Cone", &outerConeAngle, 0.0f, 90.0f); - if (changed) { - SetConeAngles(innerConeAngle, outerConeAngle); - } - } +void LightComponent::Reflect(FieldVisitor& v) { + static constexpr std::array TypeNames{"Directional", "Point", "Spot"}; + + const bool directional = m_Type == LightType::Directional; + const bool spot = m_Type == LightType::Spot; + v.Field("type", m_Type, {.enumNames = TypeNames}); + v.Field("color", m_Color, {.color = true}); + v.Property("intensity", m_Intensity, [this](float intensity) { SetIntensity(intensity); }, {.speed = 0.05f, .min = 0.0}); + v.Field("castShadows", m_CastShadows, {.hidden = !directional && !spot, + .tooltip = directional ? "Only the first directional light casts shadows" : nullptr}); + v.Property("range", m_Range, [this](float range) { SetRange(range); }, {.speed = 0.05f, .min = 0.01, .hidden = directional}); + v.Property("innerConeAngle", m_InnerConeAngle, [this](float angle) { SetConeAngles(angle, m_OuterConeAngle); }, + {.min = 0.0, .max = glm::half_pi(), .angle = true, .hidden = !spot, .label = "Inner Cone"}); + v.Property("outerConeAngle", m_OuterConeAngle, [this](float angle) { SetConeAngles(m_InnerConeAngle, angle); }, + {.min = 0.0, .max = glm::half_pi(), .angle = true, .hidden = !spot, .label = "Outer Cone"}); } diff --git a/destrum/src/Components/OrbitAndSpin.cpp b/destrum/src/Components/OrbitAndSpin.cpp index 8a51d4d..1a48b6e 100644 --- a/destrum/src/Components/OrbitAndSpin.cpp +++ b/destrum/src/Components/OrbitAndSpin.cpp @@ -7,10 +7,9 @@ #include "destrum/ObjectModel/GameObject.h" #include "destrum/ObjectModel/Transform.h" #include "destrum/Components/MeshRendererComponent.h" -#include "destrum/Serialization/GlmJson.h" +#include "destrum/Serialization/Reflect.h" #include "destrum/Util/GameState.h" #include "destrum/Util/DeltaTime.h" -#include "destrum/Util/ImGuiUtils.h" #include @@ -126,40 +125,33 @@ void OrbitAndSpin::Start() { } -nlohmann::json OrbitAndSpin::Serialize() const { - return { - {"radius", m_Radius}, - {"center", m_Center}, - {"orbitAxis", m_OrbitAxis}, - {"orbitSpeed", m_OrbitSpeed}, - {"orbitAngle", m_OrbitAngle}, - {"orbitPhase", m_OrbitPhase}, - {"growPhase", m_GrowPhase}, - {"growSpeed", m_GrowSpeed}, - {"growMin", m_GrowMin}, - {"growMax", m_GrowMax}, - {"spinAxis", m_SpinAxis}, - {"spinSpeed", m_SpinSpeed}, - {"baseScale", m_BaseScale} - }; +void OrbitAndSpin::Reflect(FieldVisitor& v) { + v.Field("radius", m_Radius, {.speed = 0.05f, .min = 0.0}); + v.Field("center", m_Center); + v.Field("orbitAxis", m_OrbitAxis); + v.Field("orbitSpeed", m_OrbitSpeed, {.speed = 0.05f}); + v.Field("orbitPhase", m_OrbitPhase, {.speed = 0.05f}); + v.Field("orbitAngle", m_OrbitAngle, {.format = "%.2f rad", .readOnly = true}); + v.Field("growSpeed", m_GrowSpeed, {.speed = 0.05f, .min = 0.0}); + v.Field("growMin", m_GrowMin, {.speed = 0.01f, .min = 0.0}); + v.Field("growMax", m_GrowMax, {.speed = 0.01f, .min = 0.0}); + v.Field("baseScale", m_BaseScale, {.speed = 0.05f}); + v.Field("spinAxis", m_SpinAxis); + v.Field("spinSpeed", m_SpinSpeed, {.speed = 0.05f}); + v.Field("growPhase", m_GrowPhase, {.format = "%.2f rad", .readOnly = true}); } void OrbitAndSpin::Deserialize(const nlohmann::json& data) { - m_Radius = data.value("radius", m_Radius); - m_Center = data.value("center", m_Center); - m_OrbitAxis = data.value("orbitAxis", m_OrbitAxis); - m_OrbitSpeed = data.value("orbitSpeed", m_OrbitSpeed); - m_OrbitAngle = data.value("orbitAngle", m_OrbitAngle); - m_OrbitPhase = data.value("orbitPhase", m_OrbitPhase); - m_GrowPhase = data.value("growPhase", m_GrowPhase); - m_GrowSpeed = data.value("growSpeed", m_GrowSpeed); - m_GrowMin = data.value("growMin", m_GrowMin); - m_GrowMax = data.value("growMax", m_GrowMax); - m_SpinAxis = data.value("spinAxis", m_SpinAxis); - m_BaseScale = data.value("baseScale", m_BaseScale); + Component::Deserialize(data); m_BaseScaleLoaded = data.contains("baseScale"); - m_SpinSpeed = data.value("spinSpeed", m_SpinSpeed); + Sanitize(); +} +void OrbitAndSpin::OnFieldsEdited() { + Sanitize(); +} + +void OrbitAndSpin::Sanitize() { if (m_GrowMin > m_GrowMax) { std::swap(m_GrowMin, m_GrowMax); } @@ -172,62 +164,6 @@ void OrbitAndSpin::Deserialize(const nlohmann::json& data) { } void OrbitAndSpin::ImGuiInspector() { - float radius = m_Radius; - if (ImGui::DragFloat("Radius", &radius, 0.05f, 0.0f)) { - SetRadius(radius); - } - - glm::vec3 center = m_Center; - if (ImGuiUtils::DrawVec3Control("Center", center)) { - SetCenter(center); - } - - glm::vec3 orbitAxis = m_OrbitAxis; - if (ImGuiUtils::DrawVec3Control("Orbit axis", orbitAxis)) { - if (glm::dot(orbitAxis, orbitAxis) >= 1e-8f) { - m_OrbitAxis = glm::normalize(orbitAxis); - BuildOrbitBasis(); - } - } - - float orbitSpeed = m_OrbitSpeed; - if (ImGui::DragFloat("Orbit speed", &orbitSpeed, 0.05f)) { - m_OrbitSpeed = orbitSpeed; - } - ImGui::DragFloat("Orbit phase", &m_OrbitPhase, 0.05f); - ImGui::Text("Orbit angle: %.2f rad", m_OrbitAngle); - - float growSpeed = m_GrowSpeed; - if (ImGui::DragFloat("Grow speed", &growSpeed, 0.05f, 0.0f)) { - m_GrowSpeed = growSpeed; - } - - float growMin = m_GrowMin; - if (ImGui::DragFloat("Grow min", &growMin, 0.01f, 0.0f)) { - m_GrowMin = std::min(growMin, m_GrowMax); - } - - float growMax = m_GrowMax; - if (ImGui::DragFloat("Grow max", &growMax, 0.01f, 0.0f)) { - m_GrowMax = std::max(growMax, m_GrowMin); - } - - glm::vec3 baseScale = m_BaseScale; - if (ImGuiUtils::DrawVec3Control("Base scale", baseScale, 0.05f)) { - m_BaseScale = baseScale; - } - - glm::vec3 spinAxis = m_SpinAxis; - if (ImGuiUtils::DrawVec3Control("Spin axis", spinAxis)) { - if (glm::dot(spinAxis, spinAxis) >= 1e-8f) { - m_SpinAxis = glm::normalize(spinAxis); - } - } - - float spinSpeed = m_SpinSpeed; - if (ImGui::DragFloat("Spin speed", &spinSpeed, 0.05f)) { - m_SpinSpeed = spinSpeed; - } - ImGui::Text("Grow phase: %.2f rad", m_GrowPhase); + Component::ImGuiInspector(); ImGui::Text("Material ID: %u", m_MaterialID); } diff --git a/destrum/src/Components/Physics/BoxCollider.cpp b/destrum/src/Components/Physics/BoxCollider.cpp index 95ac060..3f2b8fd 100644 --- a/destrum/src/Components/Physics/BoxCollider.cpp +++ b/destrum/src/Components/Physics/BoxCollider.cpp @@ -1,32 +1,13 @@ #include #include -#include - -#include +#include void BoxCollider::DrawDebugLines(const glm::vec4& color) const { DebugDraw::Box(GetWorldCenter(), GetWorldRotation(), m_HalfExtents, color); } -nlohmann::json BoxCollider::Serialize() const { - auto data = SerializeCollider(); - data["halfExtents"] = m_HalfExtents; - return data; -} - -void BoxCollider::Deserialize(const nlohmann::json& data) { - DeserializeCollider(data); - if (data.contains("halfExtents")) { - SetHalfExtents(data.at("halfExtents").get()); - } -} - -void BoxCollider::ImGuiInspector() { - Collider::ImGuiInspector(); - - glm::vec3 halfExtents = m_HalfExtents; - if (ImGuiUtils::DrawVec3Control("Half extents", halfExtents)) { - SetHalfExtents(halfExtents); - } +void BoxCollider::Reflect(FieldVisitor& v) { + Collider::Reflect(v); + v.Property("halfExtents", m_HalfExtents, [this](const glm::vec3& halfExtents) { SetHalfExtents(halfExtents); }, {.min = 0.0001}); } diff --git a/destrum/src/Components/Physics/CapsuleCollider.cpp b/destrum/src/Components/Physics/CapsuleCollider.cpp index eb9970f..a0fabb1 100644 --- a/destrum/src/Components/Physics/CapsuleCollider.cpp +++ b/destrum/src/Components/Physics/CapsuleCollider.cpp @@ -2,22 +2,14 @@ #include -#include +#include void CapsuleCollider::DrawDebugLines(const glm::vec4& color) const { DebugDraw::Capsule(GetWorldCenter(), GetWorldRotation(), m_Radius, m_Height, color); } -void CapsuleCollider::ImGuiInspector() { - Collider::ImGuiInspector(); - - float radius = m_Radius; - if (ImGui::DragFloat("Radius", &radius, 0.05f, 0.0001f)) { - SetRadius(radius); - } - - float height = m_Height; - if (ImGui::DragFloat("Height", &height, 0.05f, 0.0001f)) { - SetHeight(height); - } +void CapsuleCollider::Reflect(FieldVisitor& v) { + Collider::Reflect(v); + v.Property("radius", m_Radius, [this](float radius) { SetRadius(radius); }, {.speed = 0.05f, .min = 0.0001}); + v.Property("height", m_Height, [this](float height) { SetHeight(height); }, {.speed = 0.05f, .min = 0.0001}); } diff --git a/destrum/src/Components/Physics/Collider.cpp b/destrum/src/Components/Physics/Collider.cpp index 900e2c8..7b6f7fd 100644 --- a/destrum/src/Components/Physics/Collider.cpp +++ b/destrum/src/Components/Physics/Collider.cpp @@ -1,9 +1,7 @@ #include -#include - #include -#include +#include glm::vec3 Collider::GetWorldCenter() const { Transform& transform = GetTransform(); @@ -14,14 +12,7 @@ glm::quat Collider::GetWorldRotation() const { return GetTransform().GetWorldRotation(); } -void Collider::ImGuiInspector() { - bool isTrigger = m_IsTrigger; - if (ImGui::Checkbox("Is trigger", &isTrigger)) { - SetTrigger(isTrigger); - } - - glm::vec3 centerOffset = m_CenterOffset; - if (ImGuiUtils::DrawVec3Control("Center offset", centerOffset)) { - SetCenterOffset(centerOffset); - } +void Collider::Reflect(FieldVisitor& v) { + v.Property("isTrigger", m_IsTrigger, [this](bool trigger) { SetTrigger(trigger); }); + v.Property("centerOffset", m_CenterOffset, [this](const glm::vec3& offset) { SetCenterOffset(offset); }); } diff --git a/destrum/src/Components/Physics/ConvexHullCollider.cpp b/destrum/src/Components/Physics/ConvexHullCollider.cpp index eba2b29..be3e84e 100644 --- a/destrum/src/Components/Physics/ConvexHullCollider.cpp +++ b/destrum/src/Components/Physics/ConvexHullCollider.cpp @@ -1,29 +1,13 @@ #include -#include +#include -nlohmann::json ConvexHullCollider::Serialize() const { - auto data = SerializeCollider(); - data["maxVertices"] = m_MaxVertices; - return data; -} - -void ConvexHullCollider::Deserialize(const nlohmann::json& data) { - DeserializeCollider(data); - if (data.contains("maxVertices")) { - SetMaxVertices(data.at("maxVertices").get()); - } +void ConvexHullCollider::Reflect(FieldVisitor& v) { + Collider::Reflect(v); + v.Property("maxVertices", m_MaxVertices, [this](int maxVertices) { SetMaxVertices(maxVertices); }, + {.speed = 1.0f, .min = MinVertices, .max = MaxVertices}); } void ConvexHullCollider::DrawDebugLines(const glm::vec4& color) const { DrawConvexHull(m_MaxVertices, color); } - -void ConvexHullCollider::ImGuiInspector() { - MeshShapeCollider::ImGuiInspector(); - - int maxVertices = m_MaxVertices; - if (ImGui::SliderInt("Max vertices", &maxVertices, MinVertices, MaxVertices)) { - SetMaxVertices(maxVertices); - } -} diff --git a/destrum/src/Components/Physics/CylinderCollider.cpp b/destrum/src/Components/Physics/CylinderCollider.cpp index e76294f..5b4e254 100644 --- a/destrum/src/Components/Physics/CylinderCollider.cpp +++ b/destrum/src/Components/Physics/CylinderCollider.cpp @@ -2,22 +2,14 @@ #include -#include +#include void CylinderCollider::DrawDebugLines(const glm::vec4& color) const { DebugDraw::Cylinder(GetWorldCenter(), GetWorldRotation(), m_Radius, m_Height, color); } -void CylinderCollider::ImGuiInspector() { - Collider::ImGuiInspector(); - - float radius = m_Radius; - if (ImGui::DragFloat("Radius", &radius, 0.05f, 0.0001f)) { - SetRadius(radius); - } - - float height = m_Height; - if (ImGui::DragFloat("Height", &height, 0.05f, 0.0001f)) { - SetHeight(height); - } +void CylinderCollider::Reflect(FieldVisitor& v) { + Collider::Reflect(v); + v.Property("radius", m_Radius, [this](float radius) { SetRadius(radius); }, {.speed = 0.05f, .min = 0.0001}); + v.Property("height", m_Height, [this](float height) { SetHeight(height); }, {.speed = 0.05f, .min = 0.0001}); } diff --git a/destrum/src/Components/Physics/Rigidbody.cpp b/destrum/src/Components/Physics/Rigidbody.cpp index 8be12cf..42a2942 100644 --- a/destrum/src/Components/Physics/Rigidbody.cpp +++ b/destrum/src/Components/Physics/Rigidbody.cpp @@ -3,40 +3,12 @@ #include #include #include +#include + +#include #include -namespace { - [[nodiscard]] const char* RigidbodyTypeName(RigidbodyType type) { - switch (type) { - case RigidbodyType::Static: - return "Static"; - case RigidbodyType::Kinematic: - return "Kinematic"; - case RigidbodyType::Dynamic: - default: - return "Dynamic"; - } - } - - [[nodiscard]] RigidbodyType ParseRigidbodyType(const nlohmann::json& value) { - if (value.is_string()) { - const std::string type = value.get(); - if (type == "Static") return RigidbodyType::Static; - if (type == "Kinematic") return RigidbodyType::Kinematic; - return RigidbodyType::Dynamic; - } - - const int type = value.get(); - switch (type) { - case 0: return RigidbodyType::Static; - case 2: return RigidbodyType::Kinematic; - case 1: - default: return RigidbodyType::Dynamic; - } - } -} - Rigidbody::~Rigidbody() { if (m_World != nullptr) { m_World->UnregisterRigidbody(*this); @@ -110,78 +82,21 @@ glm::vec3 Rigidbody::GetLinearVelocity() const { return m_World->GetLinearVelocity(m_Body); } -nlohmann::json Rigidbody::Serialize() const { - return { - {"type", RigidbodyTypeName(m_Type)}, - {"mass", m_Mass}, - {"friction", m_Friction}, - {"restitution", m_Restitution}, - {"useGravity", m_UseGravity}, - {"allowSleep", m_AllowSleep}, - {"freezeRotation", m_FreezeRotation} - }; -} +void Rigidbody::Reflect(FieldVisitor& v) { + static constexpr std::array TypeNames{"Static", "Dynamic", "Kinematic"}; -void Rigidbody::Deserialize(const nlohmann::json& data) { - if (data.contains("type")) { - m_Type = ParseRigidbodyType(data.at("type")); - } - if (data.contains("mass")) { - SetMass(data.at("mass").get()); - } - if (data.contains("friction")) { - SetFriction(data.at("friction").get()); - } - if (data.contains("restitution")) { - SetRestitution(data.at("restitution").get()); - } - if (data.contains("useGravity")) { - m_UseGravity = data.at("useGravity").get(); - } - if (data.contains("allowSleep")) { - m_AllowSleep = data.at("allowSleep").get(); - } - if (data.contains("freezeRotation")) { - m_FreezeRotation = data.at("freezeRotation").get(); - } + v.Property("type", m_Type, [this](RigidbodyType type) { SetType(type); }, {.label = "Body Type", .enumNames = TypeNames}); + v.Property("mass", m_Mass, [this](float mass) { SetMass(mass); }, {.min = 0.0001}); + v.Property("friction", m_Friction, [this](float friction) { SetFriction(friction); }, {.speed = 0.01f, .min = 0.0}); + v.Property("restitution", m_Restitution, [this](float restitution) { SetRestitution(restitution); }, + {.speed = 0.01f, .min = 0.0, .max = 1.0}); + v.Property("useGravity", m_UseGravity, [this](bool useGravity) { SetUseGravity(useGravity); }); + v.Property("allowSleep", m_AllowSleep, [this](bool allowSleep) { SetAllowSleep(allowSleep); }); + v.Property("freezeRotation", m_FreezeRotation, [this](bool freezeRotation) { SetFreezeRotation(freezeRotation); }); } void Rigidbody::ImGuiInspector() { - static const char* typeNames[] = {"Static", "Dynamic", "Kinematic"}; - int type = static_cast(m_Type); - if (ImGui::Combo("Body type", &type, typeNames, IM_ARRAYSIZE(typeNames))) { - SetType(static_cast(type)); - } - - float mass = m_Mass; - if (ImGui::DragFloat("Mass", &mass, 0.1f, 0.0001f)) { - SetMass(mass); - } - - float friction = m_Friction; - if (ImGui::DragFloat("Friction", &friction, 0.01f, 0.0f)) { - SetFriction(friction); - } - - float restitution = m_Restitution; - if (ImGui::SliderFloat("Restitution", &restitution, 0.0f, 1.0f)) { - SetRestitution(restitution); - } - - bool useGravity = m_UseGravity; - if (ImGui::Checkbox("Use gravity", &useGravity)) { - SetUseGravity(useGravity); - } - - bool allowSleep = m_AllowSleep; - if (ImGui::Checkbox("Allow sleep", &allowSleep)) { - SetAllowSleep(allowSleep); - } - - bool freezeRotation = m_FreezeRotation; - if (ImGui::Checkbox("Freeze rotation", &freezeRotation)) { - SetFreezeRotation(freezeRotation); - } + Component::ImGuiInspector(); if (HasPhysicsBody()) { const glm::vec3 velocity = GetLinearVelocity(); diff --git a/destrum/src/Components/Physics/SphereCollider.cpp b/destrum/src/Components/Physics/SphereCollider.cpp index 6d1aecf..4728ada 100644 --- a/destrum/src/Components/Physics/SphereCollider.cpp +++ b/destrum/src/Components/Physics/SphereCollider.cpp @@ -2,30 +2,13 @@ #include -#include +#include void SphereCollider::DrawDebugLines(const glm::vec4& color) const { DebugDraw::Sphere(GetWorldCenter(), m_Radius, color); } -nlohmann::json SphereCollider::Serialize() const { - auto data = SerializeCollider(); - data["radius"] = m_Radius; - return data; -} - -void SphereCollider::Deserialize(const nlohmann::json& data) { - DeserializeCollider(data); - if (data.contains("radius")) { - SetRadius(data.at("radius").get()); - } -} - -void SphereCollider::ImGuiInspector() { - Collider::ImGuiInspector(); - - float radius = m_Radius; - if (ImGui::DragFloat("Radius", &radius, 0.05f, 0.0001f)) { - SetRadius(radius); - } +void SphereCollider::Reflect(FieldVisitor& v) { + Collider::Reflect(v); + v.Property("radius", m_Radius, [this](float radius) { SetRadius(radius); }, {.speed = 0.05f, .min = 0.0001}); } diff --git a/destrum/src/Components/Rotator.cpp b/destrum/src/Components/Rotator.cpp index 284f9b2..9d3f9aa 100644 --- a/destrum/src/Components/Rotator.cpp +++ b/destrum/src/Components/Rotator.cpp @@ -5,11 +5,8 @@ #include #include // glm::quat, glm::angleAxis #include // operator*(quat, vec3) -#include +#include #include -#include - -#include glm::vec3 Rotator::MakePerpendicularUnitVector(const glm::vec3& axis) { @@ -98,48 +95,13 @@ void Rotator::Update(float dt) GetTransform().SetLocalPosition(m_Pivot + rotatedOffset); } -nlohmann::json Rotator::Serialize() const { - return { - {"distance", m_Distance}, - {"speed", m_Speed}, - {"currentAngle", m_CurrentAngle}, - {"pivot", m_Pivot}, - {"axis", m_Axis}, - {"initialOffset", m_InitialOffset} - }; -} - -void Rotator::Deserialize(const nlohmann::json& data) { - m_Distance = data.value("distance", m_Distance); - m_Speed = data.value("speed", m_Speed); - m_CurrentAngle = data.value("currentAngle", m_CurrentAngle); - m_Pivot = data.value("pivot", m_Pivot); - if (data.contains("axis")) { - SetAxis(data.at("axis").get()); - } - m_InitialOffset = data.value("initialOffset", m_InitialOffset); -} - -void Rotator::ImGuiInspector() { - float distance = m_Distance; - if (ImGui::DragFloat("Distance", &distance, 0.05f, 0.0f)) { - SetDistance(distance); - } - - float speed = m_Speed; - if (ImGui::DragFloat("Speed", &speed, 0.05f)) { - SetSpeed(speed); - } - - glm::vec3 pivot = m_Pivot; - if (ImGuiUtils::DrawVec3Control("Pivot", pivot)) { - SetPivotPosition(pivot); - } - - glm::vec3 axis = m_Axis; - if (ImGuiUtils::DrawVec3Control("Axis", axis)) { - SetAxis(axis); - } - - ImGui::Text("Current angle: %.2f rad", m_CurrentAngle); +// SetPivotPosition derives the offset and distance from the current position, so the pivot is +// loaded first and the saved distance and offset overwrite that +void Rotator::Reflect(FieldVisitor& v) { + v.Property("pivot", m_Pivot, [this](const glm::vec3& pivot) { SetPivotPosition(pivot); }); + v.Property("distance", m_Distance, [this](float distance) { SetDistance(distance); }, {.speed = 0.05f, .min = 0.0}); + v.Field("speed", m_Speed, {.speed = 0.05f}); + v.Property("axis", m_Axis, [this](const glm::vec3& axis) { SetAxis(axis); }); + v.Field("currentAngle", m_CurrentAngle, {.format = "%.2f rad", .readOnly = true}); + v.Field("initialOffset", m_InitialOffset, {.hidden = true}); } diff --git a/destrum/src/Components/Spinner.cpp b/destrum/src/Components/Spinner.cpp index c46d5df..126afb2 100644 --- a/destrum/src/Components/Spinner.cpp +++ b/destrum/src/Components/Spinner.cpp @@ -3,11 +3,8 @@ #include #include "destrum/ObjectModel/Transform.h" -#include "destrum/Serialization/GlmJson.h" +#include "destrum/Serialization/Reflect.h" #include "destrum/Util/DeltaTime.h" -#include "destrum/Util/ImGuiUtils.h" - -#include void Spinner::Update(float dt) { @@ -17,32 +14,8 @@ void Spinner::Update(float dt) GetTransform().SetLocalRotation(q); } -nlohmann::json Spinner::Serialize() const { - return { - {"axis", m_Axis}, - {"speed", m_Speed}, - {"angle", m_Angle} - }; -} - -void Spinner::Deserialize(const nlohmann::json& data) { - if (data.contains("axis")) { - SetAxis(data.at("axis").get()); - } - m_Speed = data.value("speed", m_Speed); - m_Angle = data.value("angle", m_Angle); -} - -void Spinner::ImGuiInspector() { - glm::vec3 axis = m_Axis; - if (ImGuiUtils::DrawVec3Control("Axis", axis)) { - SetAxis(axis); - } - - float speed = m_Speed; - if (ImGui::DragFloat("Speed", &speed, 0.05f)) { - SetSpeed(speed); - } - - ImGui::Text("Current angle: %.2f rad", m_Angle); +void Spinner::Reflect(FieldVisitor& v) { + v.Property("axis", m_Axis, [this](const glm::vec3& axis) { SetAxis(axis); }); + v.Field("speed", m_Speed, {.speed = 0.05f}); + v.Field("angle", m_Angle, {.format = "%.2f rad", .readOnly = true}); } diff --git a/destrum/src/Components/UI/Canvas.cpp b/destrum/src/Components/UI/Canvas.cpp index 7bba718..39e2d1c 100644 --- a/destrum/src/Components/UI/Canvas.cpp +++ b/destrum/src/Components/UI/Canvas.cpp @@ -7,8 +7,7 @@ #include #include #include - -#include +#include #include "tracy/Tracy.hpp" @@ -109,39 +108,13 @@ void Canvas::DrawObject(GameObject& object, const UIRect& rect, float scale, boo } } -nlohmann::json Canvas::Serialize() const +void Canvas::Reflect(FieldVisitor& v) { - return { - {"scaleMode", m_ScaleMode == CanvasScaleMode::ConstantPixelSize ? "constantPixelSize" : "scaleWithScreenHeight"}, - {"referenceHeight", m_ReferenceHeight}, - {"scaleFactor", m_ScaleFactor}, - {"drawDebugRects", m_DrawDebugRects}, - }; -} + static constexpr std::array ScaleModeNames{"constantPixelSize", "scaleWithScreenHeight"}; -void Canvas::Deserialize(const nlohmann::json& data) -{ - m_ScaleMode = data.value("scaleMode", std::string{"scaleWithScreenHeight"}) == "constantPixelSize" - ? CanvasScaleMode::ConstantPixelSize - : CanvasScaleMode::ScaleWithScreenHeight; - m_ReferenceHeight = data.value("referenceHeight", m_ReferenceHeight); - m_ScaleFactor = data.value("scaleFactor", m_ScaleFactor); - m_DrawDebugRects = data.value("drawDebugRects", m_DrawDebugRects); -} - -void Canvas::ImGuiInspector() -{ - constexpr std::array modeNames{"Constant Pixel Size", "Scale With Screen Height"}; - int mode = static_cast(m_ScaleMode); - if (ImGui::Combo("Scale Mode", &mode, modeNames.data(), static_cast(modeNames.size()))) { - m_ScaleMode = static_cast(mode); - } - - if (m_ScaleMode == CanvasScaleMode::ScaleWithScreenHeight) { - ImGui::DragFloat("Reference Height", &m_ReferenceHeight, 1.0f, 1.0f, 8192.0f); - } else { - ImGui::DragFloat("Scale Factor", &m_ScaleFactor, 0.01f, 0.01f, 10.0f); - } - - ImGui::Checkbox("Draw Debug Rects", &m_DrawDebugRects); + const bool scaleWithHeight = m_ScaleMode == CanvasScaleMode::ScaleWithScreenHeight; + v.Field("scaleMode", m_ScaleMode, {.enumNames = ScaleModeNames}); + v.Field("referenceHeight", m_ReferenceHeight, {.speed = 1.0f, .min = 1.0, .max = 8192.0, .hidden = !scaleWithHeight}); + v.Field("scaleFactor", m_ScaleFactor, {.speed = 0.01f, .min = 0.01, .max = 10.0, .hidden = scaleWithHeight}); + v.Field("drawDebugRects", m_DrawDebugRects); } diff --git a/destrum/src/Components/UI/RectTransform.cpp b/destrum/src/Components/UI/RectTransform.cpp index 27cb4d9..28773f9 100644 --- a/destrum/src/Components/UI/RectTransform.cpp +++ b/destrum/src/Components/UI/RectTransform.cpp @@ -1,6 +1,6 @@ #include -#include +#include #include @@ -61,24 +61,13 @@ void RectTransform::Stretch() m_Size = glm::vec2{0.0f}; } -nlohmann::json RectTransform::Serialize() const +void RectTransform::Reflect(FieldVisitor& v) { - return { - {"anchorMin", m_AnchorMin}, - {"anchorMax", m_AnchorMax}, - {"pivot", m_Pivot}, - {"position", m_Position}, - {"size", m_Size}, - }; -} - -void RectTransform::Deserialize(const nlohmann::json& data) -{ - m_AnchorMin = data.value("anchorMin", m_AnchorMin); - m_AnchorMax = data.value("anchorMax", m_AnchorMax); - m_Pivot = data.value("pivot", m_Pivot); - m_Position = data.value("position", m_Position); - m_Size = data.value("size", m_Size); + v.Field("anchorMin", m_AnchorMin, {.speed = 0.01f, .min = 0.0, .max = 1.0}); + v.Field("anchorMax", m_AnchorMax, {.speed = 0.01f, .min = 0.0, .max = 1.0}); + v.Field("pivot", m_Pivot, {.speed = 0.01f, .min = 0.0, .max = 1.0}); + v.Field("position", m_Position, {.speed = 1.0f}); + v.Field("size", m_Size, {.speed = 1.0f}); } void RectTransform::ImGuiInspector() @@ -118,11 +107,7 @@ void RectTransform::ImGuiInspector() } } - ImGui::DragFloat2("Anchor Min", &m_AnchorMin.x, 0.01f, 0.0f, 1.0f); - ImGui::DragFloat2("Anchor Max", &m_AnchorMax.x, 0.01f, 0.0f, 1.0f); - ImGui::DragFloat2("Pivot", &m_Pivot.x, 0.01f, 0.0f, 1.0f); - ImGui::DragFloat2("Position", &m_Position.x, 1.0f); - ImGui::DragFloat2("Size", &m_Size.x, 1.0f); + Component::ImGuiInspector(); ImGui::TextDisabled("Screen: %.0f, %.0f %.0f x %.0f", m_ScreenRect.min.x, m_ScreenRect.min.y, m_ScreenRect.size.x, m_ScreenRect.size.y); diff --git a/destrum/src/Components/UI/UIImage.cpp b/destrum/src/Components/UI/UIImage.cpp index 2c88c15..40b3e7b 100644 --- a/destrum/src/Components/UI/UIImage.cpp +++ b/destrum/src/Components/UI/UIImage.cpp @@ -5,11 +5,9 @@ #include #include #include -#include +#include #include -#include -#include #include UIImage::UIImage(GameObject& parent, const glm::vec4& color) @@ -64,45 +62,15 @@ ImageID UIImage::ResolveTexture() return m_Texture; } -nlohmann::json UIImage::Serialize() const +void UIImage::Reflect(FieldVisitor& v) { - return { - {"color", m_Color}, - {"shape", m_Shape == UIImageShape::Ellipse ? "ellipse" : "rect"}, - {"outline", m_Outline}, - {"cornerRadius", m_CornerRadius}, - {"texture", m_TexturePath}, - {"filter", m_Filter == TextureFilter::Nearest ? "nearest" : "linear"}, - }; -} - -void UIImage::Deserialize(const nlohmann::json& data) -{ - m_Color = data.value("color", m_Color); - m_Shape = data.value("shape", std::string{"rect"}) == "ellipse" ? UIImageShape::Ellipse : UIImageShape::Rect; - m_Outline = data.value("outline", m_Outline); - m_CornerRadius = data.value("cornerRadius", m_CornerRadius); - m_TexturePath = data.value("texture", m_TexturePath); - m_Filter = data.value("filter", std::string{"linear"}) == "nearest" ? TextureFilter::Nearest : TextureFilter::Linear; -} - -void UIImage::ImGuiInspector() -{ - constexpr std::array shapeNames{"Rect", "Ellipse"}; - int shape = static_cast(m_Shape); - if (ImGui::Combo("Shape", &shape, shapeNames.data(), static_cast(shapeNames.size()))) { - m_Shape = static_cast(shape); - } - - ImGui::ColorEdit4("Color", &m_Color.x); - ImGui::DragFloat("Outline", &m_Outline, 0.1f, 0.0f, 100.0f); - if (m_Shape == UIImageShape::Rect) { - ImGui::DragFloat("Corner Radius", &m_CornerRadius, 0.1f, 0.0f, 500.0f); - } - - ImGuiUtils::InputString("Texture", m_TexturePath); - bool nearest = m_Filter == TextureFilter::Nearest; - if (ImGui::Checkbox("Nearest Filter", &nearest)) { - m_Filter = nearest ? TextureFilter::Nearest : TextureFilter::Linear; - } + static constexpr std::array ShapeNames{"rect", "ellipse"}; + static constexpr std::array FilterNames{"nearest", "linear"}; + + v.Field("shape", m_Shape, {.enumNames = ShapeNames}); + v.Field("color", m_Color, {.color = true}); + v.Field("outline", m_Outline, {.min = 0.0, .max = 100.0}); + v.Field("cornerRadius", m_CornerRadius, {.min = 0.0, .max = 500.0, .hidden = m_Shape != UIImageShape::Rect}); + v.Field("texture", m_TexturePath, {.acceptAssets = true}); + v.Field("filter", m_Filter, {.enumNames = FilterNames}); } diff --git a/destrum/src/Components/UI/UIText.cpp b/destrum/src/Components/UI/UIText.cpp index 3e14349..5899559 100644 --- a/destrum/src/Components/UI/UIText.cpp +++ b/destrum/src/Components/UI/UIText.cpp @@ -4,10 +4,7 @@ #include #include -#include -#include - -#include +#include namespace { [[nodiscard]] float AlignFactor(UITextAlign align) @@ -19,24 +16,6 @@ namespace { } return 0.0f; } - - [[nodiscard]] const char* AlignName(UITextAlign align) - { - switch (align) { - case UITextAlign::Start: return "start"; - case UITextAlign::Center: return "center"; - case UITextAlign::End: return "end"; - } - return "start"; - } - - [[nodiscard]] UITextAlign ParseAlign(const std::string& name, UITextAlign fallback) - { - if (name == "start") return UITextAlign::Start; - if (name == "center") return UITextAlign::Center; - if (name == "end") return UITextAlign::End; - return fallback; - } } UIText::UIText(GameObject& parent, std::string text, float fontSize) @@ -86,48 +65,16 @@ void UIText::DrawUI(const UIRect& rect, float scale) TextStyle{.color = m_Color, .outline = m_Outline * scale, .outlineColor = m_OutlineColor}); } -nlohmann::json UIText::Serialize() const +void UIText::Reflect(FieldVisitor& v) { - return { - {"text", m_Text}, - {"font", m_FontPath}, - {"fontSize", m_FontSize}, - {"color", m_Color}, - {"outline", m_Outline}, - {"outlineColor", m_OutlineColor}, - {"horizontalAlign", AlignName(m_HorizontalAlign)}, - {"verticalAlign", AlignName(m_VerticalAlign)}, - }; -} + static constexpr std::array AlignNames{"start", "center", "end"}; -void UIText::Deserialize(const nlohmann::json& data) -{ - m_Text = data.value("text", m_Text); - m_FontPath = data.value("font", m_FontPath); - m_FontSize = data.value("fontSize", m_FontSize); - m_Color = data.value("color", m_Color); - m_Outline = data.value("outline", m_Outline); - m_OutlineColor = data.value("outlineColor", m_OutlineColor); - m_HorizontalAlign = ParseAlign(data.value("horizontalAlign", std::string{}), m_HorizontalAlign); - m_VerticalAlign = ParseAlign(data.value("verticalAlign", std::string{}), m_VerticalAlign); -} - -void UIText::ImGuiInspector() -{ - ImGuiUtils::InputStringMultiline("Text", m_Text); - ImGuiUtils::InputString("Font", m_FontPath); - ImGui::DragFloat("Font Size", &m_FontSize, 0.5f, 1.0f, 1000.0f); - ImGui::ColorEdit4("Color", &m_Color.x); - ImGui::DragFloat("Outline", &m_Outline, 0.1f, 0.0f, 16.0f); - ImGui::ColorEdit4("Outline Color", &m_OutlineColor.x); - - constexpr std::array alignNames{"Start", "Center", "End"}; - int horizontal = static_cast(m_HorizontalAlign); - if (ImGui::Combo("Horizontal", &horizontal, alignNames.data(), static_cast(alignNames.size()))) { - m_HorizontalAlign = static_cast(horizontal); - } - int vertical = static_cast(m_VerticalAlign); - if (ImGui::Combo("Vertical", &vertical, alignNames.data(), static_cast(alignNames.size()))) { - m_VerticalAlign = static_cast(vertical); - } + v.Field("text", m_Text, {.multiline = true}); + v.Field("font", m_FontPath, {.acceptAssets = true}); + v.Field("fontSize", m_FontSize, {.speed = 0.5f, .min = 1.0, .max = 1000.0}); + v.Field("color", m_Color, {.color = true}); + v.Field("outline", m_Outline, {.min = 0.0, .max = 16.0}); + v.Field("outlineColor", m_OutlineColor, {.color = true}); + v.Field("horizontalAlign", m_HorizontalAlign, {.label = "Horizontal", .enumNames = AlignNames}); + v.Field("verticalAlign", m_VerticalAlign, {.label = "Vertical", .enumNames = AlignNames}); } diff --git a/destrum/src/Serialization/Reflect.cpp b/destrum/src/Serialization/Reflect.cpp index 077ed8e..1132556 100644 --- a/destrum/src/Serialization/Reflect.cpp +++ b/destrum/src/Serialization/Reflect.cpp @@ -65,6 +65,26 @@ namespace reflect_detail { return ImGui::DragScalarN(label, ToImGui(type), value, components, options.speed, min, max, options.format, ClampFlags(options)); } + bool DrawAngle(const char* label, float& radians, const FieldOptions& options) + { + float degrees = glm::degrees(radians); + const char* format = options.format != nullptr ? options.format : "%.1f deg"; + bool changed; + if (options.min && options.max) { + changed = ImGui::SliderFloat(label, °rees, glm::degrees(static_cast(*options.min)), + glm::degrees(static_cast(*options.max)), format, ImGuiSliderFlags_AlwaysClamp); + } else { + const float min = options.min ? glm::degrees(static_cast(*options.min)) : 0.0f; + const float max = options.max ? glm::degrees(static_cast(*options.max)) : 0.0f; + changed = ImGui::DragScalar(label, ImGuiDataType_Float, °rees, options.speed, + options.min ? &min : nullptr, options.max ? &max : nullptr, format, ClampFlags(options)); + } + if (changed) { + radians = glm::radians(degrees); + } + return changed; + } + bool DrawEnum(const char* label, std::int64_t& value, const FieldOptions& options) { if (options.enumNames.empty()) { @@ -72,11 +92,11 @@ namespace reflect_detail { } const auto count = static_cast(options.enumNames.size()); - const char* preview = value >= 0 && value < count ? options.enumNames[static_cast(value)] : "?"; + const std::string preview = value >= 0 && value < count ? PrettifyFieldName(options.enumNames[static_cast(value)]) : "?"; bool changed = false; - if (ImGui::BeginCombo(label, preview)) { + if (ImGui::BeginCombo(label, preview.c_str())) { for (std::int64_t i = 0; i < count; ++i) { - if (ImGui::Selectable(options.enumNames[static_cast(i)], i == value)) { + if (ImGui::Selectable(PrettifyFieldName(options.enumNames[static_cast(i)]).c_str(), i == value)) { value = i; changed = true; } @@ -134,9 +154,17 @@ bool DrawField(const char* label, bool& value, const FieldOptions&, const Scene* return ImGui::Checkbox(label, &value); } -bool DrawField(const char* label, std::string& value, const FieldOptions&, const Scene*) +bool DrawField(const char* label, std::string& value, const FieldOptions& options, const Scene*) { - return ImGuiUtils::InputString(label, value); + bool changed = options.multiline ? ImGuiUtils::InputStringMultiline(label, value) : ImGuiUtils::InputString(label, value); + if (options.acceptAssets && ImGui::BeginDragDropTarget()) { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(ImGuiUtils::AssetPayload)) { + value = static_cast(payload->Data); + changed = true; + } + ImGui::EndDragDropTarget(); + } + return changed; } bool DrawField(const char* label, glm::vec2& value, const FieldOptions& options, const Scene*) @@ -241,14 +269,14 @@ void ResolveField(std::vector& value, const ObjectRef::ObjectMap& obj }); } -void JsonWriter::Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops) +void JsonWriter::Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops, const Commit&) { if (!options.transient) { ops.save(value, m_Result[std::string{key}], options); } } -void JsonReader::Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops) +void JsonReader::Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops, const Commit& commit) { if (options.transient || !m_Data.is_object()) { return; @@ -262,15 +290,21 @@ void JsonReader::Visit(std::string_view key, void* value, const FieldOptions& op } catch (const std::exception& exception) { throw std::runtime_error("\"" + std::string{key} + "\": " + exception.what()); } + if (commit) { + commit(value); + } } -void ReferenceResolver::Visit(std::string_view key, void* value, const FieldOptions&, const FieldOps& ops) +void ReferenceResolver::Visit(std::string_view key, void* value, const FieldOptions&, const FieldOps& ops, const Commit& commit) { if (ops.resolve == nullptr) { return; } std::vector dropped; ops.resolve(value, m_Objects, dropped); + if (commit) { + commit(value); + } const GameObject* owner = m_Owner.GetGameObject(); for (const ObjectId id : dropped) { spdlog::warn("{} on {}: {} references object {} which is not in the scene file, dropping it", @@ -278,7 +312,7 @@ void ReferenceResolver::Visit(std::string_view key, void* value, const FieldOpti } } -void InspectorDrawer::Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops) +void InspectorDrawer::Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops, const Commit& commit) { if (options.hidden) { return; @@ -286,7 +320,14 @@ void InspectorDrawer::Visit(std::string_view key, void* value, const FieldOption const std::string label = options.label != nullptr ? options.label : reflect_detail::PrettifyFieldName(key); ImGui::PushID(key.data(), key.data() + key.size()); ImGui::BeginDisabled(options.readOnly); - m_Changed |= ops.draw(label.c_str(), value, options, m_Scene); + const bool changed = ops.draw(label.c_str(), value, options, m_Scene); + if (options.tooltip != nullptr) { + ImGui::SetItemTooltip("%s", options.tooltip); + } + if (changed && commit) { + commit(value); + } + m_Changed |= changed; ImGui::EndDisabled(); ImGui::PopID(); } diff --git a/lightkeeper/include/components/Checkpoint.h b/lightkeeper/include/components/Checkpoint.h index 4e84927..1e7d193 100644 --- a/lightkeeper/include/components/Checkpoint.h +++ b/lightkeeper/include/components/Checkpoint.h @@ -13,9 +13,7 @@ public: void OnTriggerEnter(GameObject* other) override; - nlohmann::json Serialize() const override; - void Deserialize(const nlohmann::json& data) override; - void ImGuiInspector() override; + void Reflect(FieldVisitor& v) override; void SetSpawnOffset(const glm::vec3& offset) { m_SpawnOffset = offset; } [[nodiscard]] glm::vec3 GetSpawnPoint() const; diff --git a/lightkeeper/include/components/CrosshairUI.h b/lightkeeper/include/components/CrosshairUI.h index dac7b22..93d4827 100644 --- a/lightkeeper/include/components/CrosshairUI.h +++ b/lightkeeper/include/components/CrosshairUI.h @@ -16,10 +16,7 @@ public: void LateUpdate(float dt) override; - nlohmann::json Serialize() const override; - void Deserialize(const nlohmann::json& data) override; - void ResolveReferences(const ObjectMap& objects) override; - void ImGuiInspector() override; + void Reflect(FieldVisitor& v) override; void SetPlayer(const GameObject& player) { m_Player = ObjectRef(player); } void SetCrosshair(const GameObject& crosshair) { m_Crosshair = ObjectRef(crosshair); } diff --git a/lightkeeper/include/components/FirstPersonController.h b/lightkeeper/include/components/FirstPersonController.h index 4e55bd4..4009543 100644 --- a/lightkeeper/include/components/FirstPersonController.h +++ b/lightkeeper/include/components/FirstPersonController.h @@ -20,8 +20,8 @@ public: void Start() override; void Update(float dt) override; void FixedUpdate(float fixedDt) override; + void Reflect(FieldVisitor& v) override; void ImGuiInspector() override; - nlohmann::json Serialize() const override; void Deserialize(const nlohmann::json& data) override; [[nodiscard]] float GetYaw() const { return m_Yaw; } diff --git a/lightkeeper/include/components/LightToggle.h b/lightkeeper/include/components/LightToggle.h index 0638cd8..635122f 100644 --- a/lightkeeper/include/components/LightToggle.h +++ b/lightkeeper/include/components/LightToggle.h @@ -11,9 +11,8 @@ public: void Start() override; - nlohmann::json Serialize() const override; - void Deserialize(const nlohmann::json& data) override; - void ImGuiInspector() override; + void Reflect(FieldVisitor& v) override; + void OnFieldsEdited() override; void SetOn(bool on); [[nodiscard]] bool IsOn() const { return m_On; } diff --git a/lightkeeper/include/components/MovingPlatform.h b/lightkeeper/include/components/MovingPlatform.h index 2cc2354..f1fd5ec 100644 --- a/lightkeeper/include/components/MovingPlatform.h +++ b/lightkeeper/include/components/MovingPlatform.h @@ -14,8 +14,7 @@ public: void Start() override; void Update(float dt) override; - nlohmann::json Serialize() const override; - void Deserialize(const nlohmann::json& data) override; + void Reflect(FieldVisitor& v) override; void ImGuiInspector() override; void SetTravelOffset(const glm::vec3& offset) { m_TravelOffset = offset; } diff --git a/lightkeeper/include/components/PlayerRespawn.h b/lightkeeper/include/components/PlayerRespawn.h index 07fff10..82d6375 100644 --- a/lightkeeper/include/components/PlayerRespawn.h +++ b/lightkeeper/include/components/PlayerRespawn.h @@ -14,6 +14,7 @@ public: void Start() override; void FixedUpdate(float fixedDt) override; + void Reflect(FieldVisitor& v) override; nlohmann::json Serialize() const override; void Deserialize(const nlohmann::json& data) override; void ImGuiInspector() override; diff --git a/lightkeeper/include/components/PrintComponent.h b/lightkeeper/include/components/PrintComponent.h index 22321cd..b0ac1c6 100644 --- a/lightkeeper/include/components/PrintComponent.h +++ b/lightkeeper/include/components/PrintComponent.h @@ -10,9 +10,7 @@ public: std::string GetTypeName() const override { return "PrintComponent"; } void Start() override; void Update(float dt) override; - void ImGuiInspector() override; - nlohmann::json Serialize() const override; - void Deserialize(const nlohmann::json&) override; + void Reflect(FieldVisitor& v) override; void SetMessage(std::string_view message) { diff --git a/lightkeeper/include/components/TriggerLoggerComponent.h b/lightkeeper/include/components/TriggerLoggerComponent.h index 144829b..d431a35 100644 --- a/lightkeeper/include/components/TriggerLoggerComponent.h +++ b/lightkeeper/include/components/TriggerLoggerComponent.h @@ -11,8 +11,7 @@ public: void OnTriggerEnter(GameObject* other) override; void OnTriggerExit(GameObject* other) override; - nlohmann::json Serialize() const override; - void Deserialize(const nlohmann::json&) override; + void Reflect(FieldVisitor& v) override; int triggerEnterCount{0}; int triggerExitCount{0}; diff --git a/lightkeeper/include/components/WinGoal.h b/lightkeeper/include/components/WinGoal.h index 445e72f..def8ccd 100644 --- a/lightkeeper/include/components/WinGoal.h +++ b/lightkeeper/include/components/WinGoal.h @@ -9,9 +9,7 @@ public: std::string GetTypeName() const override { return "WinGoal"; } - nlohmann::json Serialize() const override; - void Deserialize(const nlohmann::json& data) override; - void ImGuiInspector() override; + void Reflect(FieldVisitor& v) override; [[nodiscard]] bool IsReached() const { return m_Reached; } void Reset() { m_Reached = false; } diff --git a/lightkeeper/include/components/WinScreenUI.h b/lightkeeper/include/components/WinScreenUI.h index 293cd4a..67c1b8f 100644 --- a/lightkeeper/include/components/WinScreenUI.h +++ b/lightkeeper/include/components/WinScreenUI.h @@ -13,10 +13,7 @@ public: void LateUpdate(float dt) override; - nlohmann::json Serialize() const override; - void Deserialize(const nlohmann::json& data) override; - void ResolveReferences(const ObjectMap& objects) override; - void ImGuiInspector() override; + void Reflect(FieldVisitor& v) override; void SetGoal(const GameObject& goal) { m_Goal = ObjectRef(goal); } void SetScreen(const GameObject& screen) { m_Screen = ObjectRef(screen); } diff --git a/lightkeeper/src/components/Checkpoint.cpp b/lightkeeper/src/components/Checkpoint.cpp index 5270030..25118e0 100644 --- a/lightkeeper/src/components/Checkpoint.cpp +++ b/lightkeeper/src/components/Checkpoint.cpp @@ -1,12 +1,10 @@ #include #include -#include +#include #include -#include -#include #include Checkpoint::Checkpoint(GameObject& parent) @@ -35,17 +33,7 @@ void Checkpoint::OnTriggerEnter(GameObject* other) spdlog::info("{} reached checkpoint {}", other->GetName(), GetGameObject()->GetName()); } -nlohmann::json Checkpoint::Serialize() const +void Checkpoint::Reflect(FieldVisitor& v) { - return {{"spawnOffset", m_SpawnOffset}}; -} - -void Checkpoint::Deserialize(const nlohmann::json& data) -{ - m_SpawnOffset = data.value("spawnOffset", m_SpawnOffset); -} - -void Checkpoint::ImGuiInspector() -{ - ImGui::DragFloat3("Spawn offset", glm::value_ptr(m_SpawnOffset), 0.05f); + v.Field("spawnOffset", m_SpawnOffset, {.speed = 0.05f}); } diff --git a/lightkeeper/src/components/CrosshairUI.cpp b/lightkeeper/src/components/CrosshairUI.cpp index d4553db..b311ad8 100644 --- a/lightkeeper/src/components/CrosshairUI.cpp +++ b/lightkeeper/src/components/CrosshairUI.cpp @@ -6,10 +6,7 @@ #include #include #include -#include -#include - -#include +#include namespace { void TintHierarchy(GameObject& object, const glm::vec4& color) @@ -59,49 +56,13 @@ void CrosshairUI::LateUpdate(float) TintHierarchy(*crosshair, focused ? m_FocusedColor : m_Color); } -nlohmann::json CrosshairUI::Serialize() const +void CrosshairUI::Reflect(FieldVisitor& v) { - return { - {"player", m_Player}, - {"crosshair", m_Crosshair}, - {"focusIndicator", m_FocusIndicator}, - {"color", m_Color}, - {"focusedColor", m_FocusedColor}, - {"size", m_Size}, - {"focusedSize", m_FocusedSize}, - }; -} - -void CrosshairUI::Deserialize(const nlohmann::json& data) -{ - m_Player = data.value("player", ObjectRef{}); - m_Crosshair = data.value("crosshair", ObjectRef{}); - m_FocusIndicator = data.value("focusIndicator", ObjectRef{}); - m_Color = data.value("color", m_Color); - m_FocusedColor = data.value("focusedColor", m_FocusedColor); - m_Size = data.value("size", m_Size); - m_FocusedSize = data.value("focusedSize", m_FocusedSize); -} - -void CrosshairUI::ResolveReferences(const ObjectMap& objects) -{ - m_Player.Resolve(objects); - m_Crosshair.Resolve(objects); - m_FocusIndicator.Resolve(objects); -} - -void CrosshairUI::ImGuiInspector() -{ - if (const Scene* scene = GetGameObject()->GetScene()) { - ImGuiUtils::DrawObjectRefPicker("Player", m_Player, *scene, [](const GameObject& object) { - return object.GetComponent() != nullptr; - }); - ImGuiUtils::DrawObjectRefPicker("Crosshair", m_Crosshair, *scene); - ImGuiUtils::DrawObjectRefPicker("Focus Indicator", m_FocusIndicator, *scene); - } - - ImGui::ColorEdit4("Color", &m_Color.x); - ImGui::ColorEdit4("Focused Color", &m_FocusedColor.x); - ImGui::DragFloat("Size", &m_Size, 0.5f, 0.0f, 500.0f); - ImGui::DragFloat("Focused Size", &m_FocusedSize, 0.5f, 0.0f, 500.0f); + v.Field("player", m_Player, {.filter = [](const GameObject& object) { return object.GetComponent() != nullptr; }}); + v.Field("crosshair", m_Crosshair); + v.Field("focusIndicator", m_FocusIndicator); + v.Field("color", m_Color, {.color = true}); + v.Field("focusedColor", m_FocusedColor, {.color = true}); + v.Field("size", m_Size, {.speed = 0.5f, .min = 0.0, .max = 500.0}); + v.Field("focusedSize", m_FocusedSize, {.speed = 0.5f, .min = 0.0, .max = 500.0}); } diff --git a/lightkeeper/src/components/FirstPersonController.cpp b/lightkeeper/src/components/FirstPersonController.cpp index 8a523fc..8602e86 100644 --- a/lightkeeper/src/components/FirstPersonController.cpp +++ b/lightkeeper/src/components/FirstPersonController.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -274,70 +275,40 @@ glm::vec3 FirstPersonController::GetEyePosition() const return position + glm::vec3{0.0f, m_EyeHeight, 0.0f}; } +void FirstPersonController::Reflect(FieldVisitor& v) +{ + v.Field("walkSpeed", m_WalkSpeed, {.speed = 0.1f, .min = 0.0, .max = 100.0}); + v.Field("sprintSpeed", m_SprintSpeed, {.speed = 0.1f, .min = 0.0, .max = 100.0}); + v.Field("groundAcceleration", m_GroundAcceleration, {.speed = 0.5f, .min = 0.0, .max = 500.0}); + v.Field("airAcceleration", m_AirAcceleration, {.speed = 0.5f, .min = 0.0, .max = 500.0}); + v.Field("jumpHeight", m_JumpHeight, {.speed = 0.01f, .min = 0.0, .max = 10.0}); + v.Field("gravity", m_Gravity, {.speed = 0.1f, .min = 0.0, .max = 100.0}); + v.Field("fallGravityMultiplier", m_FallGravityMultiplier, {.speed = 0.01f, .min = 1.0, .max = 5.0}); + v.Field("coyoteTime", m_CoyoteTime, {.speed = 0.005f, .min = 0.0, .max = 1.0}); + v.Field("jumpBufferTime", m_JumpBufferTime, {.speed = 0.005f, .min = 0.0, .max = 1.0}); + v.Field("interactDistance", m_InteractDistance, {.speed = 0.05f, .min = 0.0, .max = 20.0}); + v.Field("maxSlopeAngle", m_MaxSlopeAngle, {.min = 0.0, .max = glm::radians(89.0), .angle = true}); + v.Field("groundStickSpeed", m_GroundStickSpeed, {.speed = 0.05f, .min = 0.0, .max = 20.0}); + v.Field("mouseSensitivity", m_MouseSensitivity, {.speed = 0.0001f, .min = 0.0, .max = 0.1, .format = "%.4f"}); + v.Field("eyeHeight", m_EyeHeight, {.speed = 0.01f, .min = -2.0, .max = 2.0}); + v.Field("yaw", m_Yaw, {.angle = true, .readOnly = true}); + v.Field("pitch", m_Pitch, {.angle = true, .readOnly = true}); +} + void FirstPersonController::ImGuiInspector() { Component::ImGuiInspector(); - ImGui::DragFloat("Walk Speed", &m_WalkSpeed, 0.1f, 0.0f, 100.0f); - ImGui::DragFloat("Sprint Speed", &m_SprintSpeed, 0.1f, 0.0f, 100.0f); - ImGui::DragFloat("Ground Acceleration", &m_GroundAcceleration, 0.5f, 0.0f, 500.0f); - ImGui::DragFloat("Air Acceleration", &m_AirAcceleration, 0.5f, 0.0f, 500.0f); - ImGui::DragFloat("Jump Height", &m_JumpHeight, 0.01f, 0.0f, 10.0f); - ImGui::DragFloat("Gravity", &m_Gravity, 0.1f, 0.0f, 100.0f); - ImGui::DragFloat("Fall Gravity Multiplier", &m_FallGravityMultiplier, 0.01f, 1.0f, 5.0f); - ImGui::DragFloat("Coyote Time", &m_CoyoteTime, 0.005f, 0.0f, 1.0f); - ImGui::DragFloat("Jump Buffer Time", &m_JumpBufferTime, 0.005f, 0.0f, 1.0f); - ImGui::DragFloat("Interact Distance", &m_InteractDistance, 0.05f, 0.0f, 20.0f); - ImGui::SliderAngle("Max Slope Angle", &m_MaxSlopeAngle, 0.0f, 89.0f); - ImGui::DragFloat("Ground Stick Speed", &m_GroundStickSpeed, 0.05f, 0.0f, 20.0f); - ImGui::DragFloat("Mouse Sensitivity", &m_MouseSensitivity, 0.0001f, 0.0f, 0.1f); - ImGui::DragFloat("Eye Height", &m_EyeHeight, 0.01f, -2.0f, 2.0f); ImGui::Text("Grounded: %s", m_Grounded ? "yes" : "no"); ImGui::Text("Ground slope: %.1f deg", glm::degrees(std::acos(glm::clamp(m_GroundNormal.y, -1.0f, 1.0f)))); - ImGui::Text("Yaw: %.1f deg", glm::degrees(m_Yaw)); - ImGui::Text("Pitch: %.1f deg", glm::degrees(m_Pitch)); if (bool possessed = m_Possessed; ImGui::Checkbox("Possessed", &possessed)) { SetPossessed(possessed); } } -nlohmann::json FirstPersonController::Serialize() const -{ - return { - {"walkSpeed", m_WalkSpeed}, - {"sprintSpeed", m_SprintSpeed}, - {"groundAcceleration", m_GroundAcceleration}, - {"airAcceleration", m_AirAcceleration}, - {"jumpHeight", m_JumpHeight}, - {"gravity", m_Gravity}, - {"fallGravityMultiplier", m_FallGravityMultiplier}, - {"coyoteTime", m_CoyoteTime}, - {"jumpBufferTime", m_JumpBufferTime}, - {"interactDistance", m_InteractDistance}, - {"maxSlopeAngle", m_MaxSlopeAngle}, - {"groundStickSpeed", m_GroundStickSpeed}, - {"mouseSensitivity", m_MouseSensitivity}, - {"eyeHeight", m_EyeHeight}, - {"yaw", m_Yaw}, - {"pitch", m_Pitch}, - }; -} - void FirstPersonController::Deserialize(const nlohmann::json& data) { - m_WalkSpeed = data.value("walkSpeed", data.value("movementSpeed", m_WalkSpeed)); - m_SprintSpeed = data.value("sprintSpeed", m_SprintSpeed); - m_GroundAcceleration = data.value("groundAcceleration", m_GroundAcceleration); - m_AirAcceleration = data.value("airAcceleration", m_AirAcceleration); - m_JumpHeight = data.value("jumpHeight", m_JumpHeight); - m_Gravity = data.value("gravity", m_Gravity); - m_FallGravityMultiplier = data.value("fallGravityMultiplier", m_FallGravityMultiplier); - m_CoyoteTime = data.value("coyoteTime", m_CoyoteTime); - m_JumpBufferTime = data.value("jumpBufferTime", m_JumpBufferTime); - m_InteractDistance = data.value("interactDistance", m_InteractDistance); - m_MaxSlopeAngle = data.value("maxSlopeAngle", m_MaxSlopeAngle); - m_GroundStickSpeed = data.value("groundStickSpeed", m_GroundStickSpeed); - m_MouseSensitivity = data.value("mouseSensitivity", m_MouseSensitivity); - m_EyeHeight = data.value("eyeHeight", m_EyeHeight); - m_Yaw = data.value("yaw", m_Yaw); - m_Pitch = data.value("pitch", m_Pitch); + Component::Deserialize(data); + if (!data.contains("walkSpeed") && data.contains("movementSpeed")) { + m_WalkSpeed = data.at("movementSpeed").get(); + } } diff --git a/lightkeeper/src/components/LightToggle.cpp b/lightkeeper/src/components/LightToggle.cpp index 1620655..e4a4a22 100644 --- a/lightkeeper/src/components/LightToggle.cpp +++ b/lightkeeper/src/components/LightToggle.cpp @@ -4,10 +4,9 @@ #include #include #include +#include #include -#include - LightToggle::LightToggle(GameObject& parent) : Activatable(parent, "LightToggle") { @@ -50,19 +49,12 @@ void LightToggle::Apply() renderer.updateMaterialGPU(meshRenderer->GetMaterialID()); } -nlohmann::json LightToggle::Serialize() const +void LightToggle::Reflect(FieldVisitor& v) { - return {{"on", m_On}}; + v.Field("on", m_On); } -void LightToggle::Deserialize(const nlohmann::json& data) +void LightToggle::OnFieldsEdited() { - m_On = data.value("on", m_On); -} - -void LightToggle::ImGuiInspector() -{ - if (bool on = m_On; ImGui::Checkbox("On", &on)) { - SetOn(on); - } + Apply(); } diff --git a/lightkeeper/src/components/MovingPlatform.cpp b/lightkeeper/src/components/MovingPlatform.cpp index f08f337..f61f3b8 100644 --- a/lightkeeper/src/components/MovingPlatform.cpp +++ b/lightkeeper/src/components/MovingPlatform.cpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include @@ -50,28 +50,16 @@ glm::vec3 MovingPlatform::GetOffsetAt(float progress) const return m_TravelOffset * eased; } -nlohmann::json MovingPlatform::Serialize() const +void MovingPlatform::Reflect(FieldVisitor& v) { - return { - {"travelOffset", m_TravelOffset}, - {"travelDuration", m_TravelDuration}, - {"pauseDuration", m_PauseDuration}, - {"elapsedTime", m_ElapsedTime}, - }; -} - -void MovingPlatform::Deserialize(const nlohmann::json& data) -{ - m_TravelOffset = data.value("travelOffset", m_TravelOffset); - m_TravelDuration = std::max(data.value("travelDuration", m_TravelDuration), 0.01f); - m_PauseDuration = std::max(data.value("pauseDuration", m_PauseDuration), 0.0f); - m_ElapsedTime = std::max(data.value("elapsedTime", m_ElapsedTime), 0.0f); + v.Field("travelOffset", m_TravelOffset, {.speed = 0.05f}); + v.Field("travelDuration", m_TravelDuration, {.speed = 0.05f, .min = 0.01, .format = "%.2f s"}); + v.Field("pauseDuration", m_PauseDuration, {.speed = 0.05f, .min = 0.0, .format = "%.2f s"}); + v.Field("elapsedTime", m_ElapsedTime, {.min = 0.0, .hidden = true}); } void MovingPlatform::ImGuiInspector() { ImGui::Text("Progress: %.0f%%", GetTravelProgress() * 100.0f); - ImGui::DragFloat3("Travel Offset", &m_TravelOffset.x, 0.05f); - ImGui::DragFloat("Travel Duration", &m_TravelDuration, 0.05f, 0.01f, 60.0f, "%.2f s"); - ImGui::DragFloat("Pause Duration", &m_PauseDuration, 0.05f, 0.0f, 60.0f, "%.2f s"); + Component::ImGuiInspector(); } diff --git a/lightkeeper/src/components/PlayerRespawn.cpp b/lightkeeper/src/components/PlayerRespawn.cpp index d834a00..6c09e15 100644 --- a/lightkeeper/src/components/PlayerRespawn.cpp +++ b/lightkeeper/src/components/PlayerRespawn.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -46,9 +47,15 @@ void PlayerRespawn::Respawn() } } +void PlayerRespawn::Reflect(FieldVisitor& v) +{ + v.Field("killHeight", m_KillHeight); +} + +// The respawn point is only saved once set, until then Start uses the spawn position nlohmann::json PlayerRespawn::Serialize() const { - nlohmann::json data{{"killHeight", m_KillHeight}}; + nlohmann::json data = Component::Serialize(); if (m_HasRespawnPoint) { data["respawnPoint"] = m_RespawnPoint; } @@ -57,7 +64,7 @@ nlohmann::json PlayerRespawn::Serialize() const void PlayerRespawn::Deserialize(const nlohmann::json& data) { - m_KillHeight = data.value("killHeight", m_KillHeight); + Component::Deserialize(data); if (data.contains("respawnPoint")) { SetRespawnPoint(data.at("respawnPoint").get()); } @@ -65,8 +72,10 @@ void PlayerRespawn::Deserialize(const nlohmann::json& data) void PlayerRespawn::ImGuiInspector() { - ImGui::DragFloat("Kill height", &m_KillHeight, 0.1f); - ImGui::DragFloat3("Respawn point", glm::value_ptr(m_RespawnPoint), 0.1f); + Component::ImGuiInspector(); + if (ImGui::DragFloat3("Respawn Point", glm::value_ptr(m_RespawnPoint), 0.1f)) { + m_HasRespawnPoint = true; + } if (ImGui::Button("Respawn now")) { Respawn(); } diff --git a/lightkeeper/src/components/PrintComponent.cpp b/lightkeeper/src/components/PrintComponent.cpp index a65b618..cd3c31f 100644 --- a/lightkeeper/src/components/PrintComponent.cpp +++ b/lightkeeper/src/components/PrintComponent.cpp @@ -2,6 +2,8 @@ #include "spdlog/spdlog.h" +#include + PrintComponent::PrintComponent(GameObject& parent) : Component(parent, "PrintComponent") { } @@ -15,20 +17,7 @@ void PrintComponent::Update(float dt) spdlog::debug(m_message); } -void PrintComponent::ImGuiInspector() +void PrintComponent::Reflect(FieldVisitor& v) { - Component::ImGuiInspector(); -} - -nlohmann::json PrintComponent::Serialize() const -{ - return {{"message", m_message}}; -} - -void PrintComponent::Deserialize(const nlohmann::json& basic_jsons) -{ - if (basic_jsons.contains("message")) - { - m_message = basic_jsons.at("message").get(); - } + v.Field("message", m_message); } diff --git a/lightkeeper/src/components/TriggerLoggerComponent.cpp b/lightkeeper/src/components/TriggerLoggerComponent.cpp index f0d2e8c..162773c 100644 --- a/lightkeeper/src/components/TriggerLoggerComponent.cpp +++ b/lightkeeper/src/components/TriggerLoggerComponent.cpp @@ -2,6 +2,7 @@ #include "spdlog/spdlog.h" #include +#include TriggerLoggerComponent::TriggerLoggerComponent(GameObject& parent) : Component(parent, "TriggerLoggerComponent") {} @@ -28,18 +29,8 @@ void TriggerLoggerComponent::OnTriggerExit(GameObject* other) { triggerExitCount); } -nlohmann::json TriggerLoggerComponent::Serialize() const { - return { - {"triggerEnterCount", triggerEnterCount}, - {"triggerExitCount", triggerExitCount} - }; +void TriggerLoggerComponent::Reflect(FieldVisitor& v) { + v.Field("triggerEnterCount", triggerEnterCount, {.readOnly = true}); + v.Field("triggerExitCount", triggerExitCount, {.readOnly = true}); + v.Field("lastOtherName", lastOtherName, {.readOnly = true, .transient = true}); } - -void TriggerLoggerComponent::Deserialize(const nlohmann::json& data) { - if (data.contains("triggerEnterCount")) { - triggerEnterCount = data.at("triggerEnterCount").get(); - } - if (data.contains("triggerExitCount")) { - triggerExitCount = data.at("triggerExitCount").get(); - } -} \ No newline at end of file diff --git a/lightkeeper/src/components/WinGoal.cpp b/lightkeeper/src/components/WinGoal.cpp index ebb7374..6ac4ea4 100644 --- a/lightkeeper/src/components/WinGoal.cpp +++ b/lightkeeper/src/components/WinGoal.cpp @@ -1,8 +1,8 @@ #include #include +#include -#include #include WinGoal::WinGoal(GameObject& parent) @@ -18,17 +18,7 @@ void WinGoal::OnActivate(GameObject& instigator) m_Reached = true; } -nlohmann::json WinGoal::Serialize() const +void WinGoal::Reflect(FieldVisitor& v) { - return {{"reached", m_Reached}}; -} - -void WinGoal::Deserialize(const nlohmann::json& data) -{ - m_Reached = data.value("reached", m_Reached); -} - -void WinGoal::ImGuiInspector() -{ - ImGui::Checkbox("Reached", &m_Reached); + v.Field("reached", m_Reached); } diff --git a/lightkeeper/src/components/WinScreenUI.cpp b/lightkeeper/src/components/WinScreenUI.cpp index 81f1634..f40f75b 100644 --- a/lightkeeper/src/components/WinScreenUI.cpp +++ b/lightkeeper/src/components/WinScreenUI.cpp @@ -4,7 +4,7 @@ #include #include -#include +#include WinScreenUI::WinScreenUI(GameObject& parent) : Component(parent, "WinScreenUI") @@ -28,35 +28,8 @@ void WinScreenUI::LateUpdate(float) screen->SetActive(winGoal != nullptr && winGoal->IsReached()); } -nlohmann::json WinScreenUI::Serialize() const +void WinScreenUI::Reflect(FieldVisitor& v) { - return { - {"goal", m_Goal}, - {"screen", m_Screen}, - }; -} - -void WinScreenUI::Deserialize(const nlohmann::json& data) -{ - m_Goal = data.value("goal", ObjectRef{}); - m_Screen = data.value("screen", ObjectRef{}); -} - -void WinScreenUI::ResolveReferences(const ObjectMap& objects) -{ - m_Goal.Resolve(objects); - m_Screen.Resolve(objects); -} - -void WinScreenUI::ImGuiInspector() -{ - const Scene* scene = GetGameObject()->GetScene(); - if (scene == nullptr) { - return; - } - - ImGuiUtils::DrawObjectRefPicker("Goal", m_Goal, *scene, [](const GameObject& object) { - return object.GetComponent() != nullptr; - }); - ImGuiUtils::DrawObjectRefPicker("Screen", m_Screen, *scene); + v.Field("goal", m_Goal, {.filter = [](const GameObject& object) { return object.GetComponent() != nullptr; }}); + v.Field("screen", m_Screen); } diff --git a/tests/serialization_tests.cpp b/tests/serialization_tests.cpp index 9cfe27a..cd74937 100644 --- a/tests/serialization_tests.cpp +++ b/tests/serialization_tests.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -415,6 +416,11 @@ namespace { v.Field("target", target); v.Field("targets", targets); v.Field("cache", cache, {.transient = true}); + v.Field("extents", extents, {.min = 0.5}); + v.Property("limited", limited, [this](int value) { + limited = std::min(value, 5); + ++setterCalls; + }); } bool flag{false}; @@ -431,6 +437,9 @@ namespace { ObjectRef target; std::vector targets; int cache{0}; + glm::vec3 extents{1.0f}; + int limited{0}; + int setterCalls{0}; }; [[nodiscard]] bool throwsMentioning(ReflectedComponent& component, const nlohmann::json& data, const char* key) @@ -493,10 +502,25 @@ namespace { component->Deserialize({{"mode", 2}}); check(component->mode == TestMode::Fast, "named enums must still load from a number"); + component->Deserialize({{"extents", {2.0f, 0.1f, -1.0f}}}); + check(component->extents == glm::vec3{2.0f, 0.5f, 0.5f}, "loading must clamp every component of a vector"); + check(throwsMentioning(*component, {{"mode", "Turbo"}}, "\"mode\""), "an unknown enum name must fail and name the field"); check(throwsMentioning(*component, {{"offset", "up"}}, "\"offset\""), "a wrong type must fail and name the field"); } + void testReflectedProperties() + { + GameObject object{"reflected"}; + auto* component = object.AddComponent(); + component->Deserialize({{"flag", true}}); + check(component->setterCalls == 0, "a property setter must not run when its key is missing"); + + component->Deserialize({{"limited", 9}}); + check(component->limited == 5 && component->setterCalls == 1, "loading a property must go through its setter"); + check(component->Serialize().at("limited") == 5, "a property must save its current value"); + } + void testReflectedReferencesResolve() { ComponentFactory::Register("ReflectedComponent"); @@ -538,6 +562,7 @@ void RunSerializationTests() { testReflectedRoundTrip(); testReflectedLoadClampsAndKeepsDefaults(); + testReflectedProperties(); testReflectedReferencesResolve(); testFieldLabels(); testTonemapperNames();