diff --git a/CMakeLists.txt b/CMakeLists.txt
index d12938a..3eb89ce 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -34,6 +34,8 @@ set(APP_SOURCES
"${APP_DIR}/videoio/decklink/DeckLinkAPI_i.c"
"${APP_DIR}/control/ControlServer.cpp"
"${APP_DIR}/control/ControlServer.h"
+ "${APP_DIR}/control/ControlServices.cpp"
+ "${APP_DIR}/control/ControlServices.h"
"${APP_DIR}/control/OscServer.cpp"
"${APP_DIR}/control/OscServer.h"
"${APP_DIR}/control/RuntimeControlBridge.cpp"
@@ -98,6 +100,8 @@ set(APP_SOURCES
"${APP_DIR}/resource.h"
"${APP_DIR}/runtime/RuntimeHost.cpp"
"${APP_DIR}/runtime/RuntimeHost.h"
+ "${APP_DIR}/runtime/HealthTelemetry.cpp"
+ "${APP_DIR}/runtime/HealthTelemetry.h"
"${APP_DIR}/runtime/RuntimeSnapshotProvider.cpp"
"${APP_DIR}/runtime/RuntimeSnapshotProvider.h"
"${APP_DIR}/runtime/RuntimeClock.cpp"
diff --git a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj
index 5257d31..e3e3669 100644
--- a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj
+++ b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj
@@ -89,7 +89,7 @@
Disabled
- .;control;gl;gl\pipeline;gl\renderer;gl\shader;videoio;videoio\decklink;%(AdditionalIncludeDirectories)
+ .;control;gl;gl\pipeline;gl\renderer;gl\shader;platform;runtime;shader;videoio;videoio\decklink;%(AdditionalIncludeDirectories)
WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)
EnableFastChecks
MultiThreadedDebugDLL
@@ -99,7 +99,7 @@
stdcpp17
- opengl32.lib;Glu32.lib;Windowscodecs.lib;Ole32.lib;%(AdditionalDependencies)
+ opengl32.lib;Glu32.lib;Ws2_32.lib;Crypt32.lib;Advapi32.lib;Gdiplus.lib;Ole32.lib;Windowscodecs.lib;%(AdditionalDependencies)
true
Windows
MachineX86
@@ -111,7 +111,7 @@
Disabled
- .;control;gl;gl\pipeline;gl\renderer;gl\shader;videoio;videoio\decklink;%(AdditionalIncludeDirectories)
+ .;control;gl;gl\pipeline;gl\renderer;gl\shader;platform;runtime;shader;videoio;videoio\decklink;%(AdditionalIncludeDirectories)
WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)
EnableFastChecks
MultiThreadedDebugDLL
@@ -121,7 +121,7 @@
stdcpp17
- opengl32.lib;Glu32.lib;Windowscodecs.lib;Ole32.lib;%(AdditionalDependencies)
+ opengl32.lib;Glu32.lib;Ws2_32.lib;Crypt32.lib;Advapi32.lib;Gdiplus.lib;Ole32.lib;Windowscodecs.lib;%(AdditionalDependencies)
true
Windows
MachineX64
@@ -131,7 +131,7 @@
MaxSpeed
true
- .;control;gl;gl\pipeline;gl\renderer;gl\shader;videoio;videoio\decklink;%(AdditionalIncludeDirectories)
+ .;control;gl;gl\pipeline;gl\renderer;gl\shader;platform;runtime;shader;videoio;videoio\decklink;%(AdditionalIncludeDirectories)
WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)
MultiThreadedDLL
true
@@ -141,7 +141,7 @@
stdcpp17
- opengl32.lib;Glu32.lib;Windowscodecs.lib;Ole32.lib;%(AdditionalDependencies)
+ opengl32.lib;Glu32.lib;Ws2_32.lib;Crypt32.lib;Advapi32.lib;Gdiplus.lib;Ole32.lib;Windowscodecs.lib;%(AdditionalDependencies)
true
Windows
true
@@ -156,7 +156,7 @@
MaxSpeed
true
- .;control;gl;gl\pipeline;gl\renderer;gl\shader;videoio;videoio\decklink;%(AdditionalIncludeDirectories)
+ .;control;gl;gl\pipeline;gl\renderer;gl\shader;platform;runtime;shader;videoio;videoio\decklink;%(AdditionalIncludeDirectories)
WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)
MultiThreadedDLL
true
@@ -166,7 +166,7 @@
stdcpp17
- opengl32.lib;Glu32.lib;Windowscodecs.lib;Ole32.lib;%(AdditionalDependencies)
+ opengl32.lib;Glu32.lib;Ws2_32.lib;Crypt32.lib;Advapi32.lib;Gdiplus.lib;Ole32.lib;Windowscodecs.lib;%(AdditionalDependencies)
true
Windows
true
@@ -175,18 +175,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
Create
Create
@@ -194,34 +207,71 @@
Create
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
-
diff --git a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj.filters b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj.filters
index c19a028..cf8f9c8 100644
--- a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj.filters
+++ b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj.filters
@@ -18,21 +18,45 @@
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
Source Files
+
+ Source Files
+
+
+ Source Files
+
Source Files
Source Files
+
+ Source Files
+
Source Files
Source Files
+
+ Source Files
+
Source Files
@@ -45,9 +69,21 @@
Source Files
+
+ Source Files
+
Source Files
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
Source Files
@@ -63,15 +99,45 @@
Source Files
+
+ Source Files
+
+
+ Source Files
+
Source Files
Source Files
+
+ Source Files
+
Source Files
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
Source Files
@@ -80,9 +146,33 @@
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
Header Files
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
Header Files
@@ -98,6 +188,9 @@
Header Files
+
+ Header Files
+
Header Files
@@ -110,18 +203,66 @@
Header Files
+
+ Header Files
+
Header Files
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
Header Files
Header Files
+
+ Header Files
+
+
+ Header Files
+
Header Files
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
Header Files
@@ -137,6 +278,15 @@
Header Files
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
Header Files
diff --git a/apps/LoopThroughWithOpenGLCompositing/control/ControlServices.cpp b/apps/LoopThroughWithOpenGLCompositing/control/ControlServices.cpp
new file mode 100644
index 0000000..34f3a1c
--- /dev/null
+++ b/apps/LoopThroughWithOpenGLCompositing/control/ControlServices.cpp
@@ -0,0 +1,247 @@
+#include "ControlServices.h"
+
+#include "ControlServer.h"
+#include "OscServer.h"
+#include "RuntimeControlBridge.h"
+#include "RuntimeHost.h"
+#include
+
+ControlServices::ControlServices() :
+ mControlServer(std::make_unique()),
+ mOscServer(std::make_unique()),
+ mPollRunning(false),
+ mRegistryChanged(false),
+ mReloadRequested(false),
+ mPollFailed(false)
+{
+}
+
+ControlServices::~ControlServices()
+{
+ Stop();
+}
+
+bool ControlServices::Start(OpenGLComposite& composite, RuntimeHost& runtimeHost, std::string& error)
+{
+ Stop();
+
+ if (!StartControlServicesBoundary(composite, runtimeHost, *this, *mControlServer, *mOscServer, error))
+ {
+ Stop();
+ return false;
+ }
+
+ return true;
+}
+
+void ControlServices::BeginPolling(RuntimeHost& runtimeHost)
+{
+ StartPolling(runtimeHost);
+}
+
+void ControlServices::Stop()
+{
+ StopPolling();
+
+ if (mOscServer)
+ mOscServer->Stop();
+
+ if (mControlServer)
+ mControlServer->Stop();
+}
+
+void ControlServices::BroadcastState()
+{
+ if (mControlServer)
+ mControlServer->BroadcastState();
+}
+
+void ControlServices::RequestBroadcastState()
+{
+ if (mControlServer)
+ mControlServer->RequestBroadcastState();
+}
+
+bool ControlServices::QueueOscUpdate(const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& error)
+{
+ (void)error;
+
+ PendingOscUpdate update;
+ update.layerKey = layerKey;
+ update.parameterKey = parameterKey;
+ update.valueJson = valueJson;
+
+ const std::string routeKey = layerKey + "\n" + parameterKey;
+ {
+ std::lock_guard lock(mPendingOscMutex);
+ mPendingOscUpdates[routeKey] = std::move(update);
+ }
+ return true;
+}
+
+bool ControlServices::ApplyPendingOscUpdates(std::vector& appliedUpdates, std::string& error)
+{
+ appliedUpdates.clear();
+
+ std::map pending;
+ {
+ std::lock_guard lock(mPendingOscMutex);
+ if (mPendingOscUpdates.empty())
+ return true;
+ pending.swap(mPendingOscUpdates);
+ }
+
+ for (const auto& entry : pending)
+ {
+ JsonValue targetValue;
+ std::string parseError;
+ if (!ParseJson(entry.second.valueJson, targetValue, parseError))
+ {
+ OutputDebugStringA(("OSC queued value parse failed: " + parseError + "\n").c_str());
+ continue;
+ }
+
+ AppliedOscUpdate appliedUpdate;
+ appliedUpdate.routeKey = entry.first;
+ appliedUpdate.layerKey = entry.second.layerKey;
+ appliedUpdate.parameterKey = entry.second.parameterKey;
+ appliedUpdate.targetValue = targetValue;
+ appliedUpdates.push_back(std::move(appliedUpdate));
+ }
+
+ (void)error;
+ return true;
+}
+
+bool ControlServices::QueueOscCommit(const std::string& routeKey, const std::string& layerKey, const std::string& parameterKey, const JsonValue& value, uint64_t generation, std::string& error)
+{
+ (void)error;
+
+ PendingOscCommit commit;
+ commit.routeKey = routeKey;
+ commit.layerKey = layerKey;
+ commit.parameterKey = parameterKey;
+ commit.value = value;
+ commit.generation = generation;
+
+ {
+ std::lock_guard lock(mPendingOscCommitMutex);
+ mPendingOscCommits[routeKey] = std::move(commit);
+ }
+ return true;
+}
+
+void ControlServices::ClearOscState()
+{
+ {
+ std::lock_guard lock(mPendingOscMutex);
+ mPendingOscUpdates.clear();
+ }
+ {
+ std::lock_guard lock(mPendingOscCommitMutex);
+ mPendingOscCommits.clear();
+ }
+ {
+ std::lock_guard lock(mCompletedOscCommitMutex);
+ mCompletedOscCommits.clear();
+ }
+}
+
+void ControlServices::ConsumeCompletedOscCommits(std::vector& completedCommits)
+{
+ completedCommits.clear();
+
+ std::lock_guard lock(mCompletedOscCommitMutex);
+ if (mCompletedOscCommits.empty())
+ return;
+
+ completedCommits.swap(mCompletedOscCommits);
+}
+
+RuntimePollEvents ControlServices::ConsumePollEvents()
+{
+ RuntimePollEvents events;
+ events.registryChanged = mRegistryChanged.exchange(false);
+ events.reloadRequested = mReloadRequested.exchange(false);
+ events.failed = mPollFailed.exchange(false);
+
+ if (events.failed)
+ {
+ std::lock_guard lock(mPollErrorMutex);
+ events.error = mPollError;
+ }
+
+ return events;
+}
+
+void ControlServices::StartPolling(RuntimeHost& runtimeHost)
+{
+ if (mPollRunning.exchange(true))
+ return;
+
+ mPollThread = std::thread([this, &runtimeHost]() { PollLoop(runtimeHost); });
+}
+
+void ControlServices::StopPolling()
+{
+ if (!mPollRunning.exchange(false))
+ return;
+
+ if (mPollThread.joinable())
+ mPollThread.join();
+}
+
+void ControlServices::PollLoop(RuntimeHost& runtimeHost)
+{
+ while (mPollRunning)
+ {
+ std::map pendingCommits;
+ {
+ std::lock_guard lock(mPendingOscCommitMutex);
+ pendingCommits.swap(mPendingOscCommits);
+ }
+ for (const auto& entry : pendingCommits)
+ {
+ std::string commitError;
+ if (runtimeHost.UpdateLayerParameterByControlKey(
+ entry.second.layerKey,
+ entry.second.parameterKey,
+ entry.second.value,
+ false,
+ commitError))
+ {
+ CompletedOscCommit completedCommit;
+ completedCommit.routeKey = entry.second.routeKey;
+ completedCommit.generation = entry.second.generation;
+ std::lock_guard lock(mCompletedOscCommitMutex);
+ mCompletedOscCommits.push_back(std::move(completedCommit));
+ }
+ else if (!commitError.empty())
+ {
+ OutputDebugStringA(("OSC commit failed: " + commitError + "\n").c_str());
+ }
+ }
+
+ bool registryChanged = false;
+ bool reloadRequested = false;
+ std::string runtimeError;
+ if (!runtimeHost.PollFileChanges(registryChanged, reloadRequested, runtimeError))
+ {
+ {
+ std::lock_guard lock(mPollErrorMutex);
+ mPollError = runtimeError;
+ }
+ mPollFailed = true;
+ }
+ else
+ {
+ if (registryChanged)
+ mRegistryChanged = true;
+ if (reloadRequested)
+ mReloadRequested = true;
+ }
+
+ for (int i = 0; i < 25 && mPollRunning; ++i)
+ Sleep(10);
+ }
+}
diff --git a/apps/LoopThroughWithOpenGLCompositing/control/ControlServices.h b/apps/LoopThroughWithOpenGLCompositing/control/ControlServices.h
new file mode 100644
index 0000000..35690aa
--- /dev/null
+++ b/apps/LoopThroughWithOpenGLCompositing/control/ControlServices.h
@@ -0,0 +1,95 @@
+#pragma once
+
+#include "RuntimeJson.h"
+#include "ShaderTypes.h"
+
+#include
+#include