Skip to content

Cut the join pack reload from 8 s to 3 s - #50

Merged
ryanbarlow97 merged 5 commits into
mainfrom
pack-reload-speed
Oct 15, 2026
Merged

ryanbarlow97 merged 5 commits into
mainfrom
pack-reload-speed

Conversation

@ryanbarlow97

@ryanbarlow97 ryanbarlow97 commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Joining Main spends 11–16 s on the pack even when it is cached. I profiled the reload on a real client (per-step reload timing plus Java Flight Recorder) and found two causes, both in how the pack is laid out.

Fonts. The client checks every provider of a font for every code point it knows, including unifont's ~60,000. ItemsAdder writes one bitmap provider per font image: 616 in minecraft:default and the same again in minecraft:uniform. That blocks the render thread for over 2 s. The new FontGridMerger merges one-glyph providers that share an image size and every other setting into grid sheets. It leaves a glyph alone when:

  • its texture isn't in the pack;
  • an overlay replaces its texture;
  • its code point is defined twice;
  • its PNG isn't 8-bit RGB, RGBA or palette;
  • or the font has providers whose code points can't be listed.

It decodes ItemsAdder's protected PNGs (scrambled chunk CRCs and zlib checksum) the way the client does. On Main this takes the fonts from 616 providers to 309. The other 247 are the mcemojis vanilla item emojis, which use vanilla textures the server doesn't have.

Zip scanning. In 1.21.10 the client reads every entry of a pack zip once per namespace in that zip, each time it lists resources, and ZipFile is synchronised. Core held 33 namespaces and 19,795 entries. Core is now split into core, core2 and core3, using an exact partition of the namespaces sorted by size that minimises namespaces × entries. That cuts the listing work about 5×.

The merged fonts and sheets are written as ordinary zip records; every other record is still copied byte for byte. The HTTP server's allowed paths now come from PackPartitioner.PARTS.

Results

Measured on Main's real generated.zip (cf2c8627), using a vanilla 1.21.10 client with warm F3+T reloads on a 16-thread PC:

Reload Render-thread block
Current (3 parts) 8.0 s 2.7 s
This PR (5 parts) 3.0 s 1.1 s
No pack 0.3 s 0.1 s
  • All 616 glyphs have the same cell pixels, height, ascent and filter.
  • The client logs the same 79 kinds of warnings as before.
  • Publishing takes 1.9 s and is deterministic.
  • models.zip and sounds.zip keep their current hashes, so players' cached copies stay valid.

clean verify passes with full line coverage.

Docs: TF-Minecraft/Docs#263

🤖 Generated with Claude Code

Merge ItemsAdder's one-glyph font providers into grid sheets and split
the core pack into three parts by namespace.

The client checks every font provider for every code point it knows,
including unifont's ~60,000, so 616 one-glyph providers froze the render
thread for over 2 s. Same-shaped glyphs now share a sheet (616 -> 309 on
Main; the rest use vanilla textures the server does not have).

A 1.21 client reads every entry of a zip once per namespace it holds each
time it lists resources, and ZipFile is synchronised. Core held 33
namespaces and 19,795 entries; splitting it into three parts that
minimise namespaces x entries cuts that work about 5x and lets the
workers read in parallel.

On a vanilla 1.21.10 client with Main's pack, a warm reload drops from
8.0 s to 3.0 s and the render-thread block from 2.7 s to 1.1 s. Every
glyph cell, height, ascent and filter is unchanged, the client logs the
same warnings, and models.zip and sounds.zip keep their hashes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the size:L 100-499 effective changed lines (tests excluded in mixed PRs). label Oct 15, 2026
@coderabbitai

coderabbitai Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 7 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 3e13ce09-d641-43e1-8f97-dfbf5f832de4
📥 Commits

Reviewing files that changed from the base of the PR and between 116ff9c and 09b87d5.

📒 Files selected for processing: 5
  • src/main/java/net/tfminecraft/tfmccore/resourcepack/FontGridMerger.java
  • src/main/java/net/tfminecraft/tfmccore/resourcepack/MultipartPackService.java
  • src/main/java/net/tfminecraft/tfmccore/resourcepack/PackPublisher.java
  • src/test/java/net/tfminecraft/tfmccore/resourcepack/FontGridMergerTest.java
  • src/test/java/net/tfminecraft/tfmccore/resourcepack/MultipartPackServiceCoverageTest.java

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: ee67d116-630a-46a5-95ee-7833b5eb4804
📥 Commits

Reviewing files that changed from the base of the PR and between a999998 and 116ff9c.

📒 Files selected for processing: 7
  • src/main/java/net/tfminecraft/tfmccore/resourcepack/FontGridMerger.java
  • src/main/java/net/tfminecraft/tfmccore/resourcepack/MultipartPackService.java
  • src/main/java/net/tfminecraft/tfmccore/resourcepack/PackHttpServer.java
  • src/main/java/net/tfminecraft/tfmccore/resourcepack/PackPartitioner.java
  • src/test/java/net/tfminecraft/tfmccore/resourcepack/FontGridMergerTest.java
  • src/test/java/net/tfminecraft/tfmccore/resourcepack/MultipartPackServiceCoverageTest.java
  • src/test/java/net/tfminecraft/tfmccore/resourcepack/MultipartPackTest.java

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

