106 lines
5.1 KiB
Markdown
106 lines
5.1 KiB
Markdown
# 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: `RenderContext` was all no-ops and
|
|
`MixinWorldRenderer` threw unconditionally in the 26.1.2 port. Gone, along
|
|
with `WorldRenderEvent` (14 event types that could never fire).
|
|
- `MixinClientCommonNetworkHandler`: empty mixin.
|
|
- `TickEvent.Server` and the server scheduler: nothing ever posted or ticked
|
|
them.
|
|
- Duplicate mixin sources: both parents shipped identical `.java` files in
|
|
`src/main/java` *and* `src/main/kotlin`, which broke `sourcesJar` on
|
|
Gradle 9.
|
|
- `Renderer.createImage(id)` parameter: ignored by the implementation.
|
|
- `scrollbarIgnorePadding`/`scrollbarCustomPadding`: duplicated
|
|
`scrollbarPadding`.
|
|
|
|
## Fixes
|
|
|
|
- **Linux natives**: Vexel shipped only `natives-macos-arm64` for NanoVG.
|
|
Lattice ships linux, linux-arm64, windows, macos, macos-arm64 (as Aether
|
|
does).
|
|
- **Framebuffer reflection cached**: `NVGRenderer.beginFrame` walked class
|
|
hierarchies reflectively every frame to find `directStateAccess`/`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.y`** used the width-based scale factor (copy-paste bug in
|
|
Knit).
|
|
- **`TickScheduler` actually runs**: Knit's was never wired to a tick source;
|
|
Lattice registers it on `TickEvent.Start` on first use.
|
|
- **`TimeScheduler.repeat` honored `initialDelayMillis`** only in one
|
|
overload; the rewrite has one code path.
|
|
- **Animation preset leak**: `slideIn`/`bounceScale` stored original geometry
|
|
in global maps keyed by `hashCode()`, never cleaned; the bookkeeping now
|
|
lives on the element and dies with it.
|
|
- **`SvgImage` releases 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).
|