001package arez;
002
003import arez.spy.Priority;
004import arez.spy.TaskCompleteEvent;
005import arez.spy.TaskInfo;
006import arez.spy.TaskStartEvent;
007import grim.annotations.OmitSymbol;
008import java.util.Objects;
009import javax.annotation.Nonnull;
010import javax.annotation.Nullable;
011import static org.realityforge.braincheck.Guards.*;
012
013/**
014 * A task represents an executable element that can be run by the task executor.
015 */
016public final class Task
017  extends Node
018{
019  /**
020   * The code to invoke when task is executed.
021   */
022  @Nonnull
023  private final SafeProcedure _work;
024  /**
025   * State of the task.
026   */
027  private int _flags;
028  /**
029   * Cached info object associated with element.
030   * This should be null if {@link Arez#areSpiesEnabled()} is false;
031   */
032  @OmitSymbol( unless = "arez.enable_spies" )
033  @Nullable
034  private TaskInfo _info;
035
036  Task( @Nullable final ArezContext context,
037        @Nullable final String name,
038        @Nonnull final SafeProcedure work,
039        final int flags )
040  {
041    super( context, name );
042    if ( Arez.shouldCheckApiInvariants() )
043    {
044      apiInvariant( () -> ( ~Flags.CONFIG_FLAGS_MASK & flags ) == 0,
045                    () -> "Arez-0224: Task named '" + name + "' passed invalid flags: " +
046                          ( ~Flags.CONFIG_FLAGS_MASK & flags ) );
047    }
048
049    _work = Objects.requireNonNull( work );
050    _flags = flags | Flags.STATE_IDLE | Flags.runType( flags ) | Flags.priority( flags );
051    if ( Arez.areRegistriesEnabled() && 0 == ( _flags & Flags.NO_REGISTER_TASK ) )
052    {
053      getContext().registerTask( this );
054    }
055  }
056
057  /**
058   * Re-schedule this task if it is idle and trigger the scheduler if it is not active.
059   */
060  public void schedule()
061  {
062    if ( isIdle() )
063    {
064      queueTask();
065    }
066    getContext().triggerScheduler();
067  }
068
069  int getFlags()
070  {
071    return _flags;
072  }
073
074  void queueTask()
075  {
076    getContext().getTaskQueue().queueTask( this );
077  }
078
079  void initialSchedule()
080  {
081    queueTask();
082    triggerSchedulerInitiallyUnlessRunLater();
083  }
084
085  void triggerSchedulerInitiallyUnlessRunLater()
086  {
087    // If we have not explicitly supplied the RUN_LATER flag then assume it is a run now and
088    // trigger the scheduler
089    if ( 0 == ( _flags & Flags.RUN_LATER ) )
090    {
091      getContext().triggerScheduler();
092    }
093  }
094
095  /**
096   * Return the priority of the task.
097   * This is only meaningful when TaskQueue observes priority.
098   *
099   * @return the priority of the task.
100   */
101  int getPriorityIndex()
102  {
103    return Flags.getPriorityIndex( _flags );
104  }
105
106  /**
107   * Return the priority enum for task.
108   *
109   * @return the priority.
110   */
111  @Nonnull
112  Priority getPriority()
113  {
114    return Priority.values()[ getPriorityIndex() ];
115  }
116
117  /**
118   * Return the task.
119   *
120   * @return the task.
121   */
122  @Nonnull
123  SafeProcedure getWork()
124  {
125    return _work;
126  }
127
128  /**
129   * Execute the work associated with the task.
130   */
131  void executeTask()
132  {
133    // It is possible that the task was executed outside the executor and
134    // may no longer need to be executed. This particularly true when executing tasks
135    // using the "idle until urgent" strategy.
136    if ( isQueued() )
137    {
138      markAsIdle();
139
140      if ( 0 == ( _flags & Flags.NO_WRAP_TASK ) )
141      {
142        runTask();
143      }
144      else
145      {
146        // It is expected that the task/observers currently catch error
147        // and handle internally. Thus no need to catch errors here.
148        _work.call();
149      }
150
151      // If this task has been marked as a task to dispose on completion then do so
152      if ( 0 != ( _flags & Flags.DISPOSE_ON_COMPLETE ) )
153      {
154        dispose();
155      }
156    }
157  }
158
159  /**
160   * Actually execute the task, capture errors and send spy events.
161   */
162  private void runTask()
163  {
164    long startedAt = 0L;
165    if ( willPropagateSpyEvents() )
166    {
167      startedAt = System.currentTimeMillis();
168      getSpy().reportSpyEvent( new TaskStartEvent( asInfo() ) );
169    }
170    Throwable error = null;
171    try
172    {
173      getWork().call();
174    }
175    catch ( final Throwable t )
176    {
177      // Should we handle it with a per-task handler or a global error handler?
178      error = t;
179    }
180    if ( willPropagateSpyEvents() )
181    {
182      final long duration = System.currentTimeMillis() - startedAt;
183      getSpy().reportSpyEvent( new TaskCompleteEvent( asInfo(), error, (int) duration ) );
184    }
185  }
186
187  @Override
188  public void dispose()
189  {
190    if ( isNotDisposed() )
191    {
192      _flags = Flags.setState( _flags, Flags.STATE_DISPOSED );
193      if ( Arez.areRegistriesEnabled() && 0 == ( _flags & Flags.NO_REGISTER_TASK ) )
194      {
195        getContext().deregisterTask( this );
196      }
197    }
198  }
199
200  @Override
201  public boolean isDisposed()
202  {
203    return Flags.STATE_DISPOSED == Flags.getState( _flags );
204  }
205
206  /**
207   * Return the info associated with this class.
208   *
209   * @return the info associated with this class.
210   */
211  @SuppressWarnings( "ConstantConditions" )
212  @OmitSymbol( unless = "arez.enable_spies" )
213  @Nonnull
214  TaskInfo asInfo()
215  {
216    if ( Arez.shouldCheckInvariants() )
217    {
218      invariant( Arez::areSpiesEnabled,
219                 () -> "Arez-0130: Task.asInfo() invoked but Arez.areSpiesEnabled() returned false." );
220    }
221    if ( Arez.areSpiesEnabled() && null == _info )
222    {
223      _info = new TaskInfoImpl( this );
224    }
225    return Arez.areSpiesEnabled() ? _info : null;
226  }
227
228  /**
229   * Mark task as being queued, first verifying that it is not already queued.
230   * This is used so that task will not be able to be queued again until it has run.
231   */
232  void markAsQueued()
233  {
234    if ( Arez.shouldCheckInvariants() )
235    {
236      invariant( this::isIdle,
237                 () -> "Arez-0128: Attempting to queue task named '" + getName() + "' when task is not idle." );
238    }
239    _flags = Flags.setState( _flags, Flags.STATE_QUEUED );
240  }
241
242  /**
243   * Clear the queued flag, first verifying that the task is queued.
244   */
245  void markAsIdle()
246  {
247    if ( Arez.shouldCheckInvariants() )
248    {
249      invariant( this::isQueued,
250                 () -> "Arez-0129: Attempting to clear queued flag on task named '" + getName() +
251                       "' but task is not queued." );
252    }
253    _flags = Flags.setState( _flags, Flags.STATE_IDLE );
254  }
255
256  /**
257   * Return true if task is idle or not disposed and not scheduled.
258   *
259   * @return true if task is idle.
260   */
261  boolean isIdle()
262  {
263    return Flags.STATE_IDLE == Flags.getState( _flags );
264  }
265
266  /**
267   * Return true if task is already scheduled.
268   *
269   * @return true if task is already scheduled.
270   */
271  boolean isQueued()
272  {
273    return Flags.STATE_QUEUED == Flags.getState( _flags );
274  }
275
276  public static final class Flags
277  {
278    /**
279     * Highest priority.
280     * This priority should be used when the task will dispose or release other reactive elements
281     * (and thus remove elements from being scheduled).
282     *
283     * <p>Only one of the PRIORITY_* flags should be applied to a task.</p>
284     *
285     * @see arez.annotations.Priority#HIGHEST
286     * @see Priority#HIGHEST
287     */
288    public static final int PRIORITY_HIGHEST = 0b001 << 15;
289    /**
290     * High priority.
291     * To reduce the chance that downstream elements will react multiple times within a single
292     * reaction round, this priority should be used when the task may trigger many downstream tasks.
293     * <p>Only one of the PRIORITY_* flags should be applied to a task.</p>
294     *
295     * @see arez.annotations.Priority#HIGH
296     * @see Priority#HIGH
297     */
298    public static final int PRIORITY_HIGH = 0b010 << 15;
299    /**
300     * Normal priority if no other priority otherwise specified.
301     *
302     * <p>Only one of the PRIORITY_* flags should be applied to a task.</p>
303     *
304     * @see arez.annotations.Priority#NORMAL
305     * @see Priority#NORMAL
306     */
307    public static final int PRIORITY_NORMAL = 0b011 << 15;
308    /**
309     * Low priority.
310     * Usually used to schedule tasks that reflect state onto non-reactive
311     * application components. i.e. Tasks that are used to build html views,
312     * perform network operations etc. These reactions are often at low priority
313     * to avoid recalculation of dependencies (i.e. {@link ComputableValue}s) triggering
314     * this reaction multiple times within a single reaction round.
315     *
316     * <p>Only one of the PRIORITY_* flags should be applied to a task.</p>
317     *
318     * @see arez.annotations.Priority#LOW
319     * @see Priority#LOW
320     */
321    public static final int PRIORITY_LOW = 0b100 << 15;
322    /**
323     * Lowest priority. Use this priority if the task is a {@link ComputableValue} that
324     * may be unobserved when a {@link #PRIORITY_LOW} observer reacts. This is used to avoid
325     * recomputing state that is likely to either be unobserved or recomputed as part of
326     * another observers reaction.
327     * <p>Only one of the PRIORITY_* flags should be applied to a task.</p>
328     *
329     * @see arez.annotations.Priority#LOWEST
330     * @see Priority#LOWEST
331     */
332    public static final int PRIORITY_LOWEST = 0b101 << 15;
333    /**
334     * Mask used to extract priority bits.
335     */
336    static final int PRIORITY_MASK = 0b111 << 15;
337    /**
338     * Shift used to extract priority after applying mask.
339     */
340    private static final int PRIORITY_SHIFT = 15;
341    /**
342     * The number of priority levels.
343     */
344    static final int PRIORITY_COUNT = 5;
345    /**
346     * The scheduler will be triggered when the task is created to immediately invoke the task.
347     * This should not be specified if {@link #RUN_LATER} is specified.
348     */
349    public static final int RUN_NOW = 1 << 22;
350    /**
351     * The scheduler will not be triggered when the task is created. The application is responsible
352     * for ensuring thatthe  {@link ArezContext#triggerScheduler()} method is invoked at a later time.
353     * This should not be specified if {@link #RUN_NOW} is specified.
354     */
355    public static final int RUN_LATER = 1 << 21;
356    /**
357     * Mask used to extract run type bits.
358     */
359    static final int RUN_TYPE_MASK = RUN_NOW | RUN_LATER;
360    /**
361     * The flag that indicates that task should not be wrapped.
362     * The wrapping is responsible for ensuring the task never generates an exception and for generating
363     * the spy events. If wrapping is disabled it is expected that the caller is responsible for integrating
364     * with the spy subsystem and catching exceptions if any.
365     */
366    public static final int NO_WRAP_TASK = 1 << 20;
367    /**
368     * The flag that specifies that the task should be disposed after it has completed execution.
369     */
370    public static final int DISPOSE_ON_COMPLETE = 1 << 19;
371    /**
372     * The flag that indicates that task should not be registered in top level registry.
373     * This is used when Observers etc create tasks and do not need them exposed to the spy framework.
374     */
375    static final int NO_REGISTER_TASK = 1 << 18;
376    /**
377     * Mask containing flags that can be applied to a task.
378     */
379    static final int CONFIG_FLAGS_MASK =
380      PRIORITY_MASK | RUN_TYPE_MASK | DISPOSE_ON_COMPLETE | NO_REGISTER_TASK | NO_WRAP_TASK;
381    /**
382     * Mask containing flags that can be applied to a task representing an observer.
383     * This omits the flag DISPOSE_ON_COMPLETE as the observer is responsible for disposing the task.
384     */
385    static final int OBSERVER_TASK_FLAGS_MASK = PRIORITY_MASK | RUN_TYPE_MASK;
386    /**
387     * State when the task has not been scheduled.
388     */
389    static final int STATE_IDLE = 0;
390    /**
391     * State when the task has been scheduled and should not be re-scheduled until next executed.
392     */
393    static final int STATE_QUEUED = 1;
394    /**
395     * State when the task has been disposed and should no longer be scheduled.
396     */
397    static final int STATE_DISPOSED = 2;
398    /**
399     * Invalid state that should never be set.
400     */
401    static final int STATE_INVALID = 3;
402    /**
403     * Mask used to extract state bits.
404     */
405    private static final int STATE_MASK = STATE_IDLE | STATE_QUEUED | STATE_DISPOSED;
406    /**
407     * Mask containing flags that are used to track runtime state.
408     */
409    static final int RUNTIME_FLAGS_MASK = STATE_MASK;
410
411    /**
412     * Return true if flags contains valid priority.
413     *
414     * @param flags the flags.
415     * @return true if flags contains priority.
416     */
417    static boolean isStateValid( final int flags )
418    {
419      assert Arez.shouldCheckInvariants() || Arez.shouldCheckApiInvariants();
420      return STATE_INVALID != ( STATE_MASK & flags );
421    }
422
423    static int setState( final int flags, final int state )
424    {
425      return ( ~STATE_MASK & flags ) | state;
426    }
427
428    static int getState( final int flags )
429    {
430      return STATE_MASK & flags;
431    }
432
433    /**
434     * Return true if flags contains a valid react type.
435     *
436     * @param flags the flags.
437     * @return true if flags contains react type.
438     */
439    static boolean isRunTypeValid( final int flags )
440    {
441      assert Arez.shouldCheckInvariants() || Arez.shouldCheckApiInvariants();
442      return RUN_NOW == ( flags & RUN_NOW ) ^ RUN_LATER == ( flags & RUN_LATER );
443    }
444
445    /**
446     * Return the RUN_NOW flag if run type not specified.
447     *
448     * @param flags the flags.
449     * @return the default run type if run type unspecified else 0.
450     */
451    static int runType( final int flags )
452    {
453      return runType( flags, RUN_NOW );
454    }
455
456    /**
457     * Return the default run type flag if run type not specified.
458     *
459     * @param flags       the flags.
460     * @param defaultFlag the default flag.
461     * @return the default run type if run type unspecified else 0.
462     */
463    static int runType( final int flags, final int defaultFlag )
464    {
465      return 0 == ( flags & RUN_TYPE_MASK ) ? defaultFlag : 0;
466    }
467
468    /**
469     * Return true if flags contains valid priority.
470     *
471     * @param flags the flags.
472     * @return true if flags contains priority.
473     */
474    static boolean isPriorityValid( final int flags )
475    {
476      assert Arez.shouldCheckInvariants() || Arez.shouldCheckApiInvariants();
477      final int priorityIndex = getPriorityIndex( flags );
478      return priorityIndex <= 4 && priorityIndex >= 0;
479    }
480
481    /**
482     * Extract and return the priority flag.
483     * This method will not attempt to check priority value is valid.
484     *
485     * @param flags the flags.
486     * @return the priority.
487     */
488    static int getPriority( final int flags )
489    {
490      return flags & PRIORITY_MASK;
491    }
492
493    /**
494     * Extract and return the priority value ranging from the highest priority 0 and lowest priority 4.
495     * This method assumes that flags has valid priority and will not attempt to re-check.
496     *
497     * @param flags the flags.
498     * @return the priority.
499     */
500    static int getPriorityIndex( final int flags )
501    {
502      return ( getPriority( flags ) >> PRIORITY_SHIFT ) - 1;
503    }
504
505    static int priority( final int flags )
506    {
507      return 0 != getPriority( flags ) ? 0 : PRIORITY_NORMAL;
508    }
509
510    private Flags()
511    {
512    }
513  }
514}