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