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