Skip to content

Commit 0c8be35

Browse files
authored
Merge pull request #92 from NighterDevelopment/copilot/fix-hopper-bug-and-refactor
Fix hopper task termination after single execution and refactor for Folia thread-safety
2 parents c0c3009 + 01980fe commit 0c8be35

2 files changed

Lines changed: 125 additions & 55 deletions

File tree

core/src/main/java/github/nighter/smartspawner/SmartSpawner.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,14 @@ private void initializeListeners() {
311311
}
312312

313313
public void setUpHopperHandler() {
314-
this.hopperHandler = getConfig().getBoolean("hopper.enabled", false) ? new HopperHandler(this) : null;
314+
if (this.hopperHandler != null) {
315+
this.hopperHandler.cleanup();
316+
this.hopperHandler = null;
317+
}
318+
319+
if (getConfig().getBoolean("hopper.enabled", false)) {
320+
this.hopperHandler = new HopperHandler(this);
321+
}
315322
}
316323

317324
private void registerListeners() {

core/src/main/java/github/nighter/smartspawner/extras/HopperHandler.java

Lines changed: 117 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,23 @@ public void restartAllHoppers() {
7272
}
7373

7474
private void processChunkHoppers(Chunk chunk) {
75+
if (chunk == null || !chunk.isLoaded()) return;
76+
7577
Location chunkLoc = new Location(chunk.getWorld(),
7678
chunk.getX() * 16 + 8, 64, chunk.getZ() * 16 + 8);
7779

7880
Scheduler.runLocationTask(chunkLoc, () -> {
7981
try {
80-
for (BlockState state : chunk.getTileEntities(block -> block.getType() == Material.HOPPER, false)) {
82+
Collection<BlockState> hopperStates = chunk.getTileEntities(block -> block.getType() == Material.HOPPER, false);
83+
if (hopperStates == null || hopperStates.isEmpty()) return;
84+
85+
for (BlockState state : hopperStates) {
86+
if (state == null) continue;
87+
8188
Block hopperBlock = state.getBlock();
82-
Block aboveBlock = hopperBlock.getRelative(BlockFace.UP);
89+
if (hopperBlock.getType() != Material.HOPPER) continue;
8390

91+
Block aboveBlock = hopperBlock.getRelative(BlockFace.UP);
8492
if (aboveBlock.getType() == Material.SPAWNER) {
8593
startHopperTask(hopperBlock.getLocation(), aboveBlock.getLocation());
8694
}
@@ -102,15 +110,42 @@ public void onChunkLoad(ChunkLoadEvent event) {
102110

103111
@EventHandler
104112
public void onChunkUnload(ChunkUnloadEvent event) {
113+
if (!plugin.getConfig().getBoolean("hopper.enabled", false)) return;
114+
105115
Chunk chunk = event.getChunk();
106-
for (BlockState state : chunk.getTileEntities(block -> block.getType() == Material.HOPPER, false)) {
107-
stopHopperTask(state.getLocation());
116+
if (chunk == null) return;
117+
118+
try {
119+
Collection<BlockState> hopperStates = chunk.getTileEntities(block -> block.getType() == Material.HOPPER, false);
120+
if (hopperStates == null || hopperStates.isEmpty()) return;
121+
122+
for (BlockState state : hopperStates) {
123+
if (state == null) continue;
124+
stopHopperTask(state.getLocation());
125+
}
126+
} catch (Exception e) {
127+
plugin.getLogger().log(Level.WARNING, "Error stopping hoppers in unloading chunk at " +
128+
chunk.getX() + "," + chunk.getZ(), e);
108129
}
109130
}
110131

111132
public void cleanup() {
112-
activeHoppers.values().forEach(Scheduler.Task::cancel);
113-
activeHoppers.clear();
133+
try {
134+
List<Scheduler.Task> tasks = new ArrayList<>(activeHoppers.values());
135+
activeHoppers.clear();
136+
137+
for (Scheduler.Task task : tasks) {
138+
if (task != null) {
139+
try {
140+
task.cancel();
141+
} catch (Exception e) {
142+
plugin.getLogger().log(Level.WARNING, "Error cancelling hopper task during cleanup", e);
143+
}
144+
}
145+
}
146+
} catch (Exception e) {
147+
plugin.getLogger().log(Level.SEVERE, "Error during hopper handler cleanup", e);
148+
}
114149
}
115150

116151
@EventHandler
@@ -133,9 +168,10 @@ public void onHopperBreak(BlockBreakEvent event) {
133168

134169
public void startHopperTask(Location hopperLoc, Location spawnerLoc) {
135170
if (!plugin.getConfig().getBoolean("hopper.enabled", false)) return;
136-
if (activeHoppers.containsKey(hopperLoc)) return;
171+
if (hopperLoc == null || spawnerLoc == null) return;
172+
173+
stopHopperTask(hopperLoc);
137174

138-
// Create a runnable for the hopper task
139175
Runnable hopperRunnable = () -> {
140176
try {
141177
if (!isValidSetup(hopperLoc, spawnerLoc)) {
@@ -145,11 +181,9 @@ public void startHopperTask(Location hopperLoc, Location spawnerLoc) {
145181
transferItems(hopperLoc, spawnerLoc);
146182
} catch (Exception e) {
147183
plugin.getLogger().log(Level.WARNING, "Error in hopper task at " + hopperLoc, e);
148-
// Don't stop the task on error, just log it
149184
}
150185
};
151186

152-
// Use the location-based scheduler for better Folia compatibility
153187
try {
154188
Scheduler.Task task = Scheduler.runLocationTaskTimer(
155189
hopperLoc,
@@ -158,7 +192,9 @@ public void startHopperTask(Location hopperLoc, Location spawnerLoc) {
158192
plugin.getTimeFromConfig("hopper.check_delay", "3s")
159193
);
160194

161-
activeHoppers.put(hopperLoc, task);
195+
if (task != null && !task.isCancelled()) {
196+
activeHoppers.put(hopperLoc, task);
197+
}
162198
} catch (Exception e) {
163199
plugin.getLogger().log(Level.SEVERE, "Failed to start hopper task at " + hopperLoc, e);
164200
}
@@ -174,81 +210,108 @@ private boolean isValidSetup(Location hopperLoc, Location spawnerLoc) {
174210
}
175211

176212
public void stopHopperTask(Location hopperLoc) {
213+
if (hopperLoc == null) return;
214+
177215
Scheduler.Task task = activeHoppers.remove(hopperLoc);
178216
if (task != null) {
179-
task.cancel();
217+
try {
218+
task.cancel();
219+
} catch (Exception e) {
220+
plugin.getLogger().log(Level.WARNING, "Error cancelling hopper task at " + hopperLoc, e);
221+
}
180222
}
181223
}
182224

183225
public void restartHopperForSpawner(Location spawnerLoc) {
184226
if (!plugin.getConfig().getBoolean("hopper.enabled", false)) return;
185-
186-
// Find hopper below the spawner
187-
Block spawnerBlock = spawnerLoc.getBlock();
188-
if (spawnerBlock.getType() != Material.SPAWNER) return;
189-
190-
Block hopperBlock = spawnerBlock.getRelative(BlockFace.DOWN);
191-
if (hopperBlock.getType() != Material.HOPPER) return;
192-
193-
Location hopperLoc = hopperBlock.getLocation();
194-
195-
// Stop existing hopper task if any
196-
stopHopperTask(hopperLoc);
197-
198-
// Start new hopper task
199-
startHopperTask(hopperLoc, spawnerLoc);
227+
if (spawnerLoc == null) return;
228+
229+
Scheduler.runLocationTask(spawnerLoc, () -> {
230+
try {
231+
Block spawnerBlock = spawnerLoc.getBlock();
232+
if (spawnerBlock.getType() != Material.SPAWNER) return;
233+
234+
Block hopperBlock = spawnerBlock.getRelative(BlockFace.DOWN);
235+
if (hopperBlock.getType() != Material.HOPPER) return;
236+
237+
Location hopperLoc = hopperBlock.getLocation();
238+
239+
startHopperTask(hopperLoc, spawnerLoc);
240+
} catch (Exception e) {
241+
plugin.getLogger().log(Level.WARNING, "Error restarting hopper for spawner at " + spawnerLoc, e);
242+
}
243+
});
200244
}
201245

202246
private void transferItems(Location hopperLoc, Location spawnerLoc) {
247+
if (hopperLoc == null || spawnerLoc == null) return;
248+
203249
SpawnerData spawner = spawnerManager.getSpawnerByLocation(spawnerLoc);
204250
if (spawner == null) return;
205251

206-
// Use inventoryLock for hopper transfers
207252
ReentrantLock lock = spawner.getInventoryLock();
208-
if (!lock.tryLock()) return; // Skip this tick if we can't get the lock
253+
if (!lock.tryLock()) return;
209254

210255
try {
256+
Block hopperBlock = hopperLoc.getBlock();
257+
if (hopperBlock.getType() != Material.HOPPER) {
258+
stopHopperTask(hopperLoc);
259+
return;
260+
}
261+
211262
VirtualInventory virtualInv = spawner.getVirtualInventory();
212-
Hopper hopper = (Hopper) hopperLoc.getBlock().getState(false);
263+
if (virtualInv == null) return;
264+
265+
Hopper hopper = (Hopper) hopperBlock.getState(false);
266+
if (hopper == null) return;
267+
268+
Map<Integer, ItemStack> displayItems = virtualInv.getDisplayInventory();
269+
if (displayItems == null || displayItems.isEmpty()) return;
213270

214271
int itemsPerTransfer = plugin.getConfig().getInt("hopper.stack_per_transfer", 5);
272+
273+
org.bukkit.inventory.Inventory hopperInv = hopper.getInventory();
274+
275+
List<ItemStack> itemsToRemove = new ArrayList<>(itemsPerTransfer);
215276
int transferred = 0;
216277
boolean inventoryChanged = false;
217278

218-
Map<Integer, ItemStack> displayItems = virtualInv.getDisplayInventory();
219-
List<ItemStack> itemsToRemove = new ArrayList<>();
220-
221279
for (Map.Entry<Integer, ItemStack> entry : displayItems.entrySet()) {
222280
if (transferred >= itemsPerTransfer) break;
223281

224282
ItemStack item = entry.getValue();
225283
if (item == null || item.getType() == Material.AIR) continue;
226284

227-
ItemStack[] hopperContents = hopper.getInventory().getContents();
228-
for (int i = 0; i < hopperContents.length; i++) {
229-
if (transferred >= itemsPerTransfer) break;
230-
285+
ItemStack[] hopperContents = hopperInv.getContents();
286+
287+
boolean itemTransferred = false;
288+
for (int i = 0; i < hopperContents.length && !itemTransferred; i++) {
231289
ItemStack hopperItem = hopperContents[i];
290+
232291
if (hopperItem == null || hopperItem.getType() == Material.AIR) {
233-
hopper.getInventory().setItem(i, item.clone());
292+
hopperInv.setItem(i, item.clone());
234293
itemsToRemove.add(item);
235294
transferred++;
236295
inventoryChanged = true;
237-
break;
238-
} else if (hopperItem.isSimilar(item) &&
239-
hopperItem.getAmount() < hopperItem.getMaxStackSize()) {
240-
int space = hopperItem.getMaxStackSize() - hopperItem.getAmount();
241-
int toTransfer = Math.min(space, item.getAmount());
242-
243-
hopperItem.setAmount(hopperItem.getAmount() + toTransfer);
244-
245-
ItemStack toRemove = item.clone();
246-
toRemove.setAmount(toTransfer);
247-
itemsToRemove.add(toRemove);
248-
249-
transferred++;
250-
inventoryChanged = true;
251-
break;
296+
itemTransferred = true;
297+
} else if (hopperItem.isSimilar(item)) {
298+
int currentAmount = hopperItem.getAmount();
299+
int maxStackSize = hopperItem.getMaxStackSize();
300+
301+
if (currentAmount < maxStackSize) {
302+
int space = maxStackSize - currentAmount;
303+
int toTransfer = Math.min(space, item.getAmount());
304+
305+
hopperItem.setAmount(currentAmount + toTransfer);
306+
307+
ItemStack toRemove = item.clone();
308+
toRemove.setAmount(toTransfer);
309+
itemsToRemove.add(toRemove);
310+
311+
transferred++;
312+
inventoryChanged = true;
313+
itemTransferred = true;
314+
}
252315
}
253316
}
254317
}
@@ -261,7 +324,7 @@ private void transferItems(Location hopperLoc, Location spawnerLoc) {
261324
updateOpenGuis(spawner);
262325
}
263326
} catch (Exception e) {
264-
plugin.getLogger().log(Level.WARNING, "Error transferring items from spawner to hopper", e);
327+
plugin.getLogger().log(Level.WARNING, "Error transferring items from spawner to hopper at " + hopperLoc, e);
265328
} finally {
266329
lock.unlock();
267330
}

0 commit comments

Comments
 (0)