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