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