From 1f349a22eb8788c52276b7fe675768e794096e5c Mon Sep 17 00:00:00 2001 From: Sage Kummer Date: Wed, 23 Sep 2026 01:48:39 -0400 Subject: [PATCH] Skip matchMaterial's regex for enum names and cache blockdata encodings 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. --- .../net/coreprotect/utility/BlockUtils.java | 114 +++++++++++++----- .../coreprotect/utility/MaterialUtils.java | 14 ++- 2 files changed, 98 insertions(+), 30 deletions(-) diff --git a/src/main/java/net/coreprotect/utility/BlockUtils.java b/src/main/java/net/coreprotect/utility/BlockUtils.java index 9cdbce959..a47230146 100644 --- a/src/main/java/net/coreprotect/utility/BlockUtils.java +++ b/src/main/java/net/coreprotect/utility/BlockUtils.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.bukkit.Material; import org.bukkit.block.Banner; @@ -21,59 +22,114 @@ import net.coreprotect.CoreProtect; import net.coreprotect.bukkit.BukkitAdapter; +import net.coreprotect.config.ConfigHandler; import net.coreprotect.model.PendingBlockChange; import net.coreprotect.thread.Scheduler; public class BlockUtils { + private static final int BLOCK_DATA_CACHE_LIMIT = 4096; + private static volatile BlockDataCache blockDataCache; + private BlockUtils() { throw new IllegalStateException("Utility class"); } public static byte[] stringToByteData(String string, int type) { - byte[] result = null; - if (string != null) { - Material material = MaterialUtils.getType(type); - String blockKey = MaterialUtils.getBlockName(type); - if ((blockKey == null || blockKey.length() == 0) && material != null) { - blockKey = material.getKey().toString(); - } - if (blockKey == null || blockKey.length() == 0) { - return result; - } + if (string == null) { + return null; + } - BlockData defaultBlockData = createBlockData(type); - if (defaultBlockData != null && !defaultBlockData.getAsString().equals(string) && string.startsWith(blockKey + "[") && string.endsWith("]")) { - String substring = string.substring(blockKey.length() + 1, string.length() - 1); - String[] blockDataSplit = substring.split(","); - ArrayList blockDataArray = new ArrayList<>(); - for (String data : blockDataSplit) { - int id = MaterialUtils.getBlockdataId(data, true); - if (id > -1) { - blockDataArray.add(Integer.toString(id)); - } - } - string = String.join(",", blockDataArray); + // ClickHouse uncaches identifiers when a batch is discarded, so a cached encoding could point at an unpublished id + Map cache = ConfigHandler.databaseType.isClickHouse() ? null : blockDataCache(); + if (cache != null) { + CachedBlockData cached = cache.get(string); + if (cached != null && cached.type == type) { + return cached.data; } - else if (material != null && !string.contains(":") && (material == Material.PAINTING || BukkitAdapter.ADAPTER.isItemFrame(material))) { - int id = MaterialUtils.getBlockdataId(string, true); + } + + byte[] result = null; + boolean resolved = true; + Material material = MaterialUtils.getType(type); + String blockKey = MaterialUtils.getBlockName(type); + if ((blockKey == null || blockKey.length() == 0) && material != null) { + blockKey = material.getKey().toString(); + } + if (blockKey == null || blockKey.length() == 0) { + return null; + } + + BlockData defaultBlockData = createBlockData(type); + if (defaultBlockData != null && !defaultBlockData.getAsString().equals(string) && string.startsWith(blockKey + "[") && string.endsWith("]")) { + String substring = string.substring(blockKey.length() + 1, string.length() - 1); + String[] blockDataSplit = substring.split(","); + ArrayList blockDataArray = new ArrayList<>(); + for (String data : blockDataSplit) { + int id = MaterialUtils.getBlockdataId(data, true); if (id > -1) { - string = Integer.toString(id); + blockDataArray.add(Integer.toString(id)); } else { - return result; + resolved = false; } } + result = String.join(",", blockDataArray).getBytes(StandardCharsets.UTF_8); + } + else if (material != null && !string.contains(":") && (material == Material.PAINTING || BukkitAdapter.ADAPTER.isItemFrame(material))) { + int id = MaterialUtils.getBlockdataId(string, true); + if (id > -1) { + result = Integer.toString(id).getBytes(StandardCharsets.UTF_8); + } else { - return result; + resolved = false; } - - result = string.getBytes(StandardCharsets.UTF_8); } + if (cache != null && resolved) { + if (cache.size() >= BLOCK_DATA_CACHE_LIMIT) { + cache.clear(); + } + cache.put(string, new CachedBlockData(type, result)); + } return result; } + // Loading the material or blockdata maps assigns new map instances, which starts a new cache + private static Map blockDataCache() { + BlockDataCache cache = blockDataCache; + Map materials = ConfigHandler.materialsReversed; + Map blockdata = ConfigHandler.blockdata; + if (cache == null || cache.materials != materials || cache.blockdata != blockdata) { + cache = new BlockDataCache(materials, blockdata); + blockDataCache = cache; + } + return cache.entries; + } + + private static final class BlockDataCache { + + private final Map materials; + private final Map blockdata; + private final Map entries = new ConcurrentHashMap<>(); + + private BlockDataCache(Map materials, Map blockdata) { + this.materials = materials; + this.blockdata = blockdata; + } + } + + private static final class CachedBlockData { + + private final int type; + private final byte[] data; + + private CachedBlockData(int type, byte[] data) { + this.type = type; + this.data = data; + } + } + public static String byteDataToString(byte[] data, int type) { String result = ""; if (data != null) { diff --git a/src/main/java/net/coreprotect/utility/MaterialUtils.java b/src/main/java/net/coreprotect/utility/MaterialUtils.java index 7e5c35e03..9c761686b 100644 --- a/src/main/java/net/coreprotect/utility/MaterialUtils.java +++ b/src/main/java/net/coreprotect/utility/MaterialUtils.java @@ -172,12 +172,24 @@ public static Material getType(String name) { } name = net.coreprotect.bukkit.BukkitAdapter.ADAPTER.parseLegacyName(name); - material = Material.matchMaterial(name); + material = isEnumName(name) ? Material.getMaterial(name) : Material.matchMaterial(name); } return material; } + // matchMaterial only uppercases and strips whitespace and non-word characters before getMaterial, none of which changes an A-Z, 0-9 and underscore name + private static boolean isEnumName(String name) { + for (int index = 0; index < name.length(); index++) { + char character = name.charAt(index); + if ((character < 'A' || character > 'Z') && (character < '0' || character > '9') && character != '_') { + return false; + } + } + + return !name.isEmpty(); + } + public static int getArtId(String name, boolean internal) { int id = -1; name = name.toLowerCase(Locale.ROOT).trim();