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