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