#include "RuntimeConfigStore.h" #include "RuntimeJson.h" #include #include #include #include #include namespace { double Clamp01(double value) { return (std::max)(0.0, (std::min)(1.0, value)); } bool LooksLikePackagedRuntimeRoot(const std::filesystem::path& candidate) { return std::filesystem::exists(candidate / "config" / "runtime-host.json") && std::filesystem::exists(candidate / "runtime" / "templates" / "shader_wrapper.slang.in") && std::filesystem::exists(candidate / "shaders"); } bool LooksLikeRepoRoot(const std::filesystem::path& candidate) { return std::filesystem::exists(candidate / "CMakeLists.txt") && std::filesystem::exists(candidate / "apps" / "LoopThroughWithOpenGLCompositing"); } std::filesystem::path FindRepoRootCandidate() { std::vector rootsToTry; char currentDirectory[MAX_PATH] = {}; if (GetCurrentDirectoryA(MAX_PATH, currentDirectory) > 0) rootsToTry.push_back(std::filesystem::path(currentDirectory)); char modulePath[MAX_PATH] = {}; DWORD moduleLength = GetModuleFileNameA(NULL, modulePath, MAX_PATH); if (moduleLength > 0 && moduleLength < MAX_PATH) rootsToTry.push_back(std::filesystem::path(modulePath).parent_path()); for (const std::filesystem::path& startPath : rootsToTry) { std::filesystem::path candidate = startPath; for (int depth = 0; depth < 10 && !candidate.empty(); ++depth) { if (LooksLikePackagedRuntimeRoot(candidate) || LooksLikeRepoRoot(candidate)) return candidate; candidate = candidate.parent_path(); } } return std::filesystem::path(); } } bool RuntimeConfigStore::Initialize(std::string& error) { if (!ResolvePaths(error)) return false; if (!LoadConfig(error)) return false; RefreshConfigDependentPaths(); return true; } const RuntimeConfigStore::AppConfig& RuntimeConfigStore::GetConfig() const { return mConfig; } const std::filesystem::path& RuntimeConfigStore::GetRepoRoot() const { return mRepoRoot; } const std::filesystem::path& RuntimeConfigStore::GetUiRoot() const { return mUiRoot; } const std::filesystem::path& RuntimeConfigStore::GetDocsRoot() const { return mDocsRoot; } const std::filesystem::path& RuntimeConfigStore::GetShaderRoot() const { return mShaderRoot; } const std::filesystem::path& RuntimeConfigStore::GetRuntimeRoot() const { return mRuntimeRoot; } const std::filesystem::path& RuntimeConfigStore::GetPresetRoot() const { return mPresetRoot; } const std::filesystem::path& RuntimeConfigStore::GetRuntimeStatePath() const { return mRuntimeStatePath; } const std::filesystem::path& RuntimeConfigStore::GetWrapperPath() const { return mWrapperPath; } const std::filesystem::path& RuntimeConfigStore::GetGeneratedGlslPath() const { return mGeneratedGlslPath; } const std::filesystem::path& RuntimeConfigStore::GetPatchedGlslPath() const { return mPatchedGlslPath; } void RuntimeConfigStore::SetBoundControlServerPort(unsigned short port) { mConfig.serverPort = port; } bool RuntimeConfigStore::ResolvePaths(std::string& error) { mRepoRoot = FindRepoRootCandidate(); if (mRepoRoot.empty()) { error = "Could not locate the repository root from the current runtime path."; return false; } const std::filesystem::path builtUiRoot = mRepoRoot / "ui" / "dist"; mUiRoot = std::filesystem::exists(builtUiRoot) ? builtUiRoot : (mRepoRoot / "ui"); mDocsRoot = mRepoRoot / "docs"; mConfigPath = mRepoRoot / "config" / "runtime-host.json"; mRuntimeRoot = mRepoRoot / "runtime"; mPresetRoot = mRuntimeRoot / "stack_presets"; mRuntimeStatePath = mRuntimeRoot / "runtime_state.json"; RefreshConfigDependentPaths(); std::error_code fsError; std::filesystem::create_directories(mRuntimeRoot / "shader_cache", fsError); std::filesystem::create_directories(mPresetRoot, fsError); return true; } bool RuntimeConfigStore::LoadConfig(std::string& error) { if (!std::filesystem::exists(mConfigPath)) return true; std::string configText = ReadTextFile(mConfigPath, error); if (configText.empty()) return false; JsonValue configJson; if (!ParseJson(configText, configJson, error)) return false; if (const JsonValue* shaderLibraryValue = configJson.find("shaderLibrary")) mConfig.shaderLibrary = shaderLibraryValue->asString(); if (const JsonValue* serverPortValue = configJson.find("serverPort")) mConfig.serverPort = static_cast(serverPortValue->asNumber(mConfig.serverPort)); if (const JsonValue* oscPortValue = configJson.find("oscPort")) mConfig.oscPort = static_cast(oscPortValue->asNumber(mConfig.oscPort)); if (const JsonValue* oscBindAddressValue = configJson.find("oscBindAddress")) mConfig.oscBindAddress = oscBindAddressValue->asString(); if (const JsonValue* oscSmoothingValue = configJson.find("oscSmoothing")) mConfig.oscSmoothing = Clamp01(oscSmoothingValue->asNumber(mConfig.oscSmoothing)); if (const JsonValue* autoReloadValue = configJson.find("autoReload")) mConfig.autoReload = autoReloadValue->asBoolean(mConfig.autoReload); if (const JsonValue* maxTemporalHistoryFramesValue = configJson.find("maxTemporalHistoryFrames")) { const double configuredValue = maxTemporalHistoryFramesValue->asNumber(static_cast(mConfig.maxTemporalHistoryFrames)); mConfig.maxTemporalHistoryFrames = configuredValue <= 0.0 ? 0u : static_cast(configuredValue); } if (const JsonValue* previewFpsValue = configJson.find("previewFps")) { const double configuredValue = previewFpsValue->asNumber(static_cast(mConfig.previewFps)); mConfig.previewFps = configuredValue <= 0.0 ? 0u : static_cast(configuredValue); } if (const JsonValue* enableExternalKeyingValue = configJson.find("enableExternalKeying")) mConfig.enableExternalKeying = enableExternalKeyingValue->asBoolean(mConfig.enableExternalKeying); if (const JsonValue* videoFormatValue = configJson.find("videoFormat")) { if (videoFormatValue->isString() && !videoFormatValue->asString().empty()) { mConfig.inputVideoFormat = videoFormatValue->asString(); mConfig.outputVideoFormat = videoFormatValue->asString(); } } if (const JsonValue* frameRateValue = configJson.find("frameRate")) { if (frameRateValue->isString() && !frameRateValue->asString().empty()) { mConfig.inputFrameRate = frameRateValue->asString(); mConfig.outputFrameRate = frameRateValue->asString(); } else if (frameRateValue->isNumber()) { std::ostringstream stream; stream << frameRateValue->asNumber(); mConfig.inputFrameRate = stream.str(); mConfig.outputFrameRate = stream.str(); } } if (const JsonValue* inputVideoFormatValue = configJson.find("inputVideoFormat")) { if (inputVideoFormatValue->isString() && !inputVideoFormatValue->asString().empty()) mConfig.inputVideoFormat = inputVideoFormatValue->asString(); } if (const JsonValue* inputFrameRateValue = configJson.find("inputFrameRate")) { if (inputFrameRateValue->isString() && !inputFrameRateValue->asString().empty()) mConfig.inputFrameRate = inputFrameRateValue->asString(); else if (inputFrameRateValue->isNumber()) { std::ostringstream stream; stream << inputFrameRateValue->asNumber(); mConfig.inputFrameRate = stream.str(); } } if (const JsonValue* outputVideoFormatValue = configJson.find("outputVideoFormat")) { if (outputVideoFormatValue->isString() && !outputVideoFormatValue->asString().empty()) mConfig.outputVideoFormat = outputVideoFormatValue->asString(); } if (const JsonValue* outputFrameRateValue = configJson.find("outputFrameRate")) { if (outputFrameRateValue->isString() && !outputFrameRateValue->asString().empty()) mConfig.outputFrameRate = outputFrameRateValue->asString(); else if (outputFrameRateValue->isNumber()) { std::ostringstream stream; stream << outputFrameRateValue->asNumber(); mConfig.outputFrameRate = stream.str(); } } return true; } std::string RuntimeConfigStore::ReadTextFile(const std::filesystem::path& path, std::string& error) const { std::ifstream input(path, std::ios::binary); if (!input) { error = "Could not open file: " + path.string(); return std::string(); } std::ostringstream buffer; buffer << input.rdbuf(); return buffer.str(); } void RuntimeConfigStore::RefreshConfigDependentPaths() { mShaderRoot = mRepoRoot / mConfig.shaderLibrary; mWrapperPath = mRuntimeRoot / "shader_cache" / "active_shader_wrapper.slang"; mGeneratedGlslPath = mRuntimeRoot / "shader_cache" / "active_shader.raw.frag"; mPatchedGlslPath = mRuntimeRoot / "shader_cache" / "active_shader.frag"; }