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