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