using System; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.SceneManagement; namespace Proxima { internal class ProximaGameObjectCommands { [Serializable] internal class GameObjectInfo { public int Id; public string Name; public bool ActiveSelf; public int Parent; public int Depth; public int SiblingIndex; public int ChildCount; public int Order; public int Layer; public string Tag; public string Scene; } [Serializable] internal class GameObjectList { public List GameObjects = new List(); public string[] Layers; public List Destroyed = new List(); } private static Dictionary _idToGameObject; public static Dictionary IdToGameObject => _idToGameObject; private static List _rootGameObjects; private static GameObjectList _gameObjects; private static GameObjectList _changeList; private static Dictionary _gameObjectToInfo; private static int _lastUpdate; private static int _order; private static GameObject _lastUpdatedGameObject; private static int _lastDeleteCheckedGameObjectInfo = -1; private static Scene _dontDestroyOnLoadScene; private static HashSet _pendingStreamIds; private static bool _showHidden; public static bool ShowHidden => _showHidden; [ProximaInitialize] public static void Init() { _idToGameObject = new Dictionary(); _rootGameObjects = new List(); _gameObjects = new GameObjectList(); _gameObjects.Layers = Enumerable.Range(0, 31).Select(index => LayerMask.LayerToName(index)).ToArray(); _changeList = new GameObjectList(); _gameObjectToInfo = new Dictionary(); _lastUpdate = -1; _pendingStreamIds = new HashSet(); var ddolGO = new GameObject("Proxima DDOL"); GameObject.DontDestroyOnLoad(ddolGO); _dontDestroyOnLoadScene = ddolGO.scene; GameObject.Destroy(ddolGO); } [ProximaTeardown] public static void Teardown() { _idToGameObject = null; _rootGameObjects = null; _gameObjects = null; _changeList = null; _gameObjectToInfo = null; _lastUpdate = -1; _pendingStreamIds = null; _order = 0; _lastUpdatedGameObject = null; _lastDeleteCheckedGameObjectInfo = -1; _dontDestroyOnLoadScene = default; } [ProximaCommand("Internal")] public static void SetParent(int id, int parentId, int index) { if (_idToGameObject.TryGetValue(id, out var go)) { _idToGameObject.TryGetValue(parentId, out var parentGo); go.transform.SetParent(parentGo?.transform, true); go.transform.SetSiblingIndex(index); } else { Log.Warning($"SetParent: GameObject with id {id} not found"); } } [ProximaCommand("Internal")] public static void SetScene(int id, int sceneIndex, int index) { if (_idToGameObject.TryGetValue(id, out var go)) { if (sceneIndex < SceneManager.sceneCount) { go.transform.SetParent(null, true); SceneManager.MoveGameObjectToScene(go, SceneManager.GetSceneAt(sceneIndex)); go.transform.SetSiblingIndex(index); } } else { Log.Warning($"SetScene: GameObject with id {id} not found"); } } [ProximaCommand("Internal")] public static void SetActive(int id, bool active) { if (_idToGameObject.TryGetValue(id, out var go)) { go.SetActive(active); } else { Log.Warning($"SetActive: GameObject with id {id} not found"); } } [ProximaCommand("Internal")] public static void SetName(int id, string name) { if (_idToGameObject.TryGetValue(id, out var go)) { go.name = name; } else { Log.Warning($"SetName: GameObject with id {id} not found"); } } [ProximaCommand("Internal")] public static void SetLayer(int id, int layer) { if (_idToGameObject.TryGetValue(id, out var go)) { go.layer = layer; } else { Log.Warning($"SetLayer: GameObject with id {id} not found"); } } [ProximaCommand("Internal")] public static void SetTag(int id, string tag) { if (_idToGameObject.TryGetValue(id, out var go)) { go.tag = tag; } else { Log.Warning($"SetTag: GameObject with id {id} not found"); } } [ProximaCommand("Internal")] public static void CreateGameObject(int parentId) { var go = new GameObject(); if (_idToGameObject.TryGetValue(parentId, out var parentGo)) { go.transform.SetParent(parentGo.transform, true); } } [ProximaCommand("Internal")] public static void DuplicateGameObject(int id) { if (!_idToGameObject.TryGetValue(id, out var go)) { Log.Warning($"DuplicateGameObject: GameObject with id {id} not found"); } GameObject.Instantiate(go, go.transform.parent); } [ProximaCommand("Internal")] public static void DestroyGameObject(int id) { if (_idToGameObject.TryGetValue(id, out var go) && go) { GameObject.Destroy(go); } else { Log.Warning($"DestroyGameObject: GameObject with id {id} not found"); } } [ProximaCommand("Internal")] public static void AddGameObjectComponent(int id, string component) { if (!_idToGameObject.TryGetValue(id, out var go)) { Log.Warning($"AddComponent: GameObject with id {id} not found"); return; } var componentType = ProximaCommandHelpers.FindFirstComponentType(component); if (componentType == null) { throw new Exception($"Component type {component} not found."); } go.AddComponent(componentType); } [ProximaCommand("Internal")] public static void SetShowHidden(bool showHidden) { _showHidden = showHidden; } [ProximaStreamStart("GameObjects")] public static void StartStream(string id) { _pendingStreamIds.Add(id); } [ProximaStreamUpdate("GameObjects")] public static GameObjectList UpdateStream(string id) { UpdateInfoLists(); if (_pendingStreamIds.Contains(id)) { _pendingStreamIds.Remove(id); return _gameObjects; } bool changed = _changeList.Destroyed.Count > 0 || _changeList.GameObjects.Count > 0; return changed ? _changeList : null; } [ProximaStreamStop("GameObjects")] public static void StopStream(string id) { _pendingStreamIds.Remove(id); } public static void UpdateInfoLists() { if (_lastUpdate == Time.frameCount) { return; } _lastUpdate = Time.frameCount; _changeList.GameObjects.Clear(); _changeList.Destroyed.Clear(); if (!_lastUpdatedGameObject) { _lastUpdatedGameObject = null; } var updates = 0; while (updates <= ProximaInspector.MaxGameObjectUpdatesPerFrame && UpdateNextGameObjectInfo()) { updates++; } var deleteUpdates = 0; while (deleteUpdates <= ProximaInspector.MaxGameObjectUpdatesPerFrame && UpdateNextDeletedGameObjectInfo()) { deleteUpdates++; } } private static bool UpdateNextGameObjectInfo() { _lastUpdatedGameObject = GetNextGameObject(_lastUpdatedGameObject); if (_lastUpdatedGameObject != null) { UpdateGameObjectInfo(_lastUpdatedGameObject); return true; } return false; } private static bool ShouldHide(GameObject go) { return !_showHidden && go.hideFlags.HasFlag(HideFlags.HideInHierarchy); } private static GameObject GetNextChildGameObject(Transform t) { for (int i = 0; i < t.childCount; i++) { var child = t.GetChild(i); if (!ShouldHide(child.gameObject)) { return child.gameObject; } } return null; } private static GameObject GetNextSiblingGameObject(Transform parent, Transform t) { var nextSiblingIndex = t.GetSiblingIndex() + 1; while (nextSiblingIndex < parent.childCount) { var nextSibling = parent.GetChild(nextSiblingIndex); var go = nextSibling.gameObject; if (!ShouldHide(go)) { return go; } nextSiblingIndex++; } return null; } private static GameObject GetNextRootGameObject(GameObject go) { var rootIndex = _rootGameObjects.IndexOf(go); if (rootIndex >= 0) { return GetNextRootGameObject(rootIndex + 1); } return null; } private static GameObject GetNextRootGameObject(int rootIndex) { while (rootIndex < _rootGameObjects.Count) { var nextRoot = _rootGameObjects[rootIndex]; if (nextRoot && !ShouldHide(nextRoot)) { return nextRoot; } rootIndex++; } return null; } private static GameObject GetNextSceneGameObject(Scene? currentScene) { var nextSceneIndex = 0; for (int i = 0; i < SceneManager.sceneCount; i++) { if (SceneManager.GetSceneAt(i) == currentScene) { nextSceneIndex = i + 1; } } if (nextSceneIndex == 0) { _order = 0; } while (nextSceneIndex < SceneManager.sceneCount) { var nextScene = SceneManager.GetSceneAt(nextSceneIndex); if (nextScene.isLoaded) { nextScene.GetRootGameObjects(_rootGameObjects); var nextRoot = GetNextRootGameObject(0); if (nextRoot != null) { return nextRoot; } } nextSceneIndex++; } if (_dontDestroyOnLoadScene != null && currentScene != _dontDestroyOnLoadScene) { _dontDestroyOnLoadScene.GetRootGameObjects(_rootGameObjects); var nextRoot = GetNextRootGameObject(0); if (nextRoot != null) { return nextRoot; } } return null; } private static GameObject GetNextGameObject(GameObject go) { if (go == null) { return GetNextSceneGameObject(null); } _order++; var t = go.transform; var nextChild = GetNextChildGameObject(t); if (nextChild != null) { return nextChild; } var parent = t.parent; while (parent != null) { var nextSibling = GetNextSiblingGameObject(parent, t); if (nextSibling != null) { return nextSibling; } t = parent; parent = t.parent; } go = t.gameObject; go.scene.GetRootGameObjects(_rootGameObjects); var nextRoot = GetNextRootGameObject(go); if (nextRoot != null) { return nextRoot; } return GetNextSceneGameObject(go.scene); } private static bool FastStringEqual(string lhs, string rhs) { return lhs.Length == rhs.Length && lhs[0] == rhs[0] && lhs[lhs.Length - 1] == rhs[rhs.Length - 1]; } private static void UpdateGameObjectInfo(GameObject go) { var id = go.GetInstanceID(); var parentId = go.transform.parent ? go.transform.parent.gameObject.GetInstanceID() : 0; var siblingIndex = go.transform.GetSiblingIndex(); int depth = 0; if (go.transform.parent) { if (_gameObjectToInfo.TryGetValue(parentId, out var parentGoi)) { depth = parentGoi.Depth + 1; } } if (!_gameObjectToInfo.TryGetValue(id, out var goi)) { goi = new GameObjectInfo { Id = id, Name = go.name, ActiveSelf = go.activeSelf, Parent = parentId, Depth = depth, SiblingIndex = siblingIndex, ChildCount = go.transform.childCount, Order = _order, Tag = go.tag, Layer = go.layer, Scene = depth == 0 ? go.scene.name + "#" + go.scene.handle : null }; _gameObjects.GameObjects.Add(goi); _gameObjectToInfo.Add(id, goi); _idToGameObject.Add(id, go); _changeList.GameObjects.Add(goi); } else { if (!FastStringEqual(goi.Name, go.name) || goi.ActiveSelf != go.activeSelf || goi.Parent != parentId || goi.Depth != depth || goi.SiblingIndex != siblingIndex || goi.ChildCount != go.transform.childCount || goi.Order != _order || !FastStringEqual(goi.Tag, go.tag) || goi.Layer != go.layer) { goi.Name = go.name; goi.ActiveSelf = go.activeSelf; goi.Parent = parentId; goi.Depth = depth; goi.SiblingIndex = siblingIndex; goi.ChildCount = go.transform.childCount; goi.Order = _order; goi.Tag = go.tag; goi.Layer = go.layer; goi.Scene = depth == 0 ? go.scene.name + "#" + go.scene.handle : null; _changeList.GameObjects.Add(goi); } } } private static bool UpdateNextDeletedGameObjectInfo() { if (_gameObjects.GameObjects.Count == 0) { return false; } _lastDeleteCheckedGameObjectInfo = (_lastDeleteCheckedGameObjectInfo + 1) % _gameObjects.GameObjects.Count; var goi = _gameObjects.GameObjects[_lastDeleteCheckedGameObjectInfo]; var go = _idToGameObject[goi.Id]; if (!go || ShouldHide(go)) { _gameObjectToInfo.Remove(goi.Id); _gameObjects.GameObjects.RemoveAt(_lastDeleteCheckedGameObjectInfo); _changeList.Destroyed.Add(goi.Id); _idToGameObject.Remove(goi.Id); } return true; } } }