#include "RuntimeEvent.h" #include "RuntimeEventCoalescingQueue.h" #include "RuntimeEventDispatcher.h" #include "RuntimeEventQueue.h" #include "RuntimeEventType.h" #include "RuntimeEventPayloads.h" #include "RuntimeEventTestHarness.h" #include #include #include #include #include namespace { int gFailures = 0; void Expect(bool condition, const char* message) { if (condition) return; std::cerr << "FAIL: " << message << "\n"; ++gFailures; } void TestRuntimeEventTypeNames() { Expect(RuntimeEventTypeName(RuntimeEventType::Unknown) == "Unknown", "unknown event type has a stable name"); Expect(RuntimeEventTypeName(RuntimeEventType::OscCommitRequested) == "OscCommitRequested", "control event name is stable"); Expect(RuntimeEventTypeName(RuntimeEventType::RuntimeMutationAccepted) == "RuntimeMutationAccepted", "runtime event name is stable"); Expect(RuntimeEventTypeName(RuntimeEventType::ShaderBuildPrepared) == "ShaderBuildPrepared", "shader build event name is stable"); Expect(RuntimeEventTypeName(RuntimeEventType::RenderSnapshotPublished) == "RenderSnapshotPublished", "render event name is stable"); Expect(RuntimeEventTypeName(RuntimeEventType::BackendStateChanged) == "BackendStateChanged", "backend event name is stable"); Expect(RuntimeEventTypeName(RuntimeEventType::QueueDepthChanged) == "QueueDepthChanged", "telemetry event name is stable"); } void TestRuntimeEventPayloadTypes() { OscCommitRequestedEvent oscCommit; oscCommit.routeKey = "layer-1\namount"; oscCommit.layerKey = "layer-1"; oscCommit.parameterKey = "amount"; oscCommit.generation = 42; Expect(RuntimeEventPayloadType(oscCommit) == RuntimeEventType::OscCommitRequested, "OSC commit payload maps to OSC commit event type"); Expect(oscCommit.generation == 42, "OSC commit payload keeps generation"); RuntimeMutationEvent acceptedMutation; acceptedMutation.action = "SetLayerShader"; acceptedMutation.accepted = true; acceptedMutation.shaderBuildRequested = true; acceptedMutation.persistenceRequested = true; Expect(RuntimeEventPayloadType(acceptedMutation) == RuntimeEventType::RuntimeMutationAccepted, "accepted mutation payload maps to accepted event type"); Expect(acceptedMutation.shaderBuildRequested, "mutation payload carries shader build follow-up"); Expect(acceptedMutation.persistenceRequested, "mutation payload carries persistence follow-up"); RuntimeMutationEvent rejectedMutation; rejectedMutation.accepted = false; rejectedMutation.errorMessage = "Unknown layer."; Expect(RuntimeEventPayloadType(rejectedMutation) == RuntimeEventType::RuntimeMutationRejected, "rejected mutation payload maps to rejected event type"); Expect(rejectedMutation.errorMessage == "Unknown layer.", "mutation payload carries rejection error"); RuntimePersistenceRequestedEvent persistence; persistence.reason = "UpdateLayerParameter"; persistence.debounceAllowed = true; Expect(RuntimeEventPayloadType(persistence) == RuntimeEventType::RuntimePersistenceRequested, "runtime persistence payload maps to persistence event type"); Expect(persistence.debounceAllowed, "runtime persistence payload carries debounce policy"); FileChangeDetectedEvent fileChange; fileChange.path = "PollRuntimeStoreChanges"; fileChange.shaderPackageCandidate = true; Expect(RuntimeEventPayloadType(fileChange) == RuntimeEventType::FileChangeDetected, "file change payload maps to file change event type"); Expect(fileChange.shaderPackageCandidate, "file change payload carries shader package candidate flag"); ManualReloadRequestedEvent manualReload; manualReload.preserveFeedbackState = true; manualReload.reason = "RequestShaderReload"; Expect(RuntimeEventPayloadType(manualReload) == RuntimeEventType::ManualReloadRequested, "manual reload payload maps to manual reload event type"); Expect(manualReload.preserveFeedbackState, "manual reload payload carries feedback preservation policy"); ShaderBuildEvent preparedBuild; preparedBuild.phase = RuntimeEventShaderBuildPhase::Prepared; preparedBuild.inputWidth = 1920; preparedBuild.inputHeight = 1080; Expect(RuntimeEventPayloadType(preparedBuild) == RuntimeEventType::ShaderBuildPrepared, "shader build payload maps by phase"); Expect(preparedBuild.inputWidth == 1920 && preparedBuild.inputHeight == 1080, "shader build payload carries input dimensions"); RenderResetEvent appliedReset; appliedReset.scope = RuntimeEventRenderResetScope::TemporalHistoryAndFeedback; appliedReset.applied = true; Expect(RuntimeEventPayloadType(appliedReset) == RuntimeEventType::RenderResetApplied, "render reset payload maps applied state"); Expect(appliedReset.scope == RuntimeEventRenderResetScope::TemporalHistoryAndFeedback, "render reset payload carries reset scope"); RenderSnapshotPublishRequestedEvent snapshotRequest; snapshotRequest.outputWidth = 1920; snapshotRequest.outputHeight = 1080; snapshotRequest.reason = "test"; Expect(RuntimeEventPayloadType(snapshotRequest) == RuntimeEventType::RenderSnapshotPublishRequested, "render snapshot request payload maps to request event"); RenderSnapshotPublishedEvent snapshotPublished; snapshotPublished.snapshotVersion = 3; snapshotPublished.parameterVersion = 4; snapshotPublished.layerCount = 2; Expect(RuntimeEventPayloadType(snapshotPublished) == RuntimeEventType::RenderSnapshotPublished, "render snapshot published payload maps to published event"); Expect(snapshotPublished.layerCount == 2, "render snapshot published payload carries layer count"); OutputFrameCompletedEvent completedFrame; completedFrame.result = "Completed"; Expect(RuntimeEventPayloadType(completedFrame) == RuntimeEventType::OutputFrameCompleted, "completed output frame payload maps to completed event"); completedFrame.result = "DisplayedLate"; Expect(RuntimeEventPayloadType(completedFrame) == RuntimeEventType::OutputLateFrameDetected, "late output frame payload maps to late event"); completedFrame.result = "Dropped"; Expect(RuntimeEventPayloadType(completedFrame) == RuntimeEventType::OutputDroppedFrameDetected, "dropped output frame payload maps to dropped event"); TimingSampleRecordedEvent timingSample; timingSample.subsystem = "RuntimeEventDispatcher"; timingSample.metric = "dispatchDuration"; timingSample.value = 0.5; timingSample.unit = "ms"; Expect(RuntimeEventPayloadType(timingSample) == RuntimeEventType::TimingSampleRecorded, "timing sample payload maps to timing event"); QueueDepthChangedEvent queueDepth; queueDepth.queueName = "runtime-events"; queueDepth.depth = 1; queueDepth.capacity = 16; Expect(RuntimeEventPayloadType(queueDepth) == RuntimeEventType::QueueDepthChanged, "queue depth payload maps to queue depth event"); SubsystemWarningEvent warning; warning.subsystem = "VideoBackend"; warning.warningKey = "late-frame"; Expect(RuntimeEventPayloadType(warning) == RuntimeEventType::SubsystemWarningRaised, "warning payload maps to raised event by default"); warning.cleared = true; Expect(RuntimeEventPayloadType(warning) == RuntimeEventType::SubsystemWarningCleared, "warning payload maps to cleared event when marked cleared"); } void TestRuntimeEventEnvelope() { const auto createdAt = std::chrono::steady_clock::now(); OscCommitRequestedEvent oscCommit; oscCommit.routeKey = "layer-1\namount"; oscCommit.layerKey = "layer-1"; oscCommit.parameterKey = "amount"; oscCommit.generation = 7; RuntimeEvent event = MakeRuntimeEvent(oscCommit, "ControlServices", 12, createdAt); Expect(event.type == RuntimeEventType::OscCommitRequested, "runtime event infers type from payload"); Expect(event.sequence == 12, "runtime event stores sequence"); Expect(event.source == "ControlServices", "runtime event stores source"); Expect(event.createdAt == createdAt, "runtime event stores creation time"); Expect(event.HasPayload(), "runtime event reports payload presence"); Expect(event.PayloadMatchesType(), "runtime event payload matches inferred type"); const auto* payload = std::get_if(&event.payload); Expect(payload && payload->generation == 7, "runtime event stores typed payload in variant"); event.type = RuntimeEventType::RuntimeMutationAccepted; Expect(!event.PayloadMatchesType(), "runtime event detects mismatched payload and type"); RuntimeEvent empty; Expect(!empty.HasPayload(), "default runtime event has no payload"); Expect(empty.PayloadMatchesType(), "default runtime event unknown type matches empty payload"); RuntimeMutationEvent acceptedMutation; acceptedMutation.accepted = true; acceptedMutation.action = "SetLayerBypass"; RuntimeEvent acceptedEvent = MakeRuntimeEvent(acceptedMutation, "RuntimeCoordinator", 13, createdAt); Expect(acceptedEvent.type == RuntimeEventType::RuntimeMutationAccepted, "runtime event handles payloads with dynamic accepted mapping"); RuntimeMutationEvent rejectedMutation; rejectedMutation.accepted = false; rejectedMutation.action = "SetLayerBypass"; RuntimeEvent rejectedEvent = MakeRuntimeEvent(rejectedMutation, "RuntimeCoordinator", 14, createdAt); Expect(rejectedEvent.type == RuntimeEventType::RuntimeMutationRejected, "runtime event handles payloads with dynamic rejected mapping"); } void TestRuntimeEventQueue() { RuntimeEventQueue queue(2); const auto createdAt = std::chrono::steady_clock::now() - std::chrono::milliseconds(5); RuntimeStateBroadcastRequestedEvent firstPayload; firstPayload.reason = "first"; RuntimeStateBroadcastRequestedEvent secondPayload; secondPayload.reason = "second"; RuntimeStateBroadcastRequestedEvent thirdPayload; thirdPayload.reason = "third"; Expect(queue.Push(MakeRuntimeEvent(firstPayload, "test", 1, createdAt)), "queue accepts first event"); Expect(queue.Push(MakeRuntimeEvent(secondPayload, "test", 2, createdAt)), "queue accepts second event"); Expect(!queue.Push(MakeRuntimeEvent(thirdPayload, "test", 3, createdAt)), "queue rejects event when capacity is full"); RuntimeEventQueueMetrics fullMetrics = queue.GetMetrics(std::chrono::steady_clock::now()); Expect(fullMetrics.depth == 2, "queue metrics report depth"); Expect(fullMetrics.capacity == 2, "queue metrics report capacity"); Expect(fullMetrics.droppedCount == 1, "queue metrics report dropped count"); Expect(fullMetrics.oldestEventAgeMilliseconds >= 0.0, "queue metrics report oldest event age"); RuntimeEvent event; Expect(queue.TryPop(event), "queue pops first event"); Expect(event.sequence == 1, "queue preserves FIFO order"); std::vector drained = queue.Drain(); Expect(drained.size() == 1, "queue drains remaining event"); Expect(drained[0].sequence == 2, "drained event preserves FIFO order"); Expect(queue.Depth() == 0, "queue is empty after drain"); } void TestRuntimeEventDispatcher() { RuntimeEventDispatcher dispatcher(4); int allHandlerCount = 0; int broadcastHandlerCount = 0; int failureHandlerCount = 0; dispatcher.SubscribeAll([&](const RuntimeEvent& event) { Expect(event.sequence != 0, "dispatcher assigns sequence before all-handler dispatch"); ++allHandlerCount; }); dispatcher.Subscribe(RuntimeEventType::RuntimeStateBroadcastRequested, [&](const RuntimeEvent& event) { Expect(event.type == RuntimeEventType::RuntimeStateBroadcastRequested, "dispatcher invokes type-specific handler for matching event"); ++broadcastHandlerCount; }); dispatcher.Subscribe(RuntimeEventType::RuntimeStateBroadcastRequested, [&](const RuntimeEvent&) { ++failureHandlerCount; throw std::runtime_error("test handler failure"); }); RuntimeStateBroadcastRequestedEvent broadcast; broadcast.reason = "test"; Expect(dispatcher.PublishPayload(broadcast, "test"), "dispatcher publishes payload"); Expect(dispatcher.QueueDepth() == 1, "dispatcher reports queued depth"); RuntimeEventDispatchResult result = dispatcher.DispatchPending(); Expect(result.dispatchedEvents == 1, "dispatcher reports dispatched event count"); Expect(result.handlerInvocations == 3, "dispatcher reports handler invocation count"); Expect(result.handlerFailures == 1, "dispatcher catches and reports handler failures"); Expect(allHandlerCount == 1, "dispatcher invoked all-handler"); Expect(broadcastHandlerCount == 1, "dispatcher invoked type-specific handler"); Expect(failureHandlerCount == 1, "dispatcher invoked failing handler"); Expect(dispatcher.QueueDepth() == 0, "dispatcher queue is empty after dispatch"); RuntimeEvent mismatched = MakeRuntimeEvent(broadcast, "test"); mismatched.type = RuntimeEventType::ShaderBuildRequested; Expect(!dispatcher.Publish(mismatched), "dispatcher rejects mismatched event type and payload"); RuntimeEventDispatcher tinyDispatcher(1); RuntimeMutationEvent acceptedMutation; acceptedMutation.accepted = true; Expect(tinyDispatcher.PublishPayload(acceptedMutation, "test"), "tiny dispatcher accepts first FIFO event"); Expect(!tinyDispatcher.PublishPayload(acceptedMutation, "test"), "tiny dispatcher rejects FIFO event when queue is full"); RuntimeEventQueueMetrics metrics = tinyDispatcher.GetQueueMetrics(); Expect(metrics.droppedCount == 1, "dispatcher exposes queue drop metrics"); } void TestRuntimeEventDispatcherCoalescing() { RuntimeEventDispatcher dispatcher(4); std::string seenReason; std::string seenShaderMessage; double seenTimingValue = 0.0; int broadcastHandlerCount = 0; int shaderHandlerCount = 0; int timingHandlerCount = 0; dispatcher.Subscribe(RuntimeEventType::RuntimeStateBroadcastRequested, [&](const RuntimeEvent& event) { const auto* payload = std::get_if(&event.payload); if (payload) seenReason = payload->reason; ++broadcastHandlerCount; }); dispatcher.Subscribe(RuntimeEventType::ShaderBuildRequested, [&](const RuntimeEvent& event) { const auto* payload = std::get_if(&event.payload); if (payload) seenShaderMessage = payload->message; ++shaderHandlerCount; }); dispatcher.Subscribe(RuntimeEventType::TimingSampleRecorded, [&](const RuntimeEvent& event) { const auto* payload = std::get_if(&event.payload); if (payload) seenTimingValue = payload->value; ++timingHandlerCount; }); RuntimeStateBroadcastRequestedEvent first; first.reason = "parameter"; RuntimeStateBroadcastRequestedEvent second; second.reason = "reload"; Expect(dispatcher.PublishPayload(first, "RuntimeCoordinator"), "dispatcher accepts first coalescable event"); Expect(dispatcher.PublishPayload(second, "RuntimeCoordinator"), "dispatcher coalesces second matching event"); RuntimeEventQueueMetrics queuedMetrics = dispatcher.GetQueueMetrics(); Expect(queuedMetrics.depth == 1, "dispatcher reports coalesced event depth"); Expect(queuedMetrics.coalescedCount == 1, "dispatcher reports coalesced event count"); RuntimeEventDispatchResult result = dispatcher.DispatchPending(); Expect(result.dispatchedEvents == 1, "dispatcher dispatches one coalesced event"); Expect(broadcastHandlerCount == 1, "dispatcher invokes handler once for coalesced event"); Expect(seenReason == "reload", "dispatcher dispatches latest coalesced payload"); ShaderBuildEvent shaderFirst; shaderFirst.phase = RuntimeEventShaderBuildPhase::Requested; shaderFirst.inputWidth = 1920; shaderFirst.inputHeight = 1080; shaderFirst.preserveFeedbackState = true; shaderFirst.message = "first"; ShaderBuildEvent shaderSecond = shaderFirst; shaderSecond.message = "second"; Expect(dispatcher.PublishPayload(shaderFirst, "RuntimeCoordinator"), "dispatcher accepts first shader build request"); Expect(dispatcher.PublishPayload(shaderSecond, "RuntimeCoordinator"), "dispatcher coalesces matching shader build request"); result = dispatcher.DispatchPending(); Expect(result.dispatchedEvents == 1, "dispatcher dispatches one coalesced shader build request"); Expect(shaderHandlerCount == 1, "dispatcher invokes shader handler once for matching coalesced request"); Expect(seenShaderMessage == "second", "dispatcher dispatches latest shader build request payload"); TimingSampleRecordedEvent timingFirst; timingFirst.subsystem = "RuntimeEventDispatcher"; timingFirst.metric = "dispatchDuration"; timingFirst.value = 1.0; timingFirst.unit = "ms"; TimingSampleRecordedEvent timingSecond = timingFirst; timingSecond.value = 2.0; Expect(dispatcher.PublishPayload(timingFirst, "HealthTelemetry"), "dispatcher accepts first timing sample"); Expect(dispatcher.PublishPayload(timingSecond, "HealthTelemetry"), "dispatcher coalesces matching timing sample"); result = dispatcher.DispatchPending(); Expect(result.dispatchedEvents == 1, "dispatcher dispatches one coalesced timing sample"); Expect(timingHandlerCount == 1, "dispatcher invokes timing handler once for matching coalesced sample"); Expect(seenTimingValue == 2.0, "dispatcher dispatches latest timing sample payload"); } void TestRuntimeEventCoalescingQueue() { RuntimeEventCoalescingQueue queue(2); const auto createdAt = std::chrono::steady_clock::now() - std::chrono::milliseconds(5); OscValueReceivedEvent first; first.routeKey = "layer-1\namount"; first.layerKey = "layer-1"; first.parameterKey = "amount"; first.valueJson = "0.1"; first.generation = 1; OscValueReceivedEvent replacement = first; replacement.valueJson = "0.9"; replacement.generation = 2; OscValueReceivedEvent otherRoute; otherRoute.routeKey = "layer-2\namount"; otherRoute.layerKey = "layer-2"; otherRoute.parameterKey = "amount"; otherRoute.valueJson = "0.3"; otherRoute.generation = 3; OscValueReceivedEvent overflow; overflow.routeKey = "layer-3\namount"; overflow.layerKey = "layer-3"; overflow.parameterKey = "amount"; overflow.valueJson = "0.4"; overflow.generation = 4; Expect(queue.Push(MakeRuntimeEvent(first, "ControlServices", 1, createdAt)), "coalescing queue accepts first keyed event"); Expect(queue.Push(MakeRuntimeEvent(replacement, "ControlServices", 2, std::chrono::steady_clock::now())), "coalescing queue replaces matching keyed event"); Expect(queue.Push(MakeRuntimeEvent(otherRoute, "ControlServices", 3, createdAt)), "coalescing queue accepts second keyed event"); Expect(!queue.Push(MakeRuntimeEvent(overflow, "ControlServices", 4, createdAt)), "coalescing queue rejects new key when full"); RuntimeEventCoalescingQueueMetrics metrics = queue.GetMetrics(std::chrono::steady_clock::now()); Expect(metrics.depth == 2, "coalescing queue metrics report unique-key depth"); Expect(metrics.capacity == 2, "coalescing queue metrics report capacity"); Expect(metrics.coalescedCount == 1, "coalescing queue metrics report coalesced count"); Expect(metrics.droppedCount == 1, "coalescing queue metrics report dropped count"); Expect(metrics.oldestEventAgeMilliseconds >= 0.0, "coalescing queue metrics report oldest event age"); std::vector drained = queue.Drain(); Expect(drained.size() == 2, "coalescing queue drains unique events"); Expect(drained[0].sequence == 2, "coalescing queue keeps latest replacement event"); Expect(drained[1].sequence == 3, "coalescing queue preserves first-seen key order"); const auto* latestPayload = std::get_if(&drained[0].payload); Expect(latestPayload && latestPayload->valueJson == "0.9", "coalescing queue keeps latest payload value"); Expect(queue.Depth() == 0, "coalescing queue is empty after drain"); } void TestRuntimeEventCoalescingCustomKey() { RuntimeEventCoalescingQueue queue(4, [](const RuntimeEvent& event) { return std::string(RuntimeEventTypeName(event.type)); }); RuntimeStateBroadcastRequestedEvent first; first.reason = "parameter"; RuntimeStateBroadcastRequestedEvent second; second.reason = "reload"; Expect(queue.Push(MakeRuntimeEvent(first, "RuntimeCoordinator", 10)), "custom-key coalescing queue accepts first event"); Expect(queue.Push(MakeRuntimeEvent(second, "RuntimeCoordinator", 11)), "custom-key coalescing queue coalesces second event by type"); std::vector drained = queue.Drain(); Expect(drained.size() == 1, "custom-key coalescing queue drains one coalesced event"); Expect(drained[0].sequence == 11, "custom-key coalescing queue keeps latest event"); const auto* payload = std::get_if(&drained[0].payload); Expect(payload && payload->reason == "reload", "custom-key coalescing queue keeps latest typed payload"); } void TestRuntimeEventTestHarness() { RuntimeEventTestHarness harness; RuntimeStateBroadcastRequestedEvent broadcast; broadcast.reason = "parameter"; RuntimeEventDispatchResult firstDispatch = harness.PublishAndDispatch(broadcast, "RuntimeCoordinator"); Expect(firstDispatch.dispatchedEvents == 1, "test harness publishes and dispatches payloads"); Expect(harness.SeenCount() == 1, "test harness records dispatched events"); Expect(harness.SeenCount(RuntimeEventType::RuntimeStateBroadcastRequested) == 1, "test harness counts seen events by type"); const RuntimeEvent* seenBroadcast = harness.LastSeen(RuntimeEventType::RuntimeStateBroadcastRequested); Expect(seenBroadcast && seenBroadcast->source == "RuntimeCoordinator", "test harness returns last seen event by type"); harness.ClearSeen(); Expect(harness.SeenCount() == 0, "test harness clears seen events"); OscValueReceivedEvent first; first.routeKey = "layer-1\namount"; first.layerKey = "layer-1"; first.parameterKey = "amount"; first.valueJson = "0.1"; first.generation = 1; OscValueReceivedEvent replacement = first; replacement.valueJson = "0.8"; replacement.generation = 2; Expect(harness.PublishCoalesced(first, "ControlServices", 20), "test harness accepts first coalesced payload"); Expect(harness.PublishCoalesced(replacement, "ControlServices", 21), "test harness accepts replacement coalesced payload"); RuntimeEventDispatchResult coalescedDispatch = harness.FlushCoalescedAndDispatch(); Expect(coalescedDispatch.dispatchedEvents == 1, "test harness dispatches one coalesced event"); Expect(harness.SeenCount(RuntimeEventType::OscValueReceived) == 1, "test harness records coalesced event"); const RuntimeEvent* seenOsc = harness.LastSeen(RuntimeEventType::OscValueReceived); const auto* seenPayload = seenOsc ? std::get_if(&seenOsc->payload) : nullptr; Expect(seenPayload && seenPayload->valueJson == "0.8", "test harness keeps latest coalesced payload"); } void TestAcceptedMutationFollowUps() { RuntimeEventTestHarness harness; RuntimeMutationEvent mutation; mutation.action = "SetLayerShader"; mutation.accepted = true; mutation.runtimeStateChanged = true; mutation.runtimeStateBroadcastRequired = true; mutation.shaderBuildRequested = true; mutation.persistenceRequested = true; RuntimeStateChangedEvent stateChanged; stateChanged.reason = mutation.action; stateChanged.persistenceRequested = true; RuntimePersistenceRequestedEvent persistence; persistence.reason = mutation.action; persistence.debounceAllowed = true; RuntimeReloadRequestedEvent reload; reload.reason = mutation.action; reload.preserveFeedbackState = false; ShaderBuildEvent build; build.phase = RuntimeEventShaderBuildPhase::Requested; build.succeeded = true; build.message = "Shader rebuild queued."; Expect(harness.Publish(mutation, "RuntimeCoordinator"), "accepted mutation event publishes"); Expect(harness.Publish(stateChanged, "RuntimeCoordinator"), "state changed follow-up publishes"); Expect(harness.Publish(persistence, "RuntimeCoordinator"), "persistence follow-up publishes"); Expect(harness.Publish(reload, "RuntimeCoordinator"), "reload follow-up publishes"); Expect(harness.Publish(build, "RuntimeCoordinator"), "shader build follow-up publishes"); RuntimeEventDispatchResult result = harness.DispatchPending(); Expect(result.dispatchedEvents == 5, "accepted mutation dispatches every expected follow-up"); Expect(harness.SeenCount(RuntimeEventType::RuntimeMutationAccepted) == 1, "accepted mutation fact is observed"); Expect(harness.SeenCount(RuntimeEventType::RuntimeStateChanged) == 1, "accepted mutation publishes state changed follow-up"); Expect(harness.SeenCount(RuntimeEventType::RuntimePersistenceRequested) == 1, "accepted mutation publishes persistence follow-up"); Expect(harness.SeenCount(RuntimeEventType::RuntimeReloadRequested) == 1, "accepted mutation publishes reload follow-up"); Expect(harness.SeenCount(RuntimeEventType::ShaderBuildRequested) == 1, "accepted mutation publishes shader build follow-up"); const RuntimeEvent* persistenceEvent = harness.LastSeen(RuntimeEventType::RuntimePersistenceRequested); const auto* persistencePayload = persistenceEvent ? std::get_if(&persistenceEvent->payload) : nullptr; Expect(persistencePayload && persistencePayload->reason == "SetLayerShader", "persistence follow-up preserves mutation action reason"); } void TestAppLevelBroadcastAndBuildCoalescing() { RuntimeEventTestHarness harness; RuntimeMutationEvent firstMutation; firstMutation.action = "SetLayerShader"; firstMutation.accepted = true; firstMutation.runtimeStateChanged = true; firstMutation.runtimeStateBroadcastRequired = true; firstMutation.shaderBuildRequested = true; RuntimeMutationEvent secondMutation = firstMutation; secondMutation.action = "LoadStackPreset"; RuntimeStateBroadcastRequestedEvent firstBroadcast; firstBroadcast.reason = "SetLayerShader"; RuntimeStateBroadcastRequestedEvent secondBroadcast; secondBroadcast.reason = "LoadStackPreset"; ShaderBuildEvent firstBuild; firstBuild.phase = RuntimeEventShaderBuildPhase::Requested; firstBuild.inputWidth = 1920; firstBuild.inputHeight = 1080; firstBuild.preserveFeedbackState = false; firstBuild.message = "first build request"; ShaderBuildEvent secondBuild = firstBuild; secondBuild.message = "second build request"; Expect(harness.Publish(firstMutation, "RuntimeCoordinator"), "first accepted mutation fact publishes"); Expect(harness.Publish(firstBroadcast, "RuntimeUpdateController"), "first broadcast request publishes through app dispatcher"); Expect(harness.Publish(firstBuild, "RuntimeCoordinator"), "first shader build request publishes through app dispatcher"); Expect(harness.Publish(secondMutation, "RuntimeCoordinator"), "second accepted mutation fact publishes"); Expect(harness.Publish(secondBroadcast, "RuntimeUpdateController"), "second broadcast request coalesces through app dispatcher"); Expect(harness.Publish(secondBuild, "RuntimeCoordinator"), "second shader build request coalesces through app dispatcher"); RuntimeEventQueueMetrics metrics = harness.Dispatcher().GetQueueMetrics(); Expect(metrics.depth == 4, "app dispatcher keeps FIFO facts plus coalesced broadcast/build requests"); Expect(metrics.coalescedCount == 2, "app dispatcher reports broadcast and build coalescing"); RuntimeEventDispatchResult result = harness.DispatchPending(); Expect(result.dispatchedEvents == 4, "app dispatcher dispatches FIFO facts plus one broadcast and one build request"); Expect(harness.SeenCount(RuntimeEventType::RuntimeMutationAccepted) == 2, "app dispatcher preserves every accepted mutation fact"); Expect(harness.SeenCount(RuntimeEventType::RuntimeStateBroadcastRequested) == 1, "app dispatcher coalesces broadcast requests"); Expect(harness.SeenCount(RuntimeEventType::ShaderBuildRequested) == 1, "app dispatcher coalesces matching shader build requests"); const RuntimeEvent* broadcastEvent = harness.LastSeen(RuntimeEventType::RuntimeStateBroadcastRequested); const auto* broadcastPayload = broadcastEvent ? std::get_if(&broadcastEvent->payload) : nullptr; Expect(broadcastPayload && broadcastPayload->reason == "LoadStackPreset", "app dispatcher dispatches latest broadcast request"); const RuntimeEvent* buildEvent = harness.LastSeen(RuntimeEventType::ShaderBuildRequested); const auto* buildPayload = buildEvent ? std::get_if(&buildEvent->payload) : nullptr; Expect(buildPayload && buildPayload->message == "second build request", "app dispatcher dispatches latest shader build request"); } void TestManualReloadBridgeEvents() { RuntimeEventTestHarness harness; ManualReloadRequestedEvent manualReload; manualReload.preserveFeedbackState = true; manualReload.reason = "RequestShaderReload"; RuntimeReloadRequestedEvent runtimeReload; runtimeReload.preserveFeedbackState = true; runtimeReload.reason = "RequestShaderReload"; ShaderBuildEvent shaderBuild; shaderBuild.phase = RuntimeEventShaderBuildPhase::Requested; shaderBuild.preserveFeedbackState = true; shaderBuild.message = "Shader rebuild queued."; Expect(harness.Publish(manualReload, "RuntimeCoordinator"), "manual reload ingress event publishes"); Expect(harness.Publish(runtimeReload, "RuntimeCoordinator"), "manual reload bridge publishes runtime reload request"); Expect(harness.Publish(shaderBuild, "RuntimeCoordinator"), "manual reload bridge publishes shader build request"); RuntimeEventDispatchResult result = harness.DispatchPending(); Expect(result.dispatchedEvents == 3, "manual reload bridge dispatches ingress and follow-up events"); Expect(harness.SeenCount(RuntimeEventType::ManualReloadRequested) == 1, "manual reload ingress event is observed"); Expect(harness.SeenCount(RuntimeEventType::RuntimeReloadRequested) == 1, "manual reload runtime reload follow-up is observed"); Expect(harness.SeenCount(RuntimeEventType::ShaderBuildRequested) == 1, "manual reload shader build follow-up is observed"); const RuntimeEvent* reloadEvent = harness.LastSeen(RuntimeEventType::RuntimeReloadRequested); const auto* reloadPayload = reloadEvent ? std::get_if(&reloadEvent->payload) : nullptr; Expect(reloadPayload && reloadPayload->preserveFeedbackState, "manual reload bridge preserves feedback policy in runtime reload event"); } void TestFileReloadBridgeEvents() { RuntimeEventTestHarness harness; FileChangeDetectedEvent fileChange; fileChange.path = "PollRuntimeStoreChanges"; fileChange.shaderPackageCandidate = true; RuntimeReloadRequestedEvent runtimeReload; runtimeReload.preserveFeedbackState = false; runtimeReload.reason = "PollRuntimeStoreChanges"; ShaderBuildEvent shaderBuild; shaderBuild.phase = RuntimeEventShaderBuildPhase::Requested; shaderBuild.preserveFeedbackState = false; shaderBuild.message = "Shader rebuild queued."; Expect(harness.Publish(fileChange, "RuntimeCoordinator"), "file change ingress event publishes"); Expect(harness.Publish(runtimeReload, "RuntimeCoordinator"), "file change bridge publishes runtime reload request"); Expect(harness.Publish(shaderBuild, "RuntimeCoordinator"), "file change bridge publishes shader build request"); RuntimeEventDispatchResult result = harness.DispatchPending(); Expect(result.dispatchedEvents == 3, "file reload bridge dispatches ingress and follow-up events"); Expect(harness.SeenCount(RuntimeEventType::FileChangeDetected) == 1, "file change ingress event is observed"); Expect(harness.SeenCount(RuntimeEventType::RuntimeReloadRequested) == 1, "file reload runtime reload follow-up is observed"); Expect(harness.SeenCount(RuntimeEventType::ShaderBuildRequested) == 1, "file reload shader build follow-up is observed"); const RuntimeEvent* fileEvent = harness.LastSeen(RuntimeEventType::FileChangeDetected); const auto* filePayload = fileEvent ? std::get_if(&fileEvent->payload) : nullptr; Expect(filePayload && filePayload->shaderPackageCandidate, "file reload bridge marks shader package candidate changes"); } void TestRejectedMutationHasNoDownstreamFollowUps() { RuntimeEventTestHarness harness; RuntimeMutationEvent mutation; mutation.action = "SetLayerShader"; mutation.accepted = false; mutation.errorMessage = "Unknown shader id: missing"; Expect(harness.Publish(mutation, "RuntimeCoordinator"), "rejected mutation event publishes"); RuntimeEventDispatchResult result = harness.DispatchPending(); Expect(result.dispatchedEvents == 1, "rejected mutation dispatches only the rejection fact"); Expect(harness.SeenCount(RuntimeEventType::RuntimeMutationRejected) == 1, "rejected mutation fact is observed"); Expect(harness.SeenCount(RuntimeEventType::RuntimeStateChanged) == 0, "rejected mutation has no state follow-up"); Expect(harness.SeenCount(RuntimeEventType::RuntimePersistenceRequested) == 0, "rejected mutation has no persistence follow-up"); Expect(harness.SeenCount(RuntimeEventType::RuntimeReloadRequested) == 0, "rejected mutation has no reload follow-up"); Expect(harness.SeenCount(RuntimeEventType::ShaderBuildRequested) == 0, "rejected mutation has no shader build follow-up"); const RuntimeEvent* rejectedEvent = harness.LastSeen(RuntimeEventType::RuntimeMutationRejected); const auto* rejectedPayload = rejectedEvent ? std::get_if(&rejectedEvent->payload) : nullptr; Expect(rejectedPayload && rejectedPayload->errorMessage == "Unknown shader id: missing", "rejected mutation preserves error message"); } void TestShaderBuildGenerationEventMatching() { RuntimeEventTestHarness harness; std::size_t handledBuilds = 0; uint64_t handledGeneration = 0; harness.Dispatcher().Subscribe(RuntimeEventType::ShaderBuildPrepared, [&](const RuntimeEvent& event) { const auto* payload = std::get_if(&event.payload); if (!payload || payload->generation != 7) return; ++handledBuilds; handledGeneration = payload->generation; }); ShaderBuildEvent stale; stale.phase = RuntimeEventShaderBuildPhase::Prepared; stale.generation = 6; stale.succeeded = true; ShaderBuildEvent current = stale; current.generation = 7; Expect(harness.Publish(stale, "ShaderBuildQueue"), "stale shader build event publishes"); Expect(harness.Publish(current, "ShaderBuildQueue"), "current shader build event publishes"); RuntimeEventDispatchResult result = harness.DispatchPending(); Expect(result.dispatchedEvents == 2, "shader build readiness events dispatch in order"); Expect(harness.SeenCount(RuntimeEventType::ShaderBuildPrepared) == 2, "both shader build readiness facts are observable"); Expect(handledBuilds == 1, "generation-aware handler applies only the expected build once"); Expect(handledGeneration == 7, "generation-aware handler records the expected generation"); } void TestHandlerFailureCanBecomeTelemetryEvent() { RuntimeEventTestHarness harness; harness.Dispatcher().Subscribe(RuntimeEventType::RuntimeStateBroadcastRequested, [](const RuntimeEvent&) { throw std::runtime_error("handler failed"); }); RuntimeStateBroadcastRequestedEvent broadcast; broadcast.reason = "test"; Expect(harness.Publish(broadcast, "test"), "broadcast event publishes before failing handler"); RuntimeEventDispatchResult result = harness.DispatchPending(); Expect(result.handlerFailures == 1, "dispatcher reports handler failure for telemetry"); TimingSampleRecordedEvent timing; timing.subsystem = "RuntimeEventDispatcher"; timing.metric = "handlerFailures"; timing.value = static_cast(result.handlerFailures); timing.unit = "count"; Expect(harness.Publish(timing, "HealthTelemetry"), "handler failure timing sample publishes"); harness.DispatchPending(); Expect(harness.SeenCount(RuntimeEventType::TimingSampleRecorded) == 1, "handler failure can be observed as telemetry event"); } } int main() { TestRuntimeEventTypeNames(); TestRuntimeEventPayloadTypes(); TestRuntimeEventEnvelope(); TestRuntimeEventQueue(); TestRuntimeEventDispatcher(); TestRuntimeEventDispatcherCoalescing(); TestRuntimeEventCoalescingQueue(); TestRuntimeEventCoalescingCustomKey(); TestRuntimeEventTestHarness(); TestAcceptedMutationFollowUps(); TestAppLevelBroadcastAndBuildCoalescing(); TestManualReloadBridgeEvents(); TestFileReloadBridgeEvents(); TestRejectedMutationHasNoDownstreamFollowUps(); TestShaderBuildGenerationEventMatching(); TestHandlerFailureCanBecomeTelemetryEvent(); if (gFailures != 0) { std::cerr << gFailures << " RuntimeEventType test failure(s).\n"; return 1; } std::cout << "RuntimeEventType tests passed.\n"; return 0; }