diff --git a/CMakeLists.txt b/CMakeLists.txt index a3d3bdc..ed0618c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,11 @@ set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + # Default install location: /dist + set(CMAKE_INSTALL_PREFIX "${CMAKE_SOURCE_DIR}/dist" CACHE PATH "Local export directory" FORCE) +endif() + file( GLOB_RECURSE SRC CONFIGURE @@ -53,6 +58,63 @@ target_include_directories(rlImGui PUBLIC ${RLIMGUI_DIR}) target_link_libraries(rlImGui PUBLIC imgui raylib) +#------------------------------------------------------------------------------------------ +# Framework Library +#------------------------------------------------------------------------------------------ + +include(GNUInstallDirs) + +file( GLOB_RECURSE + ENGINE_SRC + CONFIGURE + "src/engine/*cpp" + "src/engine/*.hpp" +) + +add_library(${PROJECT_NAME}_lib ${ENGINE_SRC}) + + +set_target_properties(${PROJECT_NAME}_lib PROPERTIES + OUTPUT_NAME "varicle" + ) +target_include_directories(${PROJECT_NAME}_lib PUBLIC src vendor/entt) + +target_link_libraries(${PROJECT_NAME}_lib PUBLIC + AssetPacker::assetpacker + rlImGui + imgui + raylib +) + +## Get Metadata +install(TARGETS ${PROJECT_NAME}_lib + EXPORT ${PROJECT_NAME}Targets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} # Static libs (.a / .lib) + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} # Shared libs (.so / .dylib) + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} # Windows DLLs (.dll) + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/engine +) + +## Headers +# Engine +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src/engine/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/engine + FILES_MATCHING PATTERN "*.hpp" +) + +# Entt +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/vendor/entt/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/entt + FILES_MATCHING PATTERN "*.hpp" PATTERN "*.h" +) + + +# install(EXPORT ${PROJECT_NAME}Targets +# FILE ${PROJECT_NAME}Targets.cmake +# NAMESPACE ${PROJECT_NAME}:: +# DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} +# ) + #------------------------------------------------------------------------------------------ # Executable #------------------------------------------------------------------------------------------ @@ -126,3 +188,6 @@ add_custom_target( run DEPENDS ${PROJECT_NAME} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) + +enable_testing() +add_subdirectory(tests) diff --git a/src/engine/core/engine-variant/engine-variant-operation.hpp b/src/engine/core/engine-variant/engine-variant-operation.hpp index ca2af26..1b078f1 100644 --- a/src/engine/core/engine-variant/engine-variant-operation.hpp +++ b/src/engine/core/engine-variant/engine-variant-operation.hpp @@ -68,13 +68,13 @@ class VariantOpManager { return p + (n - p) * req.alpha; }, [&](Vec2 p, Vec2 n) -> EngineVariant::InternalVariant { - return Vec2Lerp(p, n, req.alpha); + return vec2_lerp(p, n, req.alpha); }, [&](Vec3 p, Vec3 n) -> EngineVariant::InternalVariant { - return Vec3Lerp(p, n, req.alpha); + return vec3_lerp(p, n, req.alpha); }, [&](Vec4 p, Vec4 n) -> EngineVariant::InternalVariant { - return Vec4Lerp(p, n, req.alpha); + return vec4_lerp(p, n, req.alpha); }, [&](const std::string &p, const std::string &n) -> EngineVariant::InternalVariant { diff --git a/src/engine/core/engine-variant/engine-variant-property-id.hpp b/src/engine/core/engine-variant/engine-variant-property-id.hpp new file mode 100644 index 0000000..2f043b4 --- /dev/null +++ b/src/engine/core/engine-variant/engine-variant-property-id.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include + +namespace varicle { +namespace PropertyID { +using namespace entt::literals; + +// Guaranteed unique & typo-proof at compile time! +constexpr auto TransformPosition = "transform:position"_hs; +constexpr auto TransformScale = "transform:scale"_hs; +constexpr auto TransformRotation = "transform:rotation"_hs; + +constexpr auto GlobalTransformPosition = "global_transform:position"_hs; +constexpr auto GlobalTransformScale = "global_transform:scale"_hs; +constexpr auto GlobalTransformRotation = "global_transform:rotation"_hs; + +constexpr auto VelocityX = "velocity:x"_hs; +constexpr auto VelocityY = "velocity:y"_hs; + +constexpr auto SpriteRotation = "sprite:rotation"_hs; +constexpr auto SpriteTexture = "sprite:texture"_hs; +constexpr auto SpriteOffsetX = "sprite:offset:x"_hs; +constexpr auto SpriteOffsetY = "sprite:offset:y"_hs; +constexpr auto SpriteWidth = "sprite:width"_hs; +constexpr auto SpriteHeight = "sprite:height"_hs; +constexpr auto SpriteFlipV = "sprite:flip_v"_hs; +constexpr auto SpriteFlipH = "sprite:flip_h"_hs; + +} // namespace PropertyID + +} // namespace varicle diff --git a/src/engine/core/engine-variant/engine-variant-property.hpp b/src/engine/core/engine-variant/engine-variant-property.hpp index 11f3260..d497f4d 100644 --- a/src/engine/core/engine-variant/engine-variant-property.hpp +++ b/src/engine/core/engine-variant/engine-variant-property.hpp @@ -11,7 +11,7 @@ namespace varicle { using VariantGetter = std::function; using VariantSetter = - std::function; + std::function; struct PropertyRef { uint32_t property_id; @@ -22,10 +22,11 @@ struct PropertyRef { return getter ? getter(reg, e) : EngineVariant(); } - void set(entt::registry ®, entt::entity e, + bool set(entt::registry ®, entt::entity e, const EngineVariant &value) const { if (setter) - setter(reg, e, value); + return setter(reg, e, value); + return false; } }; @@ -33,23 +34,33 @@ class PropertyRegistry { public: template static PropertyRef bind_field(uint32_t prop_hash, T Component::*member) { - return PropertyRef{.property_id = prop_hash, + return PropertyRef{ + .property_id = prop_hash, - .setter = - [member](entt::registry ®, entt::entity e, - const EngineVariant &val) { - if (auto *comp = reg.try_get(e)) { - (*comp).*member = val.Get(); - } - }, - .getter = [member](const entt::registry ®, - entt::entity e) -> EngineVariant { - if (auto *component = - reg.try_get(e)) { - return EngineVariant((*component).*member); - } - return EngineVariant(); - }}; + .setter = [member, prop_hash](entt::registry ®, entt::entity e, + const EngineVariant &val) -> bool { + auto *comp = reg.try_get(e); + if (!comp) { + return false; + } + + if (const T *val_ptr = val.try_get()) { + (*comp).*member = *val_ptr; + return true; + } + // Type mismatch caught safely! + std::cout << "[WARN] Property type mismatch: cannot assign " + "value to property hash " + << prop_hash << "\n"; + return false; + }, + .getter = [member](const entt::registry ®, + entt::entity e) -> EngineVariant { + if (auto *component = reg.try_get(e)) { + return EngineVariant((*component).*member); + } + return EngineVariant(); + }}; } }; @@ -76,7 +87,6 @@ class PropertyDatabase { auto it = properites.find(prop_hash); if (it != properites.end()) { it->second.set(reg, e, value); - return true; } return false; } diff --git a/src/engine/core/engine-variant/engine-variant.hpp b/src/engine/core/engine-variant/engine-variant.hpp index bf13e20..5363166 100644 --- a/src/engine/core/engine-variant/engine-variant.hpp +++ b/src/engine/core/engine-variant/engine-variant.hpp @@ -5,10 +5,9 @@ namespace varicle { // Overload helper pattern -template struct Overloaded : Ts... { +template struct Overloaded : Ts... { using Ts::operator()...; }; -template Overloaded(Ts...) -> Overloaded; struct Vec2 { float x = 0.0f; @@ -59,14 +58,14 @@ struct Vec4 { }; // Standalone Interpolation Helpers -inline Vec2 Vec2Lerp(const Vec2 &s, const Vec2 &e, float a) { +inline Vec2 vec2_lerp(const Vec2 &s, const Vec2 &e, float a) { return {s.x + (e.x - s.x) * a, s.y + (e.y - s.y) * a}; } -inline Vec3 Vec3Lerp(const Vec3 &s, const Vec3 &e, float a) { +inline Vec3 vec3_lerp(const Vec3 &s, const Vec3 &e, float a) { return {s.x + (e.x - s.x) * a, s.y + (e.y - s.y) * a, s.z + (e.z - s.z) * a}; } -inline Vec4 Vec4Lerp(const Vec4 &s, const Vec4 &e, float a) { +inline Vec4 vec4_lerp(const Vec4 &s, const Vec4 &e, float a) { return {s.x + (e.x - s.x) * a, s.y + (e.y - s.y) * a, s.z + (e.z - s.z) * a, s.w + (e.w - s.w) * a}; } @@ -87,10 +86,12 @@ class EngineVariant { EngineVariant(std::string v) : data(v) {} EngineVariant(bool v) : data(v) {} - template T Get() const { return std::get(data); } - template T *TryGet() { return std::get_if(&data); } + template T get() const { return std::get(data); } + template const T *try_get() const { + return std::get_if(&data); + } - std::string ToString() const { + std::string to_string() const { return std::visit( Overloaded{[](std::monostate) { return std::string("!"); }, [](float v) { return std::to_string(v); }, @@ -118,33 +119,23 @@ class EngineVariant { friend std::ostream &operator<<(std::ostream &os, const EngineVariant &variant) { - return os << variant.GetTypeName() << variant.ToString(); + return os << variant.get_type_name() << variant.to_string(); } private: InternalVariant data; - std::string GetTypeName() const { + std::string get_type_name() const { return std::visit( - [](auto &&arg) -> std::string { - using T = std::decay_t; - if constexpr (std::is_same_v) - return "Null"; - else if constexpr (std::is_same_v) - return "Float"; - else if constexpr (std::is_same_v) - return "Vec2"; - else if constexpr (std::is_same_v) - return "Vec3"; - else if constexpr (std::is_same_v) - return "Vec4"; - else if constexpr (std::is_same_v) - return "String"; - else if constexpr (std::is_same_v) - return "Bool"; - else - return "Unknown"; - }, + Overloaded{ + [](std::monostate) -> std::string { return "Null"; }, + [](bool) -> std::string { return "Bool"; }, + [](float) -> std::string { return "Float"; }, + [](const Vec2 &) -> std::string { return "Vec2"; }, + [](const Vec3 &) -> std::string { return "Vec3"; }, + [](const Vec4 &) -> std::string { return "Vec4"; }, + [](const std::string &) -> std::string { return "String"; }, + [](const auto &) -> std::string { return "Unknown"; }}, data); } diff --git a/src/engine/ecs/bindings.cpp b/src/engine/ecs/bindings.cpp index c931e82..c500184 100644 --- a/src/engine/ecs/bindings.cpp +++ b/src/engine/ecs/bindings.cpp @@ -1,5 +1,6 @@ #include "bindings.hpp" #include "components.hpp" +#include "engine/core/engine-variant/engine-variant-property-id.hpp" using namespace entt::literals; @@ -9,84 +10,90 @@ void varicle::register_all_component_properties(PropertyDatabase &db) { // LocalTransform2D //---------------------------------------------------------- db.register_property( - "position"_hs, - PropertyRegistry::bind_field("position"_hs, + PropertyID::TransformPosition, + PropertyRegistry::bind_field(PropertyID::TransformPosition, &varicle::LocalTransform2D::position)); - db.register_property("scale"_hs, - PropertyRegistry::bind_field( - "scale"_hs, &varicle::LocalTransform2D::scale)); + db.register_property( + PropertyID::TransformScale, + PropertyRegistry::bind_field(PropertyID::TransformScale, + &varicle::LocalTransform2D::scale)); db.register_property( - "rotation"_hs, - PropertyRegistry::bind_field("rotation"_hs, + PropertyID::TransformRotation, + PropertyRegistry::bind_field(PropertyID::TransformRotation, &varicle::LocalTransform2D::rotation)); //---------------------------------------------------------- // GlobalTransform2D //---------------------------------------------------------- db.register_property( - "global_position"_hs, - PropertyRegistry::bind_field("global_position"_hs, + PropertyID::GlobalTransformPosition, + PropertyRegistry::bind_field(PropertyID::GlobalTransformPosition, &varicle::GlobalTransform2D::position)); - db.register_property("global_scale"_hs, - PropertyRegistry::bind_field( - "global_scale"_hs, &varicle::GlobalTransform2D::scale)); + db.register_property( + PropertyID::GlobalTransformScale, + PropertyRegistry::bind_field(PropertyID::GlobalTransformScale, + &varicle::GlobalTransform2D::scale)); db.register_property( - "global_rotation"_hs, - PropertyRegistry::bind_field("global_rotation"_hs, + PropertyID::GlobalTransformRotation, + PropertyRegistry::bind_field(PropertyID::GlobalTransformRotation, &varicle::GlobalTransform2D::rotation)); //---------------------------------------------------------- // Velocity //---------------------------------------------------------- - db.register_property( - "velocity:x"_hs, - PropertyRegistry::bind_field("velocity:x"_hs, &varicle::Velocity::dx)); + db.register_property(PropertyID::VelocityX, + PropertyRegistry::bind_field(PropertyID::VelocityX, + &varicle::Velocity::dx)); - db.register_property( - "velocity:y"_hs, - PropertyRegistry::bind_field("velocity:y"_hs, &varicle::Velocity::dy)); + db.register_property(PropertyID::VelocityY, + PropertyRegistry::bind_field(PropertyID::VelocityY, + &varicle::Velocity::dy)); //---------------------------------------------------------- // Sprite //---------------------------------------------------------- db.register_property( - "sprite:texture_path"_hs, - PropertyRegistry::bind_field("sprite:texture_path"_hs, &varicle::Sprite::texture_path)); + PropertyID::SpriteTexture, + PropertyRegistry::bind_field(PropertyID::SpriteTexture, + &varicle::Sprite::texture_path)); db.register_property( - "sprite:offset:y"_hs, - PropertyRegistry::bind_field("sprite:offset:y"_hs, &varicle::Sprite::offset_y)); + PropertyID::SpriteOffsetY, + PropertyRegistry::bind_field(PropertyID::SpriteOffsetY, + &varicle::Sprite::offset_y)); db.register_property( - "sprite:offset:x"_hs, - PropertyRegistry::bind_field("sprite:offset:x"_hs, &varicle::Sprite::offset_x)); + PropertyID::SpriteOffsetX, + PropertyRegistry::bind_field(PropertyID::SpriteOffsetX, + &varicle::Sprite::offset_x)); + db.register_property(PropertyID::SpriteWidth, + PropertyRegistry::bind_field(PropertyID::SpriteWidth, + &varicle::Sprite::width)); db.register_property( - "sprite:width"_hs, - PropertyRegistry::bind_field("sprite:width"_hs, &varicle::Sprite::width)); - + PropertyID::SpriteHeight, + PropertyRegistry::bind_field(PropertyID::SpriteHeight, + &varicle::Sprite::height)); db.register_property( - "sprite:height"_hs, - PropertyRegistry::bind_field("sprite:height"_hs, &varicle::Sprite::height)); - - db.register_property( - "sprite:flip_h"_hs, - PropertyRegistry::bind_field("sprite:flip_h"_hs, &varicle::Sprite::flip_h)); + PropertyID::SpriteFlipH, + PropertyRegistry::bind_field(PropertyID::SpriteFlipH, + &varicle::Sprite::flip_h)); db.register_property( - "sprite:flip_v"_hs, - PropertyRegistry::bind_field("sprite:flip_v"_hs, &varicle::Sprite::flip_v)); + PropertyID::SpriteFlipV, + PropertyRegistry::bind_field(PropertyID::SpriteFlipV, + &varicle::Sprite::flip_v)); db.register_property( - "sprite:rotation"_hs, - PropertyRegistry::bind_field("sprite:rotation"_hs, &varicle::Sprite::rotation)); - + PropertyID::SpriteRotation, + PropertyRegistry::bind_field(PropertyID::SpriteRotation, + &varicle::Sprite::rotation)); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..5babd33 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,14 @@ +file(GLOB TEST_SOURCES "*.cpp") + +# Loop through each .cpp file and build a separate executable for it! +foreach(TEST_FILE ${TEST_SOURCES}) + # Extract filename without path/extension (e.g., "test_property_reflection") + get_filename_component(TEST_NAME ${TEST_FILE} NAME_WE) + + # Create individual target for this specific test + add_executable(${TEST_NAME} ${TEST_FILE}) + target_link_libraries(${TEST_NAME} PRIVATE my_app_lib) + + # Register each as an individual test with CTest + add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME}) +endforeach() diff --git a/tests/test_property_reflection.cpp b/tests/test_property_reflection.cpp new file mode 100644 index 0000000..b91c589 --- /dev/null +++ b/tests/test_property_reflection.cpp @@ -0,0 +1,79 @@ +#include +#include +#include "engine/ecs/bindings.hpp" +#include "engine/ecs/components.hpp" +#include "engine/core/engine-variant/engine-variant.hpp" +#include "engine/core/engine-variant/engine-variant-property.hpp" +#include "engine/core/engine-variant/engine-variant-property-id.hpp" +#include + + +using namespace varicle; + +int main() { + std::cout << "[TEST] Starting Property Reflection & Type Safety Tests...\n"; + + // Setup ECS Registry & Property DB + entt::registry registry; + PropertyDatabase propDB; + register_all_component_properties(propDB); + + // Create an Entity with LocalTransform2D & Sprite + entt::entity player = registry.create(); + registry.emplace(player, Vec2{10.0f, 20.0f}, 1.5f); + registry.emplace(player,"",0.f,0.f,120.f,120.f,false,false,45.0f); + + // ========================================================================= + // TEST 1: Reading Values via PropertyID (Getter) + // ========================================================================= + { + Vec2 pos = propDB.get_value(registry,player,varicle::PropertyID::TransformPosition).get(); + float scale = propDB.get_value(registry,player,varicle::PropertyID::TransformScale).get(); + float rot = propDB.get_value(registry,player,varicle::PropertyID::SpriteRotation).get(); + + assert(pos.x == 10.0f && pos.y == 20.0f && "Transform position getter failed!"); + assert(scale == 1.5f && "Transform scale getter failed!"); + assert(rot == 45.0f && "Sprite rotation getter failed!"); + + std::cout << " [PASS] Test 1: Getters retrieved correct values.\n"; + } + + // ========================================================================= + // TEST 2: Writing Values via PropertyID (Setter) + // ========================================================================= + { + propDB.set_value(registry,player,varicle::PropertyID::TransformPosition, Vec2{100.0f, 200.0f}); + std::cout << "setting value: " << (propDB.set_value(registry,player,varicle::PropertyID::SpriteRotation, 90.0f) ? "success" : "failed"); + propDB.set_value(registry,player,varicle::PropertyID::SpriteFlipH, true); + + // Verify the actual component struct in EnTT was mutated directly! + const auto& transform = registry.get(player); + const auto& sprite = registry.get(player); + + assert(transform.position.x == 100.0f && transform.position.y == 200.0f && "Position write failed!"); + assert(sprite.rotation == 90.0f && "Rotation write failed!"); + assert(sprite.flip_h == true && "FlipH write failed!"); + + std::cout << " [PASS] Test 2: Setters successfully mutated component struct.\n"; + } + + // ========================================================================= + // TEST 3: Type Safety Guard (Attempting Wrong Type Assignment) + // ========================================================================= + { + std::cout << " [INFO] Expecting type safety warning below:\n"; + + // Try to push a std::string into a float property (SpriteRotation) + EngineVariant badType(std::string("WrongTypeData")); + propDB.set_value(registry,player,varicle::PropertyID::SpriteRotation, badType); + + // Verify value was NOT modified because type mismatch was caught + const auto& sprite = registry.get(player); + assert(sprite.rotation == 90.0f && "Type safety guard failed! Wrong type was written!"); + + std::cout << " [PASS] Test 3: Type Safety Guard blocked invalid type write.\n"; + } + + std::cout << "\n[SUCCESS] All reflection tests passed successfully!\n"; + return 0; +}