Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public class SpawnerStorageAction implements Listener {
private final MessageService messageService;
private final FilterConfigUI filterConfigUI;
private final SpawnerManager spawnerManager;
private final SpawnerStorageUI spawnerStorageUI;

private static final int INVENTORY_SIZE = 54;
private static final int STORAGE_SLOTS = 45;
Expand All @@ -59,6 +60,7 @@ public SpawnerStorageAction(SmartSpawner plugin) {
this.messageService = plugin.getMessageService();
this.filterConfigUI = plugin.getFilterConfigUI();
this.spawnerManager = plugin.getSpawnerManager();
this.spawnerStorageUI = plugin.getSpawnerStorageUI();
loadConfig();
}

Expand All @@ -69,8 +71,12 @@ public void loadConfig() {

@EventHandler(priority = EventPriority.LOWEST)
public void onInventoryClick(InventoryClickEvent event) {
if (!(event.getWhoClicked() instanceof Player player) ||
!(event.getInventory().getHolder(false) instanceof StoragePageHolder holder)) {
if (!(event.getWhoClicked() instanceof Player player)) {
return;
}

StoragePageHolder holder = spawnerStorageUI.getStorageHolder(event.getInventory());
if (holder == null) {
return;
}

Expand Down Expand Up @@ -252,7 +258,7 @@ private boolean handleCollectExpAction(Player player, SpawnerData spawner, Inven
boolean collected = plugin.getSpawnerMenuAction().tryCollectExpForPlayer(player, spawner);
if (collected) {
// Refresh button display so the XP counter updates to 0
StoragePageHolder holder = (StoragePageHolder) inventory.getHolder(false);
StoragePageHolder holder = spawnerStorageUI.getStorageHolder(inventory);
if (holder != null) {
plugin.getSpawnerStorageUI().updateDisplay(inventory, spawner, holder.getCurrentPage(), holder.getTotalPages());
}
Expand Down Expand Up @@ -434,7 +440,7 @@ private void updatePageAfterRemoval(Player player, Inventory inventory,
}

private boolean handleDropPageItems(Player player, SpawnerData spawner, Inventory inventory) {
StoragePageHolder holder = (StoragePageHolder) inventory.getHolder(false);
StoragePageHolder holder = spawnerStorageUI.getStorageHolder(inventory);
if (holder == null) {
return false;
}
Expand Down Expand Up @@ -546,11 +552,13 @@ private void openFilterConfig(Player player, SpawnerData spawner) {

private void updatePageContent(Player player, SpawnerData spawner, int newPage, Inventory inventory) {
SpawnerStorageUI spawnerStorageUI = plugin.getSpawnerStorageUI();
StoragePageHolder holder = (StoragePageHolder) inventory.getHolder(false);
StoragePageHolder holder = spawnerStorageUI.getStorageHolder(inventory);
if (holder == null) {
return;
}

int totalPages = calculateTotalPages(spawner);

assert holder != null;
holder.setTotalPages(totalPages);
holder.setCurrentPage(newPage);
holder.updateOldUsedSlots();
Expand Down Expand Up @@ -680,7 +688,7 @@ private boolean handleSortItemsClick(Player player, SpawnerData spawner, Invento
spawner.getVirtualInventory().sortItems(nextSort);

// Update GUI display to reflect VirtualInventory state
StoragePageHolder holder = (StoragePageHolder) inventory.getHolder(false);
StoragePageHolder holder = spawnerStorageUI.getStorageHolder(inventory);
if (holder != null) {
updatePageContent(player, spawner, holder.getCurrentPage(), inventory);
}
Expand Down Expand Up @@ -719,7 +727,10 @@ private void openLootPage(Player player, SpawnerData spawner, int page) {
}

public boolean handleTakeAllItems(Player player, Inventory sourceInventory) {
StoragePageHolder holder = (StoragePageHolder) sourceInventory.getHolder(false);
StoragePageHolder holder = spawnerStorageUI.getStorageHolder(sourceInventory);
if (holder == null) {
return false;
}
SpawnerData spawner = holder.getSpawnerData();
VirtualInventory virtualInv = spawner.getVirtualInventory();

Expand All @@ -745,7 +756,7 @@ public boolean handleTakeAllItems(Player player, Inventory sourceInventory) {
}

// Transfer items and update VirtualInventory
TransferResult result = transferItems(player, sourceInventory, sourceItems, virtualInv);
TransferResult result = transferItems(player, sourceInventory, sourceItems, virtualInv, holder);
sendTransferMessage(player, result);
player.updateInventory();

Expand Down Expand Up @@ -803,7 +814,8 @@ private void playActionResult(
}

private TransferResult transferItems(Player player, Inventory sourceInventory,
Map<Integer, ItemStack> sourceItems, VirtualInventory virtualInv) {
Map<Integer, ItemStack> sourceItems, VirtualInventory virtualInv,
StoragePageHolder holder) {
boolean anyItemMoved = false;
boolean inventoryFull = false;
PlayerInventory playerInv = player.getInventory();
Expand Down Expand Up @@ -864,7 +876,6 @@ else if (targetItem.isSimilar(itemToMove)) {

// Update VirtualInventory
if (!itemsToRemove.isEmpty()) {
StoragePageHolder holder = (StoragePageHolder) sourceInventory.getHolder(false);
SpawnerData spawnerData = holder.getSpawnerData();

spawnerData.removeItemsAndUpdateSellValue(itemsToRemove);
Expand All @@ -886,7 +897,7 @@ private void sendTransferMessage(Player player, TransferResult result) {

@EventHandler
public void onInventoryDrag(InventoryDragEvent event) {
if (!(event.getInventory().getHolder(false) instanceof StoragePageHolder)) {
if (spawnerStorageUI.getStorageHolder(event.getInventory()) == null) {
return;
}
event.setCancelled(true);
Expand All @@ -895,14 +906,11 @@ public void onInventoryDrag(InventoryDragEvent event) {
@EventHandler
public void onInventoryClose(InventoryCloseEvent event) {
Inventory inventory = event.getInventory();
if (!(inventory.getHolder(false) instanceof StoragePageHolder holder)) {
StoragePageHolder holder = spawnerStorageUI.unregisterStorageInventory(inventory);
if (holder == null) {
return;
}

// Inventory close events already execute on the owning player's region thread.
// Do not defer this work to the player's scheduler: after closing, a block-backed
// inventory could belong to a different region and resolving its holder there
// violates Folia's thread ownership rules.
handleInventoryClose(holder);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public class SpawnerStorageUI {

// Precomputed buttons to avoid repeated creation
private final Map<String, ItemStack> staticButtons;
private final Map<Inventory, StoragePageHolder> storageInventories;

// Lightweight caches with better eviction strategies
private final Map<String, ItemStack> navigationButtonCache;
Expand All @@ -59,6 +60,7 @@ public SpawnerStorageUI(SmartSpawner plugin) {

// Initialize caches with appropriate initial capacity
this.staticButtons = new ConcurrentHashMap<>(16);
this.storageInventories = Collections.synchronizedMap(new IdentityHashMap<>());
this.navigationButtonCache = new ConcurrentHashMap<>(16);
this.pageIndicatorCache = new ConcurrentHashMap<>(16);

Expand Down Expand Up @@ -209,17 +211,38 @@ public Inventory createStorageInventory(Player player, SpawnerData spawner, int
GuiLayout layout = layoutConfig.getStorageLayout(spawner, player);

// Create inventory with title including page info using placeholder-based format
StoragePageHolder holder = new StoragePageHolder(spawner, page, totalPages, layout);
Inventory pageInv = Bukkit.createInventory(
new StoragePageHolder(spawner, page, totalPages, layout),
holder,
INVENTORY_SIZE,
getStorageTitle(spawner, page, totalPages)
);
storageInventories.put(pageInv, holder);

// Populate the inventory
updateDisplay(pageInv, spawner, page, totalPages);
// Populate the inventory. If construction fails, do not retain a
// registry entry for an inventory that will never be opened.
try {
updateDisplay(pageInv, spawner, page, totalPages);
} catch (RuntimeException | Error failure) {
storageInventories.remove(pageInv);
throw failure;
}
return pageInv;
}

/**
* Resolves holders only for virtual storage inventories created by this UI.
* This deliberately avoids Inventory#getHolder(), which may access a block
* inventory in a region not owned by the current Folia thread.
*/
public StoragePageHolder getStorageHolder(Inventory inventory) {
return storageInventories.get(inventory);
}

public StoragePageHolder unregisterStorageInventory(Inventory inventory) {
return storageInventories.remove(inventory);
}

public void updateDisplay(Inventory inventory, SpawnerData spawner, int page, int totalPages) {
if (!spawner.getInventoryLock().tryLock()) {
if (plugin.isDebugMode()) {
Expand All @@ -242,8 +265,11 @@ public void updateDisplay(Inventory inventory, SpawnerData spawner, int page, in
}

// Also mark all button slots for potential clearing (fixes visual bug where buttons remain when they shouldn't)
StoragePageHolder holder = (StoragePageHolder) inventory.getHolder(false);
assert holder != null;
StoragePageHolder holder = getStorageHolder(inventory);
if (holder == null) {
spawner.getInventoryLock().unlock();
return;
}
GuiLayout layout = holder.getLayout();
if (layout != null) {
slotsToEmpty.addAll(layout.getUsedSlots());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import github.nighter.smartspawner.spawner.gui.synchronization.services.TimerUpdateService;
import github.nighter.smartspawner.spawner.properties.SpawnerData;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;

Expand Down Expand Up @@ -204,36 +203,39 @@ public void updateSpawnerMenuViewers(SpawnerData spawner) {
continue;
}

// Schedule batched GUI update for main menu viewers
guiUpdateService.scheduleUpdate(viewerId, GuiUpdateService.UPDATE_ALL);
ViewerTrackingManager.ViewerInfo viewerInfo = viewerTrackingManager.getViewerInfo(viewerId);
if (viewerInfo == null) {
continue;
}

// Handle storage page updates - calculate pages on the correct thread
Inventory openInv = viewer.getOpenInventory().getTopInventory();
if (openInv.getHolder(false) instanceof StoragePageHolder holder) {
// Schedule storage update - page calculation happens inside
Location loc = viewer.getLocation();
if (viewerInfo.getViewerType() == ViewerTrackingManager.ViewerType.MAIN_MENU) {
guiUpdateService.scheduleUpdate(viewerId, GuiUpdateService.UPDATE_ALL);
continue;
}
if (viewerInfo.getViewerType() != ViewerTrackingManager.ViewerType.STORAGE) {
continue;
}

if (!holder.getSpawnerData().getSpawnerId().equals(spawner.getSpawnerId())) {
continue;
// Entity schedulers follow players across region changes. Resolve the
// inventory only after the task owns the player, then use our registry
// rather than querying a potentially block-backed inventory holder.
Scheduler.runEntityTask(viewer, () -> {
if (!viewer.isOnline()) {
return;
}
Scheduler.runLocationTask(loc, () -> {
if (!viewer.isOnline()) {
return;
}

Inventory inv = viewer.getOpenInventory().getTopInventory();
if (inv.getHolder(false) instanceof StoragePageHolder spHolder) {

if (!spHolder.getSpawnerData().getSpawnerId().equals(spawner.getSpawnerId())) {
return;
}
int oldPages = storageUpdateService.calculateTotalPages(holder.getOldUsedSlots());
int newPages = storageUpdateService.calculateTotalPages(spawner.getVirtualInventory().getUsedSlots());
Inventory inv = viewer.getOpenInventory().getTopInventory();
StoragePageHolder holder = plugin.getSpawnerStorageUI().getStorageHolder(inv);
if (holder == null ||
!holder.getSpawnerData().getSpawnerId().equals(spawner.getSpawnerId())) {
return;
}

storageUpdateService.processStorageUpdateDirect(viewer, inv, spawner, spHolder, oldPages, newPages);
}
});
}
int oldPages = storageUpdateService.calculateTotalPages(holder.getOldUsedSlots());
int newPages = storageUpdateService.calculateTotalPages(spawner.getVirtualInventory().getUsedSlots());
storageUpdateService.processStorageUpdateDirect(
viewer, inv, spawner, holder, oldPages, newPages);
});
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import github.nighter.smartspawner.spawner.gui.storage.SpawnerStorageUI;
import github.nighter.smartspawner.spawner.gui.storage.StoragePageHolder;
import github.nighter.smartspawner.spawner.properties.SpawnerData;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;

Expand Down Expand Up @@ -58,22 +57,19 @@ public void reloadCache() {
* @param newTotalPages New total pages
*/
public void processStorageUpdate(Player viewer, SpawnerData spawner, int oldTotalPages, int newTotalPages) {
Location loc = viewer.getLocation();
if (loc != null) {
Scheduler.runLocationTask(loc, () -> {
if (!viewer.isOnline()) {
return;
}

Inventory openInv = viewer.getOpenInventory().getTopInventory();
if (openInv == null || !(openInv.getHolder(false) instanceof StoragePageHolder)) {
return;
}

StoragePageHolder holder = (StoragePageHolder) openInv.getHolder(false);
processStorageUpdateDirect(viewer, openInv, spawner, holder, oldTotalPages, newTotalPages);
});
}
Scheduler.runEntityTask(viewer, () -> {
if (!viewer.isOnline()) {
return;
}

Inventory openInv = viewer.getOpenInventory().getTopInventory();
StoragePageHolder holder = spawnerStorageUI.getStorageHolder(openInv);
if (holder == null) {
return;
}

processStorageUpdateDirect(viewer, openInv, spawner, holder, oldTotalPages, newTotalPages);
});
}

/**
Expand Down
Loading