fix: await playout callbacks before refresh
This commit is contained in:
@@ -213,6 +213,13 @@ public sealed record PlayoutStatus(
|
||||
{
|
||||
public int OperationTimeoutMilliseconds { get; init; } = 5_000;
|
||||
|
||||
/// <summary>
|
||||
/// True while Tornado has accepted an SDK Play call but has not yet delivered the
|
||||
/// corresponding scene-played completion callback. This is observation-only state;
|
||||
/// callers must not retry or infer completion by issuing another scene mutation.
|
||||
/// </summary>
|
||||
public bool IsPlayCompletionPending { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// True only after the adapter entered the KAEngine.KTAPConnect dispatch path.
|
||||
/// Process discovery, registration probes, DryRun, and configuration preflight do not set it.
|
||||
|
||||
@@ -157,10 +157,10 @@ internal sealed class DynamicK3dSession : IK3dGuardedConnectSession
|
||||
private object? _engine;
|
||||
private object? _eventHandler;
|
||||
private object? _player;
|
||||
private object? _preparedScene;
|
||||
private object? _currentScene;
|
||||
private readonly List<object> _retiredScenes = [];
|
||||
private readonly List<object> _pendingTakeOutScenes = [];
|
||||
private SceneReference? _preparedScene;
|
||||
private SceneReference? _currentScene;
|
||||
private readonly List<SceneReference> _retiredScenes = [];
|
||||
private readonly List<SceneReference> _pendingTakeOutScenes = [];
|
||||
private int? _outputChannel;
|
||||
private int? _ownerThreadId;
|
||||
private int _lastKtapConnectState;
|
||||
@@ -408,19 +408,21 @@ internal sealed class DynamicK3dSession : IK3dGuardedConnectSession
|
||||
var engine = _engine!;
|
||||
var player = _player!;
|
||||
var outputChannel = _outputChannel;
|
||||
object? nextScene = null;
|
||||
SceneReference? nextScene = null;
|
||||
var transactionStarted = false;
|
||||
|
||||
try
|
||||
{
|
||||
nextScene = InvokeRequired(engine, "LoadScene", cue.SceneFile, cue.SceneName);
|
||||
nextScene = new SceneReference(
|
||||
InvokeRequired(engine, "LoadScene", cue.SceneFile, cue.SceneName),
|
||||
cue.SceneName);
|
||||
if (outputChannel.HasValue)
|
||||
{
|
||||
Invoke(nextScene, "SetOutputChannelIndex", outputChannel.GetValueOrDefault());
|
||||
Invoke(nextScene.Value, "SetOutputChannelIndex", outputChannel.GetValueOrDefault());
|
||||
}
|
||||
|
||||
Invoke(
|
||||
nextScene,
|
||||
nextScene.Value,
|
||||
"SetSceneEffectType",
|
||||
K3dComConstants.SceneEffectInEnabled,
|
||||
K3dComConstants.SceneChangeEffectFade,
|
||||
@@ -430,7 +432,7 @@ internal sealed class DynamicK3dSession : IK3dGuardedConnectSession
|
||||
{
|
||||
if (IsBeforeTransactionSceneSetupMutation(mutation))
|
||||
{
|
||||
ApplySceneSetupMutation(nextScene, mutation);
|
||||
ApplySceneSetupMutation(nextScene.Value, mutation);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -438,18 +440,18 @@ internal sealed class DynamicK3dSession : IK3dGuardedConnectSession
|
||||
transactionStarted = true;
|
||||
foreach (var field in cue.Fields ?? [])
|
||||
{
|
||||
ApplyField(nextScene, field);
|
||||
ApplyField(nextScene.Value, field);
|
||||
}
|
||||
|
||||
foreach (var mutation in cue.Mutations ?? [])
|
||||
{
|
||||
if (!IsBeforeTransactionSceneSetupMutation(mutation))
|
||||
{
|
||||
ApplyTransactionMutation(nextScene, mutation);
|
||||
ApplyTransactionMutation(nextScene.Value, mutation);
|
||||
}
|
||||
}
|
||||
|
||||
Invoke(nextScene, "QueryVariables");
|
||||
Invoke(nextScene.Value, "QueryVariables");
|
||||
|
||||
if (outputChannel.HasValue)
|
||||
{
|
||||
@@ -461,7 +463,7 @@ internal sealed class DynamicK3dSession : IK3dGuardedConnectSession
|
||||
}
|
||||
|
||||
transactionStarted = false;
|
||||
Invoke(player, "Prepare", layoutIndex, nextScene);
|
||||
Invoke(player, "Prepare", layoutIndex, nextScene.Value);
|
||||
|
||||
var replacedPreparedScene = _preparedScene;
|
||||
_preparedScene = nextScene;
|
||||
@@ -503,7 +505,7 @@ internal sealed class DynamicK3dSession : IK3dGuardedConnectSession
|
||||
var previousCurrentScene = _currentScene;
|
||||
_currentScene = preparedScene;
|
||||
if (previousCurrentScene is not null &&
|
||||
!ReferenceEquals(previousCurrentScene, preparedScene))
|
||||
!ReferenceEquals(previousCurrentScene.Value, preparedScene.Value))
|
||||
{
|
||||
RetainRetiredScene(previousCurrentScene);
|
||||
}
|
||||
@@ -586,10 +588,10 @@ internal sealed class DynamicK3dSession : IK3dGuardedConnectSession
|
||||
// scene. An in-place page update must never retire/unload the active scene.
|
||||
if (_currentScene is null)
|
||||
{
|
||||
_currentScene = playingScene;
|
||||
_currentScene = new SceneReference(playingScene, cue.SceneName);
|
||||
playingScene = null;
|
||||
}
|
||||
else if (ReferenceEquals(_currentScene, playingScene))
|
||||
else if (ReferenceEquals(_currentScene.Value, playingScene))
|
||||
{
|
||||
playingScene = null;
|
||||
}
|
||||
@@ -1029,7 +1031,7 @@ internal sealed class DynamicK3dSession : IK3dGuardedConnectSession
|
||||
|
||||
private void ReleaseAll()
|
||||
{
|
||||
var scenes = new List<object>();
|
||||
var scenes = new List<SceneReference>();
|
||||
AddDistinctScene(scenes, _preparedScene);
|
||||
_preparedScene = null;
|
||||
AddDistinctScene(scenes, _currentScene);
|
||||
@@ -1059,7 +1061,7 @@ internal sealed class DynamicK3dSession : IK3dGuardedConnectSession
|
||||
|
||||
foreach (var scene in scenes)
|
||||
{
|
||||
ReleaseComObject(scene);
|
||||
ReleaseComObject(scene.Value);
|
||||
}
|
||||
|
||||
ReleaseComObject(player);
|
||||
@@ -1067,12 +1069,12 @@ internal sealed class DynamicK3dSession : IK3dGuardedConnectSession
|
||||
ReleaseComObject(eventHandler);
|
||||
}
|
||||
|
||||
private void RetainRetiredScene(object? scene)
|
||||
private void RetainRetiredScene(SceneReference? scene)
|
||||
{
|
||||
if (scene is not null &&
|
||||
!ReferenceEquals(scene, _preparedScene) &&
|
||||
!ReferenceEquals(scene, _currentScene) &&
|
||||
!_retiredScenes.Any(item => ReferenceEquals(item, scene)))
|
||||
!ReferenceEquals(scene.Value, _preparedScene?.Value) &&
|
||||
!ReferenceEquals(scene.Value, _currentScene?.Value) &&
|
||||
!_retiredScenes.Any(item => ReferenceEquals(item.Value, scene.Value)))
|
||||
{
|
||||
_retiredScenes.Add(scene);
|
||||
}
|
||||
@@ -1092,16 +1094,38 @@ internal sealed class DynamicK3dSession : IK3dGuardedConnectSession
|
||||
_retiredScenes.Clear();
|
||||
}
|
||||
|
||||
private int UnloadAndRelease(List<object> scenes)
|
||||
private int UnloadAndRelease(List<SceneReference> scenes)
|
||||
{
|
||||
var unloaded = 0;
|
||||
var activeSceneNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
if (_preparedScene is not null)
|
||||
{
|
||||
activeSceneNames.Add(_preparedScene.Name);
|
||||
}
|
||||
|
||||
if (_currentScene is not null)
|
||||
{
|
||||
activeSceneNames.Add(_currentScene.Name);
|
||||
}
|
||||
|
||||
var unloadedSceneNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
while (scenes.Count > 0)
|
||||
{
|
||||
var scene = scenes[0];
|
||||
Invoke(scene, "Unload");
|
||||
var shouldUnload =
|
||||
!activeSceneNames.Contains(scene.Name) &&
|
||||
unloadedSceneNames.Add(scene.Name);
|
||||
if (shouldUnload)
|
||||
{
|
||||
Invoke(scene.Value, "Unload");
|
||||
}
|
||||
|
||||
scenes.RemoveAt(0);
|
||||
ReleaseComObject(scene);
|
||||
unloaded++;
|
||||
ReleaseComObject(scene.Value);
|
||||
if (shouldUnload)
|
||||
{
|
||||
unloaded++;
|
||||
}
|
||||
}
|
||||
|
||||
return unloaded;
|
||||
@@ -1114,14 +1138,19 @@ internal sealed class DynamicK3dSession : IK3dGuardedConnectSession
|
||||
callback.LayerIndex == layoutIndex;
|
||||
}
|
||||
|
||||
private static void AddDistinctScene(List<object> scenes, object? scene)
|
||||
private static void AddDistinctScene(
|
||||
List<SceneReference> scenes,
|
||||
SceneReference? scene)
|
||||
{
|
||||
if (scene is not null && !scenes.Any(item => ReferenceEquals(item, scene)))
|
||||
if (scene is not null &&
|
||||
!scenes.Any(item => ReferenceEquals(item.Value, scene.Value)))
|
||||
{
|
||||
scenes.Add(scene);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed record SceneReference(object Value, string Name);
|
||||
|
||||
private object InvokeRequired(object target, string method, params object?[] arguments) =>
|
||||
Invoke(target, method, arguments)
|
||||
?? throw new InvalidOperationException("The K3D operation returned no object.");
|
||||
|
||||
@@ -1829,6 +1829,7 @@ internal sealed class TornadoPlayoutEngine : IPlayoutEngine
|
||||
Interlocked.Increment(ref _sequence))
|
||||
{
|
||||
OperationTimeoutMilliseconds = (int)_options.OperationTimeout.TotalMilliseconds,
|
||||
IsPlayCompletionPending = _session?.HasPendingPlayCallbacks == true,
|
||||
LastKtapConnectState = (PlayoutKtapConnectState)Volatile.Read(
|
||||
ref _lastKtapConnectState),
|
||||
KtapHelloObserved = Volatile.Read(ref _ktapHelloObservedState) switch
|
||||
|
||||
Reference in New Issue
Block a user