From b2369c418b658dcbf8d423a5cdcd344ea96bdafb Mon Sep 17 00:00:00 2001 From: Aiden <68633820+awils27@users.noreply.github.com> Date: Mon, 11 May 2026 01:29:44 +1000 Subject: [PATCH] pass 2 --- CMakeLists.txt | 4 +- .../gl/OpenGLComposite.cpp | 117 +- .../gl/OpenGLComposite.h | 12 +- .../gl/OpenGLCompositeRuntimeControls.cpp | 37 +- .../gl/RenderEngine.cpp | 4 +- .../gl/RuntimeUpdateController.cpp | 103 ++ .../gl/RuntimeUpdateController.h | 36 + .../gl/shader/OpenGLShaderPrograms.cpp | 2 +- .../gl/shader/ShaderBuildQueue.cpp | 2 +- .../runtime/RuntimeCoordinator.cpp | 111 +- .../runtime/RuntimeCoordinator.h | 7 +- .../runtime/RuntimeSnapshotProvider.cpp | 44 +- .../runtime/RuntimeSnapshotProvider.h | 16 +- .../runtime/RuntimeStore.cpp | 1019 +++++++++++++---- .../runtime/RuntimeStore.h | 102 +- docs/PHASE_1_SUBSYSTEM_BOUNDARIES_DESIGN.md | 7 +- docs/subsystems/RuntimeSnapshotProvider.md | 2 +- docs/subsystems/RuntimeStore.md | 2 +- 18 files changed, 1143 insertions(+), 484 deletions(-) create mode 100644 apps/LoopThroughWithOpenGLCompositing/gl/RuntimeUpdateController.cpp create mode 100644 apps/LoopThroughWithOpenGLCompositing/gl/RuntimeUpdateController.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e17a13..12ebbb7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,6 +64,8 @@ set(APP_SOURCES "${APP_DIR}/gl/OpenGLCompositeRuntimeControls.cpp" "${APP_DIR}/gl/RenderEngine.cpp" "${APP_DIR}/gl/RenderEngine.h" + "${APP_DIR}/gl/RuntimeUpdateController.cpp" + "${APP_DIR}/gl/RuntimeUpdateController.h" "${APP_DIR}/gl/pipeline/OpenGLRenderPass.cpp" "${APP_DIR}/gl/pipeline/OpenGLRenderPass.h" "${APP_DIR}/gl/pipeline/OpenGLRenderPipeline.cpp" @@ -100,8 +102,6 @@ set(APP_SOURCES "${APP_DIR}/platform/NativeHandles.h" "${APP_DIR}/platform/NativeSockets.h" "${APP_DIR}/resource.h" - "${APP_DIR}/runtime/RuntimeHost.cpp" - "${APP_DIR}/runtime/RuntimeHost.h" "${APP_DIR}/runtime/HealthTelemetry.cpp" "${APP_DIR}/runtime/HealthTelemetry.h" "${APP_DIR}/runtime/RuntimeCoordinator.cpp" diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp index d9612b1..a9ce8da 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp @@ -4,8 +4,12 @@ #include "GlRenderConstants.h" #include "PngScreenshotWriter.h" #include "RenderEngine.h" +#include "RuntimeCoordinator.h" #include "RuntimeParameterUtils.h" #include "RuntimeServices.h" +#include "RuntimeSnapshotProvider.h" +#include "RuntimeStore.h" +#include "RuntimeUpdateController.h" #include "ShaderBuildQueue.h" #include "VideoBackend.h" @@ -46,6 +50,13 @@ OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) : mVideoBackend = std::make_unique(*mRenderEngine, mRuntimeStore->GetHealthTelemetry()); mShaderBuildQueue = std::make_unique(*mRuntimeSnapshotProvider); mRuntimeServices = std::make_unique(); + mRuntimeUpdateController = std::make_unique( + *mRuntimeStore, + *mRuntimeCoordinator, + *mRuntimeServices, + *mRenderEngine, + *mShaderBuildQueue, + *mVideoBackend); } OpenGLComposite::~OpenGLComposite() @@ -245,7 +256,7 @@ bool OpenGLComposite::InitOpenGLState() mRenderEngine->ResetTemporalHistoryState(); mRenderEngine->ResetShaderFeedbackState(); - broadcastRuntimeState(); + mRuntimeUpdateController->BroadcastRuntimeState(); mRuntimeServices->BeginPolling(*mRuntimeCoordinator); return true; } @@ -273,7 +284,8 @@ bool OpenGLComposite::Stop() bool OpenGLComposite::ReloadShader(bool preserveFeedbackState) { return mRuntimeCoordinator && - ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->RequestShaderReload(preserveFeedbackState)); + mRuntimeUpdateController && + mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->RequestShaderReload(preserveFeedbackState)); } bool OpenGLComposite::RequestScreenshot(std::string& error) @@ -285,7 +297,8 @@ bool OpenGLComposite::RequestScreenshot(std::string& error) void OpenGLComposite::renderEffect() { - ProcessRuntimePollResults(); + if (mRuntimeUpdateController) + mRuntimeUpdateController->ProcessRuntimeWork(); std::vector appliedOscUpdates; std::vector completedOscCommits; if (mRuntimeServices) @@ -400,105 +413,7 @@ std::filesystem::path OpenGLComposite::BuildScreenshotPath() const return root / "screenshots" / filename.str(); } -bool OpenGLComposite::ProcessRuntimePollResults() -{ - if (!mRuntimeServices) - return true; - - bool shaderBuildRequested = false; - std::vector serviceResults; - mRuntimeServices->ConsumeRuntimeCoordinatorResults(serviceResults); - for (const RuntimeCoordinatorServiceResult& serviceResult : serviceResults) - { - shaderBuildRequested = shaderBuildRequested || serviceResult.result.shaderBuildRequested; - ApplyRuntimeCoordinatorResult(serviceResult.result); - if (serviceResult.failed) - return false; - } - - if (!shaderBuildRequested) - { - if (!mShaderBuildQueue || !mRenderEngine) - return true; - - const RenderEngine::PreparedShaderBuildApplyResult buildResult = mRenderEngine->TryApplyReadyShaderBuild( - *mShaderBuildQueue, - mVideoBackend->InputFrameWidth(), - mVideoBackend->InputFrameHeight(), - mRuntimeCoordinator && mRuntimeCoordinator->PreserveFeedbackOnNextShaderBuild()); - if (!buildResult.hadReadyBuild) - return true; - - if (!buildResult.applied) - { - ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->HandlePreparedShaderBuildFailure(buildResult.errorMessage)); - return false; - } - - ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->HandlePreparedShaderBuildSuccess()); - return true; - } - - return true; -} - -void OpenGLComposite::RequestShaderBuild() -{ - if (!mShaderBuildQueue || !mVideoBackend) - return; - - mShaderBuildQueue->RequestBuild(mVideoBackend->InputFrameWidth(), mVideoBackend->InputFrameHeight()); -} - -bool OpenGLComposite::ApplyRuntimeCoordinatorResult(const RuntimeCoordinatorResult& result, std::string* error) -{ - if (!result.accepted) - { - if (error) - *error = result.errorMessage; - return false; - } - - if (result.compileStatusChanged && mRuntimeStore) - mRuntimeStore->SetCompileStatus(result.compileStatusSucceeded, result.compileStatusMessage); - - if (result.clearReloadRequest && mRuntimeStore) - mRuntimeStore->ClearReloadRequest(); - - if (mRuntimeCoordinator) - mRuntimeCoordinator->ApplyCommittedStateMode(result.committedStateMode); - - if (result.clearTransientOscState) - { - if (mRenderEngine) - mRenderEngine->ClearOscOverlayState(); - if (mRuntimeServices) - mRuntimeServices->ClearOscState(); - } - - if (mRenderEngine) - mRenderEngine->ApplyRuntimeCoordinatorRenderReset(result.renderResetScope); - - if (result.shaderBuildRequested) - RequestShaderBuild(); - - if (result.runtimeStateBroadcastRequired) - broadcastRuntimeState(); - - return true; -} - -void OpenGLComposite::broadcastRuntimeState() -{ - if (mRuntimeServices) - mRuntimeServices->BroadcastState(); -} - bool OpenGLComposite::CheckOpenGLExtensions() { return true; } - -//////////////////////////////////////////// - - diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h index e083d35..6611bb2 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h +++ b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h @@ -12,9 +12,6 @@ #include #include "GLExtensions.h" -#include "RuntimeCoordinator.h" -#include "RuntimeSnapshotProvider.h" -#include "RuntimeStore.h" #include #include @@ -24,7 +21,11 @@ #include class RenderEngine; +class RuntimeCoordinator; +class RuntimeSnapshotProvider; class RuntimeServices; +class RuntimeStore; +class RuntimeUpdateController; class ShaderBuildQueue; class VideoBackend; @@ -78,17 +79,14 @@ private: std::unique_ptr mRenderEngine; std::unique_ptr mShaderBuildQueue; std::unique_ptr mRuntimeServices; + std::unique_ptr mRuntimeUpdateController; std::unique_ptr mVideoBackend; std::atomic mScreenshotRequested; bool InitOpenGLState(); void renderEffect(); - bool ProcessRuntimePollResults(); - void RequestShaderBuild(); - bool ApplyRuntimeCoordinatorResult(const RuntimeCoordinatorResult& result, std::string* error = nullptr); void ProcessScreenshotRequest(); std::filesystem::path BuildScreenshotPath() const; - void broadcastRuntimeState(); }; #endif // __OPENGL_COMPOSITE_H__ diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLCompositeRuntimeControls.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLCompositeRuntimeControls.cpp index 6a1b804..df49ff5 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLCompositeRuntimeControls.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLCompositeRuntimeControls.cpp @@ -1,5 +1,9 @@ #include "OpenGLComposite.h" +#include "RuntimeCoordinator.h" +#include "RuntimeJson.h" #include "RuntimeServices.h" +#include "RuntimeStore.h" +#include "RuntimeUpdateController.h" std::string OpenGLComposite::GetRuntimeStateJson() const { @@ -39,37 +43,43 @@ std::string OpenGLComposite::GetOscAddress() const bool OpenGLComposite::AddLayer(const std::string& shaderId, std::string& error) { return mRuntimeCoordinator && - ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->AddLayer(shaderId), &error); + mRuntimeUpdateController && + mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->AddLayer(shaderId), &error); } bool OpenGLComposite::RemoveLayer(const std::string& layerId, std::string& error) { return mRuntimeCoordinator && - ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->RemoveLayer(layerId), &error); + mRuntimeUpdateController && + mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->RemoveLayer(layerId), &error); } bool OpenGLComposite::MoveLayer(const std::string& layerId, int direction, std::string& error) { return mRuntimeCoordinator && - ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->MoveLayer(layerId, direction), &error); + mRuntimeUpdateController && + mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->MoveLayer(layerId, direction), &error); } bool OpenGLComposite::MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error) { return mRuntimeCoordinator && - ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->MoveLayerToIndex(layerId, targetIndex), &error); + mRuntimeUpdateController && + mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->MoveLayerToIndex(layerId, targetIndex), &error); } bool OpenGLComposite::SetLayerBypass(const std::string& layerId, bool bypassed, std::string& error) { return mRuntimeCoordinator && - ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->SetLayerBypass(layerId, bypassed), &error); + mRuntimeUpdateController && + mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->SetLayerBypass(layerId, bypassed), &error); } bool OpenGLComposite::SetLayerShader(const std::string& layerId, const std::string& shaderId, std::string& error) { return mRuntimeCoordinator && - ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->SetLayerShader(layerId, shaderId), &error); + mRuntimeUpdateController && + mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->SetLayerShader(layerId, shaderId), &error); } bool OpenGLComposite::UpdateLayerParameterJson(const std::string& layerId, const std::string& parameterId, const std::string& valueJson, std::string& error) @@ -79,7 +89,8 @@ bool OpenGLComposite::UpdateLayerParameterJson(const std::string& layerId, const return false; return mRuntimeCoordinator && - ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->UpdateLayerParameter(layerId, parameterId, parsedValue), &error); + mRuntimeUpdateController && + mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->UpdateLayerParameter(layerId, parameterId, parsedValue), &error); } bool OpenGLComposite::UpdateLayerParameterByControlKeyJson(const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& error) @@ -89,23 +100,27 @@ bool OpenGLComposite::UpdateLayerParameterByControlKeyJson(const std::string& la return false; return mRuntimeCoordinator && - ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->UpdateLayerParameterByControlKey(layerKey, parameterKey, parsedValue), &error); + mRuntimeUpdateController && + mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->UpdateLayerParameterByControlKey(layerKey, parameterKey, parsedValue), &error); } bool OpenGLComposite::ResetLayerParameters(const std::string& layerId, std::string& error) { return mRuntimeCoordinator && - ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->ResetLayerParameters(layerId), &error); + mRuntimeUpdateController && + mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->ResetLayerParameters(layerId), &error); } bool OpenGLComposite::SaveStackPreset(const std::string& presetName, std::string& error) { return mRuntimeCoordinator && - ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->SaveStackPreset(presetName), &error); + mRuntimeUpdateController && + mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->SaveStackPreset(presetName), &error); } bool OpenGLComposite::LoadStackPreset(const std::string& presetName, std::string& error) { return mRuntimeCoordinator && - ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->LoadStackPreset(presetName), &error); + mRuntimeUpdateController && + mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->LoadStackPreset(presetName), &error); } diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.cpp index 5e265e2..a2810c8 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.cpp @@ -379,7 +379,7 @@ bool RenderEngine::ResolveRenderLayerStates( ApplyOscOverlays(renderSnapshot.states, true, oscSmoothing, commitRequests); if (mCachedParameterStateVersion != versions.parameterStateVersion && - mRuntimeSnapshotProvider.TryRefreshSnapshotParameters(renderSnapshot)) + mRuntimeSnapshotProvider.TryRefreshPublishedSnapshotParameters(renderSnapshot)) { mCachedParameterStateVersion = renderSnapshot.versions.parameterStateVersion; ApplyOscOverlays(renderSnapshot.states, true, oscSmoothing, commitRequests); @@ -392,7 +392,7 @@ bool RenderEngine::ResolveRenderLayerStates( } RuntimeRenderStateSnapshot renderSnapshot; - if (mRuntimeSnapshotProvider.TryGetRenderStateSnapshot(renderWidth, renderHeight, renderSnapshot)) + if (mRuntimeSnapshotProvider.TryPublishRenderStateSnapshot(renderWidth, renderHeight, renderSnapshot)) { mCachedLayerRenderStates = renderSnapshot.states; mCachedRenderStateVersion = renderSnapshot.versions.renderStateVersion; diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/RuntimeUpdateController.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/RuntimeUpdateController.cpp new file mode 100644 index 0000000..c178d2d --- /dev/null +++ b/apps/LoopThroughWithOpenGLCompositing/gl/RuntimeUpdateController.cpp @@ -0,0 +1,103 @@ +#include "RuntimeUpdateController.h" + +#include "RenderEngine.h" +#include "RuntimeServices.h" +#include "RuntimeStore.h" +#include "ShaderBuildQueue.h" +#include "VideoBackend.h" + +#include + +RuntimeUpdateController::RuntimeUpdateController( + RuntimeStore& runtimeStore, + RuntimeCoordinator& runtimeCoordinator, + RuntimeServices& runtimeServices, + RenderEngine& renderEngine, + ShaderBuildQueue& shaderBuildQueue, + VideoBackend& videoBackend) : + mRuntimeStore(runtimeStore), + mRuntimeCoordinator(runtimeCoordinator), + mRuntimeServices(runtimeServices), + mRenderEngine(renderEngine), + mShaderBuildQueue(shaderBuildQueue), + mVideoBackend(videoBackend) +{ +} + +bool RuntimeUpdateController::ApplyRuntimeCoordinatorResult(const RuntimeCoordinatorResult& result, std::string* error) +{ + if (!result.accepted) + { + if (error) + *error = result.errorMessage; + return false; + } + + if (result.compileStatusChanged) + mRuntimeStore.SetCompileStatus(result.compileStatusSucceeded, result.compileStatusMessage); + + if (result.clearReloadRequest) + mRuntimeStore.ClearReloadRequest(); + + mRuntimeCoordinator.ApplyCommittedStateMode(result.committedStateMode); + + if (result.clearTransientOscState) + { + mRenderEngine.ClearOscOverlayState(); + mRuntimeServices.ClearOscState(); + } + + mRenderEngine.ApplyRuntimeCoordinatorRenderReset(result.renderResetScope); + + if (result.shaderBuildRequested) + RequestShaderBuild(); + + if (result.runtimeStateBroadcastRequired) + BroadcastRuntimeState(); + + return true; +} + +bool RuntimeUpdateController::ProcessRuntimeWork() +{ + bool shaderBuildRequested = false; + std::vector serviceResults; + mRuntimeServices.ConsumeRuntimeCoordinatorResults(serviceResults); + for (const RuntimeCoordinatorServiceResult& serviceResult : serviceResults) + { + shaderBuildRequested = shaderBuildRequested || serviceResult.result.shaderBuildRequested; + ApplyRuntimeCoordinatorResult(serviceResult.result); + if (serviceResult.failed) + return false; + } + + if (shaderBuildRequested) + return true; + + const RenderEngine::PreparedShaderBuildApplyResult buildResult = mRenderEngine.TryApplyReadyShaderBuild( + mShaderBuildQueue, + mVideoBackend.InputFrameWidth(), + mVideoBackend.InputFrameHeight(), + mRuntimeCoordinator.PreserveFeedbackOnNextShaderBuild()); + if (!buildResult.hadReadyBuild) + return true; + + if (!buildResult.applied) + { + ApplyRuntimeCoordinatorResult(mRuntimeCoordinator.HandlePreparedShaderBuildFailure(buildResult.errorMessage)); + return false; + } + + ApplyRuntimeCoordinatorResult(mRuntimeCoordinator.HandlePreparedShaderBuildSuccess()); + return true; +} + +void RuntimeUpdateController::RequestShaderBuild() +{ + mShaderBuildQueue.RequestBuild(mVideoBackend.InputFrameWidth(), mVideoBackend.InputFrameHeight()); +} + +void RuntimeUpdateController::BroadcastRuntimeState() +{ + mRuntimeServices.BroadcastState(); +} diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/RuntimeUpdateController.h b/apps/LoopThroughWithOpenGLCompositing/gl/RuntimeUpdateController.h new file mode 100644 index 0000000..e0f1dcf --- /dev/null +++ b/apps/LoopThroughWithOpenGLCompositing/gl/RuntimeUpdateController.h @@ -0,0 +1,36 @@ +#pragma once + +#include "RuntimeCoordinator.h" + +#include + +class RenderEngine; +class RuntimeServices; +class RuntimeStore; +class ShaderBuildQueue; +class VideoBackend; + +class RuntimeUpdateController +{ +public: + RuntimeUpdateController( + RuntimeStore& runtimeStore, + RuntimeCoordinator& runtimeCoordinator, + RuntimeServices& runtimeServices, + RenderEngine& renderEngine, + ShaderBuildQueue& shaderBuildQueue, + VideoBackend& videoBackend); + + bool ApplyRuntimeCoordinatorResult(const RuntimeCoordinatorResult& result, std::string* error = nullptr); + bool ProcessRuntimeWork(); + void RequestShaderBuild(); + void BroadcastRuntimeState(); + +private: + RuntimeStore& mRuntimeStore; + RuntimeCoordinator& mRuntimeCoordinator; + RuntimeServices& mRuntimeServices; + RenderEngine& mRenderEngine; + ShaderBuildQueue& mShaderBuildQueue; + VideoBackend& mVideoBackend; +}; diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/shader/OpenGLShaderPrograms.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/shader/OpenGLShaderPrograms.cpp index 31525e4..3e67958 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/shader/OpenGLShaderPrograms.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/shader/OpenGLShaderPrograms.cpp @@ -40,7 +40,7 @@ OpenGLShaderPrograms::OpenGLShaderPrograms(OpenGLRenderer& renderer, RuntimeSnap bool OpenGLShaderPrograms::CompileLayerPrograms(unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage) { const RuntimeRenderStateSnapshot renderSnapshot = - mRuntimeSnapshotProvider.GetRenderStateSnapshot(inputFrameWidth, inputFrameHeight); + mRuntimeSnapshotProvider.PublishRenderStateSnapshot(inputFrameWidth, inputFrameHeight); const std::vector& layerStates = renderSnapshot.states; std::string temporalError; const unsigned historyCap = mRuntimeSnapshotProvider.GetMaxTemporalHistoryFrames(); diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/shader/ShaderBuildQueue.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/shader/ShaderBuildQueue.cpp index ee9fb45..9c88b35 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/shader/ShaderBuildQueue.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/shader/ShaderBuildQueue.cpp @@ -111,7 +111,7 @@ PreparedShaderBuild ShaderBuildQueue::Build(uint64_t generation, unsigned output { PreparedShaderBuild build; build.generation = generation; - build.renderSnapshot = mRuntimeSnapshotProvider.GetRenderStateSnapshot(outputWidth, outputHeight); + build.renderSnapshot = mRuntimeSnapshotProvider.PublishRenderStateSnapshot(outputWidth, outputHeight); build.layers.reserve(build.renderSnapshot.states.size()); for (const RuntimeRenderState& state : build.renderSnapshot.states) diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeCoordinator.cpp b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeCoordinator.cpp index 9f904e2..c09687a 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeCoordinator.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeCoordinator.cpp @@ -1,10 +1,8 @@ #include "RuntimeCoordinator.h" +#include "RuntimeParameterUtils.h" #include "RuntimeStore.h" -#include -#include - RuntimeCoordinator::RuntimeCoordinator(RuntimeStore& runtimeStore) : mRuntimeStore(runtimeStore) { @@ -252,84 +250,50 @@ bool RuntimeCoordinator::PreserveFeedbackOnNextShaderBuild() const bool RuntimeCoordinator::BuildParameterMutationById(const std::string& layerId, const std::string& parameterId, const JsonValue& newValue, bool persistState, ResolvedParameterMutation& mutation, std::string& error) const { - std::lock_guard lock(mRuntimeStore.mRuntimeHost.mMutex); - - RuntimeHost::LayerPersistentState* layer = mRuntimeStore.mRuntimeHost.FindLayerById(layerId); - if (!layer) - { - error = "Unknown layer id: " + layerId; + RuntimeStore::StoredParameterSnapshot snapshot; + if (!mRuntimeStore.TryGetStoredParameterById(layerId, parameterId, snapshot, error)) return false; - } - auto shaderIt = mRuntimeStore.mRuntimeHost.mPackagesById.find(layer->shaderId); - if (shaderIt == mRuntimeStore.mRuntimeHost.mPackagesById.end()) - { - error = "Unknown shader id: " + layer->shaderId; - return false; - } - - auto parameterIt = std::find_if(shaderIt->second.parameters.begin(), shaderIt->second.parameters.end(), - [¶meterId](const ShaderParameterDefinition& definition) { return definition.id == parameterId; }); - if (parameterIt == shaderIt->second.parameters.end()) - { - error = "Unknown parameter id: " + parameterId; - return false; - } - - return BuildParameterMutationLocked(*layer, *parameterIt, newValue, persistState, mutation, error); + return BuildParameterMutationFromSnapshot(snapshot.layerId, snapshot.definition, snapshot.currentValue, snapshot.hasCurrentValue, + newValue, persistState, mutation, error); } bool RuntimeCoordinator::BuildParameterMutationByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue, bool persistState, ResolvedParameterMutation& mutation, std::string& error) const { - std::lock_guard lock(mRuntimeStore.mRuntimeHost.mMutex); - - RuntimeHost::LayerPersistentState* matchedLayer = nullptr; - const ShaderPackage* matchedPackage = nullptr; - std::vector::const_iterator parameterIt; - if (!mRuntimeStore.TryResolveStoredLayerAndParameterByControlKeyLocked(layerKey, parameterKey, matchedLayer, matchedPackage, parameterIt, error)) + RuntimeStore::StoredParameterSnapshot snapshot; + if (!mRuntimeStore.TryGetStoredParameterByControlKey(layerKey, parameterKey, snapshot, error)) return false; - return BuildParameterMutationLocked(*matchedLayer, *parameterIt, newValue, persistState, mutation, error); + return BuildParameterMutationFromSnapshot(snapshot.layerId, snapshot.definition, snapshot.currentValue, snapshot.hasCurrentValue, + newValue, persistState, mutation, error); } -bool RuntimeCoordinator::BuildParameterMutationLocked(RuntimeHost::LayerPersistentState& layer, const ShaderParameterDefinition& definition, const JsonValue& newValue, +bool RuntimeCoordinator::BuildParameterMutationFromSnapshot(const std::string& layerId, const ShaderParameterDefinition& definition, + const ShaderParameterValue& currentValue, bool hasCurrentValue, const JsonValue& newValue, bool persistState, ResolvedParameterMutation& mutation, std::string& error) const { - mutation.layerId = layer.id; + mutation.layerId = layerId; mutation.parameterId = definition.id; mutation.persistState = persistState; if (definition.type == ShaderParameterType::Trigger) { - const auto existingValue = layer.parameterValues.find(definition.id); - const double previousCount = existingValue == layer.parameterValues.end() || existingValue->second.numberValues.empty() + const double previousCount = !hasCurrentValue || currentValue.numberValues.empty() ? 0.0 - : existingValue->second.numberValues[0]; - const double triggerTime = std::chrono::duration_cast>( - std::chrono::steady_clock::now() - mRuntimeStore.mRuntimeHost.mStartTime).count(); + : currentValue.numberValues[0]; + const double triggerTime = mRuntimeStore.GetRuntimeElapsedSeconds(); mutation.value.numberValues = { previousCount + 1.0, triggerTime }; mutation.persistState = false; return true; } - return mRuntimeStore.mRuntimeHost.NormalizeAndValidateValue(definition, newValue, mutation.value, error); -} - -bool RuntimeCoordinator::HasLayerLocked(const std::string& layerId) const -{ - return mRuntimeStore.mRuntimeHost.FindLayerById(layerId) != nullptr; -} - -bool RuntimeCoordinator::HasShaderLocked(const std::string& shaderId) const -{ - return mRuntimeStore.mRuntimeHost.mPackagesById.find(shaderId) != mRuntimeStore.mRuntimeHost.mPackagesById.end(); + return NormalizeAndValidateParameterValue(definition, newValue, mutation.value, error); } bool RuntimeCoordinator::ValidateLayerExists(const std::string& layerId, std::string& error) const { - std::lock_guard lock(mRuntimeStore.mRuntimeHost.mMutex); - if (HasLayerLocked(layerId)) + if (mRuntimeStore.HasStoredLayer(layerId)) return true; error = "Unknown layer id: " + layerId; @@ -338,8 +302,7 @@ bool RuntimeCoordinator::ValidateLayerExists(const std::string& layerId, std::st bool RuntimeCoordinator::ValidateShaderExists(const std::string& shaderId, std::string& error) const { - std::lock_guard lock(mRuntimeStore.mRuntimeHost.mMutex); - if (HasShaderLocked(shaderId)) + if (mRuntimeStore.HasStoredShader(shaderId)) return true; error = "Unknown shader id: " + shaderId; @@ -348,49 +311,17 @@ bool RuntimeCoordinator::ValidateShaderExists(const std::string& shaderId, std:: bool RuntimeCoordinator::ResolveLayerMove(const std::string& layerId, int direction, bool& shouldMove, std::string& error) const { - std::lock_guard lock(mRuntimeStore.mRuntimeHost.mMutex); - const auto& layers = mRuntimeStore.mRuntimeHost.mPersistentState.layers; - auto it = std::find_if(layers.begin(), layers.end(), - [&layerId](const RuntimeHost::LayerPersistentState& layer) { return layer.id == layerId; }); - if (it == layers.end()) - { - error = "Unknown layer id: " + layerId; - return false; - } - - const std::ptrdiff_t index = std::distance(layers.begin(), it); - const std::ptrdiff_t newIndex = index + direction; - shouldMove = newIndex >= 0 && newIndex < static_cast(layers.size()) && newIndex != index; - return true; + return mRuntimeStore.ResolveStoredLayerMove(layerId, direction, shouldMove, error); } bool RuntimeCoordinator::ResolveLayerMoveToIndex(const std::string& layerId, std::size_t targetIndex, bool& shouldMove, std::string& error) const { - std::lock_guard lock(mRuntimeStore.mRuntimeHost.mMutex); - const auto& layers = mRuntimeStore.mRuntimeHost.mPersistentState.layers; - auto it = std::find_if(layers.begin(), layers.end(), - [&layerId](const RuntimeHost::LayerPersistentState& layer) { return layer.id == layerId; }); - if (it == layers.end()) - { - error = "Unknown layer id: " + layerId; - return false; - } - - if (layers.empty()) - { - shouldMove = false; - return true; - } - - const std::size_t clampedTargetIndex = (std::min)(targetIndex, layers.size() - 1); - const std::size_t sourceIndex = static_cast(std::distance(layers.begin(), it)); - shouldMove = sourceIndex != clampedTargetIndex; - return true; + return mRuntimeStore.ResolveStoredLayerMoveToIndex(layerId, targetIndex, shouldMove, error); } bool RuntimeCoordinator::ValidatePresetName(const std::string& presetName, std::string& error) const { - if (!mRuntimeStore.MakeSafePresetFileStem(presetName).empty()) + if (mRuntimeStore.IsValidStackPresetName(presetName)) return true; error = "Preset name must include at least one letter or number."; diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeCoordinator.h b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeCoordinator.h index e6ad30e..fb01096 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeCoordinator.h +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeCoordinator.h @@ -1,7 +1,7 @@ #pragma once -#include "RuntimeHost.h" #include "RuntimeJson.h" +#include "ShaderTypes.h" #include #include @@ -80,10 +80,9 @@ private: bool persistState, ResolvedParameterMutation& mutation, std::string& error) const; bool BuildParameterMutationByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue, bool persistState, ResolvedParameterMutation& mutation, std::string& error) const; - bool BuildParameterMutationLocked(RuntimeHost::LayerPersistentState& layer, const ShaderParameterDefinition& definition, const JsonValue& newValue, + bool BuildParameterMutationFromSnapshot(const std::string& layerId, const ShaderParameterDefinition& definition, + const ShaderParameterValue& currentValue, bool hasCurrentValue, const JsonValue& newValue, bool persistState, ResolvedParameterMutation& mutation, std::string& error) const; - bool HasLayerLocked(const std::string& layerId) const; - bool HasShaderLocked(const std::string& shaderId) const; bool ValidateLayerExists(const std::string& layerId, std::string& error) const; bool ValidateShaderExists(const std::string& shaderId, std::string& error) const; bool ResolveLayerMove(const std::string& layerId, int direction, bool& shouldMove, std::string& error) const; diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeSnapshotProvider.cpp b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeSnapshotProvider.cpp index 72d7695..710157c 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeSnapshotProvider.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeSnapshotProvider.cpp @@ -75,11 +75,14 @@ void RuntimeSnapshotProvider::AdvanceFrame() mRuntimeStore.AdvanceFrameCounter(); } -RuntimeRenderStateSnapshot RuntimeSnapshotProvider::GetRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight) const +RuntimeRenderStateSnapshot RuntimeSnapshotProvider::PublishRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight) const { for (;;) { const RuntimeSnapshotVersions versionsBefore = GetVersions(); + RuntimeRenderStateSnapshot publishedSnapshot; + if (TryGetPublishedRenderStateSnapshot(outputWidth, outputHeight, versionsBefore, publishedSnapshot)) + return publishedSnapshot; RuntimeRenderStateSnapshot snapshot; snapshot.outputWidth = outputWidth; @@ -91,14 +94,17 @@ RuntimeRenderStateSnapshot RuntimeSnapshotProvider::GetRenderStateSnapshot(unsig versionsBefore.parameterStateVersion == versionsAfter.parameterStateVersion) { snapshot.versions = versionsAfter; + StorePublishedRenderStateSnapshot(snapshot); return snapshot; } } } -bool RuntimeSnapshotProvider::TryGetRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight, RuntimeRenderStateSnapshot& snapshot) const +bool RuntimeSnapshotProvider::TryPublishRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight, RuntimeRenderStateSnapshot& snapshot) const { const RuntimeSnapshotVersions versionsBefore = GetVersions(); + if (TryGetPublishedRenderStateSnapshot(outputWidth, outputHeight, versionsBefore, snapshot)) + return true; std::vector states; if (!mRuntimeStore.TryBuildLayerRenderStates(outputWidth, outputHeight, states)) @@ -115,10 +121,11 @@ bool RuntimeSnapshotProvider::TryGetRenderStateSnapshot(unsigned outputWidth, un snapshot.outputHeight = outputHeight; snapshot.versions = versionsAfter; snapshot.states = std::move(states); + StorePublishedRenderStateSnapshot(snapshot); return true; } -bool RuntimeSnapshotProvider::TryRefreshSnapshotParameters(RuntimeRenderStateSnapshot& snapshot) const +bool RuntimeSnapshotProvider::TryRefreshPublishedSnapshotParameters(RuntimeRenderStateSnapshot& snapshot) const { const uint64_t expectedRenderStateVersion = snapshot.versions.renderStateVersion; if (!mRuntimeStore.TryRefreshLayerParameters(snapshot.states)) @@ -129,6 +136,7 @@ bool RuntimeSnapshotProvider::TryRefreshSnapshotParameters(RuntimeRenderStateSna return false; snapshot.versions = versions; + StorePublishedRenderStateSnapshot(snapshot); return true; } @@ -136,3 +144,33 @@ void RuntimeSnapshotProvider::RefreshDynamicRenderStateFields(std::vector lock(mPublishedSnapshotMutex); + if (!mHasPublishedRenderStateSnapshot || + !SnapshotMatches(mPublishedRenderStateSnapshot, outputWidth, outputHeight, versions)) + { + return false; + } + + snapshot = mPublishedRenderStateSnapshot; + return true; +} + +void RuntimeSnapshotProvider::StorePublishedRenderStateSnapshot(const RuntimeRenderStateSnapshot& snapshot) const +{ + std::lock_guard lock(mPublishedSnapshotMutex); + mPublishedRenderStateSnapshot = snapshot; + mHasPublishedRenderStateSnapshot = true; +} + +bool RuntimeSnapshotProvider::SnapshotMatches(const RuntimeRenderStateSnapshot& snapshot, unsigned outputWidth, unsigned outputHeight, + const RuntimeSnapshotVersions& versions) +{ + return snapshot.outputWidth == outputWidth && + snapshot.outputHeight == outputHeight && + snapshot.versions.renderStateVersion == versions.renderStateVersion && + snapshot.versions.parameterStateVersion == versions.parameterStateVersion; +} diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeSnapshotProvider.h b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeSnapshotProvider.h index cb48ab2..4d6a622 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeSnapshotProvider.h +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeSnapshotProvider.h @@ -3,6 +3,7 @@ #include "RuntimeStore.h" #include +#include #include #include @@ -29,11 +30,20 @@ public: unsigned GetMaxTemporalHistoryFrames() const; RuntimeSnapshotVersions GetVersions() const; void AdvanceFrame(); - RuntimeRenderStateSnapshot GetRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight) const; - bool TryGetRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight, RuntimeRenderStateSnapshot& snapshot) const; - bool TryRefreshSnapshotParameters(RuntimeRenderStateSnapshot& snapshot) const; + RuntimeRenderStateSnapshot PublishRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight) const; + bool TryPublishRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight, RuntimeRenderStateSnapshot& snapshot) const; + bool TryRefreshPublishedSnapshotParameters(RuntimeRenderStateSnapshot& snapshot) const; void RefreshDynamicRenderStateFields(std::vector& states) const; private: + bool TryGetPublishedRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight, + const RuntimeSnapshotVersions& versions, RuntimeRenderStateSnapshot& snapshot) const; + void StorePublishedRenderStateSnapshot(const RuntimeRenderStateSnapshot& snapshot) const; + static bool SnapshotMatches(const RuntimeRenderStateSnapshot& snapshot, unsigned outputWidth, unsigned outputHeight, + const RuntimeSnapshotVersions& versions); + RuntimeStore& mRuntimeStore; + mutable std::mutex mPublishedSnapshotMutex; + mutable bool mHasPublishedRenderStateSnapshot = false; + mutable RuntimeRenderStateSnapshot mPublishedRenderStateSnapshot; }; diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.cpp b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.cpp index 985d77e..7c6a8a5 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.cpp @@ -4,9 +4,13 @@ #include "ShaderPackageRegistry.h" #include "RuntimeParameterUtils.h" +#include +#include +#include #include #include #include +#include #include #include @@ -129,60 +133,111 @@ bool FontAssetsEqual(const std::vector& left, const std::vector return true; } + +double GenerateStartupRandom() +{ + std::random_device randomDevice; + std::uniform_real_distribution distribution(0.0, 1.0); + return distribution(randomDevice); } -RuntimeStore::RuntimeStore() +bool TryParseLayerIdNumber(const std::string& layerId, uint64_t& number) +{ + const std::string prefix = "layer-"; + if (layerId.rfind(prefix, 0) != 0 || layerId.size() == prefix.size()) + return false; + + uint64_t parsed = 0; + for (std::size_t index = prefix.size(); index < layerId.size(); ++index) + { + const unsigned char ch = static_cast(layerId[index]); + if (!std::isdigit(ch)) + return false; + parsed = parsed * 10 + static_cast(ch - '0'); + } + + number = parsed; + return true; +} + +std::string ShaderParameterTypeToString(ShaderParameterType type) +{ + switch (type) + { + case ShaderParameterType::Float: return "float"; + case ShaderParameterType::Vec2: return "vec2"; + case ShaderParameterType::Color: return "color"; + case ShaderParameterType::Boolean: return "bool"; + case ShaderParameterType::Enum: return "enum"; + case ShaderParameterType::Text: return "text"; + case ShaderParameterType::Trigger: return "trigger"; + } + return "unknown"; +} +} + +RuntimeStore::RuntimeStore() : + 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)()), + mFrameCounter(0), + mNextLayerId(0) { } HealthTelemetry& RuntimeStore::GetHealthTelemetry() { - return mRuntimeHost.GetHealthTelemetry(); + return mHealthTelemetry; } const HealthTelemetry& RuntimeStore::GetHealthTelemetry() const { - return mRuntimeHost.GetHealthTelemetry(); + return mHealthTelemetry; } bool RuntimeStore::InitializeStore(std::string& error) { try { - std::lock_guard lock(mRuntimeHost.mMutex); + std::lock_guard lock(mMutex); if (!ResolvePaths(error)) return false; if (!LoadConfig(error)) return false; - mRuntimeHost.mShaderRoot = mRuntimeHost.mRepoRoot / mRuntimeHost.mConfig.shaderLibrary; + mShaderRoot = mRepoRoot / mConfig.shaderLibrary; if (!LoadPersistentState(error)) return false; if (!ScanShaderPackages(error)) return false; - mRuntimeHost.NormalizePersistentLayerIdsLocked(); + NormalizePersistentLayerIdsLocked(); - for (RuntimeHost::LayerPersistentState& layer : mRuntimeHost.mPersistentState.layers) + for (RuntimeStore::LayerPersistentState& layer : mPersistentState.layers) { - auto shaderIt = mRuntimeHost.mPackagesById.find(layer.shaderId); - if (shaderIt != mRuntimeHost.mPackagesById.end()) - mRuntimeHost.EnsureLayerDefaultsLocked(layer, shaderIt->second); + auto shaderIt = mPackagesById.find(layer.shaderId); + if (shaderIt != mPackagesById.end()) + EnsureLayerDefaultsLocked(layer, shaderIt->second); } - if (mRuntimeHost.mPersistentState.layers.empty() && !mRuntimeHost.mPackageOrder.empty()) + if (mPersistentState.layers.empty() && !mPackageOrder.empty()) { - RuntimeHost::LayerPersistentState layer; - layer.id = mRuntimeHost.GenerateLayerId(); - layer.shaderId = mRuntimeHost.mPackageOrder.front(); + RuntimeStore::LayerPersistentState layer; + layer.id = GenerateLayerId(); + layer.shaderId = mPackageOrder.front(); layer.bypass = false; - mRuntimeHost.EnsureLayerDefaultsLocked(layer, mRuntimeHost.mPackagesById[layer.shaderId]); - mRuntimeHost.mPersistentState.layers.push_back(layer); + EnsureLayerDefaultsLocked(layer, mPackagesById[layer.shaderId]); + mPersistentState.layers.push_back(layer); } - mRuntimeHost.mServerPort = mRuntimeHost.mConfig.serverPort; - mRuntimeHost.mAutoReloadEnabled = mRuntimeHost.mConfig.autoReload; - mRuntimeHost.mReloadRequested = true; - mRuntimeHost.mCompileMessage = "Waiting for shader compile."; + mServerPort = mConfig.serverPort; + mAutoReloadEnabled = mConfig.autoReload; + mReloadRequested = true; + mCompileMessage = "Waiting for shader compile."; return true; } catch (const std::exception& exception) @@ -206,31 +261,31 @@ bool RuntimeStore::PollStoredFileChanges(bool& registryChanged, bool& reloadRequ { try { - std::lock_guard lock(mRuntimeHost.mMutex); + std::lock_guard lock(mMutex); registryChanged = false; reloadRequested = false; - if (!mRuntimeHost.mAutoReloadEnabled) + if (!mAutoReloadEnabled) { - reloadRequested = mRuntimeHost.mReloadRequested; + reloadRequested = 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) + if (mLastScanTime != (std::chrono::steady_clock::time_point::min)() && + std::chrono::duration_cast(now - mLastScanTime).count() < 250) { - reloadRequested = mRuntimeHost.mReloadRequested; + reloadRequested = mReloadRequested; return true; } - mRuntimeHost.mLastScanTime = now; + mLastScanTime = now; std::string scanError; - std::map previousPackages = mRuntimeHost.mPackagesById; - std::vector previousOrder = mRuntimeHost.mPackageOrder; + std::map previousPackages = mPackagesById; + std::vector previousOrder = mPackageOrder; std::map> previousLayerShaderTimes; - for (const RuntimeHost::LayerPersistentState& layer : mRuntimeHost.mPersistentState.layers) + for (const RuntimeStore::LayerPersistentState& layer : mPersistentState.layers) { auto previous = previousPackages.find(layer.shaderId); if (previous != previousPackages.end()) @@ -243,10 +298,10 @@ bool RuntimeStore::PollStoredFileChanges(bool& registryChanged, bool& reloadRequ return false; } - registryChanged = previousOrder != mRuntimeHost.mPackageOrder; - if (!registryChanged && previousPackages.size() == mRuntimeHost.mPackagesById.size()) + registryChanged = previousOrder != mPackageOrder; + if (!registryChanged && previousPackages.size() == mPackagesById.size()) { - for (const auto& item : mRuntimeHost.mPackagesById) + for (const auto& item : mPackagesById) { auto previous = previousPackages.find(item.first); if (previous == previousPackages.end()) @@ -265,13 +320,13 @@ bool RuntimeStore::PollStoredFileChanges(bool& registryChanged, bool& reloadRequ } } - for (RuntimeHost::LayerPersistentState& layer : mRuntimeHost.mPersistentState.layers) + for (RuntimeStore::LayerPersistentState& layer : mPersistentState.layers) { - auto active = mRuntimeHost.mPackagesById.find(layer.shaderId); + auto active = mPackagesById.find(layer.shaderId); auto previous = previousLayerShaderTimes.find(layer.id); - if (active == mRuntimeHost.mPackagesById.end()) + if (active == mPackagesById.end()) continue; - mRuntimeHost.EnsureLayerDefaultsLocked(layer, active->second); + EnsureLayerDefaultsLocked(layer, active->second); if (previous != previousLayerShaderTimes.end()) { auto previousPackage = previousPackages.find(layer.shaderId); @@ -281,14 +336,14 @@ bool RuntimeStore::PollStoredFileChanges(bool& registryChanged, bool& reloadRequ (!TextureAssetsEqual(previousPackage->second.textureAssets, active->second.textureAssets) || !FontAssetsEqual(previousPackage->second.fontAssets, active->second.fontAssets)))) { - mRuntimeHost.mReloadRequested = true; + mReloadRequested = true; } } } - reloadRequested = mRuntimeHost.mReloadRequested; + reloadRequested = mReloadRequested; if (registryChanged || reloadRequested) - mRuntimeHost.MarkRenderStateDirtyLocked(); + MarkRenderStateDirtyLocked(); return true; } catch (const std::exception& exception) @@ -305,97 +360,97 @@ bool RuntimeStore::PollStoredFileChanges(bool& registryChanged, bool& reloadRequ 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()) + std::lock_guard lock(mMutex); + auto shaderIt = mPackagesById.find(shaderId); + if (shaderIt == mPackagesById.end()) { error = "Unknown shader id: " + shaderId; return false; } - RuntimeHost::LayerPersistentState layer; - layer.id = mRuntimeHost.GenerateLayerId(); + RuntimeStore::LayerPersistentState layer; + layer.id = GenerateLayerId(); layer.shaderId = shaderId; layer.bypass = false; - mRuntimeHost.EnsureLayerDefaultsLocked(layer, shaderIt->second); - mRuntimeHost.mPersistentState.layers.push_back(layer); - mRuntimeHost.mReloadRequested = true; - mRuntimeHost.MarkRenderStateDirtyLocked(); + EnsureLayerDefaultsLocked(layer, shaderIt->second); + mPersistentState.layers.push_back(layer); + mReloadRequested = true; + MarkRenderStateDirtyLocked(); return 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()) + std::lock_guard lock(mMutex); + auto it = std::find_if(mPersistentState.layers.begin(), mPersistentState.layers.end(), + [&layerId](const RuntimeStore::LayerPersistentState& layer) { return layer.id == layerId; }); + if (it == mPersistentState.layers.end()) { error = "Unknown layer id: " + layerId; return false; } - mRuntimeHost.mPersistentState.layers.erase(it); - mRuntimeHost.mReloadRequested = true; - mRuntimeHost.MarkRenderStateDirtyLocked(); + mPersistentState.layers.erase(it); + mReloadRequested = true; + MarkRenderStateDirtyLocked(); return 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()) + std::lock_guard lock(mMutex); + auto it = std::find_if(mPersistentState.layers.begin(), mPersistentState.layers.end(), + [&layerId](const RuntimeStore::LayerPersistentState& layer) { return layer.id == layerId; }); + if (it == 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 index = std::distance(mPersistentState.layers.begin(), it); const std::ptrdiff_t newIndex = index + direction; - if (newIndex < 0 || newIndex >= static_cast(mRuntimeHost.mPersistentState.layers.size())) + if (newIndex < 0 || newIndex >= static_cast(mPersistentState.layers.size())) return true; - std::swap(mRuntimeHost.mPersistentState.layers[index], mRuntimeHost.mPersistentState.layers[newIndex]); - mRuntimeHost.mReloadRequested = true; - mRuntimeHost.MarkRenderStateDirtyLocked(); + std::swap(mPersistentState.layers[index], mPersistentState.layers[newIndex]); + mReloadRequested = true; + MarkRenderStateDirtyLocked(); return 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()) + std::lock_guard lock(mMutex); + auto it = std::find_if(mPersistentState.layers.begin(), mPersistentState.layers.end(), + [&layerId](const RuntimeStore::LayerPersistentState& layer) { return layer.id == layerId; }); + if (it == mPersistentState.layers.end()) { error = "Unknown layer id: " + layerId; return false; } - if (mRuntimeHost.mPersistentState.layers.empty()) + if (mPersistentState.layers.empty()) return true; - if (targetIndex >= mRuntimeHost.mPersistentState.layers.size()) - targetIndex = mRuntimeHost.mPersistentState.layers.size() - 1; + if (targetIndex >= mPersistentState.layers.size()) + targetIndex = mPersistentState.layers.size() - 1; - const std::size_t sourceIndex = static_cast(std::distance(mRuntimeHost.mPersistentState.layers.begin(), it)); + const std::size_t sourceIndex = static_cast(std::distance(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(); + RuntimeStore::LayerPersistentState movedLayer = *it; + mPersistentState.layers.erase(mPersistentState.layers.begin() + static_cast(sourceIndex)); + mPersistentState.layers.insert(mPersistentState.layers.begin() + static_cast(targetIndex), movedLayer); + mReloadRequested = true; + MarkRenderStateDirtyLocked(); return 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); + std::lock_guard lock(mMutex); + RuntimeStore::LayerPersistentState* layer = FindLayerById(layerId); if (!layer) { error = "Unknown layer id: " + layerId; @@ -403,23 +458,23 @@ bool RuntimeStore::SetStoredLayerBypassState(const std::string& layerId, bool by } layer->bypass = bypassed; - mRuntimeHost.mReloadRequested = true; - mRuntimeHost.MarkParameterStateDirtyLocked(); + mReloadRequested = true; + MarkParameterStateDirtyLocked(); return 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); + std::lock_guard lock(mMutex); + RuntimeStore::LayerPersistentState* layer = FindLayerById(layerId); if (!layer) { error = "Unknown layer id: " + layerId; return false; } - auto shaderIt = mRuntimeHost.mPackagesById.find(shaderId); - if (shaderIt == mRuntimeHost.mPackagesById.end()) + auto shaderIt = mPackagesById.find(shaderId); + if (shaderIt == mPackagesById.end()) { error = "Unknown shader id: " + shaderId; return false; @@ -427,17 +482,17 @@ bool RuntimeStore::SetStoredLayerShaderSelection(const std::string& layerId, con layer->shaderId = shaderId; layer->parameterValues.clear(); - mRuntimeHost.EnsureLayerDefaultsLocked(*layer, shaderIt->second); - mRuntimeHost.mReloadRequested = true; - mRuntimeHost.MarkRenderStateDirtyLocked(); + EnsureLayerDefaultsLocked(*layer, shaderIt->second); + 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(mRuntimeHost.mMutex); + std::lock_guard lock(mMutex); - RuntimeHost::LayerPersistentState* layer = mRuntimeHost.FindLayerById(layerId); + RuntimeStore::LayerPersistentState* layer = FindLayerById(layerId); if (!layer) { error = "Unknown layer id: " + layerId; @@ -445,37 +500,37 @@ bool RuntimeStore::SetStoredParameterValue(const std::string& layerId, const std } layer->parameterValues[parameterId] = value; - mRuntimeHost.MarkParameterStateDirtyLocked(); + MarkParameterStateDirtyLocked(); return !persistState || SavePersistentState(error); } bool RuntimeStore::ResetStoredLayerParameterValues(const std::string& layerId, std::string& error) { - std::lock_guard lock(mRuntimeHost.mMutex); + std::lock_guard lock(mMutex); - RuntimeHost::LayerPersistentState* layer = mRuntimeHost.FindLayerById(layerId); + RuntimeStore::LayerPersistentState* layer = FindLayerById(layerId); if (!layer) { error = "Unknown layer id: " + layerId; return false; } - auto shaderIt = mRuntimeHost.mPackagesById.find(layer->shaderId); - if (shaderIt == mRuntimeHost.mPackagesById.end()) + auto shaderIt = mPackagesById.find(layer->shaderId); + if (shaderIt == mPackagesById.end()) { error = "Unknown shader id: " + layer->shaderId; return false; } layer->parameterValues.clear(); - mRuntimeHost.EnsureLayerDefaultsLocked(*layer, shaderIt->second); - mRuntimeHost.MarkParameterStateDirtyLocked(); + EnsureLayerDefaultsLocked(*layer, shaderIt->second); + MarkParameterStateDirtyLocked(); return SavePersistentState(error); } bool RuntimeStore::SaveStackPresetSnapshot(const std::string& presetName, std::string& error) const { - std::lock_guard lock(mRuntimeHost.mMutex); + std::lock_guard lock(mMutex); const std::string safeStem = MakeSafePresetFileStem(presetName); if (safeStem.empty()) { @@ -486,14 +541,14 @@ bool RuntimeStore::SaveStackPresetSnapshot(const std::string& presetName, std::s JsonValue root = JsonValue::MakeObject(); root.set("version", JsonValue(1.0)); root.set("name", JsonValue(TrimCopy(presetName))); - root.set("layers", mRuntimeHost.SerializeLayerStackLocked()); + root.set("layers", SerializeLayerStackLocked()); - return WriteTextFile(mRuntimeHost.mPresetRoot / (safeStem + ".json"), SerializeJson(root, true), error); + return WriteTextFile(mPresetRoot / (safeStem + ".json"), SerializeJson(root, true), error); } bool RuntimeStore::LoadStackPresetSnapshot(const std::string& presetName, std::string& error) { - std::lock_guard lock(mRuntimeHost.mMutex); + std::lock_guard lock(mMutex); const std::string safeStem = MakeSafePresetFileStem(presetName); if (safeStem.empty()) { @@ -501,7 +556,7 @@ bool RuntimeStore::LoadStackPresetSnapshot(const std::string& presetName, std::s return false; } - const std::filesystem::path presetPath = mRuntimeHost.mPresetRoot / (safeStem + ".json"); + const std::filesystem::path presetPath = mPresetRoot / (safeStem + ".json"); std::string presetText = ReadTextFile(presetPath, error); if (presetText.empty()) return false; @@ -517,8 +572,8 @@ bool RuntimeStore::LoadStackPresetSnapshot(const std::string& presetName, std::s return false; } - std::vector nextLayers; - if (!mRuntimeHost.DeserializeLayerStackLocked(*layersValue, nextLayers, error)) + std::vector nextLayers; + if (!DeserializeLayerStackLocked(*layersValue, nextLayers, error)) return false; if (nextLayers.empty()) @@ -527,132 +582,255 @@ bool RuntimeStore::LoadStackPresetSnapshot(const std::string& presetName, std::s return false; } - mRuntimeHost.mPersistentState.layers = nextLayers; - mRuntimeHost.mReloadRequested = true; - mRuntimeHost.MarkRenderStateDirtyLocked(); + mPersistentState.layers = nextLayers; + mReloadRequested = true; + MarkRenderStateDirtyLocked(); return SavePersistentState(error); } +bool RuntimeStore::HasStoredLayer(const std::string& layerId) const +{ + std::lock_guard lock(mMutex); + return FindLayerById(layerId) != nullptr; +} + +bool RuntimeStore::HasStoredShader(const std::string& shaderId) const +{ + std::lock_guard lock(mMutex); + return mPackagesById.find(shaderId) != mPackagesById.end(); +} + +bool RuntimeStore::TryGetStoredParameterById(const std::string& layerId, const std::string& parameterId, StoredParameterSnapshot& snapshot, std::string& error) const +{ + std::lock_guard lock(mMutex); + + const LayerPersistentState* layer = FindLayerById(layerId); + if (!layer) + { + error = "Unknown layer id: " + layerId; + return false; + } + + auto shaderIt = mPackagesById.find(layer->shaderId); + if (shaderIt == mPackagesById.end()) + { + error = "Unknown shader id: " + layer->shaderId; + return false; + } + + auto parameterIt = std::find_if(shaderIt->second.parameters.begin(), shaderIt->second.parameters.end(), + [¶meterId](const ShaderParameterDefinition& definition) { return definition.id == parameterId; }); + if (parameterIt == shaderIt->second.parameters.end()) + { + error = "Unknown parameter id: " + parameterId; + return false; + } + + snapshot = StoredParameterSnapshot(); + snapshot.layerId = layer->id; + snapshot.definition = *parameterIt; + auto valueIt = layer->parameterValues.find(parameterIt->id); + if (valueIt != layer->parameterValues.end()) + { + snapshot.currentValue = valueIt->second; + snapshot.hasCurrentValue = true; + } + return true; +} + +bool RuntimeStore::TryGetStoredParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, StoredParameterSnapshot& snapshot, std::string& error) const +{ + std::lock_guard lock(mMutex); + + const LayerPersistentState* layer = nullptr; + const ShaderPackage* shaderPackage = nullptr; + std::vector::const_iterator parameterIt; + if (!TryResolveStoredLayerAndParameterByControlKeyLocked(layerKey, parameterKey, layer, shaderPackage, parameterIt, error)) + return false; + + snapshot = StoredParameterSnapshot(); + snapshot.layerId = layer->id; + snapshot.definition = *parameterIt; + auto valueIt = layer->parameterValues.find(parameterIt->id); + if (valueIt != layer->parameterValues.end()) + { + snapshot.currentValue = valueIt->second; + snapshot.hasCurrentValue = true; + } + return true; +} + +bool RuntimeStore::ResolveStoredLayerMove(const std::string& layerId, int direction, bool& shouldMove, std::string& error) const +{ + std::lock_guard lock(mMutex); + auto it = std::find_if(mPersistentState.layers.begin(), mPersistentState.layers.end(), + [&layerId](const RuntimeStore::LayerPersistentState& layer) { return layer.id == layerId; }); + if (it == mPersistentState.layers.end()) + { + error = "Unknown layer id: " + layerId; + return false; + } + + const std::ptrdiff_t index = std::distance(mPersistentState.layers.begin(), it); + const std::ptrdiff_t newIndex = index + direction; + shouldMove = newIndex >= 0 && newIndex < static_cast(mPersistentState.layers.size()) && newIndex != index; + return true; +} + +bool RuntimeStore::ResolveStoredLayerMoveToIndex(const std::string& layerId, std::size_t targetIndex, bool& shouldMove, std::string& error) const +{ + std::lock_guard lock(mMutex); + auto it = std::find_if(mPersistentState.layers.begin(), mPersistentState.layers.end(), + [&layerId](const RuntimeStore::LayerPersistentState& layer) { return layer.id == layerId; }); + if (it == mPersistentState.layers.end()) + { + error = "Unknown layer id: " + layerId; + return false; + } + + if (mPersistentState.layers.empty()) + { + shouldMove = false; + return true; + } + + const std::size_t clampedTargetIndex = (std::min)(targetIndex, mPersistentState.layers.size() - 1); + const std::size_t sourceIndex = static_cast(std::distance(mPersistentState.layers.begin(), it)); + shouldMove = sourceIndex != clampedTargetIndex; + return true; +} + +bool RuntimeStore::IsValidStackPresetName(const std::string& presetName) const +{ + return !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 mRuntimeHost.mRepoRoot; + return mRepoRoot; } const std::filesystem::path& RuntimeStore::GetRuntimeUiRoot() const { - return mRuntimeHost.mUiRoot; + return mUiRoot; } const std::filesystem::path& RuntimeStore::GetRuntimeDocsRoot() const { - return mRuntimeHost.mDocsRoot; + return mDocsRoot; } const std::filesystem::path& RuntimeStore::GetRuntimeDataRoot() const { - return mRuntimeHost.mRuntimeRoot; + return mRuntimeRoot; } unsigned short RuntimeStore::GetConfiguredControlServerPort() const { - return mRuntimeHost.mServerPort; + return mServerPort; } unsigned short RuntimeStore::GetConfiguredOscPort() const { - return mRuntimeHost.mConfig.oscPort; + return mConfig.oscPort; } const std::string& RuntimeStore::GetConfiguredOscBindAddress() const { - return mRuntimeHost.mConfig.oscBindAddress; + return mConfig.oscBindAddress; } double RuntimeStore::GetConfiguredOscSmoothing() const { - return mRuntimeHost.mConfig.oscSmoothing; + return mConfig.oscSmoothing; } unsigned RuntimeStore::GetConfiguredMaxTemporalHistoryFrames() const { - return mRuntimeHost.mConfig.maxTemporalHistoryFrames; + return mConfig.maxTemporalHistoryFrames; } unsigned RuntimeStore::GetConfiguredPreviewFps() const { - return mRuntimeHost.mConfig.previewFps; + return mConfig.previewFps; } bool RuntimeStore::IsExternalKeyingConfigured() const { - return mRuntimeHost.mConfig.enableExternalKeying; + return mConfig.enableExternalKeying; } const std::string& RuntimeStore::GetConfiguredInputVideoFormat() const { - return mRuntimeHost.mConfig.inputVideoFormat; + return mConfig.inputVideoFormat; } const std::string& RuntimeStore::GetConfiguredInputFrameRate() const { - return mRuntimeHost.mConfig.inputFrameRate; + return mConfig.inputFrameRate; } const std::string& RuntimeStore::GetConfiguredOutputVideoFormat() const { - return mRuntimeHost.mConfig.outputVideoFormat; + return mConfig.outputVideoFormat; } const std::string& RuntimeStore::GetConfiguredOutputFrameRate() const { - return mRuntimeHost.mConfig.outputFrameRate; + return mConfig.outputFrameRate; } void RuntimeStore::SetBoundControlServerPort(unsigned short port) { - std::lock_guard lock(mRuntimeHost.mMutex); - mRuntimeHost.mServerPort = port; + std::lock_guard lock(mMutex); + mServerPort = port; } void RuntimeStore::SetCompileStatus(bool succeeded, const std::string& message) { - std::lock_guard lock(mRuntimeHost.mMutex); - mRuntimeHost.mCompileSucceeded = succeeded; - mRuntimeHost.mCompileMessage = message; + std::lock_guard lock(mMutex); + mCompileSucceeded = succeeded; + mCompileMessage = message; } void RuntimeStore::ClearReloadRequest() { - std::lock_guard lock(mRuntimeHost.mMutex); - mRuntimeHost.mReloadRequested = false; + std::lock_guard lock(mMutex); + mReloadRequested = false; } JsonValue RuntimeStore::BuildRuntimeStateValue() const { - const HealthTelemetry::Snapshot telemetrySnapshot = mRuntimeHost.mHealthTelemetry.GetSnapshot(); - std::lock_guard lock(mRuntimeHost.mMutex); + const HealthTelemetry::Snapshot telemetrySnapshot = mHealthTelemetry.GetSnapshot(); + std::lock_guard lock(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)); + app.set("serverPort", JsonValue(static_cast(mServerPort))); + app.set("oscPort", JsonValue(static_cast(mConfig.oscPort))); + app.set("oscBindAddress", JsonValue(mConfig.oscBindAddress)); + app.set("oscSmoothing", JsonValue(mConfig.oscSmoothing)); + app.set("autoReload", JsonValue(mAutoReloadEnabled)); + app.set("maxTemporalHistoryFrames", JsonValue(static_cast(mConfig.maxTemporalHistoryFrames))); + app.set("previewFps", JsonValue(static_cast(mConfig.previewFps))); + app.set("enableExternalKeying", JsonValue(mConfig.enableExternalKeying)); + app.set("inputVideoFormat", JsonValue(mConfig.inputVideoFormat)); + app.set("inputFrameRate", JsonValue(mConfig.inputFrameRate)); + app.set("outputVideoFormat", JsonValue(mConfig.outputVideoFormat)); + app.set("outputFrameRate", JsonValue(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)); + runtime.set("layerCount", JsonValue(static_cast(mPersistentState.layers.size()))); + runtime.set("compileSucceeded", JsonValue(mCompileSucceeded)); + runtime.set("compileMessage", JsonValue(mCompileMessage)); root.set("runtime", runtime); JsonValue video = JsonValue::MakeObject(); @@ -700,7 +878,7 @@ JsonValue RuntimeStore::BuildRuntimeStateValue() const root.set("performance", performance); JsonValue shaderLibrary = JsonValue::MakeArray(); - for (const ShaderPackageStatus& status : mRuntimeHost.mPackageStatuses) + for (const ShaderPackageStatus& status : mPackageStatuses) { JsonValue shader = JsonValue::MakeObject(); shader.set("id", JsonValue(status.id)); @@ -711,17 +889,17 @@ JsonValue RuntimeStore::BuildRuntimeStateValue() const 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) + auto shaderIt = mPackagesById.find(status.id); + if (status.available && shaderIt != 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("historySource", JsonValue(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) + if (status.available && shaderIt != mPackagesById.end() && shaderIt->second.feedback.enabled) { JsonValue feedback = JsonValue::MakeObject(); feedback.set("enabled", JsonValue(true)); @@ -743,15 +921,15 @@ JsonValue RuntimeStore::BuildRuntimeStateValue() const JsonValue RuntimeStore::SerializeLayerStack() const { - return mRuntimeHost.SerializeLayerStackLocked(); + return SerializeLayerStackLocked(); } bool RuntimeStore::LoadConfig(std::string& error) { - if (!std::filesystem::exists(mRuntimeHost.mConfigPath)) + if (!std::filesystem::exists(mConfigPath)) return true; - std::string configText = ReadTextFile(mRuntimeHost.mConfigPath, error); + std::string configText = ReadTextFile(mConfigPath, error); if (configText.empty()) return false; @@ -760,95 +938,95 @@ bool RuntimeStore::LoadConfig(std::string& error) return false; if (const JsonValue* shaderLibraryValue = configJson.find("shaderLibrary")) - mRuntimeHost.mConfig.shaderLibrary = shaderLibraryValue->asString(); + mConfig.shaderLibrary = shaderLibraryValue->asString(); if (const JsonValue* serverPortValue = configJson.find("serverPort")) - mRuntimeHost.mConfig.serverPort = static_cast(serverPortValue->asNumber(mRuntimeHost.mConfig.serverPort)); + mConfig.serverPort = static_cast(serverPortValue->asNumber(mConfig.serverPort)); if (const JsonValue* oscPortValue = configJson.find("oscPort")) - mRuntimeHost.mConfig.oscPort = static_cast(oscPortValue->asNumber(mRuntimeHost.mConfig.oscPort)); + mConfig.oscPort = static_cast(oscPortValue->asNumber(mConfig.oscPort)); if (const JsonValue* oscBindAddressValue = configJson.find("oscBindAddress")) - mRuntimeHost.mConfig.oscBindAddress = oscBindAddressValue->asString(); + mConfig.oscBindAddress = oscBindAddressValue->asString(); if (const JsonValue* oscSmoothingValue = configJson.find("oscSmoothing")) - mRuntimeHost.mConfig.oscSmoothing = Clamp01(oscSmoothingValue->asNumber(mRuntimeHost.mConfig.oscSmoothing)); + mConfig.oscSmoothing = Clamp01(oscSmoothingValue->asNumber(mConfig.oscSmoothing)); if (const JsonValue* autoReloadValue = configJson.find("autoReload")) - mRuntimeHost.mConfig.autoReload = autoReloadValue->asBoolean(mRuntimeHost.mConfig.autoReload); + mConfig.autoReload = autoReloadValue->asBoolean(mConfig.autoReload); if (const JsonValue* maxTemporalHistoryFramesValue = configJson.find("maxTemporalHistoryFrames")) { - const double configuredValue = maxTemporalHistoryFramesValue->asNumber(static_cast(mRuntimeHost.mConfig.maxTemporalHistoryFrames)); - mRuntimeHost.mConfig.maxTemporalHistoryFrames = configuredValue <= 0.0 ? 0u : static_cast(configuredValue); + 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(mRuntimeHost.mConfig.previewFps)); - mRuntimeHost.mConfig.previewFps = configuredValue <= 0.0 ? 0u : static_cast(configuredValue); + 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")) - mRuntimeHost.mConfig.enableExternalKeying = enableExternalKeyingValue->asBoolean(mRuntimeHost.mConfig.enableExternalKeying); + mConfig.enableExternalKeying = enableExternalKeyingValue->asBoolean(mConfig.enableExternalKeying); if (const JsonValue* videoFormatValue = configJson.find("videoFormat")) { if (videoFormatValue->isString() && !videoFormatValue->asString().empty()) { - mRuntimeHost.mConfig.inputVideoFormat = videoFormatValue->asString(); - mRuntimeHost.mConfig.outputVideoFormat = videoFormatValue->asString(); + mConfig.inputVideoFormat = videoFormatValue->asString(); + mConfig.outputVideoFormat = videoFormatValue->asString(); } } if (const JsonValue* frameRateValue = configJson.find("frameRate")) { if (frameRateValue->isString() && !frameRateValue->asString().empty()) { - mRuntimeHost.mConfig.inputFrameRate = frameRateValue->asString(); - mRuntimeHost.mConfig.outputFrameRate = frameRateValue->asString(); + mConfig.inputFrameRate = frameRateValue->asString(); + mConfig.outputFrameRate = frameRateValue->asString(); } else if (frameRateValue->isNumber()) { std::ostringstream stream; stream << frameRateValue->asNumber(); - mRuntimeHost.mConfig.inputFrameRate = stream.str(); - mRuntimeHost.mConfig.outputFrameRate = stream.str(); + mConfig.inputFrameRate = stream.str(); + mConfig.outputFrameRate = stream.str(); } } if (const JsonValue* inputVideoFormatValue = configJson.find("inputVideoFormat")) { if (inputVideoFormatValue->isString() && !inputVideoFormatValue->asString().empty()) - mRuntimeHost.mConfig.inputVideoFormat = inputVideoFormatValue->asString(); + mConfig.inputVideoFormat = inputVideoFormatValue->asString(); } if (const JsonValue* inputFrameRateValue = configJson.find("inputFrameRate")) { if (inputFrameRateValue->isString() && !inputFrameRateValue->asString().empty()) - mRuntimeHost.mConfig.inputFrameRate = inputFrameRateValue->asString(); + mConfig.inputFrameRate = inputFrameRateValue->asString(); else if (inputFrameRateValue->isNumber()) { std::ostringstream stream; stream << inputFrameRateValue->asNumber(); - mRuntimeHost.mConfig.inputFrameRate = stream.str(); + mConfig.inputFrameRate = stream.str(); } } if (const JsonValue* outputVideoFormatValue = configJson.find("outputVideoFormat")) { if (outputVideoFormatValue->isString() && !outputVideoFormatValue->asString().empty()) - mRuntimeHost.mConfig.outputVideoFormat = outputVideoFormatValue->asString(); + mConfig.outputVideoFormat = outputVideoFormatValue->asString(); } if (const JsonValue* outputFrameRateValue = configJson.find("outputFrameRate")) { if (outputFrameRateValue->isString() && !outputFrameRateValue->asString().empty()) - mRuntimeHost.mConfig.outputFrameRate = outputFrameRateValue->asString(); + mConfig.outputFrameRate = outputFrameRateValue->asString(); else if (outputFrameRateValue->isNumber()) { std::ostringstream stream; stream << outputFrameRateValue->asNumber(); - mRuntimeHost.mConfig.outputFrameRate = stream.str(); + mConfig.outputFrameRate = stream.str(); } } - mRuntimeHost.mAutoReloadEnabled = mRuntimeHost.mConfig.autoReload; + mAutoReloadEnabled = mConfig.autoReload; return true; } bool RuntimeStore::LoadPersistentState(std::string& error) { - if (!std::filesystem::exists(mRuntimeHost.mRuntimeStatePath)) + if (!std::filesystem::exists(mRuntimeStatePath)) return true; - std::string stateText = ReadTextFile(mRuntimeHost.mRuntimeStatePath, error); + std::string stateText = ReadTextFile(mRuntimeStatePath, error); if (stateText.empty()) return false; @@ -862,7 +1040,7 @@ bool RuntimeStore::LoadPersistentState(std::string& error) { if (!layerValue.isObject()) continue; - RuntimeHost::LayerPersistentState layer; + RuntimeStore::LayerPersistentState layer; if (const JsonValue* idValue = layerValue.find("id")) layer.id = idValue->asString(); if (const JsonValue* shaderIdValue = layerValue.find("shaderId")) @@ -891,7 +1069,7 @@ bool RuntimeStore::LoadPersistentState(std::string& error) } if (!layer.shaderId.empty()) - mRuntimeHost.mPersistentState.layers.push_back(layer); + mPersistentState.layers.push_back(layer); } } else @@ -902,8 +1080,8 @@ bool RuntimeStore::LoadPersistentState(std::string& error) if (!activeShaderId.empty()) { - RuntimeHost::LayerPersistentState layer; - layer.id = mRuntimeHost.GenerateLayerId(); + RuntimeStore::LayerPersistentState layer; + layer.id = GenerateLayerId(); layer.shaderId = activeShaderId; layer.bypass = false; @@ -929,7 +1107,7 @@ bool RuntimeStore::LoadPersistentState(std::string& error) } } - mRuntimeHost.mPersistentState.layers.push_back(layer); + mPersistentState.layers.push_back(layer); } } @@ -941,7 +1119,7 @@ bool RuntimeStore::SavePersistentState(std::string& error) const JsonValue root = JsonValue::MakeObject(); JsonValue layers = JsonValue::MakeArray(); - for (const RuntimeHost::LayerPersistentState& layer : mRuntimeHost.mPersistentState.layers) + for (const RuntimeStore::LayerPersistentState& layer : mPersistentState.layers) { JsonValue layerValue = JsonValue::MakeObject(); layerValue.set("id", JsonValue(layer.id)); @@ -949,11 +1127,11 @@ bool RuntimeStore::SavePersistentState(std::string& error) const layerValue.set("bypass", JsonValue(layer.bypass)); JsonValue parameterValues = JsonValue::MakeObject(); - auto packageIt = mRuntimeHost.mPackagesById.find(layer.shaderId); + auto packageIt = mPackagesById.find(layer.shaderId); for (const auto& parameterItem : layer.parameterValues) { const ShaderParameterDefinition* definition = nullptr; - if (packageIt != mRuntimeHost.mPackagesById.end()) + if (packageIt != mPackagesById.end()) { for (const ShaderParameterDefinition& candidate : packageIt->second.parameters) { @@ -966,7 +1144,7 @@ bool RuntimeStore::SavePersistentState(std::string& error) const } if (definition) - parameterValues.set(parameterItem.first, mRuntimeHost.SerializeParameterValue(*definition, parameterItem.second)); + parameterValues.set(parameterItem.first, SerializeParameterValue(*definition, parameterItem.second)); } layerValue.set("parameterValues", parameterValues); @@ -974,7 +1152,7 @@ bool RuntimeStore::SavePersistentState(std::string& error) const } root.set("layers", layers); - return WriteTextFile(mRuntimeHost.mRuntimeStatePath, SerializeJson(root, true), error); + return WriteTextFile(mRuntimeStatePath, SerializeJson(root, true), error); } bool RuntimeStore::ScanShaderPackages(std::string& error) @@ -982,23 +1160,23 @@ bool RuntimeStore::ScanShaderPackages(std::string& error) std::map packagesById; std::vector packageOrder; std::vector packageStatuses; - ShaderPackageRegistry registry(mRuntimeHost.mConfig.maxTemporalHistoryFrames); - if (!registry.Scan(mRuntimeHost.mShaderRoot, packagesById, packageOrder, packageStatuses, error)) + ShaderPackageRegistry registry(mConfig.maxTemporalHistoryFrames); + if (!registry.Scan(mShaderRoot, packagesById, packageOrder, packageStatuses, error)) return false; - mRuntimeHost.mPackagesById.swap(packagesById); - mRuntimeHost.mPackageOrder.swap(packageOrder); - mRuntimeHost.mPackageStatuses.swap(packageStatuses); + mPackagesById.swap(packagesById); + mPackageOrder.swap(packageOrder); + mPackageStatuses.swap(packageStatuses); - for (auto it = mRuntimeHost.mPersistentState.layers.begin(); it != mRuntimeHost.mPersistentState.layers.end();) + for (auto it = mPersistentState.layers.begin(); it != mPersistentState.layers.end();) { - if (mRuntimeHost.mPackagesById.find(it->shaderId) == mRuntimeHost.mPackagesById.end()) - it = mRuntimeHost.mPersistentState.layers.erase(it); + if (mPackagesById.find(it->shaderId) == mPackagesById.end()) + it = mPersistentState.layers.erase(it); else ++it; } - mRuntimeHost.MarkRenderStateDirtyLocked(); + MarkRenderStateDirtyLocked(); return true; } @@ -1050,28 +1228,28 @@ bool RuntimeStore::WriteTextFile(const std::filesystem::path& path, const std::s bool RuntimeStore::ResolvePaths(std::string& error) { - mRuntimeHost.mRepoRoot = FindRepoRootCandidate(); - if (mRuntimeHost.mRepoRoot.empty()) + mRepoRoot = FindRepoRootCandidate(); + if (mRepoRoot.empty()) { error = "Could not locate the repository root from the current runtime path."; return false; } - const std::filesystem::path builtUiRoot = mRuntimeHost.mRepoRoot / "ui" / "dist"; - mRuntimeHost.mUiRoot = std::filesystem::exists(builtUiRoot) ? builtUiRoot : (mRuntimeHost.mRepoRoot / "ui"); - mRuntimeHost.mDocsRoot = mRuntimeHost.mRepoRoot / "docs"; - mRuntimeHost.mConfigPath = mRuntimeHost.mRepoRoot / "config" / "runtime-host.json"; - mRuntimeHost.mShaderRoot = mRuntimeHost.mRepoRoot / mRuntimeHost.mConfig.shaderLibrary; - mRuntimeHost.mRuntimeRoot = mRuntimeHost.mRepoRoot / "runtime"; - mRuntimeHost.mPresetRoot = mRuntimeHost.mRuntimeRoot / "stack_presets"; - mRuntimeHost.mRuntimeStatePath = mRuntimeHost.mRuntimeRoot / "runtime_state.json"; - mRuntimeHost.mWrapperPath = mRuntimeHost.mRuntimeRoot / "shader_cache" / "active_shader_wrapper.slang"; - mRuntimeHost.mGeneratedGlslPath = mRuntimeHost.mRuntimeRoot / "shader_cache" / "active_shader.raw.frag"; - mRuntimeHost.mPatchedGlslPath = mRuntimeHost.mRuntimeRoot / "shader_cache" / "active_shader.frag"; + 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"; + mShaderRoot = mRepoRoot / mConfig.shaderLibrary; + mRuntimeRoot = mRepoRoot / "runtime"; + mPresetRoot = mRuntimeRoot / "stack_presets"; + mRuntimeStatePath = mRuntimeRoot / "runtime_state.json"; + mWrapperPath = mRuntimeRoot / "shader_cache" / "active_shader_wrapper.slang"; + mGeneratedGlslPath = mRuntimeRoot / "shader_cache" / "active_shader.raw.frag"; + mPatchedGlslPath = mRuntimeRoot / "shader_cache" / "active_shader.frag"; std::error_code fsError; - std::filesystem::create_directories(mRuntimeHost.mRuntimeRoot / "shader_cache", fsError); - std::filesystem::create_directories(mRuntimeHost.mPresetRoot, fsError); + std::filesystem::create_directories(mRuntimeRoot / "shader_cache", fsError); + std::filesystem::create_directories(mPresetRoot, fsError); return true; } @@ -1079,10 +1257,10 @@ std::vector RuntimeStore::GetStackPresetNamesLocked() const { std::vector presetNames; std::error_code fsError; - if (!std::filesystem::exists(mRuntimeHost.mPresetRoot, fsError)) + if (!std::filesystem::exists(mPresetRoot, fsError)) return presetNames; - for (const auto& entry : std::filesystem::directory_iterator(mRuntimeHost.mPresetRoot, fsError)) + for (const auto& entry : std::filesystem::directory_iterator(mPresetRoot, fsError)) { if (!entry.is_regular_file()) continue; @@ -1101,16 +1279,16 @@ std::string RuntimeStore::MakeSafePresetFileStem(const std::string& presetName) } bool RuntimeStore::TryResolveStoredLayerAndParameterByControlKeyLocked(const std::string& layerKey, const std::string& parameterKey, - RuntimeHost::LayerPersistentState*& matchedLayer, const ShaderPackage*& matchedPackage, + const RuntimeStore::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) + for (const RuntimeStore::LayerPersistentState& layer : mPersistentState.layers) { - auto shaderIt = mRuntimeHost.mPackagesById.find(layer.shaderId); - if (shaderIt == mRuntimeHost.mPackagesById.end()) + auto shaderIt = mPackagesById.find(layer.shaderId); + if (shaderIt == mPackagesById.end()) continue; if (MatchesControlKey(layer.id, layerKey) || MatchesControlKey(shaderIt->second.id, layerKey) || @@ -1144,16 +1322,16 @@ bool RuntimeStore::TryResolveStoredLayerAndParameterByControlKeyLocked(const std bool RuntimeStore::CopyShaderPackageForStoredLayer(const std::string& layerId, ShaderPackage& shaderPackage, std::string& error) const { - std::lock_guard lock(mRuntimeHost.mMutex); - const RuntimeHost::LayerPersistentState* layer = mRuntimeHost.FindLayerById(layerId); + std::lock_guard lock(mMutex); + const RuntimeStore::LayerPersistentState* layer = FindLayerById(layerId); if (!layer) { error = "Unknown layer id: " + layerId; return false; } - auto it = mRuntimeHost.mPackagesById.find(layer->shaderId); - if (it == mRuntimeHost.mPackagesById.end()) + auto it = mPackagesById.find(layer->shaderId); + if (it == mPackagesById.end()) { error = "Unknown shader id: " + layer->shaderId; return false; @@ -1166,38 +1344,38 @@ bool RuntimeStore::CopyShaderPackageForStoredLayer(const std::string& layerId, S void RuntimeStore::GetShaderCompilerInputs(std::filesystem::path& repoRoot, std::filesystem::path& wrapperPath, std::filesystem::path& generatedGlslPath, std::filesystem::path& patchedGlslPath, unsigned& maxTemporalHistoryFrames) const { - std::lock_guard lock(mRuntimeHost.mMutex); - repoRoot = mRuntimeHost.mRepoRoot; - wrapperPath = mRuntimeHost.mWrapperPath; - generatedGlslPath = mRuntimeHost.mGeneratedGlslPath; - patchedGlslPath = mRuntimeHost.mPatchedGlslPath; - maxTemporalHistoryFrames = mRuntimeHost.mConfig.maxTemporalHistoryFrames; + std::lock_guard lock(mMutex); + repoRoot = mRepoRoot; + wrapperPath = mWrapperPath; + generatedGlslPath = mGeneratedGlslPath; + patchedGlslPath = mPatchedGlslPath; + maxTemporalHistoryFrames = mConfig.maxTemporalHistoryFrames; } uint64_t RuntimeStore::GetRenderStateVersion() const { - return mRuntimeHost.mRenderStateVersion.load(std::memory_order_relaxed); + return mRenderStateVersion.load(std::memory_order_relaxed); } uint64_t RuntimeStore::GetParameterStateVersion() const { - return mRuntimeHost.mParameterStateVersion.load(std::memory_order_relaxed); + return mParameterStateVersion.load(std::memory_order_relaxed); } void RuntimeStore::AdvanceFrameCounter() { - ++mRuntimeHost.mFrameCounter; + ++mFrameCounter; } void RuntimeStore::BuildLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector& states) const { - std::lock_guard lock(mRuntimeHost.mMutex); + std::lock_guard lock(mMutex); BuildLayerRenderStatesLocked(outputWidth, outputHeight, states); } bool RuntimeStore::TryBuildLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector& states) const { - std::unique_lock lock(mRuntimeHost.mMutex, std::try_to_lock); + std::unique_lock lock(mMutex, std::try_to_lock); if (!lock.owns_lock()) return false; @@ -1207,7 +1385,7 @@ bool RuntimeStore::TryBuildLayerRenderStates(unsigned outputWidth, unsigned outp bool RuntimeStore::TryRefreshLayerParameters(std::vector& states) const { - std::unique_lock lock(mRuntimeHost.mMutex, std::try_to_lock); + std::unique_lock lock(mMutex, std::try_to_lock); if (!lock.owns_lock()) return false; @@ -1217,19 +1395,19 @@ bool RuntimeStore::TryRefreshLayerParameters(std::vector& st void RuntimeStore::RefreshDynamicRenderStateFields(std::vector& states) const { - std::lock_guard lock(mRuntimeHost.mMutex); + std::lock_guard lock(mMutex); RefreshDynamicRenderStateFieldsLocked(states); } void RuntimeStore::BuildLayerRenderStatesLocked(unsigned outputWidth, unsigned outputHeight, std::vector& states) const { states.clear(); - const HealthTelemetry::SignalStatusSnapshot signalStatus = mRuntimeHost.mHealthTelemetry.GetSignalStatusSnapshot(); + const HealthTelemetry::SignalStatusSnapshot signalStatus = mHealthTelemetry.GetSignalStatusSnapshot(); - for (const RuntimeHost::LayerPersistentState& layer : mRuntimeHost.mPersistentState.layers) + for (const RuntimeStore::LayerPersistentState& layer : mPersistentState.layers) { - auto shaderIt = mRuntimeHost.mPackagesById.find(layer.shaderId); - if (shaderIt == mRuntimeHost.mPackagesById.end()) + auto shaderIt = mPackagesById.find(layer.shaderId); + if (shaderIt == mPackagesById.end()) continue; RuntimeRenderState state; @@ -1253,7 +1431,7 @@ void RuntimeStore::BuildLayerRenderStatesLocked(unsigned outputWidth, unsigned o for (const ShaderParameterDefinition& definition : shaderIt->second.parameters) { - ShaderParameterValue value = mRuntimeHost.DefaultValueForDefinition(definition); + ShaderParameterValue value = DefaultValueForDefinition(definition); auto valueIt = layer.parameterValues.find(definition.id); if (valueIt != layer.parameterValues.end()) value = valueIt->second; @@ -1270,16 +1448,16 @@ void RuntimeStore::RefreshLayerParametersLocked(std::vector& { for (RuntimeRenderState& state : states) { - const auto layerIt = std::find_if(mRuntimeHost.mPersistentState.layers.begin(), mRuntimeHost.mPersistentState.layers.end(), - [&state](const RuntimeHost::LayerPersistentState& layer) { return layer.id == state.layerId; }); - if (layerIt == mRuntimeHost.mPersistentState.layers.end()) + const auto layerIt = std::find_if(mPersistentState.layers.begin(), mPersistentState.layers.end(), + [&state](const RuntimeStore::LayerPersistentState& layer) { return layer.id == state.layerId; }); + if (layerIt == mPersistentState.layers.end()) continue; state.bypass = layerIt->bypass ? 1.0 : 0.0; state.parameterValues.clear(); for (const ShaderParameterDefinition& definition : state.parameterDefinitions) { - ShaderParameterValue value = mRuntimeHost.DefaultValueForDefinition(definition); + ShaderParameterValue value = DefaultValueForDefinition(definition); auto valueIt = layerIt->parameterValues.find(definition.id); if (valueIt != layerIt->parameterValues.end()) value = valueIt->second; @@ -1291,15 +1469,356 @@ void RuntimeStore::RefreshLayerParametersLocked(std::vector& void RuntimeStore::RefreshDynamicRenderStateFieldsLocked(std::vector& states) const { const RuntimeClockSnapshot clock = GetRuntimeClockSnapshot(); - const double timeSeconds = std::chrono::duration_cast>(std::chrono::steady_clock::now() - mRuntimeHost.mStartTime).count(); - const double frameCount = static_cast(mRuntimeHost.mFrameCounter.load(std::memory_order_relaxed)); + const double timeSeconds = std::chrono::duration_cast>(std::chrono::steady_clock::now() - mStartTime).count(); + const double frameCount = static_cast(mFrameCounter.load(std::memory_order_relaxed)); for (RuntimeRenderState& state : states) { state.timeSeconds = timeSeconds; state.utcTimeSeconds = clock.utcTimeSeconds; state.utcOffsetSeconds = clock.utcOffsetSeconds; - state.startupRandom = mRuntimeHost.mStartupRandom; + state.startupRandom = mStartupRandom; state.frameCount = frameCount; } } + +void RuntimeStore::MarkRenderStateDirtyLocked() +{ + mRenderStateVersion.fetch_add(1, std::memory_order_relaxed); + mParameterStateVersion.fetch_add(1, std::memory_order_relaxed); +} + +void RuntimeStore::MarkParameterStateDirtyLocked() +{ + mParameterStateVersion.fetch_add(1, std::memory_order_relaxed); +} + +bool RuntimeStore::NormalizeAndValidateValue(const ShaderParameterDefinition& definition, const JsonValue& value, ShaderParameterValue& normalizedValue, std::string& error) const +{ + return NormalizeAndValidateParameterValue(definition, value, normalizedValue, error); +} + +ShaderParameterValue RuntimeStore::DefaultValueForDefinition(const ShaderParameterDefinition& definition) const +{ + return ::DefaultValueForDefinition(definition); +} + +void RuntimeStore::EnsureLayerDefaultsLocked(LayerPersistentState& layerState, const ShaderPackage& shaderPackage) const +{ + for (const ShaderParameterDefinition& definition : shaderPackage.parameters) + { + auto valueIt = layerState.parameterValues.find(definition.id); + if (valueIt == layerState.parameterValues.end()) + { + layerState.parameterValues[definition.id] = DefaultValueForDefinition(definition); + continue; + } + + JsonValue valueJson; + bool shouldNormalize = true; + switch (definition.type) + { + case ShaderParameterType::Float: + if (valueIt->second.numberValues.empty()) + shouldNormalize = false; + else + valueJson = JsonValue(valueIt->second.numberValues.front()); + break; + case ShaderParameterType::Vec2: + case ShaderParameterType::Color: + valueJson = JsonValue::MakeArray(); + for (double number : valueIt->second.numberValues) + valueJson.pushBack(JsonValue(number)); + break; + case ShaderParameterType::Boolean: + valueJson = JsonValue(valueIt->second.booleanValue); + break; + case ShaderParameterType::Enum: + valueJson = JsonValue(valueIt->second.enumValue); + break; + case ShaderParameterType::Text: + { + const std::string textValue = !valueIt->second.textValue.empty() + ? valueIt->second.textValue + : valueIt->second.enumValue; + if (textValue.empty()) + { + valueIt->second = DefaultValueForDefinition(definition); + shouldNormalize = false; + } + else + { + valueJson = JsonValue(textValue); + } + break; + } + case ShaderParameterType::Trigger: + if (valueIt->second.numberValues.empty()) + valueJson = JsonValue(0.0); + else + valueJson = JsonValue((std::max)(0.0, std::floor(valueIt->second.numberValues.front()))); + break; + } + + if (!shouldNormalize) + continue; + + ShaderParameterValue normalizedValue; + std::string normalizeError; + if (NormalizeAndValidateValue(definition, valueJson, normalizedValue, normalizeError)) + valueIt->second = normalizedValue; + else + valueIt->second = DefaultValueForDefinition(definition); + } +} + +JsonValue RuntimeStore::SerializeLayerStackLocked() const +{ + JsonValue layers = JsonValue::MakeArray(); + for (const LayerPersistentState& layer : mPersistentState.layers) + { + auto shaderIt = mPackagesById.find(layer.shaderId); + if (shaderIt == mPackagesById.end()) + continue; + + JsonValue layerValue = JsonValue::MakeObject(); + layerValue.set("id", JsonValue(layer.id)); + layerValue.set("shaderId", JsonValue(layer.shaderId)); + layerValue.set("shaderName", JsonValue(shaderIt->second.displayName)); + layerValue.set("bypass", JsonValue(layer.bypass)); + if (shaderIt->second.temporal.enabled) + { + JsonValue temporal = JsonValue::MakeObject(); + temporal.set("enabled", JsonValue(true)); + temporal.set("historySource", JsonValue(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))); + layerValue.set("temporal", temporal); + } + if (shaderIt->second.feedback.enabled) + { + JsonValue feedback = JsonValue::MakeObject(); + feedback.set("enabled", JsonValue(true)); + feedback.set("writePass", JsonValue(shaderIt->second.feedback.writePassId)); + layerValue.set("feedback", feedback); + } + + JsonValue parameters = JsonValue::MakeArray(); + for (const ShaderParameterDefinition& definition : shaderIt->second.parameters) + { + JsonValue parameter = JsonValue::MakeObject(); + parameter.set("id", JsonValue(definition.id)); + parameter.set("label", JsonValue(definition.label)); + if (!definition.description.empty()) + parameter.set("description", JsonValue(definition.description)); + parameter.set("type", JsonValue(ShaderParameterTypeToString(definition.type))); + parameter.set("defaultValue", SerializeParameterValue(definition, DefaultValueForDefinition(definition))); + + if (!definition.minNumbers.empty()) + { + JsonValue minValue = JsonValue::MakeArray(); + for (double number : definition.minNumbers) + minValue.pushBack(JsonValue(number)); + parameter.set("min", minValue); + } + if (!definition.maxNumbers.empty()) + { + JsonValue maxValue = JsonValue::MakeArray(); + for (double number : definition.maxNumbers) + maxValue.pushBack(JsonValue(number)); + parameter.set("max", maxValue); + } + if (!definition.stepNumbers.empty()) + { + JsonValue stepValue = JsonValue::MakeArray(); + for (double number : definition.stepNumbers) + stepValue.pushBack(JsonValue(number)); + parameter.set("step", stepValue); + } + if (definition.type == ShaderParameterType::Enum) + { + JsonValue options = JsonValue::MakeArray(); + for (const ShaderParameterOption& option : definition.enumOptions) + { + JsonValue optionValue = JsonValue::MakeObject(); + optionValue.set("value", JsonValue(option.value)); + optionValue.set("label", JsonValue(option.label)); + options.pushBack(optionValue); + } + parameter.set("options", options); + } + if (definition.type == ShaderParameterType::Text) + { + parameter.set("maxLength", JsonValue(static_cast(definition.maxLength))); + if (!definition.fontId.empty()) + parameter.set("font", JsonValue(definition.fontId)); + } + + ShaderParameterValue value = DefaultValueForDefinition(definition); + auto valueIt = layer.parameterValues.find(definition.id); + if (valueIt != layer.parameterValues.end()) + value = valueIt->second; + parameter.set("value", SerializeParameterValue(definition, value)); + parameters.pushBack(parameter); + } + + layerValue.set("parameters", parameters); + layers.pushBack(layerValue); + } + return layers; +} + +bool RuntimeStore::DeserializeLayerStackLocked(const JsonValue& layersValue, std::vector& layers, std::string& error) +{ + for (const JsonValue& layerValue : layersValue.asArray()) + { + if (!layerValue.isObject()) + continue; + + const JsonValue* shaderIdValue = layerValue.find("shaderId"); + if (!shaderIdValue) + continue; + + const std::string shaderId = shaderIdValue->asString(); + auto shaderIt = mPackagesById.find(shaderId); + if (shaderIt == mPackagesById.end()) + { + error = "Preset references unknown shader id: " + shaderId; + return false; + } + + LayerPersistentState layer; + layer.id = GenerateLayerId(); + layer.shaderId = shaderId; + if (const JsonValue* bypassValue = layerValue.find("bypass")) + layer.bypass = bypassValue->asBoolean(false); + + if (const JsonValue* parametersValue = layerValue.find("parameters")) + { + for (const JsonValue& parameterValue : parametersValue->asArray()) + { + if (!parameterValue.isObject()) + continue; + + const JsonValue* parameterIdValue = parameterValue.find("id"); + const JsonValue* valueValue = parameterValue.find("value"); + if (!parameterIdValue || !valueValue) + continue; + + const std::string parameterId = parameterIdValue->asString(); + auto definitionIt = std::find_if(shaderIt->second.parameters.begin(), shaderIt->second.parameters.end(), + [¶meterId](const ShaderParameterDefinition& definition) { return definition.id == parameterId; }); + if (definitionIt == shaderIt->second.parameters.end()) + continue; + + ShaderParameterValue normalizedValue; + if (!NormalizeAndValidateValue(*definitionIt, *valueValue, normalizedValue, error)) + return false; + + layer.parameterValues[parameterId] = normalizedValue; + } + } + + EnsureLayerDefaultsLocked(layer, shaderIt->second); + layers.push_back(layer); + } + + return true; +} + +void RuntimeStore::NormalizePersistentLayerIdsLocked() +{ + std::set usedIds; + uint64_t maxLayerNumber = mNextLayerId; + + for (LayerPersistentState& layer : mPersistentState.layers) + { + uint64_t layerNumber = 0; + const bool hasReusableId = !layer.id.empty() && + usedIds.find(layer.id) == usedIds.end() && + TryParseLayerIdNumber(layer.id, layerNumber); + + if (hasReusableId) + { + usedIds.insert(layer.id); + maxLayerNumber = (std::max)(maxLayerNumber, layerNumber); + continue; + } + + do + { + ++maxLayerNumber; + layer.id = "layer-" + std::to_string(maxLayerNumber); + } + while (usedIds.find(layer.id) != usedIds.end()); + + usedIds.insert(layer.id); + } + + mNextLayerId = maxLayerNumber; +} + +JsonValue RuntimeStore::SerializeParameterValue(const ShaderParameterDefinition& definition, const ShaderParameterValue& value) const +{ + switch (definition.type) + { + case ShaderParameterType::Boolean: + return JsonValue(value.booleanValue); + case ShaderParameterType::Enum: + return JsonValue(value.enumValue); + case ShaderParameterType::Text: + return JsonValue(value.textValue); + case ShaderParameterType::Trigger: + return JsonValue(value.numberValues.empty() ? 0.0 : value.numberValues.front()); + case ShaderParameterType::Float: + return JsonValue(value.numberValues.empty() ? 0.0 : value.numberValues.front()); + case ShaderParameterType::Vec2: + case ShaderParameterType::Color: + { + JsonValue array = JsonValue::MakeArray(); + for (double number : value.numberValues) + array.pushBack(JsonValue(number)); + return array; + } + } + return JsonValue(); +} + +std::string RuntimeStore::TemporalHistorySourceToString(TemporalHistorySource source) const +{ + switch (source) + { + case TemporalHistorySource::Source: + return "source"; + case TemporalHistorySource::PreLayerInput: + return "preLayerInput"; + case TemporalHistorySource::None: + default: + return "none"; + } +} + +RuntimeStore::LayerPersistentState* RuntimeStore::FindLayerById(const std::string& layerId) +{ + auto it = std::find_if(mPersistentState.layers.begin(), mPersistentState.layers.end(), + [&layerId](const LayerPersistentState& layer) { return layer.id == layerId; }); + return it == mPersistentState.layers.end() ? nullptr : &*it; +} + +const RuntimeStore::LayerPersistentState* RuntimeStore::FindLayerById(const std::string& layerId) const +{ + auto it = std::find_if(mPersistentState.layers.begin(), mPersistentState.layers.end(), + [&layerId](const LayerPersistentState& layer) { return layer.id == layerId; }); + return it == mPersistentState.layers.end() ? nullptr : &*it; +} + +std::string RuntimeStore::GenerateLayerId() +{ + while (true) + { + ++mNextLayerId; + const std::string candidate = "layer-" + std::to_string(mNextLayerId); + if (!FindLayerById(candidate)) + return candidate; + } +} diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.h b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.h index 1438ea0..6945304 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.h +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.h @@ -1,16 +1,61 @@ #pragma once -#include "RuntimeHost.h" +#include "HealthTelemetry.h" +#include "RuntimeJson.h" +#include "ShaderTypes.h" +#include +#include #include #include +#include +#include #include +#include class RuntimeSnapshotProvider; class RuntimeStore { public: + struct StoredParameterSnapshot + { + std::string layerId; + ShaderParameterDefinition definition; + ShaderParameterValue currentValue; + bool hasCurrentValue = false; + }; + + struct AppConfig + { + std::string shaderLibrary = "shaders"; + unsigned short serverPort = 8080; + unsigned short oscPort = 9000; + std::string oscBindAddress = "127.0.0.1"; + double oscSmoothing = 0.18; + bool autoReload = true; + unsigned maxTemporalHistoryFrames = 4; + unsigned previewFps = 30; + bool enableExternalKeying = false; + std::string inputVideoFormat = "1080p"; + std::string inputFrameRate = "59.94"; + std::string outputVideoFormat = "1080p"; + std::string outputFrameRate = "59.94"; + }; + + struct LayerPersistentState + { + std::string id; + std::string shaderId; + bool bypass = false; + std::map parameterValues; + }; + + struct PersistentState + { + std::vector layers; + }; + RuntimeStore(); HealthTelemetry& GetHealthTelemetry(); const HealthTelemetry& GetHealthTelemetry() const; @@ -29,6 +74,14 @@ public: bool ResetStoredLayerParameterValues(const std::string& layerId, std::string& error); bool SaveStackPresetSnapshot(const std::string& presetName, std::string& error) const; bool LoadStackPresetSnapshot(const std::string& presetName, std::string& error); + bool HasStoredLayer(const std::string& layerId) const; + bool HasStoredShader(const std::string& shaderId) const; + bool TryGetStoredParameterById(const std::string& layerId, const std::string& parameterId, StoredParameterSnapshot& snapshot, std::string& error) const; + bool TryGetStoredParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, StoredParameterSnapshot& snapshot, std::string& error) const; + bool ResolveStoredLayerMove(const std::string& layerId, int direction, bool& shouldMove, std::string& error) const; + bool ResolveStoredLayerMoveToIndex(const std::string& layerId, std::size_t targetIndex, bool& shouldMove, std::string& error) const; + bool IsValidStackPresetName(const std::string& presetName) const; + double GetRuntimeElapsedSeconds() const; const std::filesystem::path& GetRuntimeRepositoryRoot() const; const std::filesystem::path& GetRuntimeUiRoot() const; @@ -51,7 +104,6 @@ public: void ClearReloadRequest(); private: - friend class RuntimeCoordinator; friend class RuntimeSnapshotProvider; bool LoadConfig(std::string& error); bool LoadPersistentState(std::string& error); @@ -63,7 +115,7 @@ private: std::vector GetStackPresetNamesLocked() const; std::string MakeSafePresetFileStem(const std::string& presetName) const; bool TryResolveStoredLayerAndParameterByControlKeyLocked(const std::string& layerKey, const std::string& parameterKey, - RuntimeHost::LayerPersistentState*& matchedLayer, const ShaderPackage*& matchedPackage, + const LayerPersistentState*& matchedLayer, const ShaderPackage*& matchedPackage, std::vector::const_iterator& parameterIt, std::string& error) const; bool CopyShaderPackageForStoredLayer(const std::string& layerId, ShaderPackage& shaderPackage, std::string& error) const; void GetShaderCompilerInputs(std::filesystem::path& repoRoot, std::filesystem::path& wrapperPath, @@ -80,6 +132,48 @@ private: void RefreshDynamicRenderStateFieldsLocked(std::vector& states) const; JsonValue BuildRuntimeStateValue() const; JsonValue SerializeLayerStack() const; + bool NormalizeAndValidateValue(const ShaderParameterDefinition& definition, const JsonValue& value, ShaderParameterValue& normalizedValue, std::string& error) const; + ShaderParameterValue DefaultValueForDefinition(const ShaderParameterDefinition& definition) const; + void EnsureLayerDefaultsLocked(LayerPersistentState& layerState, const ShaderPackage& shaderPackage) const; + JsonValue SerializeLayerStackLocked() const; + bool DeserializeLayerStackLocked(const JsonValue& layersValue, std::vector& layers, std::string& error); + void NormalizePersistentLayerIdsLocked(); + JsonValue SerializeParameterValue(const ShaderParameterDefinition& definition, const ShaderParameterValue& value) const; + std::string TemporalHistorySourceToString(TemporalHistorySource source) const; + LayerPersistentState* FindLayerById(const std::string& layerId); + const LayerPersistentState* FindLayerById(const std::string& layerId) const; + std::string GenerateLayerId(); + void MarkRenderStateDirtyLocked(); + void MarkParameterStateDirtyLocked(); - mutable RuntimeHost mRuntimeHost; + HealthTelemetry mHealthTelemetry; + mutable std::mutex mMutex; + AppConfig mConfig; + PersistentState mPersistentState; + std::filesystem::path mRepoRoot; + std::filesystem::path mUiRoot; + std::filesystem::path mDocsRoot; + std::filesystem::path mShaderRoot; + std::filesystem::path mRuntimeRoot; + std::filesystem::path mPresetRoot; + std::filesystem::path mRuntimeStatePath; + std::filesystem::path mConfigPath; + std::filesystem::path mWrapperPath; + std::filesystem::path mGeneratedGlslPath; + std::filesystem::path mPatchedGlslPath; + std::map mPackagesById; + std::vector mPackageOrder; + std::vector mPackageStatuses; + bool mReloadRequested; + bool mCompileSucceeded; + std::string mCompileMessage; + double mStartupRandom; + unsigned short mServerPort; + bool mAutoReloadEnabled; + std::chrono::steady_clock::time_point mStartTime; + std::chrono::steady_clock::time_point mLastScanTime; + std::atomic mFrameCounter{ 0 }; + std::atomic mRenderStateVersion{ 0 }; + std::atomic mParameterStateVersion{ 0 }; + uint64_t mNextLayerId; }; diff --git a/docs/PHASE_1_SUBSYSTEM_BOUNDARIES_DESIGN.md b/docs/PHASE_1_SUBSYSTEM_BOUNDARIES_DESIGN.md index bb75da4..ca8d4a5 100644 --- a/docs/PHASE_1_SUBSYSTEM_BOUNDARIES_DESIGN.md +++ b/docs/PHASE_1_SUBSYSTEM_BOUNDARIES_DESIGN.md @@ -133,12 +133,16 @@ These are still compatibility seams, not a completed subsystem extraction. Some - render-state and shader-build reads in `OpenGLComposite.cpp`, `OpenGLShaderPrograms.cpp`, and `ShaderBuildQueue.cpp` now route through `RuntimeSnapshotProvider` - `RuntimeSnapshotProvider` now depends on `RuntimeStore` rather than sharing `RuntimeHost` directly - render-state assembly, cached parameter refresh, and frame-context application now flow through `RuntimeSnapshotProvider` and store-owned snapshot helpers instead of `RuntimeHost` public APIs +- `RuntimeSnapshotProvider` now publishes versioned render snapshot objects and serves matching consumers from the last published snapshot - service ingress and polling coordination now route through `ControlServices` - `ControlServices` now queues coordinator results for OSC commit and file-poll outcomes instead of directly deciding runtime/store policy - timing and status writes now route through `HealthTelemetry` - `HealthTelemetry` now owns the live signal, video-I/O, and performance snapshots directly instead of `RuntimeHost` keeping those backing fields - render-side frame advancement and render-performance reporting now flow through `RuntimeSnapshotProvider` and `HealthTelemetry` instead of directly through `RuntimeHost` +- `RuntimeStore` now owns its durable/session backing fields directly instead of wrapping a compatibility `RuntimeHost` object +- `RuntimeCoordinator` now uses explicit `RuntimeStore` query APIs/read models instead of friendship or direct store-internal access - live OSC overlay state and smoothing/commit decisions now live under `RenderEngine` instead of `OpenGLComposite` +- coordinator result application, shader-build requests, ready-build application, and runtime-state broadcasts now route through `RuntimeUpdateController` instead of being interpreted directly by `OpenGLComposite` - `OpenGLComposite` now owns a `RenderEngine` seam for renderer, pipeline, render-pass, and shader-program responsibilities - `OpenGLComposite` now owns a `VideoBackend` seam for device/session ownership and callback wiring - `OpenGLVideoIOBridge` now acts as an explicit compatibility adapter between `VideoBackend` and `RenderEngine`, instead of `OpenGLComposite` directly owning both sides @@ -147,9 +151,6 @@ That means the next extraction work can focus on moving responsibility behind th Remaining extraction work includes: -- replacing the compatibility `RuntimeHost` backing object still owned inside `RuntimeStore` -- removing coordinator friendship/access to store-internal host data -- publishing render snapshots as explicit immutable or near-immutable objects rather than building them from store internals on demand - moving persistence to an asynchronous writer in a later phase - replacing polling/shared-object coordination with the planned internal event model diff --git a/docs/subsystems/RuntimeSnapshotProvider.md b/docs/subsystems/RuntimeSnapshotProvider.md index 56a8ebd..b32e46e 100644 --- a/docs/subsystems/RuntimeSnapshotProvider.md +++ b/docs/subsystems/RuntimeSnapshotProvider.md @@ -481,7 +481,7 @@ Recommended tests: Its contract is: - build from store-owned state -- publish immutable or near-immutable render snapshots +- publish immutable or near-immutable render snapshots; the current implementation keeps the last matching versioned snapshot in `RuntimeSnapshotProvider` - version them explicitly - keep frame-local timing separate - give render a cheap, lock-light read path diff --git a/docs/subsystems/RuntimeStore.md b/docs/subsystems/RuntimeStore.md index 7dbbabe..9f65373 100644 --- a/docs/subsystems/RuntimeStore.md +++ b/docs/subsystems/RuntimeStore.md @@ -405,7 +405,7 @@ Initial likely contents: - preset load/save access - package catalog read access -At this stage, `RuntimeHost` may still be the implementation behind the façade. +This stage is now past the initial compatibility point: `RuntimeStore` owns its durable/session backing fields directly rather than wrapping a `RuntimeHost` object. ### Step 2: Move Pure Persistence Helpers First