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