1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package androidx.constraintlayout.core;
17 
18 import java.util.Arrays;
19 
20 /**
21  * Store a set of variables and their values in an array-based linked list.
22  *
23  * The general idea is that we want to store a list of variables that need to be ordered,
24  * space efficient, and relatively fast to maintain (add/remove).
25  *
26  * ArrayBackedVariables implements a sparse array, so is rather space efficient, but maintaining
27  * the array sorted is costly,
28  * as we spend quite a bit of time recopying parts of the array on element deletion.
29  *
30  * LinkedVariables implements a standard linked list structure,
31  * and is able to be faster than ArrayBackedVariables
32  * even though it's more costly to set up (pool of objects...),
33  * as the elements removal and maintenance of the structure is a lot more efficient.
34  *
35  * This ArrayLinkedVariables class takes inspiration from both of the above,
36  * and implement a linked list stored in several arrays.
37  * This allows us to be a lot more efficient in terms of setup (no need to deal with pool
38  * of objects...), resetting the structure, and insertion/deletion of elements.
39  */
40 public class ArrayLinkedVariables implements ArrayRow.ArrayRowVariables {
41     private static final boolean DEBUG = false;
42 
43     static final int NONE = -1;
44     // private static final boolean FULL_NEW_CHECK = false   full validation (debug purposes)
45 
46     int mCurrentSize = 0; // current size, accessed by ArrayRow and LinearSystem
47 
48     private final ArrayRow mRow; // our owner
49 
50     // pointer to the system-wide cache, allowing access to SolverVariables
51     protected final Cache mCache;
52 
53     private int mRowSize = 8; // default array size
54 
55     private SolverVariable mCandidate = null;
56 
57     // mArrayIndices point to indexes in mCache.mIndexedVariables (i.e., the SolverVariables)
58     private int[] mArrayIndices = new int[mRowSize];
59 
60     // mArrayNextIndices point to indexes in mArrayIndices
61     private int[] mArrayNextIndices = new int[mRowSize];
62 
63     // mArrayValues contain the associated value from mArrayIndices
64     private float[] mArrayValues = new float[mRowSize];
65 
66     // mHead point to indexes in mArrayIndices
67     private int mHead = NONE;
68 
69     // mLast point to indexes in mArrayIndices
70     //
71     // While mDidFillOnce is not set, mLast is simply incremented
72     // monotonically in order to be sure to traverse the entire array; the idea here is that
73     // when we clear a linked list, we only set the counters to zero without traversing the array
74     // to fill it with NONE values, which would be costly.
75     // But if we do not fill the array with NONE values, we cannot safely simply check if an entry
76     // is set to NONE to know if we can use it or not, as it might contains a previous value...
77     // So, when adding elements, we first ensure with this mechanism of mLast/mDidFillOnce
78     // that we do traverse the array linearly,
79     // avoiding for that first pass the need to check for the value of the item in mArrayIndices.
80     // This does mean that removed elements will leave empty spaces,
81     // but we /then/ set the removed element to NONE,
82     // so that once we did that first traversal filling the array,
83     // we can safely revert to linear traversal
84     // finding an empty spot by checking the values of mArrayIndices
85     // (i.e. finding an item containing NONE).
86     private int mLast = NONE;
87 
88     // flag to keep trace if we did a full pass of the array or not, see above description
89     private boolean mDidFillOnce = false;
90     private static float sEpsilon = 0.001f;
91 
92     // Example of a basic loop
93     // current or previous point to mArrayIndices
94     //
95     // int current = mHead;
96     // int counter = 0;
97     // while (current != NONE && counter < currentSize) {
98     //  SolverVariable currentVariable = mCache.mIndexedVariables[mArrayIndices[current]];
99     //  float currentValue = mArrayValues[current];
100     //  ...
101     //  current = mArrayNextIndices[current]; counter++;
102     // }
103 
104     /**
105      * Constructor
106      *
107      * @param arrayRow the row owning us
108      * @param cache    instances cache
109      */
ArrayLinkedVariables(ArrayRow arrayRow, Cache cache)110     ArrayLinkedVariables(ArrayRow arrayRow, Cache cache) {
111         mRow = arrayRow;
112         mCache = cache;
113         if (DEBUG) {
114             for (int i = 0; i < mArrayIndices.length; i++) {
115                 mArrayIndices[i] = NONE;
116             }
117         }
118     }
119 
120     /**
121      * Insert a variable with a given value in the linked list
122      *
123      * @param variable the variable to add in the list
124      * @param value    the value of the variable
125      */
126     @Override
put(SolverVariable variable, float value)127     public final void put(SolverVariable variable, float value) {
128         if (value == 0) {
129             remove(variable, true);
130             return;
131         }
132         // Special casing empty list...
133         if (mHead == NONE) {
134             mHead = 0;
135             mArrayValues[mHead] = value;
136             mArrayIndices[mHead] = variable.id;
137             mArrayNextIndices[mHead] = NONE;
138             variable.usageInRowCount++;
139             variable.addToRow(mRow);
140             mCurrentSize++;
141             if (!mDidFillOnce) {
142                 // only increment mLast if we haven't done the first filling pass
143                 mLast++;
144                 if (mLast >= mArrayIndices.length) {
145                     mDidFillOnce = true;
146                     mLast = mArrayIndices.length - 1;
147                 }
148             }
149             return;
150         }
151         int current = mHead;
152         int previous = NONE;
153         int counter = 0;
154         while (current != NONE && counter < mCurrentSize) {
155             if (mArrayIndices[current] == variable.id) {
156                 mArrayValues[current] = value;
157                 return;
158             }
159             if (mArrayIndices[current] < variable.id) {
160                 previous = current;
161             }
162             current = mArrayNextIndices[current];
163             counter++;
164         }
165 
166         // Not found, we need to insert
167 
168         // First, let's find an available spot
169         int availableIndice = mLast + 1; // start from the previous spot
170         if (mDidFillOnce) {
171             // ... but if we traversed the array once, check the last index, which might have been
172             // set by an element removed
173             if (mArrayIndices[mLast] == NONE) {
174                 availableIndice = mLast;
175             } else {
176                 availableIndice = mArrayIndices.length;
177             }
178         }
179         if (availableIndice >= mArrayIndices.length) {
180             if (mCurrentSize < mArrayIndices.length) {
181                 // find an available spot
182                 for (int i = 0; i < mArrayIndices.length; i++) {
183                     if (mArrayIndices[i] == NONE) {
184                         availableIndice = i;
185                         break;
186                     }
187                 }
188             }
189         }
190         // ... make sure to grow the array as needed
191         if (availableIndice >= mArrayIndices.length) {
192             availableIndice = mArrayIndices.length;
193             mRowSize *= 2;
194             mDidFillOnce = false;
195             mLast = availableIndice - 1;
196             mArrayValues = Arrays.copyOf(mArrayValues, mRowSize);
197             mArrayIndices = Arrays.copyOf(mArrayIndices, mRowSize);
198             mArrayNextIndices = Arrays.copyOf(mArrayNextIndices, mRowSize);
199         }
200 
201         // Finally, let's insert the element
202         mArrayIndices[availableIndice] = variable.id;
203         mArrayValues[availableIndice] = value;
204         if (previous != NONE) {
205             mArrayNextIndices[availableIndice] = mArrayNextIndices[previous];
206             mArrayNextIndices[previous] = availableIndice;
207         } else {
208             mArrayNextIndices[availableIndice] = mHead;
209             mHead = availableIndice;
210         }
211         variable.usageInRowCount++;
212         variable.addToRow(mRow);
213         mCurrentSize++;
214         if (!mDidFillOnce) {
215             // only increment mLast if we haven't done the first filling pass
216             mLast++;
217         }
218         if (mCurrentSize >= mArrayIndices.length) {
219             mDidFillOnce = true;
220         }
221         if (mLast >= mArrayIndices.length) {
222             mDidFillOnce = true;
223             mLast = mArrayIndices.length - 1;
224         }
225     }
226 
227     /**
228      * Add value to an existing variable
229      *
230      * The code is broadly identical to the put() method, only differing
231      * in in-line deletion, and of course doing an add rather than a put
232      *
233      * @param variable             the variable we want to add
234      * @param value                its value
235      * @param removeFromDefinition if a variable's value becomes zero, it is removed. This parameter
236      *                             is whether the removal is deep now or left shallow until cleanup
237      */
238     @Override
add(SolverVariable variable, float value, boolean removeFromDefinition)239     public void add(SolverVariable variable, float value, boolean removeFromDefinition) {
240         if (value > -sEpsilon && value < sEpsilon) {
241             return;
242         }
243         // Special casing empty list...
244         if (mHead == NONE) {
245             mHead = 0;
246             mArrayValues[mHead] = value;
247             mArrayIndices[mHead] = variable.id;
248             mArrayNextIndices[mHead] = NONE;
249             variable.usageInRowCount++;
250             variable.addToRow(mRow);
251             mCurrentSize++;
252             if (!mDidFillOnce) {
253                 // only increment mLast if we haven't done the first filling pass
254                 mLast++;
255                 if (mLast >= mArrayIndices.length) {
256                     mDidFillOnce = true;
257                     mLast = mArrayIndices.length - 1;
258                 }
259             }
260             return;
261         }
262         int current = mHead;
263         int previous = NONE;
264         int counter = 0;
265         while (current != NONE && counter < mCurrentSize) {
266             int idx = mArrayIndices[current];
267             if (idx == variable.id) {
268                 float v = mArrayValues[current] + value;
269                 if (v > -sEpsilon && v < sEpsilon) {
270                     v = 0;
271                 }
272                 mArrayValues[current] = v;
273                 // Possibly delete immediately
274                 if (v == 0) {
275                     if (current == mHead) {
276                         mHead = mArrayNextIndices[current];
277                     } else {
278                         mArrayNextIndices[previous] = mArrayNextIndices[current];
279                     }
280                     if (removeFromDefinition) {
281                         variable.removeFromRow(mRow);
282                     }
283                     if (mDidFillOnce) {
284                         // If we did a full pass already, remember that spot
285                         mLast = current;
286                     }
287                     variable.usageInRowCount--;
288                     mCurrentSize--;
289                 }
290                 return;
291             }
292             if (mArrayIndices[current] < variable.id) {
293                 previous = current;
294             }
295             current = mArrayNextIndices[current];
296             counter++;
297         }
298 
299         // Not found, we need to insert
300 
301         // First, let's find an available spot
302         int availableIndice = mLast + 1; // start from the previous spot
303         if (mDidFillOnce) {
304             // ... but if we traversed the array once, check the last index, which might have been
305             // set by an element removed
306             if (mArrayIndices[mLast] == NONE) {
307                 availableIndice = mLast;
308             } else {
309                 availableIndice = mArrayIndices.length;
310             }
311         }
312         if (availableIndice >= mArrayIndices.length) {
313             if (mCurrentSize < mArrayIndices.length) {
314                 // find an available spot
315                 for (int i = 0; i < mArrayIndices.length; i++) {
316                     if (mArrayIndices[i] == NONE) {
317                         availableIndice = i;
318                         break;
319                     }
320                 }
321             }
322         }
323         // ... make sure to grow the array as needed
324         if (availableIndice >= mArrayIndices.length) {
325             availableIndice = mArrayIndices.length;
326             mRowSize *= 2;
327             mDidFillOnce = false;
328             mLast = availableIndice - 1;
329             mArrayValues = Arrays.copyOf(mArrayValues, mRowSize);
330             mArrayIndices = Arrays.copyOf(mArrayIndices, mRowSize);
331             mArrayNextIndices = Arrays.copyOf(mArrayNextIndices, mRowSize);
332         }
333 
334         // Finally, let's insert the element
335         mArrayIndices[availableIndice] = variable.id;
336         mArrayValues[availableIndice] = value;
337         if (previous != NONE) {
338             mArrayNextIndices[availableIndice] = mArrayNextIndices[previous];
339             mArrayNextIndices[previous] = availableIndice;
340         } else {
341             mArrayNextIndices[availableIndice] = mHead;
342             mHead = availableIndice;
343         }
344         variable.usageInRowCount++;
345         variable.addToRow(mRow);
346         mCurrentSize++;
347         if (!mDidFillOnce) {
348             // only increment mLast if we haven't done the first filling pass
349             mLast++;
350         }
351         if (mLast >= mArrayIndices.length) {
352             mDidFillOnce = true;
353             mLast = mArrayIndices.length - 1;
354         }
355     }
356 
357     /**
358      * Update the current list with a new definition
359      *
360      * @param definition           the row containing the definition
361      * @param removeFromDefinition if a variable's value becomes zero, it is removed. This parameter
362      *                             is whether the removal is deep now or left shallow until cleanup
363      */
364     @Override
use(ArrayRow definition, boolean removeFromDefinition)365     public float use(ArrayRow definition, boolean removeFromDefinition) {
366         float value = get(definition.mVariable);
367         remove(definition.mVariable, removeFromDefinition);
368         ArrayRow.ArrayRowVariables definitionVariables = definition.variables;
369         int definitionSize = definitionVariables.getCurrentSize();
370         for (int i = 0; i < definitionSize; i++) {
371             SolverVariable definitionVariable = definitionVariables.getVariable(i);
372             float definitionValue = definitionVariables.get(definitionVariable);
373             this.add(definitionVariable, definitionValue * value, removeFromDefinition);
374         }
375         return value;
376     }
377 
378     /**
379      * Remove a variable from the list
380      *
381      * @param variable             the variable we want to remove
382      * @param removeFromDefinition if a variable's value becomes zero, it is removed. This parameter
383      *                             is whether the removal is deep now or left shallow until cleanup
384      * @return the value of the removed variable
385      */
386     @Override
remove(SolverVariable variable, boolean removeFromDefinition)387     public final float remove(SolverVariable variable, boolean removeFromDefinition) {
388         if (mCandidate == variable) {
389             mCandidate = null;
390         }
391         if (mHead == NONE) {
392             return 0;
393         }
394         int current = mHead;
395         int previous = NONE;
396         int counter = 0;
397         while (current != NONE && counter < mCurrentSize) {
398             int idx = mArrayIndices[current];
399             if (idx == variable.id) {
400                 if (current == mHead) {
401                     mHead = mArrayNextIndices[current];
402                 } else {
403                     mArrayNextIndices[previous] = mArrayNextIndices[current];
404                 }
405 
406                 if (removeFromDefinition) {
407                     variable.removeFromRow(mRow);
408                 }
409                 variable.usageInRowCount--;
410                 mCurrentSize--;
411                 mArrayIndices[current] = NONE;
412                 if (mDidFillOnce) {
413                     // If we did a full pass already, remember that spot
414                     mLast = current;
415                 }
416                 return mArrayValues[current];
417             }
418             previous = current;
419             current = mArrayNextIndices[current];
420             counter++;
421         }
422         return 0;
423     }
424 
425     /**
426      * Clear the list of variables
427      */
428     @Override
clear()429     public final void clear() {
430         int current = mHead;
431         int counter = 0;
432         while (current != NONE && counter < mCurrentSize) {
433             SolverVariable variable = mCache.mIndexedVariables[mArrayIndices[current]];
434             if (variable != null) {
435                 variable.removeFromRow(mRow);
436             }
437             current = mArrayNextIndices[current];
438             counter++;
439         }
440 
441         mHead = NONE;
442         mLast = NONE;
443         mDidFillOnce = false;
444         mCurrentSize = 0;
445     }
446 
447     /**
448      * Returns true if the variable is contained in the list
449      *
450      * @param variable the variable we are looking for
451      * @return return true if we found the variable
452      */
453     @Override
contains(SolverVariable variable)454     public boolean contains(SolverVariable variable) {
455         if (mHead == NONE) {
456             return false;
457         }
458         int current = mHead;
459         int counter = 0;
460         while (current != NONE && counter < mCurrentSize) {
461             if (mArrayIndices[current] == variable.id) {
462                 return true;
463             }
464             current = mArrayNextIndices[current];
465             counter++;
466         }
467         return false;
468     }
469 
470     @Override
indexOf(SolverVariable variable)471     public int indexOf(SolverVariable variable) {
472         if (mHead == NONE) {
473             return -1;
474         }
475         int current = mHead;
476         int counter = 0;
477         while (current != NONE && counter < mCurrentSize) {
478             if (mArrayIndices[current] == variable.id) {
479                 return current;
480             }
481             current = mArrayNextIndices[current];
482             counter++;
483         }
484         return -1;
485     }
486 
487 
488     /**
489      * Returns true if at least one of the variable is positive
490      *
491      * @return true if at least one of the variable is positive
492      */
hasAtLeastOnePositiveVariable()493     boolean hasAtLeastOnePositiveVariable() {
494         int current = mHead;
495         int counter = 0;
496         while (current != NONE && counter < mCurrentSize) {
497             if (mArrayValues[current] > 0) {
498                 return true;
499             }
500             current = mArrayNextIndices[current];
501             counter++;
502         }
503         return false;
504     }
505 
506     /**
507      * Invert the values of all the variables in the list
508      */
509     @Override
invert()510     public void invert() {
511         int current = mHead;
512         int counter = 0;
513         while (current != NONE && counter < mCurrentSize) {
514             mArrayValues[current] *= -1;
515             current = mArrayNextIndices[current];
516             counter++;
517         }
518     }
519 
520     /**
521      * Divide the values of all the variables in the list
522      * by the given amount
523      *
524      * @param amount amount to divide by
525      */
526     @Override
divideByAmount(float amount)527     public void divideByAmount(float amount) {
528         int current = mHead;
529         int counter = 0;
530         while (current != NONE && counter < mCurrentSize) {
531             mArrayValues[current] /= amount;
532             current = mArrayNextIndices[current];
533             counter++;
534         }
535     }
536 
getHead()537     public int getHead() {
538         return mHead;
539     }
540 
541     @Override
getCurrentSize()542     public int getCurrentSize() {
543         return mCurrentSize;
544     }
545 
546     /**
547      * get Id in mCache.mIndexedVariables given the index
548      */
getId(int index)549     public final int getId(int index) {
550         return mArrayIndices[index];
551     }
552 
553     /**
554      * get value in mArrayValues given the index
555      */
getValue(int index)556     public final float getValue(int index) {
557         return mArrayValues[index];
558     }
559 
560     /**
561      * Get the next index in mArrayIndices given the current one
562      */
getNextIndice(int index)563     public final int getNextIndice(int index) {
564         return mArrayNextIndices[index];
565     }
566 
567     /**
568      * TODO: check if still needed
569      * Return a pivot candidate
570      *
571      * @return return a variable we can pivot on
572      */
getPivotCandidate()573     SolverVariable getPivotCandidate() {
574         if (mCandidate == null) {
575             // if no candidate is known, let's figure it out
576             int current = mHead;
577             int counter = 0;
578             SolverVariable pivot = null;
579             while (current != NONE && counter < mCurrentSize) {
580                 if (mArrayValues[current] < 0) {
581                     // We can return the first negative candidate as in ArrayLinkedVariables
582                     // they are already sorted by id
583 
584                     SolverVariable v = mCache.mIndexedVariables[mArrayIndices[current]];
585                     if (pivot == null || pivot.strength < v.strength) {
586                         pivot = v;
587                     }
588                 }
589                 current = mArrayNextIndices[current];
590                 counter++;
591             }
592             return pivot;
593         }
594         return mCandidate;
595     }
596 
597     /**
598      * Return a variable from its position in the linked list
599      *
600      * @param index the index of the variable we want to return
601      * @return the variable found, or null
602      */
603     @Override
getVariable(int index)604     public SolverVariable getVariable(int index) {
605         int current = mHead;
606         int counter = 0;
607         while (current != NONE && counter < mCurrentSize) {
608             if (counter == index) {
609                 return mCache.mIndexedVariables[mArrayIndices[current]];
610             }
611             current = mArrayNextIndices[current];
612             counter++;
613         }
614         return null;
615     }
616 
617     /**
618      * Return the value of a variable from its position in the linked list
619      *
620      * @param index the index of the variable we want to look up
621      * @return the value of the found variable, or 0 if not found
622      */
623     @Override
getVariableValue(int index)624     public float getVariableValue(int index) {
625         int current = mHead;
626         int counter = 0;
627         while (current != NONE && counter < mCurrentSize) {
628             if (counter == index) {
629                 return mArrayValues[current];
630             }
631             current = mArrayNextIndices[current];
632             counter++;
633         }
634         return 0;
635     }
636 
637     /**
638      * Return the value of a variable, 0 if not found
639      *
640      * @param v the variable we are looking up
641      * @return the value of the found variable, or 0 if not found
642      */
643     @Override
get(SolverVariable v)644     public final float get(SolverVariable v) {
645         int current = mHead;
646         int counter = 0;
647         while (current != NONE && counter < mCurrentSize) {
648             if (mArrayIndices[current] == v.id) {
649                 return mArrayValues[current];
650             }
651             current = mArrayNextIndices[current];
652             counter++;
653         }
654         return 0;
655     }
656 
657     /**
658      * Show size in bytes
659      *
660      * @return size in bytes
661      */
662     @Override
sizeInBytes()663     public int sizeInBytes() {
664         int size = 0;
665         size += 3 * (mArrayIndices.length * 4);
666         size += 9 * 4;
667         return size;
668     }
669 
670     /**
671      * print out the variables and their values
672      */
673     @Override
display()674     public void display() {
675         int count = mCurrentSize;
676         System.out.print("{ ");
677         for (int i = 0; i < count; i++) {
678             SolverVariable v = getVariable(i);
679             if (v == null) {
680                 continue;
681             }
682             System.out.print(v + " = " + getVariableValue(i) + " ");
683         }
684         System.out.println(" }");
685     }
686 
687     /**
688      * Returns a string representation of the list
689      *
690      * @return a string containing a representation of the list
691      */
692     @Override
toString()693     public String toString() {
694         String result = "";
695         int current = mHead;
696         int counter = 0;
697         while (current != NONE && counter < mCurrentSize) {
698             result += " -> ";
699             result += mArrayValues[current] + " : ";
700             result += mCache.mIndexedVariables[mArrayIndices[current]];
701             current = mArrayNextIndices[current];
702             counter++;
703         }
704         return result;
705     }
706 
707 }
708