Transform Enumeration Extensions

Hi All, I wrote a couple versions of a Transform Extension iterator function, that allows me to looks through all descendants of a transform, rather than just it’s immediate children, using a foreach loop. I wrote both a recursive, and since I heard that’s not a good always a good idea, a non-recursive version. I hoping this will prove useful to people, and looking for feed-back on improving them.

usage:

foreach (Transform t in transformToDisplay.AllDecendantsNonRecursive())
                //foreach (Transform t in transformToDisplay.AllDecendants())
                {
                    Debug.Log(t.position.ToString());
                }

Here are the actual extension functions:

class TransformExtensions
{
        public static Transform[] GetChildrenArray(this Transform transform)
        {
            Transform[] childArray= new Transform[transform.childCount];
            int index=0;
            foreach(Transform c in transform)
            {
                childArray[index++]= c;
            }
            return childArray;
        }

        //! Extension provided to enable foreach through transform and all decendants, rather than just children.
        public static IEnumerable<Transform> TransformAndAllDecendants(this Transform transform)
        {
            yield return transform;
            foreach (Transform decendant in transform.AllDecendants())
            {
                yield return decendant;
            }

        }

        //! Extension provided to enable foreach through all decendants, rather than just children.
        public static IEnumerable<Transform> AllDecendants(this Transform transform)
        {
           // yield return transform;
            foreach (Transform c in transform)
            {
                yield return c;
            
                if (c.childCount > 0)
                {
                    foreach (Transform decendant in c.AllDecendants())
                    {
                        yield return decendant;
                    }
                }
            }
        }

        private static Transform currentTransform;
        private static int currentChildIndex;
        private static List<Transform> stack;
        private static List<int> Istack;
        private static List<Transform[]> childArrayStack;
        private static Transform[] currentTransformChildrenArray;
        private static Transform child;
        public static IEnumerable<Transform> AllDecendantsNonRecursive(this Transform transform)
        {
            stack = new List<Transform>();
            Istack = new List<int>();
            childArrayStack = new List<Transform[]>();
            currentTransformChildrenArray = transform.GetChildrenArray();
            currentTransform = transform;
            currentChildIndex = 0;
            while(true)
            {
                if (currentChildIndex < currentTransform.childCount)
                {
                    child = currentTransformChildrenArray[currentChildIndex++];
                    yield return child;
                    if (child.childCount > 0)
                    {
                        stack.Add(currentTransform);
                        Istack.Add(currentChildIndex);
                        childArrayStack.Add(currentTransformChildrenArray);
                        currentTransform = child;
                        currentChildIndex = 0;
                        currentTransformChildrenArray = currentTransform.GetChildrenArray();
                    }//current child has children
                }
                else //reached beyond last child, pop up.
                {
                    //pup up
                    int lastIndex = stack.Count - 1;
                    if (lastIndex >= 0)
                    {
                        currentTransform = stack[lastIndex];
                        currentChildIndex = Istack[lastIndex];
                        currentTransformChildrenArray = childArrayStack[lastIndex];
                        stack.RemoveAt(lastIndex);
                        Istack.RemoveAt(lastIndex);
                        childArrayStack.RemoveAt(lastIndex);
                    }
                    else
                    {
                        // top node, stop popping up: all done
                        yield break;
                    }
                }// if done with all children for current transform
            
            
            }// end loop

        
        }// end AllDecendantsNonRecursive

}

Cool.

Here’s the ones I use:

        public static Transform[] GetAllChildren(this Transform t)
        {
            using (var lst = TempCollection.GetList<Transform>())
            {
                GetAllChildren(t, lst);

                return lst.ToArray();
            }
        }

        public static void GetAllChildren(this Transform t, ICollection<Transform> coll)
        {
            if(coll is IList<Transform>)
            {
                GetAllChildren(t, coll as IList<Transform>);
            }
            else
            {
                using (var lst = TempCollection.GetList<Transform>())
                {
                    GetAllChildren(t, lst);
                    var e = lst.GetEnumerator();
                    while (e.MoveNext()) coll.Add(e.Current);
                }
            }
        }

        public static void GetAllChildren(this Transform t, IList<Transform> lst)
        {
            int i = lst.Count;
         
            for(int j = 0; j < t.childCount; j++)
            {
                lst.Add(t.GetChild(j));
            }

            while (i < lst.Count)
            {
                t = lst[i];
                for (int j = 0; j < t.childCount; j++)
                {
                    lst.Add(t.GetChild(j));
                }
                i++;
            }
        }

        public static Transform[] GetAllChildrenAndSelf(this Transform t)
        {
            using (var lst = TempCollection.GetList<Transform>())
            {
                GetAllChildrenAndSelf(t, lst);
                return lst.ToArray();
            }
        }

        public static void GetAllChildrenAndSelf(this Transform t, ICollection<Transform> coll)
        {
            if (coll is IList<Transform>)
            {
                GetAllChildrenAndSelf(t, coll as IList<Transform>);
            }
            else
            {
                using (var lst = TempCollection.GetList<Transform>())
                {
                    GetAllChildrenAndSelf(t, lst);
                    var e = lst.GetEnumerator();
                    while (e.MoveNext()) coll.Add(e.Current);
                }
            }
        }

        public static void GetAllChildrenAndSelf(this Transform t, IList<Transform> lst)
        {
            int i = lst.Count;
            lst.Add(t);

            while (i < lst.Count)
            {
                t = lst[i];
                for(int j = 0; j < t.childCount; j++)
                {
                    lst.Add(t.GetChild(j));
                }
                i++;
            }
        }

Found here:

I personally tried to optimize for avoiding as much garbage as I can. One of the critical things I do is have recyclable lists. You can see that here:

Furthermore you may want to check out how I did my nonrecursive traversal, a little bit shorter in its code length.

