Files
video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.cpp
Aiden 27dbb55f7b
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m36s
CI / Windows Release Package (push) Successful in 2m48s
OSC seperation
2026-05-11 00:26:59 +10:00

636 lines
23 KiB
C++

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