From d11044df6fc58907be459cbd854baef77c3e773d Mon Sep 17 00:00:00 2001 From: Bram Verhulst Date: Fri, 30 Oct 2026 18:50:47 +0100 Subject: [PATCH] batman --- .gitignore | 69 +++++++++++++++++++++++++ CMakeLists.txt | 13 +++++ main.cpp | 136 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 218 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fa074af --- /dev/null +++ b/.gitignore @@ -0,0 +1,69 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Linker files +*.ilk + +# Debugger Files +*.pdb + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll +*.so.* + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# Build directories +build/ +Build/ +build-*/ + +# CMake generated files +CMakeFiles/ +CMakeCache.txt +cmake_install.cmake +Makefile +install_manifest.txt +compile_commands.json + +# Temporary files +*.tmp +*.log +*.bak +*.swp + +# vcpkg +vcpkg_installed/ + +# debug information files +*.dwo + +# test output & cache +Testing/ +.cache/ +.idea diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..125c3f4 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.20) +project(FuckAssReflection LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +include(FetchContent) +FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz) +FetchContent_MakeAvailable(json) + +add_executable(reflection_example main.cpp) +target_link_libraries(reflection_example PRIVATE nlohmann_json::nlohmann_json) diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..0264ac2 --- /dev/null +++ b/main.cpp @@ -0,0 +1,136 @@ +#include +#include +#include +#include +#include + +#include + +using json = nlohmann::json; + +struct FieldOptions { + double min = 0; + double max = 0; + bool hasMin = false; + bool hasMax = false; + bool hidden = false; + bool transient = false; + bool readOnly = false; + std::string_view label; +}; + +struct FieldOps { + void (*save)(const void*, json&); + void (*load)(void*, const json&); + std::string (*show)(const void*); +}; + +template +struct Codec { + static void Save(const void* value, json& out) { + out = *static_cast(value); + } + + static void Load(void* value, const json& in) { + *static_cast(value) = in.get(); + } + + static std::string Show(const void* value) { + if constexpr (std::same_as) return *static_cast(value); + else if constexpr (std::same_as) return *static_cast(value) ? "true" : "false"; + else return std::to_string(*static_cast(value)); + } + + static constexpr FieldOps ops{Save, Load, Show}; +}; + +class FieldVisitor { +public: + template + void Field(std::string_view key, T& value, FieldOptions options = {}) { + Visit(key, &value, options, Codec::ops); + } + +protected: + virtual void Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops) = 0; +}; + +class JsonWriter final : public FieldVisitor { +public: + json Result() const { return result; } + +protected: + void Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops) override { + if (!options.transient) ops.save(value, result[std::string(key)]); + } + +private: + json result = json::object(); +}; + +class JsonReader final : public FieldVisitor { +public: + explicit JsonReader(const json& source) : data(source) {} + +protected: + void Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops) override { + if (options.transient || !data.is_object()) return; + const auto it = data.find(std::string(key)); + if (it != data.end()) ops.load(value, *it); + } + +private: + const json& data; +}; + +class ConsoleInspector final : public FieldVisitor { +protected: + void Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops) override { + if (options.hidden) return; + const auto label = options.label.empty() ? key : options.label; + std::cout << label << " = " << ops.show(value); + if (options.readOnly) std::cout << " [read only]"; + if (options.hasMin || options.hasMax) { + std::cout << " ["; + if (options.hasMin) std::cout << options.min; + std::cout << ", "; + if (options.hasMax) std::cout << options.max; + std::cout << "]"; + } + std::cout << '\n'; + } +}; + +struct Player { + std::string name = "Ada"; + int health = 100; + float speed = 4.5f; + bool active = true; + std::string runtimeNote = "created at runtime"; + + void Reflect(FieldVisitor& visitor) { + visitor.Field("name", name); + visitor.Field("health", health, {.min = 0, .max = 100, .hasMin = true, .hasMax = true}); + visitor.Field("speed", speed, {.min = 0, .hasMin = true, .label = "Move Speed"}); + visitor.Field("active", active); + visitor.Field("internalId", runtimeNote, {.hidden = true}); + visitor.Field("runtimeNote", runtimeNote, {.transient = true}); + } +}; + +int main() { + Player player; + JsonWriter writer; + player.Reflect(writer); + const json saved = writer.Result(); + std::cout << "Saved JSON\n" << saved.dump(2) << "\n\n"; + + player.health = 1; + player.speed = 0; + JsonReader reader(saved); + player.Reflect(reader); + + std::cout << "Console inspector\n"; + ConsoleInspector inspector; + player.Reflect(inspector); +}