001package arez; 002 003import arez.spy.ComputableValueCreateEvent; 004import arez.spy.ComputableValueDisposeEvent; 005import arez.spy.ComputableValueInfo; 006import grim.annotations.OmitSymbol; 007import java.util.Objects; 008import javax.annotation.Nonnull; 009import javax.annotation.Nullable; 010import static org.realityforge.braincheck.Guards.*; 011 012/** 013 * The ComputableValue represents an ObservableValue derived from other ObservableValues within 014 * the Arez system. The value is calculated lazily. i.e., The ComputableValue will only 015 * be calculated if the ComputableValue has observers. 016 * 017 * <p>It should be noted that the ComputableValue is backed by both an ObservableValue and 018 * an Observer. The id's of each of these nodes differ, but they share the name and 019 * thus while debugging appears to be a single element.</p> 020 */ 021public final class ComputableValue<T> 022 extends Node 023{ 024 /** 025 * The component that this ComputableValue is contained within. 026 * This should only be set if {@link Arez#areNativeComponentsEnabled()} is true but may also be null if 027 * the ComputableValue is a "top-level" ComputableValue. 028 */ 029 @OmitSymbol( unless = "arez.enable_native_components" ) 030 @Nullable 031 private final Component _component; 032 /** 033 * The underlying observer that watches the dependencies triggers the re-computation when required. 034 */ 035 private final Observer _observer; 036 /** 037 * The function that recalculates the value. 038 */ 039 private final SafeFunction<T> _function; 040 /** 041 * The associated observable value. 042 */ 043 @Nonnull 044 private final ObservableValue<T> _observableValue; 045 /** 046 * Strategy used to compare old and new computed values. 047 */ 048 @Nonnull 049 private final EqualityComparator _equalityComparator; 050 /** 051 * Flag set to true if computable value can be read outside a transaction. 052 */ 053 private final boolean _readOutsideTransaction; 054 /** 055 * The cached value of the computation. 056 */ 057 private T _value; 058 /** 059 * The error that was thrown the last time that this ComputableValue was derived. 060 * If this value is non-null then {@link #_value} should be null. This exception 061 * is rethrown every time {@link #get()} is called until the computable value is 062 * recalculated. 063 */ 064 private Throwable _error; 065 /** 066 * A flag indicating whether computation is active. Used when checking 067 * invariants to detect when the derivation of the ComputableValue ultimately 068 * causes a recalculation of the ComputableValue. 069 */ 070 private boolean _computing; 071 /** 072 * Flag indicating whether dispose() method has been invoked. 073 */ 074 private boolean _disposed; 075 /** 076 * The number of times that keepAlive has been called without being released. 077 * If this is non-zero then the computedValue should not be deactivated. 078 */ 079 private int _keepAliveRefCount; 080 /** 081 * Cached info object associated with the element. 082 * This should be null if {@link Arez#areSpiesEnabled()} is false; 083 */ 084 @OmitSymbol( unless = "arez.enable_spies" ) 085 @Nullable 086 private ComputableValueInfo _info; 087 088 ComputableValue( @Nullable final ArezContext context, 089 @Nullable final Component component, 090 @Nullable final String name, 091 @Nonnull final SafeFunction<T> function, 092 final int flags ) 093 { 094 this( context, component, name, function, flags, new ObjectsEqualsComparator() ); 095 } 096 097 ComputableValue( @Nullable final ArezContext context, 098 @Nullable final Component component, 099 @Nullable final String name, 100 @Nonnull final SafeFunction<T> function, 101 final int flags, 102 @Nonnull final EqualityComparator equalityComparator ) 103 { 104 super( context, name ); 105 if ( Arez.shouldCheckInvariants() ) 106 { 107 invariant( () -> Arez.areNativeComponentsEnabled() || null == component, 108 () -> "Arez-0048: ComputableValue named '" + getName() + "' has component specified but " + 109 "Arez.areNativeComponentsEnabled() is false." ); 110 } 111 _component = Arez.areNativeComponentsEnabled() ? component : null; 112 _function = Objects.requireNonNull( function ); 113 _equalityComparator = Objects.requireNonNull( equalityComparator ); 114 _value = null; 115 _computing = false; 116 _readOutsideTransaction = Flags.READ_OUTSIDE_TRANSACTION == ( flags & Flags.READ_OUTSIDE_TRANSACTION ); 117 _observer = new Observer( this, flags & ~Flags.READ_OUTSIDE_TRANSACTION ); 118 _observableValue = 119 new ObservableValue<>( context, 120 null, 121 name, 122 _observer, 123 Arez.arePropertyIntrospectorsEnabled() ? this::getValue : null, 124 null ); 125 if ( null != _component ) 126 { 127 _component.addComputableValue( this ); 128 } 129 else if ( Arez.areRegistriesEnabled() ) 130 { 131 getContext().registerComputableValue( this ); 132 } 133 if ( willPropagateSpyEvents() ) 134 { 135 getSpy().reportSpyEvent( new ComputableValueCreateEvent( asInfo() ) ); 136 } 137 if ( Flags.KEEPALIVE == Observer.Flags.getScheduleType( flags ) ) 138 { 139 getObserver().initialSchedule(); 140 } 141 } 142 143 /** 144 * Return the computable value, calculating the value if it is not up to date. 145 * Before invoking this method, a transaction <b>MUST</b> be active, but it may be read-only or read-write. 146 * 147 * @return the computable value. 148 */ 149 public T get() 150 { 151 if ( Arez.shouldCheckApiInvariants() ) 152 { 153 apiInvariant( () -> !_computing, 154 () -> "Arez-0049: Detected a cycle deriving ComputableValue named '" + getName() + "'." ); 155 apiInvariant( _observer::isNotDisposed, 156 () -> "Arez-0050: ComputableValue named '" + getName() + "' accessed after it has been disposed." ); 157 } 158 if ( _readOutsideTransaction ) 159 { 160 getObservableValue().reportObservedIfTrackingTransactionActive(); 161 } 162 else 163 { 164 getObservableValue().reportObserved(); 165 } 166 if ( _observer.shouldCompute() ) 167 { 168 if ( _readOutsideTransaction && !getContext().isTrackingTransactionActive() ) 169 { 170 return getContext().rawCompute( this, () -> { 171 _observer.invokeReaction(); 172 return returnResult(); 173 } ); 174 } 175 else 176 { 177 _observer.invokeReaction(); 178 } 179 } 180 return returnResult(); 181 } 182 183 private T returnResult() 184 { 185 if ( null != _error ) 186 { 187 if ( Arez.shouldCheckInvariants() ) 188 { 189 invariant( () -> null == _value, 190 () -> "Arez-0051: ComputableValue generated a value during computation for ComputableValue named '" + 191 getName() + "' but still has a non-null value." ); 192 } 193 if ( _error instanceof RuntimeException ) 194 { 195 throw (RuntimeException) _error; 196 } 197 else 198 { 199 throw (Error) _error; 200 } 201 } 202 return _value; 203 } 204 205 /** 206 * Invoked when a non-arez dependency of the ComputableValue has changed. The ComputableValue 207 * may or may not change as a result of the dependency change, but Arez will recalculate 208 * the ComputableValue during the normal reaction cycle or when next accessed and will propagate 209 * the change at that time if required. This method must be explicitly invoked by the 210 * developer if the ComputableValue is derived from non-arez data and that data changes. 211 * Before invoking this method, a read-write transaction <b>MUST</b> be active. 212 */ 213 public void reportPossiblyChanged() 214 { 215 if ( Arez.shouldCheckApiInvariants() ) 216 { 217 apiInvariant( this::isNotDisposed, 218 () -> "Arez-0121: The method reportPossiblyChanged() was invoked on disposed " + 219 "ComputableValue named '" + getName() + "'." ); 220 apiInvariant( () -> getObserver().areExternalDependenciesAllowed(), 221 () -> "Arez-0085: The method reportPossiblyChanged() was invoked on ComputableValue named '" + 222 getName() + "' but the computable value has not specified the " + 223 "AREZ_OR_EXTERNAL_DEPENDENCIES flag." ); 224 } 225 if ( Arez.shouldEnforceTransactionType() ) 226 { 227 Transaction.current().verifyWriteAllowed( getObservableValue() ); 228 } 229 if ( Arez.shouldCheckInvariants() ) 230 { 231 Transaction.current().markTransactionAsUsed(); 232 } 233 if ( Observer.Flags.STATE_UP_TO_DATE == getObserver().getState() ) 234 { 235 getObserver().setState( Observer.Flags.STATE_STALE ); 236 } 237 } 238 239 /** 240 * Dispose the ComputableValue so that it can no longer be used. 241 */ 242 @Override 243 public void dispose() 244 { 245 if ( isNotDisposed() ) 246 { 247 if ( Arez.shouldCheckInvariants() ) 248 { 249 // reportDispose only checks invariant and as we don't perform any other activity within it 250 // we can elide this transaction if invariants are disabled 251 getContext().safeAction( Arez.areNamesEnabled() ? getName() + ".dispose" : null, 252 () -> getContext().getTransaction().reportDispose( this ), 253 ActionFlags.NO_VERIFY_ACTION_REQUIRED ); 254 } 255 _disposed = true; 256 _value = null; 257 _error = null; 258 if ( willPropagateSpyEvents() ) 259 { 260 reportSpyEvent( new ComputableValueDisposeEvent( asInfo() ) ); 261 } 262 if ( null != _component ) 263 { 264 _component.removeComputableValue( this ); 265 } 266 else if ( Arez.areRegistriesEnabled() ) 267 { 268 getContext().deregisterComputableValue( this ); 269 } 270 _observableValue.dispose(); 271 if ( !_observer.isDisposing() ) 272 { 273 _observer.dispose(); 274 } 275 } 276 } 277 278 @Override 279 public boolean isDisposed() 280 { 281 return _disposed; 282 } 283 284 /** 285 * Invoke this method to ensure that the ComputableValue is activated and computing 286 * a value even if there are no observers. This is used when there is a chance that 287 * the value will be accessed multiple times, without being accessed from within a 288 * tracking transaction (i.e., the value may only be accessed from actions or may have 289 * observers come and go). 290 * 291 * <p>This method should not be called if the computable value was created with the 292 * {@link Flags#KEEPALIVE} as it is never deactivated in that configuration.</p> 293 * 294 * <p>When the computable value no longer needs to be kept alive the return value 295 * from this method should be disposed.</p> 296 * 297 * @return the object to dispose when no longer need to keep alive. 298 */ 299 @Nonnull 300 public Disposable keepAlive() 301 { 302 if ( Arez.shouldCheckApiInvariants() ) 303 { 304 apiInvariant( () -> !getObserver().isKeepAlive(), 305 () -> "Arez-0223: ComputableValue.keepAlive() was invoked on computable value named '" + 306 getName() + "' but invoking this method when the computable value has been configured " + 307 "with the KEEPALIVE flag is invalid as the computable is always activated." ); 308 } 309 incrementKeepAliveRefCount(); 310 return new Disposable() 311 { 312 private boolean _disposed; 313 314 @Override 315 public void dispose() 316 { 317 if ( !_disposed ) 318 { 319 _disposed = true; 320 decrementKeepAliveRefCount(); 321 } 322 } 323 324 @Override 325 public boolean isDisposed() 326 { 327 return _disposed; 328 } 329 }; 330 } 331 332 void incrementKeepAliveRefCount() 333 { 334 keepAliveInvariants(); 335 _keepAliveRefCount++; 336 if ( 1 == _keepAliveRefCount ) 337 { 338 final ObservableValue<T> observableValue = getObservableValue(); 339 if ( !observableValue.isActive() ) 340 { 341 final ArezContext context = getContext(); 342 if ( context.isTransactionActive() ) 343 { 344 get(); 345 } 346 else 347 { 348 context.scheduleReaction( getObserver() ); 349 context.triggerScheduler(); 350 } 351 } 352 } 353 } 354 355 void decrementKeepAliveRefCount() 356 { 357 _keepAliveRefCount--; 358 keepAliveInvariants(); 359 if ( 0 == _keepAliveRefCount ) 360 { 361 final ObservableValue<T> observableValue = getObservableValue(); 362 final ArezContext context = getContext(); 363 if ( context.isTransactionActive() ) 364 { 365 if ( !observableValue.isPendingDeactivation() ) 366 { 367 context.getTransaction().queueForDeactivation( observableValue ); 368 } 369 } 370 else 371 { 372 getContext().safeAction( Arez.areNamesEnabled() ? getName() + ".deactivate" : null, 373 observableValue::deactivate, 374 ActionFlags.NO_VERIFY_ACTION_REQUIRED ); 375 } 376 } 377 } 378 379 private void keepAliveInvariants() 380 { 381 if ( Arez.shouldCheckInvariants() ) 382 { 383 invariant( () -> _keepAliveRefCount >= 0, 384 () -> "Arez-0165: KeepAliveRefCount on ComputableValue named '" + getName() + 385 "' has an invalid value " + _keepAliveRefCount ); 386 } 387 } 388 389 int getKeepAliveRefCount() 390 { 391 return _keepAliveRefCount; 392 } 393 394 /** 395 * Return true if the ComputableValue is currently being computable. 396 * 397 * @return true if the ComputableValue is currently being computable. 398 */ 399 boolean isComputing() 400 { 401 return _computing; 402 } 403 404 /** 405 * Return the Observer created to represent the ComputableValue. 406 * 407 * @return the Observer created to represent the ComputableValue. 408 */ 409 @Nonnull 410 Observer getObserver() 411 { 412 return _observer; 413 } 414 415 /** 416 * Return the observable for computable value. 417 * 418 * @return the observable for the derived value. 419 */ 420 @Nonnull 421 ObservableValue<T> getObservableValue() 422 { 423 if ( Arez.shouldCheckInvariants() ) 424 { 425 invariant( this::isNotDisposed, 426 () -> "Arez-0084: Attempted to invoke getObservableValue on disposed ComputableValue " + 427 "named '" + getName() + "'." ); 428 } 429 return _observableValue; 430 } 431 432 /** 433 * Compute the new value and compare it to the old value using equality comparator. If 434 * the new value differs from the old value, cache the new value. 435 */ 436 void compute() 437 { 438 final T oldValue = _value; 439 try 440 { 441 final T newValue = computeValue(); 442 if ( !_equalityComparator.areEqual( oldValue, newValue ) ) 443 { 444 _value = newValue; 445 _error = null; 446 getObservableValue().reportChangeConfirmed(); 447 } 448 if ( Arez.shouldCheckApiInvariants() ) 449 { 450 if ( getObserver().areArezDependenciesRequired() ) 451 { 452 final FastList<ObservableValue<?>> observableValues = Transaction.current().getObservableValues(); 453 apiInvariant( () -> null != observableValues && !observableValues.isEmpty(), 454 () -> "Arez-0173: ComputableValue named '" + getName() + "' completed compute but is not " + 455 "observing any properties. As a result compute will never be rescheduled. " + 456 "This is not a ComputableValue candidate." ); 457 } 458 } 459 } 460 catch ( final Exception e ) 461 { 462 if ( null == _error ) 463 { 464 /* 465 * This handles the scenario where the computable generates an exception. The observers should still be 466 * marked as STALE. When they react to this, the computable will throw the exception that was caught. 467 */ 468 _value = null; 469 _error = e; 470 getObservableValue().reportChangeConfirmed(); 471 } 472 throw e; 473 } 474 } 475 476 /** 477 * Actually invoke the function that calculates the value. 478 * 479 * @return the computable value. 480 */ 481 T computeValue() 482 { 483 if ( Arez.shouldCheckInvariants() ) 484 { 485 _computing = true; 486 } 487 try 488 { 489 return _function.call(); 490 } 491 finally 492 { 493 if ( Arez.shouldCheckInvariants() ) 494 { 495 _computing = false; 496 } 497 } 498 } 499 500 /** 501 * Return the info associated with this class. 502 * 503 * @return the info associated with this class. 504 */ 505 @SuppressWarnings( "ConstantConditions" ) 506 @OmitSymbol( unless = "arez.enable_spies" ) 507 @Nonnull 508 ComputableValueInfo asInfo() 509 { 510 if ( Arez.shouldCheckInvariants() ) 511 { 512 invariant( Arez::areSpiesEnabled, 513 () -> "Arez-0195: ComputableValue.asInfo() invoked but Arez.areSpiesEnabled() returned false." ); 514 } 515 if ( Arez.areSpiesEnabled() && null == _info ) 516 { 517 _info = new ComputableValueInfoImpl( this ); 518 } 519 return Arez.areSpiesEnabled() ? _info : null; 520 } 521 522 void completeDeactivate() 523 { 524 _value = null; 525 } 526 527 @OmitSymbol 528 @Nullable 529 Component getComponent() 530 { 531 return _component; 532 } 533 534 T getValue() 535 { 536 return _value; 537 } 538 539 @OmitSymbol 540 void setValue( final T value ) 541 { 542 _value = value; 543 } 544 545 Throwable getError() 546 { 547 return _error; 548 } 549 550 @OmitSymbol 551 void setError( final Throwable error ) 552 { 553 _error = error; 554 } 555 556 @OmitSymbol 557 void setDisposed( final boolean disposed ) 558 { 559 _disposed = disposed; 560 } 561 562 @OmitSymbol 563 void setComputing( final boolean computing ) 564 { 565 _computing = computing; 566 } 567 568 @OmitSymbol 569 void setKeepAliveRefCount( final int keepAliveRefCount ) 570 { 571 _keepAliveRefCount = keepAliveRefCount; 572 } 573 574 /** 575 * Flags that can configure ComputableValue instances during creation. 576 */ 577 public static final class Flags 578 { 579 /** 580 * The scheduler will be triggered when the ComputableValue is created to immediately invoke the 581 * compute function. This should not be specified if {@link #RUN_LATER} is specified. 582 * 583 * @see Task.Flags#RUN_NOW 584 */ 585 public static final int RUN_NOW = 1 << 22; 586 /** 587 * The scheduler will not be triggered when the ComputableValue is created. The ComputableValue 588 * is responsible for ensuring that {@link ArezContext#triggerScheduler()} is invoked at a later 589 * time. This should not be specified if {@link #RUN_NOW} is specified. 590 * 591 * @see Task.Flags#RUN_LATER 592 */ 593 public static final int RUN_LATER = 1 << 21; 594 /** 595 * If passed, then the computable value should not report result to the spy infrastructure. 596 * 597 * @see Observer.Flags#NO_REPORT_RESULT 598 */ 599 public static final int NO_REPORT_RESULT = 1 << 12; 600 /** 601 * Indicates that application code cannot invoke {@link ComputableValue#reportPossiblyChanged()} 602 * and the {@link ComputableValue} is only recalculated if a dependency is updated. 603 * 604 * @see arez.annotations.DepType#AREZ 605 * @see Observer.Flags#AREZ_DEPENDENCIES 606 */ 607 public static final int AREZ_DEPENDENCIES = 1 << 27; 608 /** 609 * Flag set if the application code cannot invoke {@link ComputableValue#reportPossiblyChanged()} to 610 * indicate that a dependency has changed. 611 * 612 * @see arez.annotations.DepType#AREZ_OR_NONE 613 * @see Observer.Flags#AREZ_OR_NO_DEPENDENCIES 614 */ 615 public static final int AREZ_OR_NO_DEPENDENCIES = 1 << 26; 616 /** 617 * Indicates that application code can invoke {@link ComputableValue#reportPossiblyChanged()} to 618 * indicate some dependency has changed and that the {@link ComputableValue} should recompute. 619 * 620 * @see arez.annotations.DepType#AREZ_OR_EXTERNAL 621 * @see Observer.Flags#AREZ_OR_EXTERNAL_DEPENDENCIES 622 */ 623 public static final int AREZ_OR_EXTERNAL_DEPENDENCIES = 1 << 25; 624 /** 625 * Flag indicating that the ComputableValue is allowed to observe {@link ComputableValue} instances with a lower priority. 626 * 627 * @see Observer.Flags#AREZ_OR_EXTERNAL_DEPENDENCIES 628 */ 629 public static final int OBSERVE_LOWER_PRIORITY_DEPENDENCIES = 1 << 30; 630 /** 631 * The runtime will keep the ComputableValue reacting to dependencies until disposed. 632 * 633 * @see Observer.Flags#KEEPALIVE 634 */ 635 public static final int KEEPALIVE = 1 << 20; 636 /** 637 * Highest priority. 638 * This priority should be used when the ComputableValue will dispose or release other reactive elements 639 * (and thus remove elements from being scheduled). 640 * <p>Only one of the PRIORITY_* flags should be applied to ComputableValue.</p> 641 * 642 * @see arez.annotations.Priority#HIGHEST 643 * @see arez.spy.Priority#HIGHEST 644 * @see Task.Flags#PRIORITY_HIGHEST 645 */ 646 public static final int PRIORITY_HIGHEST = 0b001 << 15; 647 /** 648 * High priority. 649 * To reduce the chance that downstream elements will react multiple times within a single 650 * reaction round, this priority should be used when the ComputableValue may trigger many downstream 651 * tasks. 652 * <p>Only one of the PRIORITY_* flags should be applied to ComputableValue.</p> 653 * 654 * @see arez.annotations.Priority#HIGH 655 * @see arez.spy.Priority#HIGH 656 * @see Task.Flags#PRIORITY_HIGH 657 */ 658 public static final int PRIORITY_HIGH = 0b010 << 15; 659 /** 660 * Normal priority if no other priority otherwise specified. 661 * <p>Only one of the PRIORITY_* flags should be applied to ComputableValue.</p> 662 * 663 * @see arez.annotations.Priority#NORMAL 664 * @see arez.spy.Priority#NORMAL 665 * @see Task.Flags#PRIORITY_NORMAL 666 */ 667 public static final int PRIORITY_NORMAL = 0b011 << 15; 668 /** 669 * Low priority. 670 * Usually used to schedule ComputableValues that support reflecting state onto non-reactive 671 * application components. i.e., ComputableValue that are used to build html views, 672 * perform network operations, etc. These ComputableValues are often at low priority 673 * to avoid recalculation of dependencies (i.e. {@link ComputableValue}s) triggering 674 * this ComputableValue multiple times within a single reaction round. 675 * <p>Only one of the PRIORITY_* flags should be applied to ComputableValue.</p> 676 * 677 * @see arez.annotations.Priority#LOW 678 * @see arez.spy.Priority#LOW 679 * @see Task.Flags#PRIORITY_LOW 680 */ 681 public static final int PRIORITY_LOW = 0b100 << 15; 682 /** 683 * Lowest priority. Use this priority if the ComputableValue may be unobserved when 684 * a {@link #PRIORITY_LOW} element reacts. This is used to avoid recomputing state, that is 685 * likely to either be unobserved or recomputed as part of another element's reaction. 686 * <p>Only one of the PRIORITY_* flags should be applied to ComputableValue.</p> 687 * 688 * @see arez.annotations.Priority#LOWEST 689 * @see arez.spy.Priority#LOWEST 690 * @see Task.Flags#PRIORITY_LOWEST 691 */ 692 public static final int PRIORITY_LOWEST = 0b101 << 15; 693 /** 694 * Mask used to extract priority bits. 695 */ 696 public static final int PRIORITY_MASK = 0b111 << 15; 697 /** 698 * Flag indicating that the ComputableValue be accessed outside a transaction. 699 */ 700 public static final int READ_OUTSIDE_TRANSACTION = 1 << 14; 701 /** 702 * Mask that identifies the bits associated with configuration of ComputableValue instances. 703 */ 704 static final int CONFIG_FLAGS_MASK = 705 READ_OUTSIDE_TRANSACTION | 706 PRIORITY_MASK | 707 ( RUN_NOW | RUN_LATER ) | 708 OBSERVE_LOWER_PRIORITY_DEPENDENCIES | 709 ( AREZ_DEPENDENCIES | AREZ_OR_NO_DEPENDENCIES | AREZ_OR_EXTERNAL_DEPENDENCIES ) | 710 KEEPALIVE | 711 NO_REPORT_RESULT; 712 } 713}