Complete legacy operator UI and playout migration

This commit is contained in:
2026-07-12 02:05:49 +09:00
parent d2e16bf0c2
commit ffb8f43c19
131 changed files with 48473 additions and 228 deletions

View File

@@ -16,10 +16,15 @@ public sealed partial class MainWindow
private readonly SemaphoreSlim _playoutCommandGate = new(1, 1);
private IPlayoutEngine? _playoutEngine;
private LegacyPlayoutWorkflow? _playoutWorkflow;
private PlayoutOptions? _playoutOptions;
private MutableLegacySceneCueCompositionOptionsSource? _legacyCompositionOptionsSource;
private LegacySceneCueCompositionOptions? _lastEnabledLegacyComposition;
private string? _playoutInitializationError;
private int _playoutCommandInFlight;
private int _playoutShutdownStarted;
private int _browserCorrelationQuarantined;
private int _operatorMutationQuarantined;
private string? _operatorMutationQuarantineMessage;
private Task? _legacyRefreshTask;
private readonly LegacyRefreshEpoch<LegacyRefreshRuntimeStatus> _legacyRefreshEpoch =
new(LegacyRefreshRuntimeStatus.Empty);
@@ -30,6 +35,13 @@ public sealed partial class MainWindow
{
var options = PlayoutOptionsLoader.Load();
var compositionOptions = PlayoutSceneCompositionFactory.Create(options);
_playoutOptions = options;
_legacyCompositionOptionsSource =
new MutableLegacySceneCueCompositionOptionsSource(compositionOptions);
_lastEnabledLegacyComposition = compositionOptions.BackgroundKind ==
LegacySceneBackgroundKind.None
? null
: compositionOptions;
_playoutEngine = PlayoutEngineFactory.Create(options);
_playoutEngine.StatusChanged += OnPlayoutStatusChanged;
if (_databaseRuntime is not null)
@@ -37,7 +49,9 @@ public sealed partial class MainWindow
_playoutWorkflow = LegacySceneRuntimeFactory.Create(
_playoutEngine,
_databaseRuntime.Executor,
compositionOptions);
compositionOptionsSource: _legacyCompositionOptionsSource,
trustedManualSource: _manualNetSellStore,
trustedViSource: _viManualListStore);
}
}
catch (Exception exception)
@@ -1027,6 +1041,57 @@ public sealed partial class MainWindow
private void ResetLegacyRefreshStatus() =>
_legacyRefreshEpoch.Stop(_ => LegacyRefreshRuntimeStatus.Empty);
/// <summary>
/// Provides the single fail-closed playout boundary used by every operator-data
/// mutation. A missing engine/workflow is not an idle state: it means native
/// state cannot be proven, so a write must not start.
/// </summary>
private bool CanStartOperatorMutation(bool familyWriteQuarantined)
{
var engine = _playoutEngine;
var workflow = _playoutWorkflow;
var status = engine?.Status;
var workflowState = workflow?.State;
var refreshStatus = GetLegacyRefreshStatus();
var snapshot = new OperatorMutationGateSnapshot(
IsFamilyWriteQuarantined: familyWriteQuarantined,
IsGlobalWriteQuarantined: IsOperatorMutationQuarantined(),
IsLifetimeCancellationRequested: _lifetimeCancellation.IsCancellationRequested,
IsPlayoutCommandInFlight: Volatile.Read(ref _playoutCommandInFlight) != 0,
IsPlayoutShutdownStarted: Volatile.Read(ref _playoutShutdownStarted) != 0,
IsBrowserCorrelationQuarantined: IsBrowserCorrelationQuarantined(),
IsEngineAvailable: engine is not null,
IsWorkflowAvailable: workflow is not null,
IsCommandAvailable: status?.IsCommandAvailable ?? false,
IsPlayCompletionPending: status?.IsPlayCompletionPending ?? true,
PreparedSceneName: status?.PreparedSceneName,
OnAirSceneName: status?.OnAirSceneName,
PreparedCutCode: workflowState?.PreparedCutCode,
OnAirCutCode: workflowState?.OnAirCutCode,
IsRefreshActive: refreshStatus.IsActive,
IsRefreshTaskRunning: _legacyRefreshTask is { IsCompleted: false });
return OperatorMutationGate.CanStart(snapshot);
}
private bool IsOperatorMutationQuarantined() =>
Volatile.Read(ref _operatorMutationQuarantined) != 0;
private string GetOperatorMutationQuarantineMessage(string fallback) =>
Volatile.Read(ref _operatorMutationQuarantineMessage) ?? fallback;
private void LatchOperatorMutationQuarantine(string message)
{
if (Interlocked.CompareExchange(ref _operatorMutationQuarantined, 1, 0) == 0)
{
Volatile.Write(
ref _operatorMutationQuarantineMessage,
string.IsNullOrWhiteSpace(message)
? "An operator-data mutation has an unknown outcome. Restart and reconcile every operator-data store before writing again."
: message);
}
}
private void ShutdownPlayoutRuntime()
{
if (Interlocked.Exchange(ref _playoutShutdownStarted, 1) != 0)