Editor/Core/PlayModeLifecycle.cs
#nullable enable
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
namespace TeamCreate;
public enum PlayModeLifecycleState
{
Source,
RuntimeDisabled,
RuntimeStarting,
RuntimeActive,
RuntimeBlocked
}
public sealed record PlayModeTransitionResult(bool Accepted, string? ActionableError = null, bool ArchiveMarker = false)
{
public static PlayModeTransitionResult Rejected(string error)
{
return new PlayModeTransitionResult(Accepted: false, error);
}
public static PlayModeTransitionResult Ok(bool archiveMarker = false)
{
return new PlayModeTransitionResult(Accepted: true, null, archiveMarker);
}
}
public sealed class PlayModeLifecycle
{
private const int MaxDiagnosticLength = 512;
private const int MaxErrorLength = 512;
public PlayModeLifecycleState State { get; private set; } = PlayModeLifecycleState.Source;
public bool OptIn { get; private set; }
public string? SourceStateToken { get; private set; }
public object? SourceSessionToken { get; private set; }
public string? SourceScenePath { get; private set; }
public string? PendingScenePath { get; private set; }
public string? PendingRunId { get; private set; }
public object? RuntimeSessionToken { get; private set; }
public object? RuntimeParentSessionToken { get; private set; }
public string? RuntimeSourceScenePath { get; private set; }
public string? RunId { get; private set; }
public string? ArchiveReason { get; private set; }
public bool HotloadArchiveRecorded { get; private set; }
public bool HasTerminalTeardownFailure { get; private set; }
public string? LastDiagnostic { get; private set; }
public string? LastActionableError { get; private set; }
public bool IsTransitionInProgress => State == PlayModeLifecycleState.RuntimeStarting;
public bool RuntimeCollaborationEnabled => State == PlayModeLifecycleState.RuntimeActive;
public bool RequiresHotloadRecoveryAfterLeave => State == PlayModeLifecycleState.RuntimeBlocked;
public PlayModeLifecycle(bool optIn, string? sourceStateToken = null)
{
OptIn = optIn;
SourceStateToken = sourceStateToken;
}
public bool HasApprovedCurrentRuntime(RuntimeSessionDescriptor? currentRuntime)
{
if (!OptIn || State != PlayModeLifecycleState.RuntimeActive)
{
return false;
}
if (currentRuntime is null || !currentRuntime.IsValid || currentRuntime.IsDestroyed)
{
return false;
}
if (currentRuntime.Kind != RuntimeSessionKind.Game || currentRuntime.SessionToken == null)
{
return false;
}
if (currentRuntime.SessionToken != RuntimeSessionToken)
{
return false;
}
if (currentRuntime.ParentSessionToken != RuntimeParentSessionToken)
{
return false;
}
if (currentRuntime.ParentSessionToken != SourceSessionToken)
{
return false;
}
if (currentRuntime.SourceScenePath == null || RuntimeSourceScenePath == null || SourceScenePath == null)
{
return false;
}
if (!PathsEqual(currentRuntime.SourceScenePath, RuntimeSourceScenePath))
{
return false;
}
return PathsEqual(currentRuntime.SourceScenePath, SourceScenePath);
}
public PlayModeTransitionResult RecordStartIntent(string? scenePath, object? expectedSourceSession = null, string? expectedSourcePath = null, string? pendingRunId = null)
{
if (HasTerminalTeardownFailure)
{
return PlayModeTransitionResult.Rejected("A connection teardown failed; this editor session remains fail-stop.");
}
if (State == PlayModeLifecycleState.RuntimeBlocked)
{
return Reject("Runtime lifecycle is blocked. Leave the collaboration session, recover, then start a new session.");
}
if (IsTransitionInProgress)
{
return Reject("A play-mode lifecycle transition is already in progress.");
}
if (State == PlayModeLifecycleState.RuntimeActive)
{
return Reject("A runtime collaboration run is already active. Stop it before starting another.");
}
if (!RuntimeStateRules.IsSafeScenePath(expectedSourcePath ?? scenePath))
{
return Reject("Play mode requires a saved scene under scenes/.");
}
if (pendingRunId != null && !RuntimeStateRules.IsValidRunId(pendingRunId))
{
return Reject("Runtime run ID is invalid.");
}
SourceSessionToken = expectedSourceSession ?? SourceSessionToken;
SourceScenePath = CanonicalPath(expectedSourcePath ?? scenePath);
PendingScenePath = SourceScenePath;
PendingRunId = pendingRunId;
RuntimeSessionToken = null;
RuntimeParentSessionToken = null;
RuntimeSourceScenePath = null;
RunId = null;
ArchiveReason = null;
HotloadArchiveRecorded = false;
HasTerminalTeardownFailure = false;
LastActionableError = null;
State = PlayModeLifecycleState.RuntimeStarting;
LastDiagnostic = "Runtime lifecycle start intent recorded; waiting for the GameEditorSession.";
return PlayModeTransitionResult.Ok();
}
public PlayModeTransitionResult AttachRuntime(RuntimeSessionDescriptor descriptor, string? runId = null, object? expectedSourceSession = null, string? expectedSourcePath = null)
{
if (HasTerminalTeardownFailure)
{
return PlayModeTransitionResult.Rejected("A connection teardown failed; this editor session remains fail-stop.");
}
if (State == PlayModeLifecycleState.RuntimeBlocked)
{
return Reject("Runtime lifecycle is blocked. Leave the collaboration session, recover, then start a new session.");
}
if (State != PlayModeLifecycleState.RuntimeStarting)
{
return Reject("A scene.startplay intent is required before a runtime session can attach.");
}
if (OptIn && string.IsNullOrWhiteSpace(runId))
{
return Reject("Runtime collaboration is enabled, but the runtime run ID is missing.");
}
if (runId != null && !RuntimeStateRules.IsValidRunId(runId))
{
return Reject("Runtime session run ID is invalid.");
}
if (PendingRunId != null && !string.Equals(PendingRunId, runId, StringComparison.Ordinal))
{
return Reject("Runtime session run ID does not match the pending lifecycle run ID.");
}
try
{
object? expectedSourceSession2 = expectedSourceSession ?? SourceSessionToken;
string? expectedSourcePath2 = expectedSourcePath ?? SourceScenePath;
RuntimeSessionGuard.EnsureRuntimeSession(descriptor, expectedSourceSession2, expectedSourcePath2);
if (!PathsEqual(descriptor.SourceScenePath, PendingScenePath))
{
throw new InvalidOperationException("Runtime session source path does not match the pending play-mode source path.");
}
}
catch (Exception ex)
{
return Reject(ex.Message);
}
RuntimeSessionToken = descriptor.SessionToken;
RuntimeParentSessionToken = descriptor.ParentSessionToken;
RuntimeSourceScenePath = CanonicalPath(descriptor.SourceScenePath);
PendingScenePath = null;
PendingRunId = null;
LastActionableError = null;
if (!OptIn)
{
RunId = null;
State = PlayModeLifecycleState.RuntimeDisabled;
LastDiagnostic = "Play mode detected; experimental runtime collaboration remains disabled by user choice.";
return PlayModeTransitionResult.Ok();
}
RunId = runId;
State = PlayModeLifecycleState.RuntimeActive;
LastDiagnostic = "Experimental runtime collaboration lifecycle enabled; transport, sampling, and runtime apply remain unsupported.";
return PlayModeTransitionResult.Ok();
}
public PlayModeTransitionResult RecordStop()
{
if (HasTerminalTeardownFailure)
{
return PlayModeTransitionResult.Rejected("A connection teardown failed; this editor session remains fail-stop.");
}
if (State == PlayModeLifecycleState.RuntimeBlocked)
{
return Reject("Runtime lifecycle is blocked; stop cannot discard the preserved transition state.");
}
if (State == PlayModeLifecycleState.Source)
{
LastDiagnostic = "No active runtime collaboration run; source collaboration remains authoritative.";
return PlayModeTransitionResult.Ok();
}
PlayModeLifecycleState state = State;
RuntimeSessionToken = null;
RuntimeParentSessionToken = null;
RuntimeSourceScenePath = null;
PendingScenePath = null;
PendingRunId = null;
RunId = null;
ArchiveReason = null;
HotloadArchiveRecorded = false;
HasTerminalTeardownFailure = false;
LastActionableError = null;
State = PlayModeLifecycleState.Source;
LastDiagnostic = Bounded($"Runtime collaboration stopped from {state}; returned to source collaboration without changing source scene state.", 512);
return PlayModeTransitionResult.Ok();
}
public PlayModeTransitionResult ValidateSessionStart()
{
if (HasTerminalTeardownFailure)
{
return PlayModeTransitionResult.Rejected("A connection teardown failed; this editor session remains fail-stop.");
}
if (State == PlayModeLifecycleState.RuntimeStarting || State == PlayModeLifecycleState.RuntimeBlocked)
{
return PlayModeTransitionResult.Rejected("Stop play mode, complete hotload recovery, and Leave the collaboration session before starting a new session");
}
return PlayModeTransitionResult.Ok();
}
public PlayModeTransitionResult RecoverFromHotload()
{
if (HasTerminalTeardownFailure)
{
return PlayModeTransitionResult.Rejected("A connection teardown failed; this editor session remains fail-stop.");
}
if (State != PlayModeLifecycleState.RuntimeBlocked)
{
return PlayModeTransitionResult.Rejected("Hotload recovery reset requires RuntimeBlocked.");
}
if (!HotloadArchiveRecorded)
{
return Reject("Hotload recovery reset requires a successfully recorded recovery archive. Leave will retry while RuntimeBlocked.");
}
string lastDiagnostic = Bounded("Hotload lifecycle reset to Source; transport recovery and Leave must be completed before a new play session. Complete source recovery, sync source in Git, and start a new collaboration session.", 512);
State = PlayModeLifecycleState.Source;
SourceSessionToken = null;
SourceScenePath = null;
PendingScenePath = null;
PendingRunId = null;
RuntimeSessionToken = null;
RuntimeParentSessionToken = null;
RuntimeSourceScenePath = null;
RunId = null;
ArchiveReason = null;
HotloadArchiveRecorded = false;
LastActionableError = null;
LastDiagnostic = lastDiagnostic;
return PlayModeTransitionResult.Ok();
}
public void RecordHotloadTeardownFailure(string? error)
{
HasTerminalTeardownFailure = true;
LastActionableError = Bounded(string.IsNullOrWhiteSpace(error) ? "Hotload connection teardown failed; runtime remains blocked." : ("Hotload connection teardown failed; runtime remains blocked: " + error), 512);
LastDiagnostic = LastActionableError;
}
public static string FormatHotloadArchiveFailure(Exception? error)
{
string? text = error?.Message;
return Bounded("Runtime lifecycle blocked, but recovery archive failed: " + ((text != null && text.Length > 0) ? text : "unknown archive error"), 512);
}
public void RecordHotloadArchiveFailure(Exception? error)
{
HotloadArchiveRecorded = false;
LastActionableError = FormatHotloadArchiveFailure(error);
LastDiagnostic = LastActionableError;
}
public PlayModeTransitionResult RecordHotloadArchive()
{
if (HasTerminalTeardownFailure)
{
return PlayModeTransitionResult.Rejected("A connection teardown failed; this editor session remains fail-stop.");
}
if (State != PlayModeLifecycleState.RuntimeBlocked)
{
return Reject("Hotload recovery archive can only be recorded while RuntimeBlocked.");
}
HotloadArchiveRecorded = true;
LastActionableError = null;
LastDiagnostic = "Runtime lifecycle recovery archive recorded; transport teardown and Leave must complete before recovery reset.";
return PlayModeTransitionResult.Ok();
}
public PlayModeTransitionResult BlockForHotload(string reason)
{
if (HasTerminalTeardownFailure)
{
return PlayModeTransitionResult.Rejected("A connection teardown failed; this editor session remains fail-stop.");
}
if (State == PlayModeLifecycleState.Source && !IsTransitionInProgress)
{
return Reject("There is no runtime lifecycle transition to block.");
}
State = PlayModeLifecycleState.RuntimeBlocked;
RuntimeSessionToken = null;
RuntimeParentSessionToken = null;
RuntimeSourceScenePath = null;
PendingScenePath = null;
PendingRunId = null;
RunId = null;
HotloadArchiveRecorded = false;
ArchiveReason = Bounded(string.IsNullOrWhiteSpace(reason) ? "hotloaded" : reason, 512);
LastDiagnostic = "Runtime lifecycle blocked and recovery marker requested before collaboration connections are disposed.";
return PlayModeTransitionResult.Ok(archiveMarker: true);
}
public PlayModeTransitionResult SetOptIn(bool value)
{
if (IsTransitionInProgress)
{
return Reject("Runtime opt-in cannot change while a lifecycle transition is in progress.");
}
if (State == PlayModeLifecycleState.RuntimeActive && value != OptIn)
{
return Reject("Stop the current runtime run before changing experimental runtime collaboration opt-in.");
}
OptIn = value;
LastActionableError = null;
LastDiagnostic = (value ? "Experimental runtime collaboration is explicitly enabled for the next play session." : "Experimental runtime collaboration is explicitly disabled; source collaboration remains the default.");
return PlayModeTransitionResult.Ok();
}
private PlayModeTransitionResult Reject(string error)
{
LastActionableError = Bounded(error, 512);
LastDiagnostic = LastActionableError;
return PlayModeTransitionResult.Rejected(LastActionableError);
}
private static string CanonicalPath(string? path)
{
return (path ?? string.Empty).Replace('\\', '/').ToLowerInvariant();
}
private static bool PathsEqual(string? left, string? right)
{
return left != null && right != null && string.Equals(CanonicalPath(left), CanonicalPath(right), StringComparison.Ordinal);
}
private static string Bounded(string? value, int length)
{
return (string.IsNullOrEmpty(value) || value.Length <= length) ? (value ?? string.Empty) : value.Substring(0, length);
}
}
public enum HotloadTeardownRecoveryStatus
{
NotRequired,
ArchiveFailed,
WaitingForTeardown,
TerminalTeardownFailure,
Ready
}
public sealed record HotloadTeardownRecoveryRequest(bool LeaveRequested, bool RequiresHotloadRecovery, bool ArchiveSucceeded, bool HotloadArchiveRecorded, bool TeardownPending, string? TeardownFailure, bool TeardownFailed, bool RecoveryDeferred);
public sealed record HotloadTeardownRecoveryDecision(HotloadTeardownRecoveryStatus Status, bool EnableDeferredRecovery, bool AttemptRecovery, string? Error);
public static class HotloadTeardownRecovery
{
public static HotloadTeardownRecoveryDecision Decide(HotloadTeardownRecoveryRequest request)
{
if (request.TeardownFailed || request.TeardownFailure != null)
{
return new HotloadTeardownRecoveryDecision(HotloadTeardownRecoveryStatus.TerminalTeardownFailure, EnableDeferredRecovery: false, AttemptRecovery: false, Bounded((request.RequiresHotloadRecovery ? "Hotload connection teardown failed; runtime remains blocked: " : "Connection teardown failed; this editor session remains offline and fail-stop: ") + (string.IsNullOrWhiteSpace(request.TeardownFailure) ? "unknown teardown error" : request.TeardownFailure), 512));
}
if (!request.LeaveRequested)
{
return new HotloadTeardownRecoveryDecision(HotloadTeardownRecoveryStatus.NotRequired, EnableDeferredRecovery: false, AttemptRecovery: false, null);
}
if (!request.RequiresHotloadRecovery)
{
return new HotloadTeardownRecoveryDecision(HotloadTeardownRecoveryStatus.NotRequired, EnableDeferredRecovery: false, AttemptRecovery: false, null);
}
if (!request.ArchiveSucceeded || !request.HotloadArchiveRecorded)
{
return new HotloadTeardownRecoveryDecision(HotloadTeardownRecoveryStatus.ArchiveFailed, EnableDeferredRecovery: false, AttemptRecovery: false, "Hotload recovery archive has not been recorded; runtime remains blocked.");
}
if (request.TeardownPending)
{
return new HotloadTeardownRecoveryDecision(HotloadTeardownRecoveryStatus.WaitingForTeardown, EnableDeferredRecovery: false, AttemptRecovery: false, "Runtime remains blocked until pending connection teardown completes.");
}
return new HotloadTeardownRecoveryDecision(HotloadTeardownRecoveryStatus.Ready, !request.RecoveryDeferred, AttemptRecovery: true, null);
}
private static string Bounded(string? value, int length)
{
return (string.IsNullOrEmpty(value) || value.Length <= length) ? (value ?? string.Empty) : value.Substring(0, length);
}
}
public sealed record HotloadCodeChangeRequest(PlayModeLifecycleState LifecycleState, bool LifecycleBlockAccepted, bool ArchiveWritten, bool TransportActive, string? LifecycleRejectionReason = null);
public sealed record HotloadCodeChangeReport(bool ArchiveClaimed, bool RequiresHotloadRecoveryAfterLeave, bool TeardownTransport, bool SurfaceError, string Message, string? LifecycleRejectionReason);
public static class HotloadCodeChangeRules
{
public const string ArchivedMessage = "Code hotloaded during collaboration. Recovery is archived before connections close; Leave will finish transport teardown before lifecycle recovery.";
public const string ArchiveFailedMessage = "Code hotload archive failed; runtime remains blocked and Leave will retry before recovery.";
public const string NoArchiveNeededMessage = "Code hotloaded during collaboration. Connections were closed; no runtime lifecycle recovery archive was required.";
public static HotloadCodeChangeReport Decide(HotloadCodeChangeRequest request)
{
bool flag = request.LifecycleState == PlayModeLifecycleState.RuntimeBlocked;
bool flag2 = flag && request.ArchiveWritten;
string value;
if (flag2)
{
value = "Code hotloaded during collaboration. Recovery is archived before connections close; Leave will finish transport teardown before lifecycle recovery.";
}
else
{
value = (flag ? "Code hotload archive failed; runtime remains blocked and Leave will retry before recovery." : "Code hotloaded during collaboration. Connections were closed; no runtime lifecycle recovery archive was required.");
}
return new HotloadCodeChangeReport(flag2, flag2, TeardownTransport: true, request.TransportActive || (flag && !flag2), CollaborationDiagnostics.Bounded(value), request.LifecycleBlockAccepted ? null : CollaborationDiagnostics.Bounded(request.LifecycleRejectionReason));
}
}
public sealed record PlayModePendingCandidate(object SourceSessionToken, string SourceScenePath, string RuntimeRunId);
public sealed class ConnectionTeardownGate
{
private readonly object sync = new object();
private TaskCompletionSource<bool> completion = Completed();
private string? failure;
public bool FailStopTeardown { get; private set; }
public bool HasTerminalFailure
{
get
{
lock (sync)
{
return FailStopTeardown || failure != null;
}
}
}
public bool IsPending
{
get
{
lock (sync)
{
return !completion.Task.IsCompleted;
}
}
}
public string? Failure
{
get
{
lock (sync)
{
return failure;
}
}
}
public void Begin<T>(Task<T> connecting) where T : IDisposable
{
if (connecting == null)
{
throw new ArgumentNullException("connecting");
}
lock (sync)
{
EnsureRetryAllowed();
completion = NewCompletion();
}
_ = CompleteAsync(connecting);
}
public void ResetForRetry()
{
lock (sync)
{
EnsureRetryAllowed();
}
}
public void CompleteWithoutConnection()
{
lock (sync)
{
EnsureRetryAllowed();
completion = NewCompletion();
completion.TrySetResult(result: true);
}
}
public bool TryTakeCompletion(out string? teardownFailure)
{
lock (sync)
{
teardownFailure = null;
if (!completion.Task.IsCompleted)
{
return false;
}
teardownFailure = failure;
FailStopTeardown = failure != null;
return true;
}
}
private void EnsureRetryAllowed()
{
if (!completion.Task.IsCompleted)
{
throw new InvalidOperationException("A connection teardown is already pending.");
}
if (FailStopTeardown || failure != null)
{
throw new InvalidOperationException("A previous connection teardown failed; failure remains terminal for this editor session.");
}
}
private async Task CompleteAsync<T>(Task<T> connecting) where T : IDisposable
{
Exception? disposalError = null;
try
{
T produced = await connecting.ConfigureAwait(continueOnCapturedContext: false);
try
{
produced.Dispose();
}
catch (Exception ex)
{
Exception error = ex;
disposalError = error;
}
}
catch (Exception ex)
{
Exception error2 = ex;
disposalError = error2;
}
lock (sync)
{
failure = ((disposalError == null) ? null : Bounded("Connection teardown failed: " + disposalError.Message, 512));
FailStopTeardown = failure != null;
completion.TrySetResult(result: true);
}
}
private static TaskCompletionSource<bool> NewCompletion()
{
return new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
}
private static TaskCompletionSource<bool> Completed()
{
TaskCompletionSource<bool> taskCompletionSource = NewCompletion();
taskCompletionSource.TrySetResult(result: true);
return taskCompletionSource;
}
private static string Bounded(string? value, int length)
{
return (string.IsNullOrEmpty(value) || value.Length <= length) ? (value ?? string.Empty) : value.Substring(0, length);
}
}
public sealed class PlayModeLifecycleEventState
{
private PlayModePendingCandidate? pending;
public PlayModePendingCandidate? Pending => pending;
public PlayModeTransitionResult RecoverFromHotload(PlayModeLifecycle lifecycle)
{
PlayModeTransitionResult playModeTransitionResult = lifecycle.RecoverFromHotload();
if (playModeTransitionResult.Accepted)
{
pending = null;
}
return playModeTransitionResult;
}
public PlayModeTransitionResult AttachRuntime(PlayModeLifecycle lifecycle, RuntimeSessionDescriptor descriptor, string runId)
{
PlayModePendingCandidate? playModePendingCandidate = pending;
if (playModePendingCandidate is null)
{
return lifecycle.AttachRuntime(descriptor, runId);
}
PlayModeTransitionResult playModeTransitionResult = lifecycle.AttachRuntime(descriptor, runId, playModePendingCandidate.SourceSessionToken, playModePendingCandidate.SourceScenePath);
if (playModeTransitionResult.Accepted)
{
pending = null;
}
return playModeTransitionResult;
}
public PlayModeTransitionResult RecordStop(PlayModeLifecycle lifecycle)
{
PlayModeTransitionResult playModeTransitionResult = lifecycle.RecordStop();
if (playModeTransitionResult.Accepted)
{
pending = null;
}
return playModeTransitionResult;
}
public PlayModeTransitionResult RecordStartIntent(PlayModeLifecycle lifecycle, object sourceSession, string sourceScenePath, string runId)
{
PlayModePendingCandidate playModePendingCandidate = new PlayModePendingCandidate(sourceSession, sourceScenePath, runId);
PlayModeTransitionResult playModeTransitionResult = lifecycle.RecordStartIntent(sourceScenePath, sourceSession, sourceScenePath, runId);
if (playModeTransitionResult.Accepted)
{
pending = playModePendingCandidate;
}
return playModeTransitionResult;
}
}
public sealed record ScenePacketRouteResult(bool Admitted, string? CanonicalSceneKey, string? TargetScenePath, string? Error);
public static class ScenePacketRouting
{
public static string? CanonicalSceneKey(string? scene)
{
return (!string.IsNullOrWhiteSpace(scene) && PlayModeSourceCoexistence.IsUsablePathKey(scene)) ? scene.Trim().Replace('\\', '/').ToLowerInvariant() : null;
}
public static bool TryResolveTracked(string? packetScene, IReadOnlyDictionary<string, object> trackedByScene, out string? canonical, out object? target)
{
canonical = CanonicalSceneKey(packetScene);
target = null;
if (canonical == null)
{
return false;
}
if (!trackedByScene.TryGetValue(canonical, out target))
{
foreach (KeyValuePair<string, object> item in trackedByScene)
{
if (CanonicalSceneKey(item.Key) != canonical)
{
continue;
}
target = item.Value;
break;
}
}
if (target == null)
{
target = null;
return false;
}
return true;
}
public static ScenePacketRouteResult RouteTrackedPacket(string packetKind, string? packetScene, IReadOnlyDictionary<string, object> trackedByScene, Func<object, bool> isPlaying, Func<object, string?> currentTargetPath, PlayModeLifecycle lifecycle, Func<object, RuntimeSessionDescriptor?> currentRuntime)
{
if ((!(packetKind == "snapshot") && !(packetKind == "accepted")) || 1 == 0)
{
return Reject("Only snapshot and accepted packets use tracked-scene routing.");
}
string? text = CanonicalSceneKey(packetScene);
if (text == null)
{
return Reject("A snapshot or accepted packet has a missing or invalid scene key.");
}
if (!TryResolveTracked(packetScene, trackedByScene, out string? _, out object? target))
{
return Reject("No current tracked target exists for scene " + text + ".");
}
if (target == null)
{
return Reject("No current tracked target exists for scene " + text + ".");
}
string? text2 = currentTargetPath(target);
if (!PlayModeSourceCoexistence.CanAdmitTrackedScenePacket(packetKind, isPlaying(target), target, text2, lifecycle, currentRuntime(target), text))
{
return Reject("The tracked target's current scene path does not admit packet " + text + ".");
}
return new ScenePacketRouteResult(Admitted: true, text, text2, null);
}
private static ScenePacketRouteResult Reject(string error)
{
return new ScenePacketRouteResult(Admitted: false, null, null, CollaborationDiagnostics.Bounded(error));
}
}
public static class PlayModeSourceCoexistence
{
public static bool CanProcessSourcePackets(bool isPlaying, object? sourceSession, string? sourceScenePath, PlayModeLifecycle lifecycle, RuntimeSessionDescriptor? currentRuntime = null)
{
return CanAdmitTrackedSceneState(isPlaying, sourceSession, sourceScenePath, lifecycle, currentRuntime, sourceScenePath);
}
public static bool CanAdmitTrackedScenePacket(string packetKind, bool isPlaying, object? sourceSession, string? sourceScenePath, PlayModeLifecycle lifecycle, RuntimeSessionDescriptor? currentRuntime, string? packetScenePath)
{
bool flag = ((packetKind == "snapshot" || packetKind == "accepted") ? true : false);
return flag && CanAdmitTrackedSceneState(isPlaying, sourceSession, sourceScenePath, lifecycle, currentRuntime, packetScenePath);
}
private static bool CanAdmitTrackedSceneState(bool isPlaying, object? sourceSession, string? sourceScenePath, PlayModeLifecycle lifecycle, RuntimeSessionDescriptor? currentRuntime, string? packetScenePath)
{
if (lifecycle == null)
{
return false;
}
PlayModeLifecycleState state = lifecycle.State;
if ((state == PlayModeLifecycleState.RuntimeStarting || state == PlayModeLifecycleState.RuntimeBlocked) ? true : false)
{
return false;
}
if (!IsUsablePathKey(sourceScenePath))
{
return false;
}
if (!IsUsablePathKey(packetScenePath) || !PathsEqual(packetScenePath, sourceScenePath))
{
return false;
}
if (!isPlaying)
{
return true;
}
if (sourceSession != null && sourceScenePath != null && CanSampleAuthoringSource(sourceSession, sourceScenePath, lifecycle, currentRuntime))
{
return true;
}
return false;
}
public static bool IsUsablePathKey(string? path)
{
return !string.IsNullOrWhiteSpace(path) && CanonicalPath(path).Length > 0 && RuntimeStateRules.IsSafeScenePath(CanonicalPath(path));
}
public static bool CanSampleAuthoringSource(object sourceSession, string sourceScenePath, PlayModeLifecycle lifecycle, RuntimeSessionDescriptor? currentRuntime = null)
{
return lifecycle != null && lifecycle.OptIn && lifecycle.State == PlayModeLifecycleState.RuntimeActive && lifecycle.SourceSessionToken != null && lifecycle.SourceSessionToken == sourceSession && IsUsablePathKey(sourceScenePath) && PathsEqual(sourceScenePath, lifecycle.SourceScenePath) && currentRuntime is not null && lifecycle.HasApprovedCurrentRuntime(currentRuntime);
}
private static string CanonicalPath(string path)
{
return path.Replace('\\', '/').Trim().ToLowerInvariant();
}
private static bool PathsEqual(string? left, string? right)
{
return left != null && right != null && IsUsablePathKey(left) && IsUsablePathKey(right) && string.Equals(CanonicalPath(left), CanonicalPath(right), StringComparison.OrdinalIgnoreCase);
}
}