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