From a9b08f7f272c6f21fd714d691dcc7fb774d4a9c4 Mon Sep 17 00:00:00 2001 From: Aiden <68633820+awils27@users.noreply.github.com> Date: Mon, 11 May 2026 15:42:14 +1000 Subject: [PATCH] dispatch event intergration --- CMakeLists.txt | 17 ++ .../control/ControlServices.cpp | 57 +++++ .../control/ControlServices.h | 3 + .../gl/OpenGLComposite.cpp | 2 +- .../gl/RuntimeUpdateController.cpp | 241 +++++++++++++++-- .../gl/RuntimeUpdateController.h | 22 ++ .../gl/shader/ShaderBuildQueue.cpp | 50 +++- .../gl/shader/ShaderBuildQueue.h | 7 +- .../coordination/RuntimeCoordinator.cpp | 242 +++++++++++++++--- .../runtime/coordination/RuntimeCoordinator.h | 2 + .../runtime/events/RuntimeEventDispatcher.h | 4 + .../runtime/events/RuntimeEventPayloads.h | 1 + .../presentation/RuntimeStatePresenter.cpp | 20 ++ .../runtime/telemetry/HealthTelemetry.cpp | 65 +++++ .../runtime/telemetry/HealthTelemetry.h | 39 +++ tests/HealthTelemetryTests.cpp | 72 ++++++ 16 files changed, 785 insertions(+), 59 deletions(-) create mode 100644 tests/HealthTelemetryTests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 2581b7b..82f971a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -230,6 +230,23 @@ endif() add_test(NAME RuntimeClockTests COMMAND RuntimeClockTests) +add_executable(HealthTelemetryTests + "${APP_DIR}/runtime/telemetry/HealthTelemetry.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/tests/HealthTelemetryTests.cpp" +) + +target_include_directories(HealthTelemetryTests PRIVATE + "${APP_DIR}" + "${APP_DIR}/runtime" + "${APP_DIR}/runtime/telemetry" +) + +if(MSVC) + target_compile_options(HealthTelemetryTests PRIVATE /W3) +endif() + +add_test(NAME HealthTelemetryTests COMMAND HealthTelemetryTests) + add_executable(RuntimeParameterUtilsTests "${APP_DIR}/runtime/support/RuntimeJson.cpp" "${APP_DIR}/runtime/support/RuntimeParameterUtils.cpp" diff --git a/apps/LoopThroughWithOpenGLCompositing/control/ControlServices.cpp b/apps/LoopThroughWithOpenGLCompositing/control/ControlServices.cpp index d7784e8..4a4be87 100644 --- a/apps/LoopThroughWithOpenGLCompositing/control/ControlServices.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/control/ControlServices.cpp @@ -3,6 +3,7 @@ #include "ControlServer.h" #include "OscServer.h" #include "RuntimeControlBridge.h" +#include "RuntimeEventDispatcher.h" #include "RuntimeStore.h" #include @@ -56,6 +57,8 @@ void ControlServices::BroadcastState() void ControlServices::RequestBroadcastState() { + PublishRuntimeStateBroadcastRequested("control-service-request"); + if (mControlServer) mControlServer->RequestBroadcastState(); } @@ -105,6 +108,7 @@ bool ControlServices::ApplyPendingOscUpdates(std::vector& appl appliedUpdate.parameterKey = entry.second.parameterKey; appliedUpdate.targetValue = targetValue; appliedUpdates.push_back(std::move(appliedUpdate)); + PublishOscValueReceived(entry.second, entry.first); } (void)error; @@ -195,6 +199,7 @@ void ControlServices::PollLoop(RuntimeCoordinator& runtimeCoordinator) } for (const auto& entry : pendingCommits) { + PublishOscCommitRequested(entry.second); const RuntimeCoordinatorResult result = runtimeCoordinator.CommitOscParameterByControlKey( entry.second.layerKey, entry.second.parameterKey, @@ -233,3 +238,55 @@ void ControlServices::QueueRuntimeCoordinatorResult(const RuntimeCoordinatorResu std::lock_guard lock(mRuntimeCoordinatorResultMutex); mRuntimeCoordinatorResults.push_back(std::move(serviceResult)); } + +void ControlServices::PublishRuntimeStateBroadcastRequested(const std::string& reason) +{ + try + { + RuntimeStateBroadcastRequestedEvent event; + event.reason = reason; + if (!mRuntimeEventDispatcher.PublishPayload(event, "ControlServices")) + OutputDebugStringA("RuntimeStateBroadcastRequested event publish failed.\n"); + } + catch (...) + { + OutputDebugStringA("RuntimeStateBroadcastRequested event publish threw.\n"); + } +} + +void ControlServices::PublishOscValueReceived(const PendingOscUpdate& update, const std::string& routeKey) +{ + try + { + OscValueReceivedEvent event; + event.routeKey = routeKey; + event.layerKey = update.layerKey; + event.parameterKey = update.parameterKey; + event.valueJson = update.valueJson; + if (!mRuntimeEventDispatcher.PublishPayload(event, "ControlServices")) + OutputDebugStringA("OscValueReceived event publish failed.\n"); + } + catch (...) + { + OutputDebugStringA("OscValueReceived event publish threw.\n"); + } +} + +void ControlServices::PublishOscCommitRequested(const PendingOscCommit& commit) +{ + try + { + OscCommitRequestedEvent event; + event.routeKey = commit.routeKey; + event.layerKey = commit.layerKey; + event.parameterKey = commit.parameterKey; + event.valueJson = SerializeJson(commit.value, false); + event.generation = commit.generation; + if (!mRuntimeEventDispatcher.PublishPayload(event, "ControlServices")) + OutputDebugStringA("OscCommitRequested event publish failed.\n"); + } + catch (...) + { + OutputDebugStringA("OscCommitRequested event publish threw.\n"); + } +} diff --git a/apps/LoopThroughWithOpenGLCompositing/control/ControlServices.h b/apps/LoopThroughWithOpenGLCompositing/control/ControlServices.h index eef344a..5f4a4fd 100644 --- a/apps/LoopThroughWithOpenGLCompositing/control/ControlServices.h +++ b/apps/LoopThroughWithOpenGLCompositing/control/ControlServices.h @@ -77,6 +77,9 @@ private: void StopPolling(); void PollLoop(RuntimeCoordinator& runtimeCoordinator); void QueueRuntimeCoordinatorResult(const RuntimeCoordinatorResult& result, bool failed = false); + void PublishRuntimeStateBroadcastRequested(const std::string& reason); + void PublishOscValueReceived(const PendingOscUpdate& update, const std::string& routeKey); + void PublishOscCommitRequested(const PendingOscCommit& commit); std::unique_ptr mControlServer; std::unique_ptr mOscServer; diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp index fcfb45f..f15677f 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp @@ -50,7 +50,7 @@ OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) : [this]() { ProcessScreenshotRequest(); }, [this]() { paintGL(false); }); mVideoBackend = std::make_unique(*mRenderEngine, mRuntimeStore->GetHealthTelemetry()); - mShaderBuildQueue = std::make_unique(*mRuntimeSnapshotProvider); + mShaderBuildQueue = std::make_unique(*mRuntimeSnapshotProvider, *mRuntimeEventDispatcher); mRuntimeServices = std::make_unique(*mRuntimeEventDispatcher); mRuntimeUpdateController = std::make_unique( *mRuntimeStore, diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/RuntimeUpdateController.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/RuntimeUpdateController.cpp index 286482a..434ed93 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/RuntimeUpdateController.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/RuntimeUpdateController.cpp @@ -7,8 +7,26 @@ #include "ShaderBuildQueue.h" #include "VideoBackend.h" +#include #include +namespace +{ +RuntimeCoordinatorRenderResetScope ToRuntimeCoordinatorRenderResetScope(RuntimeEventRenderResetScope scope) +{ + switch (scope) + { + case RuntimeEventRenderResetScope::TemporalHistoryOnly: + return RuntimeCoordinatorRenderResetScope::TemporalHistoryOnly; + case RuntimeEventRenderResetScope::TemporalHistoryAndFeedback: + return RuntimeCoordinatorRenderResetScope::TemporalHistoryAndFeedback; + case RuntimeEventRenderResetScope::None: + default: + return RuntimeCoordinatorRenderResetScope::None; + } +} +} + RuntimeUpdateController::RuntimeUpdateController( RuntimeStore& runtimeStore, RuntimeCoordinator& runtimeCoordinator, @@ -28,6 +46,21 @@ RuntimeUpdateController::RuntimeUpdateController( mRuntimeEventDispatcher.Subscribe( RuntimeEventType::RuntimeStateBroadcastRequested, [this](const RuntimeEvent& event) { HandleRuntimeStateBroadcastRequested(event); }); + mRuntimeEventDispatcher.Subscribe( + RuntimeEventType::ShaderBuildRequested, + [this](const RuntimeEvent& event) { HandleShaderBuildRequested(event); }); + mRuntimeEventDispatcher.Subscribe( + RuntimeEventType::ShaderBuildPrepared, + [this](const RuntimeEvent& event) { HandleShaderBuildPrepared(event); }); + mRuntimeEventDispatcher.Subscribe( + RuntimeEventType::ShaderBuildFailed, + [this](const RuntimeEvent& event) { HandleShaderBuildFailed(event); }); + mRuntimeEventDispatcher.Subscribe( + RuntimeEventType::CompileStatusChanged, + [this](const RuntimeEvent& event) { HandleCompileStatusChanged(event); }); + mRuntimeEventDispatcher.Subscribe( + RuntimeEventType::RenderResetRequested, + [this](const RuntimeEvent& event) { HandleRenderResetRequested(event); }); } bool RuntimeUpdateController::ApplyRuntimeCoordinatorResult(const RuntimeCoordinatorResult& result, std::string* error) @@ -40,7 +73,10 @@ bool RuntimeUpdateController::ApplyRuntimeCoordinatorResult(const RuntimeCoordin } if (result.compileStatusChanged) + { mRuntimeStore.SetCompileStatus(result.compileStatusSucceeded, result.compileStatusMessage); + ++mPendingCoordinatorCompileStatusEvents; + } if (result.clearReloadRequest) mRuntimeStore.ClearReloadRequest(); @@ -54,9 +90,14 @@ bool RuntimeUpdateController::ApplyRuntimeCoordinatorResult(const RuntimeCoordin } mRenderEngine.ApplyRuntimeCoordinatorRenderReset(result.renderResetScope); + if (result.renderResetScope != RuntimeCoordinatorRenderResetScope::None) + ++mPendingCoordinatorRenderResetEvents; if (result.shaderBuildRequested) + { RequestShaderBuild(); + ++mPendingCoordinatorShaderBuildEvents; + } if (result.runtimeStateBroadcastRequired) BroadcastRuntimeState(); @@ -66,8 +107,6 @@ bool RuntimeUpdateController::ApplyRuntimeCoordinatorResult(const RuntimeCoordin bool RuntimeUpdateController::ProcessRuntimeWork() { - mRuntimeEventDispatcher.DispatchPending(); - bool shaderBuildRequested = false; std::vector serviceResults; mRuntimeServices.ConsumeRuntimeCoordinatorResults(serviceResults); @@ -82,22 +121,9 @@ bool RuntimeUpdateController::ProcessRuntimeWork() if (shaderBuildRequested) return true; - const RenderEngine::PreparedShaderBuildApplyResult buildResult = mRenderEngine.TryApplyReadyShaderBuild( - mShaderBuildQueue, - mVideoBackend.InputFrameWidth(), - mVideoBackend.InputFrameHeight(), - mRuntimeCoordinator.PreserveFeedbackOnNextShaderBuild()); - if (!buildResult.hadReadyBuild) - return true; + DispatchRuntimeEvents(); - if (!buildResult.applied) - { - ApplyRuntimeCoordinatorResult(mRuntimeCoordinator.HandlePreparedShaderBuildFailure(buildResult.errorMessage)); - return false; - } - - ApplyRuntimeCoordinatorResult(mRuntimeCoordinator.HandlePreparedShaderBuildSuccess()); - return true; + return ConsumeReadyShaderBuild(0, true, true); } void RuntimeUpdateController::RequestShaderBuild() @@ -115,11 +141,188 @@ void RuntimeUpdateController::BroadcastRuntimeState() return; } - mRuntimeEventDispatcher.DispatchPending(); + DispatchRuntimeEvents(); } void RuntimeUpdateController::HandleRuntimeStateBroadcastRequested(const RuntimeEvent& event) { - (void)event; + if (event.source == "ControlServices") + return; + mRuntimeServices.BroadcastState(); } + +void RuntimeUpdateController::HandleShaderBuildRequested(const RuntimeEvent& event) +{ + const ShaderBuildEvent* payload = std::get_if(&event.payload); + if (!payload || payload->phase != RuntimeEventShaderBuildPhase::Requested) + return; + if (ShouldSuppressCoordinatorFollowUp(event, mPendingCoordinatorShaderBuildEvents)) + return; + + RequestShaderBuild(); +} + +void RuntimeUpdateController::HandleShaderBuildPrepared(const RuntimeEvent& event) +{ + const ShaderBuildEvent* payload = std::get_if(&event.payload); + if (!payload || payload->phase != RuntimeEventShaderBuildPhase::Prepared) + return; + + ConsumeReadyShaderBuild(payload->generation, false, true); +} + +void RuntimeUpdateController::HandleShaderBuildFailed(const RuntimeEvent& event) +{ + const ShaderBuildEvent* payload = std::get_if(&event.payload); + if (!payload || payload->phase != RuntimeEventShaderBuildPhase::Failed) + return; + + ConsumeReadyShaderBuild(payload->generation, false, false); +} + +void RuntimeUpdateController::HandleCompileStatusChanged(const RuntimeEvent& event) +{ + const CompileStatusChangedEvent* payload = std::get_if(&event.payload); + if (!payload) + return; + if (ShouldSuppressCoordinatorFollowUp(event, mPendingCoordinatorCompileStatusEvents)) + return; + + mRuntimeStore.SetCompileStatus(payload->succeeded, payload->message); +} + +void RuntimeUpdateController::HandleRenderResetRequested(const RuntimeEvent& event) +{ + const RenderResetEvent* payload = std::get_if(&event.payload); + if (!payload || payload->applied) + return; + if (ShouldSuppressCoordinatorFollowUp(event, mPendingCoordinatorRenderResetEvents)) + return; + + mRenderEngine.ApplyRuntimeCoordinatorRenderReset(ToRuntimeCoordinatorRenderResetScope(payload->scope)); +} + +bool RuntimeUpdateController::ConsumeReadyShaderBuild(uint64_t expectedGeneration, bool publishPreparedEvent, bool publishFailureEvent) +{ + PreparedShaderBuild readyBuild; + const bool consumed = expectedGeneration == 0 + ? mShaderBuildQueue.TryConsumeReadyBuild(readyBuild) + : mShaderBuildQueue.TryConsumeReadyBuild(expectedGeneration, readyBuild); + if (!consumed) + return true; + + const unsigned inputWidth = mVideoBackend.InputFrameWidth(); + const unsigned inputHeight = mVideoBackend.InputFrameHeight(); + if (!readyBuild.succeeded) + { + if (publishFailureEvent) + { + PublishShaderBuildLifecycleEvent( + RuntimeEventShaderBuildPhase::Failed, + readyBuild.generation, + inputWidth, + inputHeight, + false, + readyBuild.message); + DispatchRuntimeEvents(); + } + ApplyRuntimeCoordinatorResult(mRuntimeCoordinator.HandlePreparedShaderBuildFailure(readyBuild.message)); + return false; + } + + if (publishPreparedEvent) + { + PublishShaderBuildLifecycleEvent( + RuntimeEventShaderBuildPhase::Prepared, + readyBuild.generation, + inputWidth, + inputHeight, + true, + readyBuild.message); + DispatchRuntimeEvents(); + } + + char compilerErrorMessage[1024] = {}; + if (!mRenderEngine.ApplyPreparedShaderBuild( + readyBuild, + inputWidth, + inputHeight, + mRuntimeCoordinator.PreserveFeedbackOnNextShaderBuild(), + sizeof(compilerErrorMessage), + compilerErrorMessage)) + { + const std::string errorMessage = compilerErrorMessage; + if (publishFailureEvent) + { + PublishShaderBuildLifecycleEvent( + RuntimeEventShaderBuildPhase::Failed, + readyBuild.generation, + inputWidth, + inputHeight, + false, + errorMessage); + DispatchRuntimeEvents(); + } + ApplyRuntimeCoordinatorResult(mRuntimeCoordinator.HandlePreparedShaderBuildFailure(errorMessage)); + return false; + } + + PublishShaderBuildLifecycleEvent( + RuntimeEventShaderBuildPhase::Applied, + readyBuild.generation, + inputWidth, + inputHeight, + true, + "Shader layers applied successfully."); + ApplyRuntimeCoordinatorResult(mRuntimeCoordinator.HandlePreparedShaderBuildSuccess()); + return true; +} + +void RuntimeUpdateController::PublishShaderBuildLifecycleEvent( + RuntimeEventShaderBuildPhase phase, + uint64_t generation, + unsigned inputWidth, + unsigned inputHeight, + bool succeeded, + const std::string& message) +{ + ShaderBuildEvent event; + event.phase = phase; + event.generation = generation; + event.inputWidth = inputWidth; + event.inputHeight = inputHeight; + event.preserveFeedbackState = mRuntimeCoordinator.PreserveFeedbackOnNextShaderBuild(); + event.succeeded = succeeded; + event.message = message; + mRuntimeEventDispatcher.PublishPayload(event, "RuntimeUpdateController"); +} + +bool RuntimeUpdateController::ShouldSuppressCoordinatorFollowUp(const RuntimeEvent& event, std::size_t& pendingSuppressions) +{ + if (event.source != "RuntimeCoordinator") + return false; + + if (pendingSuppressions > 0) + --pendingSuppressions; + return true; +} + +RuntimeEventDispatchResult RuntimeUpdateController::DispatchRuntimeEvents(std::size_t maxEvents) +{ + RuntimeEventDispatchResult result = mRuntimeEventDispatcher.DispatchPending(maxEvents); + const RuntimeEventQueueMetrics queueMetrics = mRuntimeEventDispatcher.GetQueueMetrics(); + HealthTelemetry& telemetry = mRuntimeStore.GetHealthTelemetry(); + telemetry.TryRecordRuntimeEventDispatchStats( + result.dispatchedEvents, + result.handlerInvocations, + result.handlerFailures, + result.dispatchDurationMilliseconds); + telemetry.TryRecordRuntimeEventQueueMetrics( + "runtime-events", + queueMetrics.depth, + queueMetrics.capacity, + static_cast(queueMetrics.droppedCount), + queueMetrics.oldestEventAgeMilliseconds); + return result; +} diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/RuntimeUpdateController.h b/apps/LoopThroughWithOpenGLCompositing/gl/RuntimeUpdateController.h index f30a0f9..20ca169 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/RuntimeUpdateController.h +++ b/apps/LoopThroughWithOpenGLCompositing/gl/RuntimeUpdateController.h @@ -1,11 +1,15 @@ #pragma once #include "RuntimeCoordinator.h" +#include "RuntimeEventPayloads.h" +#include +#include #include class RenderEngine; struct RuntimeEvent; +struct RuntimeEventDispatchResult; class RuntimeEventDispatcher; class RuntimeServices; class RuntimeStore; @@ -31,6 +35,21 @@ public: private: void HandleRuntimeStateBroadcastRequested(const RuntimeEvent& event); + void HandleShaderBuildRequested(const RuntimeEvent& event); + void HandleShaderBuildPrepared(const RuntimeEvent& event); + void HandleShaderBuildFailed(const RuntimeEvent& event); + void HandleCompileStatusChanged(const RuntimeEvent& event); + void HandleRenderResetRequested(const RuntimeEvent& event); + bool ConsumeReadyShaderBuild(uint64_t expectedGeneration, bool publishPreparedEvent, bool publishFailureEvent); + void PublishShaderBuildLifecycleEvent( + RuntimeEventShaderBuildPhase phase, + uint64_t generation, + unsigned inputWidth, + unsigned inputHeight, + bool succeeded, + const std::string& message); + bool ShouldSuppressCoordinatorFollowUp(const RuntimeEvent& event, std::size_t& pendingSuppressions); + RuntimeEventDispatchResult DispatchRuntimeEvents(std::size_t maxEvents = 0); RuntimeStore& mRuntimeStore; RuntimeCoordinator& mRuntimeCoordinator; @@ -39,4 +58,7 @@ private: RenderEngine& mRenderEngine; ShaderBuildQueue& mShaderBuildQueue; VideoBackend& mVideoBackend; + std::size_t mPendingCoordinatorShaderBuildEvents = 0; + std::size_t mPendingCoordinatorCompileStatusEvents = 0; + std::size_t mPendingCoordinatorRenderResetEvents = 0; }; diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/shader/ShaderBuildQueue.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/shader/ShaderBuildQueue.cpp index 9c88b35..ede2f1d 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/shader/ShaderBuildQueue.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/shader/ShaderBuildQueue.cpp @@ -1,5 +1,7 @@ #include "ShaderBuildQueue.h" +#include "RuntimeEventDispatcher.h" + #include #include @@ -8,8 +10,9 @@ namespace constexpr auto kShaderBuildDebounce = std::chrono::milliseconds(400); } -ShaderBuildQueue::ShaderBuildQueue(RuntimeSnapshotProvider& runtimeSnapshotProvider) : +ShaderBuildQueue::ShaderBuildQueue(RuntimeSnapshotProvider& runtimeSnapshotProvider, RuntimeEventDispatcher& runtimeEventDispatcher) : mRuntimeSnapshotProvider(runtimeSnapshotProvider), + mRuntimeEventDispatcher(runtimeEventDispatcher), mWorkerThread([this]() { WorkerLoop(); }) { } @@ -44,6 +47,18 @@ bool ShaderBuildQueue::TryConsumeReadyBuild(PreparedShaderBuild& build) return true; } +bool ShaderBuildQueue::TryConsumeReadyBuild(uint64_t expectedGeneration, PreparedShaderBuild& build) +{ + std::lock_guard lock(mMutex); + if (!mHasReadyBuild || mReadyBuild.generation != expectedGeneration) + return false; + + build = std::move(mReadyBuild); + mReadyBuild = PreparedShaderBuild(); + mHasReadyBuild = false; + return true; +} + void ShaderBuildQueue::Stop() { { @@ -97,13 +112,20 @@ void ShaderBuildQueue::WorkerLoop() PreparedShaderBuild build = Build(generation, outputWidth, outputHeight); - std::lock_guard lock(mMutex); - if (mStopping) - return; - if (generation != mRequestedGeneration) - continue; - mReadyBuild = std::move(build); - mHasReadyBuild = true; + bool shouldPublish = false; + { + std::lock_guard lock(mMutex); + if (mStopping) + return; + if (generation != mRequestedGeneration) + continue; + mReadyBuild = build; + mHasReadyBuild = true; + shouldPublish = true; + } + + if (shouldPublish) + PublishBuildLifecycleEvent(build, outputWidth, outputHeight); } } @@ -130,3 +152,15 @@ PreparedShaderBuild ShaderBuildQueue::Build(uint64_t generation, unsigned output build.message = "Shader layers prepared successfully."; return build; } + +void ShaderBuildQueue::PublishBuildLifecycleEvent(const PreparedShaderBuild& build, unsigned outputWidth, unsigned outputHeight) const +{ + ShaderBuildEvent event; + event.phase = build.succeeded ? RuntimeEventShaderBuildPhase::Prepared : RuntimeEventShaderBuildPhase::Failed; + event.generation = build.generation; + event.inputWidth = outputWidth; + event.inputHeight = outputHeight; + event.succeeded = build.succeeded; + event.message = build.message; + mRuntimeEventDispatcher.PublishPayload(event, "ShaderBuildQueue"); +} diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/shader/ShaderBuildQueue.h b/apps/LoopThroughWithOpenGLCompositing/gl/shader/ShaderBuildQueue.h index ea36368..a4b2092 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/shader/ShaderBuildQueue.h +++ b/apps/LoopThroughWithOpenGLCompositing/gl/shader/ShaderBuildQueue.h @@ -10,6 +10,8 @@ #include #include +class RuntimeEventDispatcher; + struct PreparedLayerShader { RuntimeRenderState state; @@ -28,7 +30,7 @@ struct PreparedShaderBuild class ShaderBuildQueue { public: - explicit ShaderBuildQueue(RuntimeSnapshotProvider& runtimeSnapshotProvider); + ShaderBuildQueue(RuntimeSnapshotProvider& runtimeSnapshotProvider, RuntimeEventDispatcher& runtimeEventDispatcher); ~ShaderBuildQueue(); ShaderBuildQueue(const ShaderBuildQueue&) = delete; @@ -36,13 +38,16 @@ public: void RequestBuild(unsigned outputWidth, unsigned outputHeight); bool TryConsumeReadyBuild(PreparedShaderBuild& build); + bool TryConsumeReadyBuild(uint64_t expectedGeneration, PreparedShaderBuild& build); void Stop(); private: void WorkerLoop(); PreparedShaderBuild Build(uint64_t generation, unsigned outputWidth, unsigned outputHeight); + void PublishBuildLifecycleEvent(const PreparedShaderBuild& build, unsigned outputWidth, unsigned outputHeight) const; RuntimeSnapshotProvider& mRuntimeSnapshotProvider; + RuntimeEventDispatcher& mRuntimeEventDispatcher; std::thread mWorkerThread; std::mutex mMutex; std::condition_variable mCondition; diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/coordination/RuntimeCoordinator.cpp b/apps/LoopThroughWithOpenGLCompositing/runtime/coordination/RuntimeCoordinator.cpp index 4b6770c..151866f 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/coordination/RuntimeCoordinator.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/coordination/RuntimeCoordinator.cpp @@ -1,8 +1,27 @@ #include "RuntimeCoordinator.h" +#include "RuntimeEventDispatcher.h" +#include "RuntimeEventPayloads.h" #include "RuntimeParameterUtils.h" #include "RuntimeStore.h" +namespace +{ +RuntimeEventRenderResetScope ToRuntimeEventRenderResetScope(RuntimeCoordinatorRenderResetScope scope) +{ + switch (scope) + { + case RuntimeCoordinatorRenderResetScope::TemporalHistoryOnly: + return RuntimeEventRenderResetScope::TemporalHistoryOnly; + case RuntimeCoordinatorRenderResetScope::TemporalHistoryAndFeedback: + return RuntimeEventRenderResetScope::TemporalHistoryAndFeedback; + case RuntimeCoordinatorRenderResetScope::None: + default: + return RuntimeEventRenderResetScope::None; + } +} +} + RuntimeCoordinator::RuntimeCoordinator(RuntimeStore& runtimeStore, RuntimeEventDispatcher& runtimeEventDispatcher) : mRuntimeStore(runtimeStore), mRuntimeEventDispatcher(runtimeEventDispatcher) @@ -14,9 +33,15 @@ RuntimeCoordinatorResult RuntimeCoordinator::AddLayer(const std::string& shaderI std::lock_guard lock(mMutex); std::string error; if (!ValidateShaderExists(shaderId, error)) - return ApplyStoreMutation(false, error, false, false); + { + RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false); + PublishCoordinatorResult("AddLayer", result); + return result; + } - return ApplyStoreMutation(mRuntimeStore.CreateStoredLayer(shaderId, error), error, true, true); + RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.CreateStoredLayer(shaderId, error), error, true, true); + PublishCoordinatorResult("AddLayer", result); + return result; } RuntimeCoordinatorResult RuntimeCoordinator::RemoveLayer(const std::string& layerId) @@ -24,9 +49,15 @@ RuntimeCoordinatorResult RuntimeCoordinator::RemoveLayer(const std::string& laye std::lock_guard lock(mMutex); std::string error; if (!ValidateLayerExists(layerId, error)) - return ApplyStoreMutation(false, error, false, false); + { + RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false); + PublishCoordinatorResult("RemoveLayer", result); + return result; + } - return ApplyStoreMutation(mRuntimeStore.DeleteStoredLayer(layerId, error), error, true, true); + RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.DeleteStoredLayer(layerId, error), error, true, true); + PublishCoordinatorResult("RemoveLayer", result); + return result; } RuntimeCoordinatorResult RuntimeCoordinator::MoveLayer(const std::string& layerId, int direction) @@ -35,11 +66,20 @@ RuntimeCoordinatorResult RuntimeCoordinator::MoveLayer(const std::string& layerI std::string error; bool shouldMove = false; if (!ResolveLayerMove(layerId, direction, shouldMove, error)) - return ApplyStoreMutation(false, error, false, false); + { + RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false); + PublishCoordinatorResult("MoveLayer", result); + return result; + } if (!shouldMove) - return BuildAcceptedNoReloadResult(); + { + RuntimeCoordinatorResult result = BuildAcceptedNoReloadResult(); + return result; + } - return ApplyStoreMutation(mRuntimeStore.MoveStoredLayer(layerId, direction, error), error, true, true); + RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.MoveStoredLayer(layerId, direction, error), error, true, true); + PublishCoordinatorResult("MoveLayer", result); + return result; } RuntimeCoordinatorResult RuntimeCoordinator::MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex) @@ -48,11 +88,20 @@ RuntimeCoordinatorResult RuntimeCoordinator::MoveLayerToIndex(const std::string& std::string error; bool shouldMove = false; if (!ResolveLayerMoveToIndex(layerId, targetIndex, shouldMove, error)) - return ApplyStoreMutation(false, error, false, false); + { + RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false); + PublishCoordinatorResult("MoveLayerToIndex", result); + return result; + } if (!shouldMove) - return BuildAcceptedNoReloadResult(); + { + RuntimeCoordinatorResult result = BuildAcceptedNoReloadResult(); + return result; + } - return ApplyStoreMutation(mRuntimeStore.MoveStoredLayerToIndex(layerId, targetIndex, error), error, true, true); + RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.MoveStoredLayerToIndex(layerId, targetIndex, error), error, true, true); + PublishCoordinatorResult("MoveLayerToIndex", result); + return result; } RuntimeCoordinatorResult RuntimeCoordinator::SetLayerBypass(const std::string& layerId, bool bypassed) @@ -60,9 +109,15 @@ RuntimeCoordinatorResult RuntimeCoordinator::SetLayerBypass(const std::string& l std::lock_guard lock(mMutex); std::string error; if (!ValidateLayerExists(layerId, error)) - return ApplyStoreMutation(false, error, false, false); + { + RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false); + PublishCoordinatorResult("SetLayerBypass", result); + return result; + } - return ApplyStoreMutation(mRuntimeStore.SetStoredLayerBypassState(layerId, bypassed, error), error, true, false); + RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.SetStoredLayerBypassState(layerId, bypassed, error), error, true, false); + PublishCoordinatorResult("SetLayerBypass", result); + return result; } RuntimeCoordinatorResult RuntimeCoordinator::SetLayerShader(const std::string& layerId, const std::string& shaderId) @@ -70,9 +125,15 @@ RuntimeCoordinatorResult RuntimeCoordinator::SetLayerShader(const std::string& l std::lock_guard lock(mMutex); std::string error; if (!ValidateLayerExists(layerId, error) || !ValidateShaderExists(shaderId, error)) - return ApplyStoreMutation(false, error, false, false); + { + RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false); + PublishCoordinatorResult("SetLayerShader", result); + return result; + } - return ApplyStoreMutation(mRuntimeStore.SetStoredLayerShaderSelection(layerId, shaderId, error), error, true, false); + RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.SetStoredLayerShaderSelection(layerId, shaderId, error), error, true, false); + PublishCoordinatorResult("SetLayerShader", result); + return result; } RuntimeCoordinatorResult RuntimeCoordinator::UpdateLayerParameter(const std::string& layerId, const std::string& parameterId, const JsonValue& newValue) @@ -81,9 +142,15 @@ RuntimeCoordinatorResult RuntimeCoordinator::UpdateLayerParameter(const std::str std::string error; ResolvedParameterMutation mutation; if (!BuildParameterMutationById(layerId, parameterId, newValue, true, mutation, error)) - return ApplyStoreMutation(false, error, false, false); + { + RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false); + PublishCoordinatorResult("UpdateLayerParameter", result); + return result; + } - return ApplyStoreMutation(mRuntimeStore.SetStoredParameterValue(mutation.layerId, mutation.parameterId, mutation.value, mutation.persistState, error), error, false, false); + RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.SetStoredParameterValue(mutation.layerId, mutation.parameterId, mutation.value, mutation.persistState, error), error, false, false); + PublishCoordinatorResult("UpdateLayerParameter", result); + return result; } RuntimeCoordinatorResult RuntimeCoordinator::UpdateLayerParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue) @@ -92,9 +159,15 @@ RuntimeCoordinatorResult RuntimeCoordinator::UpdateLayerParameterByControlKey(co std::string error; ResolvedParameterMutation mutation; if (!BuildParameterMutationByControlKey(layerKey, parameterKey, newValue, true, mutation, error)) - return ApplyStoreMutation(false, error, false, false); + { + RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false); + PublishCoordinatorResult("UpdateLayerParameterByControlKey", result); + return result; + } - return ApplyStoreMutation(mRuntimeStore.SetStoredParameterValue(mutation.layerId, mutation.parameterId, mutation.value, mutation.persistState, error), error, false, false); + RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.SetStoredParameterValue(mutation.layerId, mutation.parameterId, mutation.value, mutation.persistState, error), error, false, false); + PublishCoordinatorResult("UpdateLayerParameterByControlKey", result); + return result; } RuntimeCoordinatorResult RuntimeCoordinator::CommitOscParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue) @@ -103,9 +176,15 @@ RuntimeCoordinatorResult RuntimeCoordinator::CommitOscParameterByControlKey(cons std::string error; ResolvedParameterMutation mutation; if (!BuildParameterMutationByControlKey(layerKey, parameterKey, newValue, false, mutation, error)) - return ApplyStoreMutation(false, error, false, false); + { + RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false); + PublishCoordinatorResult("CommitOscParameterByControlKey", result); + return result; + } - return ApplyStoreMutation(mRuntimeStore.SetStoredParameterValue(mutation.layerId, mutation.parameterId, mutation.value, mutation.persistState, error), error, false, false); + RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.SetStoredParameterValue(mutation.layerId, mutation.parameterId, mutation.value, mutation.persistState, error), error, false, false); + PublishCoordinatorResult("CommitOscParameterByControlKey", result); + return result; } RuntimeCoordinatorResult RuntimeCoordinator::ResetLayerParameters(const std::string& layerId) @@ -113,14 +192,22 @@ RuntimeCoordinatorResult RuntimeCoordinator::ResetLayerParameters(const std::str std::lock_guard lock(mMutex); std::string error; if (!ValidateLayerExists(layerId, error)) - return ApplyStoreMutation(false, error, false, false); + { + RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false); + PublishCoordinatorResult("ResetLayerParameters", result); + return result; + } RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.ResetStoredLayerParameterValues(layerId, error), error, false, false); if (!result.accepted) + { + PublishCoordinatorResult("ResetLayerParameters", result); return result; + } result.clearTransientOscState = true; result.renderResetScope = RuntimeCoordinatorRenderResetScope::TemporalHistoryAndFeedback; + PublishCoordinatorResult("ResetLayerParameters", result); return result; } @@ -129,9 +216,15 @@ RuntimeCoordinatorResult RuntimeCoordinator::SaveStackPreset(const std::string& std::lock_guard lock(mMutex); std::string error; if (!ValidatePresetName(presetName, error)) - return ApplyStoreMutation(false, error, false, false); + { + RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false); + PublishCoordinatorResult("SaveStackPreset", result); + return result; + } - return ApplyStoreMutation(mRuntimeStore.SaveStackPresetSnapshot(presetName, error), error, false, false); + RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.SaveStackPresetSnapshot(presetName, error), error, false, false); + PublishCoordinatorResult("SaveStackPreset", result); + return result; } RuntimeCoordinatorResult RuntimeCoordinator::LoadStackPreset(const std::string& presetName) @@ -139,15 +232,23 @@ RuntimeCoordinatorResult RuntimeCoordinator::LoadStackPreset(const std::string& std::lock_guard lock(mMutex); std::string error; if (!ValidatePresetName(presetName, error)) - return ApplyStoreMutation(false, error, false, false); + { + RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false); + PublishCoordinatorResult("LoadStackPreset", result); + return result; + } - return ApplyStoreMutation(mRuntimeStore.LoadStackPresetSnapshot(presetName, error), error, true, false); + RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.LoadStackPresetSnapshot(presetName, error), error, true, false); + PublishCoordinatorResult("LoadStackPreset", result); + return result; } RuntimeCoordinatorResult RuntimeCoordinator::RequestShaderReload(bool preserveFeedbackState) { std::lock_guard lock(mMutex); - return BuildQueuedReloadResult(preserveFeedbackState); + RuntimeCoordinatorResult result = BuildQueuedReloadResult(preserveFeedbackState); + PublishCoordinatorFollowUpEvents("RequestShaderReload", result); + return result; } RuntimeCoordinatorResult RuntimeCoordinator::PollRuntimeStoreChanges(bool& registryChanged) @@ -158,13 +259,24 @@ RuntimeCoordinatorResult RuntimeCoordinator::PollRuntimeStoreChanges(bool& regis bool reloadRequested = false; std::string error; if (!mRuntimeStore.PollStoredFileChanges(registryChanged, reloadRequested, error)) - return HandleRuntimePollFailure(error); + { + RuntimeCoordinatorResult result = HandleRuntimePollFailure(error); + return result; + } if (reloadRequested) - return BuildQueuedReloadResult(false); + { + RuntimeCoordinatorResult result = BuildQueuedReloadResult(false); + PublishCoordinatorFollowUpEvents("PollRuntimeStoreChanges", result); + return result; + } if (registryChanged) - return BuildAcceptedNoReloadResult(); + { + RuntimeCoordinatorResult result = BuildAcceptedNoReloadResult(); + PublishCoordinatorFollowUpEvents("PollRuntimeStoreChanges", result); + return result; + } RuntimeCoordinatorResult result; result.accepted = true; @@ -179,6 +291,7 @@ RuntimeCoordinatorResult RuntimeCoordinator::HandleRuntimePollFailure(const std: result.compileStatusChanged = true; result.compileStatusSucceeded = false; result.compileStatusMessage = error; + PublishCoordinatorFollowUpEvents("HandleRuntimePollFailure", result); return result; } @@ -195,6 +308,7 @@ RuntimeCoordinatorResult RuntimeCoordinator::HandlePreparedShaderBuildFailure(co result.compileStatusSucceeded = false; result.compileStatusMessage = error; result.committedStateMode = RuntimeCoordinatorCommittedStateMode::UseCommittedStates; + PublishCoordinatorFollowUpEvents("HandlePreparedShaderBuildFailure", result); return result; } @@ -211,13 +325,16 @@ RuntimeCoordinatorResult RuntimeCoordinator::HandlePreparedShaderBuildSuccess() result.compileStatusMessage = "Shader layers compiled successfully."; result.committedStateMode = RuntimeCoordinatorCommittedStateMode::UseLiveSnapshots; mPreserveFeedbackOnNextShaderBuild = false; + PublishCoordinatorFollowUpEvents("HandlePreparedShaderBuildSuccess", result); return result; } RuntimeCoordinatorResult RuntimeCoordinator::HandleRuntimeReloadRequest() { std::lock_guard lock(mMutex); - return BuildQueuedReloadResult(false); + RuntimeCoordinatorResult result = BuildQueuedReloadResult(false); + PublishCoordinatorFollowUpEvents("HandleRuntimeReloadRequest", result); + return result; } void RuntimeCoordinator::ApplyCommittedStateMode(RuntimeCoordinatorCommittedStateMode mode) @@ -369,3 +486,68 @@ RuntimeCoordinatorResult RuntimeCoordinator::BuildAcceptedNoReloadResult() const result.runtimeStateBroadcastRequired = true; return result; } + +void RuntimeCoordinator::PublishCoordinatorResult(const std::string& action, const RuntimeCoordinatorResult& result) const +{ + try + { + RuntimeMutationEvent mutation; + mutation.action = action; + mutation.accepted = result.accepted; + mutation.runtimeStateChanged = result.accepted && result.runtimeStateBroadcastRequired; + mutation.runtimeStateBroadcastRequired = result.runtimeStateBroadcastRequired; + mutation.shaderBuildRequested = result.shaderBuildRequested; + mutation.clearTransientOscState = result.clearTransientOscState; + mutation.renderResetScope = ToRuntimeEventRenderResetScope(result.renderResetScope); + mutation.errorMessage = result.errorMessage; + mRuntimeEventDispatcher.PublishPayload(mutation, "RuntimeCoordinator"); + + PublishCoordinatorFollowUpEvents(action, result); + } + catch (...) + { + } +} + +void RuntimeCoordinator::PublishCoordinatorFollowUpEvents(const std::string& action, const RuntimeCoordinatorResult& result) const +{ + try + { + if (!result.accepted) + return; + + if (result.runtimeStateBroadcastRequired) + { + RuntimeStateChangedEvent stateChanged; + stateChanged.reason = action; + stateChanged.renderVisible = result.renderResetScope != RuntimeCoordinatorRenderResetScope::None; + mRuntimeEventDispatcher.PublishPayload(stateChanged, "RuntimeCoordinator"); + } + + if (result.shaderBuildRequested) + { + RuntimeReloadRequestedEvent reloadRequested; + reloadRequested.preserveFeedbackState = mPreserveFeedbackOnNextShaderBuild; + reloadRequested.reason = action; + mRuntimeEventDispatcher.PublishPayload(reloadRequested, "RuntimeCoordinator"); + + ShaderBuildEvent shaderBuild; + shaderBuild.phase = RuntimeEventShaderBuildPhase::Requested; + shaderBuild.preserveFeedbackState = mPreserveFeedbackOnNextShaderBuild; + shaderBuild.succeeded = true; + shaderBuild.message = result.compileStatusMessage; + mRuntimeEventDispatcher.PublishPayload(shaderBuild, "RuntimeCoordinator"); + } + + if (result.compileStatusChanged) + { + CompileStatusChangedEvent compileStatus; + compileStatus.succeeded = result.compileStatusSucceeded; + compileStatus.message = result.compileStatusMessage; + mRuntimeEventDispatcher.PublishPayload(compileStatus, "RuntimeCoordinator"); + } + } + catch (...) + { + } +} diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/coordination/RuntimeCoordinator.h b/apps/LoopThroughWithOpenGLCompositing/runtime/coordination/RuntimeCoordinator.h index 26033c1..7d4c3ee 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/coordination/RuntimeCoordinator.h +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/coordination/RuntimeCoordinator.h @@ -92,6 +92,8 @@ private: RuntimeCoordinatorResult ApplyStoreMutation(bool succeeded, const std::string& errorMessage, bool reloadRequired, bool preserveFeedbackState); RuntimeCoordinatorResult BuildQueuedReloadResult(bool preserveFeedbackState); RuntimeCoordinatorResult BuildAcceptedNoReloadResult() const; + void PublishCoordinatorResult(const std::string& action, const RuntimeCoordinatorResult& result) const; + void PublishCoordinatorFollowUpEvents(const std::string& action, const RuntimeCoordinatorResult& result) const; RuntimeStore& mRuntimeStore; RuntimeEventDispatcher& mRuntimeEventDispatcher; diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/events/RuntimeEventDispatcher.h b/apps/LoopThroughWithOpenGLCompositing/runtime/events/RuntimeEventDispatcher.h index f479d63..4f42322 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/events/RuntimeEventDispatcher.h +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/events/RuntimeEventDispatcher.h @@ -13,6 +13,7 @@ struct RuntimeEventDispatchResult std::size_t dispatchedEvents = 0; std::size_t handlerInvocations = 0; std::size_t handlerFailures = 0; + double dispatchDurationMilliseconds = 0.0; }; class RuntimeEventDispatcher @@ -56,6 +57,7 @@ public: RuntimeEventDispatchResult DispatchPending(std::size_t maxEvents = 0) { + const auto startedAt = std::chrono::steady_clock::now(); RuntimeEventDispatchResult result; std::vector events = mQueue.Drain(maxEvents); result.dispatchedEvents = events.size(); @@ -78,6 +80,8 @@ public: } } + result.dispatchDurationMilliseconds = + std::chrono::duration(std::chrono::steady_clock::now() - startedAt).count(); return result; } diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/events/RuntimeEventPayloads.h b/apps/LoopThroughWithOpenGLCompositing/runtime/events/RuntimeEventPayloads.h index 15f0e5d..b09efd7 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/events/RuntimeEventPayloads.h +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/events/RuntimeEventPayloads.h @@ -143,6 +143,7 @@ struct RuntimeStatePresentationChangedEvent struct ShaderBuildEvent { RuntimeEventShaderBuildPhase phase = RuntimeEventShaderBuildPhase::Requested; + uint64_t generation = 0; unsigned inputWidth = 0; unsigned inputHeight = 0; bool preserveFeedbackState = false; diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/presentation/RuntimeStatePresenter.cpp b/apps/LoopThroughWithOpenGLCompositing/runtime/presentation/RuntimeStatePresenter.cpp index 431235d..6c15e68 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/presentation/RuntimeStatePresenter.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/presentation/RuntimeStatePresenter.cpp @@ -80,6 +80,26 @@ JsonValue RuntimeStatePresenter::BuildRuntimeStateValue(const RuntimeStore& runt performance.set("flushedFrameCount", JsonValue(static_cast(telemetrySnapshot.performance.flushedFrameCount))); root.set("performance", performance); + JsonValue eventQueue = JsonValue::MakeObject(); + eventQueue.set("name", JsonValue(telemetrySnapshot.runtimeEvents.queue.queueName)); + eventQueue.set("depth", JsonValue(static_cast(telemetrySnapshot.runtimeEvents.queue.depth))); + eventQueue.set("capacity", JsonValue(static_cast(telemetrySnapshot.runtimeEvents.queue.capacity))); + eventQueue.set("droppedCount", JsonValue(static_cast(telemetrySnapshot.runtimeEvents.queue.droppedCount))); + eventQueue.set("oldestEventAgeMs", JsonValue(telemetrySnapshot.runtimeEvents.queue.oldestEventAgeMilliseconds)); + + JsonValue eventDispatch = JsonValue::MakeObject(); + eventDispatch.set("dispatchCallCount", JsonValue(static_cast(telemetrySnapshot.runtimeEvents.dispatch.dispatchCallCount))); + eventDispatch.set("dispatchedEventCount", JsonValue(static_cast(telemetrySnapshot.runtimeEvents.dispatch.dispatchedEventCount))); + eventDispatch.set("handlerInvocationCount", JsonValue(static_cast(telemetrySnapshot.runtimeEvents.dispatch.handlerInvocationCount))); + eventDispatch.set("handlerFailureCount", JsonValue(static_cast(telemetrySnapshot.runtimeEvents.dispatch.handlerFailureCount))); + eventDispatch.set("lastDispatchDurationMs", JsonValue(telemetrySnapshot.runtimeEvents.dispatch.lastDispatchDurationMilliseconds)); + eventDispatch.set("maxDispatchDurationMs", JsonValue(telemetrySnapshot.runtimeEvents.dispatch.maxDispatchDurationMilliseconds)); + + JsonValue runtimeEvents = JsonValue::MakeObject(); + runtimeEvents.set("queue", eventQueue); + runtimeEvents.set("dispatch", eventDispatch); + root.set("runtimeEvents", runtimeEvents); + JsonValue shaderLibrary = JsonValue::MakeArray(); for (const ShaderPackageStatus& status : model.packageStatuses) { diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/telemetry/HealthTelemetry.cpp b/apps/LoopThroughWithOpenGLCompositing/runtime/telemetry/HealthTelemetry.cpp index 77cbc85..92e81e2 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/telemetry/HealthTelemetry.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/telemetry/HealthTelemetry.cpp @@ -111,6 +111,64 @@ bool HealthTelemetry::TryRecordFramePacingStats(double completionIntervalMillise return true; } +void HealthTelemetry::RecordRuntimeEventQueueMetrics(const std::string& queueName, std::size_t depth, std::size_t capacity, + uint64_t droppedCount, double oldestEventAgeMilliseconds) +{ + std::lock_guard lock(mMutex); + mRuntimeEvents.queue.queueName = queueName; + mRuntimeEvents.queue.depth = depth; + mRuntimeEvents.queue.capacity = capacity; + mRuntimeEvents.queue.droppedCount = droppedCount; + mRuntimeEvents.queue.oldestEventAgeMilliseconds = std::max(oldestEventAgeMilliseconds, 0.0); +} + +bool HealthTelemetry::TryRecordRuntimeEventQueueMetrics(const std::string& queueName, std::size_t depth, std::size_t capacity, + uint64_t droppedCount, double oldestEventAgeMilliseconds) +{ + std::unique_lock lock(mMutex, std::try_to_lock); + if (!lock.owns_lock()) + return false; + + mRuntimeEvents.queue.queueName = queueName; + mRuntimeEvents.queue.depth = depth; + mRuntimeEvents.queue.capacity = capacity; + mRuntimeEvents.queue.droppedCount = droppedCount; + mRuntimeEvents.queue.oldestEventAgeMilliseconds = std::max(oldestEventAgeMilliseconds, 0.0); + return true; +} + +void HealthTelemetry::RecordRuntimeEventDispatchStats(std::size_t dispatchedEvents, std::size_t handlerInvocations, + std::size_t handlerFailures, double dispatchDurationMilliseconds) +{ + std::lock_guard lock(mMutex); + ++mRuntimeEvents.dispatch.dispatchCallCount; + mRuntimeEvents.dispatch.dispatchedEventCount += static_cast(dispatchedEvents); + mRuntimeEvents.dispatch.handlerInvocationCount += static_cast(handlerInvocations); + mRuntimeEvents.dispatch.handlerFailureCount += static_cast(handlerFailures); + mRuntimeEvents.dispatch.lastDispatchDurationMilliseconds = std::max(dispatchDurationMilliseconds, 0.0); + mRuntimeEvents.dispatch.maxDispatchDurationMilliseconds = std::max( + mRuntimeEvents.dispatch.maxDispatchDurationMilliseconds, + mRuntimeEvents.dispatch.lastDispatchDurationMilliseconds); +} + +bool HealthTelemetry::TryRecordRuntimeEventDispatchStats(std::size_t dispatchedEvents, std::size_t handlerInvocations, + std::size_t handlerFailures, double dispatchDurationMilliseconds) +{ + std::unique_lock lock(mMutex, std::try_to_lock); + if (!lock.owns_lock()) + return false; + + ++mRuntimeEvents.dispatch.dispatchCallCount; + mRuntimeEvents.dispatch.dispatchedEventCount += static_cast(dispatchedEvents); + mRuntimeEvents.dispatch.handlerInvocationCount += static_cast(handlerInvocations); + mRuntimeEvents.dispatch.handlerFailureCount += static_cast(handlerFailures); + mRuntimeEvents.dispatch.lastDispatchDurationMilliseconds = std::max(dispatchDurationMilliseconds, 0.0); + mRuntimeEvents.dispatch.maxDispatchDurationMilliseconds = std::max( + mRuntimeEvents.dispatch.maxDispatchDurationMilliseconds, + mRuntimeEvents.dispatch.lastDispatchDurationMilliseconds); + return true; +} + HealthTelemetry::SignalStatusSnapshot HealthTelemetry::GetSignalStatusSnapshot() const { std::lock_guard lock(mMutex); @@ -129,6 +187,12 @@ HealthTelemetry::PerformanceSnapshot HealthTelemetry::GetPerformanceSnapshot() c return mPerformance; } +HealthTelemetry::RuntimeEventMetricsSnapshot HealthTelemetry::GetRuntimeEventMetricsSnapshot() const +{ + std::lock_guard lock(mMutex); + return mRuntimeEvents; +} + HealthTelemetry::Snapshot HealthTelemetry::GetSnapshot() const { std::lock_guard lock(mMutex); @@ -137,5 +201,6 @@ HealthTelemetry::Snapshot HealthTelemetry::GetSnapshot() const snapshot.signal = mSignalStatus; snapshot.videoIO = mVideoIOStatus; snapshot.performance = mPerformance; + snapshot.runtimeEvents = mRuntimeEvents; return snapshot; } diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/telemetry/HealthTelemetry.h b/apps/LoopThroughWithOpenGLCompositing/runtime/telemetry/HealthTelemetry.h index b0319f6..19c1098 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/telemetry/HealthTelemetry.h +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/telemetry/HealthTelemetry.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -43,11 +44,37 @@ public: uint64_t flushedFrameCount = 0; }; + struct RuntimeEventQueueSnapshot + { + std::string queueName = "runtime-events"; + std::size_t depth = 0; + std::size_t capacity = 0; + uint64_t droppedCount = 0; + double oldestEventAgeMilliseconds = 0.0; + }; + + struct RuntimeEventDispatchSnapshot + { + uint64_t dispatchCallCount = 0; + uint64_t dispatchedEventCount = 0; + uint64_t handlerInvocationCount = 0; + uint64_t handlerFailureCount = 0; + double lastDispatchDurationMilliseconds = 0.0; + double maxDispatchDurationMilliseconds = 0.0; + }; + + struct RuntimeEventMetricsSnapshot + { + RuntimeEventQueueSnapshot queue; + RuntimeEventDispatchSnapshot dispatch; + }; + struct Snapshot { SignalStatusSnapshot signal; VideoIOStatusSnapshot videoIO; PerformanceSnapshot performance; + RuntimeEventMetricsSnapshot runtimeEvents; }; HealthTelemetry() = default; @@ -70,9 +97,20 @@ public: bool TryRecordFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds, double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount); + void RecordRuntimeEventQueueMetrics(const std::string& queueName, std::size_t depth, std::size_t capacity, + uint64_t droppedCount, double oldestEventAgeMilliseconds); + bool TryRecordRuntimeEventQueueMetrics(const std::string& queueName, std::size_t depth, std::size_t capacity, + uint64_t droppedCount, double oldestEventAgeMilliseconds); + + void RecordRuntimeEventDispatchStats(std::size_t dispatchedEvents, std::size_t handlerInvocations, + std::size_t handlerFailures, double dispatchDurationMilliseconds); + bool TryRecordRuntimeEventDispatchStats(std::size_t dispatchedEvents, std::size_t handlerInvocations, + std::size_t handlerFailures, double dispatchDurationMilliseconds); + SignalStatusSnapshot GetSignalStatusSnapshot() const; VideoIOStatusSnapshot GetVideoIOStatusSnapshot() const; PerformanceSnapshot GetPerformanceSnapshot() const; + RuntimeEventMetricsSnapshot GetRuntimeEventMetricsSnapshot() const; Snapshot GetSnapshot() const; private: @@ -80,4 +118,5 @@ private: SignalStatusSnapshot mSignalStatus; VideoIOStatusSnapshot mVideoIOStatus; PerformanceSnapshot mPerformance; + RuntimeEventMetricsSnapshot mRuntimeEvents; }; diff --git a/tests/HealthTelemetryTests.cpp b/tests/HealthTelemetryTests.cpp new file mode 100644 index 0000000..f1e9216 --- /dev/null +++ b/tests/HealthTelemetryTests.cpp @@ -0,0 +1,72 @@ +#include "HealthTelemetry.h" + +#include + +namespace +{ +int gFailures = 0; + +void Expect(bool condition, const char* message) +{ + if (condition) + return; + + std::cerr << "FAIL: " << message << "\n"; + ++gFailures; +} + +void TestRuntimeEventQueueMetrics() +{ + HealthTelemetry telemetry; + telemetry.RecordRuntimeEventQueueMetrics("runtime-events", 3, 64, 2, 12.5); + + const HealthTelemetry::RuntimeEventMetricsSnapshot metrics = telemetry.GetRuntimeEventMetricsSnapshot(); + Expect(metrics.queue.queueName == "runtime-events", "queue metrics store queue name"); + Expect(metrics.queue.depth == 3, "queue metrics store depth"); + Expect(metrics.queue.capacity == 64, "queue metrics store capacity"); + Expect(metrics.queue.droppedCount == 2, "queue metrics store dropped count"); + Expect(metrics.queue.oldestEventAgeMilliseconds == 12.5, "queue metrics store oldest event age"); +} + +void TestRuntimeEventDispatchStats() +{ + HealthTelemetry telemetry; + telemetry.RecordRuntimeEventDispatchStats(2, 5, 1, 0.75); + telemetry.RecordRuntimeEventDispatchStats(3, 6, 0, 0.25); + + const HealthTelemetry::Snapshot snapshot = telemetry.GetSnapshot(); + Expect(snapshot.runtimeEvents.dispatch.dispatchCallCount == 2, "dispatch stats count dispatch calls"); + Expect(snapshot.runtimeEvents.dispatch.dispatchedEventCount == 5, "dispatch stats accumulate dispatched events"); + Expect(snapshot.runtimeEvents.dispatch.handlerInvocationCount == 11, "dispatch stats accumulate handler invocations"); + Expect(snapshot.runtimeEvents.dispatch.handlerFailureCount == 1, "dispatch stats accumulate handler failures"); + Expect(snapshot.runtimeEvents.dispatch.lastDispatchDurationMilliseconds == 0.25, "dispatch stats store latest duration"); + Expect(snapshot.runtimeEvents.dispatch.maxDispatchDurationMilliseconds == 0.75, "dispatch stats store max duration"); +} + +void TestRuntimeEventTryRecord() +{ + HealthTelemetry telemetry; + Expect(telemetry.TryRecordRuntimeEventQueueMetrics("runtime-events", 1, 4, 0, -5.0), "try queue metrics succeeds when uncontended"); + Expect(telemetry.TryRecordRuntimeEventDispatchStats(1, 2, 0, -1.0), "try dispatch stats succeeds when uncontended"); + + const HealthTelemetry::RuntimeEventMetricsSnapshot metrics = telemetry.GetRuntimeEventMetricsSnapshot(); + Expect(metrics.queue.oldestEventAgeMilliseconds == 0.0, "queue age is clamped to non-negative values"); + Expect(metrics.dispatch.lastDispatchDurationMilliseconds == 0.0, "dispatch duration is clamped to non-negative values"); +} +} + +int main() +{ + TestRuntimeEventQueueMetrics(); + TestRuntimeEventDispatchStats(); + TestRuntimeEventTryRecord(); + + if (gFailures != 0) + { + std::cerr << gFailures << " HealthTelemetry test failure(s).\n"; + return 1; + } + + std::cout << "HealthTelemetry tests passed.\n"; + return 0; +}