diff --git a/API/src/main/java/fr/maxlego08/zauctionhouse/api/AuctionManager.java b/API/src/main/java/fr/maxlego08/zauctionhouse/api/AuctionManager.java index 71f4da9..f8a8915 100644 --- a/API/src/main/java/fr/maxlego08/zauctionhouse/api/AuctionManager.java +++ b/API/src/main/java/fr/maxlego08/zauctionhouse/api/AuctionManager.java @@ -9,6 +9,7 @@ import fr.maxlego08.zauctionhouse.api.services.AuctionRemoveService; import fr.maxlego08.zauctionhouse.api.services.AuctionSellService; import fr.maxlego08.zauctionhouse.api.cache.PlayerCache; +import it.unimi.dsi.fastutil.ints.IntList; import org.bukkit.entity.Player; import java.util.Comparator; @@ -76,6 +77,15 @@ public interface AuctionManager { */ List getItems(StorageType storageType); + /** + * Resolves cached item identifiers back to their live instances from the storage map. + * + * @param storageType logical container to read from + * @param ids cached identifiers to resolve + * @return resolved items in the same order as provided ids when possible + */ + List resolveItems(StorageType storageType, IntList ids); + /** * Retrieves items from the given storage type and filters them before returning. Filtering is * executed synchronously on the current thread and should remain inexpensive. diff --git a/API/src/main/java/fr/maxlego08/zauctionhouse/api/cache/PlayerCacheKey.java b/API/src/main/java/fr/maxlego08/zauctionhouse/api/cache/PlayerCacheKey.java index 0a2c07e..0bf46e8 100644 --- a/API/src/main/java/fr/maxlego08/zauctionhouse/api/cache/PlayerCacheKey.java +++ b/API/src/main/java/fr/maxlego08/zauctionhouse/api/cache/PlayerCacheKey.java @@ -4,18 +4,18 @@ import fr.maxlego08.zauctionhouse.api.economy.AuctionEconomy; import fr.maxlego08.zauctionhouse.api.item.Item; import fr.maxlego08.zauctionhouse.api.item.SortItem; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; import java.math.BigDecimal; -import java.util.Collections; -import java.util.List; import java.util.function.Supplier; public enum PlayerCacheKey { - ITEMS_LISTED(new TypeToken>() {}, Collections::emptyList), - ITEMS_EXPIRED(new TypeToken>() {}, Collections::emptyList), - ITEMS_PURCHASED(new TypeToken>() {}, Collections::emptyList), - ITEMS_OWNED(new TypeToken>() {}, Collections::emptyList), + ITEMS_LISTED(new TypeToken() {}, IntArrayList::new), + ITEMS_EXPIRED(new TypeToken() {}, IntArrayList::new), + ITEMS_PURCHASED(new TypeToken() {}, IntArrayList::new), + ITEMS_OWNED(new TypeToken() {}, IntArrayList::new), ADMIN_TARGET(new TypeToken() {}, () -> null), ADMIN_TARGET_NAME(new TypeToken() {}, () -> ""), ITEM_SHOW(new TypeToken() {}, () -> null), diff --git a/build.gradle.kts b/build.gradle.kts index 069754b..3b54fbe 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -70,6 +70,7 @@ allprojects { implementation("fr.maxlego08.sarah:sarah:1.20") implementation("com.tcoded:FoliaLib:0.5.1") implementation("fr.traqueur.currencies:currenciesapi:1.0.11") + implementation("it.unimi.dsi:fastutil:8.5.14") } } diff --git a/src/main/java/fr/maxlego08/zauctionhouse/ZAuctionManager.java b/src/main/java/fr/maxlego08/zauctionhouse/ZAuctionManager.java index 02adb37..490a82a 100644 --- a/src/main/java/fr/maxlego08/zauctionhouse/ZAuctionManager.java +++ b/src/main/java/fr/maxlego08/zauctionhouse/ZAuctionManager.java @@ -27,12 +27,18 @@ import fr.maxlego08.zauctionhouse.services.SellService; import fr.maxlego08.zauctionhouse.utils.ZUtils; import fr.maxlego08.zauctionhouse.utils.cache.ZPlayerCache; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import java.util.ArrayList; import java.util.Comparator; import java.util.Date; +import java.util.EnumMap; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -49,7 +55,10 @@ public class ZAuctionManager extends ZUtils implements AuctionManager { private final AuctionExpireService auctionExpireService; private final Map caches = new HashMap<>(); - private final Map> storageItems = new HashMap<>(); + private final Map> storageItemsById = new EnumMap<>(StorageType.class); + private final Map idsListedByOwner = new HashMap<>(); + private final Map idsExpiredByOwner = new HashMap<>(); + private final Map idsPurchasedByBuyer = new HashMap<>(); public ZAuctionManager(AuctionPlugin plugin) { this.plugin = plugin; @@ -59,7 +68,7 @@ public ZAuctionManager(AuctionPlugin plugin) { this.auctionExpireService = new ExpireService(plugin, this); for (StorageType value : StorageType.values()) { - this.storageItems.put(value, new ArrayList<>()); + this.storageItemsById.put(value, new Int2ObjectOpenHashMap<>()); } } @@ -113,92 +122,183 @@ public AuctionExpireService getExpireService() { @Override public List getItems(StorageType storageType) { - return this.storageItems.get(storageType); + return new ArrayList<>(this.storageItemsById.getOrDefault(storageType, Int2ObjectMaps.emptyMap()).values()); } @Override public List getItems(StorageType storageType, Predicate predicate) { - List items = new ArrayList<>(); - - var iterator = getItems(storageType).iterator(); - while (iterator.hasNext()) { - - var item = iterator.next(); - if (item.isExpired()) { - this.auctionExpireService.processExpiredItem(item, storageType); - iterator.remove(); - continue; - } - - if (predicate.test(item)) { - items.add(item); - } - } - return items; + return resolveItems(storageType, getItemIds(storageType, predicate, null)); } @Override public List getItems(StorageType storageType, Predicate predicate, Comparator comparator) { - - List result = getItems(storageType, predicate); - - if (comparator != null && result.size() > 1) { - result.sort(comparator); - } - - return result; + return resolveItems(storageType, getItemIds(storageType, predicate, comparator)); } @Override public void addItem(StorageType storageType, Item item) { - getItems(storageType).add(item); + var storage = this.storageItemsById.get(storageType); + storage.put(item.getId(), item); + this.indexItem(storageType, item); } @Override public void removeItem(StorageType storageType, Item item) { - getItems(storageType).remove(item); + removeItem(storageType, item.getId()); } @Override public void removeItem(StorageType storageType, int itemId) { - getItems(storageType).removeIf(item -> item.getId() == itemId); + var storage = this.storageItemsById.get(storageType); + if (storage == null) return; + + Item removed = storage.remove(itemId); + if (removed != null) { + this.deindexItem(storageType, removed); + } } @Override public List getItemsListedForSale(Player player) { var cache = getCache(player); var sort = cache.get(PlayerCacheKey.ITEM_SORT, this.plugin.getConfiguration().getSort().defaultSort()); - return cache.getOrCompute(PlayerCacheKey.ITEMS_LISTED, () -> getItems(StorageType.LISTED, item -> item.getStatus() == ItemStatus.AVAILABLE, sort.getComparator())); + IntList ids = cache.getOrCompute(PlayerCacheKey.ITEMS_LISTED, () -> getItemIds(StorageType.LISTED, item -> item.getStatus() == ItemStatus.AVAILABLE, sort.getComparator())); + return resolveItems(StorageType.LISTED, ids); } @Override public List getExpiredItems(Player player) { - return getCache(player).getOrCompute(PlayerCacheKey.ITEMS_EXPIRED, () -> getItems(StorageType.EXPIRED, item -> item.getSellerUniqueId().equals(player.getUniqueId()), Comparator.comparing(Item::getExpiredAt))); + IntList ids = getCache(player).getOrCompute(PlayerCacheKey.ITEMS_EXPIRED, () -> getItemIds(StorageType.EXPIRED, item -> item.getSellerUniqueId().equals(player.getUniqueId()), Comparator.comparing(Item::getExpiredAt))); + return resolveItems(StorageType.EXPIRED, ids); } @Override public List getExpiredItems(UUID uniqueId) { - return getItems(StorageType.EXPIRED, item -> item.getSellerUniqueId().equals(uniqueId), Comparator.comparing(Item::getExpiredAt)); + return resolveItems(StorageType.EXPIRED, getItemIds(StorageType.EXPIRED, item -> item.getSellerUniqueId().equals(uniqueId), Comparator.comparing(Item::getExpiredAt))); } @Override public List getPlayerOwnedItems(Player player) { - return getCache(player).getOrCompute(PlayerCacheKey.ITEMS_OWNED, () -> getItems(StorageType.LISTED, item -> item.getSellerUniqueId().equals(player.getUniqueId()), Comparator.comparing(Item::getExpiredAt))); + IntList ids = getCache(player).getOrCompute(PlayerCacheKey.ITEMS_OWNED, () -> getItemIds(StorageType.LISTED, item -> item.getSellerUniqueId().equals(player.getUniqueId()), Comparator.comparing(Item::getExpiredAt))); + return resolveItems(StorageType.LISTED, ids); } @Override public List getPlayerOwnedItems(UUID uniqueId) { - return getItems(StorageType.LISTED, item -> item.getSellerUniqueId().equals(uniqueId), Comparator.comparing(Item::getExpiredAt)); + return resolveItems(StorageType.LISTED, getItemIds(StorageType.LISTED, item -> item.getSellerUniqueId().equals(uniqueId), Comparator.comparing(Item::getExpiredAt))); } @Override public List getPurchasedItems(Player player) { - return getCache(player).getOrCompute(PlayerCacheKey.ITEMS_PURCHASED, () -> getItems(StorageType.PURCHASED, item -> item.getBuyerUniqueId().equals(player.getUniqueId()), Comparator.comparing(Item::getExpiredAt))); + IntList ids = getCache(player).getOrCompute(PlayerCacheKey.ITEMS_PURCHASED, () -> getItemIds(StorageType.PURCHASED, item -> item.getBuyerUniqueId() != null && item.getBuyerUniqueId().equals(player.getUniqueId()), Comparator.comparing(Item::getExpiredAt))); + return resolveItems(StorageType.PURCHASED, ids); } @Override public List getPurchasedItems(UUID uniqueId) { - return getItems(StorageType.PURCHASED, item -> item.getBuyerUniqueId() != null && item.getBuyerUniqueId().equals(uniqueId), Comparator.comparing(Item::getExpiredAt)); + return resolveItems(StorageType.PURCHASED, getItemIds(StorageType.PURCHASED, item -> item.getBuyerUniqueId() != null && item.getBuyerUniqueId().equals(uniqueId), Comparator.comparing(Item::getExpiredAt))); + } + + @Override + public List resolveItems(StorageType storageType, IntList ids) { + if (ids == null || ids.isEmpty()) return List.of(); + + Int2ObjectMap storage = this.storageItemsById.get(storageType); + if (storage == null || storage.isEmpty()) return List.of(); + + List resolved = new ArrayList<>(ids.size()); + for (int id : ids) { + Item item = storage.get(id); + if (item != null) { + resolved.add(item); + } + } + + return resolved; + } + + public List onPlayerOpenMenu(Player player) { + IntList ids = getCache(player).get(PlayerCacheKey.ITEMS_LISTED, new IntArrayList()); + return resolveItems(StorageType.LISTED, ids); + } + + private IntList getItemIds(StorageType storageType, Predicate predicate, Comparator comparator) { + Int2ObjectMap items = this.storageItemsById.get(storageType); + if (items == null || items.isEmpty()) return new IntArrayList(); + + List filtered = new ArrayList<>(); + for (Item item : items.values()) { + if (item.isExpired()) { + this.auctionExpireService.processExpiredItem(item, storageType); + continue; + } + + if (predicate.test(item)) { + filtered.add(item); + } + } + + if (comparator != null && filtered.size() > 1) { + filtered.sort(comparator); + } + + IntList ids = new IntArrayList(filtered.size()); + for (Item item : filtered) { + ids.add(item.getId()); + } + + return ids; + } + + private void indexItem(StorageType storageType, Item item) { + Map index = getIndexFor(storageType); + if (index == null) return; + + UUID owner = getOwner(storageType, item); + addToIndex(index, owner, item.getId()); + } + + private void deindexItem(StorageType storageType, Item item) { + Map index = getIndexFor(storageType); + if (index == null) return; + + UUID owner = getOwner(storageType, item); + removeFromIndex(index, owner, item.getId()); + } + + private Map getIndexFor(StorageType storageType) { + return switch (storageType) { + case LISTED -> this.idsListedByOwner; + case EXPIRED -> this.idsExpiredByOwner; + case PURCHASED -> this.idsPurchasedByBuyer; + default -> null; + }; + } + + private UUID getOwner(StorageType storageType, Item item) { + return switch (storageType) { + case LISTED, EXPIRED -> item.getSellerUniqueId(); + case PURCHASED -> item.getBuyerUniqueId(); + default -> null; + }; + } + + private void addToIndex(Map index, UUID owner, int itemId) { + if (owner == null) return; + + index.computeIfAbsent(owner, uuid -> new IntArrayList()).add(itemId); + } + + private void removeFromIndex(Map index, UUID owner, int itemId) { + if (owner == null) return; + + IntList ids = index.get(owner); + if (ids == null) return; + + ids.rem(itemId); + if (ids.isEmpty()) { + index.remove(owner); + } } @Override @@ -516,8 +616,8 @@ public void updateListedItems(Item item, boolean added, Player ignoredPlayer) { private void removeFromCache(Player player, Item item) { if (this.caches.containsKey(player)) { - List items = this.caches.get(player).get(PlayerCacheKey.ITEMS_LISTED); - items.remove(item); + IntList items = this.caches.get(player).get(PlayerCacheKey.ITEMS_LISTED); + items.rem(item.getId()); } }