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();