From 484614f71e5185fb92200eb9e2138c868c4759e4 Mon Sep 17 00:00:00 2001 From: Intelli Date: Mon, 10 Mar 2025 19:39:33 -0600 Subject: [PATCH] Improved performance of BlockPreDispenseEvent --- .../net/coreprotect/config/ConfigHandler.java | 2 + .../database/logger/ContainerLogger.java | 47 +++++++++++++++ .../listener/BlockPreDispenseListener.java | 55 ++++++++++++++++- .../net/coreprotect/thread/CacheHandler.java | 59 ++++++++++++++++++- 4 files changed, 161 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/coreprotect/config/ConfigHandler.java b/src/main/java/net/coreprotect/config/ConfigHandler.java index 155095a..2594df3 100644 --- a/src/main/java/net/coreprotect/config/ConfigHandler.java +++ b/src/main/java/net/coreprotect/config/ConfigHandler.java @@ -109,6 +109,8 @@ public class ConfigHandler extends Queue { public static ConcurrentHashMap> itemsBuy = new ConcurrentHashMap<>(); public static ConcurrentHashMap hopperAbort = new ConcurrentHashMap<>(); public static ConcurrentHashMap hopperSuccess = new ConcurrentHashMap<>(); + public static ConcurrentHashMap> dispenserNoChange = new ConcurrentHashMap<>(); + public static ConcurrentHashMap dispenserPending = new ConcurrentHashMap<>(); public static Map> forceContainer = syncMap(); public static Map lookupType = syncMap(); public static Map lookupThrottle = syncMap(); diff --git a/src/main/java/net/coreprotect/database/logger/ContainerLogger.java b/src/main/java/net/coreprotect/database/logger/ContainerLogger.java index 4c81be7..a2e3f83 100644 --- a/src/main/java/net/coreprotect/database/logger/ContainerLogger.java +++ b/src/main/java/net/coreprotect/database/logger/ContainerLogger.java @@ -6,6 +6,7 @@ import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -65,6 +66,52 @@ public class ContainerLogger extends Queue { return; } + // Check if this is a dispenser with no actual changes + if (player.equals("#dispenser") && ItemUtils.compareContainers(oldInventory, newInventory)) { + // No changes detected, mark this dispenser in the dispenserNoChange map + // Extract the location key from the loggingContainerId + // Format: #dispenser.x.y.z + String[] parts = loggingContainerId.split("\\."); + if (parts.length >= 4) { + int x = Integer.parseInt(parts[1]); + int y = Integer.parseInt(parts[2]); + int z = Integer.parseInt(parts[3]); + + // Create the location key + String locationKey = location.getWorld().getUID().toString() + "." + x + "." + y + "." + z; + + // Check if we have pending event details for this dispenser + Object[] pendingEvent = ConfigHandler.dispenserPending.remove(locationKey); + if (pendingEvent != null) { + // We have the exact event details, use them to mark this event as unchanged + String eventKey = (String) pendingEvent[0]; + + // Get or create the inner map for this location + ConfigHandler.dispenserNoChange.computeIfAbsent(locationKey, k -> new ConcurrentHashMap<>()).put(eventKey, System.currentTimeMillis()); + } + } + return; + } + + // If we reach here, the dispenser event resulted in changes + // Remove any pending event for this dispenser + if (player.equals("#dispenser")) { + String[] parts = loggingContainerId.split("\\."); + if (parts.length >= 4) { + int x = Integer.parseInt(parts[1]); + int y = Integer.parseInt(parts[2]); + int z = Integer.parseInt(parts[3]); + + String locationKey = location.getWorld().getUID().toString() + "." + x + "." + y + "." + z; + + // Remove the pending event since it resulted in changes + ConfigHandler.dispenserPending.remove(locationKey); + + // Clear any existing dispenserNoChange entries for this location + ConfigHandler.dispenserNoChange.remove(locationKey); + } + } + List forceList = ConfigHandler.forceContainer.get(loggingContainerId); if (forceList != null) { int forceSize = 0; diff --git a/src/main/java/net/coreprotect/paper/listener/BlockPreDispenseListener.java b/src/main/java/net/coreprotect/paper/listener/BlockPreDispenseListener.java index 24991c9..02ea905 100644 --- a/src/main/java/net/coreprotect/paper/listener/BlockPreDispenseListener.java +++ b/src/main/java/net/coreprotect/paper/listener/BlockPreDispenseListener.java @@ -1,5 +1,7 @@ package net.coreprotect.paper.listener; +import java.util.concurrent.ConcurrentHashMap; + import org.bukkit.Material; import org.bukkit.World; import org.bukkit.block.Block; @@ -13,6 +15,7 @@ import org.bukkit.inventory.ItemStack; import io.papermc.paper.event.block.BlockPreDispenseEvent; import net.coreprotect.config.Config; +import net.coreprotect.config.ConfigHandler; import net.coreprotect.consumer.Queue; import net.coreprotect.listener.player.InventoryChangeListener; @@ -21,6 +24,9 @@ public final class BlockPreDispenseListener extends Queue implements Listener { public static boolean useBlockPreDispenseEvent = true; public static boolean useForDroppers = false; + // Maximum time to keep entries in the cache (in milliseconds) + private static final long CACHE_EXPIRY_TIME = 5000; // 5 seconds + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onBlockPreDispense(BlockPreDispenseEvent event) { Block block = event.getBlock(); @@ -35,10 +41,57 @@ public final class BlockPreDispenseListener extends Queue implements Listener { useForDroppers = true; } + // Safeguard against null items + ItemStack item = event.getItemStack(); + if (item == null) { + return; + } + + // Create a basic location key for this dispenser + String locationKey = block.getWorld().getUID().toString() + "." + block.getX() + "." + block.getY() + "." + block.getZ(); + + // Create a detailed event key that includes item details + String eventKey = event.getSlot() + "." + item.getType().name() + ":" + item.getAmount(); + + // Add metadata hash if available + if (item.hasItemMeta()) { + try { + eventKey += ":" + item.getItemMeta().hashCode(); + } + catch (Exception e) { + // If we can't get metadata hash, just use the basic key + } + } + + // Get or create the inner map for this location + ConcurrentHashMap locationMap = ConfigHandler.dispenserNoChange.computeIfAbsent(locationKey, k -> new ConcurrentHashMap<>()); + + // Check if this specific dispenser event has been marked as having no changes recently + Long lastNoChangeTime = locationMap.get(eventKey); + + long currentTime = System.currentTimeMillis(); + if (lastNoChangeTime != null && (currentTime - lastNoChangeTime) < CACHE_EXPIRY_TIME) { + // This specific dispenser event was recently processed and had no changes + // Update the timestamp to extend the skip period + locationMap.put(eventKey, currentTime); + return; + } + + // This is a new or changed event + // Clear any existing dispenserNoChange entries for this location + ConfigHandler.dispenserNoChange.remove(locationKey); + + // Store the event details for ContainerLogger to use + ConfigHandler.dispenserPending.put(locationKey, new Object[] { eventKey, // The detailed event key + currentTime, // Timestamp + event.getSlot(), // Slot + item.clone() // Item (cloned to prevent modification) + }); + + // Process the inventory transaction String user = "#dispenser"; ItemStack[] inventory = ((InventoryHolder) block.getState()).getInventory().getStorageContents(); InventoryChangeListener.inventoryTransaction(user, block.getLocation(), inventory); } } - } diff --git a/src/main/java/net/coreprotect/thread/CacheHandler.java b/src/main/java/net/coreprotect/thread/CacheHandler.java index 45dbe0b..70bb55e 100755 --- a/src/main/java/net/coreprotect/thread/CacheHandler.java +++ b/src/main/java/net/coreprotect/thread/CacheHandler.java @@ -26,7 +26,7 @@ public class CacheHandler implements Runnable { public void run() { while (ConfigHandler.serverRunning) { try { - for (int id = 0; id < 8; id++) { + for (int id = 0; id < 9; id++) { Thread.sleep(1000); int scanTime = 30; Map cache = CacheHandler.lookupCache; @@ -59,6 +59,10 @@ public class CacheHandler implements Runnable { cache = ConfigHandler.entityBlockMapper; scanTime = 5; break; + case 8: + // Clean up dispenserNoChange cache + cleanupDispenserCache(); + continue; } int timestamp = (int) (System.currentTimeMillis() / 1000L) - scanTime; @@ -88,4 +92,57 @@ public class CacheHandler implements Runnable { } } } + + /** + * Cleans up the dispenserNoChange cache by removing entries older than 5 seconds + */ + private void cleanupDispenserCache() { + try { + long currentTime = System.currentTimeMillis(); + long expiryTime = 5000; // 5 seconds + + // Clean up dispenserNoChange map (now a nested map) + Iterator>> locationIterator = ConfigHandler.dispenserNoChange.entrySet().iterator(); + while (locationIterator.hasNext()) { + Entry> locationEntry = locationIterator.next(); + ConcurrentHashMap eventMap = locationEntry.getValue(); + + if (eventMap.isEmpty()) { + // Remove empty location entries + locationIterator.remove(); + continue; + } + + // Clean up expired events within this location + Iterator> eventIterator = eventMap.entrySet().iterator(); + while (eventIterator.hasNext()) { + Entry eventEntry = eventIterator.next(); + if ((currentTime - eventEntry.getValue()) > expiryTime) { + eventIterator.remove(); + } + } + + // If all events were removed, remove the location entry + if (eventMap.isEmpty()) { + locationIterator.remove(); + } + } + + // Clean up dispenserPending map + Iterator> pendingIterator = ConfigHandler.dispenserPending.entrySet().iterator(); + while (pendingIterator.hasNext()) { + Entry entry = pendingIterator.next(); + Object[] data = entry.getValue(); + if (data != null && data.length > 1) { + long timestamp = (long) data[1]; + if ((currentTime - timestamp) > expiryTime) { + pendingIterator.remove(); + } + } + } + } + catch (Exception e) { + e.printStackTrace(); + } + } }