diff --git a/src/main/java/com/xzavier0722/mc/plugin/slimefun4/storage/controller/BlockDataController.java b/src/main/java/com/xzavier0722/mc/plugin/slimefun4/storage/controller/BlockDataController.java index 9982769f16..9c75627e40 100644 --- a/src/main/java/com/xzavier0722/mc/plugin/slimefun4/storage/controller/BlockDataController.java +++ b/src/main/java/com/xzavier0722/mc/plugin/slimefun4/storage/controller/BlockDataController.java @@ -311,11 +311,11 @@ public void removeBlock(Location l) { var removed = getChunkDataCache(l.getChunk(), true).removeBlockData(l); if (removed == null) { - getUniversalDataFromCache(l).ifPresentOrElse(data -> removeUniversalData(data.getUUID()), () -> { + getUniversalDataFromCache(l).ifPresentOrElse(data -> removeUniversalData(l, data.getUUID()), () -> { if (Bukkit.isPrimaryThread()) { Slimefun.getBlockDataService() .getUniversalDataUUID(l.getBlock()) - .ifPresent(this::removeUniversalData); + .ifPresent(uuid -> removeUniversalData(l, uuid)); } }); @@ -336,7 +336,7 @@ public void removeBlock(Location l) { } } - public void removeUniversalData(UUID uuid) { + public void removeUniversalData(Location lastPresent, UUID uuid) { checkDestroy(); var toRemove = loadedUniversalData.get(uuid); @@ -358,7 +358,7 @@ public void removeUniversalData(UUID uuid) { } if (Slimefun.getRegistry().getTickerBlocks().contains(toRemove.getSfId())) { - Slimefun.getTickerTask().disableTicker(uuid); + Slimefun.getTickerTask().disableTicker(lastPresent); } } @@ -780,7 +780,7 @@ public void loadUniversalData(SlimefunUniversalData uniData) { if (sfItem != null && sfItem.isTicking()) { if (!uniData.getLastPresent().getBlock().getType().isAir()) { - Slimefun.getTickerTask().enableTicker(uniData.getUUID(), uniData.getLastPresent()); + Slimefun.getTickerTask().enableTicker(uniData.getLastPresent(), uniData.getUUID()); } } diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/core/ticker/TickLocation.java b/src/main/java/io/github/thebusybiscuit/slimefun4/core/ticker/TickLocation.java new file mode 100644 index 0000000000..c7d5ef9693 --- /dev/null +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/core/ticker/TickLocation.java @@ -0,0 +1,32 @@ +package io.github.thebusybiscuit.slimefun4.core.ticker; + +import io.github.bakedlibs.dough.blocks.BlockPosition; + +import java.util.UUID; + +import lombok.Getter; +import org.bukkit.Location; + +@Getter +public class TickLocation { + private final BlockPosition position; + private final UUID uuid; + + public TickLocation(BlockPosition position) { + this.position = position; + uuid = null; + } + + public TickLocation(BlockPosition position, UUID uuid) { + this.position = position; + this.uuid = uuid; + } + + public boolean isUniversal() { + return uuid != null; + } + + public Location getLocation() { + return position.toLocation(); + } +} diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/androids/ProgrammableAndroid.java b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/androids/ProgrammableAndroid.java index 716debb60a..4b6bec69ff 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/androids/ProgrammableAndroid.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/androids/ProgrammableAndroid.java @@ -1050,7 +1050,7 @@ protected void move(Block from, BlockFace face, Block to) { return; } - Slimefun.getTickerTask().disableTicker(uniData.getUUID()); + Slimefun.getTickerTask().disableTicker(from.getLocation()); to.setBlockData(Material.PLAYER_HEAD.createBlockData(data -> { if (data instanceof Rotatable rotatable) { @@ -1074,7 +1074,7 @@ protected void move(Block from, BlockFace face, Block to) { uniData.setLastPresent(to.getLocation()); uniData.getUniversalMenu().update(to.getLocation()); - Slimefun.getTickerTask().enableTicker(uniData.getUUID(), to.getLocation()); + Slimefun.getTickerTask().enableTicker(to.getLocation(), uniData.getUUID()); } } diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/listeners/DebugFishListener.java b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/listeners/DebugFishListener.java index ef5d0891e4..168b28fab6 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/listeners/DebugFishListener.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/listeners/DebugFishListener.java @@ -220,16 +220,10 @@ private void sendInfo(Player p, Block b, ASlimefunDataContainer data) { p.sendMessage(ChatColors.color("&dTicker: " + redCross)); } - Slimefun.getTickerTask().getLocations(p.getLocation().getChunk()).stream() - .filter(l -> l.equals(b.getLocation())) + Slimefun.getTickerTask().getTickLocations(p.getLocation().getChunk()).stream() + .filter(l -> l.getLocation().equals(b.getLocation())) .findFirst() - .ifPresent(tickLoc -> p.sendMessage(ChatColors.color("&dIn Ticker Queue: " + greenCheckmark))); - - Slimefun.getTickerTask().getUniversalLocations(p.getLocation().getChunk()).entrySet().stream() - .filter(entry -> entry.getKey().equals(b.getLocation())) - .findFirst() - .ifPresent( - tickLoc -> p.sendMessage(ChatColors.color("&dIn Ticker Queue (Universal): " + greenCheckmark))); + .ifPresent(tickLoc -> p.sendMessage(ChatColors.color("&dIn Ticker Queue " + (tickLoc.isUniversal() ? "(Universal)" : "") + ": " + greenCheckmark))); if (Slimefun.getProfiler().hasTimings(b)) { p.sendMessage( diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/tasks/TickerTask.java b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/tasks/TickerTask.java index 2ddcfe8dfb..878796cab8 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/tasks/TickerTask.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/tasks/TickerTask.java @@ -9,6 +9,7 @@ import io.github.thebusybiscuit.slimefun4.api.ErrorReport; import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItem; import io.github.thebusybiscuit.slimefun4.core.attributes.UniversalDataSupport; +import io.github.thebusybiscuit.slimefun4.core.ticker.TickLocation; import io.github.thebusybiscuit.slimefun4.implementation.Slimefun; import java.util.Collections; import java.util.HashSet; @@ -18,7 +19,9 @@ import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; +import java.util.stream.Collectors; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import javax.annotation.ParametersAreNonnullByDefault; import me.mrCookieSlime.Slimefun.Objects.handlers.BlockTicker; import org.apache.commons.lang.Validate; @@ -41,12 +44,7 @@ public class TickerTask implements Runnable { * This Map holds all currently actively ticking locations. * The value of this map (Set entries) MUST be thread-safe and mutable. */ - private final Map> tickingLocations = new ConcurrentHashMap<>(); - - /** - * This Map holds all locations of currently actively ticking universal items. - */ - private final Map> tickingUniversalLocations = new ConcurrentHashMap<>(); + private final Map> tickingLocations = new ConcurrentHashMap<>(); /** * This Map tracks how many bugs have occurred in a given Location . @@ -98,25 +96,15 @@ public void run() { // Run our ticker code if (!halted) { - Set>> loc; + Set>> loc; synchronized (tickingLocations) { loc = new HashSet<>(tickingLocations.entrySet()); } - for (Map.Entry> entry : loc) { + for (Map.Entry> entry : loc) { tickChunk(entry.getKey(), tickers, new HashSet<>(entry.getValue())); } - - Set>> uniLoc; - - synchronized (tickingUniversalLocations) { - uniLoc = new HashSet<>(tickingUniversalLocations.entrySet()); - } - - for (Map.Entry> entry : uniLoc) { - tickChunk(entry.getKey(), tickers, new ConcurrentHashMap<>(entry.getValue())); - } } // Start a new tick cycle for every BlockTicker @@ -138,27 +126,16 @@ public void run() { } @ParametersAreNonnullByDefault - private void tickChunk(ChunkPosition chunk, Set tickers, Set locations) { - try { - // Only continue if the Chunk is actually loaded - if (chunk.isLoaded()) { - for (Location l : locations) { - tickLocation(tickers, l); - } - } - } catch (ArrayIndexOutOfBoundsException | NumberFormatException x) { - Slimefun.logger() - .log(Level.SEVERE, x, () -> "An Exception has occurred while trying to resolve Chunk: " + chunk); - } - } - - @ParametersAreNonnullByDefault - private void tickChunk(ChunkPosition chunk, Set tickers, Map locations) { + private void tickChunk(ChunkPosition chunk, Set tickers, Set locations) { try { // Only continue if the Chunk is actually loaded if (chunk.isLoaded()) { - for (Map.Entry entry : locations.entrySet()) { - tickUniversalLocation(entry.getValue(), entry.getKey(), tickers); + for (TickLocation l : locations) { + if (l.isUniversal()) { + tickUniversalLocation(l.getUuid(), l.getLocation(), tickers); + } else { + tickLocation(tickers, l.getLocation()); + } } } } catch (ArrayIndexOutOfBoundsException | NumberFormatException x) { @@ -310,6 +287,22 @@ public int getTickRate() { return tickRate; } + /** + * BINARY COMPATIBILITY + * + * Use #getTickLocations instead + * + * @return A {@link Map} representation of all ticking {@link Location Locations} + */ + @Nonnull + public Map> getLocations() { + return tickingLocations.entrySet().stream().collect( + Collectors.toMap( + Map.Entry::getKey, + e -> e.getValue().stream().map(TickLocation::getLocation + ).collect(Collectors.toUnmodifiableSet()))); + } + /** * This method returns a read-only {@link Map} * representation of every {@link ChunkPosition} and its corresponding @@ -317,10 +310,10 @@ public int getTickRate() { * * This does include any {@link Location} from an unloaded {@link Chunk} too! * - * @return A {@link Map} representation of all ticking {@link Location Locations} + * @return A {@link Map} representation of all ticking {@link TickLocation Locations} */ @Nonnull - public Map> getLocations() { + public Map> getTickLocations() { return Collections.unmodifiableMap(tickingLocations); } @@ -339,22 +332,8 @@ public Map> getLocations() { public Set getLocations(@Nonnull Chunk chunk) { Validate.notNull(chunk, "The Chunk cannot be null!"); - Set locations = tickingLocations.getOrDefault(new ChunkPosition(chunk), Collections.emptySet()); - return Collections.unmodifiableSet(locations); - } - - /** - * 返回一个 只读 的 {@link Map} - * 代表每个 {@link ChunkPosition} 中有 {@link UniversalDataSupport} 属性的物品 - * Tick 的 {@link Location 位置}集合. - * - * 其中包含的 {@link Location} 可以是已加载或卸载的 {@link Chunk} - * - * @return 包含所有通用机器 Tick {@link Location 位置}的只读 {@link Map} - */ - @Nonnull - public Map> getUniversalLocations() { - return Collections.unmodifiableMap(tickingUniversalLocations); + Set locations = tickingLocations.getOrDefault(new ChunkPosition(chunk), Collections.emptySet()); + return locations.stream().map(TickLocation::getLocation).collect(Collectors.toUnmodifiableSet()); } /** @@ -367,15 +346,13 @@ public Map> getUniversalLocations() { * @param chunk * {@link Chunk} * - * @return 包含所有通用机器 Tick {@link Location 位置}的只读 {@link Map} + * @return 包含所有机器 Tick {@link TickLocation 位置}的只读 {@link Map} */ @Nonnull - public Map getUniversalLocations(@Nonnull Chunk chunk) { + public Set getTickLocations(@Nonnull Chunk chunk) { Validate.notNull(chunk, "The Chunk cannot be null!"); - Map locations = - tickingUniversalLocations.getOrDefault(new ChunkPosition(chunk), Collections.emptyMap()); - return Collections.unmodifiableMap(locations); + return tickingLocations.getOrDefault(new ChunkPosition(chunk), Collections.emptySet()); } /** @@ -385,10 +362,15 @@ public Map getUniversalLocations(@Nonnull Chunk chunk) { * The {@link Location} to activate */ public void enableTicker(@Nonnull Location l) { + enableTicker(l, null); + } + + public void enableTicker(@Nonnull Location l, @Nullable UUID uuid) { Validate.notNull(l, "Location cannot be null!"); synchronized (tickingLocations) { ChunkPosition chunk = new ChunkPosition(l.getWorld(), l.getBlockX() >> 4, l.getBlockZ() >> 4); + final var tickPosition = uuid == null ? new TickLocation(new BlockPosition(l)) : new TickLocation(new BlockPosition(l), uuid); /* Note that all the values in #tickingLocations must be thread-safe. @@ -396,51 +378,21 @@ public void enableTicker(@Nonnull Location l) { The CHM KeySet was chosen since it at least permits multiple concurrent reads without blocking. */ - Set newValue = ConcurrentHashMap.newKeySet(); - Set oldValue = tickingLocations.putIfAbsent(chunk, newValue); + Set newValue = ConcurrentHashMap.newKeySet(); + Set oldValue = tickingLocations.putIfAbsent(chunk, newValue); /** * This is faster than doing computeIfAbsent(...) * on a ConcurrentHashMap because it won't block the Thread for too long */ if (oldValue != null) { - oldValue.add(l); + oldValue.add(tickPosition); } else { - newValue.add(l); + newValue.add(tickPosition); } } } - /** - * This enables the ticker at the given {@link Location} and adds it to our "queue". - * - * @param uuid - * The {@link UUID} to activate - */ - public void enableTicker(@Nonnull UUID uuid, @Nonnull Location l) { - Validate.notNull(uuid, "UUID cannot be null!"); - Validate.notNull(l, "Location cannot be null!"); - - synchronized (tickingUniversalLocations) { - ChunkPosition chunk = new ChunkPosition(l.getWorld(), l.getBlockX() >> 4, l.getBlockZ() >> 4); - - /* - Note that all the values in #tickingLocations must be thread-safe. - Thus, the choice is between the CHM KeySet or a synchronized set. - The CHM KeySet was chosen since it at least permits multiple concurrent - reads without blocking. - */ - Map newValue = new ConcurrentHashMap<>(); - Map oldValue = tickingUniversalLocations.putIfAbsent(chunk, newValue); - - /** - * This is faster than doing computeIfAbsent(...) - * on a ConcurrentHashMap because it won't block the Thread for too long - */ - Objects.requireNonNullElse(oldValue, newValue).put(l, uuid); - } - } - /** * This method disables the ticker at the given {@link Location} and removes it from our internal * "queue". @@ -453,10 +405,10 @@ public void disableTicker(@Nonnull Location l) { synchronized (tickingLocations) { ChunkPosition chunk = new ChunkPosition(l.getWorld(), l.getBlockX() >> 4, l.getBlockZ() >> 4); - Set locations = tickingLocations.get(chunk); + Set locations = tickingLocations.get(chunk); if (locations != null) { - locations.remove(l); + locations.removeIf(tk -> l.equals(tk.getLocation())); if (locations.isEmpty()) { tickingLocations.remove(chunk); @@ -469,14 +421,17 @@ public void disableTicker(@Nonnull Location l) { * This method disables the ticker at the given {@link UUID} and removes it from our internal * "queue". * + * DO NOT USE THIS until you cannot disable by location, + * or enjoy extremely slow. + * * @param uuid * The {@link UUID} to remove */ public void disableTicker(@Nonnull UUID uuid) { Validate.notNull(uuid, "Universal Data ID cannot be null!"); - synchronized (tickingUniversalLocations) { - tickingUniversalLocations.remove(uuid); + synchronized (tickingLocations) { + tickingLocations.values().forEach(loc -> loc.removeIf(tk -> uuid.equals(tk.getUuid()))); } }