fix: reuse scenes for on-air refresh
This commit is contained in:
@@ -413,9 +413,16 @@ internal sealed class DynamicK3dSession : IK3dGuardedConnectSession
|
||||
|
||||
try
|
||||
{
|
||||
nextScene = new SceneReference(
|
||||
InvokeRequired(engine, "LoadScene", cue.SceneFile, cue.SceneName),
|
||||
cue.SceneName);
|
||||
// MainForm refreshes the selected DTO immediately before TAKE IN and
|
||||
// Page NEXT can target the alias already on air. Tornado2 rejects a
|
||||
// second LoadScene for an alias it still owns, so mutate and re-Prepare
|
||||
// the retained loaded scene instead of creating a duplicate alias.
|
||||
nextScene =
|
||||
ReusableScene(_preparedScene, cue.SceneName) ??
|
||||
ReusableScene(_currentScene, cue.SceneName) ??
|
||||
new SceneReference(
|
||||
InvokeRequired(engine, "LoadScene", cue.SceneFile, cue.SceneName),
|
||||
cue.SceneName);
|
||||
if (outputChannel.HasValue)
|
||||
{
|
||||
Invoke(nextScene.Value, "SetOutputChannelIndex", outputChannel.GetValueOrDefault());
|
||||
@@ -548,10 +555,16 @@ internal sealed class DynamicK3dSession : IK3dGuardedConnectSession
|
||||
|
||||
try
|
||||
{
|
||||
// MainForm.timer1_Tick replays the current layout before entering
|
||||
// Show_PlayList(idx: 1), then prepares and plays the mutated scene again.
|
||||
InvokeTrackedPlay(layoutIndex);
|
||||
playingScene = InvokeRequired(player, "GetPlayingScene", layoutIndex);
|
||||
|
||||
foreach (var mutation in cue.Mutations ?? [])
|
||||
{
|
||||
if (IsBeforeTransactionSceneSetupMutation(mutation))
|
||||
{
|
||||
ApplySceneSetupMutation(playingScene, mutation);
|
||||
}
|
||||
}
|
||||
|
||||
Invoke(engine, "BeginTransaction");
|
||||
transactionStarted = true;
|
||||
|
||||
@@ -562,11 +575,12 @@ internal sealed class DynamicK3dSession : IK3dGuardedConnectSession
|
||||
|
||||
foreach (var mutation in cue.Mutations ?? [])
|
||||
{
|
||||
// The legacy idx == 1 path updates the playing scene in place. It does not
|
||||
// reapply scene-level background setup or the scene transition effect.
|
||||
if (!IsSceneLevelMutation(mutation))
|
||||
// Match Show_PlayList(idx: 1): common background setup runs before the
|
||||
// transaction, while builder-owned setup and object changes keep their
|
||||
// explicit in-transaction ordering.
|
||||
if (!IsBeforeTransactionSceneSetupMutation(mutation))
|
||||
{
|
||||
ApplyMutation(playingScene, mutation);
|
||||
ApplyTransactionMutation(playingScene, mutation);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1080,6 +1094,14 @@ internal sealed class DynamicK3dSession : IK3dGuardedConnectSession
|
||||
}
|
||||
}
|
||||
|
||||
private static SceneReference? ReusableScene(
|
||||
SceneReference? scene,
|
||||
string sceneName) =>
|
||||
scene is not null &&
|
||||
string.Equals(scene.Name, sceneName, StringComparison.OrdinalIgnoreCase)
|
||||
? scene
|
||||
: null;
|
||||
|
||||
private void MoveAllScenesToPendingTakeOut()
|
||||
{
|
||||
AddDistinctScene(_pendingTakeOutScenes, _preparedScene);
|
||||
|
||||
@@ -458,7 +458,7 @@ public sealed class DynamicK3dSessionTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateOnAir_UsesPlayingSceneWithoutReloadingOrReapplyingSceneSetup()
|
||||
public async Task UpdateOnAir_UsesPlayingSceneAndAppliesOnlyBeforeTransactionBackgroundSetup()
|
||||
{
|
||||
using var scenes = TemporarySceneDirectory.Create(
|
||||
"test-scene.t2s",
|
||||
@@ -475,6 +475,11 @@ public sealed class DynamicK3dSessionTests
|
||||
new PlayoutUseBackground(false),
|
||||
new PlayoutSetBackgroundTexture("Video/background.vrv"),
|
||||
new PlayoutSetBackgroundVideo("Video/background.vrv", 4, true),
|
||||
new PlayoutSetBackgroundVideo(
|
||||
"Video/background.vrv",
|
||||
8,
|
||||
true,
|
||||
PlayoutMutationTiming.InTransaction),
|
||||
new PlayoutSetVisible("badge", false)
|
||||
]));
|
||||
var log = new FakeComLog();
|
||||
@@ -509,12 +514,15 @@ public sealed class DynamicK3dSessionTests
|
||||
Assert.Equal(
|
||||
new[]
|
||||
{
|
||||
"Play:10",
|
||||
"GetPlayingScene:10",
|
||||
"UseBackground:0",
|
||||
"SetBackgroundTexture:background.vrv",
|
||||
"SetBackgroundVideo:background.vrv:4:1",
|
||||
"BeginTransaction",
|
||||
"GetObject:headline",
|
||||
"SetValue:headline:page two",
|
||||
"SetVisible:headline:1",
|
||||
"SetBackgroundVideo:background.vrv:8:1",
|
||||
"GetObject:badge",
|
||||
"SetVisible:badge:0",
|
||||
"QueryVariables",
|
||||
@@ -525,10 +533,7 @@ public sealed class DynamicK3dSessionTests
|
||||
updateCalls);
|
||||
Assert.DoesNotContain(updateCalls, call =>
|
||||
call.StartsWith("LoadScene:", StringComparison.Ordinal) ||
|
||||
call.StartsWith("SetSceneEffectType:", StringComparison.Ordinal) ||
|
||||
call.StartsWith("UseBackground:", StringComparison.Ordinal) ||
|
||||
call.StartsWith("SetBackgroundTexture:", StringComparison.Ordinal) ||
|
||||
call.StartsWith("SetBackgroundVideo:", StringComparison.Ordinal));
|
||||
call.StartsWith("SetSceneEffectType:", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -577,10 +582,6 @@ public sealed class DynamicK3dSessionTests
|
||||
session.ProcessPendingCallbacks(options.LayoutIndex);
|
||||
Assert.True(session.HasPendingLifecycleCallbacks);
|
||||
|
||||
callbacks.Fire("OnScenePlayed", 1, 9, 10);
|
||||
session.ProcessPendingCallbacks(options.LayoutIndex);
|
||||
Assert.True(session.HasPendingLifecycleCallbacks);
|
||||
|
||||
callbacks.Fire("OnScenePlayed", 1, 9, 10);
|
||||
session.ProcessPendingCallbacks(options.LayoutIndex);
|
||||
Assert.False(session.HasPendingLifecycleCallbacks);
|
||||
@@ -590,11 +591,48 @@ public sealed class DynamicK3dSessionTests
|
||||
TimeSpan.FromSeconds(5),
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.Equal(3, log.Names.Count(name => name == "Play:10"));
|
||||
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);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Prepare_SamePreparedScene_ReusesSceneWithoutSecondLoad()
|
||||
{
|
||||
using var scenes = TemporarySceneDirectory.Create("5001.t2s");
|
||||
var options = SceneOptions(scenes.Path, "5001");
|
||||
var log = new FakeComLog();
|
||||
var player = new FakePlayer(log);
|
||||
var scene = new FakeScene(log, "5001");
|
||||
var engine = new FakeEngine(log, player, scene);
|
||||
var activator = new FakeActivator(log, engine);
|
||||
var releaser = new FakeReleaser(
|
||||
log,
|
||||
(scene, "5001"),
|
||||
(player, "Player"),
|
||||
(engine, "Engine"),
|
||||
(activator.EventHandler, "EventHandler"));
|
||||
await using var dispatcher = new StaDispatcher(capacity: 2);
|
||||
|
||||
await dispatcher.InvokeAsync(
|
||||
() =>
|
||||
{
|
||||
using var session = new DynamicK3dSession(activator, releaser);
|
||||
session.Connect(options);
|
||||
session.Prepare(SceneCue(options, "5001"), options.LayoutIndex);
|
||||
session.Prepare(SceneCue(options, "5001"), options.LayoutIndex);
|
||||
session.Disconnect();
|
||||
return true;
|
||||
},
|
||||
TimeSpan.FromSeconds(5),
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.Equal(1, log.Names.Count(name => name == "LoadScene:5001"));
|
||||
Assert.Equal(2, log.Names.Count(name => name == "Prepare:10"));
|
||||
Assert.DoesNotContain("Unload:5001", log.Names);
|
||||
Assert.Equal(1, log.Names.Count(name => name == "Release:5001"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LifecycleCallbacks_UnloadOnlyRetiredScenesAfterMatchingSuccess()
|
||||
{
|
||||
@@ -682,25 +720,19 @@ public sealed class DynamicK3dSessionTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LifecycleCallbacks_SameNameReplacement_UnloadsOnlyAfterStopAll()
|
||||
public async Task LifecycleCallbacks_CurrentSameNamePrepare_ReusesActiveSceneUntilStopAll()
|
||||
{
|
||||
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 engine = new FakeEngine(log, player, activeScene);
|
||||
var activator = new FakeActivator(log, engine);
|
||||
var callbacks = new RecordingEventHandlerFactory();
|
||||
var releaser = new FakeReleaser(
|
||||
log,
|
||||
(retiredScene, "5001-retired"),
|
||||
(activeScene, "5001-active"),
|
||||
(activeScene, "5001"),
|
||||
(player, "Player"),
|
||||
(engine, "Engine"),
|
||||
(callbacks.Handler, "EventHandler"));
|
||||
@@ -730,8 +762,8 @@ public sealed class DynamicK3dSessionTests
|
||||
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);
|
||||
Assert.DoesNotContain("Release:5001", log.Names);
|
||||
Assert.Equal(1, log.Names.Count(name => name == "LoadScene:5001"));
|
||||
|
||||
session.TakeOut(options.LayoutIndex, PlayoutTakeOutScope.All);
|
||||
callbacks.Fire("OnStopAll", 1);
|
||||
@@ -739,39 +771,32 @@ public sealed class DynamicK3dSessionTests
|
||||
1,
|
||||
session.ProcessPendingCallbacks(options.LayoutIndex).UnloadedSceneCount);
|
||||
Assert.Equal(1, log.Names.Count(name => name == "Unload:5001"));
|
||||
Assert.Contains("Release:5001-active", log.Names);
|
||||
Assert.Contains("Release:5001", log.Names);
|
||||
session.Disconnect();
|
||||
return true;
|
||||
},
|
||||
TimeSpan.FromSeconds(5),
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.Empty(scenesToLoad);
|
||||
Assert.Equal(1, log.Names.Count(name => name == "LoadScene:5001"));
|
||||
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"));
|
||||
Assert.Equal(1, log.Names.Count(name => name == "Release:5001"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LifecycleCallbacks_SameNameReplacement_IgnoresSceneNameCasing()
|
||||
public async Task Prepare_CurrentSceneReuse_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 activeScene = new FakeScene(log, "SceneA");
|
||||
var engine = new FakeEngine(log, player, activeScene);
|
||||
var activator = new FakeActivator(log, engine);
|
||||
var callbacks = new RecordingEventHandlerFactory();
|
||||
var releaser = new FakeReleaser(
|
||||
log,
|
||||
(retiredScene, "SceneA-retired"),
|
||||
(activeScene, "scenea-active"),
|
||||
(activeScene, "SceneA"),
|
||||
(player, "Player"),
|
||||
(engine, "Engine"),
|
||||
(callbacks.Handler, "EventHandler"));
|
||||
@@ -799,7 +824,9 @@ public sealed class DynamicK3dSessionTests
|
||||
0,
|
||||
session.ProcessPendingCallbacks(options.LayoutIndex).UnloadedSceneCount);
|
||||
Assert.DoesNotContain("Unload:SceneA", log.Names);
|
||||
Assert.Contains("Release:SceneA-retired", log.Names);
|
||||
Assert.DoesNotContain("Release:SceneA", log.Names);
|
||||
Assert.Equal(1, log.Names.Count(name => name == "LoadScene:SceneA"));
|
||||
Assert.DoesNotContain("LoadScene:scenea", log.Names);
|
||||
|
||||
session.TakeOut(options.LayoutIndex, PlayoutTakeOutScope.All);
|
||||
callbacks.Fire("OnStopAll", 1);
|
||||
@@ -812,11 +839,10 @@ public sealed class DynamicK3dSessionTests
|
||||
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"));
|
||||
Assert.Equal(1, log.Names.Count(name => name == "LoadScene:SceneA"));
|
||||
Assert.DoesNotContain("LoadScene:scenea", log.Names);
|
||||
Assert.Equal(1, log.Names.Count(name => name == "Unload:SceneA"));
|
||||
Assert.Equal(1, log.Names.Count(name => name == "Release:SceneA"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user