fix: await playout callbacks before refresh
This commit is contained in:
@@ -681,6 +681,144 @@ public sealed class DynamicK3dSessionTests
|
||||
Assert.Equal(1, log.Names.Count(name => name == "Unload:5006"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LifecycleCallbacks_SameNameReplacement_UnloadsOnlyAfterStopAll()
|
||||
{
|
||||
using var scenes = TemporarySceneDirectory.Create("5001.t2s");
|
||||
var options = SceneOptions(scenes.Path, "5001");
|
||||
var log = new FakeComLog();
|
||||
var player = new FakePlayer(log);
|
||||
var retiredScene = new FakeScene(log, "5001");
|
||||
var activeScene = new FakeScene(log, "5001");
|
||||
var scenesToLoad = new Queue<FakeScene>([retiredScene, activeScene]);
|
||||
var engine = new FakeEngine(log, player, retiredScene)
|
||||
{
|
||||
SceneResolver = _ => scenesToLoad.Dequeue()
|
||||
};
|
||||
var activator = new FakeActivator(log, engine);
|
||||
var callbacks = new RecordingEventHandlerFactory();
|
||||
var releaser = new FakeReleaser(
|
||||
log,
|
||||
(retiredScene, "5001-retired"),
|
||||
(activeScene, "5001-active"),
|
||||
(player, "Player"),
|
||||
(engine, "Engine"),
|
||||
(callbacks.Handler, "EventHandler"));
|
||||
await using var dispatcher = new StaDispatcher(capacity: 3);
|
||||
|
||||
await dispatcher.InvokeAsync(
|
||||
() =>
|
||||
{
|
||||
using var session = new DynamicK3dSession(
|
||||
activator,
|
||||
releaser,
|
||||
new InstalledK3dInteropMethodInvoker(),
|
||||
callbacks);
|
||||
session.Connect(options);
|
||||
|
||||
session.Prepare(SceneCue(options, "5001"), options.LayoutIndex);
|
||||
session.Play(options.LayoutIndex);
|
||||
callbacks.Fire("OnScenePlayed", 1, 9, options.LayoutIndex);
|
||||
Assert.Equal(
|
||||
0,
|
||||
session.ProcessPendingCallbacks(options.LayoutIndex).UnloadedSceneCount);
|
||||
|
||||
session.Prepare(SceneCue(options, "5001"), options.LayoutIndex);
|
||||
session.Play(options.LayoutIndex);
|
||||
callbacks.Fire("OnScenePlayed", 1, 9, options.LayoutIndex);
|
||||
Assert.Equal(
|
||||
0,
|
||||
session.ProcessPendingCallbacks(options.LayoutIndex).UnloadedSceneCount);
|
||||
Assert.DoesNotContain("Unload:5001", log.Names);
|
||||
Assert.Contains("Release:5001-retired", log.Names);
|
||||
Assert.DoesNotContain("Release:5001-active", log.Names);
|
||||
|
||||
session.TakeOut(options.LayoutIndex, PlayoutTakeOutScope.All);
|
||||
callbacks.Fire("OnStopAll", 1);
|
||||
Assert.Equal(
|
||||
1,
|
||||
session.ProcessPendingCallbacks(options.LayoutIndex).UnloadedSceneCount);
|
||||
Assert.Equal(1, log.Names.Count(name => name == "Unload:5001"));
|
||||
Assert.Contains("Release:5001-active", log.Names);
|
||||
session.Disconnect();
|
||||
return true;
|
||||
},
|
||||
TimeSpan.FromSeconds(5),
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.Empty(scenesToLoad);
|
||||
Assert.Equal(1, log.Names.Count(name => name == "Unload:5001"));
|
||||
Assert.Equal(1, log.Names.Count(name => name == "Release:5001-retired"));
|
||||
Assert.Equal(1, log.Names.Count(name => name == "Release:5001-active"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LifecycleCallbacks_SameNameReplacement_IgnoresSceneNameCasing()
|
||||
{
|
||||
using var scenes = TemporarySceneDirectory.Create("SceneA.t2s");
|
||||
var options = SceneOptions(scenes.Path, "SceneA");
|
||||
var log = new FakeComLog();
|
||||
var player = new FakePlayer(log);
|
||||
var retiredScene = new FakeScene(log, "SceneA");
|
||||
var activeScene = new FakeScene(log, "scenea");
|
||||
var scenesToLoad = new Queue<FakeScene>([retiredScene, activeScene]);
|
||||
var engine = new FakeEngine(log, player, retiredScene)
|
||||
{
|
||||
SceneResolver = _ => scenesToLoad.Dequeue()
|
||||
};
|
||||
var activator = new FakeActivator(log, engine);
|
||||
var callbacks = new RecordingEventHandlerFactory();
|
||||
var releaser = new FakeReleaser(
|
||||
log,
|
||||
(retiredScene, "SceneA-retired"),
|
||||
(activeScene, "scenea-active"),
|
||||
(player, "Player"),
|
||||
(engine, "Engine"),
|
||||
(callbacks.Handler, "EventHandler"));
|
||||
await using var dispatcher = new StaDispatcher(capacity: 3);
|
||||
|
||||
await dispatcher.InvokeAsync(
|
||||
() =>
|
||||
{
|
||||
using var session = new DynamicK3dSession(
|
||||
activator,
|
||||
releaser,
|
||||
new InstalledK3dInteropMethodInvoker(),
|
||||
callbacks);
|
||||
session.Connect(options);
|
||||
|
||||
session.Prepare(SceneCue(options, "SceneA"), options.LayoutIndex);
|
||||
session.Play(options.LayoutIndex);
|
||||
callbacks.Fire("OnScenePlayed", 1, 9, options.LayoutIndex);
|
||||
session.ProcessPendingCallbacks(options.LayoutIndex);
|
||||
|
||||
session.Prepare(SceneCue(options, "scenea"), options.LayoutIndex);
|
||||
session.Play(options.LayoutIndex);
|
||||
callbacks.Fire("OnScenePlayed", 1, 9, options.LayoutIndex);
|
||||
Assert.Equal(
|
||||
0,
|
||||
session.ProcessPendingCallbacks(options.LayoutIndex).UnloadedSceneCount);
|
||||
Assert.DoesNotContain("Unload:SceneA", log.Names);
|
||||
Assert.Contains("Release:SceneA-retired", log.Names);
|
||||
|
||||
session.TakeOut(options.LayoutIndex, PlayoutTakeOutScope.All);
|
||||
callbacks.Fire("OnStopAll", 1);
|
||||
Assert.Equal(
|
||||
1,
|
||||
session.ProcessPendingCallbacks(options.LayoutIndex).UnloadedSceneCount);
|
||||
session.Disconnect();
|
||||
return true;
|
||||
},
|
||||
TimeSpan.FromSeconds(5),
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.Empty(scenesToLoad);
|
||||
Assert.DoesNotContain("Unload:SceneA", log.Names);
|
||||
Assert.Equal(1, log.Names.Count(name => name == "Unload:scenea"));
|
||||
Assert.Equal(1, log.Names.Count(name => name == "Release:SceneA-retired"));
|
||||
Assert.Equal(1, log.Names.Count(name => name == "Release:scenea-active"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StopAllSuccess_ClearsInterruptedPlayBeforeDisconnect()
|
||||
{
|
||||
|
||||
@@ -1295,12 +1295,16 @@ public sealed class TornadoPlayoutEngineTests
|
||||
Assert.Null(engine.Status.OnAirSceneName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Status_ExposesValidatedOperationTimeoutMilliseconds()
|
||||
[Theory]
|
||||
[InlineData(100)]
|
||||
[InlineData(837)]
|
||||
[InlineData(300_000)]
|
||||
public async Task Status_ExposesValidatedOperationTimeoutMilliseconds(
|
||||
int operationTimeoutMilliseconds)
|
||||
{
|
||||
using var scenes = TemporarySceneDirectory.Create("test-scene.t2s");
|
||||
var options = TestOptions(scenes.Path, "test-scene");
|
||||
options.OperationTimeoutMilliseconds = 837;
|
||||
options.OperationTimeoutMilliseconds = operationTimeoutMilliseconds;
|
||||
await using var engine = CreateEngine(
|
||||
options,
|
||||
new FakeK3dSessionFactory(),
|
||||
@@ -1308,7 +1312,7 @@ public sealed class TornadoPlayoutEngineTests
|
||||
new FakeTornadoProcessProbe(ProcessSnapshot("eligible-generation-a")),
|
||||
liveAuthorized: false);
|
||||
|
||||
Assert.Equal(837, engine.Status.OperationTimeoutMilliseconds);
|
||||
Assert.Equal(operationTimeoutMilliseconds, engine.Status.OperationTimeoutMilliseconds);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -2186,6 +2190,64 @@ public sealed class TornadoPlayoutEngineTests
|
||||
Assert.Equal(1, session.Calls.Count(call => call == "ProcessPendingCallbacks"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PlayCompletionPending_StatusClearsOnlyAfterCallbackDrain()
|
||||
{
|
||||
using var scenes = TemporarySceneDirectory.Create("5001.t2s");
|
||||
var callbackReady = false;
|
||||
var session = new FakeK3dSession
|
||||
{
|
||||
SupportsLifecycleCallbacks = true,
|
||||
ClearPendingLifecycleCallbacksAfterProcess = false
|
||||
};
|
||||
session.PlayAction = _ =>
|
||||
{
|
||||
session.HasPendingPlayCallbacks = true;
|
||||
session.HasPendingLifecycleCallbacks = true;
|
||||
};
|
||||
session.ProcessPendingCallbacksAction = _ =>
|
||||
{
|
||||
if (!callbackReady)
|
||||
{
|
||||
return new K3dCallbackDrainResult(0, 0, false);
|
||||
}
|
||||
|
||||
session.HasPendingPlayCallbacks = false;
|
||||
session.HasPendingLifecycleCallbacks = false;
|
||||
return new K3dCallbackDrainResult(1, 1, false);
|
||||
};
|
||||
await using var engine = CreateEngine(
|
||||
TestOptions(scenes.Path, "5001"),
|
||||
new FakeK3dSessionFactory(() => session),
|
||||
new FakeRegistrationProbe(),
|
||||
new FakeTornadoProcessProbe(new TornadoProcessSnapshot(1, 1, 0)),
|
||||
liveAuthorized: false);
|
||||
|
||||
Assert.True((await engine.ConnectAsync(CancellationToken.None)).IsSuccess);
|
||||
Assert.True((await engine.PrepareAsync(
|
||||
Cue("5001"),
|
||||
CancellationToken.None)).IsSuccess);
|
||||
var observedPendingStates = new List<bool>();
|
||||
engine.StatusChanged += (_, args) =>
|
||||
observedPendingStates.Add(args.Current.IsPlayCompletionPending);
|
||||
|
||||
Assert.True((await engine.TakeInAsync(CancellationToken.None)).IsSuccess);
|
||||
await engine.PollProcessOnceAsync(CancellationToken.None);
|
||||
|
||||
Assert.True(engine.Status.IsPlayCompletionPending);
|
||||
Assert.Equal([true, true], observedPendingStates);
|
||||
|
||||
callbackReady = true;
|
||||
await engine.PollProcessOnceAsync(CancellationToken.None);
|
||||
|
||||
Assert.False(engine.Status.IsPlayCompletionPending);
|
||||
Assert.Equal([true, true, false], observedPendingStates);
|
||||
Assert.True((await engine.TakeOutAsync(
|
||||
PlayoutTakeOutScope.All,
|
||||
CancellationToken.None)).IsSuccess);
|
||||
Assert.True((await engine.DisconnectAsync(CancellationToken.None)).IsSuccess);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PendingPlayCallback_BlocksDelayedMultiNextUntilEachCompletionIsDrained()
|
||||
{
|
||||
@@ -2229,6 +2291,7 @@ public sealed class TornadoPlayoutEngineTests
|
||||
CancellationToken.None)).IsSuccess);
|
||||
Assert.True((await engine.TakeInAsync(CancellationToken.None)).IsSuccess);
|
||||
Assert.True(session.HasPendingPlayCallbacks);
|
||||
Assert.True(engine.Status.IsPlayCompletionPending);
|
||||
|
||||
var firstEarlyNext = await engine.NextAsync(
|
||||
Cue("5006"),
|
||||
@@ -2261,6 +2324,7 @@ public sealed class TornadoPlayoutEngineTests
|
||||
callbackReady = true;
|
||||
await engine.PollProcessOnceAsync(CancellationToken.None);
|
||||
Assert.False(session.HasPendingPlayCallbacks);
|
||||
Assert.False(engine.Status.IsPlayCompletionPending);
|
||||
Assert.True((await engine.TakeOutAsync(
|
||||
PlayoutTakeOutScope.All,
|
||||
CancellationToken.None)).IsSuccess);
|
||||
@@ -2373,6 +2437,7 @@ public sealed class TornadoPlayoutEngineTests
|
||||
CancellationToken.None)).IsSuccess);
|
||||
Assert.True((await engine.TakeInAsync(CancellationToken.None)).IsSuccess);
|
||||
Assert.True(session.HasPendingPlayCallbacks);
|
||||
Assert.True(engine.Status.IsPlayCompletionPending);
|
||||
|
||||
var result = await engine.TakeOutAsync(
|
||||
PlayoutTakeOutScope.All,
|
||||
@@ -2380,6 +2445,7 @@ public sealed class TornadoPlayoutEngineTests
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.Contains("TakeOut:All", session.Calls);
|
||||
Assert.True(engine.Status.IsPlayCompletionPending);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
||||
Reference in New Issue
Block a user