Oh neat!
what the heck does this do?
using(var lst = TempCollection.GetList())

I don’t see TempCollection defined anywhere… that the recyclable list you mentioned?
Edit: followed link, finally, duh!

So when you iterate the hierarchy your order is:
sibling1,sibling2,sibling3, child1 of sibling1, child2 of sibling1 child of sibling3
is that correct?

I was going for the other tree order option:
sibling 1, child1 of sibling 1, child2 of sibling1, sibling 2, sibling 3, child of sibling 3

Still, I love the way you use the OUTPUT list, INSIDE the function loop, to determine what child transforms to iterate through.

Yeah, it’s that.

Yeah, when doing a non-recursive tree search, going layer by layer is a simpler algorithm, which is why I go with that one.

This is primarily just to avoid garbage. It avoids the whole creating new arrays when one already exists.

Unity recently started implementing methods like this themselves, like the ‘GetComponents’ method that accepts a list, or the Physics.RayCastNonAlloc method.

I tried to follow your example of using the output list to do the child iteration, but I found myself stuck using INSERT(index), to get the output order I’m looking for :frowning:

static public List<Transform> AllDecendantsList(this Transform transform)
        {
            List<Transform> returnValue = new List<Transform>();
            foreach(Transform t in transform)
            {
                returnValue.Add(t);
            }
            int CurrentTransformIndex=0;
            while (CurrentTransformIndex<returnValue.Count)
            {

                Transform currentTransform= returnValue[CurrentTransformIndex++];
                if (currentTransform.childCount > 0)
                {
                    int childCounter = 0;
                    foreach (Transform t in currentTransform)
                    {
                        returnValue.Insert(CurrentTransformIndex + childCounter, t);
                        childCounter++;
                    }

                }
            }
            return returnValue;
        }

At work currently, can’t dig into this algorithm right now, can later though.

It’s basically the same algorithm you used in GetAllChildren(thisTransform t, IList lst), except instead of just adding to the end, I use Insert(index,item) to put the children in the order I was looking for (immediately after their parent).

Though it IS an insert operation, I suspect this version is far superior to all the crappy stack stuff I was doing before. Many thanks for the examples!

Edit:
Here we go, now it sorts either way…

static public List<Transform> AllDecendantsList(this Transform transform, bool groupByLevel=false)
        {
            List<Transform> returnValue = new List<Transform>();
            foreach(Transform t in transform)
            {
                returnValue.Add(t);
            }
            int CurrentTransformIndex=0;
           
            while (CurrentTransformIndex< returnValue.Count)
            {

                Transform currentTransform= returnValue[CurrentTransformIndex++];
                if (currentTransform.childCount > 0)
                {
                    int childCounter = 0;
                    foreach (Transform t in currentTransform)
                    {
                        if (groupByLevel)
                            returnValue.Add(t);
                        else
                            returnValue.Insert(CurrentTransformIndex + childCounter, t);
                        childCounter++;
                    }

                }
            }
            return returnValue;
        }

Inserts are going to be slow…

The fastest way to get the order you want is with a stack… but you don’t need to create one, there’s a stack (in the form of a link list) already in place.
Transform.parent

With that in mind, this should work:

    public static void GetAllChildren(this Transform t, IList<Transform> lst)
    {
//traversed as pre-order instead of level-order
        for (int i = 0; i < t.childCount; i++)
        {
            Transform node = t.GetChild(i);
            while (!object.ReferenceEquals(node, null))
            {
                lst.Add(node);
                if (node.childCount > 0)
                {
                    node = node.GetChild(0);
                }
                else
                {
                    int j = node.GetSiblingIndex() + 1;
                    while (node.parent != t && j >= node.parent.childCount)
                    {
                        node = node.parent;
                        j = node.GetSiblingIndex() + 1;
                    }

                    node = (node.parent == t) ? null : node.parent.GetChild(j);
                }
            }
        }
    }

note, I didn’t really test this, I just used the standard pre-order traversal algorithm and inserted the link list to remember the trail instead of a stack.

Ah, GetSiblingIndex(): slick. I didn’t realize that data was available (I didn’t even count on it being consistent). Normally when working with tree structure it is NOT safe to assume nodes are in any particular order. I should have realized that is NOT the case with a transform tree, since GetChild(int) is provided.

I assume this value represents the order the transforms are displayed, under the parent, in the hierarchy tree? Just to confirm, this does NOT effect ANYTHING else, right?

note to self: F6 will NOT compile my post, LOL

The method only fills a list using pre-order traversal of the tree, it doesn’t change anything.

Pre-ordered depth first search is the name of the traversal you prefer. Where as my original code was in ‘level-order’ or ‘breadth-first search’. See:
https://en.wikipedia.org/wiki/Tree_traversal

I realized I tried to compile my forum post, but THAT much was clear :wink:
I was talking about the unity provided GetSiblingIndex. I understand what it represents, I just wonder what that value actually does in Unity, and when it might change.

Oh.

So the hierarchy tree in unity allows multiple children, and those children are indexed. This way their order is preserved. So like when you like in the hierarchy view, the order of the children from top to bottom is the index order of those children.

It changes if you move its indexed position. This can be done through the hierarchy view, or programmatically via the sibling indexing methods:

See:
SetSiblingIndex
SetAsFirstSibling
SetAsLastSibling

Or if a new GameObject is added to the child list.

While you’re doing this traversal, and because we know that the Unity API is single threaded, we can be sure that this value won’t change while gathering the information (unless you explicitly call those methods in the algorithm).

I didn’t even realize what was bugging me about the sibling index stuff: that was exactly it. (And why I don’t need to worry about it. I just can’t STORE that index, and expect it to be the same later.)

Many thanks for your posts!