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); } }