OSC seperation
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

This commit is contained in:
Aiden
2026-05-11 00:26:59 +10:00
parent f6b26bf28b
commit 27dbb55f7b
10 changed files with 106 additions and 214 deletions

View File

@@ -4,6 +4,7 @@
#include "OscServer.h" #include "OscServer.h"
#include "RuntimeControlBridge.h" #include "RuntimeControlBridge.h"
#include "RuntimeHost.h" #include "RuntimeHost.h"
#include "RuntimeStore.h"
#include <windows.h> #include <windows.h>
ControlServices::ControlServices() : ControlServices::ControlServices() :
@@ -34,9 +35,9 @@ bool ControlServices::Start(OpenGLComposite& composite, RuntimeHost& runtimeHost
return true; return true;
} }
void ControlServices::BeginPolling(RuntimeHost& runtimeHost) void ControlServices::BeginPolling(RuntimeHost& runtimeHost, RuntimeStore& runtimeStore)
{ {
StartPolling(runtimeHost); StartPolling(runtimeHost, runtimeStore);
} }
void ControlServices::Stop() void ControlServices::Stop()
@@ -174,12 +175,12 @@ RuntimePollEvents ControlServices::ConsumePollEvents()
return events; return events;
} }
void ControlServices::StartPolling(RuntimeHost& runtimeHost) void ControlServices::StartPolling(RuntimeHost& runtimeHost, RuntimeStore& runtimeStore)
{ {
if (mPollRunning.exchange(true)) if (mPollRunning.exchange(true))
return; return;
mPollThread = std::thread([this, &runtimeHost]() { PollLoop(runtimeHost); }); mPollThread = std::thread([this, &runtimeHost, &runtimeStore]() { PollLoop(runtimeHost, runtimeStore); });
} }
void ControlServices::StopPolling() void ControlServices::StopPolling()
@@ -191,7 +192,7 @@ void ControlServices::StopPolling()
mPollThread.join(); mPollThread.join();
} }
void ControlServices::PollLoop(RuntimeHost& runtimeHost) void ControlServices::PollLoop(RuntimeHost& runtimeHost, RuntimeStore& runtimeStore)
{ {
while (mPollRunning) while (mPollRunning)
{ {
@@ -203,7 +204,7 @@ void ControlServices::PollLoop(RuntimeHost& runtimeHost)
for (const auto& entry : pendingCommits) for (const auto& entry : pendingCommits)
{ {
std::string commitError; std::string commitError;
if (runtimeHost.UpdateLayerParameterByControlKey( if (runtimeStore.SetStoredParameterValueByControlKey(
entry.second.layerKey, entry.second.layerKey,
entry.second.parameterKey, entry.second.parameterKey,
entry.second.value, entry.second.value,

View File

@@ -15,6 +15,7 @@ class ControlServer;
class OpenGLComposite; class OpenGLComposite;
class OscServer; class OscServer;
class RuntimeHost; class RuntimeHost;
class RuntimeStore;
struct RuntimePollEvents struct RuntimePollEvents
{ {
@@ -45,7 +46,7 @@ public:
~ControlServices(); ~ControlServices();
bool Start(OpenGLComposite& composite, RuntimeHost& runtimeHost, std::string& error); bool Start(OpenGLComposite& composite, RuntimeHost& runtimeHost, std::string& error);
void BeginPolling(RuntimeHost& runtimeHost); void BeginPolling(RuntimeHost& runtimeHost, RuntimeStore& runtimeStore);
void Stop(); void Stop();
void BroadcastState(); void BroadcastState();
void RequestBroadcastState(); void RequestBroadcastState();
@@ -73,9 +74,9 @@ private:
uint64_t generation = 0; uint64_t generation = 0;
}; };
void StartPolling(RuntimeHost& runtimeHost); void StartPolling(RuntimeHost& runtimeHost, RuntimeStore& runtimeStore);
void StopPolling(); void StopPolling();
void PollLoop(RuntimeHost& runtimeHost); void PollLoop(RuntimeHost& runtimeHost, RuntimeStore& runtimeStore);
std::unique_ptr<ControlServer> mControlServer; std::unique_ptr<ControlServer> mControlServer;
std::unique_ptr<OscServer> mOscServer; std::unique_ptr<OscServer> mOscServer;

View File

@@ -1,5 +1,7 @@
#include "RuntimeServices.h" #include "RuntimeServices.h"
#include "RuntimeStore.h"
RuntimeServices::RuntimeServices() : RuntimeServices::RuntimeServices() :
mControlServices(std::make_unique<ControlServices>()) mControlServices(std::make_unique<ControlServices>())
{ {
@@ -15,10 +17,10 @@ bool RuntimeServices::Start(OpenGLComposite& composite, RuntimeHost& runtimeHost
return mControlServices && mControlServices->Start(composite, runtimeHost, error); return mControlServices && mControlServices->Start(composite, runtimeHost, error);
} }
void RuntimeServices::BeginPolling(RuntimeHost& runtimeHost) void RuntimeServices::BeginPolling(RuntimeHost& runtimeHost, RuntimeStore& runtimeStore)
{ {
if (mControlServices) if (mControlServices)
mControlServices->BeginPolling(runtimeHost); mControlServices->BeginPolling(runtimeHost, runtimeStore);
} }
void RuntimeServices::Stop() void RuntimeServices::Stop()

View File

@@ -6,6 +6,7 @@
#include <string> #include <string>
class OpenGLComposite; class OpenGLComposite;
class RuntimeHost; class RuntimeHost;
class RuntimeStore;
class RuntimeServices class RuntimeServices
{ {
@@ -17,7 +18,7 @@ public:
~RuntimeServices(); ~RuntimeServices();
bool Start(OpenGLComposite& composite, RuntimeHost& runtimeHost, std::string& error); bool Start(OpenGLComposite& composite, RuntimeHost& runtimeHost, std::string& error);
void BeginPolling(RuntimeHost& runtimeHost); void BeginPolling(RuntimeHost& runtimeHost, RuntimeStore& runtimeStore);
void Stop(); void Stop();
void BroadcastState(); void BroadcastState();
void RequestBroadcastState(); void RequestBroadcastState();

View File

@@ -270,7 +270,7 @@ bool OpenGLComposite::InitOpenGLState()
mRenderEngine->ResetShaderFeedbackState(); mRenderEngine->ResetShaderFeedbackState();
broadcastRuntimeState(); broadcastRuntimeState();
mRuntimeServices->BeginPolling(*mRuntimeHost); mRuntimeServices->BeginPolling(*mRuntimeHost, *mRuntimeStore);
return true; return true;
} }

View File

@@ -825,201 +825,6 @@ void RuntimeHost::ClearReloadRequest()
mReloadRequested = false; mReloadRequested = false;
} }
bool RuntimeHost::UpdateLayerParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue, std::string& error)
{
return UpdateLayerParameterByControlKey(layerKey, parameterKey, newValue, true, error);
}
bool RuntimeHost::UpdateLayerParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue, bool persistState, std::string& error)
{
std::lock_guard<std::mutex> lock(mMutex);
LayerPersistentState* matchedLayer = nullptr;
const ShaderPackage* matchedPackage = nullptr;
for (LayerPersistentState& layer : mPersistentState.layers)
{
auto shaderIt = mPackagesById.find(layer.shaderId);
if (shaderIt == 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;
}
const 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;
}
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() - mStartTime).count();
value.numberValues = { previousCount + 1.0, triggerTime };
MarkParameterStateDirtyLocked();
return true;
}
ShaderParameterValue normalized;
if (!NormalizeAndValidateValue(*parameterIt, newValue, normalized, error))
return false;
matchedLayer->parameterValues[parameterIt->id] = normalized;
MarkParameterStateDirtyLocked();
return !persistState || SavePersistentState(error);
}
bool RuntimeHost::ApplyOscTargetByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& targetValue, double smoothingAmount, bool& keepApplying, std::string& resolvedLayerId, std::string& resolvedParameterId, ShaderParameterValue& appliedValue, std::string& error)
{
keepApplying = false;
resolvedLayerId.clear();
resolvedParameterId.clear();
appliedValue = ShaderParameterValue();
std::lock_guard<std::mutex> lock(mMutex);
LayerPersistentState* matchedLayer = nullptr;
const ShaderPackage* matchedPackage = nullptr;
for (LayerPersistentState& layer : mPersistentState.layers)
{
auto shaderIt = mPackagesById.find(layer.shaderId);
if (shaderIt == 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;
}
resolvedLayerId = matchedLayer->id;
const 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;
}
resolvedParameterId = parameterIt->id;
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() - mStartTime).count();
value.numberValues = { previousCount + 1.0, triggerTime };
MarkParameterStateDirtyLocked();
appliedValue = value;
return true;
}
ShaderParameterValue normalizedTarget;
if (!NormalizeAndValidateValue(*parameterIt, targetValue, normalizedTarget, error))
return false;
const bool smoothableType =
parameterIt->type == ShaderParameterType::Float ||
parameterIt->type == ShaderParameterType::Vec2 ||
parameterIt->type == ShaderParameterType::Color;
const bool smoothableInput = targetValue.isNumber() || JsonValueContainsOnlyNumbers(targetValue);
const double alpha = Clamp01(smoothingAmount);
if (!smoothableType || !smoothableInput || alpha <= 0.0)
{
matchedLayer->parameterValues[parameterIt->id] = normalizedTarget;
MarkParameterStateDirtyLocked();
appliedValue = normalizedTarget;
return true;
}
ShaderParameterValue currentValue = DefaultValueForDefinition(*parameterIt);
auto currentIt = matchedLayer->parameterValues.find(parameterIt->id);
if (currentIt != matchedLayer->parameterValues.end())
currentValue = currentIt->second;
ShaderParameterValue nextValue = normalizedTarget;
nextValue.numberValues = normalizedTarget.numberValues;
if (currentValue.numberValues.size() != normalizedTarget.numberValues.size())
currentValue.numberValues = normalizedTarget.numberValues;
bool changed = false;
bool converged = true;
for (std::size_t index = 0; index < normalizedTarget.numberValues.size(); ++index)
{
const double currentNumber = currentValue.numberValues[index];
const double targetNumber = normalizedTarget.numberValues[index];
const double delta = targetNumber - currentNumber;
double nextNumber = currentNumber + delta * alpha;
if (std::fabs(delta) <= 0.0005)
{
nextNumber = targetNumber;
}
else
{
converged = false;
}
if (std::fabs(nextNumber - currentNumber) > 0.0000001)
changed = true;
nextValue.numberValues[index] = nextNumber;
}
if (!converged)
{
keepApplying = true;
}
else
{
nextValue.numberValues = normalizedTarget.numberValues;
}
if (!changed && !keepApplying)
{
appliedValue = matchedLayer->parameterValues[parameterIt->id];
return true;
}
matchedLayer->parameterValues[parameterIt->id] = nextValue;
MarkParameterStateDirtyLocked();
appliedValue = nextValue;
return true;
}
void RuntimeHost::SetCompileStatus(bool succeeded, const std::string& message) void RuntimeHost::SetCompileStatus(bool succeeded, const std::string& message)
{ {
std::lock_guard<std::mutex> lock(mMutex); std::lock_guard<std::mutex> lock(mMutex);

View File

@@ -25,10 +25,6 @@ public:
bool ManualReloadRequested(); bool ManualReloadRequested();
void ClearReloadRequest(); void ClearReloadRequest();
bool UpdateLayerParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue, std::string& error);
bool UpdateLayerParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue, bool persistState, std::string& error);
bool ApplyOscTargetByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& targetValue, double smoothingAmount, bool& keepApplying, std::string& resolvedLayerId, std::string& resolvedParameterId, ShaderParameterValue& appliedValue, std::string& error);
void SetCompileStatus(bool succeeded, const std::string& message); void SetCompileStatus(bool succeeded, const std::string& message);
void SetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName); void SetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName);
bool TrySetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName); bool TrySetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName);

