using System.Collections; using System.Collections.Generic; using UnityEngine; namespace Photon.Pun { public static class NestedComponentUtilities { public static T EnsureRootComponentExists<T, NestedT>(this Transform transform) where T : Component where NestedT : Component { var root = GetParentComponent<NestedT>(transform); if (root) { var comp = root.GetComponent<T>(); if (comp) return comp; return root.gameObject.AddComponent<T>(); } return null; } #region GetComponent Replacements // Recycled collections private static Queue<Transform> nodesQueue = new Queue<Transform>(); public static Dictionary<System.Type, ICollection> searchLists = new Dictionary<System.Type, ICollection>(); private static Stack<Transform> nodeStack = new Stack<Transform>(); /// <summary> /// Find T on supplied transform or any parent. Unlike GetComponentInParent, GameObjects do not need to be active to be found. /// </summary> public static T GetParentComponent<T>(this Transform t) where T : Component { T found = t.GetComponent<T>(); if (found) return found; var par = t.parent; while (par) { found = par.GetComponent<T>(); if (found) return found; par = par.parent; } return null; } /// <summary> /// Returns all T found between the child transform and its root. Order in List from child to parent, with the root/parent most being last. /// </summary> /// <param name="t"></param> /// <returns></returns> public static void GetNestedComponentsInParents<T>(this Transform t, List<T> list) where T : Component { list.Clear(); while (t != null) { T obj = t.GetComponent<T>(); if (obj) list.Add(obj); t = t.parent; } } public static T GetNestedComponentInChildren<T, NestedT>(this Transform t, bool includeInactive) where T : class where NestedT : class { // Look for the most obvious check first on the root. var found = t.GetComponent<T>(); if (!ReferenceEquals(found, null)) return found; // No root found, start testing layer by layer - root is the first layer. Add to queue. nodesQueue.Clear(); nodesQueue.Enqueue(t); while (nodesQueue.Count > 0) { var node = nodesQueue.Dequeue(); for (int c = 0, ccnt = node.childCount; c < ccnt; ++c) { var child = node.GetChild(c); // Ignore branches that are not active if (!includeInactive && !child.gameObject.activeSelf) continue; // Hit a nested node - don't search this node if (!ReferenceEquals(child.GetComponent<NestedT>(), null)) continue; // see if what we are looking for is on this node found = child.GetComponent<T>(); // Return if we found what we are looking for if (!ReferenceEquals(found, null)) return found; // Add node to queue for next depth pass since nothing was found on this layer. nodesQueue.Enqueue(child); } } return found; } /// <summary> /// Same as GetComponentInParent, but will always include inactive objects in search. /// </summary> /// <typeparam name="T"></typeparam> /// <typeparam name="DontRecurseOnT"></typeparam> /// <param name="t"></param> /// <returns></returns> public static T GetNestedComponentInParent<T, NestedT>(this Transform t) where T : class where NestedT : class { T found = null; Transform node = t; do { found = node.GetComponent<T>(); if (!ReferenceEquals(found, null)) return found; // stop search on node with PV if (!ReferenceEquals(node.GetComponent<NestedT>(), null)) return null; node = node.parent; } while (!ReferenceEquals(node, null)); return null; } /// <summary> /// UNTESTED /// </summary> /// <typeparam name="T"></typeparam> /// <typeparam name="StopSearchOnT"></typeparam> /// <param name="t"></param> /// <returns></returns> public static T GetNestedComponentInParents<T, NestedT>(this Transform t) where T : class where NestedT : class { // First try root var found = t.GetComponent<T>(); if (!ReferenceEquals(found, null)) return found; /// Get the reverse list of transforms climbing for start up to netobject var par = t.parent; while (!ReferenceEquals(par, null)) { found = par.GetComponent<T>(); if (!ReferenceEquals(found, null)) return found; /// Stop climbing at the NetObj (this is how we detect nesting if (!ReferenceEquals(par.GetComponent<NestedT>(), null)) return null; par = par.parent; }; return null; } /// <summary> /// Finds components of type T on supplied transform, and every parent above that node, inclusively stopping on node StopSearchOnT component. /// </summary> /// <typeparam name="T"></typeparam> /// <typeparam name="StopSearchOnT"></typeparam> /// <param name="t"></param> /// <param name="list"></param> /// <returns></returns> public static void GetNestedComponentsInParents<T, NestedT>(this Transform t, List<T> list) where T : class where NestedT : class { // Get components on the starting node - this is a given. t.GetComponents(list); // If the starting node has the stop component, we are done. if (!ReferenceEquals(t.GetComponent<NestedT>(), null)) return; var tnode = t.parent; // If there is no parent, we are done. if (ReferenceEquals(tnode, null)) return; nodeStack.Clear(); while (true) { // add new parent to stack nodeStack.Push(tnode); // if this node has the Stop, we are done recursing up. if (!ReferenceEquals(tnode.GetComponent<NestedT>(), null)) break; // Get the next parent node and add it to the stack tnode = tnode.parent; // Stop recursing up if the parent is null if (ReferenceEquals(tnode, null)) break; } if (nodeStack.Count == 0) return; System.Type type = typeof(T); // Acquire the right searchlist from our pool List<T> searchList; if (!searchLists.ContainsKey(type)) { searchList = new List<T>(); searchLists.Add(type, searchList); } else { searchList = searchLists[type] as List<T>; } // Reverse iterate the nodes found. This produces a GetComponentInParent that starts from the parent Stop down to the provided transform while (nodeStack.Count > 0) { var node = nodeStack.Pop(); node.GetComponents(searchList); list.AddRange(searchList); } } /// <summary> /// Same as GetComponentsInChildren, but will not recurse into children with component of the DontRecurseOnT type. This allows nesting of PhotonViews/NetObjects to be respected. /// </summary> /// <typeparam name="T"></typeparam> /// <param name="t"></param> /// <param name="list">Pass null and a reused list will be used. Consume immediately.</param> public static List<T> GetNestedComponentsInChildren<T, NestedT>(this Transform t, List<T> list, bool includeInactive = true) where T : class where NestedT : class { System.Type type = typeof(T); // Temp lists are also recycled. Get/Create a reusable List of this type. List<T> searchList; if (!searchLists.ContainsKey(type)) searchLists.Add(type, searchList = new List<T>()); else searchList = searchLists[type] as List<T>; nodesQueue.Clear(); if (list == null) list = new List<T>(); // Get components on starting transform - no exceptions t.GetComponents(list); // Add first layer of children to the queue for next layer processing. for (int i = 0, cnt = t.childCount; i < cnt; ++i) { var child = t.GetChild(i); // Ignore inactive nodes (optional) if (!includeInactive && !child.gameObject.activeSelf) continue; // ignore nested DontRecurseOnT if (!ReferenceEquals(child.GetComponent<NestedT>(), null)) continue; nodesQueue.Enqueue(child); } // Recurse node layers while (nodesQueue.Count > 0) { var node = nodesQueue.Dequeue(); // Add found components on this gameobject node node.GetComponents(searchList); list.AddRange(searchList); // Add children to the queue for next layer processing. for (int i = 0, cnt = node.childCount; i < cnt; ++i) { var child = node.GetChild(i); // Ignore inactive nodes (optional) if (!includeInactive && !child.gameObject.activeSelf) continue; // ignore nested NestedT if (!ReferenceEquals(child.GetComponent<NestedT>(), null)) continue; nodesQueue.Enqueue(child); } } return list; } /// <summary> /// Same as GetComponentsInChildren, but will not recurse into children with component of the DontRecurseOnT type. This allows nesting of PhotonViews/NetObjects to be respected. /// </summary> /// <typeparam name="T"></typeparam> /// <param name="t"></param> /// <param name="list">Pass null and a reused list will be used. Consume immediately.</param> public static List<T> GetNestedComponentsInChildren<T>(this Transform t, List<T> list, bool includeInactive = true, params System.Type[] stopOn) where T : class { System.Type type = typeof(T); // Temp lists are also recycled. Get/Create a reusable List of this type. List<T> searchList; if (!searchLists.ContainsKey(type)) searchLists.Add(type, searchList = new List<T>()); else searchList = searchLists[type] as List<T>; nodesQueue.Clear(); // Get components on starting transform - no exceptions t.GetComponents(list); // Add first layer of children to the queue for next layer processing. for (int i = 0, cnt = t.childCount; i < cnt; ++i) { var child = t.GetChild(i); // Ignore inactive nodes (optional) if (!includeInactive && !child.gameObject.activeSelf) continue; // ignore nested DontRecurseOnT bool stopRecurse = false; for (int s = 0, scnt = stopOn.Length; s < scnt; ++s) { if (!ReferenceEquals(child.GetComponent(stopOn[s]), null)) { stopRecurse = true; break; } } if (stopRecurse) continue; nodesQueue.Enqueue(child); } // Recurse node layers while (nodesQueue.Count > 0) { var node = nodesQueue.Dequeue(); // Add found components on this gameobject node node.GetComponents(searchList); list.AddRange(searchList); // Add children to the queue for next layer processing. for (int i = 0, cnt = node.childCount; i < cnt; ++i) { var child = node.GetChild(i); // Ignore inactive nodes (optional) if (!includeInactive && !child.gameObject.activeSelf) continue; // ignore nested NestedT bool stopRecurse = false; for (int s = 0, scnt = stopOn.Length; s < scnt; ++s) { if (!ReferenceEquals(child.GetComponent(stopOn[s]), null)) { stopRecurse = true; break; } } if (stopRecurse) continue; nodesQueue.Enqueue(child); } } return list; } /// <summary> /// Same as GetComponentsInChildren, but will not recurse into children with component of the NestedT type. This allows nesting of PhotonViews/NetObjects to be respected. /// </summary> /// <typeparam name="T">Cast found components to this type. Typically Component, but any other class/interface will work as long as they are assignable from SearchT.</typeparam> /// <typeparam name="SearchT">Find components of this class or interface type.</typeparam> /// <typeparam name="DontRecurseOnT"></typeparam> /// <param name="t"></param> /// <param name="includeInactive"></param> /// <param name="list"></param> /// <returns></returns> public static void GetNestedComponentsInChildren<T, SearchT, NestedT>(this Transform t, bool includeInactive, List<T> list) where T : class where SearchT : class { list.Clear(); // If this is inactive, nothing will be found. Give up now if we are restricted to active. if (!includeInactive && !t.gameObject.activeSelf) return; System.Type searchType = typeof(SearchT); // Temp lists are also recycled. Get/Create a reusable List of this type. List<SearchT> searchList; if (!searchLists.ContainsKey(searchType)) searchLists.Add(searchType, searchList = new List<SearchT>()); else searchList = searchLists[searchType] as List<SearchT>; // Recurse child nodes one layer at a time. Using a Queue allows this to happen without a lot of work. nodesQueue.Clear(); nodesQueue.Enqueue(t); while (nodesQueue.Count > 0) { var node = nodesQueue.Dequeue(); // Add found components on this gameobject node searchList.Clear(); node.GetComponents(searchList); foreach (var comp in searchList) { var casted = comp as T; if (!ReferenceEquals(casted, null)) list.Add(casted); } // Add children to the queue for next layer processing. for (int i = 0, cnt = node.childCount; i < cnt; ++i) { var child = node.GetChild(i); // Ignore inactive nodes (optional) if (!includeInactive && !child.gameObject.activeSelf) continue; // ignore nested DontRecurseOnT if (!ReferenceEquals(child.GetComponent<NestedT>(), null)) continue; nodesQueue.Enqueue(child); } } } #endregion } }