Class UiRuntime

java.lang.Object
limn.concurrent.UiRuntime
All Implemented Interfaces:
AutoCloseable

public final class UiRuntime extends Object implements AutoCloseable
The concurrency runtime behind Ui: a single UI thread fed by a thread-safe (MPSC) task queue, plus a worker pool for the application's heavy background work.

The owning backend binds the UI thread (bindToCurrentThread()), drains the queue once per frame (drain()) and sleeps in the native event wait between frames, never busy-waiting. Posting from any other thread triggers the UiRuntime.Waker, which the LWJGL backend maps to glfwPostEmptyEvent() so the sleeping loop wakes up.

Drain semantics: drain() runs only the tasks that were queued when it started (a snapshot). Tasks posted while draining run on the next frame, which keeps a self-reposting task from live-locking the loop. The loop must therefore consult nanosUntilNextDeadline() before sleeping: 0 means "don't sleep, there is pending work".

Every task is timed; tasks exceeding the slow-task budget (default 8 ms) are logged as warnings, making "my click handler does blocking I/O" contract violations visible during development.

  • Nested Class Summary

    Nested Classes
    Modifier and Type
    Class
    Description
    static interface 
    Wakes the native event loop when work is posted from another thread.
  • Constructor Summary

    Constructors
    Constructor
    Description
    Creates a runtime with an injectable clock and worker pool; the pool is not shut down by close().
  • Method Summary

    Modifier and Type
    Method
    Description
    async(Supplier<T> work)
    Runs work on the worker pool and completes the returned future on the UI thread.
    void
    Declares the calling thread as the UI thread.
    void
    Enforces thread confinement: throws unless called on the UI thread.
    void
    Shuts down the worker pool if this runtime created it.
    static UiRuntime
    Creates a runtime with the real clock and an owned daemon worker pool.
    int
    Runs due tasks on the UI thread: first promotes expired delayed tasks, then runs the tasks queued at the moment this call started (snapshot; see class docs).
    int
    drain(Runnable onTaskCrash)
    drain(), plus a hook for the state a crashed task left behind.
    boolean
     
    boolean
     
    long
    How long the native loop may sleep: 0 if immediate work is pending, -1 if it may sleep indefinitely, otherwise the nanoseconds until the earliest delayed task is due.
    void
    post(Runnable action)
    Enqueues action to run on the UI thread on the next frame.
    void
    postDelayed(Runnable action, long delayMillis)
    Enqueues action to run on the UI thread once delayMillis have elapsed.
    void
    Budget above which a drained task is reported as slow.
     
    <T> Work<T>
    work(Work.Body<T> body)
    Describes background work with a lifecycle: cancellable, able to report progress, and able to decline delivery when whoever asked has gone away.
     

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Constructor Details

  • Method Details

    • create

      public static UiRuntime create(UiRuntime.Waker waker)
      Creates a runtime with the real clock and an owned daemon worker pool.
      Parameters:
      waker - invoked (from arbitrary threads) whenever work is posted from outside the UI thread
    • bindToCurrentThread

      public void bindToCurrentThread()
      Declares the calling thread as the UI thread. Called once by the backend.
    • isUiThread

      public boolean isUiThread()
      Returns:
      true iff called on the bound UI thread
    • checkUiThread

      public void checkUiThread()
      Enforces thread confinement: throws unless called on the UI thread.
      Throws:
      IllegalStateException - if called from any other thread, or before a UI thread was bound
    • post

      public void post(Runnable action)
      Enqueues action to run on the UI thread on the next frame. Safe to call from any thread; wakes the native loop when needed.

      A task that mutates widget or scene state is responsible for invalidating what it touched: Widget.invalidate(), Widget.markNeedsLayout() or Scene.requestRender(). Running a task buys no frame of its own, so a mutation nothing invalidates stays unpainted until something else asks for one. Every widget setter and every tree change invalidates already; a task that writes a field behind a setter's back, or that changes what a custom onPaint reads, does not.

    • postDelayed

      public void postDelayed(Runnable action, long delayMillis)
      Enqueues action to run on the UI thread once delayMillis have elapsed. Safe to call from any thread.

      A task that mutates widget or scene state is responsible for invalidating what it touched: see post(java.lang.Runnable). Firing buys no frame of its own, which is what makes a timer the cheap way to watch something that rarely changes: a poll that re-reads a value and finds it unchanged costs a wake-up and nothing else, where a ticker asks for a frame every frame it stays registered.

    • async

      public <T> CompletableFuture<T> async(Supplier<T> work)
      Runs work on the worker pool and completes the returned future on the UI thread. The future's default async executor is the UI thread too, so thenAccept/whenComplete callbacks land on the UI thread, the canonical "click → fetch → update label" path.

      Precisely: dependents registered before completion (the normal case, chaining right after this call) and all *Async dependents run on the UI thread. A non-async dependent attached from a background thread after the future already completed runs inline on the attaching thread, and cancel() completes the future on the cancelling thread; in those corners, use thenAcceptAsync(fn) (the default executor is already the UI thread) or attach from the UI thread.

      A dependent that mutates widget or scene state is responsible for invalidating what it touched: see post(java.lang.Runnable). Completing on the UI thread buys no frame; label.setText(data) asks for the frame that shows it, a field written directly does not.

    • work

      public <T> Work<T> work(Work.Body<T> body)
      Describes background work with a lifecycle: cancellable, able to report progress, and able to decline delivery when whoever asked has gone away. Nothing runs until Work.start().

      Reach for this over async(java.util.function.Supplier<T>) whenever the request can be superseded: a cancelled job delivers nothing, so a view that starts one per keystroke shows the answer to the question last asked, where competing async calls show whichever answer happened to finish last.

    • uiExecutor

      public Executor uiExecutor()
      Returns:
      an Executor that posts to the UI thread (post(java.lang.Runnable))
    • workerPool

      public ExecutorService workerPool()
      Returns:
      the worker pool used by async(java.util.function.Supplier<T>)
    • drain

      public int drain()
      Runs due tasks on the UI thread: first promotes expired delayed tasks, then runs the tasks queued at the moment this call started (snapshot; see class docs). A task that throws is logged and does not abort the drain, and a task over the slow-task budget is logged as a warning.

      Draining schedules no repaint. Each task invalidates what it mutated (see post(java.lang.Runnable)); one that threw did not finish doing so, which is what drain(Runnable) exists for.

      Returns:
      the number of tasks executed
    • drain

      public int drain(Runnable onTaskCrash)
      drain(), plus a hook for the state a crashed task left behind.
      Parameters:
      onTaskCrash - run on the UI thread, inside the drain, once per task that threw, after the crash is logged and reported. A task that throws part-way has applied part of its mutation and invalidated none of it, and how much is unknowable from here, so the caller that owns the surfaces settles them: the LWJGL backend repaints every window. Called on no other path; a task that returns normally is trusted to have invalidated its own work. Runs inside the drain, so it must not throw (an exception from it aborts the remaining tasks).
      Returns:
      the number of tasks executed, crashed ones included
    • hasPendingTasks

      public boolean hasPendingTasks()
      Returns:
      true if immediate tasks are queued right now
    • nanosUntilNextDeadline

      public long nanosUntilNextDeadline()
      How long the native loop may sleep: 0 if immediate work is pending, -1 if it may sleep indefinitely, otherwise the nanoseconds until the earliest delayed task is due.
    • setSlowTaskBudgetMillis

      public void setSlowTaskBudgetMillis(long millis)
      Budget above which a drained task is reported as slow. Default: 8 ms.
    • close

      public void close()
      Shuts down the worker pool if this runtime created it.
      Specified by:
      close in interface AutoCloseable