Skip matchMaterial's regex for enum names and cache blockdata encodings - #1022
Merged
Intelli merged 1 commit intoSep 25, 2026
Merged
Conversation
The consumer ran Material.matchMaterial's two regex passes on names they cannot change, and re-encoded the same blockdata strings into ids on every row. Plain enum names now go straight to getMaterial, and stringToByteData caches its results per string, bounded, reset when the id maps reload and skipped on ClickHouse.
❌ Deploy Preview for coreprotect failed. Why did it fail? →
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two conversions run for nearly every block row on the consumer thread and redo the same work each time: a regex-based material lookup and a blockdata string to id-bytes encoding. This change skips the regex when it cannot change the result, and caches the encoding. It cuts about 1 µs of consumer CPU per row on both SQLite and DuckDB.
The problem
MaterialUtils.getType(String)(utility/MaterialUtils.java:165-179) callsMaterial.matchMaterial. Before looking the name up, that runs two regexreplaceAllpasses: whitespace to underscores, then non-word characters removed. The block loggers call it per row with keys likeSTONEorOAK_PLANKS, for which both passes change nothing.BlockUtils.stringToByteData(utility/BlockUtils.java:33-75) turns a blockdata string such asminecraft:oak_stairs[facing=east,half=bottom]into the comma-separated blockdata ids CoreProtect stores. On every row it builds a defaultBlockDatafor the type and serializes it withgetAsString()for comparison, splits the property list, looks up each property's id, joins them and encodes the result. A server logs the same few hundred states over and over, and the answer only changes when the id maps change.The fix
getType(String)callsMaterial.getMaterial(name)directly when the name contains onlyA-Z,0-9and_. For such namesmatchMaterialreturns exactlygetMaterial(name). Anything else still goes throughmatchMaterial.stringToByteDatakeeps aConcurrentHashMapfrom blockdata string to its type and result, capped at 4,096 entries and cleared when full.Behaviour change
None. The bytes produced for any input are the same as before.
Risk
Testing
Build:
mvn packagepasses.Performance, a consumer benchmark plugin (ConsumerLoad) on Paper 26.2, 2 GB heap. Each round queues 200,000 rows through the API (50%
logPlacement, 30%logRemoval, 20%logInteraction, 10 users) on the main thread, then waits for the consumer to drain. Consumer CPU is the consumer thread's CPU time over the round. One warmup round, then 3 measured rounds, 2 runs of each jar.The spread within each cell is mostly the database growing across the three rounds of one run. DuckDB's drain time does not follow its CPU, because the consumer is not CPU-bound there.
Row parity: a 47-step scenario plugin (blocks, containers, pistons, fluids, dispensers, bone meal, explosions, then rollback and restore) compared order-insensitively against upstream, with each row's stored blockdata bytes compared by hash. Paper 26.2 and Folia 1.21.11 with SQLite matched upstream row for row. Paper 26.2 with DuckDB differed by one far-basin water row whose settling window lands either side of the boundary depending on run pacing. No new errors.