From bda9a9dc22d53bd9b933a407d015f0f77e4fcf87 Mon Sep 17 00:00:00 2001 From: Aiden Date: Thu, 21 May 2026 16:08:46 +1000 Subject: [PATCH] runtime read of json layer state --- src/app/RuntimeLayerController.cpp | 24 +++- src/app/RuntimeLayerController.h | 1 + src/app/RuntimeLayerControllerBuild.cpp | 43 ++++++ src/runtime/RuntimeLayerModel.cpp | 128 ++++++++++++++++++ src/runtime/RuntimeLayerModel.h | 2 + ...adenceCompositorRuntimeLayerModelTests.cpp | 52 +++++++ 6 files changed, 245 insertions(+), 5 deletions(-) diff --git a/src/app/RuntimeLayerController.cpp b/src/app/RuntimeLayerController.cpp index 6927ba8..178a471 100644 --- a/src/app/RuntimeLayerController.cpp +++ b/src/app/RuntimeLayerController.cpp @@ -29,16 +29,30 @@ void RuntimeLayerController::Initialize(const std::string& shaderLibrary, unsign void RuntimeLayerController::StartStartupBuild(const std::string& runtimeShaderId) { - if (runtimeShaderId.empty()) + std::vector> buildsToStart; + { + std::lock_guard lock(mRuntimeLayerMutex); + buildsToStart = mRuntimeLayerModel.PendingLayerBuilds(); + } + + if (buildsToStart.empty() && runtimeShaderId.empty()) { Log("runtime-shader", "Runtime shader build disabled."); return; } - Log("runtime-shader", "Starting background Slang build for shader '" + runtimeShaderId + "'."); - const std::string layerId = FirstRuntimeLayerId(); - if (!layerId.empty()) - StartLayerShaderBuild(layerId, runtimeShaderId); + if (buildsToStart.empty()) + { + const std::string layerId = FirstRuntimeLayerId(); + if (!layerId.empty()) + buildsToStart.push_back({ layerId, runtimeShaderId }); + } + + for (const auto& build : buildsToStart) + { + Log("runtime-shader", "Starting background Slang build for layer '" + build.first + "' shader '" + build.second + "'."); + StartLayerShaderBuild(build.first, build.second); + } } void RuntimeLayerController::Stop() diff --git a/src/app/RuntimeLayerController.h b/src/app/RuntimeLayerController.h index b95101f..fd03fb8 100644 --- a/src/app/RuntimeLayerController.h +++ b/src/app/RuntimeLayerController.h @@ -41,6 +41,7 @@ public: private: bool LoadSupportedShaderCatalog(const std::string& shaderLibrary, unsigned maxTemporalHistoryFrames); void InitializeLayerModel(std::string& runtimeShaderId); + bool InitializeLayerModelFromRuntimeState(); void StartLayerShaderBuild(const std::string& layerId, const std::string& shaderId); void RetireLayerShaderBuild(const std::string& layerId); void CleanupRetiredShaderBuilds(); diff --git a/src/app/RuntimeLayerControllerBuild.cpp b/src/app/RuntimeLayerControllerBuild.cpp index 68acc29..52be42a 100644 --- a/src/app/RuntimeLayerControllerBuild.cpp +++ b/src/app/RuntimeLayerControllerBuild.cpp @@ -1,9 +1,12 @@ #include "RuntimeLayerController.h" #include "AppConfigProvider.h" +#include "RuntimeJson.h" #include "../logging/Logger.h" #include +#include +#include namespace RenderCadenceCompositor { @@ -30,6 +33,9 @@ bool RuntimeLayerController::LoadSupportedShaderCatalog(const std::string& shade void RuntimeLayerController::InitializeLayerModel(std::string& runtimeShaderId) { + if (InitializeLayerModelFromRuntimeState()) + return; + std::lock_guard lock(mRuntimeLayerMutex); std::string error; if (!mRuntimeLayerModel.InitializeSingleLayer(mShaderCatalog, runtimeShaderId, error)) @@ -40,6 +46,43 @@ void RuntimeLayerController::InitializeLayerModel(std::string& runtimeShaderId) } } +bool RuntimeLayerController::InitializeLayerModelFromRuntimeState() +{ + const std::filesystem::path runtimeStatePath = FindRepoPath("runtime/runtime_state.json"); + if (runtimeStatePath.empty()) + return false; + + std::ifstream input(runtimeStatePath, std::ios::binary); + if (!input) + { + LogWarning("runtime-state", "Could not open runtime state file: " + runtimeStatePath.string()); + return false; + } + + std::ostringstream buffer; + buffer << input.rdbuf(); + + JsonValue runtimeState; + std::string error; + if (!ParseJson(buffer.str(), runtimeState, error)) + { + LogWarning("runtime-state", "Could not parse runtime state file: " + error); + return false; + } + + { + std::lock_guard lock(mRuntimeLayerMutex); + if (!mRuntimeLayerModel.InitializeFromRuntimeState(mShaderCatalog, runtimeState, error)) + { + LogWarning("runtime-state", "Could not restore runtime state: " + error); + return false; + } + } + + Log("runtime-state", "Restored runtime layer stack from " + runtimeStatePath.string() + "."); + return true; +} + void RuntimeLayerController::StartLayerShaderBuild(const std::string& layerId, const std::string& shaderId) { CleanupRetiredShaderBuilds(); diff --git a/src/runtime/RuntimeLayerModel.cpp b/src/runtime/RuntimeLayerModel.cpp index ef5041b..1a4a50a 100644 --- a/src/runtime/RuntimeLayerModel.cpp +++ b/src/runtime/RuntimeLayerModel.cpp @@ -4,6 +4,8 @@ #include #include +#include +#include #include namespace RenderCadenceCompositor @@ -35,6 +37,38 @@ JsonValue ParameterValueToJson(const ShaderParameterDefinition& definition, cons } return JsonValue(); } + +bool ParseRuntimeLayerNumber(const std::string& layerId, uint64_t& number) +{ + const std::string prefix = "runtime-layer-"; + if (layerId.compare(0, prefix.size(), prefix) != 0) + return false; + + const std::string suffix = layerId.substr(prefix.size()); + if (suffix.empty()) + return false; + + uint64_t parsed = 0; + for (char character : suffix) + { + if (!std::isdigit(static_cast(character))) + return false; + parsed = parsed * 10 + static_cast(character - '0'); + } + + number = parsed; + return true; +} + +std::string AllocateRestoredLayerId(std::set& usedLayerIds, uint64_t& nextLayerNumber) +{ + for (;;) + { + std::string candidate = "runtime-layer-" + std::to_string(nextLayerNumber++); + if (usedLayerIds.insert(candidate).second) + return candidate; + } +} } bool RuntimeLayerModel::InitializeSingleLayer(const SupportedShaderCatalog& shaderCatalog, const std::string& shaderId, std::string& error) @@ -227,6 +261,89 @@ bool RuntimeLayerModel::ResetParameters(const std::string& layerId, std::string& return true; } +bool RuntimeLayerModel::InitializeFromRuntimeState(const SupportedShaderCatalog& shaderCatalog, const JsonValue& runtimeState, std::string& error) +{ + if (!runtimeState.isObject()) + { + error = "Runtime state root must be a JSON object."; + return false; + } + + const JsonValue* layersValue = runtimeState.find("layers"); + if (!layersValue || !layersValue->isArray()) + { + error = "Runtime state must contain a layers array."; + return false; + } + + std::vector restoredLayers; + std::set usedLayerIds; + uint64_t nextLayerNumber = 1; + + for (const JsonValue& layerValue : layersValue->asArray()) + { + if (!layerValue.isObject()) + continue; + + const JsonValue* shaderIdValue = layerValue.find("shaderId"); + if (!shaderIdValue || !shaderIdValue->isString() || shaderIdValue->asString().empty()) + continue; + + const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(shaderIdValue->asString()); + if (!shaderPackage) + continue; + + Layer layer; + const JsonValue* layerIdValue = layerValue.find("id"); + if (layerIdValue && layerIdValue->isString() && !layerIdValue->asString().empty() && usedLayerIds.insert(layerIdValue->asString()).second) + layer.id = layerIdValue->asString(); + else + layer.id = AllocateRestoredLayerId(usedLayerIds, nextLayerNumber); + + uint64_t restoredLayerNumber = 0; + if (ParseRuntimeLayerNumber(layer.id, restoredLayerNumber) && restoredLayerNumber >= nextLayerNumber) + nextLayerNumber = restoredLayerNumber + 1; + + layer.shaderId = shaderPackage->id; + layer.packageFingerprint = ShaderPackageFingerprint(*shaderPackage); + layer.shaderName = shaderPackage->displayName.empty() ? shaderPackage->id : shaderPackage->displayName; + const JsonValue* bypassValue = layerValue.find("bypass"); + layer.bypass = bypassValue && bypassValue->isBoolean() ? bypassValue->asBoolean() : false; + layer.buildState = RuntimeLayerBuildState::Pending; + layer.message = "Runtime Slang build is waiting to start."; + InitializeDefaultParameterValues(layer, *shaderPackage); + + const JsonValue* parameterValues = layerValue.find("parameterValues"); + if (parameterValues && parameterValues->isObject()) + { + for (const ShaderParameterDefinition& definition : layer.parameterDefinitions) + { + const JsonValue* value = parameterValues->find(definition.id); + if (!value) + continue; + + ShaderParameterValue normalizedValue; + std::string normalizeError; + if (NormalizeAndValidateParameterValue(definition, *value, normalizedValue, normalizeError)) + layer.parameterValues[definition.id] = normalizedValue; + } + } + + restoredLayers.push_back(std::move(layer)); + } + + if (restoredLayers.empty()) + { + error = "Runtime state did not contain any supported layers."; + return false; + } + + mLayers = std::move(restoredLayers); + mNextLayerNumber = nextLayerNumber; + error.clear(); + return true; +} + bool RuntimeLayerModel::ReloadFromCatalog(const SupportedShaderCatalog& shaderCatalog, std::vector>& buildsToStart, std::string& error) { buildsToStart.clear(); @@ -392,6 +509,17 @@ std::string RuntimeLayerModel::FirstLayerId() const return mLayers.empty() ? std::string() : mLayers.front().id; } +std::vector> RuntimeLayerModel::PendingLayerBuilds() const +{ + std::vector> builds; + for (const Layer& layer : mLayers) + { + if (layer.buildState == RuntimeLayerBuildState::Pending) + builds.push_back({ layer.id, layer.shaderId }); + } + return builds; +} + RuntimeLayerModel::Layer* RuntimeLayerModel::FindLayer(const std::string& layerId) { for (Layer& layer : mLayers) diff --git a/src/runtime/RuntimeLayerModel.h b/src/runtime/RuntimeLayerModel.h index 463ea83..22027d7 100644 --- a/src/runtime/RuntimeLayerModel.h +++ b/src/runtime/RuntimeLayerModel.h @@ -53,6 +53,7 @@ class RuntimeLayerModel { public: bool InitializeSingleLayer(const SupportedShaderCatalog& shaderCatalog, const std::string& shaderId, std::string& error); + bool InitializeFromRuntimeState(const SupportedShaderCatalog& shaderCatalog, const JsonValue& runtimeState, std::string& error); void Clear(); bool AddLayer(const SupportedShaderCatalog& shaderCatalog, const std::string& shaderId, std::string& layerId, std::string& error); @@ -71,6 +72,7 @@ public: RuntimeLayerModelSnapshot Snapshot() const; std::string FirstLayerId() const; + std::vector> PendingLayerBuilds() const; private: struct Layer diff --git a/tests/RenderCadenceCompositorRuntimeLayerModelTests.cpp b/tests/RenderCadenceCompositorRuntimeLayerModelTests.cpp index 0253cba..8ead531 100644 --- a/tests/RenderCadenceCompositorRuntimeLayerModelTests.cpp +++ b/tests/RenderCadenceCompositorRuntimeLayerModelTests.cpp @@ -159,6 +159,57 @@ void TestAddAndRemoveLayers() std::filesystem::remove_all(root); } +void TestInitializeFromRuntimeStateRestoresLayerStack() +{ + std::filesystem::path root; + RenderCadenceCompositor::SupportedShaderCatalog catalog = MakeCatalog(root); + + JsonValue runtimeState; + std::string error; + Expect(ParseJson(R"({ + "layers": [ + { + "id": "layer-31", + "shaderId": "solid", + "bypass": true, + "parameterValues": { + "gain": 0.75, + "drop": 4 + } + }, + { + "id": "layer-32", + "shaderId": "missing", + "parameterValues": { + "gain": 0.9 + } + }, + { + "id": "layer-33", + "shaderId": "solid", + "parameterValues": { + "gain": "bad" + } + } + ] + })", runtimeState, error), "runtime state fixture parses"); + + RenderCadenceCompositor::RuntimeLayerModel model; + Expect(model.InitializeFromRuntimeState(catalog, runtimeState, error), "runtime state can initialize the layer model"); + RenderCadenceCompositor::RuntimeLayerModelSnapshot snapshot = model.Snapshot(); + Expect(snapshot.displayLayers.size() == 2, "restore keeps supported layers and skips missing shaders"); + Expect(snapshot.displayLayers[0].id == "layer-31", "restore preserves saved layer id"); + Expect(snapshot.displayLayers[0].bypass, "restore preserves bypass state"); + Expect(snapshot.displayLayers[0].parameterValues.at("gain").numberValues.front() == 0.75, "restore preserves valid parameter values"); + Expect(snapshot.displayLayers[1].id == "layer-33", "restore preserves later supported layer order"); + Expect(snapshot.displayLayers[1].parameterValues.at("gain").numberValues.front() == 0.5, "restore falls back to defaults for invalid parameter values"); + + const std::vector> builds = model.PendingLayerBuilds(); + Expect(builds.size() == 2 && builds[0].first == "layer-31" && builds[1].first == "layer-33", "restore queues startup builds for every restored layer"); + + std::filesystem::remove_all(root); +} + void TestLayerControlsUpdateDisplayAndRenderModels() { std::filesystem::path root; @@ -243,6 +294,7 @@ int main() TestRejectsUnsupportedStartupShader(); TestBuildFailureStaysDisplaySide(); TestAddAndRemoveLayers(); + TestInitializeFromRuntimeStateRestoresLayerStack(); TestLayerControlsUpdateDisplayAndRenderModels(); TestReloadRefreshesChangedShaderMetadataAndPreservesValues();