📝 Summary

Summary by CodeRabbit

  • New Features
    • Resource packs are now split into five parts, with core resources more evenly distributed. Font files with compatible single-glyph images can be combined into grid sheets to reduce duplication.
  • Improvements
    • Pack delivery and download handling now support the updated set of parts, and status tracking waits for every part to complete.
    • Resource-pack messages now refer to parts generally rather than assuming a fixed count.

The resource-pack partitioner now creates three core parts alongside models and sounds, balances namespaces, and merges eligible bitmap font glyphs into generated sheets. ZIP handling preserves source records for existing entries. HTTP matching, multipart delivery wording, and tests now use the configured part list.

Changes: Resource-pack partitioning

Layer / File(s) Summary
Font glyph merging
src/main/java/net/tfminecraft/tfmccore/resourcepack/FontGridMerger.java, src/test/java/net/tfminecraft/tfmccore/resourcepack/FontGridMergerTest.java
Compatible single-glyph bitmap providers are combined into generated PNG grids. The merger retains ineligible providers and leaves malformed fonts or unsupported images unchanged. Tests cover provider selection, generated sheet content, decoder inputs, and boundary cases.
Namespace partitioning and archive writing
src/main/java/net/tfminecraft/tfmccore/resourcepack/PackPartitioner.java, src/test/java/net/tfminecraft/tfmccore/resourcepack/MultipartPackTest.java, src/test/java/net/tfminecraft/tfmccore/resourcepack/FontGridMergerTest.java
The partitioner adds core2 and core3, balances namespaces across the three core parts, and integrates merged font records. ZIP writing copies source records for existing entries and creates records for generated content. Tests check namespace allocation, part contents, and merged font output.
Multipart delivery and HTTP matching
src/main/java/net/tfminecraft/tfmccore/resourcepack/PackHttpServer.java, src/main/java/net/tfminecraft/tfmccore/resourcepack/MultipartPackService.java, src/test/java/net/tfminecraft/tfmccore/resourcepack/MultipartPackServiceCoverageTest.java
The HTTP path pattern derives accepted part names from PackPartitioner.PARTS. Service messages and Javadoc refer to parts without a fixed count. Coverage tests derive expected counts and completion checks from the configured parts.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Refactor

Merge Risk: ⚪ Minimal · up to 116ff

No actionable merge-blocking issue was identified; the change is mergeable after normal checks.

- [ ] Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Oct 15, 2026
- Keep generations under parts-v2: an existing three-part generation of the
  same ItemsAdder hash failed the integrity check and blocked publishing.
- Bound font and texture reads as they inflate (protected ZIPs mask sizes),
  cap each glyph at 1024x1024 and the glyphs held at 16M pixels.
- Reject what stb_image rejects: unknown critical chunks, IHDR not first or
  repeated, PLTE/tRNS after IDAT or repeated, tRNS before PLTE, bad palettes
  and non-standard header fields. Such glyphs stay unmerged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ryanbarlow97 and others added 3 commits October 15, 2026 16:47
- Check a glyph's pixel count against its own cap and the build's remaining
  budget from IHDR, before anything is inflated or allocated.
- Reject what stb_image rejects: a bad zlib header (check bits, preset
  dictionary, not deflate), a stream with no final block, no IEND, an empty
  palette, and critical chunks by the PNG bit rather than letter case.
- Reject palette indices past the palette: stb reads past it, ImageIO reads
  black, so a merged copy could differ.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Palette chunks only on palette images, before the data; no tRNS on RGB or
  RGBA; image data as one run of non-empty IDAT chunks with the palette
  already present; the four Adler-32 bytes present after the stream (their
  value is still not checked). Anything else stays unmerged.
- Hold at most 16 MiB of rewritten fonts and sheets; later fonts stay as
  ItemsAdder wrote them.

Against LWJGL/stb 3.3.3 on a 7,026-image corpus, every image the merger
accepts now decodes to the same pixels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 16 MiB output check ran only after a sheet was built and encoded, so a
4096x4096 sheet of incompressible glyphs could exhaust a small heap first.
Sheets are now at most 2048x2048, glyphs at most 512x512, and a font's
glyphs at most 4M pixels (images are freed after each font). Each sheet is
encoded into a stream capped at the output room left; a font that outgrows
it stays as ItemsAdder wrote it.

Main's largest glyph is 256x256 and its busiest font 1.77M pixels, so its
parts are byte-identical to before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ryanbarlow97

Copy link
Copy Markdown
Contributor Author

Reviewed by GPT-6.1-Sol (xhigh) over five rounds; the final round's verdict was OK with no findings. It checked decoding against LWJGL/stb 3.3.3 on 41,982 PNGs (zero mismatches), bounded memory under -Xmx256m, deterministic output on Main's real pack, and the parts-v2 upgrade. CodeRabbit is rate-limited, so this is merged with --admin.

@ryanbarlow97
ryanbarlow97 merged commit 7703f52 into main Oct 15, 2026
3 checks passed
@ryanbarlow97
ryanbarlow97 deleted the pack-reload-speed branch October 15, 2026 17:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 effective changed lines (tests excluded in mixed PRs).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant