From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: wangxyper Date: Mon, 9 Jan 2023 18:43:19 +0800 Subject: [PATCH] Hearse: Fix some concurrent problems Original license: MIT Original project: https://github.com/NaturalCodeClub/HearseRewrite diff --git a/src/main/java/com/destroystokyo/paper/util/maplist/IBlockDataList.java b/src/main/java/com/destroystokyo/paper/util/maplist/IBlockDataList.java index 277cfd9d1e8fff5d9b5e534b75c3c5162d58b0b7..07247f11b079bfb631010ff06fe353d3dcc0a0f6 100644 --- a/src/main/java/com/destroystokyo/paper/util/maplist/IBlockDataList.java +++ b/src/main/java/com/destroystokyo/paper/util/maplist/IBlockDataList.java @@ -53,7 +53,7 @@ public final class IBlockDataList { return this.add(getLocationKey(x, y, z), data); } - public long add(final int location, final BlockState data) { + public synchronized long add(final int location, final BlockState data) { final long curr = this.map.get((short)location); if (curr == Long.MAX_VALUE) { @@ -81,7 +81,7 @@ public final class IBlockDataList { return this.remove(getLocationKey(x, y, z)); } - public long remove(final int location) { + public synchronized long remove(final int location) { final long ret = this.map.remove((short)location); final int index = getIndexFromRaw(ret); if (ret == Long.MAX_VALUE) { @@ -101,11 +101,11 @@ public final class IBlockDataList { return ret; } - public int size() { + public synchronized int size() { return this.size; } - public long getRaw(final int index) { + public synchronized long getRaw(final int index) { return this.byIndex[index]; } @@ -117,12 +117,12 @@ public final class IBlockDataList { return getBlockDataFromRaw(this.getRaw(index)); } - public void clear() { + public synchronized void clear() { this.size = 0; this.map.clear(); } - public LongIterator getRawIterator() { + public synchronized LongIterator getRawIterator() { return this.map.values().iterator(); } } diff --git a/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java b/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java index 62a74cbdb7f04b652dddac9e9c6191d5b86c3323..028b23f5c23bbfd83498c3e06a56079ceb0798ad 100644 --- a/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java +++ b/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java @@ -7,9 +7,9 @@ import io.papermc.paper.util.CoordinateUtils; import io.papermc.paper.util.IntervalledCounter; import io.papermc.paper.util.TickThread; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; -import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; -import it.unimi.dsi.fastutil.objects.Reference2ObjectLinkedOpenHashMap; -import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet; +import it.unimi.dsi.fastutil.longs.LongSet; +import it.unimi.dsi.fastutil.longs.LongSets; +import it.unimi.dsi.fastutil.objects.*; import net.minecraft.network.protocol.game.ClientboundSetChunkCacheCenterPacket; import net.minecraft.network.protocol.game.ClientboundSetChunkCacheRadiusPacket; import net.minecraft.network.protocol.game.ClientboundSetSimulationDistancePacket; @@ -24,9 +24,9 @@ import org.bukkit.craftbukkit.entity.CraftPlayer; import org.bukkit.entity.Player; import java.util.*; +import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.locks.StampedLock; public final class PlayerChunkLoader { @@ -37,11 +37,11 @@ public final class PlayerChunkLoader { public static final int LOADED_TICKET_LEVEL = 33; public static int getTickViewDistance(final Player player) { - return getTickViewDistance(((CraftPlayer) player).getHandle()); + return getTickViewDistance(((CraftPlayer)player).getHandle()); } public static int getTickViewDistance(final ServerPlayer player) { - final ServerLevel level = (ServerLevel) player.level; + final ServerLevel level = (ServerLevel)player.level; final PlayerLoaderData data = level.chunkSource.chunkMap.playerChunkManager.getData(player); if (data == null) { return level.chunkSource.chunkMap.playerChunkManager.getTargetTickViewDistance(); @@ -50,11 +50,11 @@ public final class PlayerChunkLoader { } public static int getLoadViewDistance(final Player player) { - return getLoadViewDistance(((CraftPlayer) player).getHandle()); + return getLoadViewDistance(((CraftPlayer)player).getHandle()); } public static int getLoadViewDistance(final ServerPlayer player) { - final ServerLevel level = (ServerLevel) player.level; + final ServerLevel level = (ServerLevel)player.level; final PlayerLoaderData data = level.chunkSource.chunkMap.playerChunkManager.getData(player); if (data == null) { return level.chunkSource.chunkMap.playerChunkManager.getLoadDistance(); @@ -63,11 +63,11 @@ public final class PlayerChunkLoader { } public static int getSendViewDistance(final Player player) { - return getSendViewDistance(((CraftPlayer) player).getHandle()); + return getSendViewDistance(((CraftPlayer)player).getHandle()); } public static int getSendViewDistance(final ServerPlayer player) { - final ServerLevel level = (ServerLevel) player.level; + final ServerLevel level = (ServerLevel)player.level; final PlayerLoaderData data = level.chunkSource.chunkMap.playerChunkManager.getData(player); if (data == null) { return level.chunkSource.chunkMap.playerChunkManager.getTargetSendDistance(); @@ -76,10 +76,10 @@ public final class PlayerChunkLoader { } protected final ChunkMap chunkMap; - protected final Reference2ObjectLinkedOpenHashMap playerMap = new Reference2ObjectLinkedOpenHashMap<>(512, 0.7f); - protected final ReferenceLinkedOpenHashSet chunkSendQueue = new ReferenceLinkedOpenHashSet<>(512, 0.7f); + protected final Reference2ObjectMap playerMap = Reference2ObjectMaps.synchronize(new Reference2ObjectLinkedOpenHashMap<>(512, 0.7f)); + protected final Deque chunkSendQueue = new ConcurrentLinkedDeque<>(); - protected final TreeSet chunkLoadQueue = new TreeSet<>((final PlayerLoaderData p1, final PlayerLoaderData p2) -> { + protected final NavigableSet chunkLoadQueue = new ConcurrentSkipListSet<>((final PlayerLoaderData p1, final PlayerLoaderData p2) -> { if (p1 == p2) { return 0; } @@ -308,8 +308,8 @@ public final class PlayerChunkLoader { }); } - protected final LongOpenHashSet isTargetedForPlayerLoad = new LongOpenHashSet(); - protected final LongOpenHashSet chunkTicketTracker = new LongOpenHashSet(); + protected final LongSet isTargetedForPlayerLoad = LongSets.synchronize(new LongOpenHashSet()); + protected final LongSet chunkTicketTracker = LongSets.synchronize(new LongOpenHashSet()); public boolean isChunkNearPlayers(final int chunkX, final int chunkZ) { final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInSendRange = this.broadcastMap.getObjectsInRange(chunkX, chunkZ); @@ -383,15 +383,15 @@ public final class PlayerChunkLoader { protected int getMaxChunkLoads() { double config = GlobalConfiguration.get().chunkLoading.playerMaxConcurrentLoads; double max = GlobalConfiguration.get().chunkLoading.globalMaxConcurrentLoads; - return (int) Math.ceil(Math.min(config * MinecraftServer.getServer().getPlayerCount(), max <= 1.0 ? Double.MAX_VALUE : max)); + return (int)Math.ceil(Math.min(config * MinecraftServer.getServer().getPlayerCount(), max <= 1.0 ? Double.MAX_VALUE : max)); } protected long getTargetSendPerPlayerAddend() { - return GlobalConfiguration.get().chunkLoading.targetPlayerChunkSendRate <= 1.0 ? 0L : (long) Math.round(1.0e9 / GlobalConfiguration.get().chunkLoading.targetPlayerChunkSendRate); + return GlobalConfiguration.get().chunkLoading.targetPlayerChunkSendRate <= 1.0 ? 0L : (long)Math.round(1.0e9 / GlobalConfiguration.get().chunkLoading.targetPlayerChunkSendRate); } protected long getMaxSendAddend() { - return GlobalConfiguration.get().chunkLoading.globalMaxChunkSendRate <= 1.0 ? 0L : (long) Math.round(1.0e9 / GlobalConfiguration.get().chunkLoading.globalMaxChunkSendRate); + return GlobalConfiguration.get().chunkLoading.globalMaxChunkSendRate <= 1.0 ? 0L : (long)Math.round(1.0e9 / GlobalConfiguration.get().chunkLoading.globalMaxChunkSendRate); } public void onChunkPlayerTickReady(final int chunkX, final int chunkZ) { @@ -413,7 +413,7 @@ public final class PlayerChunkLoader { if (!(raw instanceof ServerPlayer)) { continue; } - this.onChunkSendReady((ServerPlayer) raw, chunkX, chunkZ); + this.onChunkSendReady((ServerPlayer)raw, chunkX, chunkZ); } } @@ -481,11 +481,8 @@ public final class PlayerChunkLoader { return; } loaderData.remove(); - this.chunkLoadQueue.remove(loaderData); - this.chunkSendQueue.remove(loaderData); - this.chunkSendWaitQueue.remove(loaderData); synchronized (this.sendingChunkCounts) { final int count = this.sendingChunkCounts.removeInt(loaderData); @@ -521,23 +518,21 @@ public final class PlayerChunkLoader { protected static final AtomicInteger concurrentChunkSends = new AtomicInteger(); protected final Reference2IntOpenHashMap sendingChunkCounts = new Reference2IntOpenHashMap<>(); private static long nextChunkSend; - private void trySendChunks() { final long time = System.nanoTime(); if (time < nextChunkSend) { return; } + PlayerLoaderData data1; // drain entries from wait queue - while (!this.chunkSendWaitQueue.isEmpty()) { - final PlayerLoaderData data = this.chunkSendWaitQueue.first(); - - if (data.nextChunkSendTarget > time) { + while ((data1 = this.chunkSendWaitQueue.pollFirst())!=null) { + if (data1.nextChunkSendTarget > time) { break; } this.chunkSendWaitQueue.pollFirst(); - this.chunkSendQueue.add(data); + this.chunkSendQueue.add(data1); } if (this.chunkSendQueue.isEmpty()) { @@ -546,11 +541,9 @@ public final class PlayerChunkLoader { final int maxSends = this.getMaxConcurrentChunkSends(); final long nextPlayerDeadline = this.getTargetSendPerPlayerAddend() + time; - for (; ; ) { - if (this.chunkSendQueue.isEmpty()) { - break; - } - + final Deque tempCopy = new ArrayDeque<>(this.chunkSendQueue); + PlayerLoaderData data; + while ((data = tempCopy.pollFirst())!=null) { final int currSends = concurrentChunkSends.get(); if (currSends >= maxSends) { break; @@ -559,19 +552,12 @@ public final class PlayerChunkLoader { if (!concurrentChunkSends.compareAndSet(currSends, currSends + 1)) { continue; } - // send chunk - - PlayerLoaderData data = this.chunkSendQueue.removeFirst(); - + this.chunkSendQueue.remove(data); final ChunkPriorityHolder queuedSend = data.sendQueue.pollFirst(); if (queuedSend == null) { concurrentChunkSends.getAndDecrement(); // we never sent, so decrease // stop iterating over players who have nothing to send - if (this.chunkSendQueue.isEmpty()) { - // nothing left - break; - } continue; } @@ -580,22 +566,24 @@ public final class PlayerChunkLoader { } data.nextChunkSendTarget = nextPlayerDeadline; + this.chunkSendWaitQueue.add(data); synchronized (this.sendingChunkCounts) { this.sendingChunkCounts.addTo(data, 1); } + final PlayerLoaderData finalData = data; data.sendChunk(queuedSend.chunkX, queuedSend.chunkZ, () -> { synchronized (this.sendingChunkCounts) { - final int count = this.sendingChunkCounts.getInt(data); + final int count = this.sendingChunkCounts.getInt(finalData); if (count == 0) { // disconnected, so we don't need to decrement: it will be decremented for us return; } if (count == 1) { - this.sendingChunkCounts.removeInt(data); + this.sendingChunkCounts.removeInt(finalData); } else { - this.sendingChunkCounts.put(data, count - 1); + this.sendingChunkCounts.put(finalData, count - 1); } } @@ -611,10 +599,9 @@ public final class PlayerChunkLoader { protected int concurrentChunkLoads; // this interval prevents bursting a lot of chunk loads - protected static final IntervalledCounter TICKET_ADDITION_COUNTER_SHORT = new IntervalledCounter((long) (1.0e6 * 50.0)); // 50ms + protected static final IntervalledCounter TICKET_ADDITION_COUNTER_SHORT = new IntervalledCounter((long)(1.0e6 * 50.0)); // 50ms // this interval ensures the rate is kept between ticks correctly - protected static final IntervalledCounter TICKET_ADDITION_COUNTER_LONG = new IntervalledCounter((long) (1.0e6 * 1000.0)); // 1000ms - + protected static final IntervalledCounter TICKET_ADDITION_COUNTER_LONG = new IntervalledCounter((long)(1.0e6 * 1000.0)); // 1000ms private void tryLoadChunks() { if (this.chunkLoadQueue.isEmpty()) { return; @@ -623,16 +610,12 @@ public final class PlayerChunkLoader { final int maxLoads = this.getMaxChunkLoads(); final long time = System.nanoTime(); boolean updatedCounters = false; - for (; ; ) { - PlayerLoaderData data = this.chunkLoadQueue.pollFirst(); - + PlayerLoaderData data; + while ((data = this.chunkLoadQueue.pollFirst())!=null) { data.lastChunkLoad = time; final ChunkPriorityHolder queuedLoad = data.loadQueue.peekFirst(); if (queuedLoad == null) { - if (this.chunkLoadQueue.isEmpty()) { - break; - } continue; } @@ -648,6 +631,7 @@ public final class PlayerChunkLoader { // already loaded! data.loadQueue.pollFirst(); // already loaded so we just skip this.chunkLoadQueue.add(data); + // ensure the chunk is queued to send this.onChunkSendReady(queuedLoad.chunkX, queuedLoad.chunkZ); continue; @@ -768,7 +752,7 @@ public final class PlayerChunkLoader { protected static final double PRIORITISED_DISTANCE = 12.0 * 16.0; // Player max sprint speed is approximately 8m/s - protected static final double LOOK_PRIORITY_SPEED_THRESHOLD = (10.0 / 20.0) * (10.0 / 20.0); + protected static final double LOOK_PRIORITY_SPEED_THRESHOLD = (10.0/20.0) * (10.0/20.0); protected static final double LOOK_PRIORITY_YAW_DELTA_RECALC_THRESHOLD = 3.0f; protected double lastLocX = Double.NEGATIVE_INFINITY; @@ -790,11 +774,11 @@ public final class PlayerChunkLoader { // warning: modifications of this field must be aware that the loadQueue inside PlayerChunkLoader uses this field // in a comparator! - protected final ArrayDeque loadQueue = new ArrayDeque<>(); - protected final LongOpenHashSet sentChunks = new LongOpenHashSet(); - protected final LongOpenHashSet chunksToBeSent = new LongOpenHashSet(); + protected final Deque loadQueue = new ConcurrentLinkedDeque<>(); + protected final LongSet sentChunks = LongSets.synchronize(new LongOpenHashSet()); + protected final LongSet chunksToBeSent = LongSets.synchronize(new LongOpenHashSet()); - protected final TreeSet sendQueue = new TreeSet<>((final ChunkPriorityHolder p1, final ChunkPriorityHolder p2) -> { + protected final NavigableSet sendQueue = new ConcurrentSkipListSet<>((final ChunkPriorityHolder p1, final ChunkPriorityHolder p2) -> { final int distanceCompare = Integer.compare(p1.manhattanDistanceToPlayer, p2.manhattanDistanceToPlayer); if (distanceCompare != 0) { return distanceCompare; @@ -815,9 +799,9 @@ public final class PlayerChunkLoader { protected long nextChunkSendTarget; // this interval prevents bursting a lot of chunk loads - protected final IntervalledCounter ticketAdditionCounterShort = new IntervalledCounter((long) (1.0e6 * 50.0)); // 50ms + protected final IntervalledCounter ticketAdditionCounterShort = new IntervalledCounter((long)(1.0e6 * 50.0)); // 50ms // this ensures the rate is kept between ticks correctly - protected final IntervalledCounter ticketAdditionCounterLong = new IntervalledCounter((long) (1.0e6 * 1000.0)); // 1000ms + protected final IntervalledCounter ticketAdditionCounterLong = new IntervalledCounter((long)(1.0e6 * 1000.0)); // 1000ms public long lastChunkLoad; @@ -914,14 +898,14 @@ public final class PlayerChunkLoader { // b = ((p3z - p1z)(targetX - p3x) + (p1x - p3x)(targetZ - p3z)) / d // c = 1.0 - a - b - final double d = (p2z - p3z) * (p1x - p3x) + (p3x - p2x) * (p1z - p3z); - final double a = ((p2z - p3z) * (targetX - p3x) + (p3x - p2x) * (targetZ - p3z)) / d; + final double d = (p2z - p3z)*(p1x - p3x) + (p3x - p2x)*(p1z - p3z); + final double a = ((p2z - p3z)*(targetX - p3x) + (p3x - p2x)*(targetZ - p3z)) / d; if (a < 0.0 || a > 1.0) { return false; } - final double b = ((p3z - p1z) * (targetX - p3x) + (p1x - p3x) * (targetZ - p3z)) / d; + final double b = ((p3z - p1z)*(targetX - p3x) + (p1x - p3x)*(targetZ - p3z)) / d; if (b < 0.0 || b > 1.0) { return false; } @@ -1024,15 +1008,15 @@ public final class PlayerChunkLoader { final double p1z = posZ; // to the left of the looking direction - final double p2x = PRIORITISED_DISTANCE * Math.cos(Math.toRadians(yaw + (double) (FOV / 2.0))) // calculate rotated vector + final double p2x = PRIORITISED_DISTANCE * Math.cos(Math.toRadians(yaw + (double)(FOV / 2.0))) // calculate rotated vector + p1x; // offset vector - final double p2z = PRIORITISED_DISTANCE * Math.sin(Math.toRadians(yaw + (double) (FOV / 2.0))) // calculate rotated vector + final double p2z = PRIORITISED_DISTANCE * Math.sin(Math.toRadians(yaw + (double)(FOV / 2.0))) // calculate rotated vector + p1z; // offset vector // to the right of the looking direction - final double p3x = PRIORITISED_DISTANCE * Math.cos(Math.toRadians(yaw - (double) (FOV / 2.0))) // calculate rotated vector + final double p3x = PRIORITISED_DISTANCE * Math.cos(Math.toRadians(yaw - (double)(FOV / 2.0))) // calculate rotated vector + p1x; // offset vector - final double p3z = PRIORITISED_DISTANCE * Math.sin(Math.toRadians(yaw - (double) (FOV / 2.0))) // calculate rotated vector + final double p3z = PRIORITISED_DISTANCE * Math.sin(Math.toRadians(yaw - (double)(FOV / 2.0))) // calculate rotated vector + p1z; // offset vector // now that we have all of our points, we can recalculate the load queue @@ -1070,7 +1054,7 @@ public final class PlayerChunkLoader { p1x, p1z, p2x, p2z, p3x, p3z, // center of chunk - (double) ((chunkX << 4) | 8), (double) ((chunkZ << 4) | 8) + (double)((chunkX << 4) | 8), (double)((chunkZ << 4) | 8) ); final int manhattanDistance = Math.abs(dx) + Math.abs(dz); @@ -1085,9 +1069,9 @@ public final class PlayerChunkLoader { if (prioritised) { // we don't prioritise these chunks above others because we also want to make sure some chunks // will be loaded if the player changes direction - priority = (double) manhattanDistance / 6.0; + priority = (double)manhattanDistance / 6.0; } else { - priority = (double) manhattanDistance; + priority = (double)manhattanDistance; } } diff --git a/src/main/java/io/papermc/paper/chunk/system/entity/EntityLookup.java b/src/main/java/io/papermc/paper/chunk/system/entity/EntityLookup.java index 26e1b4060f2a93cb659170f83e6ce64086e0eb0c..d6951b05128fea7eb5f1b40837cea77e0c209165 100644 --- a/src/main/java/io/papermc/paper/chunk/system/entity/EntityLookup.java +++ b/src/main/java/io/papermc/paper/chunk/system/entity/EntityLookup.java @@ -423,8 +423,10 @@ public final class EntityLookup implements LevelEntityGetter { final ChunkEntitySlices old = this.getChunk(entity.sectionX, entity.sectionZ); final ChunkEntitySlices slices = this.getOrCreateChunk(newSectionX, newSectionZ); - if (!old.removeEntity(entity, entity.sectionY)) { - LOGGER.warn("Could not remove entity " + entity + " from its old chunk section (" + entity.sectionX + "," + entity.sectionY + "," + entity.sectionZ + ") since it was not contained in the section"); + if (old!=null){ + if (!old.removeEntity(entity, entity.sectionY)) { + LOGGER.warn("Could not remove entity " + entity + " from its old chunk section (" + entity.sectionX + "," + entity.sectionY + "," + entity.sectionZ + ") since it was not contained in the section"); + } } if (!slices.addEntity(entity, newSectionY)) { diff --git a/src/main/java/io/papermc/paper/util/misc/Delayed26WayDistancePropagator3D.java b/src/main/java/io/papermc/paper/util/misc/Delayed26WayDistancePropagator3D.java index 470402573bc31106d5a63e415b958fb7f9c36aa9..762f09c8f374fbccc9f5be985401ad334e1655a0 100644 --- a/src/main/java/io/papermc/paper/util/misc/Delayed26WayDistancePropagator3D.java +++ b/src/main/java/io/papermc/paper/util/misc/Delayed26WayDistancePropagator3D.java @@ -94,24 +94,24 @@ public final class Delayed26WayDistancePropagator3D { protected final void addToIncreaseWorkQueue(final long coordinate, final byte level) { final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelIncreaseWorkQueues[level]; - queue.queuedCoordinates.enqueue(coordinate); - queue.queuedLevels.enqueue(level); + queue.queuedCoordinates.add(coordinate); + queue.queuedLevels.add(level); this.levelIncreaseWorkQueueBitset |= (1L << level); } protected final void addToIncreaseWorkQueue(final long coordinate, final byte index, final byte level) { final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelIncreaseWorkQueues[index]; - queue.queuedCoordinates.enqueue(coordinate); - queue.queuedLevels.enqueue(level); + queue.queuedCoordinates.add(coordinate); + queue.queuedLevels.add(level); this.levelIncreaseWorkQueueBitset |= (1L << index); } protected final void addToRemoveWorkQueue(final long coordinate, final byte level) { final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelRemoveWorkQueues[level]; - queue.queuedCoordinates.enqueue(coordinate); - queue.queuedLevels.enqueue(level); + queue.queuedCoordinates.add(coordinate); + queue.queuedLevels.add(level); this.levelRemoveWorkQueueBitset |= (1L << level); } @@ -164,8 +164,8 @@ public final class Delayed26WayDistancePropagator3D { final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelIncreaseWorkQueues[queueIndex]; while (!queue.queuedLevels.isEmpty()) { - final long coordinate = queue.queuedCoordinates.removeFirstLong(); - byte level = queue.queuedLevels.removeFirstByte(); + final long coordinate = queue.queuedCoordinates.removeFirst(); + byte level = queue.queuedLevels.removeFirst(); final boolean neighbourCheck = level < 0; @@ -233,8 +233,8 @@ public final class Delayed26WayDistancePropagator3D { final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelRemoveWorkQueues[queueIndex]; while (!queue.queuedLevels.isEmpty()) { - final long coordinate = queue.queuedCoordinates.removeFirstLong(); - final byte level = queue.queuedLevels.removeFirstByte(); + final long coordinate = queue.queuedCoordinates.removeFirst(); + final byte level = queue.queuedLevels.removeFirst(); final byte currentLevel = this.levels.removeIfGreaterOrEqual(coordinate, level); if (currentLevel == 0) { diff --git a/src/main/java/io/papermc/paper/util/misc/Delayed8WayDistancePropagator2D.java b/src/main/java/io/papermc/paper/util/misc/Delayed8WayDistancePropagator2D.java index 808d1449ac44ae86a650932365081fbaf178d141..8c5a51b5992eccf3627f326e164288b5f6bbcff6 100644 --- a/src/main/java/io/papermc/paper/util/misc/Delayed8WayDistancePropagator2D.java +++ b/src/main/java/io/papermc/paper/util/misc/Delayed8WayDistancePropagator2D.java @@ -1,12 +1,13 @@ package io.papermc.paper.util.misc; +import io.papermc.paper.util.MCUtil; import it.unimi.dsi.fastutil.HashCommon; -import it.unimi.dsi.fastutil.bytes.ByteArrayFIFOQueue; import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap; -import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; import it.unimi.dsi.fastutil.longs.LongIterator; import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet; -import io.papermc.paper.util.MCUtil; + +import java.util.Deque; +import java.util.concurrent.ConcurrentLinkedDeque; public final class Delayed8WayDistancePropagator2D { @@ -356,24 +357,24 @@ public final class Delayed8WayDistancePropagator2D { protected final void addToIncreaseWorkQueue(final long coordinate, final byte level) { final WorkQueue queue = this.levelIncreaseWorkQueues[level]; - queue.queuedCoordinates.enqueue(coordinate); - queue.queuedLevels.enqueue(level); + queue.queuedCoordinates.add(coordinate); + queue.queuedLevels.add(level); this.levelIncreaseWorkQueueBitset |= (1L << level); } protected final void addToIncreaseWorkQueue(final long coordinate, final byte index, final byte level) { final WorkQueue queue = this.levelIncreaseWorkQueues[index]; - queue.queuedCoordinates.enqueue(coordinate); - queue.queuedLevels.enqueue(level); + queue.queuedCoordinates.add(coordinate); + queue.queuedLevels.add(level); this.levelIncreaseWorkQueueBitset |= (1L << index); } protected final void addToRemoveWorkQueue(final long coordinate, final byte level) { final WorkQueue queue = this.levelRemoveWorkQueues[level]; - queue.queuedCoordinates.enqueue(coordinate); - queue.queuedLevels.enqueue(level); + queue.queuedCoordinates.add(coordinate); + queue.queuedLevels.add(level); this.levelRemoveWorkQueueBitset |= (1L << level); } @@ -426,8 +427,8 @@ public final class Delayed8WayDistancePropagator2D { final WorkQueue queue = this.levelIncreaseWorkQueues[queueIndex]; while (!queue.queuedLevels.isEmpty()) { - final long coordinate = queue.queuedCoordinates.removeFirstLong(); - byte level = queue.queuedLevels.removeFirstByte(); + final long coordinate = queue.queuedCoordinates.removeFirst(); + byte level = queue.queuedLevels.removeFirst(); final boolean neighbourCheck = level < 0; @@ -492,8 +493,8 @@ public final class Delayed8WayDistancePropagator2D { final WorkQueue queue = this.levelRemoveWorkQueues[queueIndex]; while (!queue.queuedLevels.isEmpty()) { - final long coordinate = queue.queuedCoordinates.removeFirstLong(); - final byte level = queue.queuedLevels.removeFirstByte(); + final long coordinate = queue.queuedCoordinates.removeFirst(); + final byte level = queue.queuedLevels.removeFirst(); final byte currentLevel = this.levels.removeIfGreaterOrEqual(coordinate, level); if (currentLevel == 0) { @@ -678,41 +679,8 @@ public final class Delayed8WayDistancePropagator2D { } protected static final class WorkQueue { - - public final NoResizeLongArrayFIFODeque queuedCoordinates = new NoResizeLongArrayFIFODeque(); - public final NoResizeByteArrayFIFODeque queuedLevels = new NoResizeByteArrayFIFODeque(); - - } - - protected static final class NoResizeLongArrayFIFODeque extends LongArrayFIFOQueue { - - /** - * Assumes non-empty. If empty, undefined behaviour. - */ - public long removeFirstLong() { - // copied from superclass - long t = this.array[this.start]; - if (++this.start == this.length) { - this.start = 0; - } - - return t; - } + public final Deque queuedCoordinates = new ConcurrentLinkedDeque<>(); + public final Deque queuedLevels = new ConcurrentLinkedDeque<>(); } - protected static final class NoResizeByteArrayFIFODeque extends ByteArrayFIFOQueue { - - /** - * Assumes non-empty. If empty, undefined behaviour. - */ - public byte removeFirstByte() { - // copied from superclass - byte t = this.array[this.start]; - if (++this.start == this.length) { - this.start = 0; - } - - return t; - } - } } diff --git a/src/main/java/net/himeki/mcmtfabric/parallelised/ConcurrentDoublyLinkedList.java b/src/main/java/net/himeki/mcmtfabric/parallelised/ConcurrentDoublyLinkedList.java new file mode 100644 index 0000000000000000000000000000000000000000..22b9d217dc06caaf8fbec21f0e31aa1cd13144ee --- /dev/null +++ b/src/main/java/net/himeki/mcmtfabric/parallelised/ConcurrentDoublyLinkedList.java @@ -0,0 +1,945 @@ +package net.himeki.mcmtfabric.parallelised; + +/* + * From: http://www.java2s.com/Code/Java/Collections-Data-Structure/ConcurrentDoublyLinkedList.htm + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/licenses/publicdomain + * + * Modified to actually implement List + */ + +import java.util.AbstractCollection; +import java.util.ArrayList; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.Deque; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.NoSuchElementException; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.commons.lang3.NotImplementedException; + +/** + * A concurrent linked-list implementation of a {@link Deque} (double-ended + * queue). Concurrent insertion, removal, and access operations execute safely + * across multiple threads. Iterators are weakly consistent, returning + * elements reflecting the state of the deque at some point at or since the + * creation of the iterator. They do not throw + * {@link ConcurrentModificationException}, and may proceed concurrently with + * other operations. + * + *

