diff --git a/apps/LoopThroughWithOpenGLCompositing/control/ControlServices.cpp b/apps/LoopThroughWithOpenGLCompositing/control/ControlServices.cpp index 34f3a1c..ffe341d 100644 --- a/apps/LoopThroughWithOpenGLCompositing/control/ControlServices.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/control/ControlServices.cpp @@ -4,6 +4,7 @@ #include "OscServer.h" #include "RuntimeControlBridge.h" #include "RuntimeHost.h" +#include "RuntimeStore.h" #include ControlServices::ControlServices() : @@ -34,9 +35,9 @@ bool ControlServices::Start(OpenGLComposite& composite, RuntimeHost& runtimeHost return true; } -void ControlServices::BeginPolling(RuntimeHost& runtimeHost) +void ControlServices::BeginPolling(RuntimeHost& runtimeHost, RuntimeStore& runtimeStore) { - StartPolling(runtimeHost); + StartPolling(runtimeHost, runtimeStore); } void ControlServices::Stop() @@ -174,12 +175,12 @@ RuntimePollEvents ControlServices::ConsumePollEvents() return events; } -void ControlServices::StartPolling(RuntimeHost& runtimeHost) +void ControlServices::StartPolling(RuntimeHost& runtimeHost, RuntimeStore& runtimeStore) { if (mPollRunning.exchange(true)) return; - mPollThread = std::thread([this, &runtimeHost]() { PollLoop(runtimeHost); }); + mPollThread = std::thread([this, &runtimeHost, &runtimeStore]() { PollLoop(runtimeHost, runtimeStore); }); } void ControlServices::StopPolling() @@ -191,7 +192,7 @@ void ControlServices::StopPolling() mPollThread.join(); } -void ControlServices::PollLoop(RuntimeHost& runtimeHost) +void ControlServices::PollLoop(RuntimeHost& runtimeHost, RuntimeStore& runtimeStore) { while (mPollRunning) { @@ -203,7 +204,7 @@ void ControlServices::PollLoop(RuntimeHost& runtimeHost) for (const auto& entry : pendingCommits) { std::string commitError; - if (runtimeHost.UpdateLayerParameterByControlKey( + if (runtimeStore.SetStoredParameterValueByControlKey( entry.second.layerKey, entry.second.parameterKey, entry.second.value, diff --git a/apps/LoopThroughWithOpenGLCompositing/control/ControlServices.h b/apps/LoopThroughWithOpenGLCompositing/control/ControlServices.h index 35690aa..0a20c92 100644 --- a/apps/LoopThroughWithOpenGLCompositing/control/ControlServices.h +++ b/apps/LoopThroughWithOpenGLCompositing/control/ControlServices.h @@ -15,6 +15,7 @@ class ControlServer; class OpenGLComposite; class OscServer; class RuntimeHost; +class RuntimeStore; struct RuntimePollEvents { @@ -45,7 +46,7 @@ public: ~ControlServices(); bool Start(OpenGLComposite& composite, RuntimeHost& runtimeHost, std::string& error); - void BeginPolling(RuntimeHost& runtimeHost); + void BeginPolling(RuntimeHost& runtimeHost, RuntimeStore& runtimeStore); void Stop(); void BroadcastState(); void RequestBroadcastState(); @@ -73,9 +74,9 @@ private: uint64_t generation = 0; }; - void StartPolling(RuntimeHost& runtimeHost); + void StartPolling(RuntimeHost& runtimeHost, RuntimeStore& runtimeStore); void StopPolling(); - void PollLoop(RuntimeHost& runtimeHost); + void PollLoop(RuntimeHost& runtimeHost, RuntimeStore& runtimeStore); std::unique_ptr mControlServer; std::unique_ptr mOscServer; diff --git a/apps/LoopThroughWithOpenGLCompositing/control/RuntimeServices.cpp b/apps/LoopThroughWithOpenGLCompositing/control/RuntimeServices.cpp index 35a034e..de418d7 100644 --- a/apps/LoopThroughWithOpenGLCompositing/control/RuntimeServices.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/control/RuntimeServices.cpp @@ -1,5 +1,7 @@ #include "RuntimeServices.h" +#include "RuntimeStore.h" + RuntimeServices::RuntimeServices() : mControlServices(std::make_unique()) { @@ -15,10 +17,10 @@ bool RuntimeServices::Start(OpenGLComposite& composite, RuntimeHost& runtimeHost return mControlServices && mControlServices->Start(composite, runtimeHost, error); } -void RuntimeServices::BeginPolling(RuntimeHost& runtimeHost) +void RuntimeServices::BeginPolling(RuntimeHost& runtimeHost, RuntimeStore& runtimeStore) { if (mControlServices) - mControlServices->BeginPolling(runtimeHost); + mControlServices->BeginPolling(runtimeHost, runtimeStore); } void RuntimeServices::Stop() diff --git a/apps/LoopThroughWithOpenGLCompositing/control/RuntimeServices.h b/apps/LoopThroughWithOpenGLCompositing/control/RuntimeServices.h index 9174bbe..c2094f9 100644 --- a/apps/LoopThroughWithOpenGLCompositing/control/RuntimeServices.h +++ b/apps/LoopThroughWithOpenGLCompositing/control/RuntimeServices.h @@ -6,6 +6,7 @@ #include class OpenGLComposite; class RuntimeHost; +class RuntimeStore; class RuntimeServices { @@ -17,7 +18,7 @@ public: ~RuntimeServices(); bool Start(OpenGLComposite& composite, RuntimeHost& runtimeHost, std::string& error); - void BeginPolling(RuntimeHost& runtimeHost); + void BeginPolling(RuntimeHost& runtimeHost, RuntimeStore& runtimeStore); void Stop(); void BroadcastState(); void RequestBroadcastState(); diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp index ce5ea6a..d13c236 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp @@ -270,7 +270,7 @@ bool OpenGLComposite::InitOpenGLState() mRenderEngine->ResetShaderFeedbackState(); broadcastRuntimeState(); - mRuntimeServices->BeginPolling(*mRuntimeHost); + mRuntimeServices->BeginPolling(*mRuntimeHost, *mRuntimeStore); return true; } diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp index f64119c..f201aba 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp @@ -825,201 +825,6 @@ void RuntimeHost::ClearReloadRequest() 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 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(), - [¶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; - } - - 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::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 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(), - [¶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; - } - - 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::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) { std::lock_guard lock(mMutex); diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.h b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.h index aeadc6d..8141abb 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.h +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.h @@ -25,10 +25,6 @@ public: bool ManualReloadRequested(); 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 SetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName); bool TrySetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName); diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.cpp b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.cpp index 6d704f3..5bb7358 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.cpp @@ -14,6 +14,22 @@ std::string TrimCopy(const std::string& text) 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); +} } 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) { - 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) { - return mRuntimeHost.UpdateLayerParameterByControlKey(layerKey, parameterKey, newValue, persistState, error); + std::lock_guard lock(mRuntimeHost.mMutex); + + RuntimeHost::LayerPersistentState* matchedLayer = nullptr; + const ShaderPackage* matchedPackage = nullptr; + std::vector::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::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) @@ -551,3 +591,45 @@ 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::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(), + [¶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; + } + + return true; +} diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.h b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.h index 3495f00..215c3ce 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.h +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.h @@ -46,6 +46,9 @@ public: void ClearReloadRequest(); private: + bool TryResolveStoredLayerAndParameterByControlKeyLocked(const std::string& layerKey, const std::string& parameterKey, + RuntimeHost::LayerPersistentState*& matchedLayer, const ShaderPackage*& matchedPackage, + std::vector::const_iterator& parameterIt, std::string& error) const; JsonValue BuildRuntimeStateValue() const; JsonValue SerializeLayerStack() const; diff --git a/docs/PHASE_1_SUBSYSTEM_BOUNDARIES_DESIGN.md b/docs/PHASE_1_SUBSYSTEM_BOUNDARIES_DESIGN.md index 72d67bf..dda838d 100644 --- a/docs/PHASE_1_SUBSYSTEM_BOUNDARIES_DESIGN.md +++ b/docs/PHASE_1_SUBSYSTEM_BOUNDARIES_DESIGN.md @@ -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/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 +- 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` - 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