Editor/RuntimeSceneAdapter.cs
using System;
using System.Collections.Generic;
using Editor;
using Sandbox;

namespace TeamCreate;

public static class RuntimeSceneAdapter
{
    public const string UnsupportedMutationReason = "Runtime scene mutation is disabled until a tested runtime undo/apply strategy is proven.";

    public static RuntimeSessionDescriptor Describe(GameEditorSession session)
    {
        if (session == null)
        {
            return null;
        }
        return new RuntimeSessionDescriptor
        {
            Kind = RuntimeSessionKind.Game,
            SessionToken = session,
            ParentSessionToken = session.Parent,
            IsValid = (session.Scene != null && session.Scene.IsValid()),
            IsDestroyed = (session.Scene == null || !session.Scene.IsValid()),
            SourceScenePath = session.Parent?.Scene?.Source?.ResourcePath
        };
    }

    public static Dictionary<string, ObjectState> Capture(GameEditorSession session, SceneEditorSession expectedSource, string configuredSourcePath)
    {
        RuntimeSessionDescriptor runtimeSessionDescriptor = RuntimeSessionGuard.EnsureRuntimeSession(Describe(session), expectedSource, configuredSourcePath);
        Scene scene = session.Scene;
        if (scene == null || !scene.IsValid())
        {
            throw new InvalidOperationException("Runtime session is missing or destroyed.");
        }
        Dictionary<string, ObjectState> result = new Dictionary<string, ObjectState>(StringComparer.Ordinal);
        using (scene.Push())
        {
            GameObject[] array = scene.Children.ToArray();
            foreach (GameObject gameObject in array)
            {
                CaptureObject(gameObject, string.Empty, isRoot: true, result);
            }
        }
        return result;
    }

    public static RuntimeSceneApplyPlan PlanApply(GameEditorSession session, SceneEditorSession expectedSource, string configuredSourcePath, IReadOnlyDictionary<string, ObjectState> target)
    {
        RuntimeSessionGuard.EnsureRuntimeSession(Describe(session), expectedSource, configuredSourcePath);
        Scene scene = session.Scene;
        if (scene == null || !scene.IsValid())
        {
            throw new InvalidOperationException("Runtime session is missing or destroyed.");
        }
        Dictionary<string, ObjectState> before = Capture(session, expectedSource, configuredSourcePath);
        return RuntimeSceneTargetOnlyBoundary.Shared.PlanForAdapter(configuredSourcePath, before, target);
    }

    public static RuntimeSceneApplyPlan Apply(GameEditorSession session, SceneEditorSession expectedSource, string configuredSourcePath, IReadOnlyDictionary<string, ObjectState> target)
    {
        return PlanApply(session, expectedSource, configuredSourcePath, target);
    }

    private static void CaptureObject(GameObject gameObject, string parentId, bool isRoot, Dictionary<string, ObjectState> result)
    {
        RuntimeSceneCapture.EnsureRuntimeObjectEntry(gameObject, isRoot);
        if (gameObject.IsDestroyed)
        {
            throw new InvalidOperationException("Runtime object is destroyed or being destroyed: " + gameObject.Id);
        }
        if (!gameObject.IsValid())
        {
            throw new InvalidOperationException("Runtime object is invalid: " + gameObject.Id);
        }
        if (!gameObject.Flags.HasFlag(GameObjectFlags.NotSaved))
        {
            ObjectState objectState = RuntimeSceneCapture.CaptureObject(gameObject, gameObject.Id, gameObject.Serialize(), parentId, isRoot);
            if (result.ContainsKey(objectState.Id))
            {
                throw new InvalidOperationException("Runtime scene contains duplicate object id: " + objectState.Id);
            }
            result.Add(objectState.Id, objectState);
            GameObject[] array = gameObject.Children.ToArray();
            foreach (GameObject gameObject2 in array)
            {
                CaptureObject(gameObject2, objectState.Id, isRoot: false, result);
            }
        }
    }
}