View File

@@ -14,6 +14,22 @@ std::string TrimCopy(const std::string& text)
return text.substr(start, end - start); 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) : RuntimeStore::RuntimeStore(RuntimeHost& runtimeHost) :
@@ -256,12 +272,36 @@ bool RuntimeStore::SetStoredParameterValue(const std::string& layerId, const std
bool RuntimeStore::SetStoredParameterValueByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue, std::string& error) bool RuntimeStore::SetStoredParameterValueByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue, std::string& error)
{ {
return mRuntimeHost.UpdateLayerParameterByControlKey(layerKey, parameterKey, newValue, 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) bool RuntimeStore::SetStoredParameterValueByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue, bool persistState, std::string& error)
{ {
return mRuntimeHost.UpdateLayerParameterByControlKey(layerKey, parameterKey, newValue, persistState, 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) bool RuntimeStore::ResetStoredLayerParameterValues(const std::string& layerId, std::string& error)
@@ -551,3 +591,45 @@ JsonValue RuntimeStore::SerializeLayerStack() const
{ {
return mRuntimeHost.SerializeLayerStackLocked(); 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;
}

View File

@@ -46,6 +46,9 @@ public:
void ClearReloadRequest(); void ClearReloadRequest();
private: private:
bool 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;
JsonValue BuildRuntimeStateValue() const; JsonValue BuildRuntimeStateValue() const;
JsonValue SerializeLayerStack() const; JsonValue SerializeLayerStack() const;

View File

@@ -118,6 +118,7 @@ These are still compatibility seams, not a completed subsystem extraction. Most
- runtime startup for path resolution, config load, persistent state load, and shader package scan now initializes through `RuntimeStore` - runtime startup for path resolution, config load, persistent state load, and shader package scan now initializes through `RuntimeStore`
- runtime/UI state JSON composition now lives in `RuntimeStore` instead of `RuntimeHost` - runtime/UI state JSON composition now lives in `RuntimeStore` instead of `RuntimeHost`
- regular stored layer mutations and stack preset save/load now live in `RuntimeStore` instead of `RuntimeHost` public APIs - regular stored layer mutations and stack preset save/load now live in `RuntimeStore` instead of `RuntimeHost` public APIs
- persisted OSC-by-control-key commits now apply through `RuntimeStore`, while `RuntimeHost` no longer exposes the old OSC smoothing/apply helper
- mutation and reload policy now routes through `RuntimeCoordinator` - mutation and reload policy now routes through `RuntimeCoordinator`
- render-state and shader-build reads in `OpenGLComposite.cpp`, `OpenGLShaderPrograms.cpp`, and `ShaderBuildQueue.cpp` now route through `RuntimeSnapshotProvider` - render-state and shader-build reads in `OpenGLComposite.cpp`, `OpenGLShaderPrograms.cpp`, and `ShaderBuildQueue.cpp` now route through `RuntimeSnapshotProvider`
- render-state assembly, cached parameter refresh, and frame-context application now live in `RuntimeSnapshotProvider` instead of `RuntimeHost` public APIs - render-state assembly, cached parameter refresh, and frame-context application now live in `RuntimeSnapshotProvider` instead of `RuntimeHost` public APIs