Class GuiScheduler
It provides:
- a check for whether the current thread is the main thread
- scheduling a task to run on the main thread
- frame scheduling: coalescing multiple update requests within a short window
- prevention of duplicate scheduling
Thread-safety contract: State.setState(...) and Element.markNeedsBuild()
are safe to call from any thread -- a call made off the main thread is marshalled onto it, and
multiple calls that land within the same frame window are coalesced via this scheduler's
AtomicBoolean compare-and-set guard, at a nominal 16ms frame
interval (floored to one Bukkit tick when scheduling has to wait for one). flush(), by
contrast, throws IllegalStateException when called off the main thread -- it runs
pending work synchronously rather than marshalling it.
Frame scheduling:
Timeline:
|----16ms----|----16ms----|----16ms----|
↑ ↑ ↑
setState rebuild actually runs
↑
setState (coalesced into the same frame)
- Since:
- 6.2.0
- Version:
- 1.0.0
- Author:
- UltiTools Team
-
Field Summary
FieldsModifier and TypeFieldDescriptionprivate static final longThe default frame interval, in milliseconds.private final longprivate final AtomicBooleanprivate longprivate final ConcurrentLinkedQueue<Runnable> private final org.bukkit.plugin.Plugin -
Constructor Summary
ConstructorsConstructorDescriptionCreates a GuiScheduler using the default plugin instance.GuiScheduler(@NotNull org.bukkit.plugin.Plugin plugin) Creates a GuiScheduler.GuiScheduler(@NotNull org.bukkit.plugin.Plugin plugin, long frameIntervalMs) Creates a GuiScheduler with the given frame interval. -
Method Summary
Modifier and TypeMethodDescriptionvoidCancels every pending task.private voidRuns every pending frame task.voidflush()Runs every pending task immediately (blocking until complete).booleanChecks whether the current thread is the main thread.private voidThe console-visible half of a GUI frame task's failure -- a warning naming the task plus a full stack trace.private voidReports aRenderDepthExceededExceptiontoErrorReportCollector.voidrunOnMainThread(@NotNull Runnable task) Ensures the task runs on the main thread.voidscheduleFrame(@NotNull Runnable frameTask) Schedules a frame task.
-
Field Details
-
DEFAULT_FRAME_INTERVAL_MS
private static final long DEFAULT_FRAME_INTERVAL_MSThe default frame interval, in milliseconds. Minecraft's default tick is 50ms, but GUI updates can run more frequently.- See Also:
-
plugin
private final org.bukkit.plugin.Plugin plugin -
frameIntervalMs
private final long frameIntervalMs -
isScheduled
-
pendingTasks
-
lastFrameTime
private long lastFrameTime
-
-
Constructor Details
-
GuiScheduler
public GuiScheduler()Creates a GuiScheduler using the default plugin instance. -
GuiScheduler
public GuiScheduler(@NotNull @NotNull org.bukkit.plugin.Plugin plugin) Creates a GuiScheduler.- Parameters:
plugin- the plugin instance
-
GuiScheduler
public GuiScheduler(@NotNull @NotNull org.bukkit.plugin.Plugin plugin, long frameIntervalMs) Creates a GuiScheduler with the given frame interval.- Parameters:
plugin- the plugin instanceframeIntervalMs- the frame interval, in milliseconds
-
-
Method Details
-
isOnMainThread
public boolean isOnMainThread()Checks whether the current thread is the main thread.- Returns:
- true if on the main thread
-
runOnMainThread
Ensures the task runs on the main thread.If already on the main thread, runs immediately. Otherwise, schedules it to run on the main thread.
Gate-2 Codex finding, PR #478 round 1: this is the path
GuiRenderer.initialize(java.util.function.Supplier<com.ultikits.ultitools.abstracts.gui.declarative.core.Widget>, com.ultikits.ultitools.abstracts.gui.declarative.core.BuildContext)drives the FIRST build through, andGuiRenderer.performBuild()'s own off-main-thread re-entrant guard -- neither passes throughexecuteFrame()'s try/catch, which is whereRenderDepthExceededExceptionreporting (WR-01) lived until now. Since mounting is where the depth guard (CR-01) actually fires, and mounting happens on the very first build, that build's own trip is the MORE common case to miss, not an edge case. Both branches below now report it; only the already-on-main-thread branch re-throws afterward (preserving the existing "the caller ofrunOnMainThread()sees the exception synchronously" contractGuiRenderer.initialize(java.util.function.Supplier<com.ultikits.ultitools.abstracts.gui.declarative.core.Widget>, com.ultikits.ultitools.abstracts.gui.declarative.core.BuildContext)and every CR-01 test depend on) -- the off-thread branch's caller could never observe the deferred task's exception anyway (Bukkit's own scheduler runs it later), so there is nothing to preserve there beyond adding the report.- Parameters:
task- the task to run
-
scheduleFrame
Schedules a frame task.This method implements the frame-coalescing mechanism: multiple calls within the same frame window trigger only one actual execution.
- Parameters:
frameTask- the frame task
-
executeFrame
private void executeFrame()Runs every pending frame task. -
logFrameTaskError
The console-visible half of a GUI frame task's failure -- a warning naming the task plus a full stack trace. Shared by every catch clause inexecuteFrame()so the two log lines cannot drift between the exception types that also do additional reporting (RenderDepthExceededException) and those that do not.- Parameters:
e- the failure to log
-
reportRenderDepthExceeded
Reports aRenderDepthExceededExceptiontoErrorReportCollector.No new per-GUI "failed/closed" bookkeeping is added here: the exception is always thrown from the same guard site with a stable top stack frame (the guard name and depth vary in the message, not in the class/method/line the stack trace records), so
ErrorReportCollector's own fingerprint-based dedup already coalesces repeated occurrences -- e.g. the same over-deep tree being rebuilt on everysetState()call -- into ONE report with an incrementing count, rather than one report per frame.- Parameters:
e- the depth-guard trip to report
-
flush
public void flush()Runs every pending task immediately (blocking until complete).Note: this method may only be called on the main thread.
Gate-2 Codex finding, PR #478 round 3: a
RenderDepthExceededExceptionfrom a queued task now reachesErrorReportCollectorhere too, matchingexecuteFrame()andrunOnMainThread(java.lang.Runnable)'s on-main-thread branch -- neither of those catches ran for a task drained through this method. Still re-thrown immediately afterward, preserving this method's own documented synchronous-drain contract (and, unchanged from before this fix, still skipping theisScheduled.set(false)below on that path -- not a new gap this fix introduces or is scoped to close).- Throws:
IllegalStateException- if not on the main thread
-
cancelAll
public void cancelAll()Cancels every pending task.
-