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

This commit is contained in:
Aiden
2026-05-11 15:15:42 +10:00
parent 5503ce85a9
commit b3705d96cc
20 changed files with 1617 additions and 10 deletions

View File

@@ -6,9 +6,10 @@
#include "RuntimeStore.h"
#include <windows.h>
ControlServices::ControlServices() :
ControlServices::ControlServices(RuntimeEventDispatcher& runtimeEventDispatcher) :
mControlServer(std::make_unique<ControlServer>()),
mOscServer(std::make_unique<OscServer>()),
mRuntimeEventDispatcher(runtimeEventDispatcher),
mPollRunning(false)
{
}

View File

@@ -15,6 +15,7 @@
class ControlServer;
class OpenGLComposite;
class OscServer;
class RuntimeEventDispatcher;
class RuntimeStore;
struct RuntimeCoordinatorServiceResult
@@ -40,7 +41,7 @@ public:
uint64_t generation = 0;
};
ControlServices();
explicit ControlServices(RuntimeEventDispatcher& runtimeEventDispatcher);
~ControlServices();
bool Start(OpenGLComposite& composite, RuntimeStore& runtimeStore, std::string& error);
@@ -79,6 +80,7 @@ private:
std::unique_ptr<ControlServer> mControlServer;
std::unique_ptr<OscServer> mOscServer;
RuntimeEventDispatcher& mRuntimeEventDispatcher;
std::thread mPollThread;
std::atomic<bool> mPollRunning;
std::mutex mRuntimeCoordinatorResultMutex;

View File

@@ -2,8 +2,8 @@
#include "RuntimeStore.h"
RuntimeServices::RuntimeServices() :
mControlServices(std::make_unique<ControlServices>())
RuntimeServices::RuntimeServices(RuntimeEventDispatcher& runtimeEventDispatcher) :
mControlServices(std::make_unique<ControlServices>(runtimeEventDispatcher))
{
}

View File

@@ -6,6 +6,7 @@
#include <string>
class OpenGLComposite;
class RuntimeCoordinator;
class RuntimeEventDispatcher;
class RuntimeStore;
class RuntimeServices
@@ -14,7 +15,7 @@ public:
using AppliedOscUpdate = ControlServices::AppliedOscUpdate;
using CompletedOscCommit = ControlServices::CompletedOscCommit;
RuntimeServices();
explicit RuntimeServices(RuntimeEventDispatcher& runtimeEventDispatcher);
~RuntimeServices();
bool Start(OpenGLComposite& composite, RuntimeStore& runtimeStore, std::string& error);

View File

