fix: refresh the retained on-air scene

This commit is contained in:
2026-07-11 13:58:23 +09:00
parent c71ea425e8
commit bb4a3ec3e0
2 changed files with 300 additions and 32 deletions

View File

@@ -458,7 +458,7 @@ public sealed class DynamicK3dSessionTests
}
[Fact]
public async Task UpdateOnAir_UsesPlayingSceneAndAppliesOnlyBeforeTransactionBackgroundSetup()
public async Task UpdateOnAir_ReusesRetainedSceneAndIgnoresInvalidPlayingSceneProxy()
{
using var scenes = TemporarySceneDirectory.Create(
"test-scene.t2s",
@@ -484,7 +484,7 @@ public sealed class DynamicK3dSessionTests
]));
var log = new FakeComLog();
var scene = new FakeScene(log, "test-scene");
var player = new FakePlayer(log) { PlayingScene = scene };
var player = new FakePlayer(log) { PlayingScene = new object() };
var activator = new FakeActivator(log, new FakeEngine(log, player, scene));
await using var dispatcher = new StaDispatcher(capacity: 2);
var updateStart = 0;
@@ -514,7 +514,6 @@ public sealed class DynamicK3dSessionTests
Assert.Equal(
new[]
{
"GetPlayingScene:10",
"UseBackground:0",
"SetBackgroundTexture:background.vrv",
"SetBackgroundVideo:background.vrv:4:1",
@@ -531,20 +530,274 @@ public sealed class DynamicK3dSessionTests
"Play:10"
},
updateCalls);
Assert.Equal(1, updateCalls.Count(call => call == "Play:10"));
Assert.DoesNotContain("GetPlayingScene:10", log.Names);
Assert.DoesNotContain(updateCalls, call =>
call.StartsWith("LoadScene:", StringComparison.Ordinal) ||
call.StartsWith("SetSceneEffectType:", StringComparison.Ordinal));
}
[Fact]
public async Task UpdateOnAir_CallbackSession_TracksItsPlayUntilMatchingCallback()
public async Task UpdateOnAir_MismatchedRetainedSceneFailsBeforeAnySdkMutation()
{
using var scenes = TemporarySceneDirectory.Create(
"5001.t2s",
"5006.t2s",
"Video\\background.vrv");
var options = SceneOptions(scenes.Path, "5001", "5006");
var initialCue = SceneCue(options, "5001");
var mismatchedCue = options.ResolveCue(new PlayoutCue(
"5006.t2s",
"5006",
[new PlayoutField("headline", "must not be sent", true)],
Mutations:
[
new PlayoutUseBackground(false),
new PlayoutSetBackgroundTexture("Video/background.vrv")
]));
var log = new FakeComLog();
var retainedScene = new FakeScene(log, "5001");
var player = new FakePlayer(log) { PlayingScene = new object() };
var activator = new FakeActivator(
log,
new FakeEngine(log, player, retainedScene));
await using var dispatcher = new StaDispatcher(capacity: 2);
string[] updateCalls = [];
await dispatcher.InvokeAsync(
() =>
{
using var session = new DynamicK3dSession(activator);
session.Connect(options);
session.Prepare(initialCue, options.LayoutIndex);
session.Play(options.LayoutIndex);
var updateStart = log.Names.Length;
Assert.Throws<InvalidOperationException>(
() => session.UpdateOnAir(mismatchedCue, options.LayoutIndex));
updateCalls = log.Names.Skip(updateStart).ToArray();
session.Disconnect();
return true;
},
TimeSpan.FromSeconds(5),
CancellationToken.None);
Assert.Empty(updateCalls);
Assert.DoesNotContain("GetPlayingScene:10", log.Names);
}
[Fact]
public async Task UpdateOnAir_WithoutRetainedCurrentSceneFailsBeforeAnySdkMutation()
{
using var scenes = TemporarySceneDirectory.Create("5001.t2s");
var options = SceneOptions(scenes.Path, "5001");
var cue = SceneCue(options, "5001");
var log = new FakeComLog();
var player = new FakePlayer(log) { PlayingScene = new object() };
var scene = new FakeScene(log, "5001");
var activator = new FakeActivator(log, new FakeEngine(log, player, scene));
await using var dispatcher = new StaDispatcher(capacity: 2);
string[] updateCalls = [];
await dispatcher.InvokeAsync(
() =>
{
using var session = new DynamicK3dSession(activator);
session.Connect(options);
var updateStart = log.Names.Length;
Assert.Throws<InvalidOperationException>(
() => session.UpdateOnAir(cue, options.LayoutIndex));
updateCalls = log.Names.Skip(updateStart).ToArray();
session.Disconnect();
return true;
},
TimeSpan.FromSeconds(5),
CancellationToken.None);
Assert.Empty(updateCalls);
Assert.DoesNotContain("GetPlayingScene:10", log.Names);
}
[Fact]
public async Task UpdateOnAir_WithAnotherPreparedSceneFailsBeforeAnySdkMutation()
{
using var scenes = TemporarySceneDirectory.Create("5001.t2s", "5006.t2s");
var options = SceneOptions(scenes.Path, "5001", "5006");
var cue5001 = SceneCue(options, "5001");
var log = new FakeComLog();
var player = new FakePlayer(log) { PlayingScene = new object() };
var scene5001 = new FakeScene(log, "5001");
var scene5006 = new FakeScene(log, "5006");
var engine = new FakeEngine(log, player, scene5001)
{
SceneResolver = name => name == "5006" ? scene5006 : scene5001
};
var activator = new FakeActivator(log, engine);
await using var dispatcher = new StaDispatcher(capacity: 2);
string[] updateCalls = [];
await dispatcher.InvokeAsync(
() =>
{
using var session = new DynamicK3dSession(activator);
session.Connect(options);
session.Prepare(cue5001, options.LayoutIndex);
session.Play(options.LayoutIndex);
session.Prepare(SceneCue(options, "5006"), options.LayoutIndex);
var updateStart = log.Names.Length;
Assert.Throws<InvalidOperationException>(
() => session.UpdateOnAir(cue5001, options.LayoutIndex));
updateCalls = log.Names.Skip(updateStart).ToArray();
session.Disconnect();
return true;
},
TimeSpan.FromSeconds(5),
CancellationToken.None);
Assert.Empty(updateCalls);
Assert.DoesNotContain("GetPlayingScene:10", log.Names);
}
[Fact]
public async Task UpdateOnAir_WithPendingInitialPlayFailsBeforeAnySdkMutation()
{
using var scenes = TemporarySceneDirectory.Create("5001.t2s");
var options = SceneOptions(scenes.Path, "5001");
var cue = SceneCue(options, "5001");
var log = new FakeComLog();
var scene = new FakeScene(log, "5001");
var player = new FakePlayer(log) { PlayingScene = scene };
var player = new FakePlayer(log) { PlayingScene = new object() };
var engine = new FakeEngine(log, player, scene);
var activator = new FakeActivator(log, engine);
var callbacks = new RecordingEventHandlerFactory();
var releaser = new FakeReleaser(
log,
(scene, "5001"),
(player, "Player"),
(engine, "Engine"),
(callbacks.Handler, "EventHandler"));
await using var dispatcher = new StaDispatcher(capacity: 2);
string[] updateCalls = [];
await dispatcher.InvokeAsync(
() =>
{
using var session = new DynamicK3dSession(
activator,
releaser,
new InstalledK3dInteropMethodInvoker(),
callbacks);
session.Connect(options);
session.Prepare(cue, options.LayoutIndex);
session.Play(options.LayoutIndex);
Assert.True(session.HasPendingPlayCallbacks);
var updateStart = log.Names.Length;
Assert.Throws<InvalidOperationException>(
() => session.UpdateOnAir(cue, options.LayoutIndex));
updateCalls = log.Names.Skip(updateStart).ToArray();
callbacks.Fire("OnScenePlayed", 1, 9, options.LayoutIndex);
session.ProcessPendingCallbacks(options.LayoutIndex);
Assert.False(session.HasPendingPlayCallbacks);
session.Disconnect();
return true;
},
TimeSpan.FromSeconds(5),
CancellationToken.None);
Assert.Empty(updateCalls);
Assert.DoesNotContain("GetPlayingScene:10", log.Names);
Assert.Equal(1, log.Names.Count(name => name == "Release:5001"));
}
[Fact]
public async Task UpdateOnAir_TransactionFailureRollsBackAndRetainsSceneForStopAllCleanup()
{
using var scenes = TemporarySceneDirectory.Create("5001.t2s");
var options = SceneOptions(scenes.Path, "5001");
var initialCue = SceneCue(options, "5001");
var updateCue = options.ResolveCue(new PlayoutCue(
"5001.t2s",
"5001",
[new PlayoutField("headline", "refresh", true)],
Mutations: [new PlayoutUseBackground(false)]));
var log = new FakeComLog();
var scene = new FakeScene(log, "5001");
var player = new FakePlayer(log) { PlayingScene = new object() };
var engine = new FakeEngine(log, player, scene);
var activator = new FakeActivator(log, engine);
var callbacks = new RecordingEventHandlerFactory();
var releaser = new FakeReleaser(
log,
(scene, "5001"),
(player, "Player"),
(engine, "Engine"),
(callbacks.Handler, "EventHandler"));
await using var dispatcher = new StaDispatcher(capacity: 2);
await dispatcher.InvokeAsync(
() =>
{
using var session = new DynamicK3dSession(
activator,
releaser,
new InstalledK3dInteropMethodInvoker(),
callbacks);
session.Connect(options);
session.Prepare(initialCue, options.LayoutIndex);
session.Play(options.LayoutIndex);
callbacks.Fire("OnScenePlayed", 1, 9, options.LayoutIndex);
session.ProcessPendingCallbacks(options.LayoutIndex);
scene.QueryVariablesFailure = new InvalidOperationException("fake refresh failure");
var updateStart = log.Names.Length;
Assert.Throws<InvalidOperationException>(
() => session.UpdateOnAir(updateCue, options.LayoutIndex));
var updateCalls = log.Names.Skip(updateStart).ToArray();
Assert.Equal(
new[]
{
"UseBackground:0",
"BeginTransaction",
"GetObject:headline",
"SetValue:headline:refresh",
"SetVisible:headline:1",
"QueryVariables",
"RollbackTransaction"
},
updateCalls);
Assert.DoesNotContain("Release:5001", log.Names);
Assert.DoesNotContain("Unload:5001", log.Names);
scene.QueryVariablesFailure = null;
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.Equal(1, log.Names.Count(name => name == "Unload:5001"));
Assert.Equal(1, log.Names.Count(name => name == "Release:5001"));
Assert.DoesNotContain("GetPlayingScene:10", log.Names);
}
[Fact]
public async Task UpdateOnAir_CallbackSession_TracksPlayAndRetainsSceneUntilStopAll()
{
using var scenes = TemporarySceneDirectory.Create("5001.t2s");
var options = SceneOptions(scenes.Path, "5001");
var cue = SceneCue(options, "5001");
var log = new FakeComLog();
var scene = new FakeScene(log, "5001");
var player = new FakePlayer(log) { PlayingScene = new object() };
var engine = new FakeEngine(log, player, scene);
var activator = new FakeActivator(log, engine);
var callbacks = new RecordingEventHandlerFactory();
@@ -577,6 +830,8 @@ public sealed class DynamicK3dSessionTests
session.UpdateOnAir(cue, options.LayoutIndex);
Assert.True(session.HasPendingLifecycleCallbacks);
Assert.DoesNotContain("Release:5001", log.Names);
Assert.DoesNotContain("Unload:5001", log.Names);
callbacks.Fire("OnScenePlayed", 1, 8, 10);
session.ProcessPendingCallbacks(options.LayoutIndex);
@@ -585,6 +840,13 @@ public sealed class DynamicK3dSessionTests
callbacks.Fire("OnScenePlayed", 1, 9, 10);
session.ProcessPendingCallbacks(options.LayoutIndex);
Assert.False(session.HasPendingLifecycleCallbacks);
session.TakeOut(options.LayoutIndex, PlayoutTakeOutScope.All);
callbacks.Fire("OnStopAll", 1);
Assert.Equal(
1,
session.ProcessPendingCallbacks(options.LayoutIndex).UnloadedSceneCount);
Assert.False(session.HasPendingLifecycleCallbacks);
session.Disconnect();
return true;
},
@@ -592,8 +854,9 @@ public sealed class DynamicK3dSessionTests
CancellationToken.None);
Assert.Equal(2, log.Names.Count(name => name == "Play:10"));
Assert.Equal(1, log.Names.Count(name => name == "GetPlayingScene:10"));
Assert.DoesNotContain("Unload:5001", log.Names);
Assert.DoesNotContain("GetPlayingScene:10", log.Names);
Assert.Equal(1, log.Names.Count(name => name == "Unload:5001"));
Assert.Equal(1, log.Names.Count(name => name == "Release:5001"));
}
[Fact]
@@ -1729,7 +1992,7 @@ public sealed class DynamicK3dSessionTests
{
private readonly FakeComLog _log;
private readonly string? _name;
private readonly Exception? _queryVariablesFailure;
private Exception? _queryVariablesFailure;
private readonly Exception? _unloadFailure;
public FakeScene(
@@ -1773,6 +2036,12 @@ public sealed class DynamicK3dSessionTests
}
}
public Exception? QueryVariablesFailure
{
get => _queryVariablesFailure;
set => _queryVariablesFailure = value;
}
public void Unload()
{
_log.Add(_name is null ? "Unload" : $"Unload:{_name}");