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