- Port to Windows (MinGW/Ninja): - ModuleFactory/ModuleLoader: LoadLibrary/GetProcAddress - SystemUtils: Windows process memory APIs - FileWatcher: st_mtime instead of st_mtim - IIO.h: add missing #include <cstdint> - Tests (09, 10, 11): grove_dlopen/dlsym wrappers - Phase 4 - SceneCollector & IIO: - Implement view/proj matrix calculation in parseCamera() - Add IIO routing test with game→renderer pattern - test_22_bgfx_sprites_headless: 5 tests, 23 assertions pass 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
270 lines
8.3 KiB
C++
270 lines
8.3 KiB
C++
#include <iostream>
|
|
#include <memory>
|
|
#include <thread>
|
|
#include <chrono>
|
|
#include <grove/IModule.h>
|
|
#include <grove/JsonDataNode.h>
|
|
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#else
|
|
#include <dlfcn.h>
|
|
#endif
|
|
|
|
using namespace grove;
|
|
|
|
/**
|
|
* @brief Simple hot-reload test without full engine
|
|
*
|
|
* This test demonstrates:
|
|
* - Dynamic module loading from .so
|
|
* - State extraction before reload
|
|
* - Module replacement
|
|
* - State restoration after reload
|
|
* - Performance measurement
|
|
*/
|
|
|
|
// Function pointers for module factory
|
|
typedef IModule* (*CreateModuleFn)();
|
|
typedef void (*DestroyModuleFn)(IModule*);
|
|
|
|
class SimpleModuleLoader {
|
|
private:
|
|
void* handle = nullptr;
|
|
CreateModuleFn createFn = nullptr;
|
|
DestroyModuleFn destroyFn = nullptr;
|
|
std::string modulePath;
|
|
|
|
public:
|
|
SimpleModuleLoader(const std::string& path) : modulePath(path) {}
|
|
|
|
~SimpleModuleLoader() {
|
|
if (handle) {
|
|
#ifdef _WIN32
|
|
FreeLibrary(static_cast<HMODULE>(handle));
|
|
#else
|
|
dlclose(handle);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
bool load() {
|
|
std::cout << "\n[Loader] Loading module: " << modulePath << std::endl;
|
|
|
|
#ifdef _WIN32
|
|
handle = LoadLibraryA(modulePath.c_str());
|
|
if (!handle) {
|
|
std::cerr << "[Loader] ERROR: Failed to load module: error code " << GetLastError() << std::endl;
|
|
return false;
|
|
}
|
|
|
|
// Load factory functions
|
|
createFn = (CreateModuleFn)GetProcAddress(static_cast<HMODULE>(handle), "createModule");
|
|
if (!createFn) {
|
|
std::cerr << "[Loader] ERROR: Cannot load createModule: error code " << GetLastError() << std::endl;
|
|
FreeLibrary(static_cast<HMODULE>(handle));
|
|
handle = nullptr;
|
|
return false;
|
|
}
|
|
|
|
destroyFn = (DestroyModuleFn)GetProcAddress(static_cast<HMODULE>(handle), "destroyModule");
|
|
if (!destroyFn) {
|
|
std::cerr << "[Loader] ERROR: Cannot load destroyModule: error code " << GetLastError() << std::endl;
|
|
FreeLibrary(static_cast<HMODULE>(handle));
|
|
handle = nullptr;
|
|
return false;
|
|
}
|
|
#else
|
|
handle = dlopen(modulePath.c_str(), RTLD_NOW | RTLD_LOCAL);
|
|
if (!handle) {
|
|
std::cerr << "[Loader] ERROR: Failed to load module: " << dlerror() << std::endl;
|
|
return false;
|
|
}
|
|
|
|
// Clear any existing error
|
|
dlerror();
|
|
|
|
// Load factory functions
|
|
createFn = (CreateModuleFn)dlsym(handle, "createModule");
|
|
const char* dlsym_error = dlerror();
|
|
if (dlsym_error) {
|
|
std::cerr << "[Loader] ERROR: Cannot load createModule: " << dlsym_error << std::endl;
|
|
dlclose(handle);
|
|
handle = nullptr;
|
|
return false;
|
|
}
|
|
|
|
destroyFn = (DestroyModuleFn)dlsym(handle, "destroyModule");
|
|
dlsym_error = dlerror();
|
|
if (dlsym_error) {
|
|
std::cerr << "[Loader] ERROR: Cannot load destroyModule: " << dlsym_error << std::endl;
|
|
dlclose(handle);
|
|
handle = nullptr;
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
std::cout << "[Loader] ✅ Module loaded successfully" << std::endl;
|
|
return true;
|
|
}
|
|
|
|
void unload() {
|
|
if (handle) {
|
|
std::cout << "[Loader] Unloading module..." << std::endl;
|
|
#ifdef _WIN32
|
|
FreeLibrary(static_cast<HMODULE>(handle));
|
|
#else
|
|
dlclose(handle);
|
|
#endif
|
|
handle = nullptr;
|
|
createFn = nullptr;
|
|
destroyFn = nullptr;
|
|
}
|
|
}
|
|
|
|
IModule* createModule() {
|
|
if (!createFn) {
|
|
std::cerr << "[Loader] ERROR: createModule function not loaded" << std::endl;
|
|
return nullptr;
|
|
}
|
|
return createFn();
|
|
}
|
|
|
|
void destroyModule(IModule* module) {
|
|
if (destroyFn && module) {
|
|
destroyFn(module);
|
|
}
|
|
}
|
|
};
|
|
|
|
void printSeparator(const std::string& title) {
|
|
std::cout << "\n" << std::string(60, '=') << std::endl;
|
|
std::cout << " " << title << std::endl;
|
|
std::cout << std::string(60, '=') << std::endl;
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
std::cout << "🔥 GroveEngine Hot-Reload Test 🔥" << std::endl;
|
|
std::cout << "=================================" << std::endl;
|
|
|
|
std::string modulePath = "./libTestModule.so";
|
|
if (argc > 1) {
|
|
modulePath = argv[1];
|
|
}
|
|
|
|
std::cout << "Module path: " << modulePath << std::endl;
|
|
|
|
// Create loader
|
|
SimpleModuleLoader loader(modulePath);
|
|
|
|
// Load module
|
|
printSeparator("STEP 1: Initial Load");
|
|
if (!loader.load()) {
|
|
std::cerr << "Failed to load module!" << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
// Create module instance
|
|
IModule* module = loader.createModule();
|
|
if (!module) {
|
|
std::cerr << "Failed to create module instance!" << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
// Configure module
|
|
nlohmann::json config = {{"version", "v1.0"}};
|
|
JsonDataNode configNode("config", config);
|
|
module->setConfiguration(configNode, nullptr, nullptr);
|
|
|
|
// Process a few times
|
|
printSeparator("STEP 2: Process Module (Before Reload)");
|
|
nlohmann::json inputData = {{"message", "Hello from test"}};
|
|
JsonDataNode input("input", inputData);
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
std::cout << "\n--- Iteration " << (i + 1) << " ---" << std::endl;
|
|
module->process(input);
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
}
|
|
|
|
// Get state before reload
|
|
printSeparator("STEP 3: Extract State for Hot-Reload");
|
|
auto state = module->getState();
|
|
std::cout << "[Test] State extracted successfully" << std::endl;
|
|
|
|
// Hot-reload simulation
|
|
printSeparator("STEP 4: HOT-RELOAD (Measure Performance)");
|
|
|
|
auto startTime = std::chrono::high_resolution_clock::now();
|
|
|
|
// 1. Destroy old instance
|
|
std::cout << "[Test] Destroying old module instance..." << std::endl;
|
|
loader.destroyModule(module);
|
|
module = nullptr;
|
|
|
|
// 2. Unload old .so
|
|
std::cout << "[Test] Unloading old .so..." << std::endl;
|
|
loader.unload();
|
|
|
|
// 3. Reload .so
|
|
std::cout << "[Test] Reloading .so..." << std::endl;
|
|
if (!loader.load()) {
|
|
std::cerr << "Failed to reload module!" << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
// 4. Create new instance
|
|
std::cout << "[Test] Creating new module instance..." << std::endl;
|
|
module = loader.createModule();
|
|
if (!module) {
|
|
std::cerr << "Failed to create new module instance!" << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
// 5. Reconfigure
|
|
std::cout << "[Test] Reconfiguring module..." << std::endl;
|
|
module->setConfiguration(configNode, nullptr, nullptr);
|
|
|
|
// 6. Restore state
|
|
std::cout << "[Test] Restoring state..." << std::endl;
|
|
module->setState(*state);
|
|
|
|
auto endTime = std::chrono::high_resolution_clock::now();
|
|
auto duration = std::chrono::duration<double, std::milli>(endTime - startTime);
|
|
|
|
std::cout << "\n🚀 HOT-RELOAD COMPLETED IN: " << duration.count() << "ms 🚀" << std::endl;
|
|
|
|
// Process again to verify state was preserved
|
|
printSeparator("STEP 5: Process Module (After Reload)");
|
|
std::cout << "Counter should continue from where it left off..." << std::endl;
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
std::cout << "\n--- Iteration " << (i + 1) << " ---" << std::endl;
|
|
module->process(input);
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
}
|
|
|
|
// Check health
|
|
printSeparator("STEP 6: Health Check");
|
|
auto health = module->getHealthStatus();
|
|
std::cout << "[Test] Module health: " << health->getData()->toString() << std::endl;
|
|
|
|
// Cleanup
|
|
printSeparator("CLEANUP");
|
|
module->shutdown();
|
|
loader.destroyModule(module);
|
|
|
|
std::cout << "\n✅ Hot-Reload Test Completed Successfully!" << std::endl;
|
|
std::cout << "⏱️ Total reload time: " << duration.count() << "ms" << std::endl;
|
|
|
|
if (duration.count() < 1.0) {
|
|
std::cout << "🔥 Classification: BLAZING (< 1ms)" << std::endl;
|
|
} else if (duration.count() < 10.0) {
|
|
std::cout << "⚡ Classification: VERY FAST (< 10ms)" << std::endl;
|
|
} else {
|
|
std::cout << "👍 Classification: ACCEPTABLE" << std::endl;
|
|
}
|
|
|
|
return 0;
|
|
}
|