// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using FlaxEngine; namespace FlaxEditor.Utilities { /// /// Tool helper class used to duplicate loaded scenes (backup them) and restore later. Used for play in-editor functionality. /// public class DuplicateScenes { private struct SceneData { public bool IsDirty; public byte[] Bytes; } private readonly List _scenesData = new List(16); /// /// Checks if scene data has been gathered. /// public bool HasData => _scenesData.Count > 0; /// /// Gets a value indicating whether any scene was dirty before gathering. /// public bool WasDirty => _scenesData.Any(x => x.IsDirty); /// /// Collect loaded scenes data. /// public void GatherSceneData() { if (HasData) throw new InvalidOperationException("DuplicateScenes has already gathered scene data."); Profiler.BeginEvent("DuplicateScenes.GatherSceneData"); Editor.Log("Collecting scene data"); // Get loaded scenes var scenes = Level.Scenes; int scenesCount = scenes.Length; if (scenesCount == 0) { Profiler.EndEvent(); throw new InvalidOperationException("Cannot gather scene data. No scene loaded."); } var sceneIds = new Guid[scenesCount]; for (int i = 0; i < scenesCount; i++) { sceneIds[i] = scenes[i].ID; } // Serialize scenes _scenesData.Capacity = scenesCount; for (int i = 0; i < scenesCount; i++) { var scene = scenes[i]; var data = new SceneData { IsDirty = Editor.Instance.Scene.IsEdited(scene), Bytes = Level.SaveSceneToBytes(scene, false), }; _scenesData.Add(data); } // Delete old scenes if (Level.UnloadAllScenes()) { Profiler.EndEvent(); throw new Exception("Failed to unload scenes."); } FlaxEngine.Scripting.FlushRemovedObjects(); // Ensure that old scenes has been unregistered { var noScenes = Level.Scenes; if (noScenes != null && noScenes.Length != 0) { Profiler.EndEvent(); throw new Exception("Failed to unload scenes."); } } Editor.Log(string.Format("Gathered {0} scene(s)!", scenesCount)); Profiler.EndEvent(); } /// /// Deserialize captured scenes. /// public void CreateScenes() { if (!HasData) throw new InvalidOperationException("DuplicateScenes has not gathered scene data yet."); Profiler.BeginEvent("DuplicateScenes.CreateScenes"); Editor.Log("Creating scenes"); Stopwatch sw = Stopwatch.StartNew(); // Deserialize new scenes int scenesCount = _scenesData.Count; for (int i = 0; i < scenesCount; i++) { var data = _scenesData[i]; var scene = Level.LoadSceneFromBytes(data.Bytes); if (scene == null) { Profiler.EndEvent(); throw new Exception("Failed to deserialize scene"); } } sw.Stop(); Editor.Log("Creating scenes time: " + sw.Elapsed.TotalMilliseconds + "ms"); Profiler.EndEvent(); } /// /// Deletes the creates scenes for the simulation. /// public void UnloadScenes() { Profiler.BeginEvent("DuplicateScenes.UnloadScenes"); Editor.Log("Restoring scene data"); // TODO: here we can keep changes for actors marked to keep their state after simulation // Delete new scenes if (Level.UnloadAllScenes()) { Profiler.EndEvent(); throw new Exception("Failed to unload scenes."); } FlaxEngine.Scripting.FlushRemovedObjects(); Editor.WipeOutLeftoverSceneObjects(); Profiler.EndEvent(); } /// /// Restore captured scene data. /// public void RestoreSceneData() { if (!HasData) throw new InvalidOperationException("DuplicateScenes has not gathered scene data yet."); Profiler.BeginEvent("DuplicateScenes.RestoreSceneData"); // Deserialize old scenes for (int i = 0; i < _scenesData.Count; i++) { var data = _scenesData[i]; var scene = Level.LoadSceneFromBytes(data.Bytes); if (scene == null) { Profiler.EndEvent(); throw new Exception("Failed to deserialize scene"); } // Restore `dirty` state if (data.IsDirty) Editor.Instance.Scene.MarkSceneEdited(scene); } _scenesData.Clear(); Editor.Log("Restored previous scenes"); Profiler.EndEvent(); } } }