Class GuiRenderer
It is the declarative framework's core engine, with the main responsibilities of:
- managing the Element tree's lifecycle
- scheduling rebuilds (with frame coalescing)
- running the diff algorithm
- applying changes to the actual Inventory
Workflow:
1. Initialize: createRootElement → build the RenderNode tree 2. Render: diff → apply to Inventory 3. Update: setState → scheduleBuild → rebuild → diff → apply
- Since:
- 6.2.0
- Version:
- 1.0.0
- Author:
- UltiTools Team
-
Field Summary
FieldsModifier and TypeFieldDescriptionprivate booleanTrue whileperformBuild()is running its build body.private final RenderNodeDifferprivate final mc.obliviate.inventory.Guiprivate @Nullable List<RenderNode> private final org.bukkit.entity.Playerprivate booleanTrue when a rebuild was requested whilebuildInProgresswas set.private @Nullable BuildContextprivate @Nullable Elementprivate final GuiScheduler -
Constructor Summary
ConstructorsConstructorDescriptionGuiRenderer(@NotNull mc.obliviate.inventory.Gui gui, @NotNull org.bukkit.entity.Player player) Creates a GuiRenderer.GuiRenderer(@NotNull mc.obliviate.inventory.Gui gui, @NotNull org.bukkit.entity.Player player, @NotNull GuiScheduler scheduler) Creates a GuiRenderer with the given scheduler. -
Method Summary
Modifier and TypeMethodDescriptionprivate voidapplyDiff(@NotNull DiffResult diffResult) Applies the diff result to the Inventory.private voidclearSlot(int slot) Clears a slot.private @NotNull List<RenderNode> collectRenderNodes(@NotNull Element element) Collects every RenderNode (post-order traversal).private voidcollectRenderNodesRecursive(@NotNull Element element, @NotNull List<RenderNode> nodes) voiddispose()Destroys the renderer and releases its resources.@NotNull mc.obliviate.inventory.GuigetGui()Gets the associated Gui instance.@NotNull GuiSchedulerGets the scheduler.voidhandleClick(@NotNull org.bukkit.event.inventory.InventoryClickEvent event) Handles a click event.voidinitialize(@NotNull Supplier<Widget> widgetSupplier, @NotNull BuildContext context) Initializes the renderer.private voidCreates and mounts the root Element.private voidRuns a rebuild immediately (must be called on the main thread).private voidrebuildElement(@NotNull Element element) Recursively rebuilds the Element tree.voidSchedules a rebuild.private voidsetSlot(int slot, @Nullable mc.obliviate.inventory.Icon icon) Sets a slot's content.private voidupdateClickHandlers(@NotNull List<RenderNode> renderNodes) Updates the click-handler mapping.
-
Field Details
-
gui
private final mc.obliviate.inventory.Gui gui -
player
private final org.bukkit.entity.Player player -
scheduler
-
differ
-
rootElement
-
lastRenderNodes
-
widgetSupplier
-
rootContext
-
clickHandlers
-
buildInProgress
private boolean buildInProgressTrue whileperformBuild()is running its build body. Main-thread-confined: it is only read and written on the server main thread (performBuild()redirects itself there first, andscheduleBuild()reads it only after confirming it is on the main thread), so it needs no synchronization. -
rebuildRequestedDuringBuild
private boolean rebuildRequestedDuringBuildTrue when a rebuild was requested whilebuildInProgresswas set. Main-thread- confined likebuildInProgress. Cleared, and replayed as exactly onescheduleBuild()call, when the build that recorded it exits.
-
-
Constructor Details
-
GuiRenderer
public GuiRenderer(@NotNull @NotNull mc.obliviate.inventory.Gui gui, @NotNull @NotNull org.bukkit.entity.Player player) Creates a GuiRenderer.- Parameters:
gui- the associated Gui instanceplayer- the player
-
GuiRenderer
public GuiRenderer(@NotNull @NotNull mc.obliviate.inventory.Gui gui, @NotNull @NotNull org.bukkit.entity.Player player, @NotNull @NotNull GuiScheduler scheduler) Creates a GuiRenderer with the given scheduler.- Parameters:
gui- the associated Gui instanceplayer- the playerscheduler- the scheduler
-
-
Method Details
-
initialize
public void initialize(@NotNull @NotNull Supplier<Widget> widgetSupplier, @NotNull @NotNull BuildContext context) Initializes the renderer.D-09 item 1: no longer accepts a Widget built once up front; instead it accepts a
Supplierthat is called again at the start of every frame, re-deriving the Widget tree from the current state -- this is where the framework'sUI = f(state)tagline first actually holds. Creating the root Element is deferred until inside the firstperformBuild()call, so the supplier is called exactly once on the first frame too, rather than once here and again inside performBuild().- Parameters:
widgetSupplier- the source re-evaluated every frame to produce the Widget treecontext- the build context (used for the root Element; later frames' rebuilds and remounts reuse the same one)
-
scheduleBuild
public void scheduleBuild()Schedules a rebuild.Uses frame coalescing, so multiple calls within a short window trigger only one rebuild.
Requests raised during a build are coalesced, never re-entrant. When this is called on the main thread while
performBuild()is running (for example aState.setStateinsideinitStatewhile the tree is being mounted, or a request raised by a child created during later frame work), the request is only recorded. Every request recorded during one build becomes exactly one follow-upscheduleBuild()after that build exits, so it runs against a fully mounted tree under the scheduler's normal frame timing. Calls from other threads are unchanged: the scheduler marshals them onto the main thread, where they run after the current main-thread task. -
performBuild
private void performBuild()Runs a rebuild immediately (must be called on the main thread).D-09 item 1:
widgetSupplieris called again at the start of every frame, and its result is fed to the already-mounted root Element (Element.update(com.ultikits.ultitools.abstracts.gui.declarative.core.Widget), whose base-class implementation now marks it dirty -- see D-09 item 2), rather than being built once atinitialize(java.util.function.Supplier<com.ultikits.ultitools.abstracts.gui.declarative.core.Widget>, com.ultikits.ultitools.abstracts.gui.declarative.core.BuildContext)time and never re-derived again.No re-entrance. A call that arrives while a build is already running on the main thread (for instance a scheduler drain reached from inside the build) does not start a nested build; it is recorded like a
scheduleBuild()request. When the outermost build exits, normally or by exception, the in-progress flag is cleared first and a recorded request is replayed as exactly onescheduleBuild(). A replay that runs afterdispose()builds nothing, because it returns at thewidgetSupplier == nullcheck below. -
mountRoot
Creates and mounts the root Element.Shared by both the first build and the "supplier returned an incompatible type" case.
The root build scheduler is registered before
mounton purpose: a rebuild request raised while the tree is being mounted (asetStateinsideinitState, or a value source that delivers synchronously on subscribe) must not be dropped. It reachesscheduleBuild()while the build is in progress, is recorded there, and is replayed as one follow-up frame after this build exits.- Parameters:
widget- the root Widget
-
rebuildElement
Recursively rebuilds the Element tree.Guarded by
RenderDepthGuard(T-05-62 / #371): a module-authored tree deeper thanRenderDepthGuard.MAX_DEPTHraisesRenderDepthExceededExceptionhere rather than overflowing the JVM stack mid-frame.- Parameters:
element- the Element to rebuild
-
collectRenderNodes
Collects every RenderNode (post-order traversal).- Parameters:
element- the root Element- Returns:
- the list of RenderNodes
-
collectRenderNodesRecursive
private void collectRenderNodesRecursive(@NotNull @NotNull Element element, @NotNull @NotNull List<RenderNode> nodes) -
applyDiff
Applies the diff result to the Inventory.- Parameters:
diffResult- the diff result
-
updateClickHandlers
Updates the click-handler mapping.- Parameters:
renderNodes- the current list of RenderNodes
-
handleClick
public void handleClick(@NotNull @NotNull org.bukkit.event.inventory.InventoryClickEvent event) Handles a click event.D-09 item 4: obliviate-invs'
InvListener.onClickunconditionally callsGui.onClick(event)(i.e.DeclarativeGui.onClick(org.bukkit.event.inventory.InventoryClickEvent), which forwards here) before applying its owngetRawSlot()check -- this was confirmed from the jar's bytecode, not assumed. That means this method sees every click, including the player clicking their own inventory; it cannot assume the library has already filtered anything out.This uses
InventoryClickEvent.getRawSlot()rather thanInventoryClickEvent.getSlot()for the bounds check:getRawSlot()is an absolute index relative to the whole combined view (top GUI + player inventory) -- the top GUI occupies[0, gui.getSize())and the player's own inventory starts atgui.getSize()-- whereasgetSlot()is numbered independently relative to "whichever Inventory was clicked": the top GUI and the player inventory each count from 0 on their own, so the same value (say, 4) could be both the GUI's 4th slot and the player inventory's 4th slot. Clicking outside the window entirely (e.g. dropping an item outside it) makes Bukkit report a rawSlot of -999, which this same bounds check also rejects without throwing.clickHandlersis the sole record of this click routing -- it is populated byupdateClickHandlers(java.util.List<com.ultikits.ultitools.abstracts.gui.declarative.core.RenderNode>)usingRenderNode.getSlotIndex()(the same "slot within the top GUI" numbering space), and this method looks things up in that same space; there is no second record that could drift from it.- Parameters:
event- the click event
-
setSlot
private void setSlot(int slot, @Nullable @Nullable mc.obliviate.inventory.Icon icon) Sets a slot's content.- Parameters:
slot- the slot indexicon- the icon
-
clearSlot
private void clearSlot(int slot) Clears a slot.- Parameters:
slot- the slot index
-
dispose
public void dispose()Destroys the renderer and releases its resources. -
getGui
@NotNull public @NotNull mc.obliviate.inventory.Gui getGui()Gets the associated Gui instance.- Returns:
- the Gui instance
-
getScheduler
Gets the scheduler.- Returns:
- the GuiScheduler
-