Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
21b2603
Improve lootgen and add cached Config class
RVSkeLe May 5, 2026
e9480a3
Use fastutil
RVSkeLe May 11, 2026
7e663a7
Other fastutil stuff
RVSkeLe May 12, 2026
8f17604
Scary inventory optimization
RVSkeLe May 12, 2026
c7e398e
Dumb sort button caching
RVSkeLe May 12, 2026
2daa759
Remove signatureCache as caching was actually slower
RVSkeLe May 12, 2026
f1513cc
Cleanup for the whole display rewrite
RVSkeLe May 12, 2026
19f51d2
Start optimizing createButton itself
RVSkeLe May 12, 2026
2981565
Make LRUCache a Guava wrapper and use it in SortButton
RVSkeLe May 13, 2026
681501d
Optimize the UI further
RVSkeLe May 13, 2026
64f3250
Improve ItemSignature by caching the Material
RVSkeLe May 13, 2026
73b6b3d
Improve start times and addItems logic
RVSkeLe May 13, 2026
31adc45
LinkedHashMap is better
RVSkeLe May 14, 2026
916d184
Optimize navigation buttons
RVSkeLe May 14, 2026
70991ab
Do not check mappingFunction
RVSkeLe May 14, 2026
bfd09f8
Improve hashCode and equals in SortButtonCacheKey
RVSkeLe May 14, 2026
1a9e61d
Add -DEV to the version
RVSkeLe May 14, 2026
2d46fca
Very scary: Completely remove physical ItemStacks in the lootgen logic
RVSkeLe May 14, 2026
da1dc86
Improve ItemSignature
RVSkeLe May 14, 2026
f5fea85
Merge remote-tracking branch 'origin/main' into perf/improve-lootgen
RVSkeLe May 28, 2026
020e836
Remove leftover comment in ItemSignature
RVSkeLe May 28, 2026
b22a2bc
Less diff
RVSkeLe May 28, 2026
3118b5b
Merge remote-tracking branch 'origin/main' into perf/improve-lootgen
RVSkeLe May 29, 2026
d1e8d82
Make everything ItemSignature, Long
RVSkeLe Jun 27, 2026
838fc14
Merge remote-tracking branch 'origin/main' into perf/improve-lootgen
RVSkeLe Jun 27, 2026
a22c622
Fix wrong merge
RVSkeLe Jun 27, 2026
024f4e6
Sync selling!
RVSkeLe Jun 27, 2026
d980c35
Improve isSmartSpawner and getItemSpawnerMaterial
RVSkeLe Jul 7, 2026
7a8ad0c
Merge branch 'main' into perf/improve-lootgen
RVSkeLe Aug 4, 2026
b0850ac
Merge remote-tracking branch 'origin/main' into perf/improve-lootgen
RVSkeLe Aug 4, 2026
9fe4405
Avoid unnecessary Folia scheduling when already on the correct thread…
RVSkeLe Aug 7, 2026
d8ff5e1
Improve the cooldown in the UI
RVSkeLe Aug 8, 2026
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
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ allprojects {
apply(plugin = "maven-publish")

group = "github.nighter"
version = "1.7.1.2"
version = "1.7.1.2-DEV"

repositories {
mavenCentral()
Expand Down
67 changes: 20 additions & 47 deletions core/src/main/java/github/nighter/smartspawner/Scheduler.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ public final class Scheduler {
*/
public static Task runTask(Runnable runnable) {
if (isFolia) {
if (Bukkit.isGlobalTickThread()) {
runnable.run();
return new Task(null);
}

try {
io.papermc.paper.threadedregions.scheduler.ScheduledTask task =
Bukkit.getGlobalRegionScheduler().run(plugin, scheduledTask -> runnable.run());
Expand Down Expand Up @@ -192,6 +197,11 @@ public static Task runTaskTimerAsync(Runnable runnable, long delayTicks, long pe
*/
public static Task runEntityTask(Entity entity, Runnable runnable) {
if (isFolia && entity != null) {
if (Bukkit.isOwnedByCurrentRegion(entity)) {
runnable.run();
return new Task(null);
}

try {
io.papermc.paper.threadedregions.scheduler.ScheduledTask task =
entity.getScheduler().run(plugin, scheduledTask -> runnable.run(), null);
Expand Down Expand Up @@ -264,6 +274,11 @@ public static Task runEntityTaskTimer(Entity entity, Runnable runnable, long del
*/
public static Task runLocationTask(Location location, Runnable runnable) {
if (isFolia && location != null && location.getWorld() != null) {
if (Bukkit.isOwnedByCurrentRegion(location)) {
runnable.run();
return new Task(null);
}

try {
io.papermc.paper.threadedregions.scheduler.ScheduledTask task =
Bukkit.getRegionScheduler().run(plugin, location, scheduledTask -> runnable.run());
Expand All @@ -290,6 +305,11 @@ public static Task runLocationTask(Location location, Runnable runnable) {
*/
public static Task runChunkTask(World world, int chunkX, int chunkZ, Runnable runnable) {
if (isFolia && world != null) {
if (Bukkit.isOwnedByCurrentRegion(world, chunkX, chunkZ)) {
runnable.run();
return new Task(null);
}

try {
io.papermc.paper.threadedregions.scheduler.ScheduledTask task =
Bukkit.getRegionScheduler().run(plugin, world, chunkX, chunkZ, scheduledTask -> runnable.run());
Expand Down Expand Up @@ -352,53 +372,6 @@ public static Task runLocationTaskTimer(Location location, Runnable runnable, lo
}
}

/**
* Runs a task in the region of a specific location in a world.
* Falls back to regular scheduling on non-Folia servers.
*
* @param location The location in whose region to run the task
* @param runnable The task to run
* @return A Task object representing the scheduled task
*/
public static Task runWorldTask(Location location, Runnable runnable) {
if (isFolia && location != null && location.getWorld() != null) {
try {
io.papermc.paper.threadedregions.scheduler.ScheduledTask task =
Bukkit.getRegionScheduler().run(plugin, location, scheduledTask -> runnable.run());
return new Task(task);
} catch (Exception e) {
plugin.getLogger().log(Level.WARNING, "Error scheduling world task in Folia, falling back to global scheduler", e);
return runTask(runnable);
}
} else {
return runTask(runnable);
}
}

/**
* Runs a delayed task in the region of a specific location in a world.
*
* @param location The location in whose region to run the task
* @param runnable The task to run
* @param delayTicks The delay in ticks before running the task
* @return A Task object representing the scheduled task
*/
public static Task runWorldTaskLater(Location location, Runnable runnable, long delayTicks) {
if (isFolia && location != null && location.getWorld() != null) {
try {
io.papermc.paper.threadedregions.scheduler.ScheduledTask task =
Bukkit.getRegionScheduler().runDelayed(plugin, location, scheduledTask -> runnable.run(),
delayTicks < 1 ? 1 : delayTicks);
return new Task(task);
} catch (Exception e) {
plugin.getLogger().log(Level.WARNING, "Error scheduling delayed world task in Folia, falling back to global scheduler", e);
return runTaskLater(runnable, delayTicks);
}
} else {
return runTaskLater(runnable, delayTicks);
}
}

/**
* Creates a CompletableFuture that will be completed on the main thread or global region.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ public class SmartSpawnerAPIImpl implements SmartSpawnerAPI {
private final SpawnerManager spawnerManager;
private final SpawnerRemovalService spawnerRemovalService;
private final GuiLayoutRegistryImpl guiLayoutRegistry;
private final NamespacedKey vanillaSpawnerKey;
private final NamespacedKey itemSpawnerMaterialKey;
private volatile SpawnerGuiLayoutProvider spawnerGuiLayoutProvider;

public SmartSpawnerAPIImpl(SmartSpawner plugin) {
Expand All @@ -44,6 +46,8 @@ public SmartSpawnerAPIImpl(SmartSpawner plugin) {
this.spawnerManager = plugin.getSpawnerManager();
this.spawnerRemovalService = plugin.getSpawnerRemovalService();
this.guiLayoutRegistry = plugin.getGuiLayoutRegistry();
this.vanillaSpawnerKey = new NamespacedKey(plugin, "vanilla_spawner");
this.itemSpawnerMaterialKey = new NamespacedKey(plugin, "item_spawner_material");
}

@Override
Expand Down Expand Up @@ -78,49 +82,30 @@ public ItemStack createItemSpawnerItem(Material itemMaterial, int amount) {

@Override
public boolean isSmartSpawner(ItemStack item) {
if (item == null || item.getType() != Material.SPAWNER || !item.hasItemMeta()) {
return false;
}

ItemMeta meta = item.getItemMeta();
if (meta == null) {
if (item == null || item.getType() != Material.SPAWNER) {
return false;
}

// A SmartSpawner is a spawner that is NOT vanilla and NOT an item spawner
return !isVanillaSpawner(item) && !isItemSpawner(item);
return !hasVanillaSpawnerKey(item) && !hasItemSpawnerKey(item);
}

@Override
public boolean isVanillaSpawner(ItemStack item) {
if (item == null || item.getType() != Material.SPAWNER || !item.hasItemMeta()) {
if (item == null || item.getType() != Material.SPAWNER) {
return false;
}

ItemMeta meta = item.getItemMeta();
if (meta == null) {
return false;
}

return meta.getPersistentDataContainer().has(
new org.bukkit.NamespacedKey(plugin, "vanilla_spawner"),
org.bukkit.persistence.PersistentDataType.BOOLEAN);
return hasVanillaSpawnerKey(item);
}

@Override
public boolean isItemSpawner(ItemStack item) {
if (item == null || item.getType() != Material.SPAWNER || !item.hasItemMeta()) {
if (item == null || item.getType() != Material.SPAWNER) {
return false;
}

ItemMeta meta = item.getItemMeta();
if (meta == null) {
return false;
}

return meta.getPersistentDataContainer().has(
new org.bukkit.NamespacedKey(plugin, "item_spawner_material"),
org.bukkit.persistence.PersistentDataType.STRING);
return hasItemSpawnerKey(item);
}

@Override
Expand Down Expand Up @@ -148,13 +133,8 @@ public Material getItemSpawnerMaterial(ItemStack item) {
return null;
}

ItemMeta meta = item.getItemMeta();
if (meta == null) {
return null;
}

String materialName = meta.getPersistentDataContainer().get(
new NamespacedKey(plugin, "item_spawner_material"), PersistentDataType.STRING);
String materialName = item.getPersistentDataContainer()
.get(itemSpawnerMaterialKey, PersistentDataType.STRING);

if (materialName == null) {
return null;
Expand Down Expand Up @@ -249,6 +229,16 @@ public void clearSpawnerLayoutProvider() {
plugin.getGuiLayoutConfig().setProvider(null);
}

private boolean hasVanillaSpawnerKey(ItemStack spawnerItem) {
return spawnerItem.getPersistentDataContainer()
.has(vanillaSpawnerKey, PersistentDataType.BOOLEAN);
}

private boolean hasItemSpawnerKey(ItemStack spawnerItem) {
return spawnerItem.getPersistentDataContainer()
.has(itemSpawnerMaterialKey, PersistentDataType.STRING);
}

/**
* Gets the currently active per-spawner layout provider.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import github.nighter.smartspawner.spawner.properties.SpawnerData;
import github.nighter.smartspawner.spawner.properties.VirtualInventory;
import github.nighter.smartspawner.utils.BlockPos;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
Expand Down Expand Up @@ -64,36 +65,47 @@ private void transferItems(Location hopperLoc, Location spawnerLoc) {
var state = hopperLoc.getBlock().getState(false);
if (!(state instanceof Hopper hopper)) return;

Map<Integer, ItemStack> displayItems = virtualInv.getDisplayInventory();
if (displayItems == null || displayItems.isEmpty()) return;

Inventory hopperInv = hopper.getInventory();

int transferred = 0;

int rangeStart = 0;
int rangeSize = Math.max(plugin.getHopperConfig().getStackPerTransfer(), 9);
List<ItemStack> removed = new ArrayList<>();

for (ItemStack item : displayItems.values()) {
if (transferred >= plugin.getHopperConfig().getStackPerTransfer()) break;
if (item == null || item.getType() == Material.AIR) continue;
while (transferred < plugin.getHopperConfig().getStackPerTransfer()) {
Int2ObjectMap<ItemStack> displayItems = virtualInv.getDisplayRange(rangeStart, rangeSize);
if (displayItems.isEmpty()) {
break;
}

for (ItemStack item : displayItems.values()) {
if (transferred >= plugin.getHopperConfig().getStackPerTransfer()) {
break;
}
if (item == null || item.getType() == Material.AIR) {
continue;
}

ItemStack clone = item.clone();
int originalAmount = clone.getAmount();
ItemStack clone = item.clone();
int originalAmount = clone.getAmount();

HashMap<Integer, ItemStack> leftovers = hopperInv.addItem(clone);
HashMap<Integer, ItemStack> leftovers = hopperInv.addItem(clone);

int insertedAmount = originalAmount;
int insertedAmount = originalAmount;

if (!leftovers.isEmpty()) {
insertedAmount -= leftovers.values().iterator().next().getAmount();
}
if (!leftovers.isEmpty()) {
insertedAmount -= leftovers.values().iterator().next().getAmount();
}

if (insertedAmount > 0) {
ItemStack toRemove = item.clone();
toRemove.setAmount(insertedAmount);
removed.add(toRemove);
transferred++;
if (insertedAmount > 0) {
ItemStack toRemove = item.clone();
toRemove.setAmount(insertedAmount);
removed.add(toRemove);
transferred++;
}
}

rangeStart += rangeSize;
}

if (!removed.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package github.nighter.smartspawner.language.cache;

import github.nighter.smartspawner.utils.LRUCache;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package github.nighter.smartspawner.language.section;

import github.nighter.smartspawner.language.cache.LRUCache;
import github.nighter.smartspawner.utils.LRUCache;
import github.nighter.smartspawner.language.format.ColorUtil;
import github.nighter.smartspawner.language.format.LanguageComponentFormatter;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -438,13 +438,7 @@ private SpawnerData loadSpawnerFromConfig(String spawnerId, boolean logErrors, b
int amount = entry.getValue();

if (item != null && amount > 0) {
while (amount > 0) {
int batchSize = Math.min(amount, item.getMaxStackSize());
ItemStack batch = item.clone();
batch.setAmount(batchSize);
virtualInv.addItems(Collections.singletonList(batch));
amount -= batchSize;
}
virtualInv.addItem(item, amount);
}
}
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -680,13 +680,7 @@ private void loadInventoryFromJson(String jsonData, VirtualInventory virtualInv)
int amount = entry.getValue();

if (item != null && amount > 0) {
while (amount > 0) {
int batchSize = Math.min(amount, item.getMaxStackSize());
ItemStack batch = item.clone();
batch.setAmount(batchSize);
virtualInv.addItems(Collections.singletonList(batch));
amount -= batchSize;
}
virtualInv.addItem(item, amount);
}
}
} catch (Exception e) {
Expand Down
Loading
Loading