Pass 3
Some checks failed
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m48s
CI / Windows Release Package (push) Has been cancelled

This commit is contained in:
Aiden
2026-05-11 02:06:17 +10:00
parent b2369c418b
commit 5cbdbd6813
22 changed files with 2069 additions and 1504 deletions

View File

@@ -0,0 +1,738 @@
#include "LayerStackStore.h"
#include "RuntimeParameterUtils.h"
#include "RuntimeStateJson.h"
#include <algorithm>
#include <cctype>
#include <cmath>
#include <set>
#include <sstream>
#include <utility>
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);
}
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<unsigned char>(layerId[index]);
if (!std::isdigit(ch))
return false;
parsed = parsed * 10 + static_cast<uint64_t>(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<std::string> 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<std::ptrdiff_t>(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::size_t>(std::distance(mLayers.begin(), it));
if (sourceIndex == targetIndex)
return true;
LayerPersistentState movedLayer = *it;
mLayers.erase(mLayers.begin() + static_cast<std::ptrdiff_t>(sourceIndex));
mLayers.insert(mLayers.begin() + static_cast<std::ptrdiff_t>(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(),
[&parameterId](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(),
[&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;
}
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<std::ptrdiff_t>(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::size_t>(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<LayerPersistentState> 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::LayerPersistentState>& LayerStackStore::Layers() const
{
return mLayers;
}
std::vector<LayerStackStore::LayerPersistentState>& 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<LayerPersistentState>& 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(),
[&parameterId](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<LayerPersistentState>& 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;
}
}