diff --git a/eco-api/src/main/java/com/willfp/eco/core/items/HashedItem.java b/eco-api/src/main/java/com/willfp/eco/core/items/HashedItem.java new file mode 100644 index 00000000..79cc4ead --- /dev/null +++ b/eco-api/src/main/java/com/willfp/eco/core/items/HashedItem.java @@ -0,0 +1,102 @@ +package com.willfp.eco.core.items; + +import com.willfp.eco.core.fast.FastItemStack; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +/** + * An item and its {@link com.willfp.eco.core.fast.FastItemStack} hash. + */ +public final class HashedItem { + /** + * The item. + */ + private final ItemStack item; + + /** + * The hash. + */ + private final int hash; + + /** + * Create new hashed item. + * + * @param item The item. + * @param hash The hash. + */ + public HashedItem(@NotNull final ItemStack item, + final int hash) { + this.item = item; + this.hash = hash; + } + + /** + * Get the item. + * + * @return The ItemStack. + */ + public ItemStack getItem() { + return this.item; + } + + /** + * Get the hash. + * + * @return The hash. + */ + public int getHash() { + return this.hash; + } + + /** + * Kotlin destructuring support. + * + * @return The ItemStack. + */ + public ItemStack component1() { + return this.item(); + } + + /** + * Kotlin destructuring support. + * + * @return The hash. + */ + public int component2() { + return this.hash(); + } + + @Override + public int hashCode() { + return this.hash; + } + + @Override + public boolean equals(@Nullable final Object other) { + if (!(other instanceof HashedItem o)) { + return false; + } + + return o.hash == this.hash; + } + + /** + * Hashed item from an item. + * + * @param item The item. + * @return The hashed item. + */ + public static HashedItem of(@NotNull final ItemStack item) { + return new HashedItem(item, FastItemStack.wrap(item).hashCode()); + } + + /** + * Hashed item from a fast item stack. + * + * @param item The item. + * @return The hashed item. + */ + public static HashedItem of(@NotNull final FastItemStack item) { + return new HashedItem(item.unwrap(), item.hashCode()); + } +} diff --git a/eco-api/src/main/java/com/willfp/eco/core/items/Items.java b/eco-api/src/main/java/com/willfp/eco/core/items/Items.java index cb077a42..d4f3a044 100644 --- a/eco-api/src/main/java/com/willfp/eco/core/items/Items.java +++ b/eco-api/src/main/java/com/willfp/eco/core/items/Items.java @@ -1,5 +1,8 @@ package com.willfp.eco.core.items; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; import com.willfp.eco.core.fast.FastItemStack; import com.willfp.eco.core.items.args.LookupArgParser; import com.willfp.eco.core.items.provider.ItemProvider; @@ -25,6 +28,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -38,9 +42,27 @@ public final class Items { private static final Map REGISTRY = new ConcurrentHashMap<>(); /** - * Cached custom item lookups, using {@link FastItemStack#hashCode()}. + * Cached custom item lookups, using {@link HashedItem}. */ - private static final Map> CACHE = new ConcurrentHashMap<>(); + private static final LoadingCache> CACHE = CacheBuilder.newBuilder() + .expireAfterAccess(5, TimeUnit.MINUTES) + .build( + new CacheLoader<>() { + @Override + @NotNull + public Optional load(@NotNull final HashedItem key) { + TestableItem match = null; + for (TestableItem item : REGISTRY.values()) { + if (item.matches(key.item())) { + match = item; + break; + } + } + + return Optional.ofNullable(match); + } + } + ); /** * All item providers. @@ -297,26 +319,7 @@ public final class Items { public static CustomItem getCustomItem(@NotNull final ItemStack itemStack) { int hash = FastItemStack.wrap(itemStack).hashCode(); - if (CACHE.containsKey(hash)) { - return CACHE.get(hash).map(Items::getOrWrap).orElse(null); - } - - TestableItem match = null; - for (TestableItem item : REGISTRY.values()) { - if (item.matches(itemStack)) { - match = item; - break; - } - } - - // Cache even if not matched; allows for marking hashes as definitely not custom. - CACHE.put(hash, Optional.ofNullable(match)); - - if (match == null) { - return null; - } - - return getOrWrap(match); + return CACHE.getUnchecked(HashedItem.of(itemStack)).map(Items::getOrWrap).orElse(null); } /** @@ -350,14 +353,6 @@ public final class Items { } } - /** - * Clear the lookup cache. - */ - @ApiStatus.Internal - public static void clearCache() { - CACHE.clear(); - } - private Items() { throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); } diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/eco/internal/spigot/EcoSpigotPlugin.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/eco/internal/spigot/EcoSpigotPlugin.kt index 441b3cf0..7345bdfe 100644 --- a/eco-core/core-plugin/src/main/kotlin/com/willfp/eco/internal/spigot/EcoSpigotPlugin.kt +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/eco/internal/spigot/EcoSpigotPlugin.kt @@ -204,11 +204,6 @@ abstract class EcoSpigotPlugin : EcoPlugin( CollatedRunnable(this) DropManager.update(this) ProfileSaver(this) - this.scheduler.runTimer( - { Items.clearCache() }, - this.configYml.getInt("item-cache-ttl").toLong(), - this.configYml.getInt("item-cache-ttl").toLong() - ) this.scheduler.runTimer( { clearFrames() }, this.configYml.getInt("display-frame-ttl").toLong(), diff --git a/eco-core/core-plugin/src/main/resources/config.yml b/eco-core/core-plugin/src/main/resources/config.yml index eae724c8..689c7c78 100644 --- a/eco-core/core-plugin/src/main/resources/config.yml +++ b/eco-core/core-plugin/src/main/resources/config.yml @@ -41,11 +41,6 @@ use-display-frame: true # that display frames will be cleared / deleted. display-frame-ttl: 17 -# Time to live for storing hashes against custom item keys. Increasing this value -# can benefit performance under load (e.g. from ShopGUI+ sells) but can increase -# memory use. -item-cache-ttl: 6000 - # The default bukkit NamespacedKey creation can cause decent amounts of lag under # load due to the use of regex validation in the constructor. eco has its own system # to create NamespacedKeys, however it can be unsafe as it skips most validation checks.