+ * This class and its iterators implement all of the optional methods + * of the {@link Collection} and {@link Iterator} interfaces. Like most other + * concurrent collection implementations, this class does not permit the use of + * null elements. because some null arguments and return values cannot + * be reliably distinguished from the absence of elements. Arbitrarily, the + * {@link Collection#remove} method is mapped to removeFirstOccurrence, + * and {@link Collection#add} is mapped to addLast. + * + *

+ * Beware that, unlike in most collections, the size method is + * NOT a constant-time operation. Because of the asynchronous nature of + * these deques, determining the current number of elements requires a traversal + * of the elements. + * + *

+ * This class is Serializable, but relies on default serialization + * mechanisms. Usually, it is a better idea for any serializable class using a + * ConcurrentLinkedDeque to instead serialize a snapshot of the + * elements obtained by method toArray. + * + * @author Doug Lea + * @param the type of elements held in this collection + */ + +public class ConcurrentDoublyLinkedList extends AbstractCollection implements List, java.io.Serializable { + + /* + * This is an adaptation of an algorithm described in Paul Martin's "A Practical + * Lock-Free Doubly-Linked List". Sun Labs Tech report. The basic idea is to + * primarily rely on next-pointers to ensure consistency. Prev-pointers are in + * part optimistic, reconstructed using forward pointers as needed. The main + * forward list uses a variant of HM-list algorithm similar to the one used in + * ConcurrentSkipListMap class, but a little simpler. It is also basically + * similar to the approach in Edya Ladan-Mozes and Nir Shavit "An Optimistic + * Approach to Lock-Free FIFO Queues" in DISC04. + * + * Quoting a summary in Paul Martin's tech report: + * + * All cleanups work to maintain these invariants: (1) forward pointers are the + * ground truth. (2) forward pointers to dead nodes can be improved by swinging + * them further forward around the dead node. (2.1) forward pointers are still + * correct when pointing to dead nodes, and forward pointers from dead nodes are + * left as they were when the node was deleted. (2.2) multiple dead nodes may + * point forward to the same node. (3) backward pointers were correct when they + * were installed (3.1) backward pointers are correct when pointing to any node + * which points forward to them, but since more than one forward pointer may + * point to them, the live one is best. (4) backward pointers that are out of + * date due to deletion point to a deleted node, and need to point further back + * until they point to the live node that points to their source. (5) backward + * pointers that are out of date due to insertion point too far backwards, so + * shortening their scope (by searching forward) fixes them. (6) backward + * pointers from a dead node cannot be "improved" since there may be no live + * node pointing forward to their origin. (However, it does no harm to try to + * improve them while racing with a deletion.) + * + * + * Notation guide for local variables n, b, f : a node, its predecessor, and + * successor s : some other successor + */ + + // Minor convenience utilities + + /** + * Returns true if given reference is non-null and isn't a header, trailer, or + * marker. + * + * @param n (possibly null) node + * @return true if n exists as a user node + */ + private static boolean usable(Node n) { + return n != null && !n.isSpecial(); + } + + /** + * Throws NullPointerException if argument is null + * + * @param v the element + */ + private static void checkNullArg(Object v) { + if (v == null) + throw new NullPointerException(); + } + + /** + * Returns element unless it is null, in which case throws + * NoSuchElementException. + * + * @param v the element + * @return the element + */ + private E screenNullResult(E v) { + if (v == null) + throw new NoSuchElementException(); + return v; + } + + /** + * Creates an array list and fills it with elements of this list. Used by + * toArray. + * + * @return the arrayList + */ + private ArrayList toArrayList() { + ArrayList c = new ArrayList(); + for (Node n = header.forward(); n != null; n = n.forward()) + c.add(n.element); + return c; + } + + // Fields and constructors + + private static final long serialVersionUID = 876323262645176354L; + + /** + * List header. First usable node is at header.forward(). + */ + private final Node header; + + /** + * List trailer. Last usable node is at trailer.back(). + */ + private final Node trailer; + + /** + * Constructs an empty deque. + */ + public ConcurrentDoublyLinkedList() { + Node h = new Node(null, null, null); + Node t = new Node(null, null, h); + h.setNext(t); + header = h; + trailer = t; + } + + /** + * Constructs a deque containing the elements of the specified collection, in + * the order they are returned by the collection's iterator. + * + * @param c the collection whose elements are to be placed into this deque. + * @throws NullPointerException if c or any element within it is + * null + */ + public ConcurrentDoublyLinkedList(Collection c) { + this(); + addAll(c); + } + + /** + * Prepends the given element at the beginning of this deque. + * + * @param o the element to be inserted at the beginning of this deque. + * @throws NullPointerException if the specified element is null + */ + public void addFirst(E o) { + checkNullArg(o); + while (header.append(o) == null) + ; + } + + /** + * Appends the given element to the end of this deque. This is identical in + * function to the add method. + * + * @param o the element to be inserted at the end of this deque. + * @throws NullPointerException if the specified element is null + */ + public void addLast(E o) { + checkNullArg(o); + while (trailer.prepend(o) == null) + ; + } + + /** + * Prepends the given element at the beginning of this deque. + * + * @param o the element to be inserted at the beginning of this deque. + * @return true always + * @throws NullPointerException if the specified element is null + */ + public boolean offerFirst(E o) { + addFirst(o); + return true; + } + + /** + * Appends the given element to the end of this deque. (Identical in function to + * the add method; included only for consistency.) + * + * @param o the element to be inserted at the end of this deque. + * @return true always + * @throws NullPointerException if the specified element is null + */ + public boolean offerLast(E o) { + addLast(o); + return true; + } + + /** + * Retrieves, but does not remove, the first element of this deque, or returns + * null if this deque is empty. + * + * @return the first element of this queue, or null if empty. + */ + public E peekFirst() { + Node n = header.successor(); + return (n == null) ? null : n.element; + } + + /** + * Retrieves, but does not remove, the last element of this deque, or returns + * null if this deque is empty. + * + * @return the last element of this deque, or null if empty. + */ + public E peekLast() { + Node n = trailer.predecessor(); + return (n == null) ? null : n.element; + } + + /** + * Returns the first element in this deque. + * + * @return the first element in this deque. + * @throws NoSuchElementException if this deque is empty. + */ + public E getFirst() { + return screenNullResult(peekFirst()); + } + + /** + * Returns the last element in this deque. + * + * @return the last element in this deque. + * @throws NoSuchElementException if this deque is empty. + */ + public E getLast() { + return screenNullResult(peekLast()); + } + + /** + * Retrieves and removes the first element of this deque, or returns null if + * this deque is empty. + * + * @return the first element of this deque, or null if empty. + */ + public E pollFirst() { + for (;;) { + Node n = header.successor(); + if (!usable(n)) + return null; + if (n.delete()) + return n.element; + } + } + + /** + * Retrieves and removes the last element of this deque, or returns null if this + * deque is empty. + * + * @return the last element of this deque, or null if empty. + */ + public E pollLast() { + for (;;) { + Node n = trailer.predecessor(); + if (!usable(n)) + return null; + if (n.delete()) + return n.element; + } + } + + /** + * Removes and returns the first element from this deque. + * + * @return the first element from this deque. + * @throws NoSuchElementException if this deque is empty. + */ + public E removeFirst() { + return screenNullResult(pollFirst()); + } + + /** + * Removes and returns the last element from this deque. + * + * @return the last element from this deque. + * @throws NoSuchElementException if this deque is empty. + */ + public E removeLast() { + return screenNullResult(pollLast()); + } + + // *** Queue and stack methods *** + public boolean offer(E e) { + return offerLast(e); + } + + public boolean add(E e) { + return offerLast(e); + } + + public E poll() { + return pollFirst(); + } + + public E remove() { + return removeFirst(); + } + + public E peek() { + return peekFirst(); + } + + public E element() { + return getFirst(); + } + + public void push(E e) { + addFirst(e); + } + + public E pop() { + return removeFirst(); + } + + /** + * Removes the first element e such that o.equals(e), if such + * an element exists in this deque. If the deque does not contain the element, + * it is unchanged. + * + * @param o element to be removed from this deque, if present. + * @return true if the deque contained the specified element. + * @throws NullPointerException if the specified element is null + */ + public boolean removeFirstOccurrence(Object o) { + checkNullArg(o); + for (;;) { + Node n = header.forward(); + for (;;) { + if (n == null) + return false; + if (o.equals(n.element)) { + if (n.delete()) + return true; + else + break; // restart if interference + } + n = n.forward(); + } + } + } + + /** + * Removes the last element e such that o.equals(e), if such + * an element exists in this deque. If the deque does not contain the element, + * it is unchanged. + * + * @param o element to be removed from this deque, if present. + * @return true if the deque contained the specified element. + * @throws NullPointerException if the specified element is null + */ + public boolean removeLastOccurrence(Object o) { + checkNullArg(o); + for (;;) { + Node s = trailer; + for (;;) { + Node n = s.back(); + if (s.isDeleted() || (n != null && n.successor() != s)) + break; // restart if pred link is suspect. + if (n == null) + return false; + if (o.equals(n.element)) { + if (n.delete()) + return true; + else + break; // restart if interference + } + s = n; + } + } + } + + /** + * Returns true if this deque contains at least one element e + * such that o.equals(e). + * + * @param o element whose presence in this deque is to be tested. + * @return true if this deque contains the specified element. + */ + public boolean contains(Object o) { + if (o == null) + return false; + for (Node n = header.forward(); n != null; n = n.forward()) + if (o.equals(n.element)) + return true; + return false; + } + + /** + * Returns true if this collection contains no elements. + *

+ * + * @return true if this collection contains no elements. + */ + public boolean isEmpty() { + return !usable(header.successor()); + } + + /** + * Returns the number of elements in this deque. If this deque contains more + * than Integer.MAX_VALUE elements, it returns + * Integer.MAX_VALUE. + * + *

+ * Beware that, unlike in most collections, this method is NOT a + * constant-time operation. Because of the asynchronous nature of these deques, + * determining the current number of elements requires traversing them all to + * count them. Additionally, it is possible for the size to change during + * execution of this method, in which case the returned result will be + * inaccurate. Thus, this method is typically not very useful in concurrent + * applications. + * + * @return the number of elements in this deque. + */ + public int size() { + long count = 0; + for (Node n = header.forward(); n != null; n = n.forward()) + ++count; + return (count >= Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) count; + } + + /** + * Removes the first element e such that o.equals(e), if such + * an element exists in this deque. If the deque does not contain the element, + * it is unchanged. + * + * @param o element to be removed from this deque, if present. + * @return true if the deque contained the specified element. + * @throws NullPointerException if the specified element is null + */ + public boolean remove(Object o) { + return removeFirstOccurrence(o); + } + + /** + * Appends all of the elements in the specified collection to the end of this + * deque, in the order that they are returned by the specified collection's + * iterator. The behavior of this operation is undefined if the specified + * collection is modified while the operation is in progress. (This implies that + * the behavior of this call is undefined if the specified Collection is this + * deque, and this deque is nonempty.) + * + * @param c the elements to be inserted into this deque. + * @return true if this deque changed as a result of the call. + * @throws NullPointerException if c or any element within it is + * null + */ + public boolean addAll(Collection c) { + Iterator it = c.iterator(); + if (!it.hasNext()) + return false; + do { + addLast(it.next()); + } while (it.hasNext()); + return true; + } + + /** + * Removes all of the elements from this deque. + */ + public void clear() { + while (pollFirst() != null) + ; + } + + /** + * Returns an array containing all of the elements in this deque in the correct + * order. + * + * @return an array containing all of the elements in this deque in the correct + * order. + */ + public Object[] toArray() { + return toArrayList().toArray(); + } + + /** + * Returns an array containing all of the elements in this deque in the correct + * order; the runtime type of the returned array is that of the specified array. + * If the deque fits in the specified array, it is returned therein. Otherwise, + * a new array is allocated with the runtime type of the specified array and the + * size of this deque. + *

+ * + * If the deque fits in the specified array with room to spare (i.e., the array + * has more elements than the deque), the element in the array immediately + * following the end of the collection is set to null. This is useful in + * determining the length of the deque only if the caller knows that the + * deque does not contain any null elements. + * + * @param a the array into which the elements of the deque are to be stored, if + * it is big enough; otherwise, a new array of the same runtime type is + * allocated for this purpose. + * @return an array containing the elements of the deque. + * @throws ArrayStoreException if the runtime type of a is not a supertype of + * the runtime type of every element in this deque. + * @throws NullPointerException if the specified array is null. + */ + public T[] toArray(T[] a) { + return toArrayList().toArray(a); + } + + /** + * Returns a weakly consistent iterator over the elements in this deque, in + * first-to-last order. The next method returns elements reflecting the + * state of the deque at some point at or since the creation of the iterator. + * The method does not throw {@link ConcurrentModificationException}, + * and may proceed concurrently with other operations. + * + * @return an iterator over the elements in this deque + */ + public Iterator iterator() { + return new CLDIterator(); + } + + final class CLDIterator implements Iterator { + Node last; + + Node next = header.forward(); + + public boolean hasNext() { + return next != null; + } + + public E next() { + Node l = last = next; + if (l == null) + throw new NoSuchElementException(); + next = next.forward(); + return l.element; + } + + public void remove() { + Node l = last; + if (l == null) + throw new IllegalStateException(); + while (!l.delete() && !l.isDeleted()) + ; + } + } + + @Override + public boolean addAll(int index, Collection c) { + throw new NotImplementedException("TODO"); + } + + @Override + public E get(int index) { + Node current = header.successor(); + if (current == null) { + throw new IndexOutOfBoundsException(); + } + for (; index > 0; index --) { + current = current.successor(); + if (current == null) { + throw new IndexOutOfBoundsException(); + } + } + return current.element; + } + + @Override + public E set(int index, E element) { + throw new NotImplementedException("INVALID"); + } + + @Override + public void add(int index, E element) { + throw new NotImplementedException("INVALID"); + } + + @Override + public E remove(int index) { + throw new NotImplementedException("INVALID"); + } + + @Override + public int indexOf(Object o) { + throw new NotImplementedException("INVALID"); + } + + @Override + public int lastIndexOf(Object o) { + throw new NotImplementedException("INVALID"); + } + + @Override + public ListIterator listIterator() { + throw new NotImplementedException("INVALID"); + } + + @Override + public ListIterator listIterator(int index) { + throw new NotImplementedException("INVALID"); + } + + @Override + public List subList(int fromIndex, int toIndex) { + throw new NotImplementedException("INVALID"); + } + +} + +/** + * Linked Nodes. As a minor efficiency hack, this class opportunistically + * inherits from AtomicReference, with the atomic ref used as the "next" link. + * + * Nodes are in doubly-linked lists. There are three kinds of special nodes, + * distinguished by: * The list header has a null prev link * The list trailer + * has a null next link * A deletion marker has a prev link pointing to itself. + * All three kinds of special nodes have null element fields. + * + * Regular nodes have non-null element, next, and prev fields. To avoid visible + * inconsistencies when deletions overlap element replacement, replacements are + * done by replacing the node, not just setting the element. + * + * Nodes can be traversed by read-only ConcurrentLinkedDeque class operations + * just by following raw next pointers, so long as they ignore any special nodes + * seen along the way. (This is automated in method forward.) However, traversal + * using prev pointers is not guaranteed to see all live nodes since a prev + * pointer of a deleted node can become unrecoverably stale. + */ + +class Node extends AtomicReference> { + + private static final long serialVersionUID = 6640557564507962862L; + + private volatile Node prev; + + final E element; + + /** Creates a node with given contents */ + Node(E element, Node next, Node prev) { + super(next); + this.prev = prev; + this.element = element; + } + + /** Creates a marker node with given successor */ + Node(Node next) { + super(next); + this.prev = this; + this.element = null; + } + + /** + * Gets next link (which is actually the value held as atomic reference). + */ + private Node getNext() { + return get(); + } + + /** + * Sets next link + * + * @param n the next node + */ + void setNext(Node n) { + set(n); + } + + /** + * compareAndSet next link + */ + private boolean casNext(Node cmp, Node val) { + return compareAndSet(cmp, val); + } + + /** + * Gets prev link + */ + private Node getPrev() { + return prev; + } + + /** + * Sets prev link + * + * @param b the previous node + */ + void setPrev(Node b) { + prev = b; + } + + /** + * Returns true if this is a header, trailer, or marker node + */ + boolean isSpecial() { + return element == null; + } + + /** + * Returns true if this is a trailer node + */ + boolean isTrailer() { + return getNext() == null; + } + + /** + * Returns true if this is a header node + */ + boolean isHeader() { + return getPrev() == null; + } + + /** + * Returns true if this is a marker node + */ + boolean isMarker() { + return getPrev() == this; + } + + /** + * Returns true if this node is followed by a marker, meaning that it is + * deleted. + * + * @return true if this node is deleted + */ + boolean isDeleted() { + Node f = getNext(); + return f != null && f.isMarker(); + } + + /** + * Returns next node, ignoring deletion marker + */ + private Node nextNonmarker() { + Node f = getNext(); + return (f == null || !f.isMarker()) ? f : f.getNext(); + } + + /** + * Returns the next non-deleted node, swinging next pointer around any + * encountered deleted nodes, and also patching up successor''s prev link to + * point back to this. Returns null if this node is trailer so has no successor. + * + * @return successor, or null if no such + */ + Node successor() { + Node f = nextNonmarker(); + for (;;) { + if (f == null) + return null; + if (!f.isDeleted()) { + if (f.getPrev() != this && !isDeleted()) + f.setPrev(this); // relink f's prev + return f; + } + Node s = f.nextNonmarker(); + if (f == getNext()) + casNext(f, s); // unlink f + f = s; + } + } + + /** + * Returns the apparent predecessor of target by searching forward for it + * starting at this node, patching up pointers while traversing. Used by + * predecessor(). + * + * @return target's predecessor, or null if not found + */ + private Node findPredecessorOf(Node target) { + Node n = this; + for (;;) { + Node f = n.successor(); + if (f == target) + return n; + if (f == null) + return null; + n = f; + } + } + + /** + * Returns the previous non-deleted node, patching up pointers as needed. + * Returns null if this node is header so has no successor. May also return null + * if this node is deleted, so doesn't have a distinct predecessor. + * + * @return predecessor or null if not found + */ + Node predecessor() { + Node n = this; + for (;;) { + Node b = n.getPrev(); + if (b == null) + return n.findPredecessorOf(this); + Node s = b.getNext(); + if (s == this) + return b; + if (s == null || !s.isMarker()) { + Node p = b.findPredecessorOf(this); + if (p != null) + return p; + } + n = b; + } + } + + /** + * Returns the next node containing a nondeleted user element. Use for forward + * list traversal. + * + * @return successor, or null if no such + */ + Node forward() { + Node f = successor(); + return (f == null || f.isSpecial()) ? null : f; + } + + /** + * Returns previous node containing a nondeleted user element, if possible. Use + * for backward list traversal, but beware that if this method is called from a + * deleted node, it might not be able to determine a usable predecessor. + * + * @return predecessor, or null if no such could be found + */ + Node back() { + Node f = predecessor(); + return (f == null || f.isSpecial()) ? null : f; + } + + /** + * Tries to insert a node holding element as successor, failing if this node is + * deleted. + * + * @param element the element + * @return the new node, or null on failure. + */ + Node append(E element) { + for (;;) { + Node f = getNext(); + if (f == null || f.isMarker()) + return null; + Node x = new Node(element, f, this); + if (casNext(f, x)) { + f.setPrev(x); // optimistically link + return x; + } + } + } + + /** + * Tries to insert a node holding element as predecessor, failing if no live + * predecessor can be found to link to. + * + * @param element the element + * @return the new node, or null on failure. + */ + Node prepend(E element) { + for (;;) { + Node b = predecessor(); + if (b == null) + return null; + Node x = new Node(element, this, b); + if (b.casNext(this, x)) { + setPrev(x); // optimistically link + return x; + } + } + } + + /** + * Tries to mark this node as deleted, failing if already deleted or if this + * node is header or trailer + * + * @return true if successful + */ + boolean delete() { + Node b = getPrev(); + Node f = getNext(); + if (b != null && f != null && !f.isMarker() && casNext(f, new Node(f))) { + if (b.casNext(this, f)) + f.setPrev(b); + return true; + } + return false; + } + + /** + * Tries to insert a node holding element to replace this node. failing if + * already deleted. + * + * @param newElement the new element + * @return the new node, or null on failure. + */ + Node replace(E newElement) { + for (;;) { + Node b = getPrev(); + Node f = getNext(); + if (b == null || f == null || f.isMarker()) + return null; + Node x = new Node(newElement, f, b); + if (casNext(f, new Node(x))) { + b.successor(); // to relink b + x.successor(); // to relink f + return x; + } + } + } +} \ No newline at end of file diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java index 5ef58831a857fd8aa4ac30147762dc17d773a53e..2a8590d46bab64fe27e8dadf80f91ab0662a4352 100644 --- a/src/main/java/net/minecraft/Util.java +++ b/src/main/java/net/minecraft/Util.java @@ -65,6 +65,8 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; import javax.annotation.Nullable; + +import it.unimi.dsi.fastutil.objects.ObjectList; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.Bootstrap; import net.minecraft.util.Mth; @@ -793,7 +795,7 @@ public class Util { return objectArrayList; } - public static void shuffle(ObjectArrayList list, RandomSource random) { + public static void shuffle(ObjectList list, RandomSource random) { int i = list.size(); for(int j = i; j > 1; --j) { diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java index e642b4a83687d03e55feb340452d608c53ae7cce..4beaa69da4001fc2723e9628d64bd3de728d7213 100644 --- a/src/main/java/net/minecraft/server/level/ChunkHolder.java +++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java @@ -5,6 +5,7 @@ import com.mojang.datafixers.util.Pair; import it.unimi.dsi.fastutil.shorts.ShortArraySet; import it.unimi.dsi.fastutil.shorts.ShortSet; import it.unimi.dsi.fastutil.shorts.ShortSets; +import net.himeki.mcmtfabric.parallelised.fastutil.ConcurrentShortHashSet; import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; import net.minecraft.network.protocol.Packet; @@ -222,7 +223,7 @@ public class ChunkHolder { if (i < 0 || i >= this.changedBlocksPerSection.length) return; // CraftBukkit - SPIGOT-6086, SPIGOT-6296 if (this.changedBlocksPerSection[i] == null) { this.hasChangedSections = true; this.addToBroadcastMap(); // Paper - optimise chunk tick iteration - this.changedBlocksPerSection[i] = ShortSets.synchronize(new ShortArraySet()); + this.changedBlocksPerSection[i] = new ConcurrentShortHashSet(); } this.changedBlocksPerSection[i].add(SectionPos.sectionRelativePos(pos)); diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java index 81697ea6d00967852556c3bb741317db030c24db..85c03dc7c1e714fab281374a177cd4c54e97d939 100644 --- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java +++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java @@ -788,7 +788,7 @@ public class ServerChunkCache extends ChunkSource { //gameprofilerfiller.popPush("broadcast"); // Purpur //this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing // Purpur if (!this.chunkMap.needsChangeBroadcasting.isEmpty()) { - ObjectSet copy = new ObjectArraySet<>(this.chunkMap.needsChangeBroadcasting); + List copy = new ArrayList<>(this.chunkMap.needsChangeBroadcasting); this.chunkMap.needsChangeBroadcasting.clear(); for (ChunkHolder holder : copy) { holder.broadcastChanges(holder.getFullChunkNowUnchecked()); // LevelChunks are NEVER unloaded diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java index f33476a35706d7a236fe3bb178d166d568c07674..483c19ac24b074cbdb924d684c0892ddc546af3e 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -24,6 +24,8 @@ import java.util.function.BiConsumer; import java.util.function.Predicate; import java.util.stream.Stream; import javax.annotation.Nullable; + +import it.unimi.dsi.fastutil.objects.Object2DoubleMaps; import net.minecraft.BlockUtil; import net.minecraft.CrashReport; import net.minecraft.CrashReportCategory; @@ -511,14 +513,14 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { this.nextStep = 1.0F; this.random = SHARED_RANDOM; // Paper this.remainingFireTicks = -this.getFireImmuneTicks(); - this.fluidHeight = new Object2DoubleArrayMap(2); - this.fluidOnEyes = new HashSet(); + this.fluidHeight = Object2DoubleMaps.synchronize(new Object2DoubleArrayMap(2)); + this.fluidOnEyes = Sets.newConcurrentHashSet(); this.firstTick = true; this.levelCallback = EntityInLevelCallback.NULL; this.packetPositionCodec = new VecDeltaCodec(); this.uuid = Mth.createInsecureUUID(this.random); this.stringUUID = this.uuid.toString(); - this.tags = Sets.newHashSet(); + this.tags = Sets.newConcurrentHashSet(); this.pistonDeltas = new double[]{0.0D, 0.0D, 0.0D}; this.feetBlockState = null; this.type = type; diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java index 93c32dd39693b37efaa05af0486e1bdd298661f3..cdb33a430d0d1671899ab8bb0911193a5688af23 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java @@ -17,6 +17,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.UUID; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Predicate; import javax.annotation.Nullable; import net.minecraft.BlockUtil; @@ -181,7 +182,7 @@ public abstract class LivingEntity extends Entity { public static final float EXTRA_RENDER_CULLING_SIZE_WITH_BIG_HAT = 0.5F; private final AttributeMap attributes; public CombatTracker combatTracker = new CombatTracker(this); - public final Map activeEffects = Maps.newHashMap(); + public final Map activeEffects = Maps.newConcurrentMap(); private final NonNullList lastHandItemStacks; private final NonNullList lastArmorItemStacks; public boolean swinging; @@ -257,7 +258,7 @@ public abstract class LivingEntity extends Entity { // CraftBukkit start public int expToDrop; public boolean forceDrops; - public ArrayList drops = new ArrayList(); + public List drops = new CopyOnWriteArrayList<>(); public final org.bukkit.craftbukkit.attribute.CraftAttributeMap craftAttributes; public boolean collides = true; public Set collidableExemptions = new HashSet<>(); @@ -875,7 +876,7 @@ public abstract class LivingEntity extends Entity { // CraftBukkit start private boolean isTickingEffects = false; - private List effectsToProcess = Lists.newArrayList(); + private List effectsToProcess = Lists.newCopyOnWriteArrayList(); private static class ProcessableEffect { @@ -1779,7 +1780,7 @@ public abstract class LivingEntity extends Entity { } }); // Paper end this.postDeathDropItems(deathEvent); // Paper - this.drops = new ArrayList<>(); + this.drops = new CopyOnWriteArrayList<>(); // CraftBukkit end // this.dropInventory();// CraftBukkit - moved up diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/LongJumpToRandomPos.java b/src/main/java/net/minecraft/world/entity/ai/behavior/LongJumpToRandomPos.java index 80215972538d5cfce5f224253ea0e34ea4fd45a4..71e44f4aa64b29610f424ea91a9b54b56a155736 100644 --- a/src/main/java/net/minecraft/world/entity/ai/behavior/LongJumpToRandomPos.java +++ b/src/main/java/net/minecraft/world/entity/ai/behavior/LongJumpToRandomPos.java @@ -9,6 +9,8 @@ import java.util.function.BiPredicate; import java.util.function.Function; import java.util.stream.Collectors; import javax.annotation.Nullable; + +import net.himeki.mcmtfabric.parallelised.ConcurrentDoublyLinkedList; import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerLevel; import net.minecraft.sounds.SoundEvent; @@ -103,7 +105,7 @@ public class LongJumpToRandomPos extends Behavior { return !blockPos2.equals(blockPos); }).map((blockPos2) -> { return new LongJumpToRandomPos.PossibleJump(blockPos2.immutable(), Mth.ceil(blockPos.distSqr(blockPos2))); - }).collect(Collectors.toCollection(Lists::newArrayList)); + }).collect(Collectors.toCollection(Lists::newCopyOnWriteArrayList)); } @Override diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java index 99142f749371828f6f55e4fbab03b22eb519ec1e..fc26edc5082f701e6450ca9abf78423840cd773c 100644 --- a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java +++ b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java @@ -111,14 +111,7 @@ public class GoalSelector { } } - Iterator> iterator = this.lockedFlags.entrySet().iterator(); - - while(iterator.hasNext()) { - Map.Entry entry = iterator.next(); - if (!entry.getValue().isRunning()) { - iterator.remove(); - } - } + this.lockedFlags.entrySet().removeIf(entry -> !entry.getValue().isRunning()); //profilerFiller.pop(); // Purpur //profilerFiller.push("goalUpdate"); // Purpur diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java index 8db20db72cd51046213625fac46c35854c59ec5d..4d40526cc90c19ff5a1569c8d6d828a0d0b73ccb 100644 --- a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java +++ b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java @@ -3,6 +3,7 @@ package net.minecraft.world.entity.ai.sensing; import com.google.common.collect.ImmutableSet; import com.mojang.datafixers.util.Pair; import it.unimi.dsi.fastutil.longs.Long2LongMap; +import it.unimi.dsi.fastutil.longs.Long2LongMaps; import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap; import java.util.Optional; import java.util.Set; @@ -23,7 +24,7 @@ public class NearestBedSensor extends Sensor { private static final int CACHE_TIMEOUT = 40; private static final int BATCH_SIZE = 5; private static final int RATE = 20; - private final Long2LongMap batchCache = new Long2LongOpenHashMap(); + private final Long2LongMap batchCache = Long2LongMaps.synchronize(new Long2LongOpenHashMap()); private int triedCount; private long lastUpdate; diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/Sensing.java b/src/main/java/net/minecraft/world/entity/ai/sensing/Sensing.java index 9babe636176da3c40598eb5bdac0919a1704eaa0..58c6b1f67aedf5ab2167fd070604fc0d8f710435 100644 --- a/src/main/java/net/minecraft/world/entity/ai/sensing/Sensing.java +++ b/src/main/java/net/minecraft/world/entity/ai/sensing/Sensing.java @@ -2,13 +2,14 @@ package net.minecraft.world.entity.ai.sensing; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.ints.IntSets; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Mob; public class Sensing { private final Mob mob; - private final IntSet seen = new IntOpenHashSet(); - private final IntSet unseen = new IntOpenHashSet(); + private final IntSet seen = IntSets.synchronize(new IntOpenHashSet()); + private final IntSet unseen = IntSets.synchronize(new IntOpenHashSet()); public Sensing(Mob owner) { this.mob = owner; diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java index 9f138bc471b5c2a4fa813ff943dbe34018b8df74..5c8a90f8536c9291df5891d8c75de963b75ec4bd 100644 --- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java +++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java @@ -7,6 +7,7 @@ import com.mojang.logging.LogUtils; import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; +import it.unimi.dsi.fastutil.shorts.Short2ObjectMaps; import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap; import java.util.List; import java.util.Map; @@ -25,8 +26,9 @@ import org.slf4j.Logger; public class PoiSection { private static final Logger LOGGER = LogUtils.getLogger(); - private final Short2ObjectMap records = new Short2ObjectOpenHashMap<>(); - private final Map, Set> byType = Maps.newHashMap(); public final Map, Set> getData() { return this.byType; } // Paper - public accessor + private final Short2ObjectMap records = Short2ObjectMaps.synchronize(new Short2ObjectOpenHashMap<>()); + private final Map, Set> byType = Maps.newConcurrentMap(); + public final Map, Set> getData() { return this.byType; } // Paper - public accessor private final Runnable setDirty; private boolean isValid; public final Optional noAllocateOptional = Optional.of(this); // Paper - rewrite chunk system diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java index 8450a22b0fc6e8dc5cad0f61ac52a82b3cd3791e..c55f98fb2a6aade8e0a6f60248f4e29f66f0c193 100644 --- a/src/main/java/net/minecraft/world/item/ItemStack.java +++ b/src/main/java/net/minecraft/world/item/ItemStack.java @@ -248,7 +248,9 @@ public final class ItemStack { } private void updateEmptyCacheFlag() { - if (this.emptyCacheFlag && this == ItemStack.EMPTY) throw new AssertionError("TRAP"); // CraftBukkit + if (this.emptyCacheFlag && this == ItemStack.EMPTY){ + return; + }//throw new AssertionError("TRAP"); // CraftBukkit this.emptyCacheFlag = false; this.emptyCacheFlag = this.isEmpty(); } diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java index c0d39afe5b80159ed9aaca4ddd4763d707882f2e..2f53f8f2695231179ff16bb014cd990e94f9ec79 100644 --- a/src/main/java/net/minecraft/world/level/Explosion.java +++ b/src/main/java/net/minecraft/world/level/Explosion.java @@ -4,12 +4,15 @@ import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.mojang.datafixers.util.Pair; import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectList; import it.unimi.dsi.fastutil.objects.ObjectListIterator; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import javax.annotation.Nullable; + +import it.unimi.dsi.fastutil.objects.ObjectLists; import net.minecraft.Util; import net.minecraft.core.BlockPos; import net.minecraft.core.particles.ParticleTypes; @@ -63,7 +66,7 @@ public class Explosion { private final float radius; private final DamageSource damageSource; private final ExplosionDamageCalculator damageCalculator; - private final ObjectArrayList toBlow; + private final ObjectList toBlow; private final Map hitPlayers; public boolean wasCanceled = false; // CraftBukkit - add field @@ -81,9 +84,9 @@ public class Explosion { } public Explosion(Level world, @Nullable Entity entity, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator behavior, double x, double y, double z, float power, boolean createFire, Explosion.BlockInteraction destructionType) { - this.random = RandomSource.create(); - this.toBlow = new ObjectArrayList(); - this.hitPlayers = Maps.newHashMap(); + this.random = RandomSource.createThreadSafe(); + this.toBlow = ObjectLists.synchronize(new ObjectArrayList<>()); + this.hitPlayers = Maps.newConcurrentMap(); this.level = world; this.source = entity; this.radius = (float) Math.max(power, 0.0); // CraftBukkit - clamp bad values @@ -396,14 +399,10 @@ public class Explosion { } if (this.fire) { - ObjectListIterator objectlistiterator1 = this.toBlow.iterator(); - - while (objectlistiterator1.hasNext()) { - BlockPos blockposition2 = (BlockPos) objectlistiterator1.next(); - + for (BlockPos blockposition2 : this.toBlow) { if (this.random.nextInt(3) == 0 && this.level.getBlockState(blockposition2).isAir() && this.level.getBlockState(blockposition2.below()).isSolidRender(this.level, blockposition2.below())) { // CraftBukkit start - Ignition by explosion - if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(this.level, blockposition2.getX(), blockposition2.getY(), blockposition2.getZ(), this).isCancelled()) { + if (!CraftEventFactory.callBlockIgniteEvent(this.level, blockposition2.getX(), blockposition2.getY(), blockposition2.getZ(), this).isCancelled()) { this.level.setBlockAndUpdate(blockposition2, BaseFireBlock.getState(this.level, blockposition2)); } // CraftBukkit end diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java index 2e922bb844bc147224a60ef2aae33a0125e6ca4a..a78f04ae5eb2d0f11579cb0a40384f3f103371af 100644 --- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java @@ -88,7 +88,7 @@ public class LevelChunk extends ChunkAccess { private Supplier fullStatus; @Nullable private LevelChunk.PostLoadProcessor postLoad; - private final Int2ObjectMap gameEventListenerRegistrySections; + private final Map gameEventListenerRegistrySections; private final LevelChunkTicks blockTicks; private final LevelChunkTicks fluidTicks; @@ -114,10 +114,10 @@ public class LevelChunk extends ChunkAccess { this.setBlockNibbles(ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(world)); this.setSkyNibbles(ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(world)); // Paper end - rewrite light engine - this.tickersInLevel = Maps.newHashMap(); + this.tickersInLevel = Maps.newConcurrentMap(); this.clientLightReady = false; this.level = (ServerLevel) world; // CraftBukkit - type - this.gameEventListenerRegistrySections = new Int2ObjectOpenHashMap(); + this.gameEventListenerRegistrySections = Maps.newConcurrentMap(); Heightmap.Types[] aheightmap_type = Heightmap.Types.values(); int j = aheightmap_type.length;