• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2018 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "include/core/SkRefCnt.h"
9 #include "include/core/SkString.h"
10 #include "include/core/SkTypes.h"
11 #include "include/private/base/SkDebug.h"
12 #include "include/private/base/SkTArray.h"
13 #include "include/private/base/SkTDArray.h"
14 #include "src/base/SkTSort.h"
15 #include "tests/Test.h"
16 
17 #include <algorithm>
18 #include <cstdint>
19 
20 // A node in the graph. This corresponds to an opsTask in the MDB world.
21 class Node : public SkRefCnt {
22 public:
id() const23     char id() const { return fID; }
indexInSort() const24     int indexInSort() const { SkASSERT(fIndexInSort >= 0); return fIndexInSort; }
visited() const25     bool visited() const { return fVisited; }
26 
27 #ifdef SK_DEBUG
print() const28     void print() const {
29         SkDebugf("%d: id %c", fIndexInSort, fID);
30         if (fNodesIDependOn.size()) {
31             SkDebugf(" I depend on (%d): ", fNodesIDependOn.size());
32             for (Node* tmp : fNodesIDependOn) {
33                 SkDebugf("%c, ", tmp->id());
34             }
35         }
36         if (fNodesThatDependOnMe.size()) {
37             SkDebugf(" (%d) depend on me: ", fNodesThatDependOnMe.size());
38             for (Node* tmp : fNodesThatDependOnMe) {
39                 SkDebugf("%c, ", tmp->id());
40             }
41         }
42         SkDebugf("\n");
43     }
44 #endif
45 
validate(skiatest::Reporter * reporter) const46     void validate(skiatest::Reporter* reporter) const {
47         for (Node* dependedOn : fNodesIDependOn) {
48             REPORTER_ASSERT(reporter, dependedOn->indexInSort() < this->indexInSort());
49         }
50         for (Node* dependent : fNodesThatDependOnMe) {
51             REPORTER_ASSERT(reporter, this->indexInSort() < dependent->indexInSort());
52         }
53         REPORTER_ASSERT(reporter, !fVisited); // this flag should only be true w/in depEdges()
54     }
55 
CompareIndicesGT(Node * const & a,Node * const & b)56     static bool CompareIndicesGT(Node* const& a, Node* const& b) {
57         return a->indexInSort() > b->indexInSort();
58     }
59 
numDependents() const60     int numDependents() const { return fNodesThatDependOnMe.size(); }
dependent(int index) const61     Node* dependent(int index) const {
62         SkASSERT(0 <= index && index < fNodesThatDependOnMe.size());
63         return fNodesThatDependOnMe[index];
64     }
65 
66 private:
67     friend class Graph;
68 
Node(char id)69     explicit Node(char id) : fID(id), fIndexInSort(-1), fVisited(false) {}
70 
setIndexInSort(int indexInSort)71     void setIndexInSort(int indexInSort) { fIndexInSort = indexInSort; }
setVisited(bool visited)72     void setVisited(bool visited) { fVisited = visited; }
addDependency(Node * dependedOn)73     void addDependency(Node* dependedOn) {
74         fNodesIDependOn.push_back(dependedOn);
75 
76         dependedOn->addDependent(this);
77     }
addDependent(Node * dependent)78     void addDependent(Node* dependent) {
79         fNodesThatDependOnMe.push_back(dependent);
80     }
81 
82     char             fID;
83     SkTDArray<Node*> fNodesIDependOn;      // These nodes must appear before this one in the sort
84     SkTDArray<Node*> fNodesThatDependOnMe; // These ones must appear after this one
85     int              fIndexInSort;
86     bool             fVisited;             // only used in addEdges()
87 };
88 
89 // The DAG driving the incremental topological sort. This corresponds to the opsTask DAG in
90 // the MDB world.
91 class Graph {
92 public:
Graph(int numNodesToReserve,skiatest::Reporter * reporter)93     Graph(int numNodesToReserve, skiatest::Reporter* reporter)
94             : fNodes(numNodesToReserve)
95             , fReporter(reporter) {
96     }
97 
addNode(uint32_t id)98     Node* addNode(uint32_t id) {
99         this->validate();
100         sk_sp<Node> tmp(new Node(id));
101 
102         fNodes.push_back(tmp);       // The graph gets the creation ref
103         tmp->setIndexInSort(fNodes.size()-1);
104         this->validate();
105         return tmp.get();
106     }
107 
108     // 'dependedOn' must appear before 'dependent' in the sort
addEdge(Node * dependedOn,Node * dependent)109     void addEdge(Node* dependedOn, Node* dependent) {
110         // TODO: this would be faster if all the SkTDArray code was stripped out of
111         // addEdges but, when used in MDB sorting, this entry point will never be used.
112         SkTDArray<Node*> tmp(&dependedOn, 1);
113         this->addEdges(&tmp, dependent);
114     }
115 
116     // All the nodes in 'dependedOn' must appear before 'dependent' in the sort.
117     // This is O(v + e + cost_of_sorting(b)) where:
118     //    v: number of nodes
119     //    e: number of edges
120     //    b: number of new edges in 'dependedOn'
121     //
122     // The algorithm works by first finding the "affected region" that contains all the
123     // nodes whose position in the topological sort is invalidated by the addition of the new
124     // edges. It then traverses the affected region from left to right, temporarily removing
125     // invalid nodes from 'fNodes' and shifting valid nodes left to fill in the gaps. In this
126     // left to right traversal, when a node is shifted to the left the current set of invalid
127     // nodes is examined to see if any needed to be moved to the right of that node. If so,
128     // they are reintroduced to the 'fNodes' array but now in the appropriate position. The
129     // separation of the algorithm into search (the dfs method) and readjustment (the shift
130     // method) means that each node affected by the new edges is only ever moved once.
addEdges(SkTDArray<Node * > * dependedOn,Node * dependent)131     void addEdges(SkTDArray<Node*>* dependedOn, Node* dependent) {
132         this->validate();
133 
134         // remove any of the new dependencies that are already satisfied
135         for (int i = 0; i < dependedOn->size(); ++i) {
136             if ((*dependedOn)[i]->indexInSort() < dependent->indexInSort()) {
137                 dependent->addDependency((*dependedOn)[i]);
138                 dependedOn->removeShuffle(i);
139                 i--;
140             } else {
141                 dependent->addDependency((*dependedOn)[i]);
142             }
143         }
144 
145         if (dependedOn->empty()) {
146             return;
147         }
148 
149         // Sort the remaining dependencies into descending order based on their indices in the
150         // sort. This means that we will be proceeding from right to left in the sort when
151         // correcting the order.
152         // TODO: QSort is waaay overkill here!
153         SkTQSort<Node*>(dependedOn->begin(), dependedOn->end(), Node::CompareIndicesGT);
154 
155         // TODO: although this is the general algorithm, I think this can be simplified for our
156         // use case (i.e., the same dependent for all the new edges).
157 
158         int lowerBound = fNodes.size();  // 'lowerBound' tracks the left of the affected region
159         for (int i = 0; i < dependedOn->size(); ++i) {
160             if ((*dependedOn)[i]->indexInSort() < lowerBound) {
161                 this->shift(lowerBound);
162             }
163 
164             if (!dependent->visited()) {
165                 this->dfs(dependent, (*dependedOn)[i]->indexInSort());
166             }
167 
168             lowerBound = std::min(dependent->indexInSort(), lowerBound);
169         }
170 
171         this->shift(lowerBound);
172 
173         this->validate();
174     }
175 
176     // Get the list of node ids in the current sorted order
getActual(SkString * actual) const177     void getActual(SkString* actual) const {
178         this->validate();
179 
180         for (int i = 0; i < fNodes.size(); ++i) {
181             (*actual) += fNodes[i]->id();
182             if (i < fNodes.size()-1) {
183                 (*actual) += ',';
184             }
185         }
186     }
187 
188 #ifdef SK_DEBUG
print() const189     void print() const {
190         SkDebugf("-------------------\n");
191         for (int i = 0; i < fNodes.size(); ++i) {
192             if (fNodes[i]) {
193                 SkDebugf("%c ", fNodes[i]->id());
194             } else {
195                 SkDebugf("0 ");
196             }
197         }
198         SkDebugf("\n");
199 
200         for (int i = 0; i < fNodes.size(); ++i) {
201             if (fNodes[i]) {
202                 fNodes[i]->print();
203             }
204         }
205 
206         SkDebugf("Stack: ");
207         for (int i = 0; i < fStack.size(); ++i) {
208            SkDebugf("%c/%c ", fStack[i].fNode->id(), fStack[i].fDest->id());
209         }
210         SkDebugf("\n");
211     }
212 #endif
213 
214 private:
validate() const215     void validate() const {
216         REPORTER_ASSERT(fReporter, fStack.empty());
217 
218         for (int i = 0; i < fNodes.size(); ++i) {
219             REPORTER_ASSERT(fReporter, fNodes[i]->indexInSort() == i);
220 
221             fNodes[i]->validate(fReporter);
222         }
223 
224         // All the nodes in the Queue had better have been marked as visited
225         for (int i = 0; i < fStack.size(); ++i) {
226             SkASSERT(fStack[i].fNode->visited());
227         }
228     }
229 
230     // Collect the nodes that need to be moved within the affected region. All the nodes found
231     // to be in violation of the topological constraints are placed in 'fStack'.
dfs(Node * node,int upperBound)232     void dfs(Node* node, int upperBound) {
233         node->setVisited(true);
234 
235         for (int i = 0; i < node->numDependents(); ++i) {
236             Node* dependent = node->dependent(i);
237 
238             SkASSERT(dependent->indexInSort() != upperBound); // this would be a cycle
239 
240             if (!dependent->visited() && dependent->indexInSort() < upperBound) {
241                 this->dfs(dependent, upperBound);
242             }
243         }
244 
245         fStack.push_back({ sk_ref_sp(node), fNodes[upperBound].get() });
246     }
247 
248     // Move 'node' to the index-th slot of the sort. The index-th slot should not have a current
249     // occupant.
moveNodeInSort(sk_sp<Node> node,int index)250     void moveNodeInSort(sk_sp<Node> node, int index) {
251         SkASSERT(!fNodes[index]);
252         fNodes[index] = node;
253         node->setIndexInSort(index);
254     }
255 
256 #ifdef SK_DEBUG
257     // Does 'fStack' have 'node'? That is, was 'node' discovered to be in violation of the
258     // topological constraints?
stackContains(Node * node)259     bool stackContains(Node* node) {
260         for (int i = 0; i < fStack.size(); ++i) {
261             if (node == fStack[i].fNode.get()) {
262                 return true;
263             }
264         }
265         return false;
266     }
267 #endif
268 
269     // The 'shift' method iterates through the affected area from left to right moving Nodes that
270     // were found to be in violation of the topological order (i.e., in 'fStack') to their correct
271     // locations and shifting the non-violating nodes left, into the holes the violating nodes left
272     // behind.
shift(int index)273     void shift(int index) {
274         int numRemoved = 0;
275         while (!fStack.empty()) {
276             sk_sp<Node> node = fNodes[index];
277 
278             if (node->visited()) {
279                 // This node is in 'fStack' and was found to be in violation of the topological
280                 // constraints. Remove it from 'fNodes' so non-violating nodes can be shifted
281                 // left.
282                 SkASSERT(this->stackContains(node.get()));
283                 node->setVisited(false);  // reset for future use
284                 fNodes[index] = nullptr;
285                 numRemoved++;
286             } else {
287                 // This node was found to not be in violation of any topological constraints but
288                 // must be moved left to fill in for those that were.
289                 SkASSERT(!this->stackContains(node.get()));
290                 SkASSERT(numRemoved); // should be moving left
291 
292                 this->moveNodeInSort(node, index - numRemoved);
293                 fNodes[index] = nullptr;
294             }
295 
296             while (!fStack.empty() && node.get() == fStack.back().fDest) {
297                 // The left to right loop has finally encountered the destination for one or more
298                 // of the nodes in 'fStack'. Place them to the right of 'node' in the sort. Note
299                 // that because the violating nodes were already removed from 'fNodes' there
300                 // should be enough empty space for them to be placed now.
301                 numRemoved--;
302 
303                 this->moveNodeInSort(fStack.back().fNode, index - numRemoved);
304 
305                 fStack.pop_back();
306             }
307 
308             index++;
309         }
310     }
311 
312     SkTArray<sk_sp<Node>> fNodes;
313 
314     struct StackInfo {
315         sk_sp<Node> fNode;  // This gets a ref bc, in 'shift' it will be pulled out of 'fNodes'
316         Node*       fDest;
317     };
318 
319     SkTArray<StackInfo>   fStack;     // only used in addEdges()
320 
321     skiatest::Reporter*   fReporter;
322 };
323 
324 // This test adds a single invalidating edge.
test_1(skiatest::Reporter * reporter)325 static void test_1(skiatest::Reporter* reporter) {
326     Graph g(10, reporter);
327 
328     Node* nodeQ = g.addNode('q');
329     Node* nodeY = g.addNode('y');
330     Node* nodeA = g.addNode('a');
331     Node* nodeZ = g.addNode('z');
332     Node* nodeB = g.addNode('b');
333     /*Node* nodeC =*/ g.addNode('c');
334     Node* nodeW = g.addNode('w');
335     Node* nodeD = g.addNode('d');
336     Node* nodeX = g.addNode('x');
337     Node* nodeR = g.addNode('r');
338 
339     // All these edge are non-invalidating
340     g.addEdge(nodeD, nodeR);
341     g.addEdge(nodeZ, nodeW);
342     g.addEdge(nodeA, nodeB);
343     g.addEdge(nodeY, nodeZ);
344     g.addEdge(nodeQ, nodeA);
345 
346     {
347         const SkString kExpectedInitialState("q,y,a,z,b,c,w,d,x,r");
348         SkString actualInitialState;
349         g.getActual(&actualInitialState);
350         REPORTER_ASSERT(reporter, kExpectedInitialState == actualInitialState);
351     }
352 
353     // Add the invalidating edge
354     g.addEdge(nodeX, nodeY);
355 
356     {
357         const SkString kExpectedFinalState("q,a,b,c,d,x,y,z,w,r");
358         SkString actualFinalState;
359         g.getActual(&actualFinalState);
360         REPORTER_ASSERT(reporter, kExpectedFinalState == actualFinalState);
361     }
362 }
363 
364 // This test adds two invalidating edge sequentially
test_2(skiatest::Reporter * reporter)365 static void test_2(skiatest::Reporter* reporter) {
366     Graph g(10, reporter);
367 
368     Node* nodeY = g.addNode('y');
369     /*Node* nodeA =*/ g.addNode('a');
370     Node* nodeW = g.addNode('w');
371     /*Node* nodeB =*/ g.addNode('b');
372     Node* nodeZ = g.addNode('z');
373     Node* nodeU = g.addNode('u');
374     /*Node* nodeC =*/ g.addNode('c');
375     Node* nodeX = g.addNode('x');
376     /*Node* nodeD =*/ g.addNode('d');
377     Node* nodeV = g.addNode('v');
378 
379     // All these edge are non-invalidating
380     g.addEdge(nodeU, nodeX);
381     g.addEdge(nodeW, nodeU);
382     g.addEdge(nodeW, nodeZ);
383     g.addEdge(nodeY, nodeZ);
384 
385     {
386         const SkString kExpectedInitialState("y,a,w,b,z,u,c,x,d,v");
387         SkString actualInitialState;
388         g.getActual(&actualInitialState);
389         REPORTER_ASSERT(reporter, kExpectedInitialState == actualInitialState);
390     }
391 
392     // Add the first invalidating edge
393     g.addEdge(nodeX, nodeY);
394 
395     {
396         const SkString kExpectedFirstState("a,w,b,u,c,x,y,z,d,v");
397         SkString actualFirstState;
398         g.getActual(&actualFirstState);
399         REPORTER_ASSERT(reporter, kExpectedFirstState == actualFirstState);
400     }
401 
402     // Add the second invalidating edge
403     g.addEdge(nodeV, nodeW);
404 
405     {
406         const SkString kExpectedSecondState("a,b,c,d,v,w,u,x,y,z");
407         SkString actualSecondState;
408         g.getActual(&actualSecondState);
409         REPORTER_ASSERT(reporter, kExpectedSecondState == actualSecondState);
410     }
411 }
412 
test_diamond(skiatest::Reporter * reporter)413 static void test_diamond(skiatest::Reporter* reporter) {
414     Graph g(4, reporter);
415 
416     /* Create the graph (the '.' is the pointy side of the arrow):
417      *        b
418      *      .    \
419      *     /      .
420      *   a          d
421      *     \      .
422      *       .   /
423      *         c
424      * Possible topological orders are [a,c,b,d] and [a,b,c,d].
425      */
426 
427     Node* nodeD = g.addNode('d');
428     Node* nodeC = g.addNode('c');
429     Node* nodeB = g.addNode('b');
430 
431     {
432         SkTDArray<Node*> dependedOn;
433         dependedOn.push_back(nodeB);
434         dependedOn.push_back(nodeC);
435 
436         g.addEdges(&dependedOn, nodeD); // nodes B and C must come before node D
437     }
438 
439     Node* nodeA = g.addNode('a');
440     g.addEdge(nodeA, nodeB);            // node A must come before node B
441     g.addEdge(nodeA, nodeC);            // node A must come before node C
442 
443     const SkString kExpected0("a,c,b,d");
444     const SkString kExpected1("a,b,c,d");
445     SkString actual;
446     g.getActual(&actual);
447 
448     REPORTER_ASSERT(reporter, kExpected0 == actual || kExpected1 == actual);
449 }
450 
test_lopsided_binary_tree(skiatest::Reporter * reporter)451 static void test_lopsided_binary_tree(skiatest::Reporter* reporter) {
452     Graph g(7, reporter);
453 
454     /* Create the graph (the '.' is the pointy side of the arrow):
455      *        a
456      *       /  \
457      *      .    .
458      *     b      c
459      *           /  \
460      *          .    .
461      *         d      e
462      *               /  \
463      *              .    .
464      *             f      g
465      *
466      * Possible topological order is: [a,b,c,d,e,f,g].
467      */
468 
469     Node* nodeG = g.addNode('g');
470     Node* nodeF = g.addNode('f');
471     Node* nodeE = g.addNode('e');
472     Node* nodeD = g.addNode('d');
473     Node* nodeC = g.addNode('c');
474     Node* nodeB = g.addNode('b');
475     Node* nodeA = g.addNode('a');
476 
477     g.addEdge(nodeE, nodeG);
478     g.addEdge(nodeE, nodeF);
479     g.addEdge(nodeC, nodeE);
480     g.addEdge(nodeC, nodeD);
481     g.addEdge(nodeA, nodeC);
482     g.addEdge(nodeA, nodeB);
483 
484     const SkString kExpected("a,b,c,d,e,f,g");
485     SkString actual;
486     g.getActual(&actual);
487 
488     REPORTER_ASSERT(reporter, kExpected == actual);
489 }
490 
DEF_TEST(IncrTopoSort,reporter)491 DEF_TEST(IncrTopoSort, reporter) {
492     test_1(reporter);
493     test_2(reporter);
494     test_diamond(reporter);
495     test_lopsided_binary_tree(reporter);
496 }
497