Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 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
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.0.1"
version = "1.7.0.1-DEV"

repositories {
mavenCentral()
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
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import github.nighter.smartspawner.spawner.properties.SpawnerData;
import github.nighter.smartspawner.Scheduler;
import github.nighter.smartspawner.Scheduler.Task;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import lombok.Getter;
import net.kyori.adventure.text.Component;
import org.bukkit.inventory.Inventory;
Expand Down Expand Up @@ -283,30 +284,20 @@ public void updateDisplay(Inventory inventory, SpawnerData spawner, int page, in
}
}

private void addPageItems(Map<Integer, ItemStack> updates, Set<Integer> slotsToEmpty,
SpawnerData spawner, int page) {
private void addPageItems(Map<Integer, ItemStack> updates, Set<Integer> slotsToEmpty, SpawnerData spawner, int page) {
try {
// Get display items directly from VirtualInventory (source of truth)
// Read only the requested page instead of materializing the full logical inventory.
VirtualInventory virtualInv = spawner.getVirtualInventory();
Map<Integer, ItemStack> displayItems = virtualInv.getDisplayInventory();
Int2ObjectMap<ItemStack> displayItems = virtualInv.getDisplayPage(page, StoragePageHolder.MAX_ITEMS_PER_PAGE);

if (displayItems.isEmpty()) {
return;
}

// Calculate start index for current page
int startIndex = (page - 1) * StoragePageHolder.MAX_ITEMS_PER_PAGE;

// Add items for this page
for (Map.Entry<Integer, ItemStack> entry : displayItems.entrySet()) {
int globalIndex = entry.getKey();

// Check if item belongs on this page
if (globalIndex >= startIndex && globalIndex < startIndex + StoragePageHolder.MAX_ITEMS_PER_PAGE) {
int displaySlot = globalIndex - startIndex;
updates.put(displaySlot, entry.getValue());
slotsToEmpty.remove(displaySlot);
}
for (Int2ObjectMap.Entry<ItemStack> entry : displayItems.int2ObjectEntrySet()) {
int displaySlot = entry.getIntKey();
updates.put(displaySlot, entry.getValue());
slotsToEmpty.remove(displaySlot);
}
} finally {
spawner.getInventoryLock().unlock();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package github.nighter.smartspawner.spawner.gui.storage.button;

import github.nighter.smartspawner.language.LanguageManager;
import github.nighter.smartspawner.spawner.gui.layout.GuiButton;
import github.nighter.smartspawner.spawner.gui.layout.GuiLayout;
import github.nighter.smartspawner.utils.LRUCache;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

public final class NavigationButtonCache {
private static final int CACHE_SIZE = 512;

private final LRUCache<Integer, ItemStack> previousButtons = new LRUCache<>(CACHE_SIZE);
private final LRUCache<Integer, ItemStack> nextButtons = new LRUCache<>(CACHE_SIZE);
private final Function<ButtonData, ItemStack> buttonFactory;

private String previousButtonName;
private String nextButtonName;
private List<String> previousButtonLore = Collections.emptyList();
private List<String> nextButtonLore = Collections.emptyList();
private Material previousButtonMaterial;
private Material nextButtonMaterial;

public NavigationButtonCache(Function<ButtonData, ItemStack> buttonFactory) {
this.buttonFactory = buttonFactory;
}

public void reload(GuiLayout layout, LanguageManager languageManager) {
clear();
previousButtonName = languageManager.getGuiItemName("navigation_button_previous.name");
nextButtonName = languageManager.getGuiItemName("navigation_button_next.name");
previousButtonLore = languageManager.getGuiItemLoreAsList("navigation_button_previous.lore");
nextButtonLore = languageManager.getGuiItemLoreAsList("navigation_button_next.lore");
previousButtonMaterial = null;
nextButtonMaterial = null;

for (GuiButton button : layout.getAllButtons().values()) {
String action = getAnyActionFromButton(button);
if (action == null) {
continue;
}

switch (action) {
case "previous_page" -> previousButtonMaterial = button.getMaterial();
case "next_page" -> nextButtonMaterial = button.getMaterial();
}
}
}

public ItemStack getPreviousButton(int targetPage) {
return previousButtons.get(targetPage, this::createPreviousButton);
}

public ItemStack getNextButton(int targetPage) {
return nextButtons.get(targetPage, this::createNextButton);
}

public void clear() {
previousButtons.clear();
nextButtons.clear();
}

private ItemStack createPreviousButton(int targetPage) {
return createButton(previousButtonMaterial, previousButtonName, previousButtonLore, targetPage);
}

private ItemStack createNextButton(int targetPage) {
return createButton(nextButtonMaterial, nextButtonName, nextButtonLore, targetPage);
}

private ItemStack createButton(Material material, String name, List<String> lore, int targetPage) {
String targetPageText = String.valueOf(targetPage);
return buttonFactory.apply(
new ButtonData(
material,
replaceTargetPage(name, targetPageText),
replaceTargetPage(lore, targetPageText)
)
);
}

private String replaceTargetPage(String text, String targetPage) {
return text != null ? text.replace("{target_page}", targetPage) : null;
}

private List<String> replaceTargetPage(List<String> lore, String targetPage) {
if (lore.isEmpty()) {
return Collections.emptyList();
}

List<String> replacedLore = new ArrayList<>(lore.size());
for (String line : lore) {
replacedLore.add(line.replace("{target_page}", targetPage));
}
return replacedLore;
}

private String getAnyActionFromButton(GuiButton button) {
Map<String, String> actions = button.getActions();
if (actions == null || actions.isEmpty()) {
return null;
}

String action = actions.get("click");
if (action != null && !action.isEmpty()) {
return action;
}

action = actions.get("left_click");
if (action != null && !action.isEmpty()) {
return action;
}

action = actions.get("right_click");
if (action != null && !action.isEmpty()) {
return action;
}

return null;
}

public record ButtonData(Material material, String name, List<String> lore) {}
}
Loading
Loading