5.1 KiB
Architecture
Lattice replaces Knit and Vexel with one library. This document records the major decisions and what changed relative to the two parents.
Module layout
Single Gradle module; boundaries are packages with a strict dependency direction (lower layers never import higher ones):
event/ EventBus, EventCall, lifecycle/tick/render events
client/ Minecraft wrappers: Client, Player, Chat, Clipboard, Desktop, Loader
input/ GLFW-backed Keys/Mouse/Keyboard, InputCode abstraction
render/ Renderer interface, NVGRenderer, Font/Image/Color/Gradient, Resolution, GLState
scheduler/ TickScheduler (game ticks), TimeScheduler (wall clock)
animation/ Animation core, manager, element extensions, presets
ui/ Element, Box, Window, UIScreen, layout enums, element events
ui/component Container, Rectangle, Text, SvgImage, Tooltip
ui/widget Button, CheckBox, ColorPicker, Dropdown, Keybind, NumberInput, Slider, Switch, TextInput
ui/theme Theme tokens
text/ Texts + TextBuilder (Component DSL)
command/ Commodore command DSL over Brigadier
util/ Number/String helpers
mixins/ (Java) Minecraft, GameRenderer, GlStateManager
A multi-module split was considered and rejected: the whole library is ~10k lines and every layer targets the same Minecraft version; module plumbing would outweigh the benefit.
Key decisions
One event bus. Knit and Vexel each created their own EventBus instance,
so subscribers had to know which library fired an event. Lattice.eventBus
is the only bus. The unused class-hierarchy dispatch path (checkHierarchy)
was removed — subscribers register on concrete event classes.
One screen class. Knit's KnitScreen adapted vanilla Screen input and
Vexel's VexelScreen layered a Window plus render-event wiring on top.
UIScreen does both jobs in one class half the size.
Box deduplicates scrolling. Vexel's Container and Rectangle each
carried a full copy of the scroll/scrollbar/hover/padding logic (~300 lines
duplicated). Box owns padding, scrolling, scrollbar drawing and the
scroll-adjusted input handling; Container is Box with no drawing and
Rectangle is Box plus painting. The layout engine reads padding uniformly
through Box instead of type-switching on two unrelated classes.
Int ARGB everywhere. The renderer API mixed Int ARGB, java.awt.Color,
and Vexel's HSB Color class. The Renderer interface now takes Int ARGB
only; the HSB Color remains in render/ as a utility (used by ColorPicker
internals and available to clients).
Theme tokens. Widgets previously hardcoded their palette. Widget
constructor defaults now read Theme (accent, surfaces, borders, text,
track/scrollbar, animation durations), initialized to the exact Vexel palette
so the rendered result is unchanged. Aether's theme system inspired the shape.
Fabric-only, current-version-only. Knit carried multi-loader preprocessor
blocks (Forge/NeoForge) and multi-version conditionals that this port could
never exercise. All of it is gone; client.Loader is a thin Fabric wrapper.
Deleted dead code
- World rendering layer:
RenderContextwas all no-ops andMixinWorldRendererthrew unconditionally in the 26.1.2 port. Gone, along withWorldRenderEvent(14 event types that could never fire). MixinClientCommonNetworkHandler: empty mixin.TickEvent.Serverand the server scheduler: nothing ever posted or ticked them.- Duplicate mixin sources: both parents shipped identical
.javafiles insrc/main/javaandsrc/main/kotlin, which brokesourcesJaron Gradle 9. Renderer.createImage(id)parameter: ignored by the implementation.scrollbarIgnorePadding/scrollbarCustomPadding: duplicatedscrollbarPadding.
Fixes
- Linux natives: Vexel shipped only
natives-macos-arm64for NanoVG. Lattice ships linux, linux-arm64, windows, macos, macos-arm64 (as Aether does). - Framebuffer reflection cached:
NVGRenderer.beginFramewalked class hierarchies reflectively every frame to finddirectStateAccess/getFbo; the lookup now resolves once. - Render hook is lazy: the GameRenderer mixin skips the whole NanoVG
frame when nothing subscribes to
RenderEvent.Gui. Mouse.Scaled.yused the width-based scale factor (copy-paste bug in Knit).TickScheduleractually runs: Knit's was never wired to a tick source; Lattice registers it onTickEvent.Starton first use.TimeScheduler.repeathonoredinitialDelayMillisonly in one overload; the rewrite has one code path.- Animation preset leak:
slideIn/bounceScalestored original geometry in global maps keyed byhashCode(), never cleaned; the bookkeeping now lives on the element and dies with it. SvgImagereleases its NVG image on destroy and no longer generates a random UUID cache key per recolor.
Threading model
The event bus uses CopyOnWriteArrayList/ConcurrentHashMap; posting is
safe from any thread, but UI elements must only be touched on the render
thread. TimeScheduler runs on a background pool (use it + display() /
TickScheduler.post to hop back).