void InsertTask(CollisionTask task, int index)
        {
            //This allocates a lot of garbage due to frequently resizing, but it does not matter- task registration a one time thing at program initialization.
            //Having tight bounds is more useful for performance in the end (by virtue of having a marginally simpler heap).
            int newCount = count + 1;

            if (tasks == null || newCount > tasks.Length)
            {
                Array.Resize(ref tasks, newCount);
            }
            if (index < count)
            {
                Array.Copy(tasks, index, tasks, index + 1, count - index);
                //Every task in the array that got moved needs to have its top level index bumped.
                for (int i = 0; i < topLevelMatrix.Length; ++i)
                {
                    for (int j = 0; j < topLevelMatrix.Length; ++j)
                    {
                        if (topLevelMatrix[i][j].TaskIndex >= index)
                        {
                            ++topLevelMatrix[i][j].TaskIndex;
                        }
                    }
                }
                for (int i = index + 1; i < newCount; ++i)
                {
                    var t = tasks[i];
                    if (t.SubtaskIndices != null)
                    {
                        for (int j = 0; j < t.SubtaskIndices.Length; ++j)
                        {
                            ref var subtaskIndex = ref t.SubtaskIndices[j];
                            if (subtaskIndex >= index)
                            {
                                ++subtaskIndex;
                            }
                        }
                    }
                }
            }
        public int Register(CollisionTask task)
        {
            //Some tasks can generate tasks. Note that this can only be one level deep; nesting compounds is not allowed.
            //All such generators will be placed at the beginning.
            var index = task.SubtaskGenerator ? 0 : count;

            //This allocates a lot of garbage due to frequently resizing, but it does not matter- task registration a one time thing at program initialization.
            //Having tight bounds is more useful for performance in the end (by virtue of having a marginally simpler heap).
            int newCount = count + 1;

            if (tasks == null || newCount > tasks.Length)
            {
                Array.Resize(ref tasks, newCount);
            }
            if (index < count)
            {
                Array.Copy(tasks, index, tasks, index + 1, count - index);
                //Every task in the array that got moved needs to have its top level index bumped.
                for (int i = 0; i < topLevelMatrix.Length; ++i)
                {
                    for (int j = 0; j < topLevelMatrix.Length; ++j)
                    {
                        if (topLevelMatrix[i][j].TaskIndex >= index)
                        {
                            ++topLevelMatrix[i][j].TaskIndex;
                        }
                    }
                }
            }

            tasks[index] = task;
            count        = newCount;

            var a = task.ShapeTypeIndexA;
            var b = task.ShapeTypeIndexB;
            var highestShapeIndex = a > b ? a : b;

            if (highestShapeIndex >= 0)
            {
                //This only handles top level pairs (those associated with shape type pairs).
                //Some tasks are not directly associated with a top level entrypoint- instead, they're follow ups on top level tasks. Since they're not an entry point,
                //there is no need for them to appear in the top level matrix.
                if (highestShapeIndex >= topLevelMatrix.Length)
                {
                    ResizeMatrix(highestShapeIndex + 1);
                }
                var taskInfo = new CollisionTaskReference
                {
                    TaskIndex           = index,
                    BatchSize           = task.BatchSize,
                    ExpectedFirstTypeId = task.ShapeTypeIndexA,
                    PairType            = task.PairType
                };
                topLevelMatrix[a][b] = taskInfo;
                topLevelMatrix[b][a] = taskInfo;
            }

#if DEBUG
            //Ensure that no task dependency cycles exist.
            bool encounteredNongenerator = false;
            for (int i = 0; i < count; ++i)
            {
                if (encounteredNongenerator)
                {
                    Debug.Assert(!tasks[i].SubtaskGenerator,
                                 "To avoid cycles, the tasks list should be partitioned into two contiguous groups: subtask generators, followed by non-subtask generators.");
                }
                else
                {
                    if (!tasks[i].SubtaskGenerator)
                    {
                        encounteredNongenerator = true;
                    }
                }
            }
#endif
            return(index);
        }