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