Files
SDL3CPlusPlus/src/services/impl/json_config_writer_service.cpp
johndoe6345789 df19ae9264 feat: Enhance JSON configuration writer and add heartbeat recording to crash recovery service
- Updated JsonConfigWriterService to structure the JSON output with new sections for scripts, window settings, input bindings, paths, rendering, and GUI configurations.
- Introduced a new method in ICrashRecoveryService to record frame heartbeats, allowing for better tracking of long-running operations.
- Refactored existing code to improve readability and maintainability, including the addition of helper functions for adding string members to JSON objects.
2026-01-08 16:57:24 +00:00

263 lines
13 KiB
C++

#include "json_config_writer_service.hpp"
#include <rapidjson/document.h>
#include <rapidjson/stringbuffer.h>
#include <rapidjson/writer.h>
#include <array>
#include <filesystem>
#include <fstream>
#include <stdexcept>
#include <utility>
namespace sdl3cpp::services::impl {
JsonConfigWriterService::JsonConfigWriterService(std::shared_ptr<ILogger> logger)
: logger_(std::move(logger)) {
if (logger_) {
logger_->Trace("JsonConfigWriterService", "JsonConfigWriterService");
}
}
void JsonConfigWriterService::WriteConfig(const RuntimeConfig& config, const std::filesystem::path& configPath) {
if (logger_) {
logger_->Trace("JsonConfigWriterService", "WriteConfig",
"config.width=" + std::to_string(config.width) +
", config.height=" + std::to_string(config.height) +
", config.scriptPath=" + config.scriptPath.string() +
", config.luaDebug=" + std::string(config.luaDebug ? "true" : "false") +
", config.windowTitle=" + config.windowTitle +
", configPath=" + configPath.string(),
"Entering");
}
rapidjson::Document document;
document.SetObject();
auto& allocator = document.GetAllocator();
auto addStringMember = [&](rapidjson::Value& target, const char* name, const std::string& value) {
rapidjson::Value nameValue(name, allocator);
rapidjson::Value stringValue(value.c_str(), allocator);
target.AddMember(nameValue, stringValue, allocator);
};
document.AddMember("schema_version", 2, allocator);
rapidjson::Value scriptsObject(rapidjson::kObjectType);
addStringMember(scriptsObject, "entry", config.scriptPath.string());
scriptsObject.AddMember("lua_debug", config.luaDebug, allocator);
document.AddMember("scripts", scriptsObject, allocator);
rapidjson::Value windowObject(rapidjson::kObjectType);
addStringMember(windowObject, "title", config.windowTitle);
rapidjson::Value sizeObject(rapidjson::kObjectType);
sizeObject.AddMember("width", config.width, allocator);
sizeObject.AddMember("height", config.height, allocator);
windowObject.AddMember("size", sizeObject, allocator);
std::filesystem::path scriptsDir = config.scriptPath.parent_path();
rapidjson::Value mouseGrabObject(rapidjson::kObjectType);
mouseGrabObject.AddMember("enabled", config.mouseGrab.enabled, allocator);
mouseGrabObject.AddMember("grab_on_click", config.mouseGrab.grabOnClick, allocator);
mouseGrabObject.AddMember("release_on_escape", config.mouseGrab.releaseOnEscape, allocator);
mouseGrabObject.AddMember("start_grabbed", config.mouseGrab.startGrabbed, allocator);
mouseGrabObject.AddMember("hide_cursor", config.mouseGrab.hideCursor, allocator);
mouseGrabObject.AddMember("relative_mode", config.mouseGrab.relativeMode, allocator);
mouseGrabObject.AddMember("grab_mouse_button",
rapidjson::Value(config.mouseGrab.grabMouseButton.c_str(), allocator),
allocator);
mouseGrabObject.AddMember("release_key",
rapidjson::Value(config.mouseGrab.releaseKey.c_str(), allocator),
allocator);
windowObject.AddMember("mouse_grab", mouseGrabObject, allocator);
document.AddMember("window", windowObject, allocator);
rapidjson::Value bindingsObject(rapidjson::kObjectType);
auto addBindingMember = [&](const char* name, const std::string& value) {
rapidjson::Value nameValue(name, allocator);
rapidjson::Value stringValue(value.c_str(), allocator);
bindingsObject.AddMember(nameValue, stringValue, allocator);
};
struct BindingSpec {
const char* name;
std::string InputBindings::* member;
};
const std::array<BindingSpec, 18> bindingSpecs = {{
{"move_forward", &InputBindings::moveForwardKey},
{"move_back", &InputBindings::moveBackKey},
{"move_left", &InputBindings::moveLeftKey},
{"move_right", &InputBindings::moveRightKey},
{"fly_up", &InputBindings::flyUpKey},
{"fly_down", &InputBindings::flyDownKey},
{"jump", &InputBindings::jumpKey},
{"noclip_toggle", &InputBindings::noclipToggleKey},
{"music_toggle", &InputBindings::musicToggleKey},
{"music_toggle_gamepad", &InputBindings::musicToggleGamepadButton},
{"gamepad_move_x_axis", &InputBindings::gamepadMoveXAxis},
{"gamepad_move_y_axis", &InputBindings::gamepadMoveYAxis},
{"gamepad_look_x_axis", &InputBindings::gamepadLookXAxis},
{"gamepad_look_y_axis", &InputBindings::gamepadLookYAxis},
{"gamepad_dpad_up", &InputBindings::gamepadDpadUpButton},
{"gamepad_dpad_down", &InputBindings::gamepadDpadDownButton},
{"gamepad_dpad_left", &InputBindings::gamepadDpadLeftButton},
{"gamepad_dpad_right", &InputBindings::gamepadDpadRightButton},
}};
for (const auto& spec : bindingSpecs) {
addBindingMember(spec.name, config.inputBindings.*(spec.member));
}
auto addMappingObject = [&](const char* name,
const std::unordered_map<std::string, std::string>& mappings,
rapidjson::Value& target) {
rapidjson::Value mappingObject(rapidjson::kObjectType);
for (const auto& [key, value] : mappings) {
rapidjson::Value keyValue(key.c_str(), allocator);
rapidjson::Value stringValue(value.c_str(), allocator);
mappingObject.AddMember(keyValue, stringValue, allocator);
}
target.AddMember(rapidjson::Value(name, allocator), mappingObject, allocator);
};
addMappingObject("gamepad_button_actions", config.inputBindings.gamepadButtonActions, bindingsObject);
addMappingObject("gamepad_axis_actions", config.inputBindings.gamepadAxisActions, bindingsObject);
bindingsObject.AddMember("gamepad_axis_action_threshold",
config.inputBindings.gamepadAxisActionThreshold, allocator);
rapidjson::Value inputObject(rapidjson::kObjectType);
inputObject.AddMember("bindings", bindingsObject, allocator);
document.AddMember("input", inputObject, allocator);
std::filesystem::path projectRoot = scriptsDir.parent_path();
rapidjson::Value pathsObject(rapidjson::kObjectType);
if (!scriptsDir.empty()) {
addStringMember(pathsObject, "scripts", scriptsDir.string());
}
if (!projectRoot.empty()) {
addStringMember(pathsObject, "project_root", projectRoot.string());
addStringMember(pathsObject, "shaders", (projectRoot / "shaders").string());
} else {
addStringMember(pathsObject, "shaders", "shaders");
}
document.AddMember("paths", pathsObject, allocator);
rapidjson::Value bgfxObject(rapidjson::kObjectType);
bgfxObject.AddMember("renderer",
rapidjson::Value(config.bgfx.renderer.c_str(), allocator),
allocator);
rapidjson::Value materialObject(rapidjson::kObjectType);
materialObject.AddMember("enabled", config.materialX.enabled, allocator);
materialObject.AddMember("document",
rapidjson::Value(config.materialX.documentPath.string().c_str(), allocator),
allocator);
materialObject.AddMember("shader_key",
rapidjson::Value(config.materialX.shaderKey.c_str(), allocator),
allocator);
materialObject.AddMember("material",
rapidjson::Value(config.materialX.materialName.c_str(), allocator),
allocator);
materialObject.AddMember("library_path",
rapidjson::Value(config.materialX.libraryPath.string().c_str(), allocator),
allocator);
rapidjson::Value libraryFolders(rapidjson::kArrayType);
for (const auto& folder : config.materialX.libraryFolders) {
libraryFolders.PushBack(rapidjson::Value(folder.c_str(), allocator), allocator);
}
materialObject.AddMember("library_folders", libraryFolders, allocator);
materialObject.AddMember("use_constant_color", config.materialX.useConstantColor, allocator);
rapidjson::Value constantColor(rapidjson::kArrayType);
constantColor.PushBack(config.materialX.constantColor[0], allocator);
constantColor.PushBack(config.materialX.constantColor[1], allocator);
constantColor.PushBack(config.materialX.constantColor[2], allocator);
materialObject.AddMember("constant_color", constantColor, allocator);
if (!config.materialXMaterials.empty()) {
rapidjson::Value materialsArray(rapidjson::kArrayType);
for (const auto& material : config.materialXMaterials) {
rapidjson::Value entry(rapidjson::kObjectType);
entry.AddMember("enabled", material.enabled, allocator);
entry.AddMember("document",
rapidjson::Value(material.documentPath.string().c_str(), allocator),
allocator);
entry.AddMember("shader_key",
rapidjson::Value(material.shaderKey.c_str(), allocator),
allocator);
entry.AddMember("material",
rapidjson::Value(material.materialName.c_str(), allocator),
allocator);
entry.AddMember("use_constant_color", material.useConstantColor, allocator);
rapidjson::Value materialColor(rapidjson::kArrayType);
materialColor.PushBack(material.constantColor[0], allocator);
materialColor.PushBack(material.constantColor[1], allocator);
materialColor.PushBack(material.constantColor[2], allocator);
entry.AddMember("constant_color", materialColor, allocator);
materialsArray.PushBack(entry, allocator);
}
materialObject.AddMember("materials", materialsArray, allocator);
}
rapidjson::Value atmosphericsObject(rapidjson::kObjectType);
atmosphericsObject.AddMember("ambient_strength", config.atmospherics.ambientStrength, allocator);
atmosphericsObject.AddMember("fog_density", config.atmospherics.fogDensity, allocator);
rapidjson::Value fogColor(rapidjson::kArrayType);
fogColor.PushBack(config.atmospherics.fogColor[0], allocator);
fogColor.PushBack(config.atmospherics.fogColor[1], allocator);
fogColor.PushBack(config.atmospherics.fogColor[2], allocator);
atmosphericsObject.AddMember("fog_color", fogColor, allocator);
rapidjson::Value skyColor(rapidjson::kArrayType);
skyColor.PushBack(config.atmospherics.skyColor[0], allocator);
skyColor.PushBack(config.atmospherics.skyColor[1], allocator);
skyColor.PushBack(config.atmospherics.skyColor[2], allocator);
atmosphericsObject.AddMember("sky_color", skyColor, allocator);
atmosphericsObject.AddMember("gamma", config.atmospherics.gamma, allocator);
atmosphericsObject.AddMember("exposure", config.atmospherics.exposure, allocator);
atmosphericsObject.AddMember("enable_tone_mapping", config.atmospherics.enableToneMapping, allocator);
atmosphericsObject.AddMember("enable_shadows", config.atmospherics.enableShadows, allocator);
atmosphericsObject.AddMember("enable_ssgi", config.atmospherics.enableSSGI, allocator);
atmosphericsObject.AddMember("enable_volumetric_lighting", config.atmospherics.enableVolumetricLighting, allocator);
atmosphericsObject.AddMember("pbr_roughness", config.atmospherics.pbrRoughness, allocator);
atmosphericsObject.AddMember("pbr_metallic", config.atmospherics.pbrMetallic, allocator);
rapidjson::Value renderingObject(rapidjson::kObjectType);
renderingObject.AddMember("bgfx", bgfxObject, allocator);
renderingObject.AddMember("materialx", materialObject, allocator);
renderingObject.AddMember("atmospherics", atmosphericsObject, allocator);
document.AddMember("rendering", renderingObject, allocator);
rapidjson::Value guiObject(rapidjson::kObjectType);
rapidjson::Value fontObject(rapidjson::kObjectType);
fontObject.AddMember("use_freetype", config.guiFont.useFreeType, allocator);
fontObject.AddMember("font_path",
rapidjson::Value(config.guiFont.fontPath.string().c_str(), allocator),
allocator);
fontObject.AddMember("font_size", config.guiFont.fontSize, allocator);
guiObject.AddMember("font", fontObject, allocator);
guiObject.AddMember("opacity", config.guiOpacity, allocator);
document.AddMember("gui", guiObject, allocator);
if (!configPath.empty()) {
addStringMember(document, "config_file", configPath.string());
}
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
document.Accept(writer);
auto parentDir = configPath.parent_path();
if (!parentDir.empty()) {
std::filesystem::create_directories(parentDir);
}
std::ofstream outFile(configPath);
if (!outFile) {
throw std::runtime_error("Failed to open config output file: " + configPath.string());
}
outFile << buffer.GetString();
if (logger_) {
logger_->Info("JsonConfigWriterService: Wrote runtime config to " + configPath.string());
logger_->Trace("JsonConfigWriterService", "WriteConfig", "", "Exiting");
}
}
} // namespace sdl3cpp::services::impl