Skip to content

Performance and thread-safety improvements#100

Open
GG-MD wants to merge 10 commits into
Zrips:mainfrom
GG-MD:perf/optimizations
Open

Performance and thread-safety improvements#100
GG-MD wants to merge 10 commits into
Zrips:mainfrom
GG-MD:perf/optimizations

Conversation

@GG-MD

@GG-MD GG-MD commented Jul 19, 2026

Copy link
Copy Markdown

Hi, @Zrips!

Performance:

  • Lazy NBT tag and no duplicate ItemMeta reads - avoids a full item codec serialization on every NBT access on 1.20.5+
  • Cache compiled regex patterns, reuse StringBuilder/Matcher and ThreadLocalRandom across text/color/placeholder hot paths
  • Fetch the chunk/block once and drop Location.clone() allocations in cuboid scans
  • Cache enum values() arrays in CMIBlock direction/stair lookups

Fixes:

  • CMIBlock.getInventory no longer leaks force-loaded chunks (setForceLoaded was never cleared)
  • CMIMaterial.get(int) returned NONE instead of the material it found
  • Skin/image caches are thread-safe now (ConcurrentHashMap) and all Mojang/minotar/minepic requests get connect/read timeouts
  • Fixed a race in CMIFutureBatcher that could leave a queued future unprocessed
  • Permission cache is thread-safe and cleared on quit instead of growing forever
  • Advancement revoke runs on the player's own region so it works on Folia
  • Clamp GUI button auto-update interval to avoid a scheduler crash when <= 0

Each change is its own commit. Compiles clean, no behavior changes.

@mrfloris

Copy link
Copy Markdown
Contributor
// Before
public HashMap<UUID, CMISkin> skinCacheByUUID;

// After
public ConcurrentHashMap<UUID, CMISkin> skinCacheByUUID;

Although this looks like a compatible implementation improvement, Java bytecode identifies a field using all three of: owning class + field name + exact declared type
so now SkinManager.skinCacheByUUID : java.util.HashMap is SkinManager.skinCacheByUUID : java.util.concurrent.ConcurrentHashMap, meaning people who replace the jar but have plugins compiled previously against it have to update and recompile their addons.

The two public fields and protected preFetchNames are affected. Simply declaring them as Map would also be binary-incompatible.

and..

The permission cache now has a concurrent outer map, but non-concurrent inner maps:
ConcurrentHashMap<UUID, HashMap<String, PermissionInfo>>
The unsafe operation now seems to be

HashMap<String, PermissionInfo> playerCache = cache.get(uuid);

if (playerCache == null) {
    playerCache = new HashMap<>();
}

playerCache.put(permission, result);
cache.put(uuid, playerCache);

and..

The PR converts three maps to ConcurrentHashMap, but preFetchUUIDs remains a HashMap. The lookup uses a compound sequence:

if (!preFetchUUIDS().containsKey(name)) {
    UUID uuid = getUUID(name); // remote request
    preFetchUUIDS().put(name, uuid);
}

The usual fix is one canonical concurrent cache plus an atomic per-key loading mechanism, often storing a CompletableFuture<CMISkin> so simultaneous requests share one lookup. Failed lookups need special handling because ConcurrentHashMap cannot store null.

Sorry if i am wrong.

@NeglectDream

Copy link
Copy Markdown

Would appreciate it if you could address this minor issue in the current PR.
#79

@GG-MD

GG-MD commented Jul 19, 2026

Copy link
Copy Markdown
Author
// Before
public HashMap<UUID, CMISkin> skinCacheByUUID;

// After
public ConcurrentHashMap<UUID, CMISkin> skinCacheByUUID;

Although this looks like a compatible implementation improvement, Java bytecode identifies a field using all three of: owning class + field name + exact declared type so now SkinManager.skinCacheByUUID : java.util.HashMap is SkinManager.skinCacheByUUID : java.util.concurrent.ConcurrentHashMap, meaning people who replace the jar but have plugins compiled previously against it have to update and recompile their addons.

The two public fields and protected preFetchNames are affected. Simply declaring them as Map would also be binary-incompatible.

and..

The permission cache now has a concurrent outer map, but non-concurrent inner maps: ConcurrentHashMap<UUID, HashMap<String, PermissionInfo>> The unsafe operation now seems to be

HashMap<String, PermissionInfo> playerCache = cache.get(uuid);

if (playerCache == null) {
    playerCache = new HashMap<>();
}

playerCache.put(permission, result);
cache.put(uuid, playerCache);

and..

The PR converts three maps to ConcurrentHashMap, but preFetchUUIDs remains a HashMap. The lookup uses a compound sequence:

if (!preFetchUUIDS().containsKey(name)) {
    UUID uuid = getUUID(name); // remote request
    preFetchUUIDS().put(name, uuid);
}

The usual fix is one canonical concurrent cache plus an atomic per-key loading mechanism, often storing a CompletableFuture<CMISkin> so simultaneous requests share one lookup. Failed lookups need special handling because ConcurrentHashMap cannot store null.

Sorry if i am wrong.

Thanks for the detailed review, you're right on all three.

  1. Binary compatibility - reverted skinCacheByUUID, skinCacheByName and preFetchNames back to HashMap so the public/protected field types stay unchanged. Dropped the skin-cache thread-safety from this PR, it's not worth breaking the ABI for downstream plugins. Kept only the HTTP connect/read timeouts there.

  2. Permission cache - fixed properly: the inner maps are ConcurrentHashMap now and get-or-create goes through computeIfAbsent, so the read-modify-write is atomic and the lost-update race is gone.

  3. preFetchUUIDs - moot now that the skin caches are back to HashMap, so the maps are consistent again.

Pushed the two fixes as separate commits.

@mrfloris

Copy link
Copy Markdown
Contributor

Would appreciate it if you could address this minor issue in the current PR. #79

please stop replying to discord msgs and things like this - you got your own issue ticket, you're constantly asking all over the place to get attention. this is not the way to do it.

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.

3 participants