@@ -5,6 +5,7 @@
#include "PngScreenshotWriter.h"
#include "RenderEngine.h"
#include "RuntimeCoordinator.h"
#include "RuntimeEventDispatcher.h"
#include "RuntimeParameterUtils.h"
#include "RuntimeServices.h"
#include "RuntimeSnapshotProvider.h"
@@ -36,8 +37,9 @@ OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) :
{
InitializeCriticalSection(&pMutex);
mRuntimeStore = std::make_unique<RuntimeStore>();
mRuntimeEventDispatcher = std::make_unique<RuntimeEventDispatcher>();
mRuntimeSnapshotProvider = std::make_unique<RuntimeSnapshotProvider>(mRuntimeStore->GetRenderSnapshotBuilder());
mRuntimeCoordinator = std::make_unique<RuntimeCoordinator>(*mRuntimeStore);
mRuntimeCoordinator = std::make_unique<RuntimeCoordinator>(*mRuntimeStore, *mRuntimeEventDispatcher);
mRenderEngine = std::make_unique<RenderEngine>(
*mRuntimeSnapshotProvider,
mRuntimeStore->GetHealthTelemetry(),
@@ -49,10 +51,11 @@ OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) :
[this]() { paintGL(false); });
mVideoBackend = std::make_unique<VideoBackend>(*mRenderEngine, mRuntimeStore->GetHealthTelemetry());
mShaderBuildQueue = std::make_unique<ShaderBuildQueue>(*mRuntimeSnapshotProvider);
mRuntimeServices = std::make_unique<RuntimeServices>();
mRuntimeServices = std::make_unique<RuntimeServices>(*mRuntimeEventDispatcher);
mRuntimeUpdateController = std::make_unique<RuntimeUpdateController>(
*mRuntimeStore,
*mRuntimeCoordinator,
*mRuntimeEventDispatcher,
*mRuntimeServices,
*mRenderEngine,
*mShaderBuildQueue,

View File

@@ -22,6 +22,7 @@
class RenderEngine;
class RuntimeCoordinator;
class RuntimeEventDispatcher;
class RuntimeSnapshotProvider;
class RuntimeServices;
class RuntimeStore;
@@ -76,6 +77,7 @@ private:
std::unique_ptr<RuntimeStore> mRuntimeStore;
std::unique_ptr<RuntimeCoordinator> mRuntimeCoordinator;
std::unique_ptr<RuntimeSnapshotProvider> mRuntimeSnapshotProvider;
std::unique_ptr<RuntimeEventDispatcher> mRuntimeEventDispatcher;
std::unique_ptr<RenderEngine> mRenderEngine;
std::unique_ptr<ShaderBuildQueue> mShaderBuildQueue;
std::unique_ptr<RuntimeServices> mRuntimeServices;

View File

@@ -11,12 +11,14 @@
RuntimeUpdateController::RuntimeUpdateController(
RuntimeStore& runtimeStore,
RuntimeCoordinator& runtimeCoordinator,
RuntimeEventDispatcher& runtimeEventDispatcher,
RuntimeServices& runtimeServices,
RenderEngine& renderEngine,
ShaderBuildQueue& shaderBuildQueue,
VideoBackend& videoBackend) :
mRuntimeStore(runtimeStore),
mRuntimeCoordinator(runtimeCoordinator),
mRuntimeEventDispatcher(runtimeEventDispatcher),
mRuntimeServices(runtimeServices),
mRenderEngine(renderEngine),
mShaderBuildQueue(shaderBuildQueue),

View File

@@ -5,6 +5,7 @@
#include <string>
class RenderEngine;
class RuntimeEventDispatcher;
class RuntimeServices;
class RuntimeStore;
class ShaderBuildQueue;
@@ -16,6 +17,7 @@ public:
RuntimeUpdateController(
RuntimeStore& runtimeStore,
RuntimeCoordinator& runtimeCoordinator,
RuntimeEventDispatcher& runtimeEventDispatcher,
RuntimeServices& runtimeServices,
RenderEngine& renderEngine,
ShaderBuildQueue& shaderBuildQueue,
@@ -29,6 +31,7 @@ public:
private:
RuntimeStore& mRuntimeStore;
RuntimeCoordinator& mRuntimeCoordinator;
RuntimeEventDispatcher& mRuntimeEventDispatcher;
RuntimeServices& mRuntimeServices;
RenderEngine& mRenderEngine;
ShaderBuildQueue& mShaderBuildQueue;

View File

@@ -3,8 +3,9 @@
#include "RuntimeParameterUtils.h"
#include "RuntimeStore.h"
RuntimeCoordinator::RuntimeCoordinator(RuntimeStore& runtimeStore) :
mRuntimeStore(runtimeStore)
RuntimeCoordinator::RuntimeCoordinator(RuntimeStore& runtimeStore, RuntimeEventDispatcher& runtimeEventDispatcher) :
mRuntimeStore(runtimeStore),
mRuntimeEventDispatcher(runtimeEventDispatcher)
{
}

View File

@@ -9,6 +9,7 @@
#include <string>
class RuntimeStore;
class RuntimeEventDispatcher;
enum class RuntimeCoordinatorCommittedStateMode
{
@@ -42,7 +43,7 @@ struct RuntimeCoordinatorResult
class RuntimeCoordinator
{
public:
explicit RuntimeCoordinator(RuntimeStore& runtimeStore);
RuntimeCoordinator(RuntimeStore& runtimeStore, RuntimeEventDispatcher& runtimeEventDispatcher);
RuntimeCoordinatorResult AddLayer(const std::string& shaderId);
RuntimeCoordinatorResult RemoveLayer(const std::string& layerId);
@@ -93,6 +94,7 @@ private:
RuntimeCoordinatorResult BuildAcceptedNoReloadResult() const;
RuntimeStore& mRuntimeStore;
RuntimeEventDispatcher& mRuntimeEventDispatcher;
mutable std::mutex mMutex;
bool mPreserveFeedbackOnNextShaderBuild = false;
std::atomic<bool> mUseCommittedLayerStates{ false };

View File

@@ -0,0 +1,87 @@
#pragma once
#include "RuntimeEventPayloads.h"
#include <chrono>
#include <cstdint>
#include <string>
#include <type_traits>
#include <utility>
#include <variant>
using RuntimeEventPayload = std::variant<
std::monostate,
OscValueReceivedEvent,
OscValueCoalescedEvent,
OscCommitRequestedEvent,
HttpControlMutationRequestedEvent,
WebSocketClientConnectedEvent,
RuntimeStateBroadcastRequestedEvent,
FileChangeDetectedEvent,
ManualReloadRequestedEvent,
RuntimeMutationEvent,
RuntimeStateChangedEvent,
RuntimePersistenceRequestedEvent,
RuntimeReloadRequestedEvent,
ShaderPackagesChangedEvent,
RenderSnapshotPublishRequestedEvent,
RuntimeStatePresentationChangedEvent,
ShaderBuildEvent,
CompileStatusChangedEvent,
RenderSnapshotPublishedEvent,
RenderResetEvent,
OscOverlayEvent,
FrameRenderedEvent,
PreviewFrameAvailableEvent,
InputSignalChangedEvent,
InputFrameArrivedEvent,
OutputFrameScheduledEvent,
OutputFrameCompletedEvent,
BackendStateChangedEvent,
SubsystemWarningEvent,
SubsystemRecoveredEvent,
TimingSampleRecordedEvent,
QueueDepthChangedEvent>;
inline RuntimeEventType RuntimeEventPayloadType(const RuntimeEventPayload& payload)
{
return std::visit([](const auto& value) -> RuntimeEventType {
using PayloadType = std::decay_t<decltype(value)>;
if constexpr (std::is_same_v<PayloadType, std::monostate>)
return RuntimeEventType::Unknown;
else
return RuntimeEventPayloadType(value);
}, payload);
}
struct RuntimeEvent
{
RuntimeEventType type = RuntimeEventType::Unknown;
uint64_t sequence = 0;
std::chrono::steady_clock::time_point createdAt = std::chrono::steady_clock::now();
std::string source;
RuntimeEventPayload payload;
bool HasPayload() const
{
return !std::holds_alternative<std::monostate>(payload);
}
bool PayloadMatchesType() const
{
return RuntimeEventPayloadType(payload) == type;
}
};
template <typename Payload>
RuntimeEvent MakeRuntimeEvent(Payload payload, std::string source = {}, uint64_t sequence = 0,
std::chrono::steady_clock::time_point createdAt = std::chrono::steady_clock::now())
{
RuntimeEvent event;
event.type = RuntimeEventPayloadType(payload);
event.sequence = sequence;
event.createdAt = createdAt;
event.source = std::move(source);
event.payload = std::move(payload);
return event;
}

View File

@@ -0,0 +1,147 @@
#pragma once
#include "RuntimeEvent.h"
#include <chrono>
#include <cstddef>
#include <deque>
#include <functional>
#include <map>
#include <mutex>
#include <string>
#include <utility>
#include <vector>
struct RuntimeEventCoalescingQueueMetrics
{
std::size_t depth = 0;
std::size_t capacity = 0;
std::size_t droppedCount = 0;
std::size_t coalescedCount = 0;
double oldestEventAgeMilliseconds = 0.0;
};
inline std::string RuntimeEventDefaultCoalescingKey(const RuntimeEvent& event)
{
if (const auto* payload = std::get_if<OscValueReceivedEvent>(&event.payload))
return std::string(RuntimeEventTypeName(event.type)) + ":" + payload->routeKey;
if (const auto* payload = std::get_if<OscCommitRequestedEvent>(&event.payload))
return std::string(RuntimeEventTypeName(event.type)) + ":" + payload->routeKey;
if (const auto* payload = std::get_if<FileChangeDetectedEvent>(&event.payload))
return std::string(RuntimeEventTypeName(event.type)) + ":" + payload->path;
if (const auto* payload = std::get_if<QueueDepthChangedEvent>(&event.payload))
return std::string(RuntimeEventTypeName(event.type)) + ":" + payload->queueName;
return std::string(RuntimeEventTypeName(event.type));
}
class RuntimeEventCoalescingQueue
{
public:
using KeySelector = std::function<std::string(const RuntimeEvent&)>;
explicit RuntimeEventCoalescingQueue(std::size_t capacity = 256, KeySelector keySelector = RuntimeEventDefaultCoalescingKey) :
mCapacity(capacity),
mKeySelector(std::move(keySelector))
{
}
bool Push(RuntimeEvent event)
{
const std::string key = mKeySelector(event);
if (key.empty())
return false;
std::lock_guard<std::mutex> lock(mMutex);
auto found = mEntries.find(key);
if (found != mEntries.end())
{
const auto firstCreatedAt = found->second.event.createdAt;
found->second.event = std::move(event);
found->second.event.createdAt = firstCreatedAt;
++found->second.coalescedCount;
++mCoalescedCount;
return true;
}
if (mEntries.size() >= mCapacity)
{
++mDroppedCount;
return false;
}
mOrder.push_back(key);
Entry entry;
entry.event = std::move(event);
mEntries.emplace(key, std::move(entry));
return true;
}
std::vector<RuntimeEvent> Drain(std::size_t maxEvents = 0)
{
std::vector<RuntimeEvent> events;
std::lock_guard<std::mutex> lock(mMutex);
const std::size_t count = maxEvents == 0 || maxEvents > mOrder.size() ? mOrder.size() : maxEvents;
events.reserve(count);
for (std::size_t index = 0; index < count; ++index)
{
const std::string key = std::move(mOrder.front());
mOrder.pop_front();
auto found = mEntries.find(key);
if (found == mEntries.end())
continue;
events.push_back(std::move(found->second.event));
mEntries.erase(found);
}
return events;
}
RuntimeEventCoalescingQueueMetrics GetMetrics(std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()) const
{
std::lock_guard<std::mutex> lock(mMutex);
RuntimeEventCoalescingQueueMetrics metrics;
metrics.depth = mEntries.size();
metrics.capacity = mCapacity;
metrics.droppedCount = mDroppedCount;
metrics.coalescedCount = mCoalescedCount;
if (!mOrder.empty())
{
const auto found = mEntries.find(mOrder.front());
if (found != mEntries.end())
{
const auto age = now - found->second.event.createdAt;
metrics.oldestEventAgeMilliseconds = std::chrono::duration<double, std::milli>(age).count();
}
}
return metrics;
}
std::size_t Depth() const
{
std::lock_guard<std::mutex> lock(mMutex);
return mEntries.size();
}
private:
struct Entry
{
RuntimeEvent event;
std::size_t coalescedCount = 0;
};
mutable std::mutex mMutex;
std::size_t mCapacity = 0;
KeySelector mKeySelector;
std::deque<std::string> mOrder;
std::map<std::string, Entry> mEntries;
std::size_t mDroppedCount = 0;
std::size_t mCoalescedCount = 0;
};

View File

@@ -0,0 +1,117 @@
#pragma once
#include "RuntimeEventQueue.h"
#include <atomic>
#include <functional>
#include <map>
#include <mutex>
#include <vector>
struct RuntimeEventDispatchResult
{
std::size_t dispatchedEvents = 0;
std::size_t handlerInvocations = 0;
std::size_t handlerFailures = 0;
};
class RuntimeEventDispatcher
{
public:
using Handler = std::function<void(const RuntimeEvent&)>;
explicit RuntimeEventDispatcher(std::size_t queueCapacity = 1024) :
mQueue(queueCapacity)
{
}
bool Publish(RuntimeEvent event)
{
if (!event.PayloadMatchesType())
return false;
if (event.sequence == 0)
event.sequence = mNextSequence.fetch_add(1);
return mQueue.Push(std::move(event));
}
template <typename Payload>
bool PublishPayload(Payload payload, std::string source = {})
{
return Publish(MakeRuntimeEvent(std::move(payload), std::move(source)));
}
void Subscribe(RuntimeEventType type, Handler handler)
{
std::lock_guard<std::mutex> lock(mHandlerMutex);
mHandlers[type].push_back(std::move(handler));
}
void SubscribeAll(Handler handler)
{
std::lock_guard<std::mutex> lock(mHandlerMutex);
mAllHandlers.push_back(std::move(handler));
}
RuntimeEventDispatchResult DispatchPending(std::size_t maxEvents = 0)
{
RuntimeEventDispatchResult result;
std::vector<RuntimeEvent> events = mQueue.Drain(maxEvents);
result.dispatchedEvents = events.size();
for (const RuntimeEvent& event : events)
{
std::vector<Handler> handlers = HandlersFor(event.type);
result.handlerInvocations += handlers.size();
for (const Handler& handler : handlers)
{
try
{
handler(event);
}
catch (...)
{
++result.handlerFailures;
}
}
}
return result;
}
bool TryPop(RuntimeEvent& event)
{
return mQueue.TryPop(event);
}
RuntimeEventQueueMetrics GetQueueMetrics(std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()) const
{
return mQueue.GetMetrics(now);
}
std::size_t QueueDepth() const
{
return mQueue.Depth();
}
private:
std::vector<Handler> HandlersFor(RuntimeEventType type) const
{
std::lock_guard<std::mutex> lock(mHandlerMutex);
std::vector<Handler> handlers = mAllHandlers;
const auto found = mHandlers.find(type);
if (found != mHandlers.end())
handlers.insert(handlers.end(), found->second.begin(), found->second.end());
return handlers;
}
RuntimeEventQueue mQueue;
std::atomic<uint64_t> mNextSequence{ 1 };
mutable std::mutex mHandlerMutex;
std::map<RuntimeEventType, std::vector<Handler>> mHandlers;
std::vector<Handler> mAllHandlers;
};

View File

@@ -0,0 +1,436 @@
#pragma once
#include "RuntimeEventType.h"
#include <cstddef>
#include <cstdint>
#include <string>
enum class RuntimeEventSeverity
{
Debug,
Info,
Warning,
Error
};
enum class RuntimeEventRenderResetScope
{
None,
TemporalHistoryOnly,
TemporalHistoryAndFeedback
};
enum class RuntimeEventShaderBuildPhase
{
Requested,
Prepared,
Applied,
Failed
};
struct OscValueReceivedEvent
{
std::string routeKey;
std::string layerKey;
std::string parameterKey;
std::string valueJson;
uint64_t generation = 0;
};
struct OscValueCoalescedEvent
{
std::string routeKey;
std::size_t coalescedCount = 0;
uint64_t latestGeneration = 0;
};
struct OscCommitRequestedEvent
{
std::string routeKey;
std::string layerKey;
std::string parameterKey;
std::string valueJson;
uint64_t generation = 0;
};
struct HttpControlMutationRequestedEvent
{
std::string method;
std::string path;
std::string bodyJson;
};
struct WebSocketClientConnectedEvent
{
std::string clientId;
std::size_t connectedClientCount = 0;
};
struct RuntimeStateBroadcastRequestedEvent
{
std::string reason;
bool coalescable = true;
};
struct FileChangeDetectedEvent
{
std::string path;
bool shaderPackageCandidate = false;
bool runtimeConfigCandidate = false;
bool presetCandidate = false;
};
struct ManualReloadRequestedEvent
{
bool preserveFeedbackState = false;
std::string reason;
};
struct RuntimeMutationEvent
{
std::string action;
bool accepted = false;
bool runtimeStateChanged = false;
bool runtimeStateBroadcastRequired = false;
bool shaderBuildRequested = false;
bool persistenceRequested = false;
bool clearTransientOscState = false;
RuntimeEventRenderResetScope renderResetScope = RuntimeEventRenderResetScope::None;
std::string errorMessage;
};
struct RuntimeStateChangedEvent
{
std::string reason;
bool renderVisible = false;
bool persistenceRequested = false;
};
struct RuntimePersistenceRequestedEvent
{
std::string reason;
bool debounceAllowed = true;
};
struct RuntimeReloadRequestedEvent
{
bool preserveFeedbackState = false;
std::string reason;
};
struct ShaderPackagesChangedEvent
{
bool registryChanged = false;
std::size_t packageCount = 0;
std::string reason;
};
struct RenderSnapshotPublishRequestedEvent
{
unsigned inputWidth = 0;
unsigned inputHeight = 0;
unsigned outputWidth = 0;
unsigned outputHeight = 0;
std::string reason;
};
struct RuntimeStatePresentationChangedEvent
{
std::string reason;
};
struct ShaderBuildEvent
{
RuntimeEventShaderBuildPhase phase = RuntimeEventShaderBuildPhase::Requested;
unsigned inputWidth = 0;
unsigned inputHeight = 0;
bool preserveFeedbackState = false;
bool succeeded = false;
std::string message;
};
struct CompileStatusChangedEvent
{
bool succeeded = false;
std::string message;
};
struct RenderSnapshotPublishedEvent
{
uint64_t snapshotVersion = 0;
uint64_t structureVersion = 0;
uint64_t parameterVersion = 0;
uint64_t packageVersion = 0;
unsigned outputWidth = 0;
unsigned outputHeight = 0;
std::size_t layerCount = 0;
};
struct RenderResetEvent
{
RuntimeEventRenderResetScope scope = RuntimeEventRenderResetScope::None;
bool applied = false;
std::string reason;
};
struct OscOverlayEvent
{
std::string routeKey;
std::string layerKey;
std::string parameterKey;
uint64_t generation = 0;
bool settled = false;
};
struct FrameRenderedEvent
{
uint64_t frameIndex = 0;
double renderMilliseconds = 0.0;
};
struct PreviewFrameAvailableEvent
{
uint64_t frameIndex = 0;
unsigned width = 0;
unsigned height = 0;
};
struct InputSignalChangedEvent
{
bool hasSignal = false;
unsigned width = 0;
unsigned height = 0;
std::string modeName;
};
struct InputFrameArrivedEvent
{
uint64_t frameIndex = 0;
unsigned width = 0;
unsigned height = 0;
long rowBytes = 0;
std::string pixelFormat;
bool hasNoInputSource = false;
};
struct OutputFrameScheduledEvent
{
uint64_t frameIndex = 0;
int64_t streamTime = 0;
int64_t duration = 0;
int64_t timeScale = 0;
};
struct OutputFrameCompletedEvent
{
uint64_t frameIndex = 0;
std::string result;
};
struct BackendStateChangedEvent
{
std::string backendName;
std::string state;
std::string message;
};
struct SubsystemWarningEvent
{
std::string subsystem;
std::string warningKey;
RuntimeEventSeverity severity = RuntimeEventSeverity::Warning;
std::string message;
bool cleared = false;
};
struct SubsystemRecoveredEvent
{
std::string subsystem;
std::string recoveryKey;
std::string message;
};
struct TimingSampleRecordedEvent
{
std::string subsystem;
std::string metric;
double value = 0.0;
std::string unit;
};
struct QueueDepthChangedEvent
{
std::string queueName;
std::size_t depth = 0;
std::size_t capacity = 0;
std::size_t droppedCount = 0;
std::size_t coalescedCount = 0;
};
constexpr RuntimeEventType RuntimeEventPayloadType(const OscValueReceivedEvent&)
{
return RuntimeEventType::OscValueReceived;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const OscValueCoalescedEvent&)
{
return RuntimeEventType::OscValueCoalesced;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const OscCommitRequestedEvent&)
{
return RuntimeEventType::OscCommitRequested;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const HttpControlMutationRequestedEvent&)
{
return RuntimeEventType::HttpControlMutationRequested;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const WebSocketClientConnectedEvent&)
{
return RuntimeEventType::WebSocketClientConnected;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const RuntimeStateBroadcastRequestedEvent&)
{
return RuntimeEventType::RuntimeStateBroadcastRequested;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const FileChangeDetectedEvent&)
{
return RuntimeEventType::FileChangeDetected;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const ManualReloadRequestedEvent&)
{
return RuntimeEventType::ManualReloadRequested;
}
inline RuntimeEventType RuntimeEventPayloadType(const RuntimeMutationEvent& event)
{
return event.accepted ? RuntimeEventType::RuntimeMutationAccepted : RuntimeEventType::RuntimeMutationRejected;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const RuntimeStateChangedEvent&)
{
return RuntimeEventType::RuntimeStateChanged;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const RuntimePersistenceRequestedEvent&)
{
return RuntimeEventType::RuntimePersistenceRequested;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const RuntimeReloadRequestedEvent&)
{
return RuntimeEventType::RuntimeReloadRequested;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const ShaderPackagesChangedEvent&)
{
return RuntimeEventType::ShaderPackagesChanged;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const RenderSnapshotPublishRequestedEvent&)
{
return RuntimeEventType::RenderSnapshotPublishRequested;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const RuntimeStatePresentationChangedEvent&)
{
return RuntimeEventType::RuntimeStatePresentationChanged;
}
inline RuntimeEventType RuntimeEventPayloadType(const ShaderBuildEvent& event)
{
switch (event.phase)
{
case RuntimeEventShaderBuildPhase::Requested:
return RuntimeEventType::ShaderBuildRequested;
case RuntimeEventShaderBuildPhase::Prepared:
return RuntimeEventType::ShaderBuildPrepared;
case RuntimeEventShaderBuildPhase::Applied:
return RuntimeEventType::ShaderBuildApplied;
case RuntimeEventShaderBuildPhase::Failed:
return RuntimeEventType::ShaderBuildFailed;
}
return RuntimeEventType::ShaderBuildRequested;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const CompileStatusChangedEvent&)
{
return RuntimeEventType::CompileStatusChanged;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const RenderSnapshotPublishedEvent&)
{
return RuntimeEventType::RenderSnapshotPublished;
}
inline RuntimeEventType RuntimeEventPayloadType(const RenderResetEvent& event)
{
return event.applied ? RuntimeEventType::RenderResetApplied : RuntimeEventType::RenderResetRequested;
}
inline RuntimeEventType RuntimeEventPayloadType(const OscOverlayEvent& event)
{
return event.settled ? RuntimeEventType::OscOverlaySettled : RuntimeEventType::OscOverlayApplied;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const FrameRenderedEvent&)
{
return RuntimeEventType::FrameRendered;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const PreviewFrameAvailableEvent&)
{
return RuntimeEventType::PreviewFrameAvailable;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const InputSignalChangedEvent&)
{
return RuntimeEventType::InputSignalChanged;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const InputFrameArrivedEvent&)
{
return RuntimeEventType::InputFrameArrived;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const OutputFrameScheduledEvent&)
{
return RuntimeEventType::OutputFrameScheduled;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const OutputFrameCompletedEvent&)
{
return RuntimeEventType::OutputFrameCompleted;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const BackendStateChangedEvent&)
{
return RuntimeEventType::BackendStateChanged;
}
inline RuntimeEventType RuntimeEventPayloadType(const SubsystemWarningEvent& event)
{
return event.cleared ? RuntimeEventType::SubsystemWarningCleared : RuntimeEventType::SubsystemWarningRaised;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const SubsystemRecoveredEvent&)
{
return RuntimeEventType::SubsystemRecovered;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const TimingSampleRecordedEvent&)
{
return RuntimeEventType::TimingSampleRecorded;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const QueueDepthChangedEvent&)
{
return RuntimeEventType::QueueDepthChanged;
}

View File

@@ -0,0 +1,99 @@
#pragma once
#include "RuntimeEvent.h"
#include <chrono>
#include <cstddef>
#include <deque>
#include <mutex>
#include <vector>
struct RuntimeEventQueueMetrics
{
std::size_t depth = 0;
std::size_t capacity = 0;
std::size_t droppedCount = 0;
double oldestEventAgeMilliseconds = 0.0;
};
class RuntimeEventQueue
{
public:
explicit RuntimeEventQueue(std::size_t capacity = 1024) :
mCapacity(capacity)
{
}
bool Push(RuntimeEvent event)
{
std::lock_guard<std::mutex> lock(mMutex);
if (mEvents.size() >= mCapacity)
{
++mDroppedCount;
return false;
}
mEvents.push_back(std::move(event));
return true;
}
bool TryPop(RuntimeEvent& event)
{
std::lock_guard<std::mutex> lock(mMutex);
if (mEvents.empty())
return false;
event = std::move(mEvents.front());
mEvents.pop_front();
return true;
}
std::vector<RuntimeEvent> Drain(std::size_t maxEvents = 0)
{
std::vector<RuntimeEvent> events;
std::lock_guard<std::mutex> lock(mMutex);
const std::size_t count = maxEvents == 0 || maxEvents > mEvents.size() ? mEvents.size() : maxEvents;
events.reserve(count);
for (std::size_t index = 0; index < count; ++index)
{
events.push_back(std::move(mEvents.front()));
mEvents.pop_front();
}
return events;
}
RuntimeEventQueueMetrics GetMetrics(std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()) const
{
std::lock_guard<std::mutex> lock(mMutex);
RuntimeEventQueueMetrics metrics;
metrics.depth = mEvents.size();
metrics.capacity = mCapacity;
metrics.droppedCount = mDroppedCount;
if (!mEvents.empty())
{
const auto age = now - mEvents.front().createdAt;
metrics.oldestEventAgeMilliseconds = std::chrono::duration<double, std::milli>(age).count();
}
return metrics;
}
std::size_t Depth() const
{
std::lock_guard<std::mutex> lock(mMutex);
return mEvents.size();
}
std::size_t Capacity() const
{
return mCapacity;
}
private:
mutable std::mutex mMutex;
std::deque<RuntimeEvent> mEvents;
std::size_t mCapacity = 0;
std::size_t mDroppedCount = 0;
};

View File

@@ -0,0 +1,151 @@
#pragma once
#include <string_view>
enum class RuntimeEventType
{
Unknown = 0,
// Control ingress.
OscValueReceived,
OscValueCoalesced,
OscCommitRequested,
HttpControlMutationRequested,
WebSocketClientConnected,
RuntimeStateBroadcastRequested,
FileChangeDetected,
ManualReloadRequested,
// Runtime policy and state.
RuntimeMutationAccepted,
RuntimeMutationRejected,
RuntimeStateChanged,
RuntimePersistenceRequested,
RuntimeReloadRequested,
ShaderPackagesChanged,
RenderSnapshotPublishRequested,
RuntimeStatePresentationChanged,
// Shader build lifecycle.
ShaderBuildRequested,
ShaderBuildPrepared,
ShaderBuildApplied,
ShaderBuildFailed,
CompileStatusChanged,
// Render lifecycle.
RenderSnapshotPublished,
RenderResetRequested,
RenderResetApplied,
OscOverlayApplied,
OscOverlaySettled,
FrameRendered,
PreviewFrameAvailable,
// Video backend lifecycle.
InputSignalChanged,
InputFrameArrived,
OutputFrameScheduled,
OutputFrameCompleted,
OutputLateFrameDetected,
OutputDroppedFrameDetected,
BackendStateChanged,
// Health and telemetry.
SubsystemWarningRaised,
SubsystemWarningCleared,
SubsystemRecovered,
TimingSampleRecorded,
QueueDepthChanged
};
constexpr std::string_view RuntimeEventTypeName(RuntimeEventType type)
{
switch (type)
{
case RuntimeEventType::Unknown:
return "Unknown";
case RuntimeEventType::OscValueReceived:
return "OscValueReceived";
case RuntimeEventType::OscValueCoalesced:
return "OscValueCoalesced";
case RuntimeEventType::OscCommitRequested:
return "OscCommitRequested";
case RuntimeEventType::HttpControlMutationRequested:
return "HttpControlMutationRequested";
case RuntimeEventType::WebSocketClientConnected:
return "WebSocketClientConnected";
case RuntimeEventType::RuntimeStateBroadcastRequested:
return "RuntimeStateBroadcastRequested";
case RuntimeEventType::FileChangeDetected:
return "FileChangeDetected";
case RuntimeEventType::ManualReloadRequested:
return "ManualReloadRequested";
case RuntimeEventType::RuntimeMutationAccepted:
return "RuntimeMutationAccepted";
case RuntimeEventType::RuntimeMutationRejected:
return "RuntimeMutationRejected";
case RuntimeEventType::RuntimeStateChanged:
return "RuntimeStateChanged";
case RuntimeEventType::RuntimePersistenceRequested:
return "RuntimePersistenceRequested";
case RuntimeEventType::RuntimeReloadRequested:
return "RuntimeReloadRequested";
case RuntimeEventType::ShaderPackagesChanged:
return "ShaderPackagesChanged";
case RuntimeEventType::RenderSnapshotPublishRequested:
return "RenderSnapshotPublishRequested";
case RuntimeEventType::RuntimeStatePresentationChanged:
return "RuntimeStatePresentationChanged";
case RuntimeEventType::ShaderBuildRequested:
return "ShaderBuildRequested";
case RuntimeEventType::ShaderBuildPrepared:
return "ShaderBuildPrepared";
case RuntimeEventType::ShaderBuildApplied:
return "ShaderBuildApplied";
case RuntimeEventType::ShaderBuildFailed:
return "ShaderBuildFailed";
case RuntimeEventType::CompileStatusChanged:
return "CompileStatusChanged";
case RuntimeEventType::RenderSnapshotPublished:
return "RenderSnapshotPublished";
case RuntimeEventType::RenderResetRequested:
return "RenderResetRequested";
case RuntimeEventType::RenderResetApplied:
return "RenderResetApplied";
case RuntimeEventType::OscOverlayApplied:
return "OscOverlayApplied";
case RuntimeEventType::OscOverlaySettled:
return "OscOverlaySettled";
case RuntimeEventType::FrameRendered:
return "FrameRendered";
case RuntimeEventType::PreviewFrameAvailable:
return "PreviewFrameAvailable";
case RuntimeEventType::InputSignalChanged:
return "InputSignalChanged";
case RuntimeEventType::InputFrameArrived:
return "InputFrameArrived";
case RuntimeEventType::OutputFrameScheduled:
return "OutputFrameScheduled";
case RuntimeEventType::OutputFrameCompleted:
return "OutputFrameCompleted";
case RuntimeEventType::OutputLateFrameDetected:
return "OutputLateFrameDetected";
case RuntimeEventType::OutputDroppedFrameDetected:
return "OutputDroppedFrameDetected";
case RuntimeEventType::BackendStateChanged:
return "BackendStateChanged";
case RuntimeEventType::SubsystemWarningRaised:
return "SubsystemWarningRaised";
case RuntimeEventType::SubsystemWarningCleared:
return "SubsystemWarningCleared";
case RuntimeEventType::SubsystemRecovered:
return "SubsystemRecovered";
case RuntimeEventType::TimingSampleRecorded:
return "TimingSampleRecorded";
case RuntimeEventType::QueueDepthChanged:
return "QueueDepthChanged";
}
return "Unknown";
}