feat: Implement complete IDataNode/IDataTree system with JSON backend
Major feature: Unified config/data/runtime tree system
**New System Architecture:**
- Unified data tree for config, persistent data, and runtime state
- Three separate roots: config/ (read-only + hot-reload), data/ (read-write + save), runtime/ (temporary)
- Support for modding, saves, and hot-reload in single system
**Interfaces:**
- IDataValue: Abstract data value interface (type-safe access)
- IDataNode: Tree node with navigation, search, and modification
- IDataTree: Root container with config/data/runtime management
**Concrete Implementations:**
- JsonDataValue: nlohmann::json backed value
- JsonDataNode: Full tree navigation with pattern matching & queries
- JsonDataTree: File-based JSON storage with hot-reload
**Features:**
- Pattern matching search (wildcards support)
- Property-based queries with predicates
- SHA256 hashing for validation/sync
- Hot-reload for config/ directory
- Save operations for data/ persistence
- Read-only enforcement for config/
**API Changes:**
- All namespaces changed from 'warfactory' to 'grove'
- IDataTree: Added getConfigRoot(), getDataRoot(), getRuntimeRoot()
- IDataTree: Added saveData(), saveNode() for persistence
- IDataNode: Added setChild(), removeChild(), clearChildren()
- CMakeLists.txt: Added OpenSSL dependency for hashing
**Usage:**
```cpp
auto tree = DataTreeFactory::create("json", "./gamedata");
auto config = tree->getConfigRoot(); // Read-only game config
auto data = tree->getDataRoot(); // Player saves
auto runtime = tree->getRuntimeRoot(); // Temporary state
// Hot-reload config on file changes
if (tree->reloadIfChanged()) { /* refresh modules */ }
// Save player progress
data->setChild("progress", progressNode);
tree->saveData();
```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
c01e00559b
commit
fad105afb2
@ -37,12 +37,20 @@ if(GROVE_BUILD_IMPLEMENTATIONS)
|
|||||||
add_library(grove_impl STATIC
|
add_library(grove_impl STATIC
|
||||||
src/ImGuiUI.cpp
|
src/ImGuiUI.cpp
|
||||||
src/ResourceRegistry.cpp
|
src/ResourceRegistry.cpp
|
||||||
|
src/JsonDataValue.cpp
|
||||||
|
src/JsonDataNode.cpp
|
||||||
|
src/JsonDataTree.cpp
|
||||||
|
src/DataTreeFactory.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(grove_impl PUBLIC
|
target_link_libraries(grove_impl PUBLIC
|
||||||
GroveEngine::core
|
GroveEngine::core
|
||||||
|
OpenSSL::Crypto
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Find OpenSSL for SHA256 hashing
|
||||||
|
find_package(OpenSSL REQUIRED)
|
||||||
|
|
||||||
# If imgui is available from parent project, link it
|
# If imgui is available from parent project, link it
|
||||||
if(TARGET imgui_backends)
|
if(TARGET imgui_backends)
|
||||||
target_link_libraries(grove_impl PUBLIC imgui_backends)
|
target_link_libraries(grove_impl PUBLIC imgui_backends)
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
|
|
||||||
class SerializationRegistry;
|
class SerializationRegistry;
|
||||||
|
|
||||||
@ -26,4 +26,4 @@ protected:
|
|||||||
void unregisterFromSerialization();
|
void unregisterFromSerialization();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace warfactory
|
} // namespace grove
|
||||||
@ -4,7 +4,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include "IDataTree.h"
|
#include "IDataTree.h"
|
||||||
|
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Factory for creating data tree instances
|
* @brief Factory for creating data tree instances
|
||||||
@ -20,4 +20,4 @@ public:
|
|||||||
static std::unique_ptr<IDataTree> create(const std::string& type, const std::string& sourcePath);
|
static std::unique_ptr<IDataTree> create(const std::string& type, const std::string& sourcePath);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace warfactory
|
} // namespace grove
|
||||||
@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Debug engine implementation with comprehensive logging
|
* @brief Debug engine implementation with comprehensive logging
|
||||||
@ -86,4 +86,4 @@ public:
|
|||||||
void setLogLevel(spdlog::level::level_enum level);
|
void setLogLevel(spdlog::level::level_enum level);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace warfactory
|
} // namespace grove
|
||||||
@ -8,7 +8,7 @@
|
|||||||
#include "IEngine.h"
|
#include "IEngine.h"
|
||||||
#include "DebugEngine.h"
|
#include "DebugEngine.h"
|
||||||
|
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Factory for creating engine implementations
|
* @brief Factory for creating engine implementations
|
||||||
@ -102,4 +102,4 @@ private:
|
|||||||
static std::string toLowercase(const std::string& str);
|
static std::string toLowercase(const std::string& str);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace warfactory
|
} // namespace grove
|
||||||
@ -8,12 +8,12 @@
|
|||||||
#include "IDataTree.h"
|
#include "IDataTree.h"
|
||||||
|
|
||||||
// Forward declarations
|
// Forward declarations
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
class IEngine;
|
class IEngine;
|
||||||
class IModuleSystem;
|
class IModuleSystem;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Global system orchestrator - First launched, last shutdown
|
* @brief Global system orchestrator - First launched, last shutdown
|
||||||
@ -158,4 +158,4 @@ public:
|
|||||||
virtual json getSystemHealthReport() = 0;
|
virtual json getSystemHealthReport() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace warfactory
|
} // namespace grove
|
||||||
@ -6,7 +6,7 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
#include "IDataValue.h"
|
#include "IDataValue.h"
|
||||||
|
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Interface for a single node in the data tree
|
* @brief Interface for a single node in the data tree
|
||||||
@ -226,6 +226,36 @@ public:
|
|||||||
* @return Node type identifier
|
* @return Node type identifier
|
||||||
*/
|
*/
|
||||||
virtual std::string getNodeType() const = 0;
|
virtual std::string getNodeType() const = 0;
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// TREE MODIFICATION (For data/ and runtime/ nodes)
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Add or update a child node
|
||||||
|
* @param name Child name
|
||||||
|
* @param node Child node to add/replace
|
||||||
|
*
|
||||||
|
* If a child with this name already exists, it will be replaced.
|
||||||
|
* Only works for data/ and runtime/ nodes. Config nodes are read-only.
|
||||||
|
*/
|
||||||
|
virtual void setChild(const std::string& name, std::unique_ptr<IDataNode> node) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Remove a child node
|
||||||
|
* @param name Child name to remove
|
||||||
|
* @return true if child was found and removed
|
||||||
|
*
|
||||||
|
* Only works for data/ and runtime/ nodes. Config nodes are read-only.
|
||||||
|
*/
|
||||||
|
virtual bool removeChild(const std::string& name) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Clear all children from this node
|
||||||
|
*
|
||||||
|
* Only works for data/ and runtime/ nodes. Config nodes are read-only.
|
||||||
|
*/
|
||||||
|
virtual void clearChildren() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace warfactory
|
} // namespace grove
|
||||||
@ -5,12 +5,18 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
#include "IDataNode.h"
|
#include "IDataNode.h"
|
||||||
|
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Interface for the root data tree container
|
* @brief Interface for the root data tree container
|
||||||
*
|
*
|
||||||
* Manages the entire tree structure and provides hot-reload capabilities
|
* Unified system for configuration, persistent data, and runtime state.
|
||||||
|
* Supports hot-reload for config and persistence for data.
|
||||||
|
*
|
||||||
|
* Tree Structure:
|
||||||
|
* - config/ : Read-only game configuration (hot-reload enabled, moddable)
|
||||||
|
* - data/ : Persistent player data (read-write, saved to disk)
|
||||||
|
* - runtime/ : Temporary runtime state (read-write, never saved)
|
||||||
*/
|
*/
|
||||||
class IDataTree {
|
class IDataTree {
|
||||||
public:
|
public:
|
||||||
@ -21,37 +27,91 @@ public:
|
|||||||
// ========================================
|
// ========================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get root node of the tree
|
* @brief Get root node of the entire tree
|
||||||
* @return Root node
|
* @return Root node containing config/, data/, runtime/
|
||||||
|
*
|
||||||
|
* WARNING: This gives access to everything. Use getConfigRoot(),
|
||||||
|
* getDataRoot(), or getRuntimeRoot() for isolated access.
|
||||||
*/
|
*/
|
||||||
virtual std::unique_ptr<IDataNode> getRoot() = 0;
|
virtual std::unique_ptr<IDataNode> getRoot() = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get node by path from root
|
* @brief Get node by path from root
|
||||||
* @param path Path from root (e.g., "vehicles/tanks/heavy")
|
* @param path Path from root (e.g., "config/vehicles/tanks/heavy")
|
||||||
* @return Node at path or nullptr if not found
|
* @return Node at path or nullptr if not found
|
||||||
*/
|
*/
|
||||||
virtual std::unique_ptr<IDataNode> getNode(const std::string& path) = 0;
|
virtual std::unique_ptr<IDataNode> getNode(const std::string& path) = 0;
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// MANUAL HOT-RELOAD (SIMPLE & EFFECTIVE)
|
// SEPARATE ROOTS (Recommended Access Pattern)
|
||||||
// ========================================
|
// ========================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Check if source files have changed
|
* @brief Get config tree root (read-only, hot-reload enabled)
|
||||||
* @return true if changes detected
|
* @return Config root node (config/)
|
||||||
|
*
|
||||||
|
* Use for: Game configuration, unit stats, modding
|
||||||
|
*/
|
||||||
|
virtual std::unique_ptr<IDataNode> getConfigRoot() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get persistent data root (read-write, saved to disk)
|
||||||
|
* @return Data root node (data/)
|
||||||
|
*
|
||||||
|
* Use for: Campaign progress, unlocks, player statistics
|
||||||
|
*/
|
||||||
|
virtual std::unique_ptr<IDataNode> getDataRoot() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get runtime data root (read-write, never saved)
|
||||||
|
* @return Runtime root node (runtime/)
|
||||||
|
*
|
||||||
|
* Use for: Current game state, temporary calculations, caches
|
||||||
|
*/
|
||||||
|
virtual std::unique_ptr<IDataNode> getRuntimeRoot() = 0;
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// SAVE OPERATIONS
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Save all persistent data to disk
|
||||||
|
* @return true if save succeeded
|
||||||
|
*
|
||||||
|
* Saves the entire data/ subtree to disk. Does not affect config/ or runtime/.
|
||||||
|
*/
|
||||||
|
virtual bool saveData() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Save specific node and its subtree
|
||||||
|
* @param path Path to node to save (e.g., "data/campaign/progress")
|
||||||
|
* @return true if save succeeded
|
||||||
|
*
|
||||||
|
* Allows granular saves for performance. Only works for data/ paths.
|
||||||
|
*/
|
||||||
|
virtual bool saveNode(const std::string& path) = 0;
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// HOT-RELOAD (Config Only)
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if config files have changed
|
||||||
|
* @return true if changes detected in config/
|
||||||
*/
|
*/
|
||||||
virtual bool checkForChanges() = 0;
|
virtual bool checkForChanges() = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Reload entire tree if changes detected
|
* @brief Reload config tree if files changed
|
||||||
* @return true if reload was performed
|
* @return true if reload was performed
|
||||||
|
*
|
||||||
|
* Only reloads config/. Does not affect data/ or runtime/.
|
||||||
*/
|
*/
|
||||||
virtual bool reloadIfChanged() = 0;
|
virtual bool reloadIfChanged() = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Register callback for when tree is reloaded
|
* @brief Register callback for when config is reloaded
|
||||||
* @param callback Function called after successful reload
|
* @param callback Function called after successful config reload
|
||||||
*/
|
*/
|
||||||
virtual void onTreeReloaded(std::function<void()> callback) = 0;
|
virtual void onTreeReloaded(std::function<void()> callback) = 0;
|
||||||
|
|
||||||
@ -66,4 +126,4 @@ public:
|
|||||||
virtual std::string getType() = 0;
|
virtual std::string getType() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace warfactory
|
} // namespace grove
|
||||||
@ -4,7 +4,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Interface for data values - abstracts underlying data format
|
* @brief Interface for data values - abstracts underlying data format
|
||||||
@ -40,4 +40,4 @@ public:
|
|||||||
virtual std::string toString() const = 0;
|
virtual std::string toString() const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace warfactory
|
} // namespace grove
|
||||||
|
|||||||
@ -4,12 +4,12 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
// Forward declarations to avoid circular dependencies
|
// Forward declarations to avoid circular dependencies
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
class IModuleSystem;
|
class IModuleSystem;
|
||||||
class IIO;
|
class IIO;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
|
|
||||||
enum class EngineType {
|
enum class EngineType {
|
||||||
DEBUG = 0,
|
DEBUG = 0,
|
||||||
@ -120,4 +120,4 @@ public:
|
|||||||
virtual EngineType getType() const = 0;
|
virtual EngineType getType() const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace warfactory
|
} // namespace grove
|
||||||
@ -6,7 +6,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include "IDataNode.h"
|
#include "IDataNode.h"
|
||||||
|
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
|
|
||||||
enum class IOType {
|
enum class IOType {
|
||||||
INTRA = 0, // Same process
|
INTRA = 0, // Same process
|
||||||
@ -98,4 +98,4 @@ public:
|
|||||||
virtual IOType getType() const = 0;
|
virtual IOType getType() const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace warfactory
|
} // namespace grove
|
||||||
@ -6,11 +6,11 @@
|
|||||||
#include "ITaskScheduler.h"
|
#include "ITaskScheduler.h"
|
||||||
|
|
||||||
// Forward declarations
|
// Forward declarations
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
class IIO;
|
class IIO;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -108,4 +108,4 @@ public:
|
|||||||
virtual std::string getType() const = 0;
|
virtual std::string getType() const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace warfactory
|
} // namespace grove
|
||||||
@ -5,12 +5,12 @@
|
|||||||
#include "ITaskScheduler.h"
|
#include "ITaskScheduler.h"
|
||||||
|
|
||||||
// Forward declarations to avoid circular dependencies
|
// Forward declarations to avoid circular dependencies
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
class IModule;
|
class IModule;
|
||||||
class IIO;
|
class IIO;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
|
|
||||||
enum class ModuleSystemType {
|
enum class ModuleSystemType {
|
||||||
SEQUENTIAL = 0,
|
SEQUENTIAL = 0,
|
||||||
@ -89,4 +89,4 @@ public:
|
|||||||
virtual ModuleSystemType getType() const = 0;
|
virtual ModuleSystemType getType() const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace warfactory
|
} // namespace grove
|
||||||
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Factory for creating IO transport implementations
|
* @brief Factory for creating IO transport implementations
|
||||||
@ -131,4 +131,4 @@ private:
|
|||||||
static std::string generateEndpoint(IOType ioType);
|
static std::string generateEndpoint(IOType ioType);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace warfactory
|
} // namespace grove
|
||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Interface for geological regions during world generation
|
* @brief Interface for geological regions during world generation
|
||||||
@ -47,4 +47,4 @@ public:
|
|||||||
virtual bool canFuseWith(const IRegion* other) const = 0;
|
virtual bool canFuseWith(const IRegion* other) const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace warfactory
|
} // namespace grove
|
||||||
@ -3,7 +3,7 @@
|
|||||||
#include "IDataNode.h"
|
#include "IDataNode.h"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
|
|
||||||
class ISerializable {
|
class ISerializable {
|
||||||
public:
|
public:
|
||||||
@ -13,4 +13,4 @@ public:
|
|||||||
virtual void deserialize(const IDataNode& data) = 0;
|
virtual void deserialize(const IDataNode& data) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace warfactory
|
} // namespace grove
|
||||||
@ -4,7 +4,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include "IDataNode.h"
|
#include "IDataNode.h"
|
||||||
|
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Task scheduling interface for module delegation to execution system
|
* @brief Task scheduling interface for module delegation to execution system
|
||||||
@ -100,4 +100,4 @@ public:
|
|||||||
virtual std::unique_ptr<IDataNode> getCompletedTask() = 0;
|
virtual std::unique_ptr<IDataNode> getCompletedTask() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace warfactory
|
} // namespace grove
|
||||||
@ -4,7 +4,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
@ -126,4 +126,4 @@ public:
|
|||||||
virtual void setState(const json& state) = 0;
|
virtual void setState(const json& state) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace warfactory
|
} // namespace grove
|
||||||
@ -4,7 +4,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
@ -337,4 +337,4 @@ constexpr const char* toString(Orientation orient) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace warfactory
|
} // namespace grove
|
||||||
@ -13,7 +13,7 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief ImGui implementation of IUI interface
|
* @brief ImGui implementation of IUI interface
|
||||||
@ -704,4 +704,4 @@ private:
|
|||||||
void renderLogConsole();
|
void renderLogConsole();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace warfactory
|
} // namespace grove
|
||||||
@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
|
|
||||||
// Interface for message delivery to avoid circular include
|
// Interface for message delivery to avoid circular include
|
||||||
class IIntraIODelivery {
|
class IIntraIODelivery {
|
||||||
@ -132,4 +132,4 @@ public:
|
|||||||
const std::string& getInstanceId() const;
|
const std::string& getInstanceId() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace warfactory
|
} // namespace grove
|
||||||
@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
|
|
||||||
class IntraIO; // Forward declaration
|
class IntraIO; // Forward declaration
|
||||||
class IIntraIODelivery; // Forward declaration
|
class IIntraIODelivery; // Forward declaration
|
||||||
@ -88,4 +88,4 @@ public:
|
|||||||
static IntraIOManager& getInstance();
|
static IntraIOManager& getInstance();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace warfactory
|
} // namespace grove
|
||||||
108
include/grove/JsonDataNode.h
Normal file
108
include/grove/JsonDataNode.h
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "IDataNode.h"
|
||||||
|
#include "JsonDataValue.h"
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
#include <map>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace grove {
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Concrete implementation of IDataNode backed by JSON
|
||||||
|
*
|
||||||
|
* Represents a node in the hierarchical data tree. Can have:
|
||||||
|
* - Children nodes (map of name -> node)
|
||||||
|
* - Own data (JSON value)
|
||||||
|
* - Path in the tree for identification
|
||||||
|
*/
|
||||||
|
class JsonDataNode : public IDataNode {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Create a node with name and optional data
|
||||||
|
* @param name Node name
|
||||||
|
* @param data Optional JSON data for this node
|
||||||
|
* @param parent Optional parent node (for path tracking)
|
||||||
|
* @param readOnly Whether this node is read-only (for config/)
|
||||||
|
*/
|
||||||
|
JsonDataNode(const std::string& name,
|
||||||
|
const json& data = json::object(),
|
||||||
|
JsonDataNode* parent = nullptr,
|
||||||
|
bool readOnly = false);
|
||||||
|
|
||||||
|
virtual ~JsonDataNode() = default;
|
||||||
|
|
||||||
|
// Tree navigation
|
||||||
|
std::unique_ptr<IDataNode> getChild(const std::string& name) override;
|
||||||
|
std::vector<std::string> getChildNames() override;
|
||||||
|
bool hasChildren() override;
|
||||||
|
|
||||||
|
// Exact search in children
|
||||||
|
std::vector<IDataNode*> getChildrenByName(const std::string& name) override;
|
||||||
|
bool hasChildrenByName(const std::string& name) const override;
|
||||||
|
IDataNode* getFirstChildByName(const std::string& name) override;
|
||||||
|
|
||||||
|
// Pattern matching search
|
||||||
|
std::vector<IDataNode*> getChildrenByNameMatch(const std::string& pattern) override;
|
||||||
|
bool hasChildrenByNameMatch(const std::string& pattern) const override;
|
||||||
|
IDataNode* getFirstChildByNameMatch(const std::string& pattern) override;
|
||||||
|
|
||||||
|
// Query by properties
|
||||||
|
std::vector<IDataNode*> queryByProperty(const std::string& propName,
|
||||||
|
const std::function<bool(const IDataValue&)>& predicate) override;
|
||||||
|
|
||||||
|
// Node's own data
|
||||||
|
std::unique_ptr<IDataValue> getData() const override;
|
||||||
|
bool hasData() const override;
|
||||||
|
void setData(std::unique_ptr<IDataValue> data) override;
|
||||||
|
|
||||||
|
// Typed data access
|
||||||
|
std::string getString(const std::string& name, const std::string& defaultValue = "") const override;
|
||||||
|
int getInt(const std::string& name, int defaultValue = 0) const override;
|
||||||
|
double getDouble(const std::string& name, double defaultValue = 0.0) const override;
|
||||||
|
bool getBool(const std::string& name, bool defaultValue = false) const override;
|
||||||
|
bool hasProperty(const std::string& name) const override;
|
||||||
|
|
||||||
|
// Hash system
|
||||||
|
std::string getDataHash() override;
|
||||||
|
std::string getTreeHash() override;
|
||||||
|
std::string getSubtreeHash(const std::string& childPath) override;
|
||||||
|
|
||||||
|
// Metadata
|
||||||
|
std::string getPath() const override;
|
||||||
|
std::string getName() const override;
|
||||||
|
std::string getNodeType() const override;
|
||||||
|
|
||||||
|
// Tree modification
|
||||||
|
void setChild(const std::string& name, std::unique_ptr<IDataNode> node) override;
|
||||||
|
bool removeChild(const std::string& name) override;
|
||||||
|
void clearChildren() override;
|
||||||
|
|
||||||
|
// Direct JSON access (for internal use by JsonDataTree)
|
||||||
|
const json& getJsonData() const { return m_data; }
|
||||||
|
json& getJsonData() { return m_data; }
|
||||||
|
const std::map<std::string, std::unique_ptr<JsonDataNode>>& getChildren() const { return m_children; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string m_name;
|
||||||
|
json m_data;
|
||||||
|
JsonDataNode* m_parent;
|
||||||
|
bool m_readOnly;
|
||||||
|
std::map<std::string, std::unique_ptr<JsonDataNode>> m_children;
|
||||||
|
|
||||||
|
// Helper methods
|
||||||
|
bool matchesPattern(const std::string& text, const std::string& pattern) const;
|
||||||
|
void collectMatchingNodes(const std::string& pattern, std::vector<IDataNode*>& results);
|
||||||
|
void collectNodesByProperty(const std::string& propName,
|
||||||
|
const std::function<bool(const IDataValue&)>& predicate,
|
||||||
|
std::vector<IDataNode*>& results);
|
||||||
|
std::string computeHash(const std::string& input) const;
|
||||||
|
void checkReadOnly() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace grove
|
||||||
86
include/grove/JsonDataTree.h
Normal file
86
include/grove/JsonDataTree.h
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "IDataTree.h"
|
||||||
|
#include "JsonDataNode.h"
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
|
#include <chrono>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
namespace grove {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Concrete implementation of IDataTree backed by JSON files
|
||||||
|
*
|
||||||
|
* Manages three separate trees:
|
||||||
|
* - config/ : Read-only configuration loaded from files (hot-reload enabled)
|
||||||
|
* - data/ : Persistent player data (read-write, saved to disk)
|
||||||
|
* - runtime/ : Temporary runtime state (read-write, never saved)
|
||||||
|
*
|
||||||
|
* File structure:
|
||||||
|
* basePath/
|
||||||
|
* ├─ config/
|
||||||
|
* │ ├─ tanks.json
|
||||||
|
* │ ├─ weapons.json
|
||||||
|
* │ └─ ...
|
||||||
|
* ├─ data/
|
||||||
|
* │ ├─ campaign.json
|
||||||
|
* │ ├─ unlocks.json
|
||||||
|
* │ └─ ...
|
||||||
|
* └─ runtime/ (in-memory only, not on disk)
|
||||||
|
*/
|
||||||
|
class JsonDataTree : public IDataTree {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Create a data tree from a base directory
|
||||||
|
* @param basePath Base directory containing config/, data/ subdirs
|
||||||
|
*/
|
||||||
|
explicit JsonDataTree(const std::string& basePath);
|
||||||
|
virtual ~JsonDataTree() = default;
|
||||||
|
|
||||||
|
// Tree access
|
||||||
|
std::unique_ptr<IDataNode> getRoot() override;
|
||||||
|
std::unique_ptr<IDataNode> getNode(const std::string& path) override;
|
||||||
|
|
||||||
|
// Separate roots
|
||||||
|
std::unique_ptr<IDataNode> getConfigRoot() override;
|
||||||
|
std::unique_ptr<IDataNode> getDataRoot() override;
|
||||||
|
std::unique_ptr<IDataNode> getRuntimeRoot() override;
|
||||||
|
|
||||||
|
// Save operations
|
||||||
|
bool saveData() override;
|
||||||
|
bool saveNode(const std::string& path) override;
|
||||||
|
|
||||||
|
// Hot-reload
|
||||||
|
bool checkForChanges() override;
|
||||||
|
bool reloadIfChanged() override;
|
||||||
|
void onTreeReloaded(std::function<void()> callback) override;
|
||||||
|
|
||||||
|
// Metadata
|
||||||
|
std::string getType() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string m_basePath;
|
||||||
|
std::unique_ptr<JsonDataNode> m_root;
|
||||||
|
std::unique_ptr<JsonDataNode> m_configRoot;
|
||||||
|
std::unique_ptr<JsonDataNode> m_dataRoot;
|
||||||
|
std::unique_ptr<JsonDataNode> m_runtimeRoot;
|
||||||
|
|
||||||
|
std::map<std::string, std::filesystem::file_time_type> m_configFileTimes;
|
||||||
|
std::vector<std::function<void()>> m_reloadCallbacks;
|
||||||
|
|
||||||
|
// Helper methods
|
||||||
|
void loadConfigTree();
|
||||||
|
void loadDataTree();
|
||||||
|
void initializeRuntimeTree();
|
||||||
|
void scanDirectory(const std::string& dirPath, JsonDataNode* parentNode, bool readOnly);
|
||||||
|
json loadJsonFile(const std::string& filePath);
|
||||||
|
bool saveJsonFile(const std::string& filePath, const json& data);
|
||||||
|
void buildNodeFromJson(const std::string& name, const json& data, JsonDataNode* parentNode, bool readOnly);
|
||||||
|
json nodeToJson(const JsonDataNode* node);
|
||||||
|
void updateFileTimestamps(const std::string& dirPath);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace grove
|
||||||
53
include/grove/JsonDataValue.h
Normal file
53
include/grove/JsonDataValue.h
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "IDataValue.h"
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace grove {
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Concrete implementation of IDataValue backed by nlohmann::json
|
||||||
|
*/
|
||||||
|
class JsonDataValue : public IDataValue {
|
||||||
|
public:
|
||||||
|
explicit JsonDataValue(const json& value);
|
||||||
|
explicit JsonDataValue(json&& value);
|
||||||
|
virtual ~JsonDataValue() = default;
|
||||||
|
|
||||||
|
// Type checking
|
||||||
|
bool isNull() const override;
|
||||||
|
bool isBool() const override;
|
||||||
|
bool isNumber() const override;
|
||||||
|
bool isString() const override;
|
||||||
|
bool isArray() const override;
|
||||||
|
bool isObject() const override;
|
||||||
|
|
||||||
|
// Value access with defaults
|
||||||
|
bool asBool(bool defaultValue = false) const override;
|
||||||
|
int asInt(int defaultValue = 0) const override;
|
||||||
|
double asDouble(double defaultValue = 0.0) const override;
|
||||||
|
std::string asString(const std::string& defaultValue = "") const override;
|
||||||
|
|
||||||
|
// Array/Object access
|
||||||
|
size_t size() const override;
|
||||||
|
std::unique_ptr<IDataValue> get(size_t index) const override;
|
||||||
|
std::unique_ptr<IDataValue> get(const std::string& key) const override;
|
||||||
|
bool has(const std::string& key) const override;
|
||||||
|
|
||||||
|
// Serialization
|
||||||
|
std::string toString() const override;
|
||||||
|
|
||||||
|
// Direct JSON access (for internal use)
|
||||||
|
const json& getJson() const { return m_value; }
|
||||||
|
json& getJson() { return m_value; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
json m_value;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace grove
|
||||||
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Factory for loading and creating modules from shared libraries (.so files)
|
* @brief Factory for loading and creating modules from shared libraries (.so files)
|
||||||
@ -99,4 +99,4 @@ private:
|
|||||||
void logModuleError(const std::string& operation, const std::string& details) const;
|
void logModuleError(const std::string& operation, const std::string& details) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace warfactory
|
} // namespace grove
|
||||||
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Factory for creating ModuleSystem implementations
|
* @brief Factory for creating ModuleSystem implementations
|
||||||
@ -113,4 +113,4 @@ private:
|
|||||||
static int detectCpuCores();
|
static int detectCpuCores();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace warfactory
|
} // namespace grove
|
||||||
@ -3,7 +3,7 @@
|
|||||||
#include <random>
|
#include <random>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Centralized random number generator singleton
|
* @brief Centralized random number generator singleton
|
||||||
@ -86,4 +86,4 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace warfactory
|
} // namespace grove
|
||||||
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
|
|
||||||
class Resource {
|
class Resource {
|
||||||
private:
|
private:
|
||||||
@ -34,4 +34,4 @@ public:
|
|||||||
static Resource loadFromJson(const std::string& resource_id, const json& resource_data);
|
static Resource loadFromJson(const std::string& resource_id, const json& resource_data);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace warfactory
|
} // namespace grove
|
||||||
@ -6,7 +6,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Singleton registry for all game resources with fast uint32_t ID lookup
|
* @brief Singleton registry for all game resources with fast uint32_t ID lookup
|
||||||
@ -110,4 +110,4 @@ public:
|
|||||||
#define GET_RESOURCE(id) warfactory::ResourceRegistry::getInstance().getResource(id)
|
#define GET_RESOURCE(id) warfactory::ResourceRegistry::getInstance().getResource(id)
|
||||||
#define VALID_RESOURCE(id) warfactory::ResourceRegistry::getInstance().isValidResourceId(id)
|
#define VALID_RESOURCE(id) warfactory::ResourceRegistry::getInstance().isValidResourceId(id)
|
||||||
|
|
||||||
} // namespace warfactory
|
} // namespace grove
|
||||||
@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Sequential module system implementation for debug and testing
|
* @brief Sequential module system implementation for debug and testing
|
||||||
@ -84,4 +84,4 @@ public:
|
|||||||
void setLogLevel(spdlog::level::level_enum level);
|
void setLogLevel(spdlog::level::level_enum level);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace warfactory
|
} // namespace grove
|
||||||
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
|
|
||||||
class ASerializable;
|
class ASerializable;
|
||||||
|
|
||||||
@ -35,4 +35,4 @@ public:
|
|||||||
void clear();
|
void clear();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace warfactory
|
} // namespace grove
|
||||||
15
src/DataTreeFactory.cpp
Normal file
15
src/DataTreeFactory.cpp
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#include "grove/DataTreeFactory.h"
|
||||||
|
#include "grove/JsonDataTree.h"
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace grove {
|
||||||
|
|
||||||
|
std::unique_ptr<IDataTree> DataTreeFactory::create(const std::string& type, const std::string& sourcePath) {
|
||||||
|
if (type == "json") {
|
||||||
|
return std::make_unique<JsonDataTree>(sourcePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw std::runtime_error("Unknown data tree type: " + type);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace grove
|
||||||
@ -4,7 +4,7 @@
|
|||||||
#include <spdlog/sinks/stdout_color_sinks.h>
|
#include <spdlog/sinks/stdout_color_sinks.h>
|
||||||
#include <spdlog/sinks/basic_file_sink.h>
|
#include <spdlog/sinks/basic_file_sink.h>
|
||||||
|
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
|
|
||||||
DebugEngine::DebugEngine() {
|
DebugEngine::DebugEngine() {
|
||||||
// Create comprehensive logger with multiple sinks
|
// Create comprehensive logger with multiple sinks
|
||||||
@ -484,4 +484,4 @@ void DebugEngine::validateConfiguration() {
|
|||||||
logger->trace("🚧 TODO: Implement comprehensive config validation");
|
logger->trace("🚧 TODO: Implement comprehensive config validation");
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace warfactory
|
} // namespace grove
|
||||||
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
|
|
||||||
std::unique_ptr<IEngine> EngineFactory::createEngine(const std::string& engineType) {
|
std::unique_ptr<IEngine> EngineFactory::createEngine(const std::string& engineType) {
|
||||||
auto logger = getFactoryLogger();
|
auto logger = getFactoryLogger();
|
||||||
@ -204,4 +204,4 @@ std::string EngineFactory::toLowercase(const std::string& str) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace warfactory
|
} // namespace grove
|
||||||
@ -11,7 +11,7 @@
|
|||||||
// #include "LocalIO.h"
|
// #include "LocalIO.h"
|
||||||
// #include "NetworkIO.h"
|
// #include "NetworkIO.h"
|
||||||
|
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
|
|
||||||
std::unique_ptr<IIO> IOFactory::create(const std::string& transportType, const std::string& instanceId) {
|
std::unique_ptr<IIO> IOFactory::create(const std::string& transportType, const std::string& instanceId) {
|
||||||
auto logger = getFactoryLogger();
|
auto logger = getFactoryLogger();
|
||||||
@ -308,4 +308,4 @@ std::string IOFactory::generateEndpoint(IOType ioType) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace warfactory
|
} // namespace grove
|
||||||
@ -3,7 +3,7 @@
|
|||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// IUI INTERFACE IMPLEMENTATION - REQUESTS & EVENTS
|
// IUI INTERFACE IMPLEMENTATION - REQUESTS & EVENTS
|
||||||
@ -543,4 +543,4 @@ void ImGuiUI::renderLogConsole() {
|
|||||||
ImGui::End();
|
ImGui::End();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace warfactory
|
} // namespace grove
|
||||||
@ -6,7 +6,7 @@
|
|||||||
#include <spdlog/sinks/stdout_color_sinks.h>
|
#include <spdlog/sinks/stdout_color_sinks.h>
|
||||||
#include <spdlog/sinks/basic_file_sink.h>
|
#include <spdlog/sinks/basic_file_sink.h>
|
||||||
|
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
|
|
||||||
// Factory function for IntraIOManager to avoid circular include
|
// Factory function for IntraIOManager to avoid circular include
|
||||||
std::shared_ptr<IntraIO> createIntraIOInstance(const std::string& instanceId) {
|
std::shared_ptr<IntraIO> createIntraIOInstance(const std::string& instanceId) {
|
||||||
@ -481,4 +481,4 @@ const std::string& IntraIO::getInstanceId() const {
|
|||||||
return instanceId;
|
return instanceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace warfactory
|
} // namespace grove
|
||||||
@ -4,7 +4,7 @@
|
|||||||
#include <spdlog/sinks/stdout_color_sinks.h>
|
#include <spdlog/sinks/stdout_color_sinks.h>
|
||||||
#include <spdlog/sinks/basic_file_sink.h>
|
#include <spdlog/sinks/basic_file_sink.h>
|
||||||
|
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
|
|
||||||
IntraIOManager::IntraIOManager() {
|
IntraIOManager::IntraIOManager() {
|
||||||
// Create logger
|
// Create logger
|
||||||
@ -266,4 +266,4 @@ IntraIOManager& IntraIOManager::getInstance() {
|
|||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace warfactory
|
} // namespace grove
|
||||||
343
src/JsonDataNode.cpp
Normal file
343
src/JsonDataNode.cpp
Normal file
@ -0,0 +1,343 @@
|
|||||||
|
#include "grove/JsonDataNode.h"
|
||||||
|
#include <regex>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <openssl/sha.h>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace grove {
|
||||||
|
|
||||||
|
JsonDataNode::JsonDataNode(const std::string& name, const json& data, JsonDataNode* parent, bool readOnly)
|
||||||
|
: m_name(name), m_data(data), m_parent(parent), m_readOnly(readOnly) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// TREE NAVIGATION
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
std::unique_ptr<IDataNode> JsonDataNode::getChild(const std::string& name) {
|
||||||
|
auto it = m_children.find(name);
|
||||||
|
if (it == m_children.end()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
// Return a copy wrapped in unique_ptr
|
||||||
|
return std::make_unique<JsonDataNode>(it->second->getName(),
|
||||||
|
it->second->getJsonData(),
|
||||||
|
this,
|
||||||
|
it->second->m_readOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> JsonDataNode::getChildNames() {
|
||||||
|
std::vector<std::string> names;
|
||||||
|
names.reserve(m_children.size());
|
||||||
|
for (const auto& [name, _] : m_children) {
|
||||||
|
names.push_back(name);
|
||||||
|
}
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JsonDataNode::hasChildren() {
|
||||||
|
return !m_children.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// EXACT SEARCH IN CHILDREN
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
std::vector<IDataNode*> JsonDataNode::getChildrenByName(const std::string& name) {
|
||||||
|
std::vector<IDataNode*> results;
|
||||||
|
auto it = m_children.find(name);
|
||||||
|
if (it != m_children.end()) {
|
||||||
|
results.push_back(it->second.get());
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JsonDataNode::hasChildrenByName(const std::string& name) const {
|
||||||
|
return m_children.find(name) != m_children.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
IDataNode* JsonDataNode::getFirstChildByName(const std::string& name) {
|
||||||
|
auto it = m_children.find(name);
|
||||||
|
if (it != m_children.end()) {
|
||||||
|
return it->second.get();
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// PATTERN MATCHING SEARCH
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
bool JsonDataNode::matchesPattern(const std::string& text, const std::string& pattern) const {
|
||||||
|
// Convert wildcard pattern to regex
|
||||||
|
std::string regexPattern = pattern;
|
||||||
|
|
||||||
|
// Escape special regex characters except *
|
||||||
|
regexPattern = std::regex_replace(regexPattern, std::regex("\\."), "\\.");
|
||||||
|
regexPattern = std::regex_replace(regexPattern, std::regex("\\+"), "\\+");
|
||||||
|
regexPattern = std::regex_replace(regexPattern, std::regex("\\?"), "\\?");
|
||||||
|
regexPattern = std::regex_replace(regexPattern, std::regex("\\["), "\\[");
|
||||||
|
regexPattern = std::regex_replace(regexPattern, std::regex("\\]"), "\\]");
|
||||||
|
regexPattern = std::regex_replace(regexPattern, std::regex("\\^"), "\\^");
|
||||||
|
regexPattern = std::regex_replace(regexPattern, std::regex("\\$"), "\\$");
|
||||||
|
regexPattern = std::regex_replace(regexPattern, std::regex("\\("), "\\(");
|
||||||
|
regexPattern = std::regex_replace(regexPattern, std::regex("\\)"), "\\)");
|
||||||
|
regexPattern = std::regex_replace(regexPattern, std::regex("\\{"), "\\{");
|
||||||
|
regexPattern = std::regex_replace(regexPattern, std::regex("\\}"), "\\}");
|
||||||
|
regexPattern = std::regex_replace(regexPattern, std::regex("\\|"), "\\|");
|
||||||
|
|
||||||
|
// Convert * to .*
|
||||||
|
regexPattern = std::regex_replace(regexPattern, std::regex("\\*"), ".*");
|
||||||
|
|
||||||
|
// Match entire string
|
||||||
|
std::regex re("^" + regexPattern + "$");
|
||||||
|
return std::regex_match(text, re);
|
||||||
|
}
|
||||||
|
|
||||||
|
void JsonDataNode::collectMatchingNodes(const std::string& pattern, std::vector<IDataNode*>& results) {
|
||||||
|
// Check this node
|
||||||
|
if (matchesPattern(m_name, pattern)) {
|
||||||
|
results.push_back(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively check children
|
||||||
|
for (auto& [name, child] : m_children) {
|
||||||
|
child->collectMatchingNodes(pattern, results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<IDataNode*> JsonDataNode::getChildrenByNameMatch(const std::string& pattern) {
|
||||||
|
std::vector<IDataNode*> results;
|
||||||
|
collectMatchingNodes(pattern, results);
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JsonDataNode::hasChildrenByNameMatch(const std::string& pattern) const {
|
||||||
|
// Cast away const for search (doesn't modify state)
|
||||||
|
JsonDataNode* mutableThis = const_cast<JsonDataNode*>(this);
|
||||||
|
std::vector<IDataNode*> results;
|
||||||
|
mutableThis->collectMatchingNodes(pattern, results);
|
||||||
|
return !results.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
IDataNode* JsonDataNode::getFirstChildByNameMatch(const std::string& pattern) {
|
||||||
|
std::vector<IDataNode*> results;
|
||||||
|
collectMatchingNodes(pattern, results);
|
||||||
|
return results.empty() ? nullptr : results[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// QUERY BY PROPERTIES
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
void JsonDataNode::collectNodesByProperty(const std::string& propName,
|
||||||
|
const std::function<bool(const IDataValue&)>& predicate,
|
||||||
|
std::vector<IDataNode*>& results) {
|
||||||
|
// Check this node
|
||||||
|
if (hasProperty(propName)) {
|
||||||
|
auto value = getData();
|
||||||
|
if (value->has(propName)) {
|
||||||
|
auto propValue = value->get(propName);
|
||||||
|
if (predicate(*propValue)) {
|
||||||
|
results.push_back(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively check children
|
||||||
|
for (auto& [name, child] : m_children) {
|
||||||
|
child->collectNodesByProperty(propName, predicate, results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<IDataNode*> JsonDataNode::queryByProperty(const std::string& propName,
|
||||||
|
const std::function<bool(const IDataValue&)>& predicate) {
|
||||||
|
std::vector<IDataNode*> results;
|
||||||
|
collectNodesByProperty(propName, predicate, results);
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// NODE'S OWN DATA
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
std::unique_ptr<IDataValue> JsonDataNode::getData() const {
|
||||||
|
return std::make_unique<JsonDataValue>(m_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JsonDataNode::hasData() const {
|
||||||
|
return !m_data.is_null() && !m_data.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void JsonDataNode::setData(std::unique_ptr<IDataValue> data) {
|
||||||
|
checkReadOnly();
|
||||||
|
|
||||||
|
// Extract JSON from JsonDataValue
|
||||||
|
if (auto* jsonValue = dynamic_cast<JsonDataValue*>(data.get())) {
|
||||||
|
m_data = jsonValue->getJson();
|
||||||
|
} else {
|
||||||
|
throw std::runtime_error("JsonDataNode requires JsonDataValue");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// TYPED DATA ACCESS
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
std::string JsonDataNode::getString(const std::string& name, const std::string& defaultValue) const {
|
||||||
|
if (!m_data.is_object() || !m_data.contains(name)) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
const auto& val = m_data[name];
|
||||||
|
return val.is_string() ? val.get<std::string>() : defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int JsonDataNode::getInt(const std::string& name, int defaultValue) const {
|
||||||
|
if (!m_data.is_object() || !m_data.contains(name)) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
const auto& val = m_data[name];
|
||||||
|
return val.is_number() ? val.get<int>() : defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
double JsonDataNode::getDouble(const std::string& name, double defaultValue) const {
|
||||||
|
if (!m_data.is_object() || !m_data.contains(name)) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
const auto& val = m_data[name];
|
||||||
|
return val.is_number() ? val.get<double>() : defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JsonDataNode::getBool(const std::string& name, bool defaultValue) const {
|
||||||
|
if (!m_data.is_object() || !m_data.contains(name)) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
const auto& val = m_data[name];
|
||||||
|
return val.is_boolean() ? val.get<bool>() : defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JsonDataNode::hasProperty(const std::string& name) const {
|
||||||
|
return m_data.is_object() && m_data.contains(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// HASH SYSTEM
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
std::string JsonDataNode::computeHash(const std::string& input) const {
|
||||||
|
unsigned char hash[SHA256_DIGEST_LENGTH];
|
||||||
|
SHA256(reinterpret_cast<const unsigned char*>(input.c_str()), input.length(), hash);
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
|
||||||
|
ss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(hash[i]);
|
||||||
|
}
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string JsonDataNode::getDataHash() {
|
||||||
|
std::string dataStr = m_data.dump();
|
||||||
|
return computeHash(dataStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string JsonDataNode::getTreeHash() {
|
||||||
|
// Combine data hash with all children hashes
|
||||||
|
std::string combined = getDataHash();
|
||||||
|
for (const auto& [name, child] : m_children) {
|
||||||
|
combined += name + ":" + child->getTreeHash();
|
||||||
|
}
|
||||||
|
return computeHash(combined);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string JsonDataNode::getSubtreeHash(const std::string& childPath) {
|
||||||
|
// Parse path and navigate
|
||||||
|
size_t pos = childPath.find('/');
|
||||||
|
if (pos == std::string::npos) {
|
||||||
|
// Direct child
|
||||||
|
auto it = m_children.find(childPath);
|
||||||
|
if (it != m_children.end()) {
|
||||||
|
return it->second->getTreeHash();
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
} else {
|
||||||
|
// Nested path
|
||||||
|
std::string firstPart = childPath.substr(0, pos);
|
||||||
|
std::string rest = childPath.substr(pos + 1);
|
||||||
|
auto it = m_children.find(firstPart);
|
||||||
|
if (it != m_children.end()) {
|
||||||
|
return it->second->getSubtreeHash(rest);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// METADATA
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
std::string JsonDataNode::getPath() const {
|
||||||
|
if (m_parent == nullptr) {
|
||||||
|
return m_name;
|
||||||
|
}
|
||||||
|
std::string parentPath = m_parent->getPath();
|
||||||
|
return parentPath.empty() ? m_name : parentPath + "/" + m_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string JsonDataNode::getName() const {
|
||||||
|
return m_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string JsonDataNode::getNodeType() const {
|
||||||
|
return "JsonDataNode";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// TREE MODIFICATION
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
void JsonDataNode::checkReadOnly() const {
|
||||||
|
if (m_readOnly) {
|
||||||
|
throw std::runtime_error("Cannot modify read-only node: " + getPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void JsonDataNode::setChild(const std::string& name, std::unique_ptr<IDataNode> node) {
|
||||||
|
checkReadOnly();
|
||||||
|
|
||||||
|
// Extract JsonDataNode
|
||||||
|
if (auto* jsonNode = dynamic_cast<JsonDataNode*>(node.get())) {
|
||||||
|
auto newNode = std::make_unique<JsonDataNode>(
|
||||||
|
jsonNode->getName(),
|
||||||
|
jsonNode->getJsonData(),
|
||||||
|
this,
|
||||||
|
m_readOnly // Inherit read-only status
|
||||||
|
);
|
||||||
|
|
||||||
|
// Copy children recursively
|
||||||
|
for (const auto& [childName, child] : jsonNode->getChildren()) {
|
||||||
|
newNode->setChild(childName, std::make_unique<JsonDataNode>(
|
||||||
|
child->getName(),
|
||||||
|
child->getJsonData(),
|
||||||
|
newNode.get(),
|
||||||
|
m_readOnly
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
m_children[name] = std::move(newNode);
|
||||||
|
} else {
|
||||||
|
throw std::runtime_error("JsonDataNode requires JsonDataNode child");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JsonDataNode::removeChild(const std::string& name) {
|
||||||
|
checkReadOnly();
|
||||||
|
return m_children.erase(name) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void JsonDataNode::clearChildren() {
|
||||||
|
checkReadOnly();
|
||||||
|
m_children.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace grove
|
||||||
352
src/JsonDataTree.cpp
Normal file
352
src/JsonDataTree.cpp
Normal file
@ -0,0 +1,352 @@
|
|||||||
|
#include "grove/JsonDataTree.h"
|
||||||
|
#include <fstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace grove {
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
JsonDataTree::JsonDataTree(const std::string& basePath)
|
||||||
|
: m_basePath(basePath) {
|
||||||
|
|
||||||
|
// Create root node
|
||||||
|
m_root = std::make_unique<JsonDataNode>("", json::object(), nullptr, false);
|
||||||
|
|
||||||
|
// Load three sub-trees
|
||||||
|
loadConfigTree();
|
||||||
|
loadDataTree();
|
||||||
|
initializeRuntimeTree();
|
||||||
|
|
||||||
|
// Attach to root
|
||||||
|
m_root->setChild("config", std::move(m_configRoot));
|
||||||
|
m_root->setChild("data", std::move(m_dataRoot));
|
||||||
|
m_root->setChild("runtime", std::move(m_runtimeRoot));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// TREE ACCESS
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
std::unique_ptr<IDataNode> JsonDataTree::getRoot() {
|
||||||
|
// Return copy of root
|
||||||
|
return std::make_unique<JsonDataNode>("", m_root->getJsonData(), nullptr, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<IDataNode> JsonDataTree::getNode(const std::string& path) {
|
||||||
|
if (path.empty()) {
|
||||||
|
return getRoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split path and navigate
|
||||||
|
JsonDataNode* current = m_root.get();
|
||||||
|
std::string remaining = path;
|
||||||
|
|
||||||
|
while (!remaining.empty()) {
|
||||||
|
size_t pos = remaining.find('/');
|
||||||
|
std::string part = (pos == std::string::npos) ? remaining : remaining.substr(0, pos);
|
||||||
|
remaining = (pos == std::string::npos) ? "" : remaining.substr(pos + 1);
|
||||||
|
|
||||||
|
auto child = current->getFirstChildByName(part);
|
||||||
|
if (!child) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
current = static_cast<JsonDataNode*>(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_unique<JsonDataNode>(current->getName(),
|
||||||
|
current->getJsonData(),
|
||||||
|
nullptr,
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// SEPARATE ROOTS
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
std::unique_ptr<IDataNode> JsonDataTree::getConfigRoot() {
|
||||||
|
auto configNode = m_root->getFirstChildByName("config");
|
||||||
|
if (!configNode) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
auto* jsonNode = static_cast<JsonDataNode*>(configNode);
|
||||||
|
return std::make_unique<JsonDataNode>(jsonNode->getName(),
|
||||||
|
jsonNode->getJsonData(),
|
||||||
|
nullptr,
|
||||||
|
true); // Read-only
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<IDataNode> JsonDataTree::getDataRoot() {
|
||||||
|
auto dataNode = m_root->getFirstChildByName("data");
|
||||||
|
if (!dataNode) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
auto* jsonNode = static_cast<JsonDataNode*>(dataNode);
|
||||||
|
return std::make_unique<JsonDataNode>(jsonNode->getName(),
|
||||||
|
jsonNode->getJsonData(),
|
||||||
|
nullptr,
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<IDataNode> JsonDataTree::getRuntimeRoot() {
|
||||||
|
auto runtimeNode = m_root->getFirstChildByName("runtime");
|
||||||
|
if (!runtimeNode) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
auto* jsonNode = static_cast<JsonDataNode*>(runtimeNode);
|
||||||
|
return std::make_unique<JsonDataNode>(jsonNode->getName(),
|
||||||
|
jsonNode->getJsonData(),
|
||||||
|
nullptr,
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// SAVE OPERATIONS
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
bool JsonDataTree::saveData() {
|
||||||
|
try {
|
||||||
|
std::string dataPath = m_basePath + "/data";
|
||||||
|
auto dataNode = m_root->getFirstChildByName("data");
|
||||||
|
if (!dataNode) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* jsonNode = static_cast<JsonDataNode*>(dataNode);
|
||||||
|
json dataJson = nodeToJson(jsonNode);
|
||||||
|
|
||||||
|
// Save each top-level child as separate file
|
||||||
|
for (const auto& [name, child] : jsonNode->getChildren()) {
|
||||||
|
std::string filePath = dataPath + "/" + name + ".json";
|
||||||
|
json childJson = nodeToJson(child.get());
|
||||||
|
if (!saveJsonFile(filePath, childJson)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "Failed to save data: " << e.what() << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JsonDataTree::saveNode(const std::string& path) {
|
||||||
|
// Only allow saving data/ paths
|
||||||
|
if (path.find("data/") != 0) {
|
||||||
|
std::cerr << "Can only save nodes under data/: " << path << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto node = getNode(path);
|
||||||
|
if (!node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract filename from path
|
||||||
|
size_t lastSlash = path.find_last_of('/');
|
||||||
|
std::string filename = (lastSlash == std::string::npos) ? path : path.substr(lastSlash + 1);
|
||||||
|
std::string filePath = m_basePath + "/" + path + ".json";
|
||||||
|
|
||||||
|
auto* jsonNode = dynamic_cast<JsonDataNode*>(node.get());
|
||||||
|
if (!jsonNode) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
json nodeJson = nodeToJson(jsonNode);
|
||||||
|
return saveJsonFile(filePath, nodeJson);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "Failed to save node " << path << ": " << e.what() << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// HOT-RELOAD
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
bool JsonDataTree::checkForChanges() {
|
||||||
|
std::string configPath = m_basePath + "/config";
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (const auto& [filePath, lastTime] : m_configFileTimes) {
|
||||||
|
if (!fs::exists(filePath)) {
|
||||||
|
return true; // File deleted
|
||||||
|
}
|
||||||
|
|
||||||
|
auto currentTime = fs::last_write_time(filePath);
|
||||||
|
if (currentTime != lastTime) {
|
||||||
|
return true; // File modified
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for new files
|
||||||
|
if (fs::exists(configPath)) {
|
||||||
|
for (const auto& entry : fs::directory_iterator(configPath)) {
|
||||||
|
if (entry.is_regular_file() && entry.path().extension() == ".json") {
|
||||||
|
if (m_configFileTimes.find(entry.path().string()) == m_configFileTimes.end()) {
|
||||||
|
return true; // New file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "Error checking for changes: " << e.what() << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JsonDataTree::reloadIfChanged() {
|
||||||
|
if (!checkForChanges()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
loadConfigTree();
|
||||||
|
|
||||||
|
// Trigger callbacks
|
||||||
|
for (auto& callback : m_reloadCallbacks) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "Failed to reload config: " << e.what() << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void JsonDataTree::onTreeReloaded(std::function<void()> callback) {
|
||||||
|
m_reloadCallbacks.push_back(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// METADATA
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
std::string JsonDataTree::getType() {
|
||||||
|
return "JsonDataTree";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// HELPER METHODS
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
void JsonDataTree::loadConfigTree() {
|
||||||
|
std::string configPath = m_basePath + "/config";
|
||||||
|
m_configRoot = std::make_unique<JsonDataNode>("config", json::object(), nullptr, true);
|
||||||
|
|
||||||
|
if (fs::exists(configPath) && fs::is_directory(configPath)) {
|
||||||
|
scanDirectory(configPath, m_configRoot.get(), true);
|
||||||
|
updateFileTimestamps(configPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void JsonDataTree::loadDataTree() {
|
||||||
|
std::string dataPath = m_basePath + "/data";
|
||||||
|
m_dataRoot = std::make_unique<JsonDataNode>("data", json::object(), nullptr, false);
|
||||||
|
|
||||||
|
if (fs::exists(dataPath) && fs::is_directory(dataPath)) {
|
||||||
|
scanDirectory(dataPath, m_dataRoot.get(), false);
|
||||||
|
} else {
|
||||||
|
// Create data directory if it doesn't exist
|
||||||
|
fs::create_directories(dataPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void JsonDataTree::initializeRuntimeTree() {
|
||||||
|
m_runtimeRoot = std::make_unique<JsonDataNode>("runtime", json::object(), nullptr, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void JsonDataTree::scanDirectory(const std::string& dirPath, JsonDataNode* parentNode, bool readOnly) {
|
||||||
|
for (const auto& entry : fs::directory_iterator(dirPath)) {
|
||||||
|
if (entry.is_regular_file() && entry.path().extension() == ".json") {
|
||||||
|
std::string filename = entry.path().stem().string();
|
||||||
|
json data = loadJsonFile(entry.path().string());
|
||||||
|
buildNodeFromJson(filename, data, parentNode, readOnly);
|
||||||
|
} else if (entry.is_directory()) {
|
||||||
|
// Create child node for subdirectory
|
||||||
|
auto childNode = std::make_unique<JsonDataNode>(
|
||||||
|
entry.path().filename().string(),
|
||||||
|
json::object(),
|
||||||
|
parentNode,
|
||||||
|
readOnly
|
||||||
|
);
|
||||||
|
scanDirectory(entry.path().string(), childNode.get(), readOnly);
|
||||||
|
parentNode->setChild(entry.path().filename().string(), std::move(childNode));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
json JsonDataTree::loadJsonFile(const std::string& filePath) {
|
||||||
|
std::ifstream file(filePath);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
throw std::runtime_error("Failed to open file: " + filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
json data;
|
||||||
|
file >> data;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JsonDataTree::saveJsonFile(const std::string& filePath, const json& data) {
|
||||||
|
try {
|
||||||
|
// Ensure directory exists
|
||||||
|
fs::path path(filePath);
|
||||||
|
if (path.has_parent_path()) {
|
||||||
|
fs::create_directories(path.parent_path());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ofstream file(filePath);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
file << data.dump(2); // Pretty print with 2-space indent
|
||||||
|
return true;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "Failed to save JSON file: " << e.what() << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void JsonDataTree::buildNodeFromJson(const std::string& name, const json& data, JsonDataNode* parentNode, bool readOnly) {
|
||||||
|
auto node = std::make_unique<JsonDataNode>(name, data, parentNode, readOnly);
|
||||||
|
|
||||||
|
// If data is an object with children, create child nodes
|
||||||
|
if (data.is_object()) {
|
||||||
|
for (auto& [key, value] : data.items()) {
|
||||||
|
if (value.is_object() || value.is_array()) {
|
||||||
|
buildNodeFromJson(key, value, node.get(), readOnly);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parentNode->setChild(name, std::move(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
json JsonDataTree::nodeToJson(const JsonDataNode* node) {
|
||||||
|
json result = node->getJsonData();
|
||||||
|
|
||||||
|
// Add children
|
||||||
|
for (const auto& [name, child] : node->getChildren()) {
|
||||||
|
result[name] = nodeToJson(child.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void JsonDataTree::updateFileTimestamps(const std::string& dirPath) {
|
||||||
|
m_configFileTimes.clear();
|
||||||
|
|
||||||
|
for (const auto& entry : fs::directory_iterator(dirPath)) {
|
||||||
|
if (entry.is_regular_file() && entry.path().extension() == ".json") {
|
||||||
|
m_configFileTimes[entry.path().string()] = fs::last_write_time(entry.path());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace grove
|
||||||
97
src/JsonDataValue.cpp
Normal file
97
src/JsonDataValue.cpp
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
#include "grove/JsonDataValue.h"
|
||||||
|
|
||||||
|
namespace grove {
|
||||||
|
|
||||||
|
JsonDataValue::JsonDataValue(const json& value) : m_value(value) {}
|
||||||
|
|
||||||
|
JsonDataValue::JsonDataValue(json&& value) : m_value(std::move(value)) {}
|
||||||
|
|
||||||
|
// Type checking
|
||||||
|
bool JsonDataValue::isNull() const {
|
||||||
|
return m_value.is_null();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JsonDataValue::isBool() const {
|
||||||
|
return m_value.is_boolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JsonDataValue::isNumber() const {
|
||||||
|
return m_value.is_number();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JsonDataValue::isString() const {
|
||||||
|
return m_value.is_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JsonDataValue::isArray() const {
|
||||||
|
return m_value.is_array();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JsonDataValue::isObject() const {
|
||||||
|
return m_value.is_object();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value access with defaults
|
||||||
|
bool JsonDataValue::asBool(bool defaultValue) const {
|
||||||
|
if (!m_value.is_boolean()) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
return m_value.get<bool>();
|
||||||
|
}
|
||||||
|
|
||||||
|
int JsonDataValue::asInt(int defaultValue) const {
|
||||||
|
if (!m_value.is_number()) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
return m_value.get<int>();
|
||||||
|
}
|
||||||
|
|
||||||
|
double JsonDataValue::asDouble(double defaultValue) const {
|
||||||
|
if (!m_value.is_number()) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
return m_value.get<double>();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string JsonDataValue::asString(const std::string& defaultValue) const {
|
||||||
|
if (!m_value.is_string()) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
return m_value.get<std::string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Array/Object access
|
||||||
|
size_t JsonDataValue::size() const {
|
||||||
|
if (m_value.is_array() || m_value.is_object()) {
|
||||||
|
return m_value.size();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<IDataValue> JsonDataValue::get(size_t index) const {
|
||||||
|
if (!m_value.is_array() || index >= m_value.size()) {
|
||||||
|
return std::make_unique<JsonDataValue>(json(nullptr));
|
||||||
|
}
|
||||||
|
return std::make_unique<JsonDataValue>(m_value[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<IDataValue> JsonDataValue::get(const std::string& key) const {
|
||||||
|
if (!m_value.is_object() || !m_value.contains(key)) {
|
||||||
|
return std::make_unique<JsonDataValue>(json(nullptr));
|
||||||
|
}
|
||||||
|
return std::make_unique<JsonDataValue>(m_value[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JsonDataValue::has(const std::string& key) const {
|
||||||
|
if (!m_value.is_object()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return m_value.contains(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialization
|
||||||
|
std::string JsonDataValue::toString() const {
|
||||||
|
return m_value.dump();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace grove
|
||||||
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
|
|
||||||
ModuleFactory::ModuleFactory() {
|
ModuleFactory::ModuleFactory() {
|
||||||
// Create logger with file and console output
|
// Create logger with file and console output
|
||||||
@ -506,4 +506,4 @@ void ModuleFactory::logModuleError(const std::string& operation, const std::stri
|
|||||||
logger->error("❌ Module {} error: {}", operation, details);
|
logger->error("❌ Module {} error: {}", operation, details);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace warfactory
|
} // namespace grove
|
||||||
@ -10,7 +10,7 @@
|
|||||||
// #include "ThreadPoolModuleSystem.h"
|
// #include "ThreadPoolModuleSystem.h"
|
||||||
// #include "ClusterModuleSystem.h"
|
// #include "ClusterModuleSystem.h"
|
||||||
|
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
|
|
||||||
std::unique_ptr<IModuleSystem> ModuleSystemFactory::create(const std::string& strategy) {
|
std::unique_ptr<IModuleSystem> ModuleSystemFactory::create(const std::string& strategy) {
|
||||||
auto logger = getFactoryLogger();
|
auto logger = getFactoryLogger();
|
||||||
@ -236,4 +236,4 @@ int ModuleSystemFactory::detectCpuCores() {
|
|||||||
return cores;
|
return cores;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace warfactory
|
} // namespace grove
|
||||||
@ -1,7 +1,7 @@
|
|||||||
#include <grove/ResourceRegistry.h>
|
#include <grove/ResourceRegistry.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
|
|
||||||
// Static member initialization
|
// Static member initialization
|
||||||
std::unique_ptr<ResourceRegistry> ResourceRegistry::instance = nullptr;
|
std::unique_ptr<ResourceRegistry> ResourceRegistry::instance = nullptr;
|
||||||
@ -117,4 +117,4 @@ void ResourceRegistry::clear() {
|
|||||||
next_id = 1;
|
next_id = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace warfactory
|
} // namespace grove
|
||||||
@ -3,7 +3,7 @@
|
|||||||
#include <spdlog/sinks/stdout_color_sinks.h>
|
#include <spdlog/sinks/stdout_color_sinks.h>
|
||||||
#include <spdlog/sinks/basic_file_sink.h>
|
#include <spdlog/sinks/basic_file_sink.h>
|
||||||
|
|
||||||
namespace warfactory {
|
namespace grove {
|
||||||
|
|
||||||
SequentialModuleSystem::SequentialModuleSystem() {
|
SequentialModuleSystem::SequentialModuleSystem() {
|
||||||
// Create logger with file and console output
|
// Create logger with file and console output
|
||||||
@ -273,4 +273,4 @@ void SequentialModuleSystem::validateModule() const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace warfactory
|
} // namespace grove
|
||||||
Loading…
Reference in New Issue
Block a user