001package arez; 002 003import arez.spy.ComputeCompleteEvent; 004import arez.spy.ComputeStartEvent; 005import arez.spy.ObserveCompleteEvent; 006import arez.spy.ObserveStartEvent; 007import arez.spy.ObserverCreateEvent; 008import arez.spy.ObserverDisposeEvent; 009import arez.spy.ObserverInfo; 010import grim.annotations.OmitSymbol; 011import java.util.ArrayList; 012import java.util.HashSet; 013import java.util.LinkedHashMap; 014import java.util.List; 015import java.util.Map; 016import java.util.Objects; 017import java.util.stream.Collectors; 018import javax.annotation.Nonnull; 019import javax.annotation.Nullable; 020import static org.realityforge.braincheck.Guards.*; 021 022/** 023 * A node within Arez that is notified of changes in 0 or more Observables. 024 */ 025public final class Observer 026 extends Node 027{ 028 /** 029 * The component that this observer is contained within. 030 * This should only be set if {@link Arez#areNativeComponentsEnabled()} is true but may also be null if 031 * the observer is a "top-level" observer. 032 */ 033 @OmitSymbol( unless = "arez.enable_native_components" ) 034 @Nullable 035 private final Component _component; 036 /** 037 * The reference to the ComputableValue if this observer is a derivation. 038 */ 039 @Nullable 040 private final ComputableValue<?> _computableValue; 041 /** 042 * The observables that this observer receives notifications from. 043 * These are the dependencies within the dependency graph and will 044 * typically correspond to the observables that were accessed in the 045 * last transaction that this observer was tracking. 046 * 047 * <p>This list should contain no duplicates.</p> 048 */ 049 @Nonnull 050 private List<ObservableValue<?>> _dependencies = new ArrayList<>(); 051 /** 052 * The list of hooks that this observer that will be invoked when it deactivates or 053 * has already been invoked as part of the register/activate process. 054 * These correspond to the hooks that were registered in the last 055 * transaction that this observer was tracking. 056 */ 057 @Nonnull 058 private Map<String, Hook> _hooks = new LinkedHashMap<>(); 059 /** 060 * Observe function to invoke if any. 061 * This may be null if external executor is responsible for executing the observe function via 062 * methods such as {@link ArezContext#observe(Observer, Function, Object...)}. If this is null then 063 * {@link #_onDepsChange} must not be null. 064 */ 065 @Nullable 066 private final Procedure _observe; 067 /** 068 * Callback invoked when dependencies are updated. 069 * This may be null when the observer re-executes the observe function when dependencies change 070 * but in that case {@link #_observe} must not be null. 071 */ 072 @Nullable 073 private final Procedure _onDepsChange; 074 /** 075 * Cached info object associated with element. 076 * This should be null if {@link Arez#areSpiesEnabled()} is false. 077 */ 078 @OmitSymbol( unless = "arez.enable_spies" ) 079 @Nullable 080 private ObserverInfo _info; 081 /** 082 * A bitfield that contains config time and runtime flags/state. 083 * See the values in {@link Flags} that are covered by the masks 084 * {@link Flags#RUNTIME_FLAGS_MASK} and {@link Flags#CONFIG_FLAGS_MASK} 085 * for acceptable values. 086 */ 087 private int _flags; 088 @Nonnull 089 private final Task _task; 090 091 Observer( @Nonnull final ComputableValue<?> computableValue, final int flags ) 092 { 093 this( Arez.areZonesEnabled() ? computableValue.getContext() : null, 094 null, 095 Arez.areNamesEnabled() ? computableValue.getName() : null, 096 computableValue, 097 computableValue::compute, 098 null, 099 flags | 100 ( Flags.KEEPALIVE == Flags.getScheduleType( flags ) ? 0 : Flags.DEACTIVATE_ON_UNOBSERVE ) | 101 Task.Flags.runType( flags, Flags.KEEPALIVE == Flags.getScheduleType( flags ) ? 102 Task.Flags.RUN_NOW : 103 Task.Flags.RUN_LATER ) | 104 ( Arez.shouldEnforceTransactionType() ? Flags.READ_ONLY : 0 ) | 105 Flags.NESTED_ACTIONS_DISALLOWED | 106 Flags.dependencyType( flags ) ); 107 } 108 109 Observer( @Nullable final ArezContext context, 110 @Nullable final Component component, 111 @Nullable final String name, 112 @Nullable final Procedure observe, 113 @Nullable final Procedure onDepsChange, 114 final int flags ) 115 { 116 this( context, 117 component, 118 name, 119 null, 120 observe, 121 onDepsChange, 122 flags | 123 ( null == observe ? Flags.APPLICATION_EXECUTOR : Flags.KEEPALIVE ) | 124 Task.Flags.runType( flags, null == observe ? Task.Flags.RUN_LATER : Task.Flags.RUN_NOW ) | 125 Flags.nestedActionRule( flags ) | 126 Flags.dependencyType( flags ) | 127 Transaction.Flags.transactionMode( flags ) ); 128 } 129 130 private Observer( @Nullable final ArezContext context, 131 @Nullable final Component component, 132 @Nullable final String name, 133 @Nullable final ComputableValue<?> computableValue, 134 @Nullable final Procedure observe, 135 @Nullable final Procedure onDepsChange, 136 final int flags ) 137 { 138 super( context, name ); 139 _task = new Task( context, 140 name, 141 this::invokeReaction, 142 ( flags & Task.Flags.OBSERVER_TASK_FLAGS_MASK ) | 143 Task.Flags.NO_REGISTER_TASK | 144 Task.Flags.NO_WRAP_TASK ); 145 _flags = ( flags & ~Task.Flags.OBSERVER_TASK_FLAGS_MASK ) | Flags.STATE_INACTIVE; 146 if ( Arez.shouldCheckInvariants() ) 147 { 148 if ( Arez.shouldEnforceTransactionType() ) 149 { 150 invariant( () -> Transaction.Flags.isTransactionModeValid( flags ), 151 () -> "Arez-0079: Observer named '" + getName() + "' incorrectly specified both READ_ONLY " + 152 "and READ_WRITE transaction mode flags." ); 153 } 154 else 155 { 156 invariant( () -> !Transaction.Flags.isTransactionModeSpecified( flags ), 157 () -> "Arez-0082: Observer named '" + getName() + "' specified transaction mode '" + 158 Transaction.Flags.getTransactionModeName( flags ) + "' when " + 159 "Arez.enforceTransactionType() is false." ); 160 } 161 invariant( () -> Task.Flags.isPriorityValid( _task.getFlags() ), 162 () -> "Arez-0080: Observer named '" + getName() + "' has invalid priority " + 163 Task.Flags.getPriorityIndex( _task.getFlags() ) + "." ); 164 invariant( () -> Task.Flags.isRunTypeValid( _task.getFlags() ), 165 () -> "Arez-0081: Observer named '" + getName() + "' incorrectly specified both " + 166 "RUN_NOW and RUN_LATER flags." ); 167 invariant( () -> 0 == ( flags & Observer.Flags.RUN_NOW ) || null != observe, 168 () -> "Arez-0206: Observer named '" + getName() + "' incorrectly specified " + 169 "RUN_NOW flag but the observe function is null." ); 170 invariant( () -> Arez.areNativeComponentsEnabled() || null == component, 171 () -> "Arez-0083: Observer named '" + getName() + "' has component specified but " + 172 "Arez.areNativeComponentsEnabled() is false." ); 173 invariant( () -> Task.Flags.getPriority( flags ) != Task.Flags.PRIORITY_LOWEST || 174 0 == ( flags & Flags.OBSERVE_LOWER_PRIORITY_DEPENDENCIES ), 175 () -> "Arez-0184: Observer named '" + getName() + "' has LOWEST priority but has passed " + 176 "OBSERVE_LOWER_PRIORITY_DEPENDENCIES option which should not be present as the observer " + 177 "has no lower priority." ); 178 invariant( () -> null != observe || null != onDepsChange, 179 () -> "Arez-0204: Observer named '" + getName() + "' has not supplied a value for either the " + 180 "observe parameter or the onDepsChange parameter." ); 181 // Next lines are impossible situations to create from tests. Add asserts to verify this. 182 assert Flags.KEEPALIVE != Flags.getScheduleType( flags ) || null != observe; 183 assert Flags.APPLICATION_EXECUTOR != Flags.getScheduleType( flags ) || null == observe; 184 assert !( Observer.Flags.RUN_NOW == ( flags & Observer.Flags.RUN_NOW ) && 185 Flags.KEEPALIVE != Flags.getScheduleType( flags ) && 186 null != computableValue ); 187 invariant( () -> Flags.isNestedActionsModeValid( flags ), 188 () -> "Arez-0209: Observer named '" + getName() + "' incorrectly specified both the " + 189 "NESTED_ACTIONS_ALLOWED flag and the NESTED_ACTIONS_DISALLOWED flag." ); 190 invariant( () -> Flags.isScheduleTypeValid( flags ), 191 () -> "Arez-0210: Observer named '" + getName() + "' incorrectly specified multiple " + 192 "schedule type flags (KEEPALIVE, DEACTIVATE_ON_UNOBSERVE, APPLICATION_EXECUTOR)." ); 193 invariant( () -> ( ~( Flags.RUNTIME_FLAGS_MASK | Flags.CONFIG_FLAGS_MASK ) & flags ) == 0, 194 () -> "Arez-0207: Observer named '" + getName() + "' specified illegal flags: " + 195 ( ~( Flags.RUNTIME_FLAGS_MASK | Flags.CONFIG_FLAGS_MASK ) & flags ) ); 196 } 197 assert null == computableValue || !Arez.areNamesEnabled() || computableValue.getName().equals( name ); 198 _component = Arez.areNativeComponentsEnabled() ? component : null; 199 _computableValue = computableValue; 200 _observe = observe; 201 _onDepsChange = onDepsChange; 202 203 executeObserveNextIfPresent(); 204 205 if ( null == _computableValue ) 206 { 207 if ( null != _component ) 208 { 209 _component.addObserver( this ); 210 } 211 else if ( Arez.areRegistriesEnabled() ) 212 { 213 getContext().registerObserver( this ); 214 } 215 } 216 if ( null == _computableValue ) 217 { 218 if ( willPropagateSpyEvents() ) 219 { 220 getSpy().reportSpyEvent( new ObserverCreateEvent( asInfo() ) ); 221 } 222 if ( null != _observe ) 223 { 224 initialSchedule(); 225 } 226 } 227 } 228 229 void initialSchedule() 230 { 231 getContext().scheduleReaction( this ); 232 _task.triggerSchedulerInitiallyUnlessRunLater(); 233 } 234 235 boolean areArezDependenciesRequired() 236 { 237 assert Arez.shouldCheckApiInvariants(); 238 return Flags.AREZ_DEPENDENCIES == ( _flags & Flags.AREZ_DEPENDENCIES ); 239 } 240 241 boolean areExternalDependenciesAllowed() 242 { 243 assert Arez.shouldCheckApiInvariants(); 244 return Flags.AREZ_OR_EXTERNAL_DEPENDENCIES == ( _flags & Flags.AREZ_OR_EXTERNAL_DEPENDENCIES ); 245 } 246 247 /** 248 * Return true if the Observer supports invocations of {@link #schedule()} from non-arez code. 249 * This is true if both a {@link #_observe} and {@link #_onDepsChange} parameters 250 * are provided at construction. 251 */ 252 boolean supportsManualSchedule() 253 { 254 assert Arez.shouldCheckApiInvariants(); 255 return null != _observe && null != _onDepsChange; 256 } 257 258 boolean isApplicationExecutor() 259 { 260 assert Arez.shouldCheckApiInvariants(); 261 return null == _observe; 262 } 263 264 boolean nestedActionsAllowed() 265 { 266 assert Arez.shouldCheckApiInvariants(); 267 return 0 != ( _flags & Flags.NESTED_ACTIONS_ALLOWED ); 268 } 269 270 boolean canObserveLowerPriorityDependencies() 271 { 272 assert Arez.shouldCheckApiInvariants(); 273 return 0 != ( _flags & Flags.OBSERVE_LOWER_PRIORITY_DEPENDENCIES ); 274 } 275 276 boolean noReportResults() 277 { 278 assert Arez.areSpiesEnabled(); 279 return 0 != ( _flags & Observer.Flags.NO_REPORT_RESULT ); 280 } 281 282 boolean isComputableValue() 283 { 284 return null != _computableValue; 285 } 286 287 /** 288 * Make the Observer INACTIVE and release any resources associated with observer. 289 * The applications should NOT interact with the Observer after it has been disposed. 290 */ 291 @Override 292 public void dispose() 293 { 294 if ( isNotDisposedOrDisposing() ) 295 { 296 getContext().safeAction( Arez.areNamesEnabled() ? getName() + ".dispose" : null, 297 this::performDispose, 298 ActionFlags.NO_VERIFY_ACTION_REQUIRED ); 299 if ( !isComputableValue() ) 300 { 301 if ( willPropagateSpyEvents() ) 302 { 303 reportSpyEvent( new ObserverDisposeEvent( asInfo() ) ); 304 } 305 if ( null != _component ) 306 { 307 _component.removeObserver( this ); 308 } 309 else if ( Arez.areRegistriesEnabled() ) 310 { 311 getContext().deregisterObserver( this ); 312 } 313 } 314 if ( null != _computableValue ) 315 { 316 _computableValue.dispose(); 317 } 318 _task.dispose(); 319 markAsDisposed(); 320 } 321 } 322 323 private void performDispose() 324 { 325 getContext().getTransaction().reportDispose( this ); 326 markDependenciesLeastStaleObserverAsUpToDate(); 327 setState( Flags.STATE_DISPOSING ); 328 } 329 330 void markAsDisposed() 331 { 332 _flags = Flags.setState( _flags, Flags.STATE_DISPOSED ); 333 } 334 335 @Override 336 public boolean isDisposed() 337 { 338 return Flags.STATE_DISPOSED == getState(); 339 } 340 341 boolean isNotDisposedOrDisposing() 342 { 343 return Flags.STATE_DISPOSING < getState(); 344 } 345 346 /** 347 * Return true during invocation of dispose, false otherwise. 348 * 349 * @return true during invocation of dispose, false otherwise. 350 */ 351 boolean isDisposing() 352 { 353 return Flags.STATE_DISPOSING == getState(); 354 } 355 356 /** 357 * Return the state of the observer relative to the observers dependencies. 358 * 359 * @return the state of the observer relative to the observers dependencies. 360 */ 361 int getState() 362 { 363 return Flags.getState( _flags ); 364 } 365 366 int getLeastStaleObserverState() 367 { 368 return Flags.getLeastStaleObserverState( _flags ); 369 } 370 371 /** 372 * Return true if observer creates a READ_WRITE transaction. 373 * 374 * @return true if observer creates a READ_WRITE transaction. 375 */ 376 boolean isMutation() 377 { 378 assert Arez.shouldEnforceTransactionType(); 379 return 0 != ( _flags & Flags.READ_WRITE ); 380 } 381 382 /** 383 * Return true if the observer is active. 384 * Being "active" means that the state of the observer is not {@link Flags#STATE_INACTIVE}, 385 * {@link Flags#STATE_DISPOSING} or {@link Flags#STATE_DISPOSED}. 386 * 387 * <p>An inactive observer has no dependencies and depending on the type of observer may 388 * have other consequences. i.e. An inactive observer will never be scheduled even if it has a 389 * reaction.</p> 390 * 391 * @return true if the Observer is active. 392 */ 393 boolean isActive() 394 { 395 return Flags.isActive( _flags ); 396 } 397 398 /** 399 * Return true if the observer is not active. 400 * The inverse of {@link #isActive()} 401 * 402 * @return true if the Observer is inactive. 403 */ 404 boolean isInactive() 405 { 406 return !isActive(); 407 } 408 409 /** 410 * This method should be invoked if the observer has non-arez dependencies and one of 411 * these dependencies has been updated. This will mark the observer as stale and reschedule 412 * the reaction if necessary. The method must be invoked from within a read-write transaction. 413 * the reaction if necessary. The method must be invoked from within a read-write transaction. 414 */ 415 public void reportStale() 416 { 417 if ( Arez.shouldCheckApiInvariants() ) 418 { 419 apiInvariant( this::areExternalDependenciesAllowed, 420 () -> "Arez-0199: Observer.reportStale() invoked on observer named '" + getName() + 421 "' but the observer has not specified AREZ_OR_EXTERNAL_DEPENDENCIES flag." ); 422 apiInvariant( () -> getContext().isTransactionActive(), 423 () -> "Arez-0200: Observer.reportStale() invoked on observer named '" + getName() + 424 "' when there is no active transaction." ); 425 apiInvariant( () -> getContext().getTransaction().isMutation(), 426 () -> "Arez-0201: Observer.reportStale() invoked on observer named '" + getName() + 427 "' when the active transaction '" + getContext().getTransaction().getName() + 428 "' is READ_ONLY rather than READ_WRITE." ); 429 } 430 if ( Arez.shouldEnforceTransactionType() && Arez.shouldCheckInvariants() ) 431 { 432 getContext().getTransaction().markTransactionAsUsed(); 433 } 434 setState( Flags.STATE_STALE ); 435 } 436 437 /** 438 * Set the state of the observer. 439 * Call the hook actions for relevant state change. 440 * This is equivalent to passing true in <code>schedule</code> parameter to {@link #setState(int, boolean)} 441 * 442 * @param state the new state of the observer. 443 */ 444 void setState( final int state ) 445 { 446 setState( state, true ); 447 } 448 449 /** 450 * Set the state of the observer. 451 * Call the hook actions for relevant state change. 452 * 453 * @param state the new state of the observer. 454 * @param schedule true if a state transition can cause observer to reschedule, false otherwise. 455 */ 456 void setState( final int state, final boolean schedule ) 457 { 458 if ( Arez.shouldCheckInvariants() ) 459 { 460 invariant( () -> getContext().isTransactionActive(), 461 () -> "Arez-0086: Attempt to invoke setState on observer named '" + getName() + "' when there is " + 462 "no active transaction." ); 463 invariantState(); 464 } 465 final int originalState = getState(); 466 if ( state != originalState ) 467 { 468 _flags = Flags.setState( _flags, state ); 469 if ( Arez.shouldCheckInvariants() && Flags.STATE_DISPOSED == originalState ) 470 { 471 fail( () -> "Arez-0087: Attempted to activate disposed observer named '" + getName() + "'." ); 472 } 473 else if ( null == _computableValue && Flags.STATE_STALE == state ) 474 { 475 if ( schedule ) 476 { 477 scheduleReaction(); 478 } 479 } 480 else if ( null != _computableValue && 481 Flags.STATE_UP_TO_DATE == originalState && 482 ( Flags.STATE_STALE == state || Flags.STATE_POSSIBLY_STALE == state ) ) 483 { 484 _computableValue.getObservableValue().reportPossiblyChanged(); 485 if ( schedule ) 486 { 487 scheduleReaction(); 488 } 489 } 490 else if ( Flags.STATE_INACTIVE == state || 491 ( Flags.STATE_INACTIVE != originalState && Flags.STATE_DISPOSING == state ) ) 492 { 493 if ( isComputableValue() ) 494 { 495 getComputableValue().completeDeactivate(); 496 } 497 final Map<String, Hook> hooks = getHooks(); 498 hooks 499 .values() 500 .stream() 501 .map( Hook::getOnDeactivate ) 502 .filter( Objects::nonNull ) 503 .forEach( hook -> runHook( hook, ObserverError.ON_DEACTIVATE_ERROR ) ); 504 hooks.clear(); 505 clearDependencies(); 506 } 507 if ( Arez.shouldCheckInvariants() ) 508 { 509 invariantState(); 510 } 511 } 512 } 513 514 /** 515 * Run the supplied hook if non null. 516 * 517 * @param hook the hook to run. 518 */ 519 void runHook( @Nullable final Procedure hook, @Nonnull final ObserverError error ) 520 { 521 if ( null != hook ) 522 { 523 try 524 { 525 hook.call(); 526 } 527 catch ( final Throwable t ) 528 { 529 getContext().reportObserverError( this, error, t ); 530 } 531 } 532 } 533 534 /** 535 * Remove all dependencies, removing this observer from all dependencies in the process. 536 */ 537 void clearDependencies() 538 { 539 getDependencies().forEach( dependency -> { 540 dependency.removeObserver( this ); 541 if ( !dependency.hasObservers() ) 542 { 543 dependency.setLeastStaleObserverState( Flags.STATE_UP_TO_DATE ); 544 } 545 } ); 546 getDependencies().clear(); 547 } 548 549 /** 550 * Return the task associated with the observer. 551 * The task is used during scheduling. 552 * 553 * @return the task associated with the observer. 554 */ 555 @Nonnull 556 Task getTask() 557 { 558 return _task; 559 } 560 561 /** 562 * Schedule this observer if it does not already have a reaction pending. 563 * The observer will not actually react if it is not already marked as STALE. 564 */ 565 public void schedule() 566 { 567 if ( Arez.shouldCheckApiInvariants() ) 568 { 569 apiInvariant( this::supportsManualSchedule, 570 () -> "Arez-0202: Observer.schedule() invoked on observer named '" + getName() + 571 "' but supportsManualSchedule() returns false." ); 572 } 573 if ( Arez.shouldEnforceTransactionType() && getContext().isTransactionActive() && Arez.shouldCheckInvariants() ) 574 { 575 getContext().getTransaction().markTransactionAsUsed(); 576 } 577 executeObserveNextIfPresent(); 578 scheduleReaction(); 579 getContext().triggerScheduler(); 580 } 581 582 /** 583 * Schedule this observer if it does not already have a reaction pending. 584 */ 585 void scheduleReaction() 586 { 587 if ( isNotDisposed() ) 588 { 589 if ( Arez.shouldCheckInvariants() ) 590 { 591 invariant( this::isActive, 592 () -> "Arez-0088: Observer named '" + getName() + "' is not active but an attempt has been made " + 593 "to schedule observer." ); 594 } 595 if ( !getTask().isQueued() ) 596 { 597 getContext().scheduleReaction( this ); 598 } 599 } 600 } 601 602 /** 603 * Run the reaction in a transaction with the name and mode defined 604 * by the observer. If the reaction throws an exception, the exception is reported 605 * to the context global ObserverErrorHandlers 606 */ 607 void invokeReaction() 608 { 609 if ( isNotDisposed() ) 610 { 611 final long start; 612 if ( willPropagateSpyEvents() ) 613 { 614 start = System.currentTimeMillis(); 615 if ( isComputableValue() ) 616 { 617 reportSpyEvent( new ComputeStartEvent( getComputableValue().asInfo() ) ); 618 } 619 else 620 { 621 reportSpyEvent( new ObserveStartEvent( asInfo() ) ); 622 } 623 } 624 else 625 { 626 start = 0; 627 } 628 Throwable error = null; 629 try 630 { 631 // ComputableValues may have calculated their values and thus be up to date so no need to recalculate. 632 if ( Flags.STATE_UP_TO_DATE != getState() ) 633 { 634 if ( shouldExecuteObserveNext() ) 635 { 636 executeOnDepsChangeNextIfPresent(); 637 runObserveFunction(); 638 } 639 else 640 { 641 assert null != _onDepsChange; 642 _onDepsChange.call(); 643 } 644 } 645 else if ( shouldExecuteObserveNext() ) 646 { 647 /* 648 * The observer should invoke onDepsChange next if the following conditions hold. 649 * - a manual schedule() invocation 650 * - the observer is not stale, and 651 * - there is an onDepsChange hook present 652 * 653 * This block ensures this is the case. 654 */ 655 executeOnDepsChangeNextIfPresent(); 656 } 657 } 658 catch ( final Throwable t ) 659 { 660 error = t; 661 getContext().reportObserverError( this, ObserverError.REACTION_ERROR, t ); 662 } 663 // start == 0 implies that spy events were enabled as part of observer, and thus we can skip this 664 // chain of events 665 if ( willPropagateSpyEvents() && 0 != start ) 666 { 667 final long duration = System.currentTimeMillis() - start; 668 if ( isComputableValue() ) 669 { 670 final ComputableValue<?> computableValue = getComputableValue(); 671 reportSpyEvent( new ComputeCompleteEvent( computableValue.asInfo(), 672 noReportResults() ? null : computableValue.getValue(), 673 computableValue.getError(), 674 (int) duration ) ); 675 } 676 else 677 { 678 reportSpyEvent( new ObserveCompleteEvent( asInfo(), error, (int) duration ) ); 679 } 680 } 681 } 682 } 683 684 private void runObserveFunction() 685 throws Throwable 686 { 687 assert null != _observe; 688 final Procedure action; 689 if ( Arez.shouldCheckInvariants() && areArezDependenciesRequired() ) 690 { 691 action = () -> { 692 _observe.call(); 693 final Transaction current = Transaction.current(); 694 695 final List<ObservableValue<?>> observableValues = current.getObservableValues(); 696 invariant( () -> Objects.requireNonNull( current.getTracker() ).isDisposing() || 697 ( null != observableValues && !observableValues.isEmpty() ), 698 () -> "Arez-0172: Observer named '" + getName() + "' that does not use an external executor " + 699 "completed observe function but is not observing any properties. As a result the observer " + 700 "will never be rescheduled." ); 701 }; 702 } 703 else 704 { 705 action = _observe; 706 } 707 getContext().rawObserve( this, action, null ); 708 } 709 710 /** 711 * Utility to mark all dependencies least stale observer as up to date. 712 * Used when the Observer is determined to be up todate. 713 */ 714 void markDependenciesLeastStaleObserverAsUpToDate() 715 { 716 for ( final ObservableValue<?> dependency : getDependencies() ) 717 { 718 dependency.setLeastStaleObserverState( Flags.STATE_UP_TO_DATE ); 719 } 720 } 721 722 /** 723 * Determine if any dependency of the Observer has actually changed. 724 * If the state is POSSIBLY_STALE then recalculate any ComputableValue dependencies. 725 * If any ComputableValue dependencies actually changed then the STALE state will 726 * be propagated. 727 * 728 * <p>By iterating over the dependencies in the same order that they were reported and 729 * stopping on the first change, all the recalculations are only called for ComputableValues 730 * that will be tracked by derivation. That is because we assume that if the first N 731 * dependencies of the derivation doesn't change then the derivation should run the same way 732 * up until accessing N-th dependency.</p> 733 * 734 * @return true if the Observer should be recomputed. 735 */ 736 boolean shouldCompute() 737 { 738 final int state = getState(); 739 switch ( state ) 740 { 741 case Flags.STATE_UP_TO_DATE: 742 return false; 743 case Flags.STATE_INACTIVE: 744 case Flags.STATE_STALE: 745 return true; 746 case Flags.STATE_POSSIBLY_STALE: 747 { 748 for ( final ObservableValue<?> observableValue : getDependencies() ) 749 { 750 if ( observableValue.isComputableValue() ) 751 { 752 final Observer owner = observableValue.getObserver(); 753 final ComputableValue<?> computableValue = owner.getComputableValue(); 754 try 755 { 756 computableValue.get(); 757 } 758 catch ( final Throwable ignored ) 759 { 760 } 761 // Call to get() will update this state if ComputableValue changed 762 if ( Flags.STATE_STALE == getState() ) 763 { 764 return true; 765 } 766 } 767 } 768 } 769 break; 770 default: 771 if ( Arez.shouldCheckInvariants() ) 772 { 773 fail( () -> "Arez-0205: Observer.shouldCompute() invoked on observer named '" + getName() + 774 "' but observer is in state " + Flags.getStateName( getState() ) ); 775 } 776 } 777 /* 778 * This results in POSSIBLY_STALE returning to UP_TO_DATE 779 */ 780 markDependenciesLeastStaleObserverAsUpToDate(); 781 return false; 782 } 783 784 /** 785 * Return the hooks. 786 * 787 * @return the hooks. 788 */ 789 @Nonnull 790 Map<String, Hook> getHooks() 791 { 792 return _hooks; 793 } 794 795 /** 796 * Replace the current set of hooks with the supplied hooks. 797 * 798 * @param hooks the new set of hooks. 799 */ 800 void replaceHooks( @Nonnull final Map<String, Hook> hooks ) 801 { 802 _hooks = Objects.requireNonNull( hooks ); 803 } 804 805 /** 806 * Return the dependencies. 807 * 808 * @return the dependencies. 809 */ 810 @Nonnull 811 List<ObservableValue<?>> getDependencies() 812 { 813 return _dependencies; 814 } 815 816 /** 817 * Replace the current set of dependencies with supplied dependencies. 818 * This should be the only mechanism via which the dependencies are updated. 819 * 820 * @param dependencies the new set of dependencies. 821 */ 822 void replaceDependencies( @Nonnull final List<ObservableValue<?>> dependencies ) 823 { 824 if ( Arez.shouldCheckInvariants() ) 825 { 826 invariantDependenciesUnique( "Pre replaceDependencies" ); 827 } 828 _dependencies = Objects.requireNonNull( dependencies ); 829 if ( Arez.shouldCheckInvariants() ) 830 { 831 invariantDependenciesUnique( "Post replaceDependencies" ); 832 invariantDependenciesBackLink( "Post replaceDependencies" ); 833 invariantDependenciesNotDisposed(); 834 } 835 } 836 837 /** 838 * Ensure the dependencies list contain no duplicates. 839 * Should be optimized away if invariant checking is disabled. 840 * 841 * @param context some useful debugging context used in invariant checks. 842 */ 843 void invariantDependenciesUnique( @Nonnull final String context ) 844 { 845 if ( Arez.shouldCheckInvariants() ) 846 { 847 invariant( () -> getDependencies().size() == new HashSet<>( getDependencies() ).size(), 848 () -> "Arez-0089: " + context + ": The set of dependencies in observer named '" + 849 getName() + "' is not unique. Current list: '" + 850 getDependencies().stream().map( Node::getName ).collect( Collectors.toList() ) + "'." ); 851 } 852 } 853 854 /** 855 * Ensure all dependencies contain this observer in the list of observers. 856 * Should be optimized away if invariant checking is disabled. 857 * 858 * @param context some useful debugging context used in invariant checks. 859 */ 860 void invariantDependenciesBackLink( @Nonnull final String context ) 861 { 862 if ( Arez.shouldCheckInvariants() ) 863 { 864 getDependencies().forEach( observable -> 865 invariant( () -> observable.getObservers().contains( this ), 866 () -> "Arez-0090: " + context + ": Observer named '" + getName() + 867 "' has ObservableValue dependency named '" + observable.getName() + 868 "' which does not contain the observer in the list of " + 869 "observers." ) ); 870 invariantComputableValueObserverState(); 871 } 872 } 873 874 /** 875 * Ensure all dependencies are not disposed. 876 */ 877 void invariantDependenciesNotDisposed() 878 { 879 if ( Arez.shouldCheckInvariants() ) 880 { 881 getDependencies().forEach( observable -> 882 invariant( observable::isNotDisposed, 883 () -> "Arez-0091: Observer named '" + getName() + "' has " + 884 "ObservableValue dependency named '" + observable.getName() + 885 "' which is disposed." ) ); 886 invariantComputableValueObserverState(); 887 } 888 } 889 890 /** 891 * Ensure that state field and other fields of the Observer are consistent. 892 */ 893 void invariantState() 894 { 895 if ( Arez.shouldCheckInvariants() ) 896 { 897 if ( isInactive() && !isDisposing() ) 898 { 899 invariant( () -> getDependencies().isEmpty(), 900 () -> "Arez-0092: Observer named '" + getName() + "' is inactive but still has dependencies: " + 901 getDependencies().stream().map( Node::getName ).collect( Collectors.toList() ) + "." ); 902 } 903 if ( null != _computableValue && _computableValue.isNotDisposed() ) 904 { 905 final ObservableValue<?> observable = _computableValue.getObservableValue(); 906 invariant( () -> Objects.equals( observable.isComputableValue() ? observable.getObserver() : null, this ), 907 () -> "Arez-0093: Observer named '" + getName() + "' is associated with an ObservableValue that " + 908 "does not link back to observer." ); 909 } 910 } 911 } 912 913 void invariantComputableValueObserverState() 914 { 915 if ( Arez.shouldCheckInvariants() ) 916 { 917 if ( isComputableValue() && isActive() && isNotDisposed() ) 918 { 919 invariant( () -> !getComputableValue().getObservableValue().getObservers().isEmpty() || 920 Objects.equals( getContext().getTransaction().getTracker(), this ), 921 () -> "Arez-0094: Observer named '" + getName() + "' is a ComputableValue and active but the " + 922 "associated ObservableValue has no observers." ); 923 } 924 } 925 } 926 927 /** 928 * Return the ComputableValue for Observer. 929 * This should not be called if observer is not part of a ComputableValue and will generate an invariant failure 930 * if invariants are enabled. 931 * 932 * @return the associated ComputableValue. 933 */ 934 @Nonnull 935 ComputableValue<?> getComputableValue() 936 { 937 if ( Arez.shouldCheckInvariants() ) 938 { 939 invariant( this::isComputableValue, 940 () -> "Arez-0095: Attempted to invoke getComputableValue on observer named '" + getName() + "' when " + 941 "is not a computable observer." ); 942 } 943 assert null != _computableValue; 944 return _computableValue; 945 } 946 947 @Nullable 948 Component getComponent() 949 { 950 return _component; 951 } 952 953 /** 954 * Return the info associated with this class. 955 * 956 * @return the info associated with this class. 957 */ 958 @SuppressWarnings( "ConstantConditions" ) 959 @OmitSymbol( unless = "arez.enable_spies" ) 960 @Nonnull 961 ObserverInfo asInfo() 962 { 963 if ( Arez.shouldCheckInvariants() ) 964 { 965 invariant( Arez::areSpiesEnabled, 966 () -> "Arez-0197: Observer.asInfo() invoked but Arez.areSpiesEnabled() returned false." ); 967 } 968 if ( Arez.areSpiesEnabled() && null == _info ) 969 { 970 _info = new ObserverInfoImpl( getContext().getSpy(), this ); 971 } 972 return Arez.areSpiesEnabled() ? _info : null; 973 } 974 975 @Nullable 976 Procedure getObserve() 977 { 978 return _observe; 979 } 980 981 @Nullable 982 Procedure getOnDepsChange() 983 { 984 return _onDepsChange; 985 } 986 987 boolean isKeepAlive() 988 { 989 return Flags.KEEPALIVE == Flags.getScheduleType( _flags ); 990 } 991 992 boolean shouldExecuteObserveNext() 993 { 994 return 0 != ( _flags & Flags.EXECUTE_OBSERVE_NEXT ); 995 } 996 997 void executeObserveNextIfPresent() 998 { 999 if ( null != _observe ) 1000 { 1001 _flags |= Flags.EXECUTE_OBSERVE_NEXT; 1002 } 1003 } 1004 1005 private void executeOnDepsChangeNextIfPresent() 1006 { 1007 if ( null != _onDepsChange ) 1008 { 1009 _flags &= ~Flags.EXECUTE_OBSERVE_NEXT; 1010 } 1011 } 1012 1013 public static final class Flags 1014 { 1015 /** 1016 * The flag can be passed to actions or observers to force the action to not report result to spy infrastructure. 1017 */ 1018 public static final int NO_REPORT_RESULT = 1 << 12; 1019 /** 1020 * Highest priority. 1021 * This priority should be used when the observer will dispose or release other reactive elements 1022 * (and thus remove elements from being scheduled). 1023 * <p>Only one of the PRIORITY_* flags should be applied to observer.</p> 1024 * 1025 * @see arez.annotations.Priority#HIGHEST 1026 * @see arez.spy.Priority#HIGHEST 1027 * @see Task.Flags#PRIORITY_HIGHEST 1028 */ 1029 public static final int PRIORITY_HIGHEST = 0b001 << 15; 1030 /** 1031 * High priority. 1032 * To reduce the chance that downstream elements will react multiple times within a single 1033 * reaction round, this priority should be used when the observer may trigger many downstream 1034 * reactions. 1035 * <p>Only one of the PRIORITY_* flags should be applied to observer.</p> 1036 * 1037 * @see arez.annotations.Priority#HIGH 1038 * @see arez.spy.Priority#HIGH 1039 * @see Task.Flags#PRIORITY_HIGH 1040 */ 1041 public static final int PRIORITY_HIGH = 0b010 << 15; 1042 /** 1043 * Normal priority if no other priority otherwise specified. 1044 * <p>Only one of the PRIORITY_* flags should be applied to observer.</p> 1045 * 1046 * @see arez.annotations.Priority#NORMAL 1047 * @see arez.spy.Priority#NORMAL 1048 * @see Task.Flags#PRIORITY_NORMAL 1049 */ 1050 public static final int PRIORITY_NORMAL = 0b011 << 15; 1051 /** 1052 * Low priority. 1053 * Usually used to schedule observers that reflect state onto non-reactive 1054 * application components. i.e. Observers that are used to build html views, 1055 * perform network operations etc. These reactions are often at low priority 1056 * to avoid recalculation of dependencies (i.e. {@link ComputableValue}s) triggering 1057 * this reaction multiple times within a single reaction round. 1058 * <p>Only one of the PRIORITY_* flags should be applied to observer.</p> 1059 * 1060 * @see arez.annotations.Priority#LOW 1061 * @see arez.spy.Priority#LOW 1062 * @see Task.Flags#PRIORITY_LOW 1063 */ 1064 public static final int PRIORITY_LOW = 0b100 << 15; 1065 /** 1066 * Lowest priority. Use this priority if the observer is a {@link ComputableValue} that 1067 * may be unobserved when a {@link #PRIORITY_LOW} observer reacts. This is used to avoid 1068 * recomputing state that is likely to either be unobserved or recomputed as part of 1069 * another observers reaction. 1070 * <p>Only one of the PRIORITY_* flags should be applied to observer.</p> 1071 * 1072 * @see arez.annotations.Priority#LOWEST 1073 * @see arez.spy.Priority#LOWEST 1074 * @see Task.Flags#PRIORITY_LOWEST 1075 */ 1076 public static final int PRIORITY_LOWEST = 0b101 << 15; 1077 /** 1078 * Mask used to extract priority bits. 1079 */ 1080 public static final int PRIORITY_MASK = 0b111 << 15; 1081 /** 1082 * The observer can only read arez state. 1083 */ 1084 public static final int READ_ONLY = 1 << 24; 1085 /** 1086 * The observer can read or write arez state. 1087 */ 1088 public static final int READ_WRITE = 1 << 23; 1089 /** 1090 * The scheduler will be triggered when the observer is created to immediately invoke the 1091 * {@link Observer#_observe} function. This configuration should not be specified if there 1092 * is no {@link Observer#_observe} function supplied. This should not be 1093 * specified if {@link #RUN_LATER} is specified. 1094 */ 1095 @SuppressWarnings( "WeakerAccess" ) 1096 public static final int RUN_NOW = 1 << 22; 1097 /** 1098 * The scheduler will not be triggered when the observer is created. The observer either 1099 * has no {@link Observer#_observe} function or is responsible for ensuring that 1100 * {@link ArezContext#triggerScheduler()} is invoked at a later time. This should not be 1101 * specified if {@link #RUN_NOW} is specified. 1102 */ 1103 public static final int RUN_LATER = 1 << 21; 1104 /** 1105 * Flag indicating that the Observer is allowed to observe {@link ComputableValue} instances with a lower priority. 1106 */ 1107 public static final int OBSERVE_LOWER_PRIORITY_DEPENDENCIES = 1 << 30; 1108 /** 1109 * Indicates that the an action can be created from within the Observers observed function. 1110 */ 1111 public static final int NESTED_ACTIONS_ALLOWED = 1 << 29; 1112 /** 1113 * Indicates that the an action must not be created from within the Observers observed function. 1114 */ 1115 public static final int NESTED_ACTIONS_DISALLOWED = 1 << 28; 1116 /** 1117 * Flag set set if the application code can not invoke the {@link Observer#reportStale()} method. 1118 * 1119 * @see arez.annotations.DepType#AREZ 1120 */ 1121 public static final int AREZ_DEPENDENCIES = 1 << 27; 1122 /** 1123 * Flag set set if the application code can not invokethe {@link Observer#reportStale()} method to indicate 1124 * that a dependency has changed. In this scenario it is not an error if the observer does not invoke the 1125 * {@link ObservableValue#reportObserved()} on a dependency during it's reaction. 1126 * 1127 * @see arez.annotations.DepType#AREZ_OR_NONE 1128 */ 1129 public static final int AREZ_OR_NO_DEPENDENCIES = 1 << 26; 1130 /** 1131 * Flag set if the application code can invoke the {@link Observer#reportStale()} method to indicate that a non-arez dependency has changed. 1132 * 1133 * @see arez.annotations.DepType#AREZ_OR_EXTERNAL 1134 */ 1135 public static final int AREZ_OR_EXTERNAL_DEPENDENCIES = 1 << 25; 1136 /** 1137 * The runtime will keep the observer reacting to dependencies until disposed. This is the default value for 1138 * observers that supply a observed function. 1139 */ 1140 public static final int KEEPALIVE = 1 << 20; 1141 /** 1142 * Mask used to extract dependency type bits. 1143 */ 1144 private static final int DEPENDENCIES_TYPE_MASK = 1145 AREZ_DEPENDENCIES | AREZ_OR_NO_DEPENDENCIES | AREZ_OR_EXTERNAL_DEPENDENCIES; 1146 /** 1147 * Mask to extract "NESTED_ACTIONS" option so can derive default value if required. 1148 */ 1149 private static final int NESTED_ACTIONS_MASK = NESTED_ACTIONS_ALLOWED | NESTED_ACTIONS_DISALLOWED; 1150 /** 1151 * Flag indicating whether next scheduled invocation of {@link Observer} should invoke {@link Observer#_observe} or {@link Observer#_onDepsChange}. 1152 */ 1153 static final int EXECUTE_OBSERVE_NEXT = 1 << 9; 1154 /** 1155 * Mask used to extract state bits. 1156 * State is the lowest bits as it is the most frequently accessed numeric fields 1157 * and placing values at lower part of integer avoids a shift. 1158 */ 1159 static final int STATE_MASK = 0b111; 1160 /** 1161 * Mask that identifies the bits associated with runtime configuration. 1162 */ 1163 static final int RUNTIME_FLAGS_MASK = EXECUTE_OBSERVE_NEXT | STATE_MASK; 1164 /** 1165 * The observer has been disposed. 1166 */ 1167 static final int STATE_DISPOSED = 0b001; 1168 /** 1169 * The observer is in the process of being disposed. 1170 */ 1171 static final int STATE_DISPOSING = 0b010; 1172 /** 1173 * The observer is not active and is not holding any data about it's dependencies. 1174 * Typically mean this tracker observer has not been run or if it is a ComputableValue that 1175 * there is no observer observing the associated ObservableValue. 1176 */ 1177 static final int STATE_INACTIVE = 0b011; 1178 /** 1179 * No change since last time observer was notified. 1180 */ 1181 static final int STATE_UP_TO_DATE = 0b100; 1182 /** 1183 * A transitive dependency has changed but it has not been determined if a shallow 1184 * dependency has changed. The observer will need to check if shallow dependencies 1185 * have changed. Only Derived observables will propagate POSSIBLY_STALE state. 1186 */ 1187 static final int STATE_POSSIBLY_STALE = 0b101; 1188 /** 1189 * A dependency has changed so the observer will need to recompute. 1190 */ 1191 static final int STATE_STALE = 0b110; 1192 /** 1193 * The flag is valid on observers associated with computable values and will deactivate the observer if the 1194 * computable value has no observers. 1195 */ 1196 static final int DEACTIVATE_ON_UNOBSERVE = 1 << 19; 1197 /** 1198 * The flag is valid on observers where the observed function is invoked by the application. 1199 */ 1200 static final int APPLICATION_EXECUTOR = 1 << 18; 1201 /** 1202 * Mask used to extract react type bits. 1203 */ 1204 static final int SCHEDULE_TYPE_MASK = KEEPALIVE | DEACTIVATE_ON_UNOBSERVE | APPLICATION_EXECUTOR; 1205 /** 1206 * Mask that identifies the bits associated with static configuration. 1207 */ 1208 static final int CONFIG_FLAGS_MASK = 1209 PRIORITY_MASK | 1210 RUN_NOW | RUN_LATER | 1211 READ_ONLY | READ_WRITE | 1212 OBSERVE_LOWER_PRIORITY_DEPENDENCIES | 1213 NESTED_ACTIONS_MASK | 1214 DEPENDENCIES_TYPE_MASK | 1215 SCHEDULE_TYPE_MASK | 1216 NO_REPORT_RESULT; 1217 1218 /** 1219 * Extract and return the observer's state. 1220 * 1221 * @param flags the flags. 1222 * @return the state. 1223 */ 1224 static int getState( final int flags ) 1225 { 1226 return flags & STATE_MASK; 1227 } 1228 1229 /** 1230 * Return the new value of flags when supplied with specified state. 1231 * 1232 * @param flags the flags. 1233 * @param state the new state. 1234 * @return the new flags. 1235 */ 1236 static int setState( final int flags, final int state ) 1237 { 1238 return ( flags & ~STATE_MASK ) | state; 1239 } 1240 1241 /** 1242 * Return true if the state is UP_TO_DATE, POSSIBLY_STALE or STALE. 1243 * The inverse of {@link #isNotActive(int)} 1244 * 1245 * @param flags the flags to check. 1246 * @return true if the state is UP_TO_DATE, POSSIBLY_STALE or STALE. 1247 */ 1248 static boolean isActive( final int flags ) 1249 { 1250 return getState( flags ) > STATE_INACTIVE; 1251 } 1252 1253 /** 1254 * Return true if the state is INACTIVE, DISPOSING or DISPOSED. 1255 * The inverse of {@link #isActive(int)} 1256 * 1257 * @param flags the flags to check. 1258 * @return true if the state is INACTIVE, DISPOSING or DISPOSED. 1259 */ 1260 static boolean isNotActive( final int flags ) 1261 { 1262 return !isActive( flags ); 1263 } 1264 1265 /** 1266 * Return the least stale observer state. if the state is not active 1267 * then the {@link #STATE_UP_TO_DATE} will be returned. 1268 * 1269 * @param flags the flags to check. 1270 * @return the least stale observer state. 1271 */ 1272 static int getLeastStaleObserverState( final int flags ) 1273 { 1274 final int state = getState( flags ); 1275 return state > STATE_INACTIVE ? state : STATE_UP_TO_DATE; 1276 } 1277 1278 /** 1279 * Return the state as a string. 1280 * 1281 * @param state the state value. One of the STATE_* constants 1282 * @return the string describing state. 1283 */ 1284 @Nonnull 1285 static String getStateName( final int state ) 1286 { 1287 assert Arez.shouldCheckInvariants() || Arez.shouldCheckApiInvariants(); 1288 switch ( state ) 1289 { 1290 case STATE_DISPOSED: 1291 return "DISPOSED"; 1292 case STATE_DISPOSING: 1293 return "DISPOSING"; 1294 case STATE_INACTIVE: 1295 return "INACTIVE"; 1296 case STATE_POSSIBLY_STALE: 1297 return "POSSIBLY_STALE"; 1298 case STATE_STALE: 1299 return "STALE"; 1300 case STATE_UP_TO_DATE: 1301 return "UP_TO_DATE"; 1302 default: 1303 return "UNKNOWN(" + state + ")"; 1304 } 1305 } 1306 1307 static int nestedActionRule( final int flags ) 1308 { 1309 return Arez.shouldCheckInvariants() ? 1310 0 != ( flags & NESTED_ACTIONS_MASK ) ? 0 : NESTED_ACTIONS_DISALLOWED : 1311 0; 1312 } 1313 1314 /** 1315 * Return true if flags contains a valid nested action mode. 1316 * 1317 * @param flags the flags. 1318 * @return true if flags contains valid nested action mode. 1319 */ 1320 static boolean isNestedActionsModeValid( final int flags ) 1321 { 1322 return NESTED_ACTIONS_ALLOWED == ( flags & NESTED_ACTIONS_ALLOWED ) ^ 1323 NESTED_ACTIONS_DISALLOWED == ( flags & NESTED_ACTIONS_DISALLOWED ); 1324 } 1325 1326 /** 1327 * Return the default dependency type flag if dependency type not specified. 1328 * 1329 * @param flags the flags. 1330 * @return the default dependency type if dependency type unspecified else 0. 1331 */ 1332 static int dependencyType( final int flags ) 1333 { 1334 return Arez.shouldCheckInvariants() ? 0 != ( flags & DEPENDENCIES_TYPE_MASK ) ? 0 : AREZ_DEPENDENCIES : 0; 1335 } 1336 1337 /** 1338 * Extract and return the schedule type. 1339 * 1340 * @param flags the flags. 1341 * @return the schedule type. 1342 */ 1343 static int getScheduleType( final int flags ) 1344 { 1345 return flags & SCHEDULE_TYPE_MASK; 1346 } 1347 1348 /** 1349 * Return true if flags contains a valid ScheduleType. 1350 * 1351 * @param flags the flags. 1352 * @return true if flags contains a valid ScheduleType. 1353 */ 1354 static boolean isScheduleTypeValid( final int flags ) 1355 { 1356 return KEEPALIVE == ( flags & KEEPALIVE ) ^ 1357 DEACTIVATE_ON_UNOBSERVE == ( flags & DEACTIVATE_ON_UNOBSERVE ) ^ 1358 APPLICATION_EXECUTOR == ( flags & APPLICATION_EXECUTOR ); 1359 } 1360 } 1361}