Skip to content

Optimize UI rendering hot paths and reduce runtime allocations#44

Open
DeusSixik wants to merge 2 commits into
Low-Drag-MC:1.21from
DeusSixik:1.21
Open

Optimize UI rendering hot paths and reduce runtime allocations#44
DeusSixik wants to merge 2 commits into
Low-Drag-MC:1.21from
DeusSixik:1.21

Conversation

@DeusSixik

Copy link
Copy Markdown

Performance and Runtime Cleanup for UI Rendering and Utility Paths

This pull request focuses on reducing allocation pressure in hot paths, simplifying several runtime-heavy code paths, and replacing outdated or suboptimal data structures where the usage pattern is clearly single-threaded.

The changes are intentionally conservative where rendering behavior is involved: whenever possible, the optimizations preserve the existing external behavior and visual output while reducing per-frame overhead.

Summary

  • Replaced several ArrayList and Stack usages with ObjectArrayList where the code is single-threaded and iteration-heavy.
  • Reduced iterator allocations and temporary object creation in frequently executed UI and rendering code.
  • Reworked several rendering helpers to use primitive/local data instead of temporary vectors and lists.
  • Fixed an incorrect corner-radius mapping bug in RectTexture.
  • Added a visual screen test for some rendering-related changes com.lowdragmc.lowdraglib2.test.ui.TestRenderOperation.
  • Kept read-only list handling conservative in places where List.copyOf(...) would only add unnecessary copying.

Changes by Component

com.lowdragmc.lowdraglib2.gui.ui.ModularUI

  1. The elements storage was migrated to ObjectArrayList.
  2. getAllElements() now returns ObjectLists.unmodifiable(...) instead of Collections.unmodifiableList(...), preserving a more efficient ObjectList-backed view.

com.lowdragmc.lowdraglib2.gui.ui.UIElement

  1. The children, styles, and localStylesheets collections were migrated to ObjectArrayList.
  2. The old sortedChildrenCache strategy was reworked to reduce GC pressure during child sorting.
  3. Added int[] indexArrayBuffer and boolean isSortedCacheDirty to support the new sorting/cache flow.
  4. getSortedChildren() was rewritten to reduce allocations during repeated sorting.
  5. clearSortedChildrenCache() now clears and invalidates the cache without relying on null replacement.
  6. clearLocalStylesheets() was changed to avoid creating unnecessary temporary lists.

com.lowdragmc.lowdraglib2.gui.ui.event.UIEventDispatcher

  1. drillDown() no longer relies on iterator-based traversal for sorted children and instead iterates more directly over the cached result.

com.lowdragmc.lowdraglib2.gui.ui.rendering.GUIContext

  1. Replaced java.util.Stack with ObjectArrayList.
  2. Migrated postRenderingCalls to ObjectArrayList.

com.lowdragmc.lowdraglib2.gui.ui.rendering.UIVisualLayer

  1. Replaced the TARGET_POOL and MASK_POOL queue-based storage with ObjectArrayList using LIFO reuse.
  2. Simplified ensureTargetValid() and ensureMaskValid() to reuse the most recently released buffers first.
  3. Added a defensive target == null early-return path inside draw() to avoid invalid access if the layer is used incorrectly.

com.lowdragmc.lowdraglib2.gui.ui.elements.GraphView

  1. Changed refreshContentTransform() visibility from private to protected so subclasses can cooperate with layout updates safely.

com.lowdragmc.lowdraglib2.gui.ui.utils.HistoryStack

  1. Replaced undoStack and redoStack from java.util.Stack to ObjectArrayList.
  2. Added small internal helpers for push, pop, and peek to keep the call sites readable without depending on Stack.
  3. Reworked checkStackSize() to use removeElements(...) instead of subList(...).clear(), which better matches ObjectArrayList.

com.lowdragmc.lowdraglib2.editor.ui.view.HistoryView

  1. Replaced undoStack and redoStack from java.util.Stack to ObjectArrayList.
  2. Aligned the internal history access pattern with HistoryStack through helper-style stack operations.
  3. Adjusted checkStackSize() to remove old entries without relying on subList(...).clear().

com.lowdragmc.lowdraglib2.gui.util.TreeBuilder

  1. Replaced the internal stack from java.util.Stack to ObjectArrayList.

com.lowdragmc.lowdraglib2.gui.ui.elements.UITemplateElement

  1. Replaced the local traversal stack used for template-style application from java.util.Stack to ObjectArrayList.

com.lowdragmc.lowdraglib2.gui.ui.elements.TreeList

  1. Replaced the local path stack inside expandNodeAlongPath() from java.util.Stack to ObjectArrayList.

com.lowdragmc.lowdraglib2.nodegraphtookit.model.ChangeHintList

  1. getHints() now keeps a cached Collections.unmodifiableList(changeHints) view instead of creating fresh wrappers/copies for repeated reads.

