#include "LayerStackStore.h" #include "RuntimeParameterUtils.h" #include "RuntimeStateJson.h" #include #include #include #include #include #include namespace { std::string TrimCopy(const std::string& text) { std::size_t start = 0; while (start < text.size() && std::isspace(static_cast(text[start]))) ++start; std::size_t end = text.size(); while (end > start && std::isspace(static_cast(text[end - 1]))) --end; return text.substr(start, end - start); } std::string SimplifyControlKey(const std::string& text) { std::string simplified; for (unsigned char ch : text) { if (std::isalnum(ch)) simplified.push_back(static_cast(std::tolower(ch))); } return simplified; } bool MatchesControlKey(const std::string& candidate, const std::string& key) { return candidate == key || SimplifyControlKey(candidate) == SimplifyControlKey(key); } bool 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; } } bool LayerStackStore::LoadPersistentStateValue(const JsonValue& root) { if (const JsonValue* layersValue = root.find("layers")) { for (const JsonValue& layerValue : layersValue->asArray()) { if (!layerValue.isObject()) continue; LayerPersistentState layer; if (const JsonValue* idValue = layerValue.find("id")) layer.id = idValue->asString(); if (const JsonValue* shaderIdValue = layerValue.find("shaderId")) layer.shaderId = shaderIdValue->asString(); if (const JsonValue* bypassValue = layerValue.find("bypass")) layer.bypass = bypassValue->asBoolean(false); else if (const JsonValue* enabledValue = layerValue.find("enabled")) layer.bypass = !enabledValue->asBoolean(true); if (const JsonValue* parameterValues = layerValue.find("parameterValues")) { for (const auto& parameterItem : parameterValues->asObject()) { ShaderParameterValue value; const JsonValue& jsonValue = parameterItem.second; if (jsonValue.isBoolean()) value.booleanValue = jsonValue.asBoolean(); else if (jsonValue.isString()) value.enumValue = jsonValue.asString(); else if (jsonValue.isNumber()) value.numberValues.push_back(jsonValue.asNumber()); else if (jsonValue.isArray()) value.numberValues = JsonArrayToNumbers(jsonValue); layer.parameterValues[parameterItem.first] = value; } } if (!layer.shaderId.empty()) mLayers.push_back(layer); } } else { std::string activeShaderId; if (const JsonValue* activeShaderValue = root.find("activeShaderId")) activeShaderId = activeShaderValue->asString(); if (!activeShaderId.empty()) { LayerPersistentState layer; layer.id = GenerateLayerId(mLayers, mNextLayerId); layer.shaderId = activeShaderId; layer.bypass = false; if (const JsonValue* valuesByShader = root.find("parameterValuesByShader")) { const JsonValue* shaderValues = valuesByShader->find(activeShaderId); if (shaderValues) { for (const auto& parameterItem : shaderValues->asObject()) { ShaderParameterValue value; const JsonValue& jsonValue = parameterItem.second; if (jsonValue.isBoolean()) value.booleanValue = jsonValue.asBoolean(); else if (jsonValue.isString()) value.enumValue = jsonValue.asString(); else if (jsonValue.isNumber()) value.numberValues.push_back(jsonValue.asNumber()); else if (jsonValue.isArray()) value.numberValues = JsonArrayToNumbers(jsonValue); layer.parameterValues[parameterItem.first] = value; } } } mLayers.push_back(layer); } } return true; } JsonValue LayerStackStore::BuildPersistentStateValue(const ShaderPackageCatalog& shaderCatalog) const { JsonValue root = JsonValue::MakeObject(); JsonValue layers = JsonValue::MakeArray(); for (const LayerPersistentState& layer : mLayers) { JsonValue layerValue = JsonValue::MakeObject(); layerValue.set("id", JsonValue(layer.id)); layerValue.set("shaderId", JsonValue(layer.shaderId)); layerValue.set("bypass", JsonValue(layer.bypass)); JsonValue parameterValues = JsonValue::MakeObject(); const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer.shaderId); for (const auto& parameterItem : layer.parameterValues) { const ShaderParameterDefinition* definition = nullptr; if (shaderPackage) { for (const ShaderParameterDefinition& candidate : shaderPackage->parameters) { if (candidate.id == parameterItem.first) { definition = &candidate; break; } } } if (definition) parameterValues.set(parameterItem.first, RuntimeStateJson::SerializeParameterValue(*definition, parameterItem.second)); } layerValue.set("parameterValues", parameterValues); layers.pushBack(layerValue); } root.set("layers", layers); return root; } void LayerStackStore::NormalizeLayerIds() { std::set usedIds; uint64_t maxLayerNumber = mNextLayerId; for (LayerPersistentState& layer : mLayers) { 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; } void LayerStackStore::EnsureDefaultsForAllLayers(const ShaderPackageCatalog& shaderCatalog) { for (LayerPersistentState& layer : mLayers) { const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer.shaderId); if (shaderPackage) EnsureLayerDefaults(layer, *shaderPackage); } } void LayerStackStore::EnsureDefaultLayer(const ShaderPackageCatalog& shaderCatalog) { if (!mLayers.empty() || shaderCatalog.PackageOrder().empty()) return; LayerPersistentState layer; layer.id = GenerateLayerId(mLayers, mNextLayerId); layer.shaderId = shaderCatalog.PackageOrder().front(); layer.bypass = false; if (const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer.shaderId)) EnsureLayerDefaults(layer, *shaderPackage); mLayers.push_back(layer); } void LayerStackStore::RemoveLayersWithMissingPackages(const ShaderPackageCatalog& shaderCatalog) { for (auto it = mLayers.begin(); it != mLayers.end();) { if (!shaderCatalog.HasPackage(it->shaderId)) it = mLayers.erase(it); else ++it; } } bool LayerStackStore::CreateLayer(const ShaderPackageCatalog& shaderCatalog, const std::string& shaderId, std::string& error) { const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(shaderId); if (!shaderPackage) { error = "Unknown shader id: " + shaderId; return false; } LayerPersistentState layer; layer.id = GenerateLayerId(mLayers, mNextLayerId); layer.shaderId = shaderId; layer.bypass = false; EnsureLayerDefaults(layer, *shaderPackage); mLayers.push_back(layer); return true; } bool LayerStackStore::DeleteLayer(const std::string& layerId, std::string& error) { auto it = std::find_if(mLayers.begin(), mLayers.end(), [&layerId](const LayerPersistentState& layer) { return layer.id == layerId; }); if (it == mLayers.end()) { error = "Unknown layer id: " + layerId; return false; } mLayers.erase(it); return true; } bool LayerStackStore::MoveLayer(const std::string& layerId, int direction, std::string& error) { auto it = std::find_if(mLayers.begin(), mLayers.end(), [&layerId](const LayerPersistentState& layer) { return layer.id == layerId; }); if (it == mLayers.end()) { error = "Unknown layer id: " + layerId; return false; } const std::ptrdiff_t index = std::distance(mLayers.begin(), it); const std::ptrdiff_t newIndex = index + direction; if (newIndex < 0 || newIndex >= static_cast(mLayers.size())) return true; std::swap(mLayers[index], mLayers[newIndex]); return true; } bool LayerStackStore::MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error) { auto it = std::find_if(mLayers.begin(), mLayers.end(), [&layerId](const LayerPersistentState& layer) { return layer.id == layerId; }); if (it == mLayers.end()) { error = "Unknown layer id: " + layerId; return false; } if (mLayers.empty()) return true; if (targetIndex >= mLayers.size()) targetIndex = mLayers.size() - 1; const std::size_t sourceIndex = static_cast(std::distance(mLayers.begin(), it)); if (sourceIndex == targetIndex) return true; LayerPersistentState movedLayer = *it; mLayers.erase(mLayers.begin() + static_cast(sourceIndex)); mLayers.insert(mLayers.begin() + static_cast(targetIndex), movedLayer); return true; } bool LayerStackStore::SetLayerBypassState(const std::string& layerId, bool bypassed, std::string& error) { LayerPersistentState* layer = FindLayerById(layerId); if (!layer) { error = "Unknown layer id: " + layerId; return false; } layer->bypass = bypassed; return true; } bool LayerStackStore::SetLayerShaderSelection(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, const std::string& shaderId, std::string& error) { LayerPersistentState* layer = FindLayerById(layerId); if (!layer) { error = "Unknown layer id: " + layerId; return false; } const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(shaderId); if (!shaderPackage) { error = "Unknown shader id: " + shaderId; return false; } layer->shaderId = shaderId; layer->parameterValues.clear(); EnsureLayerDefaults(*layer, *shaderPackage); return true; } bool LayerStackStore::SetParameterValue(const std::string& layerId, const std::string& parameterId, const ShaderParameterValue& value, std::string& error) { LayerPersistentState* layer = FindLayerById(layerId); if (!layer) { error = "Unknown layer id: " + layerId; return false; } layer->parameterValues[parameterId] = value; return true; } bool LayerStackStore::ResetLayerParameterValues(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, std::string& error) { LayerPersistentState* layer = FindLayerById(layerId); if (!layer) { error = "Unknown layer id: " + layerId; return false; } const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer->shaderId); if (!shaderPackage) { error = "Unknown shader id: " + layer->shaderId; return false; } layer->parameterValues.clear(); EnsureLayerDefaults(*layer, *shaderPackage); return true; } bool LayerStackStore::HasLayer(const std::string& layerId) const { return FindLayerById(layerId) != nullptr; } bool LayerStackStore::TryGetParameterById(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, const std::string& parameterId, StoredParameterSnapshot& snapshot, std::string& error) const { const LayerPersistentState* layer = FindLayerById(layerId); if (!layer) { error = "Unknown layer id: " + layerId; return false; } const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer->shaderId); if (!shaderPackage) { error = "Unknown shader id: " + layer->shaderId; return false; } auto parameterIt = std::find_if(shaderPackage->parameters.begin(), shaderPackage->parameters.end(), [¶meterId](const ShaderParameterDefinition& definition) { return definition.id == parameterId; }); if (parameterIt == shaderPackage->parameters.end()) { error = "Unknown parameter id: " + parameterId; return false; } 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 LayerStackStore::TryGetParameterByControlKey(const ShaderPackageCatalog& shaderCatalog, const std::string& layerKey, const std::string& parameterKey, StoredParameterSnapshot& snapshot, std::string& error) const { const LayerPersistentState* matchedLayer = nullptr; const ShaderPackage* matchedPackage = nullptr; for (const LayerPersistentState& layer : mLayers) { const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer.shaderId); if (!shaderPackage) continue; if (MatchesControlKey(layer.id, layerKey) || MatchesControlKey(shaderPackage->id, layerKey) || MatchesControlKey(shaderPackage->displayName, layerKey)) { matchedLayer = &layer; matchedPackage = shaderPackage; break; } } if (!matchedLayer || !matchedPackage) { error = "Unknown OSC layer key: " + layerKey; return false; } auto parameterIt = std::find_if(matchedPackage->parameters.begin(), matchedPackage->parameters.end(), [¶meterKey](const ShaderParameterDefinition& definition) { return MatchesControlKey(definition.id, parameterKey) || MatchesControlKey(definition.label, parameterKey); }); if (parameterIt == matchedPackage->parameters.end()) { error = "Unknown OSC parameter key: " + parameterKey; return false; } snapshot = StoredParameterSnapshot(); snapshot.layerId = matchedLayer->id; snapshot.definition = *parameterIt; auto valueIt = matchedLayer->parameterValues.find(parameterIt->id); if (valueIt != matchedLayer->parameterValues.end()) { snapshot.currentValue = valueIt->second; snapshot.hasCurrentValue = true; } return true; } bool LayerStackStore::ResolveLayerMove(const std::string& layerId, int direction, bool& shouldMove, std::string& error) const { auto it = std::find_if(mLayers.begin(), mLayers.end(), [&layerId](const LayerPersistentState& layer) { return layer.id == layerId; }); if (it == mLayers.end()) { error = "Unknown layer id: " + layerId; return false; } const std::ptrdiff_t index = std::distance(mLayers.begin(), it); const std::ptrdiff_t newIndex = index + direction; shouldMove = newIndex >= 0 && newIndex < static_cast(mLayers.size()) && newIndex != index; return true; } bool LayerStackStore::ResolveLayerMoveToIndex(const std::string& layerId, std::size_t targetIndex, bool& shouldMove, std::string& error) const { auto it = std::find_if(mLayers.begin(), mLayers.end(), [&layerId](const LayerPersistentState& layer) { return layer.id == layerId; }); if (it == mLayers.end()) { error = "Unknown layer id: " + layerId; return false; } if (mLayers.empty()) { shouldMove = false; return true; } const std::size_t clampedTargetIndex = (std::min)(targetIndex, mLayers.size() - 1); const std::size_t sourceIndex = static_cast(std::distance(mLayers.begin(), it)); shouldMove = sourceIndex != clampedTargetIndex; return true; } JsonValue LayerStackStore::BuildStackPresetValue(const ShaderPackageCatalog& shaderCatalog, const std::string& presetName) const { JsonValue root = JsonValue::MakeObject(); root.set("version", JsonValue(1.0)); root.set("name", JsonValue(TrimCopy(presetName))); root.set("layers", RuntimeStateJson::SerializeLayerStack(*this, shaderCatalog)); return root; } bool LayerStackStore::LoadStackPresetValue(const ShaderPackageCatalog& shaderCatalog, const JsonValue& root, std::string& error) { const JsonValue* layersValue = root.find("layers"); if (!layersValue || !layersValue->isArray()) { error = "Preset file is missing a valid 'layers' array."; return false; } std::vector nextLayers; uint64_t nextLayerId = mNextLayerId; if (!DeserializeLayerStack(shaderCatalog, *layersValue, nextLayers, nextLayerId, error)) return false; if (nextLayers.empty()) { error = "Preset does not contain any valid layers."; return false; } mLayers = std::move(nextLayers); mNextLayerId = nextLayerId; return true; } std::string LayerStackStore::MakeSafePresetFileStem(const std::string& presetName) { return ::MakeSafePresetFileStem(presetName); } const std::vector& LayerStackStore::Layers() const { return mLayers; } std::vector& LayerStackStore::Layers() { return mLayers; } std::size_t LayerStackStore::LayerCount() const { return mLayers.size(); } const LayerStackStore::LayerPersistentState* LayerStackStore::FindLayerById(const std::string& layerId) const { auto it = std::find_if(mLayers.begin(), mLayers.end(), [&layerId](const LayerPersistentState& layer) { return layer.id == layerId; }); return it == mLayers.end() ? nullptr : &*it; } LayerStackStore::LayerPersistentState* LayerStackStore::FindLayerById(const std::string& layerId) { auto it = std::find_if(mLayers.begin(), mLayers.end(), [&layerId](const LayerPersistentState& layer) { return layer.id == layerId; }); return it == mLayers.end() ? nullptr : &*it; } ShaderParameterValue LayerStackStore::DefaultValueForDefinition(const ShaderParameterDefinition& definition) { return ::DefaultValueForDefinition(definition); } void LayerStackStore::EnsureLayerDefaults(LayerPersistentState& layerState, const ShaderPackage& shaderPackage) { 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 (NormalizeAndValidateParameterValue(definition, valueJson, normalizedValue, normalizeError)) valueIt->second = normalizedValue; else valueIt->second = DefaultValueForDefinition(definition); } } bool LayerStackStore::DeserializeLayerStack(const ShaderPackageCatalog& shaderCatalog, const JsonValue& layersValue, std::vector& layers, uint64_t& nextLayerId, 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(); const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(shaderId); if (!shaderPackage) { error = "Preset references unknown shader id: " + shaderId; return false; } LayerPersistentState layer; layer.id = GenerateLayerId(layers, nextLayerId); 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(shaderPackage->parameters.begin(), shaderPackage->parameters.end(), [¶meterId](const ShaderParameterDefinition& definition) { return definition.id == parameterId; }); if (definitionIt == shaderPackage->parameters.end()) continue; ShaderParameterValue normalizedValue; if (!NormalizeAndValidateParameterValue(*definitionIt, *valueValue, normalizedValue, error)) return false; layer.parameterValues[parameterId] = normalizedValue; } } EnsureLayerDefaults(layer, *shaderPackage); layers.push_back(layer); } return true; } std::string LayerStackStore::GenerateLayerId(std::vector& layers, uint64_t& nextLayerId) { while (true) { ++nextLayerId; const std::string candidate = "layer-" + std::to_string(nextLayerId); auto it = std::find_if(layers.begin(), layers.end(), [&candidate](const LayerPersistentState& layer) { return layer.id == candidate; }); if (it == layers.end()) return candidate; } }