From fe89b84222014f6eaca7aaae0e8c1352f9602019 Mon Sep 17 00:00:00 2001 From: antopilo Date: Sat, 3 May 2025 11:56:59 -0400 Subject: [PATCH] Added automated reflection based json serialization for reflected components --- .../Resource/Serializer/ComponentSerializer.h | 86 +++++++++++++++++++ Test/NuakeTest/Source/SerializationTest.cpp | 44 +++++++++- 2 files changed, 129 insertions(+), 1 deletion(-) diff --git a/Nuake/Source/Nuake/Resource/Serializer/ComponentSerializer.h b/Nuake/Source/Nuake/Resource/Serializer/ComponentSerializer.h index 137c86c5..71678ee8 100644 --- a/Nuake/Source/Nuake/Resource/Serializer/ComponentSerializer.h +++ b/Nuake/Source/Nuake/Resource/Serializer/ComponentSerializer.h @@ -90,9 +90,95 @@ namespace Nuake std::string value = fieldVal.cast(); cursor[displayName] = value; } + else if (auto prop = dataType.prop(HashedFieldPropName::IsEnum); prop) + { + auto enumMeta = dataType.type(); + // Fallback to integer value if name not available + cursor[displayName] = static_cast(fieldVal.cast()); + } } return jsonSnippet; } + + template + void Deserialize(const json& jsonSnippet, T& component) + { + const entt::meta_type meta = entt::resolve(); + if (!meta) return; + + entt::meta_any metaAny = meta.from_void(static_cast(&component)); + if (!metaAny) return; + + const std::string componentName = Component::GetName(meta); + if (!jsonSnippet.contains(componentName)) return; + + const json& cursor = jsonSnippet.at(componentName); + + for (auto [fst, dataMember] : meta.data()) + { + auto propName = dataMember.prop(HashedName::DisplayName); + if (!propName) continue; + + const char* displayNameC = *propName.value().try_cast(); + std::string displayName = displayNameC; + + if (!cursor.contains(displayName)) continue; + + const entt::meta_type fieldType = dataMember.type(); + + if (fieldType == entt::resolve()) + { + dataMember.set(metaAny, cursor.at(displayName).get()); + } + else if (fieldType == entt::resolve()) + { + dataMember.set(metaAny, cursor.at(displayName).get()); + } + else if (fieldType == entt::resolve()) + { + dataMember.set(metaAny, cursor.at(displayName).get()); + } + else if (fieldType == entt::resolve()) + { + Vector2 vec; + vec.x = cursor.at(displayName).at("x").get(); + vec.y = cursor.at(displayName).at("y").get(); + dataMember.set(metaAny, vec); + } + else if (fieldType == entt::resolve()) + { + Vector3 vec; + vec.x = cursor.at(displayName).at("x").get(); + vec.y = cursor.at(displayName).at("y").get(); + vec.z = cursor.at(displayName).at("z").get(); + dataMember.set(metaAny, vec); + } + else if (fieldType == entt::resolve()) + { + Vector4 vec; + vec.x = cursor.at(displayName).at("x").get(); + vec.y = cursor.at(displayName).at("y").get(); + vec.z = cursor.at(displayName).at("z").get(); + vec.w = cursor.at(displayName).at("w").get(); + dataMember.set(metaAny, vec); + } + else if (fieldType == entt::resolve()) + { + std::string fileKey = "file" + displayName; + if (cursor.contains(fileKey)) + { + //std::string relativePath = cursor.at(fileKey).get(); + //ResourceFile resource; + //resource.file = std::make_shared(relativePath); + //dataMember.set(metaAny, resource); + } + } + else if (fieldType == entt::resolve()) + { + dataMember.set(metaAny, cursor.at(displayName).get()); + } + } + } }; } \ No newline at end of file diff --git a/Test/NuakeTest/Source/SerializationTest.cpp b/Test/NuakeTest/Source/SerializationTest.cpp index 0a5c1d6d..cf91a952 100644 --- a/Test/NuakeTest/Source/SerializationTest.cpp +++ b/Test/NuakeTest/Source/SerializationTest.cpp @@ -8,6 +8,14 @@ namespace Serialization { using namespace Nuake; + enum class TestEnum : int32_t + { + One, + Two, + Three, + Four + }; + class TestData : public Component { NUAKECOMPONENT(TestData, "TestData"); @@ -19,6 +27,7 @@ namespace Serialization Vector2 myVec2; Vector3 myVec3; Vector4 myVec4; + TestEnum myEnum; static void InitializeComponentClass() { @@ -28,6 +37,7 @@ namespace Serialization BindComponentField<&TestData::myVec2>("myVec2", "myVec2"); BindComponentField<&TestData::myVec3>("myVec3", "myVec3"); BindComponentField<&TestData::myVec4>("myVec4", "myVec4"); + BindComponentField<&TestData::myEnum>("myEnum", "myEnum"); } }; @@ -42,7 +52,8 @@ namespace Serialization .myString = "Hello World", .myVec2 = Vector2(1, 2), .myVec3 = Vector3(3, 4, 5), - .myVec4 = Vector4(6, 7, 8, 9) + .myVec4 = Vector4(6, 7, 8, 9), + .myEnum = TestEnum::Two }; // Serialize into json @@ -76,4 +87,35 @@ namespace Serialization REQUIRE(result["TestData"]["myVec4"]["z"] == testData.myVec4.z); REQUIRE(result["TestData"]["myVec4"]["w"] == testData.myVec4.w); } + + TEST_CASE("Deserialize Struct", "[Serialization]") + { + // Initialize component + TestData::InternalInitializeClass(); + TestData testData = + { + .myInt = 1337, + .myBool = true, + .myString = "Hello World", + .myVec2 = Vector2(1, 2), + .myVec3 = Vector3(3, 4, 5), + .myVec4 = Vector4(6, 7, 8, 9) + }; + + // Serialize into json + ComponentSerializer serializer; + json result = serializer.Serialize(testData); + + // Deserialize + TestData inTestData = TestData{ }; + serializer.Deserialize(result, inTestData); + + // Test JSON result + REQUIRE(inTestData.myInt == testData.myInt); + REQUIRE(inTestData.myBool == testData.myBool); + REQUIRE(inTestData.myString == testData.myString); + REQUIRE(inTestData.myVec2 == testData.myVec2); + REQUIRE(inTestData.myVec3 == testData.myVec3); + REQUIRE(inTestData.myVec4 == testData.myVec4); + } } \ No newline at end of file