using System; using System.Collections.Generic; using UnityEngine; namespace jeanf.scenemanagement { /// /// Runtime tracker that collects scene loading events for debugging purposes. /// This is a singleton that persists across domain reloads. /// public class SceneLoadingTracker : MonoBehaviour { private static SceneLoadingTracker _instance; public static SceneLoadingTracker Instance { get { if (_instance == null) { var go = new GameObject("[SceneLoadingTracker]"); _instance = go.AddComponent(); DontDestroyOnLoad(go); } return _instance; } } public enum SceneType { Subscene, AdditiveScene, Section } public enum LoadingState { Pending, Loading, Loaded, Failed, Unloading, Unloaded } [Serializable] public class SceneLoadingEvent { public string sceneName; public SceneType sceneType; public LoadingState state; public float timestamp; public float? startTime; public float? endTime; public string errorMessage; public string parentScene; public List dependencies = new List(); public int eventId; public SceneLoadingEvent(string name, SceneType type, LoadingState initialState, string parent = null) { sceneName = name; sceneType = type; state = initialState; timestamp = Time.realtimeSinceStartup; parentScene = parent; eventId = GetHashCode(); } } private List _events = new List(); private Dictionary _activeScenes = new Dictionary(); private const int MAX_EVENTS = 1000; public delegate void OnEventAdded(SceneLoadingEvent evt); public event OnEventAdded EventAdded; public delegate void OnEventUpdated(SceneLoadingEvent evt); public event OnEventUpdated EventUpdated; private void Awake() { if (_instance != null && _instance != this) { Destroy(gameObject); return; } _instance = this; DontDestroyOnLoad(gameObject); } public void TrackSceneLoadStart(string sceneName, SceneType type, string parent = null) { var evt = new SceneLoadingEvent(sceneName, type, LoadingState.Loading, parent) { startTime = Time.realtimeSinceStartup }; _activeScenes[sceneName] = evt; AddEvent(evt); } public void TrackSceneLoadComplete(string sceneName) { if (_activeScenes.TryGetValue(sceneName, out var evt)) { evt.state = LoadingState.Loaded; evt.endTime = Time.realtimeSinceStartup; EventUpdated?.Invoke(evt); } } public void TrackSceneLoadFailed(string sceneName, string errorMessage) { if (_activeScenes.TryGetValue(sceneName, out var evt)) { evt.state = LoadingState.Failed; evt.endTime = Time.realtimeSinceStartup; evt.errorMessage = errorMessage; EventUpdated?.Invoke(evt); } } public void TrackSceneUnloadStart(string sceneName) { if (_activeScenes.TryGetValue(sceneName, out var evt)) { evt.state = LoadingState.Unloading; EventUpdated?.Invoke(evt); } else { var unloadEvent = new SceneLoadingEvent(sceneName, SceneType.AdditiveScene, LoadingState.Unloading); _activeScenes[sceneName] = unloadEvent; AddEvent(unloadEvent); } } public void TrackSceneUnloadComplete(string sceneName) { if (_activeScenes.TryGetValue(sceneName, out var evt)) { evt.state = LoadingState.Unloaded; evt.endTime = Time.realtimeSinceStartup; EventUpdated?.Invoke(evt); _activeScenes.Remove(sceneName); } } public void TrackSubsceneLoadStart(string subsceneName, string parent = null) { TrackSceneLoadStart(subsceneName, SceneType.Subscene, parent); } public void TrackSectionLoadStart(string sectionName, string parent = null) { TrackSceneLoadStart(sectionName, SceneType.Section, parent); } private void AddEvent(SceneLoadingEvent evt) { _events.Add(evt); // Maintain max size if (_events.Count > MAX_EVENTS) { _events.RemoveAt(0); } EventAdded?.Invoke(evt); } public List GetAllEvents() { return new List(_events); } public List GetActiveScenes() { return new List(_activeScenes.Values); } public void Clear() { _events.Clear(); _activeScenes.Clear(); } public int GetEventCount() { return _events.Count; } public int GetActiveSceneCount() { return _activeScenes.Count; } } }