Skip to content

Commit 89fd0a9

Browse files
committed
fix(barrel): NPE when loading barrels in some cases
1 parent 16bc21c commit 89fd0a9

8 files changed

Lines changed: 137 additions & 69 deletions

File tree

src/main/java/com/dre/brewery/Barrel.java

Lines changed: 79 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,10 @@
5757
import java.util.ArrayList;
5858
import java.util.List;
5959
import java.util.Map;
60+
import java.util.Objects;
6061
import java.util.UUID;
6162
import java.util.concurrent.CompletableFuture;
63+
import java.util.concurrent.ConcurrentHashMap;
6264

6365
/**
6466
* A Multi Block Barrel with Inventory
@@ -67,11 +69,10 @@
6769
@Setter
6870
public class Barrel extends BarrelBody implements InventoryHolder {
6971

70-
@Getter
71-
public static final List<Barrel> barrels = new ArrayList<>();
72+
private static final Map<UUID, List<Barrel>> barrels = new ConcurrentHashMap<>();
7273
private static final Config config = ConfigManager.getConfig(Config.class);
7374
private static final Lang lang = ConfigManager.getConfig(Lang.class);
74-
private static int check = 0; // Which Barrel was last checked
75+
private static Map<UUID, Integer> checkCounters = new ConcurrentHashMap<>(); // Which Barrel was last checked
7576
/**
7677
* -- GETTER --
7778
* Is this a small barrel?
@@ -136,28 +137,30 @@ public Barrel(Block spigot, byte sign, BoundingBox bounds, ItemStack[] items, fl
136137
}
137138

138139
public static void onUpdate() {
139-
for (Barrel barrel : barrels) {
140-
// A Minecraft day is 20 min, so add 1/20 to the time every minute
141-
if (barrel != null) {
142-
barrel.time += (float) (1.0 / config.getAgingYearDuration());
143-
}
144-
}
145-
int numBarrels = barrels.size();
146-
if (check == 0 && numBarrels > 0) {
147-
Barrel random = barrels.get((int) Math.floor(Math.random() * numBarrels));
148-
if (random != null) {
149-
// You have been selected for a random search
150-
// We want to check at least one barrel every time
151-
random.checked = false;
152-
}
153-
if (numBarrels > 50) {
154-
Barrel randomInTheBack = barrels.get(numBarrels - 1 - (int) (Math.random() * (numBarrels >>> 2)));
155-
if (randomInTheBack != null) {
156-
// Prioritize checking one of the less recently used barrels as well
157-
randomInTheBack.checked = false;
140+
barrels.values()
141+
.stream()
142+
.flatMap(List::stream)
143+
.filter(Objects::nonNull)
144+
.forEach(barrel -> barrel.time += (float) (1.0 / config.getAgingYearDuration()));
145+
for (UUID worldUuid : barrels.keySet()) {
146+
List<Barrel> worldBarrels = barrels.get(worldUuid);
147+
int numBarrels = worldBarrels.size();
148+
if (checkCounters.getOrDefault(worldUuid, 0) == 0 && numBarrels > 0) {
149+
Barrel random = worldBarrels.get((int) Math.floor(Math.random() * numBarrels));
150+
if (random != null) {
151+
// You have been selected for a random search
152+
// We want to check at least one barrel every time
153+
random.checked = false;
158154
}
155+
if (numBarrels > 50) {
156+
Barrel randomInTheBack = worldBarrels.get(numBarrels - 1 - (int) (Math.random() * (numBarrels >>> 2)));
157+
if (randomInTheBack != null) {
158+
// Prioritize checking one of the less recently used barrels as well
159+
randomInTheBack.checked = false;
160+
}
161+
}
162+
new BarrelCheck().runTaskTimer(BreweryPlugin.getInstance(), 1, 1);
159163
}
160-
new BarrelCheck().runTaskTimer(BreweryPlugin.getInstance(), 1, 1);
161164
}
162165
}
163166

@@ -309,16 +312,19 @@ public static Barrel getBySpigot(Block sign) {
309312
if (!spigot.equals(sign)) {
310313
signoffset = (byte) (sign.getY() - spigot.getY());
311314
}
312-
315+
List<Barrel> worldBarrels = barrels.get(sign.getWorld().getUID());
316+
if (worldBarrels == null) {
317+
return null;
318+
}
313319
int i = 0;
314-
for (Barrel barrel : barrels) {
320+
for (Barrel barrel : worldBarrels) {
315321
if (barrel != null && barrel.isSignOfBarrel(signoffset)) {
316322
if (barrel.spigot.equals(spigot)) {
317323
if (barrel.getSignoffset() == 0 && signoffset != 0) {
318324
// Barrel has no signOffset even though we clicked a sign, may be old
319325
barrel.setSignoffset(signoffset);
320326
}
321-
moveMRU(i);
327+
moveMRU(sign.getWorld().getUID(), i);
322328
return barrel;
323329
}
324330
}
@@ -332,26 +338,34 @@ public static Barrel getBySpigot(Block sign) {
332338
*/
333339
@Nullable
334340
public static Barrel getByWood(Block wood) {
335-
if (BarrelAsset.isBarrelAsset(BarrelAsset.PLANKS, wood.getType()) || BarrelAsset.isBarrelAsset(BarrelAsset.STAIRS, wood.getType())) {
336-
int i = 0;
337-
for (Barrel barrel : barrels) {
338-
if (barrel.getSpigot().getWorld().equals(wood.getWorld()) && barrel.getBounds().contains(wood)) {
339-
moveMRU(i);
340-
return barrel;
341-
}
342-
i++;
341+
if (!BarrelAsset.isBarrelAsset(BarrelAsset.PLANKS, wood.getType()) && !BarrelAsset.isBarrelAsset(BarrelAsset.STAIRS, wood.getType())) {
342+
return null;
343+
}
344+
List<Barrel> worldBarrels = barrels.get(wood.getWorld().getUID());
345+
if (worldBarrels == null) {
346+
return null;
347+
}
348+
for (int i = 0; i < worldBarrels.size(); i++) {
349+
Barrel barrel = worldBarrels.get(i);
350+
if (barrel.getSpigot().getWorld().equals(wood.getWorld()) && barrel.getBounds().contains(wood)) {
351+
moveMRU(wood.getWorld().getUID(), i);
352+
return barrel;
343353
}
344354
}
345355
return null;
346356
}
347357

348358
// Move Barrel that was recently used more towards the front of the List
349359
// Optimizes retrieve by Block over time
350-
private static void moveMRU(int index) {
351-
if (index > 0) {
352-
// Swap entry at the index with the one next to it
353-
barrels.set(index - 1, barrels.set(index, barrels.get(index - 1)));
360+
private static void moveMRU(UUID worldUuid, int index) {
361+
if (index < 0) {
362+
return;
363+
}
364+
List<Barrel> worldBarrels = barrels.get(worldUuid);
365+
if (index >= worldBarrels.size()) {
366+
return;
354367
}
368+
worldBarrels.set(index - 1, worldBarrels.set(index, worldBarrels.get(index - 1)));
355369
}
356370

357371
/**
@@ -386,7 +400,7 @@ public static boolean create(Block sign, Player player) {
386400
BarrelCreateEvent createEvent = new BarrelCreateEvent(barrel, player);
387401
BreweryPlugin.getInstance().getServer().getPluginManager().callEvent(createEvent);
388402
if (!createEvent.isCancelled()) {
389-
barrels.add(0, barrel);
403+
barrels.computeIfAbsent(sign.getWorld().getUID(), ignored -> new ArrayList<>()).addFirst(barrel);
390404
return true;
391405
}
392406
}
@@ -430,7 +444,7 @@ public void remove(@Nullable Block broken, @Nullable Player breaker, boolean dro
430444
if (event.willDropItems()) {
431445
if (getBounds() == null) {
432446
Logging.debugLog("Barrel Body is null, can't drop items: " + this.id);
433-
barrels.remove(this);
447+
barrels.getOrDefault(spigot.getWorld().getUID(), new ArrayList<>()).remove(this);
434448
return;
435449
}
436450

@@ -466,7 +480,7 @@ public void remove(@Nullable Block broken, @Nullable Player breaker, boolean dro
466480
}
467481
}
468482

469-
barrels.remove(this);
483+
barrels.getOrDefault(spigot.getWorld().getUID(), new ArrayList<>()).remove(this);
470484
}
471485

472486
@Override
@@ -521,32 +535,41 @@ public static Block getSpigotOfSign(Block block) {
521535
* Are any Barrels in that World
522536
*/
523537
public static boolean hasDataInWorld(World world) {
524-
return barrels.stream().anyMatch(barrel -> barrel.spigot.getWorld().equals(world));
538+
return barrels.containsKey(world.getUID()) && !barrels.get(world.getUID()).isEmpty();
525539
}
526540

527541
/**
528542
* unloads barrels that are in a unloading world
529543
*/
530544
public static void onUnload(World world) {
531-
barrels.removeIf(barrel -> barrel.spigot.getWorld().equals(world));
545+
barrels.remove(world.getUID());
532546
}
533547

534-
/**
535-
* Unload all Barrels that have a Block in a unloaded World
536-
*/
537-
public static void unloadWorlds() {
538-
List<World> worlds = BreweryPlugin.getInstance().getServer().getWorlds();
539-
barrels.removeIf(barrel -> !worlds.contains(barrel.spigot.getWorld()));
548+
public static void registerBarrel(Barrel barrel) {
549+
barrels.computeIfAbsent(barrel.spigot.getWorld().getUID(), ignored -> new ArrayList<>())
550+
.add(barrel);
551+
}
552+
553+
public static List<Barrel> getAllBarrels() {
554+
return barrels.values().stream()
555+
.flatMap(List::stream)
556+
.filter(Objects::nonNull)
557+
.toList();
540558
}
541559

542560
public static class BarrelCheck extends UniversalRunnable {
543561
@Override
544562
public void run() {
545-
boolean repeat = true;
546-
while (repeat) {
547-
if (check < barrels.size()) {
548-
Barrel barrel = barrels.get(check);
549-
if (!barrel.checked) {
563+
barrels.keySet()
564+
.forEach(worldUuid -> {
565+
int counter = checkCounters.computeIfAbsent(worldUuid, ignored -> -1);
566+
List<Barrel> worldBarrels = barrels.get(worldUuid);
567+
counter = counter + 1 % worldBarrels.size();
568+
while (counter < worldBarrels.size()) {
569+
Barrel barrel = worldBarrels.get(counter++);
570+
if (barrel.checked) {
571+
continue;
572+
}
550573
BreweryPlugin.getScheduler().runTask(barrel.getSpigot().getLocation(), () -> {
551574
Block broken = barrel.getBrokenBlock(false);
552575
if (broken != null) {
@@ -562,15 +585,10 @@ public void run() {
562585
barrel.checked = true;
563586
}
564587
});
565-
repeat = false;
588+
return;
566589
}
567-
check++;
568-
} else {
569-
check = 0;
570-
repeat = false;
571590
cancel();
572-
}
573-
}
591+
});
574592
}
575593

576594
}

src/main/java/com/dre/brewery/BreweryPlugin.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,10 +166,10 @@ public void onEnable() {
166166

167167
// Load objects
168168
DataManager.loadMiscData(dataManager.getBreweryMiscData());
169-
dataManager.getAllBarrels().thenApplyAsync(barrels -> Barrel.getBarrels().addAll(barrels.stream()
169+
dataManager.getAllBarrels().thenAcceptAsync(barrels -> barrels.stream()
170170
.filter(Objects::nonNull)
171-
.toList()
172-
));
171+
.forEach(Barrel::registerBarrel)
172+
);
173173
BCauldron.getBcauldrons().putAll(dataManager.getAllCauldrons().stream()
174174
.filter(Objects::nonNull)
175175
.collect(Collectors.toMap(
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* BreweryX Bukkit-Plugin for an alternate brewing process
3+
* Copyright (C) 2024-2025 The Brewery Team
4+
*
5+
* This file is part of BreweryX.
6+
*
7+
* BreweryX is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* BreweryX is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Public License
18+
* along with BreweryX. If not, see <http://www.gnu.org/licenses/gpl-3.0.html>.
19+
*/
20+
21+
package com.dre.brewery.listeners;
22+
23+
import com.dre.brewery.Barrel;
24+
import com.dre.brewery.storage.DataManager;
25+
import org.bukkit.event.EventHandler;
26+
import org.bukkit.event.EventPriority;
27+
import org.bukkit.event.Listener;
28+
import org.bukkit.event.world.WorldLoadEvent;
29+
import org.bukkit.event.world.WorldUnloadEvent;
30+
31+
public record WorldListener(DataManager dataManager) implements Listener {
32+
33+
@EventHandler(priority = EventPriority.MONITOR)
34+
public void onWorldLoad(WorldLoadEvent event) {
35+
dataManager.getAllBarrels()
36+
.thenAcceptAsync(barrels -> barrels.stream()
37+
.filter(barrel -> barrel.getSpigot().getWorld().equals(event.getWorld()))
38+
.forEach(Barrel.getBarrels()::add)
39+
);
40+
}
41+
42+
@EventHandler(priority = EventPriority.MONITOR)
43+
public void onWorldLoad(WorldUnloadEvent event) {
44+
dataManager.getAllBarrels()
45+
.thenAcceptAsync(barrels -> barrels.stream()
46+
.filter(barrel -> barrel.getSpigot().getWorld().equals(event.getWorld()))
47+
.forEach(Barrel.getBarrels()::remove)
48+
);
49+
}
50+
}

src/main/java/com/dre/brewery/storage/DataManager.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ public void saveAll(boolean async) {
172172
}
173173

174174
public void saveAll(boolean async, Runnable callback) {
175-
Collection<Barrel> barrels = Barrel.getBarrels();
175+
Collection<Barrel> barrels = Barrel.getAllBarrels();
176176
Collection<BCauldron> cauldrons = BCauldron.getBcauldrons().values();
177177
Collection<BPlayer> bPlayers = BPlayer.getPlayers().values();
178178
Collection<Wakeup> wakeups = Wakeup.getWakeups();

src/main/java/com/dre/brewery/storage/impls/FlatFileStorage.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ public CompletableFuture<Barrel> getBarrel(UUID id) {
187187

188188
Location spigotLoc = deserializeLocation(dataFile.getString(path + ".spigot"));
189189
if (spigotLoc == null) {
190-
return null;
190+
return CompletableFuture.completedFuture(null);
191191
}
192192

193193
int[] bounds = Arrays.stream(

src/main/java/com/dre/brewery/storage/impls/MongoDBStorage.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ public CompletableFuture<Barrel> getBarrel(UUID id) {
163163
if (serializableBarrel != null) {
164164
return serializableBarrel.toBarrel();
165165
}
166-
return null;
166+
return CompletableFuture.completedFuture(null);
167167
}
168168

169169
@Override

src/main/java/com/dre/brewery/storage/impls/MySQLStorage.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ public CompletableFuture<Barrel> getBarrel(UUID id) {
239239
if (serializableBarrel != null) {
240240
return serializableBarrel.toBarrel();
241241
}
242-
return null;
242+
return CompletableFuture.completedFuture(null);
243243
}
244244

245245
@Override

src/main/java/com/dre/brewery/storage/impls/SQLiteStorage.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ public CompletableFuture<Barrel> getBarrel(UUID id) {
250250
if (serializableBarrel != null) {
251251
return serializableBarrel.toBarrel();
252252
}
253-
return null;
253+
return CompletableFuture.completedFuture(null);
254254
}
255255

256256
@Override

0 commit comments

Comments
 (0)