java.lang.Object
com.ultikits.ultitools.abstracts.gui.declarative.core.RenderDepthGuard

@Internal public final class RenderDepthGuard extends Object
Shared depth ceiling for the declarative render frame's traversal family (T-05-62 / #371).

Five recursions walk the Element tree and need this guard:

  • Element.mount -- the mounting recursion itself (CR-01): every mount(Element) override calls super.mount(parent) before mounting its own children, so checking here guards ContainerElement/ GridViewElement.mountChildren() and the mount()->performRebuild()-> updateChild()->mount() cascade StatelessElement/StatefulElement drive, in one place. This is the recursion that runs FIRST -- before any of the four below -- both on the very first frame and whenever a later rebuild introduces a brand-new subtree.
  • GuiRenderer.rebuildElement -- the element-rebuild recursion, against an already-mounted tree
  • GridViewElement.performRebuild -- nested inside the above: walks each GridView child's complete subtree (rebuilding every dirty descendant, checked at every visited node) before the GridView's positions are applied
  • GridViewElement.collectRenderNodeLeaves -- walks a GridView cell's subtree to propagate its computed slot to every leaf
  • GuiRenderer.collectRenderNodesRecursive -- the render-node collection recursion, last in the frame
Element.update()/Element.unmount() need no separate guard: both only ever walk a subtree whose depth was already validated by the mount guard when it was first mounted, so neither can exceed MAX_DEPTH without first passing through an unguarded mount() call -- which the guard above closes. Navigator.of() walks the tree UPWARD via a plain loop over Element.getParent() (the same iterative shape as depthOf(Element) itself) -- not recursion, so it needs no guard either.

A module-authored widget tree deep enough overflows the JVM stack mid-frame; StackOverflowError is an Error, so nothing on the render path catches it and it escapes the scheduled frame, potentially leaving the Inventory half-written. Each of the five call sites checks its current depth against MAX_DEPTH before descending further, raising RenderDepthExceededException -- a named, attributable failure -- instead.

Derivation of MAX_DEPTH. Every widget tree shipped in this codebase -- the ShopPage example in the framework's own documentation, and every fixture across GridViewTest/ContainerTest/ElementReconciliationTest -- nests at most 3-4 real levels deep: a root Container wrapping a GridView or a second Container, wrapping a leaf ItemDisplay/TextButton, occasionally with one extra StatefulWidget/StatelessWidget composition layer. MAX_DEPTH is set to 64: roughly 16x that observed depth, generous headroom for legitimate composition this codebase does not currently exercise (nested Navigator routes, deeply composed stateful widgets), while remaining far below the point where these lightweight, few-local-variable recursive methods would exhaust the JVM's default thread stack (in practice, comfortably in the thousands of frames for a call shape this simple, on the stack sizes Paper's main thread runs with). It is a sanity ceiling on module-authored tree shape, not an attempt to find the exact point of stack exhaustion -- the guard is meant to trip long before the JVM ever would.

WR-03: @ApiStatus.Internal -- purely internal render-engine machinery; no module-author use case for catching RenderDepthExceededException or referencing MAX_DEPTH directly is intended, and this release adds no new public surface.

Since:
6.3.0
  • Field Details

  • Constructor Details

    • RenderDepthGuard

      private RenderDepthGuard()
  • Method Details

    • check

      public static void check(String guardName, int depth)
      Raises RenderDepthExceededException if depth exceeds MAX_DEPTH. A tree exactly at the limit is allowed through -- only depths strictly greater trip the guard.
      Parameters:
      guardName - a name identifying which of the four recursions is checking, surfaced in the exception message so a trip is attributable
      depth - the current Element-tree depth, as computed by depthOf(Element)
    • depthOf

      public static int depthOf(Element element)
      Computes an Element's depth by walking its Element.getParent() chain -- an iterative loop, not recursion, so computing the depth itself can never contribute to the stack exhaustion this guard exists to prevent. The mounted root (no parent) is depth 0.
      Parameters:
      element - the Element whose depth to compute
      Returns:
      the number of ancestors between element and the mounted root