mirror of
https://github.com/Winds-Studio/Leaf.git
synced 2025-12-23 17:09:29 +00:00
1910 lines
80 KiB
Diff
1910 lines
80 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: wangxyper <wangxyper@163.com>
|
|
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<ServerPlayer, PlayerLoaderData> playerMap = new Reference2ObjectLinkedOpenHashMap<>(512, 0.7f);
|
|
- protected final ReferenceLinkedOpenHashSet<PlayerLoaderData> chunkSendQueue = new ReferenceLinkedOpenHashSet<>(512, 0.7f);
|
|
+ protected final Reference2ObjectMap<ServerPlayer, PlayerLoaderData> playerMap = Reference2ObjectMaps.synchronize(new Reference2ObjectLinkedOpenHashMap<>(512, 0.7f));
|
|
+ protected final Deque<PlayerLoaderData> chunkSendQueue = new ConcurrentLinkedDeque<>();
|
|
|
|
- protected final TreeSet<PlayerLoaderData> chunkLoadQueue = new TreeSet<>((final PlayerLoaderData p1, final PlayerLoaderData p2) -> {
|
|
+ protected final NavigableSet<PlayerLoaderData> 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<ServerPlayer> 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<PlayerLoaderData> 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<PlayerLoaderData> 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<ChunkPriorityHolder> loadQueue = new ArrayDeque<>();
|
|
- protected final LongOpenHashSet sentChunks = new LongOpenHashSet();
|
|
- protected final LongOpenHashSet chunksToBeSent = new LongOpenHashSet();
|
|
+ protected final Deque<ChunkPriorityHolder> loadQueue = new ConcurrentLinkedDeque<>();
|
|
+ protected final LongSet sentChunks = LongSets.synchronize(new LongOpenHashSet());
|
|
+ protected final LongSet chunksToBeSent = LongSets.synchronize(new LongOpenHashSet());
|
|
|
|
- protected final TreeSet<ChunkPriorityHolder> sendQueue = new TreeSet<>((final ChunkPriorityHolder p1, final ChunkPriorityHolder p2) -> {
|
|
+ protected final NavigableSet<ChunkPriorityHolder> 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<Entity> {
|
|
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<Long> queuedCoordinates = new ConcurrentLinkedDeque<>();
|
|
+ public final Deque<Byte> 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<E>
|
|
+ */
|
|
+
|
|
+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 <i>weakly consistent</i>, returning
|
|
+ * elements reflecting the state of the deque at some point at or since the
|
|
+ * creation of the iterator. They do <em>not</em> throw
|
|
+ * {@link ConcurrentModificationException}, and may proceed concurrently with
|
|
+ * other operations.
|
|
+ *
|
|
+ * <p>
|
|
+ * This class and its iterators implement all of the <em>optional</em> methods
|
|
+ * of the {@link Collection} and {@link Iterator} interfaces. Like most other
|
|
+ * concurrent collection implementations, this class does not permit the use of
|
|
+ * <tt>null</tt> 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 <tt>removeFirstOccurrence</tt>,
|
|
+ * and {@link Collection#add} is mapped to <tt>addLast</tt>.
|
|
+ *
|
|
+ * <p>
|
|
+ * Beware that, unlike in most collections, the <tt>size</tt> method is
|
|
+ * <em>NOT</em> a constant-time operation. Because of the asynchronous nature of
|
|
+ * these deques, determining the current number of elements requires a traversal
|
|
+ * of the elements.
|
|
+ *
|
|
+ * <p>
|
|
+ * This class is <tt>Serializable</tt>, but relies on default serialization
|
|
+ * mechanisms. Usually, it is a better idea for any serializable class using a
|
|
+ * <tt>ConcurrentLinkedDeque</tt> to instead serialize a snapshot of the
|
|
+ * elements obtained by method <tt>toArray</tt>.
|
|
+ *
|
|
+ * @author Doug Lea
|
|
+ * @param <E> the type of elements held in this collection
|
|
+ */
|
|
+
|
|
+public class ConcurrentDoublyLinkedList<E> extends AbstractCollection<E> implements List<E>, 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<E> toArrayList() {
|
|
+ ArrayList<E> c = new ArrayList<E>();
|
|
+ for (Node<E> 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<E> header;
|
|
+
|
|
+ /**
|
|
+ * List trailer. Last usable node is at trailer.back().
|
|
+ */
|
|
+ private final Node<E> trailer;
|
|
+
|
|
+ /**
|
|
+ * Constructs an empty deque.
|
|
+ */
|
|
+ public ConcurrentDoublyLinkedList() {
|
|
+ Node<E> h = new Node<E>(null, null, null);
|
|
+ Node<E> t = new Node<E>(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 <tt>c</tt> or any element within it is
|
|
+ * <tt>null</tt>
|
|
+ */
|
|
+ public ConcurrentDoublyLinkedList(Collection<? extends E> 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 <tt>null</tt>
|
|
+ */
|
|
+ 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 <tt>add</tt> method.
|
|
+ *
|
|
+ * @param o the element to be inserted at the end of this deque.
|
|
+ * @throws NullPointerException if the specified element is <tt>null</tt>
|
|
+ */
|
|
+ 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 <tt>true</tt> always
|
|
+ * @throws NullPointerException if the specified element is <tt>null</tt>
|
|
+ */
|
|
+ public boolean offerFirst(E o) {
|
|
+ addFirst(o);
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Appends the given element to the end of this deque. (Identical in function to
|
|
+ * the <tt>add</tt> method; included only for consistency.)
|
|
+ *
|
|
+ * @param o the element to be inserted at the end of this deque.
|
|
+ * @return <tt>true</tt> always
|
|
+ * @throws NullPointerException if the specified element is <tt>null</tt>
|
|
+ */
|
|
+ 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 <tt>null</tt> if empty.
|
|
+ */
|
|
+ public E peekFirst() {
|
|
+ Node<E> 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 <tt>null</tt> if empty.
|
|
+ */
|
|
+ public E peekLast() {
|
|
+ Node<E> 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 <tt>null</tt> if empty.
|
|
+ */
|
|
+ public E pollFirst() {
|
|
+ for (;;) {
|
|
+ Node<E> 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 <tt>null</tt> if empty.
|
|
+ */
|
|
+ public E pollLast() {
|
|
+ for (;;) {
|
|
+ Node<E> 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 <tt>e</tt> such that <tt>o.equals(e)</tt>, 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 <tt>true</tt> if the deque contained the specified element.
|
|
+ * @throws NullPointerException if the specified element is <tt>null</tt>
|
|
+ */
|
|
+ public boolean removeFirstOccurrence(Object o) {
|
|
+ checkNullArg(o);
|
|
+ for (;;) {
|
|
+ Node<E> 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 <tt>e</tt> such that <tt>o.equals(e)</tt>, 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 <tt>true</tt> if the deque contained the specified element.
|
|
+ * @throws NullPointerException if the specified element is <tt>null</tt>
|
|
+ */
|
|
+ public boolean removeLastOccurrence(Object o) {
|
|
+ checkNullArg(o);
|
|
+ for (;;) {
|
|
+ Node<E> s = trailer;
|
|
+ for (;;) {
|
|
+ Node<E> 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 <tt>true</tt> if this deque contains at least one element <tt>e</tt>
|
|
+ * such that <tt>o.equals(e)</tt>.
|
|
+ *
|
|
+ * @param o element whose presence in this deque is to be tested.
|
|
+ * @return <tt>true</tt> if this deque contains the specified element.
|
|
+ */
|
|
+ public boolean contains(Object o) {
|
|
+ if (o == null)
|
|
+ return false;
|
|
+ for (Node<E> n = header.forward(); n != null; n = n.forward())
|
|
+ if (o.equals(n.element))
|
|
+ return true;
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Returns <tt>true</tt> if this collection contains no elements.
|
|
+ * <p>
|
|
+ *
|
|
+ * @return <tt>true</tt> 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 <tt>Integer.MAX_VALUE</tt> elements, it returns
|
|
+ * <tt>Integer.MAX_VALUE</tt>.
|
|
+ *
|
|
+ * <p>
|
|
+ * Beware that, unlike in most collections, this method is <em>NOT</em> 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<E> n = header.forward(); n != null; n = n.forward())
|
|
+ ++count;
|
|
+ return (count >= Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) count;
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Removes the first element <tt>e</tt> such that <tt>o.equals(e)</tt>, 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 <tt>true</tt> if the deque contained the specified element.
|
|
+ * @throws NullPointerException if the specified element is <tt>null</tt>
|
|
+ */
|
|
+ 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 <tt>true</tt> if this deque changed as a result of the call.
|
|
+ * @throws NullPointerException if <tt>c</tt> or any element within it is
|
|
+ * <tt>null</tt>
|
|
+ */
|
|
+ public boolean addAll(Collection<? extends E> c) {
|
|
+ Iterator<? extends E> 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.
|
|
+ * <p>
|
|
+ *
|
|
+ * 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 <i>only</i> 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> 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 <tt>next</tt> method returns elements reflecting the
|
|
+ * state of the deque at some point at or since the creation of the iterator.
|
|
+ * The method does <em>not</em> throw {@link ConcurrentModificationException},
|
|
+ * and may proceed concurrently with other operations.
|
|
+ *
|
|
+ * @return an iterator over the elements in this deque
|
|
+ */
|
|
+ public Iterator<E> iterator() {
|
|
+ return new CLDIterator();
|
|
+ }
|
|
+
|
|
+ final class CLDIterator implements Iterator<E> {
|
|
+ Node<E> last;
|
|
+
|
|
+ Node<E> next = header.forward();
|
|
+
|
|
+ public boolean hasNext() {
|
|
+ return next != null;
|
|
+ }
|
|
+
|
|
+ public E next() {
|
|
+ Node<E> l = last = next;
|
|
+ if (l == null)
|
|
+ throw new NoSuchElementException();
|
|
+ next = next.forward();
|
|
+ return l.element;
|
|
+ }
|
|
+
|
|
+ public void remove() {
|
|
+ Node<E> l = last;
|
|
+ if (l == null)
|
|
+ throw new IllegalStateException();
|
|
+ while (!l.delete() && !l.isDeleted())
|
|
+ ;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean addAll(int index, Collection<? extends E> c) {
|
|
+ throw new NotImplementedException("TODO");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public E get(int index) {
|
|
+ Node<E> 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<E> listIterator() {
|
|
+ throw new NotImplementedException("INVALID");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public ListIterator<E> listIterator(int index) {
|
|
+ throw new NotImplementedException("INVALID");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public List<E> 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<E> extends AtomicReference<Node<E>> {
|
|
+
|
|
+ private static final long serialVersionUID = 6640557564507962862L;
|
|
+
|
|
+ private volatile Node<E> prev;
|
|
+
|
|
+ final E element;
|
|
+
|
|
+ /** Creates a node with given contents */
|
|
+ Node(E element, Node<E> next, Node<E> prev) {
|
|
+ super(next);
|
|
+ this.prev = prev;
|
|
+ this.element = element;
|
|
+ }
|
|
+
|
|
+ /** Creates a marker node with given successor */
|
|
+ Node(Node<E> next) {
|
|
+ super(next);
|
|
+ this.prev = this;
|
|
+ this.element = null;
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Gets next link (which is actually the value held as atomic reference).
|
|
+ */
|
|
+ private Node<E> getNext() {
|
|
+ return get();
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Sets next link
|
|
+ *
|
|
+ * @param n the next node
|
|
+ */
|
|
+ void setNext(Node<E> n) {
|
|
+ set(n);
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * compareAndSet next link
|
|
+ */
|
|
+ private boolean casNext(Node<E> cmp, Node<E> val) {
|
|
+ return compareAndSet(cmp, val);
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Gets prev link
|
|
+ */
|
|
+ private Node<E> getPrev() {
|
|
+ return prev;
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Sets prev link
|
|
+ *
|
|
+ * @param b the previous node
|
|
+ */
|
|
+ void setPrev(Node<E> 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<E> f = getNext();
|
|
+ return f != null && f.isMarker();
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Returns next node, ignoring deletion marker
|
|
+ */
|
|
+ private Node<E> nextNonmarker() {
|
|
+ Node<E> 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<E> successor() {
|
|
+ Node<E> 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<E> 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<E> findPredecessorOf(Node<E> target) {
|
|
+ Node<E> n = this;
|
|
+ for (;;) {
|
|
+ Node<E> 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<E> predecessor() {
|
|
+ Node<E> n = this;
|
|
+ for (;;) {
|
|
+ Node<E> b = n.getPrev();
|
|
+ if (b == null)
|
|
+ return n.findPredecessorOf(this);
|
|
+ Node<E> s = b.getNext();
|
|
+ if (s == this)
|
|
+ return b;
|
|
+ if (s == null || !s.isMarker()) {
|
|
+ Node<E> 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<E> forward() {
|
|
+ Node<E> 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<E> back() {
|
|
+ Node<E> 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<E> append(E element) {
|
|
+ for (;;) {
|
|
+ Node<E> f = getNext();
|
|
+ if (f == null || f.isMarker())
|
|
+ return null;
|
|
+ Node<E> x = new Node<E>(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<E> prepend(E element) {
|
|
+ for (;;) {
|
|
+ Node<E> b = predecessor();
|
|
+ if (b == null)
|
|
+ return null;
|
|
+ Node<E> x = new Node<E>(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<E> b = getPrev();
|
|
+ Node<E> f = getNext();
|
|
+ if (b != null && f != null && !f.isMarker() && casNext(f, new Node<E>(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<E> replace(E newElement) {
|
|
+ for (;;) {
|
|
+ Node<E> b = getPrev();
|
|
+ Node<E> f = getNext();
|
|
+ if (b == null || f == null || f.isMarker())
|
|
+ return null;
|
|
+ Node<E> x = new Node<E>(newElement, f, b);
|
|
+ if (casNext(f, new Node<E>(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 <T> void shuffle(ObjectArrayList<T> list, RandomSource random) {
|
|
+ public static <T> void shuffle(ObjectList<T> 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<ChunkHolder> copy = new ObjectArraySet<>(this.chunkMap.needsChangeBroadcasting);
|
|
+ List<ChunkHolder> 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<MobEffect, MobEffectInstance> activeEffects = Maps.newHashMap();
|
|
+ public final Map<MobEffect, MobEffectInstance> activeEffects = Maps.newConcurrentMap();
|
|
private final NonNullList<ItemStack> lastHandItemStacks;
|
|
private final NonNullList<ItemStack> lastArmorItemStacks;
|
|
public boolean swinging;
|
|
@@ -257,7 +258,7 @@ public abstract class LivingEntity extends Entity {
|
|
// CraftBukkit start
|
|
public int expToDrop;
|
|
public boolean forceDrops;
|
|
- public ArrayList<org.bukkit.inventory.ItemStack> drops = new ArrayList<org.bukkit.inventory.ItemStack>();
|
|
+ public List<org.bukkit.inventory.ItemStack> drops = new CopyOnWriteArrayList<>();
|
|
public final org.bukkit.craftbukkit.attribute.CraftAttributeMap craftAttributes;
|
|
public boolean collides = true;
|
|
public Set<UUID> collidableExemptions = new HashSet<>();
|
|
@@ -875,7 +876,7 @@ public abstract class LivingEntity extends Entity {
|
|
|
|
// CraftBukkit start
|
|
private boolean isTickingEffects = false;
|
|
- private List<ProcessableEffect> effectsToProcess = Lists.newArrayList();
|
|
+ private List<ProcessableEffect> 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<E extends Mob> extends Behavior<E> {
|
|
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<Map.Entry<Goal.Flag, WrappedGoal>> iterator = this.lockedFlags.entrySet().iterator();
|
|
-
|
|
- while(iterator.hasNext()) {
|
|
- Map.Entry<Goal.Flag, WrappedGoal> 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<Mob> {
|
|
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<PoiRecord> records = new Short2ObjectOpenHashMap<>();
|
|
- private final Map<Holder<PoiType>, Set<PoiRecord>> byType = Maps.newHashMap(); public final Map<Holder<PoiType>, Set<PoiRecord>> getData() { return this.byType; } // Paper - public accessor
|
|
+ private final Short2ObjectMap<PoiRecord> records = Short2ObjectMaps.synchronize(new Short2ObjectOpenHashMap<>());
|
|
+ private final Map<Holder<PoiType>, Set<PoiRecord>> byType = Maps.newConcurrentMap();
|
|
+ public final Map<Holder<PoiType>, Set<PoiRecord>> getData() { return this.byType; } // Paper - public accessor
|
|
private final Runnable setDirty;
|
|
private boolean isValid;
|
|
public final Optional<PoiSection> 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<BlockPos> toBlow;
|
|
+ private final ObjectList<BlockPos> toBlow;
|
|
private final Map<Player, Vec3> 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<ChunkHolder.FullChunkStatus> fullStatus;
|
|
@Nullable
|
|
private LevelChunk.PostLoadProcessor postLoad;
|
|
- private final Int2ObjectMap<GameEventListenerRegistry> gameEventListenerRegistrySections;
|
|
+ private final Map<Integer,GameEventListenerRegistry> gameEventListenerRegistrySections;
|
|
private final LevelChunkTicks<Block> blockTicks;
|
|
private final LevelChunkTicks<Fluid> 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;
|
|
|