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