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