Example #1
0
        public void Sort(Action <int, int> removedEdge)
        {
            if (IsSorted)
            {
                return;
            }
            IsSorted = true;

            ProcesseDeferredForcedDrawOrders();


            int vertexCount = this.Count;

            // INITIALISE
            {
                if (edgeBits.Length < EdgeBits.Size(vertexCount))
                {
                    edgeBits = EdgeBits.Create(vertexCount * 2);
                }
                else
                {
                    Array.Clear(edgeBits, 0, EdgeBits.Size(vertexCount));
                }
            }


            // Step 1: Determine what items overlap and store in the edge bits:
            {
                // This is O(n^2) on drawn items, so we want it to be fast (later we could consider bucketing)
                for (int f = 0; f < vertexCount; f++)
                {
                    for (int t = f + 1; t < vertexCount; t++)
                    {
                        if (bounds[f].Intersects(bounds[t]))
                        {
                            edgeBits.SetEdge(vertexCount, f, t); // Abuse "edgeBits" for temporary storage
                        }
                    }
                }

                // Don't do ordering comparisons if we're just going to force the order:
                foreach (var order in forceDrawOrder)
                {
                    // Rather than picking the correct bit, just clear both:
                    edgeBits.ClearEdge(vertexCount, order.from, order.to);
                    edgeBits.ClearEdge(vertexCount, order.to, order.from);
                }

                // Don't do ordering comparisons in cases that can be handled by inheritance:
                foreach (var inheritance in inheritDrawOrders)
                {
                    for (int f = 0; f < inheritance.from; f++) // <- split loops due to ordering in edgeBits
                    {
                        if (f != inheritance.to && edgeBits.IsEdge(vertexCount, f, inheritance.from))
                        {
                            // Rather than picking the correct bit, just clear both:
                            edgeBits.ClearEdge(vertexCount, f, inheritance.to);
                            edgeBits.ClearEdge(vertexCount, inheritance.to, f);
                        }
                    }
                    for (int t = inheritance.from + 1; t < vertexCount; t++) // <- split loops due to ordering in edgeBits
                    {
                        if (t != inheritance.to && edgeBits.IsEdge(vertexCount, inheritance.from, t))
                        {
                            // Rather than picking the correct bit, just clear both:
                            edgeBits.ClearEdge(vertexCount, t, inheritance.to);
                            edgeBits.ClearEdge(vertexCount, inheritance.to, t);
                        }
                    }
                }
            }



            // Step 2: Compare all overlapping items to geneate the directed graph:
            {
                for (int f = 0; f < vertexCount; f++)
                {
                    for (int t = f + 1; t < vertexCount; t++)
                    {
                        if (edgeBits.IsEdge(vertexCount, f, t))
                        {
                            int compare = DrawOrdering.Compare(sortingInfo[f].animationSet, sortingInfo[t].animationSet,
                                                               sortingInfo[f].position, sortingInfo[t].position, sortingInfo[f].facingLeft, sortingInfo[t].facingLeft, null);

                            // If compare is negative, we basically want to leave the existing bit in place, otherwise clear it and maybe set its reverse
                            if (compare >= 0)
                            {
                                edgeBits.ClearEdge(vertexCount, f, t);
                                if (compare > 0)
                                {
                                    edgeBits.SetEdge(vertexCount, t, f);
                                }
                            }
                        }
                    }
                }

                // Apply forced ordering:
                foreach (var order in forceDrawOrder)
                {
                    edgeBits.SetEdge(vertexCount, order.from, order.to);
                }

                // Apply inherited ordering:
                foreach (var inheritance in inheritDrawOrders)
                {
                    for (int i = 0; i < vertexCount; i++)
                    {
                        if (i == inheritance.from || i == inheritance.to)
                        {
                            continue;
                        }

                        uint edgeF = edgeBits.GetEdgeBit(vertexCount, inheritance.from, i);
                        uint edgeT = edgeBits.GetEdgeBit(vertexCount, i, inheritance.from);
                        if ((edgeF | edgeT) != 0) // <- if the source has an opinion, copy it
                        {
                            edgeBits.ChangeEdgeBit(vertexCount, inheritance.to, i, edgeF);
                            edgeBits.ChangeEdgeBit(vertexCount, i, inheritance.to, edgeT);
                        }
                    }
                }
            }


            // Step 3: Topological Sort!
            {
                sortedOrder = forgivingTopologicalSort.Sort(vertexCount, edgeBits, removedEdge);
            }
        }
        SCC[] bestSCCs; // count is a local


        /// <summary>NOTE: Returns a reference to an internal array which is cleared on subsequent sorts. May be longer than the passed in vertexCount - excess data is garbage.</summary>
        public int[] Sort(int vertexCount, uint[] edgeBits, Action <int, int> removedEdge)
        {
            //
            // INITIALIZE:
            //
            {
                if (primaryVertices == null || primaryVertices.Length < vertexCount)
                {
                    // NOTE: Lazy over-allocate here, to avoid reallocation
                    primaryVertices = new int[vertexCount * 2];
                    primarySCCStack = new SCC[(vertexCount * 2) / 3]; // worst-case capacity required is that every vertex triplet is an SCC

                    // Secondary stage (NOTE: worst-case is that the whole of primary is a SCC, so this must be the same size)
                    secondaryEdgeBits = EdgeBits.Create(vertexCount * 2); // NOTE: This allocates 4x as much memory as was passed in for edges
                    secondaryVertices = new int[vertexCount * 2];
                    secondarySCCs     = new SCC[(vertexCount * 2) / 3];
                    bestVertices      = new int[vertexCount * 2];
                    bestSCCs          = new SCC[(vertexCount * 2) / 3];
                    tempOutput        = new int[vertexCount * 2];
                }
                else
                {
                    // NOTE: Nothing to clear (all items have counts and get re-initialized up to their count upon usage)
                }
            }


            //
            // RUN:
            //
            {
                // Primary:
                // --------
                TarjanOutput primaryOutput = new TarjanOutput();
                primaryOutput.sccList    = primarySCCStack;
                primaryOutput.vertexList = primaryVertices;

                ta.FindStronglyConnectedComponents(vertexCount, edgeBits, ref primaryOutput);


                // Secondary: (this is a greedy search)
                // ----------
                // Try to resolve any SCCs remaining in the output
                // Note that changes to a SCC do not affect the ordering of the surrounding DAG
                while (primaryOutput.sccCount > 0)
                {
                    SCC workingSCC = primaryOutput.sccList[--primaryOutput.sccCount]; // Pop

                    // The secondary stage uses indirect indices (ie: vertex number in primary = scc[vertex number in secondary])
                    // This allows the secondary stage to use implicit vertex numbers (0 to N-1, rather than having to access workingSCC)
                    // This requires rebuilding edges to match this indexing scheme (secondaryEdge[v, w] = primaryEdge[scc[v], scc[w]])
                    // But this is desireable, because the access pattern into the primary edges would be cache-unfriendly
                    // Also secondary is generally much smaller than primary (because it works on individual SCCs, which are usually relatively small)

                    // Rebuild edge bits:
                    int v, w;  // v is "from", w is "to"
                    for (v = 0; v < workingSCC.count; v++)
                    {
                        for (w = 0; w < workingSCC.count; w++)
                        {
                            secondaryEdgeBits.ChangeEdgeBit(workingSCC.count, v, w,
                                                            edgeBits.GetEdgeBit(vertexCount, primaryVertices[workingSCC.index + v], primaryVertices[workingSCC.index + w]));
                        }
                    }


                    int bestSCCCount          = 0;
                    int bestSCCMaximumSize    = workingSCC.count;   // The size of the largest SCCs     <- used to select "best"  (reduces size of O(N^2) problem -- unverified -AR)
                    int bestSCCCumulativeSize = workingSCC.count;   // Number of vertices in all SCCs   <- used as a tie-break    (reduces size of O(N) problem   -- unverified -AR)

                    // These are for logging
                    int bestRemovedV = 0;
                    int bestRemovedW = 0;

                    // Try to find the best constraint (ie: edge) to remove:
                    for (v = 0; v < workingSCC.count; v++)
                    {
                        for (w = 0; w < workingSCC.count; w++)
                        {
                            if (secondaryEdgeBits.IsEdge(workingSCC.count, v, w)) // PERF: Consider only looking at "backwards" edges (requires primary input is spatially sorted, then compare primary indices)
                            {
                                TarjanOutput secondaryOutput = new TarjanOutput();
                                secondaryOutput.sccList    = secondarySCCs;
                                secondaryOutput.vertexList = secondaryVertices;

                                secondaryEdgeBits.ClearEdge(workingSCC.count, v, w); // Temporarly remove an edge (see if it improves the result)

                                ta.FindStronglyConnectedComponents(workingSCC.count, secondaryEdgeBits, ref secondaryOutput);

                                int secondarySCCMaximumSize    = 0;
                                int secondarySCCCumulativeSize = 0;
                                for (int i = 0; i < secondaryOutput.sccCount; i++)
                                {
                                    int count = secondaryOutput.sccList[i].count;
                                    if (count > secondarySCCMaximumSize)
                                    {
                                        secondarySCCMaximumSize = count;
                                    }
                                    secondarySCCCumulativeSize += count;
                                }

                                // NOTE: If we change to only looking at "backwards" edges, should probably add tie-break to select rearmost edge to remove
                                //       (May require pre-sorting the SCC, to handle the early-out case properly)
                                if (secondaryOutput.sccCount == 0 ||
                                    secondarySCCMaximumSize < bestSCCMaximumSize ||
                                    (secondarySCCMaximumSize == bestSCCMaximumSize && secondarySCCCumulativeSize < bestSCCCumulativeSize))
                                {
                                    bestRemovedV = v;
                                    bestRemovedW = w;

                                    bestSCCCount          = secondaryOutput.sccCount;
                                    bestSCCMaximumSize    = secondarySCCMaximumSize;
                                    bestSCCCumulativeSize = secondarySCCCumulativeSize;
                                    Swap(ref bestSCCs, ref secondarySCCs);
                                    Swap(ref bestVertices, ref secondaryVertices);
                                }

                                if (secondaryOutput.sccCount == 0)
                                {
                                    goto earlyOut;                                 // Found optimal solution
                                }
                                secondaryEdgeBits.SetEdge(workingSCC.count, v, w); // Restore Edge
                            }
                        }
                    }

                    if (bestSCCMaximumSize == workingSCC.count) // Failed to find any improvement for this SCC - give up!
                    {
                        // Just leave the primary output in its original order
                        // NOTE: If we change to sorted input (for "backwards" edges), consider re-sorting this SCC in output
                        continue;
                    }
earlyOut:           // skips the above

                    // Notify any debug output that an edge was removed. NOTE: Before the SCC in primary gets re-ordered!
                    if (removedEdge != null)
                    {
                        removedEdge(primaryVertices[workingSCC.index + bestRemovedV], primaryVertices[workingSCC.index + bestRemovedW]);
                    }

                    // Apply the new ordering
                    for (int i = 0; i < workingSCC.count; i++)
                    {
                        tempOutput[i] = primaryVertices[workingSCC.index + bestVertices[i]];
                    }
                    Array.Copy(tempOutput, 0, primaryVertices, workingSCC.index, workingSCC.count);

                    // Transfer new SCCs to primary:
                    for (int i = 0; i < bestSCCCount; i++)
                    {
                        primaryOutput.sccList[primaryOutput.sccCount++] = new SCC(workingSCC.index + bestSCCs[i].index, bestSCCs[i].count);
                    }
                }


                // At this point, primarySCC stack is empty -- we're done
                return(primaryVertices);
            }
        }