phase 2 progress
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m26s
CI / Windows Release Package (push) Successful in 2m30s

This commit is contained in:
Aiden
2026-05-11 16:18:34 +10:00
parent 6e600be112
commit d4f6a4a268
16 changed files with 463 additions and 140 deletions

View File

@@ -67,6 +67,18 @@ void TestRuntimeEventPayloadTypes()
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;
@@ -234,12 +246,89 @@ void TestRuntimeEventDispatcher()
Expect(!dispatcher.Publish(mismatched), "dispatcher rejects mismatched event type and payload");
RuntimeEventDispatcher tinyDispatcher(1);
Expect(tinyDispatcher.PublishPayload(broadcast, "test"), "tiny dispatcher accepts first event");
Expect(!tinyDispatcher.PublishPayload(broadcast, "test"), "tiny dispatcher rejects event when queue is full");
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<RuntimeStateBroadcastRequestedEvent>(&event.payload);
if (payload)
seenReason = payload->reason;
++broadcastHandlerCount;
});
dispatcher.Subscribe(RuntimeEventType::ShaderBuildRequested, [&](const RuntimeEvent& event) {
const auto* payload = std::get_if<ShaderBuildEvent>(&event.payload);
if (payload)
seenShaderMessage = payload->message;
++shaderHandlerCount;
});
dispatcher.Subscribe(RuntimeEventType::TimingSampleRecorded, [&](const RuntimeEvent& event) {
const auto* payload = std::get_if<TimingSampleRecordedEvent>(&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);
@@ -401,6 +490,124 @@ void TestAcceptedMutationFollowUps()
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<RuntimeStateBroadcastRequestedEvent>(&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<ShaderBuildEvent>(&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<RuntimeReloadRequestedEvent>(&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<FileChangeDetectedEvent>(&fileEvent->payload) : nullptr;
Expect(filePayload && filePayload->shaderPackageCandidate, "file reload bridge marks shader package candidate changes");
}
void TestRejectedMutationHasNoDownstreamFollowUps()
{
RuntimeEventTestHarness harness;
@@ -487,10 +694,14 @@ int main()
TestRuntimeEventEnvelope();
TestRuntimeEventQueue();
TestRuntimeEventDispatcher();
TestRuntimeEventDispatcherCoalescing();
TestRuntimeEventCoalescingQueue();
TestRuntimeEventCoalescingCustomKey();
TestRuntimeEventTestHarness();
TestAcceptedMutationFollowUps();
TestAppLevelBroadcastAndBuildCoalescing();
TestManualReloadBridgeEvents();
TestFileReloadBridgeEvents();
TestRejectedMutationHasNoDownstreamFollowUps();
TestShaderBuildGenerationEventMatching();
TestHandlerFailureCanBecomeTelemetryEvent();