#include "RuntimeStore.h" #include "RuntimeStatePresenter.h" #include #include #include #include #include namespace { std::string ToLowerCopy(std::string text) { std::transform(text.begin(), text.end(), text.begin(), [](unsigned char ch) { return static_cast(std::tolower(ch)); }); return text; } double GenerateStartupRandom() { std::random_device randomDevice; std::uniform_real_distribution distribution(0.0, 1.0); return distribution(randomDevice); } std::string PersistenceTargetKindName(PersistenceTargetKind targetKind) { switch (targetKind) { case PersistenceTargetKind::RuntimeState: return "runtime-state"; case PersistenceTargetKind::StackPreset: return "stack-preset"; case PersistenceTargetKind::RuntimeConfig: return "runtime-config"; default: return "unknown"; } } } RuntimeStore::RuntimeStore() : mRenderSnapshotBuilder(*this), mHealthTelemetry(), mReloadRequested(false), mCompileSucceeded(false), mStartupRandom(GenerateStartupRandom()), mServerPort(8080), mAutoReloadEnabled(true), mStartTime(std::chrono::steady_clock::now()), mLastScanTime((std::chrono::steady_clock::time_point::min)()) { mPersistenceWriter.SetResultCallback([this](const PersistenceWriteResult& result) { mHealthTelemetry.RecordPersistenceWriteResult( result.succeeded, PersistenceTargetKindName(result.targetKind), result.targetPath, result.reason, result.errorMessage, result.newerRequestPending); }); } HealthTelemetry& RuntimeStore::GetHealthTelemetry() { return mHealthTelemetry; } const HealthTelemetry& RuntimeStore::GetHealthTelemetry() const { return mHealthTelemetry; } RenderSnapshotBuilder& RuntimeStore::GetRenderSnapshotBuilder() { return mRenderSnapshotBuilder; } const RenderSnapshotBuilder& RuntimeStore::GetRenderSnapshotBuilder() const { return mRenderSnapshotBuilder; } bool RuntimeStore::InitializeStore(std::string& error) { try { std::lock_guard lock(mMutex); if (!mConfigStore.Initialize(error)) return false; if (!LoadPersistentState(error)) return false; if (!ScanShaderPackages(error)) return false; mCommittedLiveState.NormalizeLayerIds(); mCommittedLiveState.EnsureDefaultsForAllLayers(mShaderCatalog); mCommittedLiveState.EnsureDefaultLayer(mShaderCatalog); mServerPort = mConfigStore.GetConfig().serverPort; mAutoReloadEnabled = mConfigStore.GetConfig().autoReload; mReloadRequested = true; 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 RuntimeStatePresenter::BuildRuntimeStateJson(*this); } PersistenceSnapshot RuntimeStore::BuildRuntimeStatePersistenceSnapshot(const PersistenceRequest& request) const { std::lock_guard lock(mMutex); return BuildRuntimeStatePersistenceSnapshotLocked(request); } PersistenceSnapshot RuntimeStore::BuildRuntimeStatePersistenceSnapshotLocked(const PersistenceRequest& request) const { PersistenceSnapshot snapshot; snapshot.targetKind = PersistenceTargetKind::RuntimeState; snapshot.targetPath = mConfigStore.GetRuntimeStatePath(); snapshot.contents = SerializeJson(mCommittedLiveState.BuildPersistentStateValue(mShaderCatalog), true); snapshot.reason = request.reason; snapshot.debounceKey = request.debounceKey; snapshot.debounceAllowed = request.debounceAllowed; snapshot.flushRequested = request.flushRequested; snapshot.generation = request.sequence; return snapshot; } bool RuntimeStore::PollStoredFileChanges(bool& registryChanged, bool& reloadRequested, std::string& error) { try { std::lock_guard lock(mMutex); registryChanged = false; reloadRequested = false; if (!mAutoReloadEnabled) { reloadRequested = mReloadRequested; return true; } const auto now = std::chrono::steady_clock::now(); if (mLastScanTime != (std::chrono::steady_clock::time_point::min)() && std::chrono::duration_cast(now - mLastScanTime).count() < 250) { reloadRequested = mReloadRequested; return true; } mLastScanTime = now; std::string scanError; const ShaderPackageCatalog::Snapshot previousCatalog = mShaderCatalog.CaptureSnapshot(); if (!ScanShaderPackages(scanError)) { error = scanError; return false; } registryChanged = mShaderCatalog.HasCatalogChangedSince(previousCatalog); mCommittedLiveState.EnsureDefaultsForAllLayers(mShaderCatalog); for (RuntimeStore::LayerPersistentState& layer : mCommittedLiveState.Layers()) { const ShaderPackage* active = mShaderCatalog.FindPackage(layer.shaderId); if (!active) continue; if (mShaderCatalog.HasPackageChangedSince(previousCatalog, layer.shaderId)) mReloadRequested = true; } reloadRequested = mReloadRequested; if (registryChanged || reloadRequested) 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(mMutex); if (!mCommittedLiveState.CreateLayer(mShaderCatalog, shaderId, error)) return false; mReloadRequested = true; MarkRenderStateDirtyLocked(); return SavePersistentState(error); } bool RuntimeStore::DeleteStoredLayer(const std::string& layerId, std::string& error) { std::lock_guard lock(mMutex); if (!mCommittedLiveState.DeleteLayer(layerId, error)) return false; mReloadRequested = true; MarkRenderStateDirtyLocked(); return SavePersistentState(error); } bool RuntimeStore::MoveStoredLayer(const std::string& layerId, int direction, std::string& error) { std::lock_guard lock(mMutex); bool shouldMove = false; if (!mCommittedLiveState.ResolveLayerMove(layerId, direction, shouldMove, error)) return false; if (!shouldMove) return true; if (!mCommittedLiveState.MoveLayer(layerId, direction, error)) return false; mReloadRequested = true; MarkRenderStateDirtyLocked(); return SavePersistentState(error); } bool RuntimeStore::MoveStoredLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error) { std::lock_guard lock(mMutex); bool shouldMove = false; if (!mCommittedLiveState.ResolveLayerMoveToIndex(layerId, targetIndex, shouldMove, error)) return false; if (!shouldMove) return true; if (!mCommittedLiveState.MoveLayerToIndex(layerId, targetIndex, error)) return false; mReloadRequested = true; MarkRenderStateDirtyLocked(); return SavePersistentState(error); } bool RuntimeStore::SetStoredLayerBypassState(const std::string& layerId, bool bypassed, std::string& error) { std::lock_guard lock(mMutex); if (!mCommittedLiveState.SetLayerBypassState(layerId, bypassed, error)) return false; mReloadRequested = true; MarkParameterStateDirtyLocked(); return SavePersistentState(error); } bool RuntimeStore::SetStoredLayerShaderSelection(const std::string& layerId, const std::string& shaderId, std::string& error) { std::lock_guard lock(mMutex); if (!mCommittedLiveState.SetLayerShaderSelection(mShaderCatalog, layerId, shaderId, error)) return false; mReloadRequested = true; MarkRenderStateDirtyLocked(); return SavePersistentState(error); } bool RuntimeStore::SetStoredParameterValue(const std::string& layerId, const std::string& parameterId, const ShaderParameterValue& value, bool persistState, std::string& error) { std::lock_guard lock(mMutex); if (!mCommittedLiveState.SetParameterValue(layerId, parameterId, value, error)) return false; MarkParameterStateDirtyLocked(); return !persistState || SavePersistentState(error); } bool RuntimeStore::ResetStoredLayerParameterValues(const std::string& layerId, std::string& error) { std::lock_guard lock(mMutex); if (!mCommittedLiveState.ResetLayerParameterValues(mShaderCatalog, layerId, error)) return false; MarkParameterStateDirtyLocked(); return SavePersistentState(error); } bool RuntimeStore::SaveStackPresetSnapshot(const std::string& presetName, std::string& error) const { std::lock_guard lock(mMutex); const std::string safeStem = LayerStackStore::MakeSafePresetFileStem(presetName); if (safeStem.empty()) { error = "Preset name must include at least one letter or number."; return false; } return mPersistenceWriter.WriteSnapshot(BuildStackPresetPersistenceSnapshot(presetName), error); } bool RuntimeStore::LoadStackPresetSnapshot(const std::string& presetName, std::string& error) { std::lock_guard lock(mMutex); const std::string safeStem = LayerStackStore::MakeSafePresetFileStem(presetName); if (safeStem.empty()) { error = "Preset name must include at least one letter or number."; return false; } const std::filesystem::path presetPath = mConfigStore.GetPresetRoot() / (safeStem + ".json"); std::string presetText = ReadTextFile(presetPath, error); if (presetText.empty()) return false; JsonValue root; if (!ParseJson(presetText, root, error)) return false; if (!mCommittedLiveState.LoadStackPresetValue(mShaderCatalog, root, error)) return false; mReloadRequested = true; MarkRenderStateDirtyLocked(); return SavePersistentState(error); } bool RuntimeStore::HasStoredLayer(const std::string& layerId) const { std::lock_guard lock(mMutex); return mCommittedLiveState.HasLayer(layerId); } bool RuntimeStore::HasStoredShader(const std::string& shaderId) const { std::lock_guard lock(mMutex); return mShaderCatalog.HasPackage(shaderId); } bool RuntimeStore::TryGetStoredParameterById(const std::string& layerId, const std::string& parameterId, StoredParameterSnapshot& snapshot, std::string& error) const { std::lock_guard lock(mMutex); return mCommittedLiveState.TryGetParameterById(mShaderCatalog, layerId, parameterId, snapshot, error); } bool RuntimeStore::TryGetStoredParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, StoredParameterSnapshot& snapshot, std::string& error) const { std::lock_guard lock(mMutex); return mCommittedLiveState.TryGetParameterByControlKey(mShaderCatalog, layerKey, parameterKey, snapshot, error); } bool RuntimeStore::ResolveStoredLayerMove(const std::string& layerId, int direction, bool& shouldMove, std::string& error) const { std::lock_guard lock(mMutex); return mCommittedLiveState.ResolveLayerMove(layerId, direction, shouldMove, error); } bool RuntimeStore::ResolveStoredLayerMoveToIndex(const std::string& layerId, std::size_t targetIndex, bool& shouldMove, std::string& error) const { std::lock_guard lock(mMutex); return mCommittedLiveState.ResolveLayerMoveToIndex(layerId, targetIndex, shouldMove, error); } bool RuntimeStore::IsValidStackPresetName(const std::string& presetName) const { return !LayerStackStore::MakeSafePresetFileStem(presetName).empty(); } double RuntimeStore::GetRuntimeElapsedSeconds() const { return std::chrono::duration_cast>( std::chrono::steady_clock::now() - mStartTime).count(); } const std::filesystem::path& RuntimeStore::GetRuntimeRepositoryRoot() const { return mConfigStore.GetRepoRoot(); } const std::filesystem::path& RuntimeStore::GetRuntimeUiRoot() const { return mConfigStore.GetUiRoot(); } const std::filesystem::path& RuntimeStore::GetRuntimeDocsRoot() const { return mConfigStore.GetDocsRoot(); } const std::filesystem::path& RuntimeStore::GetRuntimeDataRoot() const { return mConfigStore.GetRuntimeRoot(); } unsigned short RuntimeStore::GetConfiguredControlServerPort() const { return mServerPort; } unsigned short RuntimeStore::GetConfiguredOscPort() const { return mConfigStore.GetConfig().oscPort; } const std::string& RuntimeStore::GetConfiguredOscBindAddress() const { return mConfigStore.GetConfig().oscBindAddress; } double RuntimeStore::GetConfiguredOscSmoothing() const { return mConfigStore.GetConfig().oscSmoothing; } unsigned RuntimeStore::GetConfiguredMaxTemporalHistoryFrames() const { return mConfigStore.GetConfig().maxTemporalHistoryFrames; } unsigned RuntimeStore::GetConfiguredPreviewFps() const { return mConfigStore.GetConfig().previewFps; } bool RuntimeStore::IsExternalKeyingConfigured() const { return mConfigStore.GetConfig().enableExternalKeying; } const std::string& RuntimeStore::GetConfiguredInputVideoFormat() const { return mConfigStore.GetConfig().inputVideoFormat; } const std::string& RuntimeStore::GetConfiguredInputFrameRate() const { return mConfigStore.GetConfig().inputFrameRate; } const std::string& RuntimeStore::GetConfiguredOutputVideoFormat() const { return mConfigStore.GetConfig().outputVideoFormat; } const std::string& RuntimeStore::GetConfiguredOutputFrameRate() const { return mConfigStore.GetConfig().outputFrameRate; } void RuntimeStore::SetBoundControlServerPort(unsigned short port) { std::lock_guard lock(mMutex); mServerPort = port; mConfigStore.SetBoundControlServerPort(port); } void RuntimeStore::SetCompileStatus(bool succeeded, const std::string& message) { std::lock_guard lock(mMutex); mCompileSucceeded = succeeded; mCompileMessage = message; } void RuntimeStore::ClearReloadRequest() { std::lock_guard lock(mMutex); mReloadRequested = false; } bool RuntimeStore::LoadPersistentState(std::string& error) { if (!std::filesystem::exists(mConfigStore.GetRuntimeStatePath())) return true; std::string stateText = ReadTextFile(mConfigStore.GetRuntimeStatePath(), error); if (stateText.empty()) return false; JsonValue root; if (!ParseJson(stateText, root, error)) return false; return mCommittedLiveState.LoadPersistentStateValue(root); } bool RuntimeStore::SavePersistentState(std::string& error) const { return mPersistenceWriter.EnqueueSnapshot(BuildRuntimeStatePersistenceSnapshotLocked(PersistenceRequest::RuntimeStateRequest("SavePersistentState")), error); } PersistenceSnapshot RuntimeStore::BuildStackPresetPersistenceSnapshot(const std::string& presetName) const { const std::string safeStem = LayerStackStore::MakeSafePresetFileStem(presetName); PersistenceSnapshot snapshot; snapshot.targetKind = PersistenceTargetKind::StackPreset; snapshot.targetPath = mConfigStore.GetPresetRoot() / (safeStem + ".json"); snapshot.contents = SerializeJson(mCommittedLiveState.BuildStackPresetValue(mShaderCatalog, presetName), true); snapshot.reason = "SaveStackPreset"; snapshot.debounceKey = "stack-preset:" + safeStem; snapshot.debounceAllowed = false; snapshot.flushRequested = true; snapshot.generation = 0; return snapshot; } bool RuntimeStore::ScanShaderPackages(std::string& error) { if (!mShaderCatalog.Scan(mConfigStore.GetShaderRoot(), mConfigStore.GetConfig().maxTemporalHistoryFrames, error)) return false; mCommittedLiveState.RemoveLayersWithMissingPackages(mShaderCatalog); MarkRenderStateDirtyLocked(); return true; } std::string RuntimeStore::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(); } std::vector RuntimeStore::GetStackPresetNamesLocked() const { std::vector presetNames; std::error_code fsError; if (!std::filesystem::exists(mConfigStore.GetPresetRoot(), fsError)) return presetNames; for (const auto& entry : std::filesystem::directory_iterator(mConfigStore.GetPresetRoot(), fsError)) { if (!entry.is_regular_file()) continue; if (ToLowerCopy(entry.path().extension().string()) != ".json") continue; presetNames.push_back(entry.path().stem().string()); } std::sort(presetNames.begin(), presetNames.end()); return presetNames; } bool RuntimeStore::CopyShaderPackageForStoredLayer(const std::string& layerId, ShaderPackage& shaderPackage, std::string& error) const { std::lock_guard lock(mMutex); const RuntimeStore::LayerPersistentState* layer = mCommittedLiveState.FindLayerById(layerId); if (!layer) { error = "Unknown layer id: " + layerId; return false; } if (!mShaderCatalog.CopyPackage(layer->shaderId, shaderPackage)) { error = "Unknown shader id: " + layer->shaderId; return false; } return true; } ShaderCompilerInputs RuntimeStore::GetShaderCompilerInputs() const { std::lock_guard lock(mMutex); ShaderCompilerInputs inputs; inputs.repoRoot = mConfigStore.GetRepoRoot(); inputs.wrapperPath = mConfigStore.GetWrapperPath(); inputs.generatedGlslPath = mConfigStore.GetGeneratedGlslPath(); inputs.patchedGlslPath = mConfigStore.GetPatchedGlslPath(); inputs.maxTemporalHistoryFrames = mConfigStore.GetConfig().maxTemporalHistoryFrames; return inputs; } CommittedLiveStateReadModel RuntimeStore::BuildCommittedLiveStateReadModel() const { std::lock_guard lock(mMutex); return mCommittedLiveState.BuildReadModel(mShaderCatalog); } RenderSnapshotReadModel RuntimeStore::BuildRenderSnapshotReadModel() const { RenderSnapshotReadModel model; model.signalStatus = mHealthTelemetry.GetSignalStatusSnapshot(); model.committedLiveState = BuildCommittedLiveStateReadModel(); std::lock_guard lock(mMutex); model.timing.startTime = mStartTime; model.timing.startupRandom = mStartupRandom; return model; } std::vector RuntimeStore::CopyCommittedLiveLayerStates() const { std::lock_guard lock(mMutex); return mCommittedLiveState.CopyLayerStates(); } std::vector RuntimeStore::CopyLayerStates() const { return CopyCommittedLiveLayerStates(); } RenderTimingSnapshot RuntimeStore::GetRenderTimingSnapshot() const { std::lock_guard lock(mMutex); RenderTimingSnapshot snapshot; snapshot.startTime = mStartTime; snapshot.startupRandom = mStartupRandom; return snapshot; } RuntimeStatePresentationReadModel RuntimeStore::BuildRuntimeStatePresentationReadModel() const { RuntimeStatePresentationReadModel model; model.telemetry = mHealthTelemetry.GetSnapshot(); std::lock_guard lock(mMutex); model.config = mConfigStore.GetConfig(); model.layerStack = mCommittedLiveState.LayerStack(); model.shaderCatalog = mShaderCatalog.CaptureSnapshot(); model.packageStatuses = mShaderCatalog.PackageStatuses(); model.stackPresetNames = GetStackPresetNamesLocked(); model.serverPort = mServerPort; model.autoReloadEnabled = mAutoReloadEnabled; model.compileSucceeded = mCompileSucceeded; model.compileMessage = mCompileMessage; return model; } void RuntimeStore::MarkRenderStateDirtyLocked() { mRenderSnapshotBuilder.MarkRenderStateDirty(); } void RuntimeStore::MarkParameterStateDirtyLocked() { mRenderSnapshotBuilder.MarkParameterStateDirty(); }