#include "RuntimeStore.h" namespace { std::string TrimCopy(const std::string& text) { std::size_t start = 0; while (start < text.size() && std::isspace(static_cast(text[start]))) ++start; std::size_t end = text.size(); while (end > start && std::isspace(static_cast(text[end - 1]))) --end; return text.substr(start, end - start); } std::string SimplifyControlKey(const std::string& text) { std::string simplified; for (unsigned char ch : text) { if (std::isalnum(ch)) simplified.push_back(static_cast(std::tolower(ch))); } return simplified; } bool MatchesControlKey(const std::string& candidate, const std::string& key) { return candidate == key || SimplifyControlKey(candidate) == SimplifyControlKey(key); } bool TextureAssetsEqual(const std::vector& left, const std::vector& right) { if (left.size() != right.size()) return false; for (std::size_t index = 0; index < left.size(); ++index) { if (left[index].id != right[index].id || left[index].path != right[index].path || left[index].writeTime != right[index].writeTime) { return false; } } return true; } bool FontAssetsEqual(const std::vector& left, const std::vector& right) { if (left.size() != right.size()) return false; for (std::size_t index = 0; index < left.size(); ++index) { if (left[index].id != right[index].id || left[index].path != right[index].path || left[index].writeTime != right[index].writeTime) { return false; } } return true; } } RuntimeStore::RuntimeStore(RuntimeHost& runtimeHost) : mRuntimeHost(runtimeHost) { } bool RuntimeStore::InitializeStore(std::string& error) { try { std::lock_guard lock(mRuntimeHost.mMutex); if (!mRuntimeHost.ResolvePaths(error)) return false; if (!mRuntimeHost.LoadConfig(error)) return false; mRuntimeHost.mShaderRoot = mRuntimeHost.mRepoRoot / mRuntimeHost.mConfig.shaderLibrary; if (!mRuntimeHost.LoadPersistentState(error)) return false; if (!mRuntimeHost.ScanShaderPackages(error)) return false; mRuntimeHost.NormalizePersistentLayerIdsLocked(); for (RuntimeHost::LayerPersistentState& layer : mRuntimeHost.mPersistentState.layers) { auto shaderIt = mRuntimeHost.mPackagesById.find(layer.shaderId); if (shaderIt != mRuntimeHost.mPackagesById.end()) mRuntimeHost.EnsureLayerDefaultsLocked(layer, shaderIt->second); } if (mRuntimeHost.mPersistentState.layers.empty() && !mRuntimeHost.mPackageOrder.empty()) { RuntimeHost::LayerPersistentState layer; layer.id = mRuntimeHost.GenerateLayerId(); layer.shaderId = mRuntimeHost.mPackageOrder.front(); layer.bypass = false; mRuntimeHost.EnsureLayerDefaultsLocked(layer, mRuntimeHost.mPackagesById[layer.shaderId]); mRuntimeHost.mPersistentState.layers.push_back(layer); } mRuntimeHost.mServerPort = mRuntimeHost.mConfig.serverPort; mRuntimeHost.mAutoReloadEnabled = mRuntimeHost.mConfig.autoReload; mRuntimeHost.mReloadRequested = true; mRuntimeHost.mCompileMessage = "Waiting for shader compile."; return true; } catch (const std::exception& exception) { error = std::string("RuntimeStore::InitializeStore exception: ") + exception.what(); return false; } catch (...) { error = "RuntimeStore::InitializeStore threw a non-standard exception."; return false; } } std::string RuntimeStore::BuildPersistentStateJson() const { return SerializeJson(BuildRuntimeStateValue(), true); } bool RuntimeStore::PollStoredFileChanges(bool& registryChanged, bool& reloadRequested, std::string& error) { try { std::lock_guard lock(mRuntimeHost.mMutex); registryChanged = false; reloadRequested = false; if (!mRuntimeHost.mAutoReloadEnabled) { reloadRequested = mRuntimeHost.mReloadRequested; return true; } const auto now = std::chrono::steady_clock::now(); if (mRuntimeHost.mLastScanTime != std::chrono::steady_clock::time_point::min() && std::chrono::duration_cast(now - mRuntimeHost.mLastScanTime).count() < 250) { reloadRequested = mRuntimeHost.mReloadRequested; return true; } mRuntimeHost.mLastScanTime = now; std::string scanError; std::map previousPackages = mRuntimeHost.mPackagesById; std::vector previousOrder = mRuntimeHost.mPackageOrder; std::map> previousLayerShaderTimes; for (const RuntimeHost::LayerPersistentState& layer : mRuntimeHost.mPersistentState.layers) { auto previous = previousPackages.find(layer.shaderId); if (previous != previousPackages.end()) previousLayerShaderTimes[layer.id] = std::make_pair(previous->second.shaderWriteTime, previous->second.manifestWriteTime); } if (!mRuntimeHost.ScanShaderPackages(scanError)) { error = scanError; return false; } registryChanged = previousOrder != mRuntimeHost.mPackageOrder; if (!registryChanged && previousPackages.size() == mRuntimeHost.mPackagesById.size()) { for (const auto& item : mRuntimeHost.mPackagesById) { auto previous = previousPackages.find(item.first); if (previous == previousPackages.end()) { registryChanged = true; break; } if (previous->second.shaderWriteTime != item.second.shaderWriteTime || previous->second.manifestWriteTime != item.second.manifestWriteTime || !TextureAssetsEqual(previous->second.textureAssets, item.second.textureAssets) || !FontAssetsEqual(previous->second.fontAssets, item.second.fontAssets)) { registryChanged = true; break; } } } for (RuntimeHost::LayerPersistentState& layer : mRuntimeHost.mPersistentState.layers) { auto active = mRuntimeHost.mPackagesById.find(layer.shaderId); auto previous = previousLayerShaderTimes.find(layer.id); if (active == mRuntimeHost.mPackagesById.end()) continue; mRuntimeHost.EnsureLayerDefaultsLocked(layer, active->second); if (previous != previousLayerShaderTimes.end()) { auto previousPackage = previousPackages.find(layer.shaderId); if (previous->second.first != active->second.shaderWriteTime || previous->second.second != active->second.manifestWriteTime || (previousPackage != previousPackages.end() && (!TextureAssetsEqual(previousPackage->second.textureAssets, active->second.textureAssets) || !FontAssetsEqual(previousPackage->second.fontAssets, active->second.fontAssets)))) { mRuntimeHost.mReloadRequested = true; } } } reloadRequested = mRuntimeHost.mReloadRequested; if (registryChanged || reloadRequested) mRuntimeHost.MarkRenderStateDirtyLocked(); return true; } catch (const std::exception& exception) { error = std::string("RuntimeStore::PollStoredFileChanges exception: ") + exception.what(); return false; } catch (...) { error = "RuntimeStore::PollStoredFileChanges threw a non-standard exception."; return false; } } bool RuntimeStore::CreateStoredLayer(const std::string& shaderId, std::string& error) { std::lock_guard lock(mRuntimeHost.mMutex); auto shaderIt = mRuntimeHost.mPackagesById.find(shaderId); if (shaderIt == mRuntimeHost.mPackagesById.end()) { error = "Unknown shader id: " + shaderId; return false; } RuntimeHost::LayerPersistentState layer; layer.id = mRuntimeHost.GenerateLayerId(); layer.shaderId = shaderId; layer.bypass = false; mRuntimeHost.EnsureLayerDefaultsLocked(layer, shaderIt->second); mRuntimeHost.mPersistentState.layers.push_back(layer); mRuntimeHost.mReloadRequested = true; mRuntimeHost.MarkRenderStateDirtyLocked(); return mRuntimeHost.SavePersistentState(error); } bool RuntimeStore::DeleteStoredLayer(const std::string& layerId, std::string& error) { std::lock_guard lock(mRuntimeHost.mMutex); auto it = std::find_if(mRuntimeHost.mPersistentState.layers.begin(), mRuntimeHost.mPersistentState.layers.end(), [&layerId](const RuntimeHost::LayerPersistentState& layer) { return layer.id == layerId; }); if (it == mRuntimeHost.mPersistentState.layers.end()) { error = "Unknown layer id: " + layerId; return false; } mRuntimeHost.mPersistentState.layers.erase(it); mRuntimeHost.mReloadRequested = true; mRuntimeHost.MarkRenderStateDirtyLocked(); return mRuntimeHost.SavePersistentState(error); } bool RuntimeStore::MoveStoredLayer(const std::string& layerId, int direction, std::string& error) { std::lock_guard lock(mRuntimeHost.mMutex); auto it = std::find_if(mRuntimeHost.mPersistentState.layers.begin(), mRuntimeHost.mPersistentState.layers.end(), [&layerId](const RuntimeHost::LayerPersistentState& layer) { return layer.id == layerId; }); if (it == mRuntimeHost.mPersistentState.layers.end()) { error = "Unknown layer id: " + layerId; return false; } const std::ptrdiff_t index = std::distance(mRuntimeHost.mPersistentState.layers.begin(), it); const std::ptrdiff_t newIndex = index + direction; if (newIndex < 0 || newIndex >= static_cast(mRuntimeHost.mPersistentState.layers.size())) return true; std::swap(mRuntimeHost.mPersistentState.layers[index], mRuntimeHost.mPersistentState.layers[newIndex]); mRuntimeHost.mReloadRequested = true; mRuntimeHost.MarkRenderStateDirtyLocked(); return mRuntimeHost.SavePersistentState(error); } bool RuntimeStore::MoveStoredLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error) { std::lock_guard lock(mRuntimeHost.mMutex); auto it = std::find_if(mRuntimeHost.mPersistentState.layers.begin(), mRuntimeHost.mPersistentState.layers.end(), [&layerId](const RuntimeHost::LayerPersistentState& layer) { return layer.id == layerId; }); if (it == mRuntimeHost.mPersistentState.layers.end()) { error = "Unknown layer id: " + layerId; return false; } if (mRuntimeHost.mPersistentState.layers.empty()) return true; if (targetIndex >= mRuntimeHost.mPersistentState.layers.size()) targetIndex = mRuntimeHost.mPersistentState.layers.size() - 1; const std::size_t sourceIndex = static_cast(std::distance(mRuntimeHost.mPersistentState.layers.begin(), it)); if (sourceIndex == targetIndex) return true; RuntimeHost::LayerPersistentState movedLayer = *it; mRuntimeHost.mPersistentState.layers.erase(mRuntimeHost.mPersistentState.layers.begin() + static_cast(sourceIndex)); mRuntimeHost.mPersistentState.layers.insert(mRuntimeHost.mPersistentState.layers.begin() + static_cast(targetIndex), movedLayer); mRuntimeHost.mReloadRequested = true; mRuntimeHost.MarkRenderStateDirtyLocked(); return mRuntimeHost.SavePersistentState(error); } bool RuntimeStore::SetStoredLayerBypassState(const std::string& layerId, bool bypassed, std::string& error) { std::lock_guard lock(mRuntimeHost.mMutex); RuntimeHost::LayerPersistentState* layer = mRuntimeHost.FindLayerById(layerId); if (!layer) { error = "Unknown layer id: " + layerId; return false; } layer->bypass = bypassed; mRuntimeHost.mReloadRequested = true; mRuntimeHost.MarkParameterStateDirtyLocked(); return mRuntimeHost.SavePersistentState(error); } bool RuntimeStore::SetStoredLayerShaderSelection(const std::string& layerId, const std::string& shaderId, std::string& error) { std::lock_guard lock(mRuntimeHost.mMutex); RuntimeHost::LayerPersistentState* layer = mRuntimeHost.FindLayerById(layerId); if (!layer) { error = "Unknown layer id: " + layerId; return false; } auto shaderIt = mRuntimeHost.mPackagesById.find(shaderId); if (shaderIt == mRuntimeHost.mPackagesById.end()) { error = "Unknown shader id: " + shaderId; return false; } layer->shaderId = shaderId; layer->parameterValues.clear(); mRuntimeHost.EnsureLayerDefaultsLocked(*layer, shaderIt->second); mRuntimeHost.mReloadRequested = true; mRuntimeHost.MarkRenderStateDirtyLocked(); return mRuntimeHost.SavePersistentState(error); } bool RuntimeStore::SetStoredParameterValue(const std::string& layerId, const std::string& parameterId, const JsonValue& newValue, std::string& error) { std::lock_guard lock(mRuntimeHost.mMutex); RuntimeHost::LayerPersistentState* layer = mRuntimeHost.FindLayerById(layerId); if (!layer) { error = "Unknown layer id: " + layerId; return false; } auto shaderIt = mRuntimeHost.mPackagesById.find(layer->shaderId); if (shaderIt == mRuntimeHost.mPackagesById.end()) { error = "Unknown shader id: " + layer->shaderId; return false; } const ShaderPackage& shaderPackage = shaderIt->second; auto parameterIt = std::find_if(shaderPackage.parameters.begin(), shaderPackage.parameters.end(), [¶meterId](const ShaderParameterDefinition& definition) { return definition.id == parameterId; }); if (parameterIt == shaderPackage.parameters.end()) { error = "Unknown parameter id: " + parameterId; return false; } if (parameterIt->type == ShaderParameterType::Trigger) { ShaderParameterValue& value = layer->parameterValues[parameterId]; const double previousCount = value.numberValues.empty() ? 0.0 : value.numberValues[0]; const double triggerTime = std::chrono::duration_cast>(std::chrono::steady_clock::now() - mRuntimeHost.mStartTime).count(); value.numberValues = { previousCount + 1.0, triggerTime }; mRuntimeHost.MarkParameterStateDirtyLocked(); return true; } ShaderParameterValue normalized; if (!mRuntimeHost.NormalizeAndValidateValue(*parameterIt, newValue, normalized, error)) return false; layer->parameterValues[parameterId] = normalized; mRuntimeHost.MarkParameterStateDirtyLocked(); return mRuntimeHost.SavePersistentState(error); } bool RuntimeStore::SetStoredParameterValueByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue, std::string& error) { return SetStoredParameterValueByControlKey(layerKey, parameterKey, newValue, true, error); } bool RuntimeStore::SetStoredParameterValueByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue, bool persistState, std::string& error) { std::lock_guard lock(mRuntimeHost.mMutex); RuntimeHost::LayerPersistentState* matchedLayer = nullptr; const ShaderPackage* matchedPackage = nullptr; std::vector::const_iterator parameterIt; if (!TryResolveStoredLayerAndParameterByControlKeyLocked(layerKey, parameterKey, matchedLayer, matchedPackage, parameterIt, error)) return false; if (parameterIt->type == ShaderParameterType::Trigger) { ShaderParameterValue& value = matchedLayer->parameterValues[parameterIt->id]; const double previousCount = value.numberValues.empty() ? 0.0 : value.numberValues[0]; const double triggerTime = std::chrono::duration_cast>(std::chrono::steady_clock::now() - mRuntimeHost.mStartTime).count(); value.numberValues = { previousCount + 1.0, triggerTime }; mRuntimeHost.MarkParameterStateDirtyLocked(); return true; } ShaderParameterValue normalized; if (!mRuntimeHost.NormalizeAndValidateValue(*parameterIt, newValue, normalized, error)) return false; matchedLayer->parameterValues[parameterIt->id] = normalized; mRuntimeHost.MarkParameterStateDirtyLocked(); return !persistState || mRuntimeHost.SavePersistentState(error); } bool RuntimeStore::ResetStoredLayerParameterValues(const std::string& layerId, std::string& error) { std::lock_guard lock(mRuntimeHost.mMutex); RuntimeHost::LayerPersistentState* layer = mRuntimeHost.FindLayerById(layerId); if (!layer) { error = "Unknown layer id: " + layerId; return false; } auto shaderIt = mRuntimeHost.mPackagesById.find(layer->shaderId); if (shaderIt == mRuntimeHost.mPackagesById.end()) { error = "Unknown shader id: " + layer->shaderId; return false; } layer->parameterValues.clear(); mRuntimeHost.EnsureLayerDefaultsLocked(*layer, shaderIt->second); mRuntimeHost.MarkParameterStateDirtyLocked(); return mRuntimeHost.SavePersistentState(error); } bool RuntimeStore::SaveStackPresetSnapshot(const std::string& presetName, std::string& error) const { std::lock_guard lock(mRuntimeHost.mMutex); const std::string safeStem = mRuntimeHost.MakeSafePresetFileStem(presetName); if (safeStem.empty()) { error = "Preset name must include at least one letter or number."; return false; } JsonValue root = JsonValue::MakeObject(); root.set("version", JsonValue(1.0)); root.set("name", JsonValue(TrimCopy(presetName))); root.set("layers", mRuntimeHost.SerializeLayerStackLocked()); return mRuntimeHost.WriteTextFile(mRuntimeHost.mPresetRoot / (safeStem + ".json"), SerializeJson(root, true), error); } bool RuntimeStore::LoadStackPresetSnapshot(const std::string& presetName, std::string& error) { std::lock_guard lock(mRuntimeHost.mMutex); const std::string safeStem = mRuntimeHost.MakeSafePresetFileStem(presetName); if (safeStem.empty()) { error = "Preset name must include at least one letter or number."; return false; } const std::filesystem::path presetPath = mRuntimeHost.mPresetRoot / (safeStem + ".json"); std::string presetText = mRuntimeHost.ReadTextFile(presetPath, error); if (presetText.empty()) return false; JsonValue root; if (!ParseJson(presetText, root, error)) return false; const JsonValue* layersValue = root.find("layers"); if (!layersValue || !layersValue->isArray()) { error = "Preset file is missing a valid 'layers' array."; return false; } std::vector nextLayers; if (!mRuntimeHost.DeserializeLayerStackLocked(*layersValue, nextLayers, error)) return false; if (nextLayers.empty()) { error = "Preset does not contain any valid layers."; return false; } mRuntimeHost.mPersistentState.layers = nextLayers; mRuntimeHost.mReloadRequested = true; mRuntimeHost.MarkRenderStateDirtyLocked(); return mRuntimeHost.SavePersistentState(error); } const std::filesystem::path& RuntimeStore::GetRuntimeRepositoryRoot() const { return mRuntimeHost.GetRepoRoot(); } const std::filesystem::path& RuntimeStore::GetRuntimeUiRoot() const { return mRuntimeHost.GetUiRoot(); } const std::filesystem::path& RuntimeStore::GetRuntimeDocsRoot() const { return mRuntimeHost.GetDocsRoot(); } const std::filesystem::path& RuntimeStore::GetRuntimeDataRoot() const { return mRuntimeHost.GetRuntimeRoot(); } unsigned short RuntimeStore::GetConfiguredControlServerPort() const { return mRuntimeHost.GetServerPort(); } unsigned short RuntimeStore::GetConfiguredOscPort() const { return mRuntimeHost.GetOscPort(); } const std::string& RuntimeStore::GetConfiguredOscBindAddress() const { return mRuntimeHost.GetOscBindAddress(); } double RuntimeStore::GetConfiguredOscSmoothing() const { return mRuntimeHost.GetOscSmoothing(); } unsigned RuntimeStore::GetConfiguredMaxTemporalHistoryFrames() const { return mRuntimeHost.GetMaxTemporalHistoryFrames(); } unsigned RuntimeStore::GetConfiguredPreviewFps() const { return mRuntimeHost.GetPreviewFps(); } bool RuntimeStore::IsExternalKeyingConfigured() const { return mRuntimeHost.ExternalKeyingEnabled(); } const std::string& RuntimeStore::GetConfiguredInputVideoFormat() const { return mRuntimeHost.GetInputVideoFormat(); } const std::string& RuntimeStore::GetConfiguredInputFrameRate() const { return mRuntimeHost.GetInputFrameRate(); } const std::string& RuntimeStore::GetConfiguredOutputVideoFormat() const { return mRuntimeHost.GetOutputVideoFormat(); } const std::string& RuntimeStore::GetConfiguredOutputFrameRate() const { return mRuntimeHost.GetOutputFrameRate(); } void RuntimeStore::SetCompileStatus(bool succeeded, const std::string& message) { std::lock_guard lock(mRuntimeHost.mMutex); mRuntimeHost.mCompileSucceeded = succeeded; mRuntimeHost.mCompileMessage = message; } void RuntimeStore::ClearReloadRequest() { std::lock_guard lock(mRuntimeHost.mMutex); mRuntimeHost.mReloadRequested = false; } JsonValue RuntimeStore::BuildRuntimeStateValue() const { const HealthTelemetry::Snapshot telemetrySnapshot = mRuntimeHost.mHealthTelemetry.GetSnapshot(); std::lock_guard lock(mRuntimeHost.mMutex); JsonValue root = JsonValue::MakeObject(); JsonValue app = JsonValue::MakeObject(); app.set("serverPort", JsonValue(static_cast(mRuntimeHost.mServerPort))); app.set("oscPort", JsonValue(static_cast(mRuntimeHost.mConfig.oscPort))); app.set("oscBindAddress", JsonValue(mRuntimeHost.mConfig.oscBindAddress)); app.set("oscSmoothing", JsonValue(mRuntimeHost.mConfig.oscSmoothing)); app.set("autoReload", JsonValue(mRuntimeHost.mAutoReloadEnabled)); app.set("maxTemporalHistoryFrames", JsonValue(static_cast(mRuntimeHost.mConfig.maxTemporalHistoryFrames))); app.set("previewFps", JsonValue(static_cast(mRuntimeHost.mConfig.previewFps))); app.set("enableExternalKeying", JsonValue(mRuntimeHost.mConfig.enableExternalKeying)); app.set("inputVideoFormat", JsonValue(mRuntimeHost.mConfig.inputVideoFormat)); app.set("inputFrameRate", JsonValue(mRuntimeHost.mConfig.inputFrameRate)); app.set("outputVideoFormat", JsonValue(mRuntimeHost.mConfig.outputVideoFormat)); app.set("outputFrameRate", JsonValue(mRuntimeHost.mConfig.outputFrameRate)); root.set("app", app); JsonValue runtime = JsonValue::MakeObject(); runtime.set("layerCount", JsonValue(static_cast(mRuntimeHost.mPersistentState.layers.size()))); runtime.set("compileSucceeded", JsonValue(mRuntimeHost.mCompileSucceeded)); runtime.set("compileMessage", JsonValue(mRuntimeHost.mCompileMessage)); root.set("runtime", runtime); JsonValue video = JsonValue::MakeObject(); video.set("hasSignal", JsonValue(telemetrySnapshot.signal.hasSignal)); video.set("width", JsonValue(static_cast(telemetrySnapshot.signal.width))); video.set("height", JsonValue(static_cast(telemetrySnapshot.signal.height))); video.set("modeName", JsonValue(telemetrySnapshot.signal.modeName)); root.set("video", video); JsonValue deckLink = JsonValue::MakeObject(); deckLink.set("modelName", JsonValue(telemetrySnapshot.videoIO.modelName)); deckLink.set("supportsInternalKeying", JsonValue(telemetrySnapshot.videoIO.supportsInternalKeying)); deckLink.set("supportsExternalKeying", JsonValue(telemetrySnapshot.videoIO.supportsExternalKeying)); deckLink.set("keyerInterfaceAvailable", JsonValue(telemetrySnapshot.videoIO.keyerInterfaceAvailable)); deckLink.set("externalKeyingRequested", JsonValue(telemetrySnapshot.videoIO.externalKeyingRequested)); deckLink.set("externalKeyingActive", JsonValue(telemetrySnapshot.videoIO.externalKeyingActive)); deckLink.set("statusMessage", JsonValue(telemetrySnapshot.videoIO.statusMessage)); root.set("decklink", deckLink); JsonValue videoIO = JsonValue::MakeObject(); videoIO.set("backend", JsonValue(telemetrySnapshot.videoIO.backendName)); videoIO.set("modelName", JsonValue(telemetrySnapshot.videoIO.modelName)); videoIO.set("supportsInternalKeying", JsonValue(telemetrySnapshot.videoIO.supportsInternalKeying)); videoIO.set("supportsExternalKeying", JsonValue(telemetrySnapshot.videoIO.supportsExternalKeying)); videoIO.set("keyerInterfaceAvailable", JsonValue(telemetrySnapshot.videoIO.keyerInterfaceAvailable)); videoIO.set("externalKeyingRequested", JsonValue(telemetrySnapshot.videoIO.externalKeyingRequested)); videoIO.set("externalKeyingActive", JsonValue(telemetrySnapshot.videoIO.externalKeyingActive)); videoIO.set("statusMessage", JsonValue(telemetrySnapshot.videoIO.statusMessage)); root.set("videoIO", videoIO); JsonValue performance = JsonValue::MakeObject(); performance.set("frameBudgetMs", JsonValue(telemetrySnapshot.performance.frameBudgetMilliseconds)); performance.set("renderMs", JsonValue(telemetrySnapshot.performance.renderMilliseconds)); performance.set("smoothedRenderMs", JsonValue(telemetrySnapshot.performance.smoothedRenderMilliseconds)); performance.set("budgetUsedPercent", JsonValue( telemetrySnapshot.performance.frameBudgetMilliseconds > 0.0 ? (telemetrySnapshot.performance.smoothedRenderMilliseconds / telemetrySnapshot.performance.frameBudgetMilliseconds) * 100.0 : 0.0)); performance.set("completionIntervalMs", JsonValue(telemetrySnapshot.performance.completionIntervalMilliseconds)); performance.set("smoothedCompletionIntervalMs", JsonValue(telemetrySnapshot.performance.smoothedCompletionIntervalMilliseconds)); performance.set("maxCompletionIntervalMs", JsonValue(telemetrySnapshot.performance.maxCompletionIntervalMilliseconds)); performance.set("lateFrameCount", JsonValue(static_cast(telemetrySnapshot.performance.lateFrameCount))); performance.set("droppedFrameCount", JsonValue(static_cast(telemetrySnapshot.performance.droppedFrameCount))); performance.set("flushedFrameCount", JsonValue(static_cast(telemetrySnapshot.performance.flushedFrameCount))); root.set("performance", performance); JsonValue shaderLibrary = JsonValue::MakeArray(); for (const ShaderPackageStatus& status : mRuntimeHost.mPackageStatuses) { JsonValue shader = JsonValue::MakeObject(); shader.set("id", JsonValue(status.id)); shader.set("name", JsonValue(status.displayName)); shader.set("description", JsonValue(status.description)); shader.set("category", JsonValue(status.category)); shader.set("available", JsonValue(status.available)); if (!status.available) shader.set("error", JsonValue(status.error)); auto shaderIt = mRuntimeHost.mPackagesById.find(status.id); if (status.available && shaderIt != mRuntimeHost.mPackagesById.end() && shaderIt->second.temporal.enabled) { JsonValue temporal = JsonValue::MakeObject(); temporal.set("enabled", JsonValue(true)); temporal.set("historySource", JsonValue(mRuntimeHost.TemporalHistorySourceToString(shaderIt->second.temporal.historySource))); temporal.set("requestedHistoryLength", JsonValue(static_cast(shaderIt->second.temporal.requestedHistoryLength))); temporal.set("effectiveHistoryLength", JsonValue(static_cast(shaderIt->second.temporal.effectiveHistoryLength))); shader.set("temporal", temporal); } if (status.available && shaderIt != mRuntimeHost.mPackagesById.end() && shaderIt->second.feedback.enabled) { JsonValue feedback = JsonValue::MakeObject(); feedback.set("enabled", JsonValue(true)); feedback.set("writePass", JsonValue(shaderIt->second.feedback.writePassId)); shader.set("feedback", feedback); } shaderLibrary.pushBack(shader); } root.set("shaders", shaderLibrary); JsonValue stackPresets = JsonValue::MakeArray(); for (const std::string& presetName : mRuntimeHost.GetStackPresetNamesLocked()) stackPresets.pushBack(JsonValue(presetName)); root.set("stackPresets", stackPresets); root.set("layers", SerializeLayerStack()); return root; } JsonValue RuntimeStore::SerializeLayerStack() const { return mRuntimeHost.SerializeLayerStackLocked(); } bool RuntimeStore::TryResolveStoredLayerAndParameterByControlKeyLocked(const std::string& layerKey, const std::string& parameterKey, RuntimeHost::LayerPersistentState*& matchedLayer, const ShaderPackage*& matchedPackage, std::vector::const_iterator& parameterIt, std::string& error) const { matchedLayer = nullptr; matchedPackage = nullptr; for (RuntimeHost::LayerPersistentState& layer : mRuntimeHost.mPersistentState.layers) { auto shaderIt = mRuntimeHost.mPackagesById.find(layer.shaderId); if (shaderIt == mRuntimeHost.mPackagesById.end()) continue; if (MatchesControlKey(layer.id, layerKey) || MatchesControlKey(shaderIt->second.id, layerKey) || MatchesControlKey(shaderIt->second.displayName, layerKey)) { matchedLayer = &layer; matchedPackage = &shaderIt->second; break; } } if (!matchedLayer || !matchedPackage) { error = "Unknown OSC layer key: " + layerKey; return false; } parameterIt = std::find_if(matchedPackage->parameters.begin(), matchedPackage->parameters.end(), [¶meterKey](const ShaderParameterDefinition& definition) { return MatchesControlKey(definition.id, parameterKey) || MatchesControlKey(definition.label, parameterKey); }); if (parameterIt == matchedPackage->parameters.end()) { error = "Unknown OSC parameter key: " + parameterKey; return false; } return true; }