com.lowdragmc.lowdraglib2.nodegraphtookit.model.GraphElementModel

  1. getCapabilities() now keeps a cached Collections.unmodifiableList(capabilities) view instead of recreating wrappers or copies on every call.

com.lowdragmc.lowdraglib2.editor.resource.ResourceInstance

  1. listAllResources() intentionally keeps Collections.unmodifiableList(resources) because the method already builds a temporary list, and replacing it with List.copyOf(...) would only add another unnecessary allocation.

com.lowdragmc.lowdraglib2.gui.texture.RectTexture

  1. Reworked the cached corner arc storage from List<Vector2f>[] to a flat float[] layout.
  2. Added reusable scratch arrays for fill and border geometry generation.
  3. Removed hot-path allocations such as temporary point lists and short-lived coordinate arrays.
  4. Fixed the radius component mapping bug so Vector4f radius now follows the expected order:
    • x = left-top
    • y = right-top
    • z = right-bottom
    • w = left-bottom
  5. Applied a small micro-cleanup in drawFill() and drawBorder() by extracting halfWidth, halfHeight, and a shared maxRadius.

com.lowdragmc.lowdraglib2.test.ui.TestRenderOperation

  1. Added a visual screen test used during development to validate rendering-related changes interactively.
  2. The preview setup was adjusted to make corner-order and stroke-related rendering issues easier to spot.
  3. The status text explicitly displays the LT/RT/RB/LB order for faster debugging.

com.lowdragmc.lowdraglib2.client.utils.RenderBufferUtils

  1. Reworked drawColorLines() and drawColorTexLines() to reduce per-frame overhead:
    • added a RandomAccess fast path,
    • replaced temporary Vector3f perp objects with local px/py,
    • preserved color interpolation and UV behavior.
  2. Fixed the strip bootstrap logic by using an emittedAny flag instead of relying on the first index only.
  3. Added guards so fully degenerate line input no longer emits incorrect final vertices.
  4. Optimized drawLine(), drawLines(), and drawEdges():
    • removed unnecessary temporary Vector3f creation,
    • added safe normalization for zero-length segments,
    • reduced repeated calculations.
  5. Added base drawLine(...) overloads that operate directly on raw float coordinates.
  6. Kept the Vector3f overloads as thin wrappers around the float-based implementation.
  7. Reworked drawCircleLine() so it no longer allocates temporary prevPoint, firstPoint, or currentPoint vectors and instead uses local float coordinates plus the new float-based drawLine(...).
  8. Simplified shapeCube() and shapeSphere() by removing temporary float[][], int[][], and short-lived float[] allocations in geometry generation loops.
  9. Applied minor style-level micro-cleanups where repeated divisions could be replaced by precomputed reciprocal multipliers.
  10. Intentionally left drawCubeFace(), renderCubeFace(), shapeCone(), and shapeCircle() unchanged because they do not show the same level of avoidable allocation overhead and are more sensitive to accidental geometry regressions.

Implementation Notes

Why ObjectArrayList

ObjectArrayList was preferred over ArrayList in several hotspots because:

  • it fits the already-available FastUtil dependency,
  • it avoids unnecessary iterator creation in some iteration styles,
  • it is a better fit for single-threaded, allocation-sensitive runtime paths.

Why Stack was removed

java.util.Stack is both legacy and synchronized. In all updated call sites here, the usage pattern is clearly local or single-threaded, so the synchronization overhead is unnecessary. If FastUtil had not already been present, ArrayDeque would have been the most natural standard-library replacement.

Why some read-only lists were not changed to List.copyOf(...)

Not every Collections.unmodifiableList(...) should be replaced mechanically. In cases such as ResourceInstance, an additional List.copyOf(...) would create a fresh copy of already-temporary data and provide no practical benefit.

Validation

  • The project was rebuilt repeatedly during the refactor process.
  • During development, validation relied both on the existing tests in com.lowdragmc.lowdraglib2.test and on custom example-based test screens created for this work.

Profiling Evidence

Expected Outcome

This PR should reduce allocation pressure in UI rendering and utility code, improve data locality in several hot paths, remove outdated stack usage, and fix one visible RectTexture rendering bug, while keeping the overall behavior and public usage patterns stable.

This refactor was driven by practical observations made during mod development. There are still additional places in the UI codebase that may be worth reviewing in the future, but for now they do not appear critical enough to justify expanding the scope of this PR.

render_editor render_graph_toolkit

@DeusSixik

DeusSixik commented Jun 4, 2026

Copy link
Copy Markdown
Author

Quick question regarding List.copyOf(children) in UIElement#drawContents. Is it possible for the children list to actually be modified during the render phase?
If not, then this copyOf call is redundant and could probably be removed.

Instead of creating a new List, I use children.toArray(UIElement[]::new) to squeeze out a bit more performance.

@Yefancy

Yefancy commented Jun 9, 2026

Copy link
Copy Markdown
Member

it looks pretty good. I agree most of changes, I will make few adjustments before merge

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants