From f491335316357c78392be6cf5b64dc41e64e9888 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sat, 6 Sep 2025 06:28:45 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A8=8D=E4=BD=9C=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/world/BukkitWorldManager.java | 129 +- ...currentUUID2ReferenceChainedHashTable.java | 1154 +++++++++++++++++ 2 files changed, 1191 insertions(+), 92 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/util/ConcurrentUUID2ReferenceChainedHashTable.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorldManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorldManager.java index 2d8671d53..18050cd49 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorldManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorldManager.java @@ -1,6 +1,5 @@ package net.momirealms.craftengine.bukkit.world; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.injector.WorldStorageInjector; @@ -9,6 +8,7 @@ import net.momirealms.craftengine.bukkit.util.BlockStateUtils; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; +import net.momirealms.craftengine.core.util.ConcurrentUUID2ReferenceChainedHashTable; import net.momirealms.craftengine.core.world.CEWorld; import net.momirealms.craftengine.core.world.ChunkPos; import net.momirealms.craftengine.core.world.SectionPos; @@ -29,27 +29,21 @@ import org.bukkit.event.world.*; import org.jetbrains.annotations.NotNull; import java.io.IOException; -import java.util.Map; import java.util.Optional; import java.util.UUID; -import java.util.concurrent.locks.ReentrantReadWriteLock; public class BukkitWorldManager implements WorldManager, Listener { private static BukkitWorldManager instance; private final BukkitCraftEngine plugin; - private final Map worlds; - private CEWorld[] worldArray = new CEWorld[0]; - private final ReentrantReadWriteLock worldMapLock = new ReentrantReadWriteLock(); - // cache - private UUID lastVisitedUUID; - private CEWorld lastVisitedWorld; + private final ConcurrentUUID2ReferenceChainedHashTable worlds; + private CEWorld[] worldArray; private StorageAdaptor storageAdaptor; private boolean initialized = false; public BukkitWorldManager(BukkitCraftEngine plugin) { instance = this; this.plugin = plugin; - this.worlds = new Object2ObjectOpenHashMap<>(32, 0.5f); + this.worlds = ConcurrentUUID2ReferenceChainedHashTable.createWithCapacity(10, 0.5f); this.storageAdaptor = new DefaultStorageAdaptor(); for (World world : Bukkit.getWorlds()) { this.worlds.put(world.getUID(), new BukkitCEWorld(new BukkitWorld(world), this.storageAdaptor)); @@ -71,20 +65,7 @@ public class BukkitWorldManager implements WorldManager, Listener { @Override public CEWorld getWorld(UUID uuid) { - if (uuid.equals(this.lastVisitedUUID)) { - return this.lastVisitedWorld; - } - this.worldMapLock.readLock().lock(); - try { - CEWorld world = worlds.get(uuid); - if (world != null) { - this.lastVisitedUUID = uuid; - this.lastVisitedWorld = world; - } - return world; - } finally { - this.worldMapLock.readLock().unlock(); - } + return this.worlds.get(uuid); } @Override @@ -98,23 +79,18 @@ public class BukkitWorldManager implements WorldManager, Listener { public void delayedInit() { // load loaded chunks - this.worldMapLock.writeLock().lock(); - try { - for (World world : Bukkit.getWorlds()) { - try { - CEWorld ceWorld = this.worlds.computeIfAbsent(world.getUID(), k -> new BukkitCEWorld(new BukkitWorld(world), this.storageAdaptor)); - for (Chunk chunk : world.getLoadedChunks()) { - handleChunkLoad(ceWorld, chunk); - } - ceWorld.setTicking(true); - } catch (Exception e) { - CraftEngine.instance().logger().warn("Error loading world: " + world.getName(), e); + for (World world : Bukkit.getWorlds()) { + try { + CEWorld ceWorld = this.worlds.computeIfAbsent(world.getUID(), k -> new BukkitCEWorld(new BukkitWorld(world), this.storageAdaptor)); + for (Chunk chunk : world.getLoadedChunks()) { + handleChunkLoad(ceWorld, chunk); } + ceWorld.setTicking(true); + } catch (Exception e) { + CraftEngine.instance().logger().warn("Error loading world: " + world.getName(), e); } - this.resetWorldArray(); - } finally { - this.worldMapLock.writeLock().unlock(); } + this.resetWorldArray(); Bukkit.getPluginManager().registerEvents(this, this.plugin.javaPlugin()); this.initialized = true; } @@ -147,35 +123,25 @@ public class BukkitWorldManager implements WorldManager, Listener { @Override public void loadWorld(net.momirealms.craftengine.core.world.World world) { - this.worldMapLock.writeLock().lock(); - try { - if (this.worlds.containsKey(world.uuid())) return; - CEWorld ceWorld = new BukkitCEWorld(world, this.storageAdaptor); - this.worlds.put(world.uuid(), ceWorld); - this.resetWorldArray(); - for (Chunk chunk : ((World) world.platformWorld()).getLoadedChunks()) { - handleChunkLoad(ceWorld, chunk); - } - ceWorld.setTicking(true); - } finally { - this.worldMapLock.writeLock().unlock(); + if (this.worlds.containsKey(world.uuid())) return; + CEWorld ceWorld = new BukkitCEWorld(world, this.storageAdaptor); + this.worlds.put(world.uuid(), ceWorld); + this.resetWorldArray(); + for (Chunk chunk : ((World) world.platformWorld()).getLoadedChunks()) { + handleChunkLoad(ceWorld, chunk); } + ceWorld.setTicking(true); } @Override public void loadWorld(CEWorld world) { - this.worldMapLock.writeLock().lock(); - try { - if (this.worlds.containsKey(world.world().uuid())) return; - this.worlds.put(world.world().uuid(), world); - this.resetWorldArray(); - for (Chunk chunk : ((World) world.world().platformWorld()).getLoadedChunks()) { - handleChunkLoad(world, chunk); - } - world.setTicking(true); - } finally { - this.worldMapLock.writeLock().unlock(); + if (this.worlds.containsKey(world.world().uuid())) return; + this.worlds.put(world.world().uuid(), world); + this.resetWorldArray(); + for (Chunk chunk : ((World) world.world().platformWorld()).getLoadedChunks()) { + handleChunkLoad(world, chunk); } + world.setTicking(true); } @Override @@ -198,20 +164,11 @@ public class BukkitWorldManager implements WorldManager, Listener { @Override public void unloadWorld(net.momirealms.craftengine.core.world.World world) { CEWorld ceWorld; - this.worldMapLock.writeLock().lock(); - try { - ceWorld = this.worlds.remove(world.uuid()); - if (ceWorld == null) { - return; - } - if (ceWorld == this.lastVisitedWorld) { - this.lastVisitedWorld = null; - this.lastVisitedUUID = null; - } - this.resetWorldArray(); - } finally { - this.worldMapLock.writeLock().unlock(); + ceWorld = this.worlds.remove(world.uuid()); + if (ceWorld == null) { + return; } + this.resetWorldArray(); ceWorld.setTicking(false); for (Chunk chunk : ((World) world.platformWorld()).getLoadedChunks()) { handleChunkUnload(ceWorld, chunk); @@ -238,30 +195,18 @@ public class BukkitWorldManager implements WorldManager, Listener { @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onChunkLoad(ChunkLoadEvent event) { - this.worldMapLock.readLock().lock(); - CEWorld world; - try { - world = worlds.get(event.getWorld().getUID()); - if (world == null) { - return; - } - } finally { - this.worldMapLock.readLock().unlock(); + CEWorld world = worlds.get(event.getWorld().getUID()); + if (world == null) { + return; } handleChunkLoad(world, event.getChunk()); } @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) public void onChunkUnload(ChunkUnloadEvent event) { - CEWorld world; - this.worldMapLock.readLock().lock(); - try { - world = worlds.get(event.getWorld().getUID()); - if (world == null) { - return; - } - } finally { - this.worldMapLock.readLock().unlock(); + CEWorld world = worlds.get(event.getWorld().getUID()); + if (world == null) { + return; } handleChunkUnload(world, event.getChunk()); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/ConcurrentUUID2ReferenceChainedHashTable.java b/core/src/main/java/net/momirealms/craftengine/core/util/ConcurrentUUID2ReferenceChainedHashTable.java new file mode 100644 index 000000000..80ffcb701 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/ConcurrentUUID2ReferenceChainedHashTable.java @@ -0,0 +1,1154 @@ +/** + * This implementation references the ConcurrentUtil implementation by Tuinity, + * available at: https://github.com/Tuinity/ConcurrentUtil + *

+ * This work is licensed under the GNU General Public License v3.0 (GPLv3) + */ +package net.momirealms.craftengine.core.util; + +import ca.spottedleaf.concurrentutil.util.*; + +import java.lang.invoke.VarHandle; +import java.util.*; +import java.util.concurrent.atomic.LongAdder; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * Concurrent hashtable implementation supporting mapping UUID values onto non-null Object + * values with support for multiple writer and multiple reader threads. + */ +public class ConcurrentUUID2ReferenceChainedHashTable implements Iterable> { + protected static final int DEFAULT_CAPACITY = 16; + protected static final float DEFAULT_LOAD_FACTOR = 0.75f; + protected static final int MAXIMUM_CAPACITY = Integer.MIN_VALUE >>> 1; + + protected final LongAdder size = new LongAdder(); + protected final float loadFactor; + + protected volatile TableEntry[] table; + + protected static final int THRESHOLD_NO_RESIZE = -1; + protected static final int THRESHOLD_RESIZING = -2; + protected volatile int threshold; + protected static final VarHandle THRESHOLD_HANDLE = ConcurrentUtil.getVarHandle(ConcurrentUUID2ReferenceChainedHashTable.class, "threshold", int.class); + + protected final int getThresholdAcquire() { + return (int)THRESHOLD_HANDLE.getAcquire(this); + } + + protected final int getThresholdVolatile() { + return (int)THRESHOLD_HANDLE.getVolatile(this); + } + + protected final void setThresholdPlain(final int threshold) { + THRESHOLD_HANDLE.set(this, threshold); + } + + protected final void setThresholdRelease(final int threshold) { + THRESHOLD_HANDLE.setRelease(this, threshold); + } + + protected final void setThresholdVolatile(final int threshold) { + THRESHOLD_HANDLE.setVolatile(this, threshold); + } + + protected final int compareExchangeThresholdVolatile(final int expect, final int update) { + return (int)THRESHOLD_HANDLE.compareAndExchange(this, expect, update); + } + + protected Values values; + protected EntrySet entrySet; + + public ConcurrentUUID2ReferenceChainedHashTable() { + this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR); + } + + protected static int getTargetThreshold(final int capacity, final float loadFactor) { + final double ret = (double)capacity * (double)loadFactor; + if (Double.isInfinite(ret) || ret >= ((double)Integer.MAX_VALUE)) { + return THRESHOLD_NO_RESIZE; + } + + return (int)Math.ceil(ret); + } + + protected static int getCapacityFor(final int capacity) { + if (capacity <= 0) { + throw new IllegalArgumentException("Invalid capacity: " + capacity); + } + if (capacity >= MAXIMUM_CAPACITY) { + return MAXIMUM_CAPACITY; + } + return IntegerUtil.roundCeilLog2(capacity); + } + + protected ConcurrentUUID2ReferenceChainedHashTable(final int capacity, final float loadFactor) { + final int tableSize = getCapacityFor(capacity); + + if (loadFactor <= 0.0 || !Float.isFinite(loadFactor)) { + throw new IllegalArgumentException("Invalid load factor: " + loadFactor); + } + + if (tableSize == MAXIMUM_CAPACITY) { + this.setThresholdPlain(THRESHOLD_NO_RESIZE); + } else { + this.setThresholdPlain(getTargetThreshold(tableSize, loadFactor)); + } + + this.loadFactor = loadFactor; + // noinspection unchecked + this.table = (TableEntry[])new TableEntry[tableSize]; + } + + public static ConcurrentUUID2ReferenceChainedHashTable createWithCapacity(final int capacity) { + return createWithCapacity(capacity, DEFAULT_LOAD_FACTOR); + } + + public static ConcurrentUUID2ReferenceChainedHashTable createWithCapacity(final int capacity, final float loadFactor) { + return new ConcurrentUUID2ReferenceChainedHashTable<>(capacity, loadFactor); + } + + public static ConcurrentUUID2ReferenceChainedHashTable createWithExpected(final int expected) { + return createWithExpected(expected, DEFAULT_LOAD_FACTOR); + } + + public static ConcurrentUUID2ReferenceChainedHashTable createWithExpected(final int expected, final float loadFactor) { + final int capacity = (int)Math.ceil((double)expected / (double)loadFactor); + return createWithCapacity(capacity, loadFactor); + } + + protected static int getHash(final UUID key) { + return (int)HashUtil.mix(key.getMostSignificantBits() ^ key.getLeastSignificantBits()); + } + + public final float getLoadFactor() { + return this.loadFactor; + } + + protected static TableEntry getAtIndexVolatile(final TableEntry[] table, final int index) { + //noinspection unchecked + return (TableEntry)TableEntry.TABLE_ENTRY_ARRAY_HANDLE.getVolatile(table, index); + } + + protected static void setAtIndexRelease(final TableEntry[] table, final int index, final TableEntry value) { + TableEntry.TABLE_ENTRY_ARRAY_HANDLE.setRelease(table, index, value); + } + + protected static void setAtIndexVolatile(final TableEntry[] table, final int index, final TableEntry value) { + TableEntry.TABLE_ENTRY_ARRAY_HANDLE.setVolatile(table, index, value); + } + + protected static TableEntry compareAndExchangeAtIndexVolatile(final TableEntry[] table, final int index, + final TableEntry expect, final TableEntry update) { + //noinspection unchecked + return (TableEntry)TableEntry.TABLE_ENTRY_ARRAY_HANDLE.compareAndExchange(table, index, expect, update); + } + + /** + * Returns the possible node associated with the key, or {@code null} if there is no such node. + */ + protected final TableEntry getNode(final UUID key) { + final int hash = getHash(key); + + TableEntry[] table = this.table; + for (;;) { + TableEntry node = getAtIndexVolatile(table, hash & (table.length - 1)); + + if (node == null) { + return node; + } + + if (node.resize) { + // noinspection unchecked + table = (TableEntry[])node.getValuePlain(); + continue; + } + + for (; node != null; node = node.getNextVolatile()) { + if (node.key.equals(key)) { + return node; + } + } + + return node; + } + } + + /** + * Returns the currently mapped value associated with the specified key, or {@code null} if there is none. + */ + public V get(final UUID key) { + final TableEntry node = this.getNode(key); + return node == null ? null : node.getValueVolatile(); + } + + /** + * Returns the currently mapped value associated with the specified key, or the specified default value if there is none. + */ + public V getOrDefault(final UUID key, final V defaultValue) { + final TableEntry node = this.getNode(key); + if (node == null) { + return defaultValue; + } + + final V ret = node.getValueVolatile(); + if (ret == null) { + return defaultValue; + } + + return ret; + } + + /** + * Returns whether the specified key is mapped to some value. + */ + public boolean containsKey(final UUID key) { + return this.get(key) != null; + } + + /** + * Returns whether the specified value has a key mapped to it. + */ + public boolean containsValue(final V value) { + Validate.notNull(value, "Value cannot be null"); + + final NodeIterator iterator = new NodeIterator<>(this.table); + + TableEntry node; + while ((node = iterator.findNext()) != null) { + if (node.getValueAcquire() == value) { + return true; + } + } + + return false; + } + + /** + * Returns the number of mappings in this map. + */ + public int size() { + final long ret = this.size.sum(); + + if (ret < 0L) { + return 0; + } + if (ret > (long)Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } + + return (int)ret; + } + + /** + * Returns whether this map has no mappings. + */ + public boolean isEmpty() { + return this.size.sum() <= 0L; + } + + /** + * Adds count to size and checks threshold for resizing + */ + protected final void addSize(final long count) { + this.size.add(count); + + final int threshold = this.getThresholdAcquire(); + + if (threshold < 0L) { + return; + } + + final long sum = this.size.sum(); + + if (sum < (long)threshold) { + return; + } + + if (threshold != this.compareExchangeThresholdVolatile(threshold, THRESHOLD_RESIZING)) { + return; + } + + this.resize(sum); + } + + /** + * Resizes table + */ + private void resize(final long sum) { + int capacity; + + final double targetD = ((double)sum / (double)this.loadFactor) + 1.0; + if (targetD >= (double)MAXIMUM_CAPACITY) { + capacity = MAXIMUM_CAPACITY; + } else { + capacity = (int)Math.ceil(targetD); + capacity = IntegerUtil.roundCeilLog2(capacity); + if (capacity > MAXIMUM_CAPACITY) { + capacity = MAXIMUM_CAPACITY; + } + } + + // noinspection unchecked + final TableEntry[] newTable = new TableEntry[capacity]; + // noinspection unchecked + final TableEntry resizeNode = new TableEntry<>(null, (V)newTable, true); + + final TableEntry[] oldTable = this.table; + + final int capOldShift = IntegerUtil.floorLog2(oldTable.length); + final int capDiffShift = IntegerUtil.floorLog2(capacity) - capOldShift; + + if (capDiffShift == 0) { + throw new IllegalStateException("Resizing to same size"); + } + + // noinspection unchecked + final TableEntry[] work = new TableEntry[1 << capDiffShift]; + + for (int i = 0, len = oldTable.length; i < len; ++i) { + TableEntry binNode = getAtIndexVolatile(oldTable, i); + + for (;;) { + if (binNode == null) { + if (null == (binNode = compareAndExchangeAtIndexVolatile(oldTable, i, null, resizeNode))) { + break; + } + } + + synchronized (binNode) { + if (binNode != (binNode = getAtIndexVolatile(oldTable, i))) { + continue; + } + + TableEntry next = binNode.getNextPlain(); + + if (next == null) { + newTable[getHash(binNode.key) & (capacity - 1)] = binNode; + } else { + Arrays.fill(work, null); + + for (TableEntry curr = binNode; curr != null; curr = curr.getNextPlain()) { + final int newTableIdx = getHash(curr.key) & (capacity - 1); + final int workIdx = newTableIdx >>> capOldShift; + + final TableEntry replace = new TableEntry<>(curr.key, curr.getValuePlain()); + + final TableEntry workNode = work[workIdx]; + work[workIdx] = replace; + + if (workNode == null) { + newTable[newTableIdx] = replace; + continue; + } else { + workNode.setNextPlain(replace); + continue; + } + } + } + + setAtIndexRelease(oldTable, i, resizeNode); + break; + } + } + } + + final int newThreshold; + if (capacity == MAXIMUM_CAPACITY) { + newThreshold = THRESHOLD_NO_RESIZE; + } else { + newThreshold = getTargetThreshold(capacity, loadFactor); + } + + this.table = newTable; + this.setThresholdVolatile(newThreshold); + } + + /** + * Subtracts count from size + */ + protected final void subSize(final long count) { + this.size.add(-count); + } + + /** + * Atomically updates the value associated with {@code key} to {@code value}, or inserts a new mapping. + */ + public V put(final UUID key, final V value) { + Validate.notNull(value, "Value may not be null"); + + final int hash = getHash(key); + + TableEntry[] table = this.table; + table_loop: + for (;;) { + final int index = hash & (table.length - 1); + + TableEntry node = getAtIndexVolatile(table, index); + node_loop: + for (;;) { + if (node == null) { + if (null == (node = compareAndExchangeAtIndexVolatile(table, index, null, new TableEntry<>(key, value)))) { + this.addSize(1L); + return null; + } + } + + if (node.resize) { + // noinspection unchecked + table = (TableEntry[])node.getValuePlain(); + continue table_loop; + } + + synchronized (node) { + if (node != (node = getAtIndexVolatile(table, index))) { + continue node_loop; + } + TableEntry prev = null; + for (; node != null; prev = node, node = node.getNextPlain()) { + if (node.key.equals(key)) { + final V ret = node.getValuePlain(); + node.setValueVolatile(value); + return ret; + } + } + + prev.setNextRelease(new TableEntry<>(key, value)); + } + + this.addSize(1L); + return null; + } + } + } + + /** + * Atomically inserts a new mapping if and only if {@code key} is not currently mapped. + */ + public V putIfAbsent(final UUID key, final V value) { + Validate.notNull(value, "Value may not be null"); + + final int hash = getHash(key); + + TableEntry[] table = this.table; + table_loop: + for (;;) { + final int index = hash & (table.length - 1); + + TableEntry node = getAtIndexVolatile(table, index); + node_loop: + for (;;) { + if (node == null) { + if (null == (node = compareAndExchangeAtIndexVolatile(table, index, null, new TableEntry<>(key, value)))) { + this.addSize(1L); + return null; + } + } + + if (node.resize) { + // noinspection unchecked + table = (TableEntry[])node.getValuePlain(); + continue table_loop; + } + + if (node.key.equals(key)) { + final V ret = node.getValueVolatile(); + if (ret != null) { + return ret; + } + } + + synchronized (node) { + if (node != (node = getAtIndexVolatile(table, index))) { + continue node_loop; + } + TableEntry prev = null; + for (; node != null; prev = node, node = node.getNextPlain()) { + if (node.key.equals(key)) { + return node.getValuePlain(); + } + } + + prev.setNextRelease(new TableEntry<>(key, value)); + } + + this.addSize(1L); + return null; + } + } + } + + /** + * Atomically updates the value associated with {@code key} to {@code value}, or does nothing if not mapped. + */ + public V replace(final UUID key, final V value) { + Validate.notNull(value, "Value may not be null"); + + final int hash = getHash(key); + + TableEntry[] table = this.table; + table_loop: + for (;;) { + final int index = hash & (table.length - 1); + + TableEntry node = getAtIndexVolatile(table, index); + node_loop: + for (;;) { + if (node == null) { + return null; + } + + if (node.resize) { + // noinspection unchecked + table = (TableEntry[])node.getValuePlain(); + continue table_loop; + } + + synchronized (node) { + if (node != (node = getAtIndexVolatile(table, index))) { + continue node_loop; + } + + for (; node != null; node = node.getNextPlain()) { + if (node.key.equals(key)) { + final V ret = node.getValuePlain(); + node.setValueVolatile(value); + return ret; + } + } + } + + return null; + } + } + } + + /** + * Atomically updates the value if the currently associated value is reference equal to {@code expect}. + */ + public V replace(final UUID key, final V expect, final V update) { + Validate.notNull(expect, "Expect may not be null"); + Validate.notNull(update, "Update may not be null"); + + final int hash = getHash(key); + + TableEntry[] table = this.table; + table_loop: + for (;;) { + final int index = hash & (table.length - 1); + + TableEntry node = getAtIndexVolatile(table, index); + node_loop: + for (;;) { + if (node == null) { + return null; + } + + if (node.resize) { + // noinspection unchecked + table = (TableEntry[])node.getValuePlain(); + continue table_loop; + } + + synchronized (node) { + if (node != (node = getAtIndexVolatile(table, index))) { + continue node_loop; + } + + for (; node != null; node = node.getNextPlain()) { + if (node.key.equals(key)) { + final V ret = node.getValuePlain(); + + if (ret != expect) { + return ret; + } + + node.setValueVolatile(update); + return ret; + } + } + } + + return null; + } + } + } + + /** + * Atomically removes the mapping for the specified key. + */ + public V remove(final UUID key) { + final int hash = getHash(key); + + TableEntry[] table = this.table; + table_loop: + for (;;) { + final int index = hash & (table.length - 1); + + TableEntry node = getAtIndexVolatile(table, index); + node_loop: + for (;;) { + if (node == null) { + return null; + } + + if (node.resize) { + // noinspection unchecked + table = (TableEntry[])node.getValuePlain(); + continue table_loop; + } + + boolean removed = false; + V ret = null; + + synchronized (node) { + if (node != (node = getAtIndexVolatile(table, index))) { + continue node_loop; + } + + TableEntry prev = null; + + for (; node != null; prev = node, node = node.getNextPlain()) { + if (node.key.equals(key)) { + ret = node.getValuePlain(); + removed = true; + + if (prev == null) { + setAtIndexRelease(table, index, node.getNextPlain()); + } else { + prev.setNextRelease(node.getNextPlain()); + } + + break; + } + } + } + + if (removed) { + this.subSize(1L); + } + + return ret; + } + } + } + + /** + * Atomically removes the mapping if it is mapped to {@code expect}. + */ + public V remove(final UUID key, final V expect) { + final int hash = getHash(key); + + TableEntry[] table = this.table; + table_loop: + for (;;) { + final int index = hash & (table.length - 1); + + TableEntry node = getAtIndexVolatile(table, index); + node_loop: + for (;;) { + if (node == null) { + return null; + } + + if (node.resize) { + // noinspection unchecked + table = (TableEntry[])node.getValuePlain(); + continue table_loop; + } + + boolean removed = false; + V ret = null; + + synchronized (node) { + if (node != (node = getAtIndexVolatile(table, index))) { + continue node_loop; + } + + TableEntry prev = null; + + for (; node != null; prev = node, node = node.getNextPlain()) { + if (node.key.equals(key)) { + ret = node.getValuePlain(); + if (ret == expect) { + removed = true; + + if (prev == null) { + setAtIndexRelease(table, index, node.getNextPlain()); + } else { + prev.setNextRelease(node.getNextPlain()); + } + } + break; + } + } + } + + if (removed) { + this.subSize(1L); + } + + return ret; + } + } + } + + /** + * Removes at least all entries currently mapped at the beginning of this call. + */ + public void clear() { + final NodeIterator nodeIterator = new NodeIterator<>(this.table); + + TableEntry node; + while ((node = nodeIterator.findNext()) != null) { + this.remove(node.key); + } + } + + /** + * Returns an iterator over the entries in this map. + */ + public Iterator> entryIterator() { + return new EntryIterator<>(this); + } + + @Override + public final Iterator> iterator() { + return this.entryIterator(); + } + + /** + * Returns an iterator over the keys in this map. + */ + public Iterator keyIterator() { + return new KeyIterator<>(this); + } + + /** + * Returns an iterator over the values in this map. + */ + public Iterator valueIterator() { + return new ValueIterator<>(this); + } + + public Collection values() { + final Values values = this.values; + if (values != null) { + return values; + } + return this.values = new Values<>(this); + } + + public Set> entrySet() { + final EntrySet entrySet = this.entrySet; + if (entrySet != null) { + return entrySet; + } + return this.entrySet = new EntrySet<>(this); + } + + /** + * See {@link java.util.concurrent.ConcurrentMap#computeIfAbsent(Object, Function)} + *

+ * This function is a "functional methods" as defined by {@link ConcurrentUUID2ReferenceChainedHashTable}. + *

+ */ + public V computeIfAbsent(final UUID key, final Function function) { + Validate.notNull(function, "Function may not be null"); + + final int hash = getHash(key); + + TableEntry[] table = this.table; + table_loop: + for (;;) { + final int index = hash & (table.length - 1); + + TableEntry node = getAtIndexVolatile(table, index); + node_loop: + for (;;) { + V ret = null; + if (node == null) { + final TableEntry insert = new TableEntry<>(key, null); + + boolean added = false; + + synchronized (insert) { + if (null == (node = compareAndExchangeAtIndexVolatile(table, index, null, insert))) { + try { + ret = function.apply(key); + } catch (final Throwable throwable) { + setAtIndexVolatile(table, index, null); + ThrowUtil.throwUnchecked(throwable); + // unreachable + return null; + } + + if (ret == null) { + setAtIndexVolatile(table, index, null); + return null; + } else { + // volatile ordering ensured by addSize(), but we need release here + // to ensure proper ordering with reads and other writes + insert.setValueRelease(ret); + added = true; + } + } // else: node != null, fall through + } + + if (added) { + this.addSize(1L); + return ret; + } + } + + if (node.resize) { + // noinspection unchecked + table = (TableEntry[])node.getValuePlain(); + continue table_loop; + } + + // optimise ifAbsent calls: check if first node is key before attempting lock acquire + if (node.key.equals(key)) { + ret = node.getValueVolatile(); + if (ret != null) { + return ret; + } // else: fall back to lock to read the node + } + + boolean added = false; + + synchronized (node) { + if (node != (node = getAtIndexVolatile(table, index))) { + continue node_loop; + } + // plain reads are fine during synchronised access, as we are the only writer + TableEntry prev = null; + for (; node != null; prev = node, node = node.getNextPlain()) { + if (node.key.equals(key)) { + ret = node.getValuePlain(); + return ret; + } + } + + final V computed = function.apply(key); + if (computed != null) { + // volatile ordering ensured by addSize(), but we need release here + // to ensure proper ordering with reads and other writes + prev.setNextRelease(new TableEntry<>(key, computed)); + ret = computed; + added = true; + } + } + + if (added) { + this.addSize(1L); + } + + return ret; + } + } + } + + // Iterator implementations (similar to original but with UUID instead of long) + protected static final class EntryIterator extends BaseIteratorImpl> { + public EntryIterator(final ConcurrentUUID2ReferenceChainedHashTable map) { + super(map); + } + @Override public TableEntry next() { return this.nextNode(); } + @Override public void forEachRemaining(final Consumer> action) { + Validate.notNull(action, "Action may not be null"); + while (this.hasNext()) { action.accept(this.next()); } + } + } + + protected static final class KeyIterator extends BaseIteratorImpl { + public KeyIterator(final ConcurrentUUID2ReferenceChainedHashTable map) { super(map); } + @Override public UUID next() { return this.nextNode().key; } + @Override public void forEachRemaining(final Consumer action) { + Validate.notNull(action, "Action may not be null"); + while (this.hasNext()) { action.accept(this.next()); } + } + } + + protected static final class ValueIterator extends BaseIteratorImpl { + public ValueIterator(final ConcurrentUUID2ReferenceChainedHashTable map) { super(map); } + @Override public V next() { return this.nextNode().getValueVolatile(); } + @Override public void forEachRemaining(final Consumer action) { + Validate.notNull(action, "Action may not be null"); + while (this.hasNext()) { action.accept(this.next()); } + } + } + + protected static abstract class BaseIteratorImpl extends NodeIterator implements Iterator { + protected final ConcurrentUUID2ReferenceChainedHashTable map; + protected TableEntry lastReturned; + protected TableEntry nextToReturn; + + protected BaseIteratorImpl(final ConcurrentUUID2ReferenceChainedHashTable map) { + super(map.table); + this.map = map; + } + + @Override public final boolean hasNext() { + if (this.nextToReturn != null) return true; + return (this.nextToReturn = this.findNext()) != null; + } + + protected final TableEntry nextNode() throws NoSuchElementException { + TableEntry ret = this.nextToReturn; + if (ret != null) { + this.lastReturned = ret; + this.nextToReturn = null; + return ret; + } + ret = this.findNext(); + if (ret != null) { + this.lastReturned = ret; + return ret; + } + throw new NoSuchElementException(); + } + + @Override public final void remove() { + final TableEntry lastReturned = this.lastReturned; + if (lastReturned == null) throw new NoSuchElementException(); + this.lastReturned = null; + this.map.remove(lastReturned.key); + } + + @Override public abstract T next() throws NoSuchElementException; + @Override public abstract void forEachRemaining(final Consumer action); + } + + protected static class NodeIterator { + // Implementation similar to original but with UUID keys + protected TableEntry[] currentTable; + protected ResizeChain resizeChain; + protected TableEntry last; + protected int nextBin; + protected int increment; + + protected NodeIterator(final TableEntry[] baseTable) { + this.currentTable = baseTable; + this.increment = 1; + } + + private TableEntry[] pullResizeChain(final int index) { + final ResizeChain resizeChain = this.resizeChain; + if (resizeChain == null) { + this.currentTable = null; + return null; + } + + final ResizeChain prevChain = resizeChain.prev; + this.resizeChain = prevChain; + if (prevChain == null) { + this.currentTable = null; + return null; + } + + final TableEntry[] newTable = prevChain.table; + int newIdx = index & (newTable.length - 1); + + final ResizeChain nextPrevChain = prevChain.prev; + final int increment = nextPrevChain == null ? 1 : nextPrevChain.table.length; + + newIdx += increment; + this.increment = increment; + this.nextBin = newIdx; + this.currentTable = newTable; + + return newTable; + } + + private TableEntry[] pushResizeChain(final TableEntry[] table, final TableEntry entry) { + final ResizeChain chain = this.resizeChain; + + if (chain == null) { + // noinspection unchecked + final TableEntry[] nextTable = (TableEntry[])entry.getValuePlain(); + final ResizeChain oldChain = new ResizeChain<>(table, null, null); + final ResizeChain currChain = new ResizeChain<>(nextTable, oldChain, null); + oldChain.next = currChain; + + this.increment = table.length; + this.resizeChain = currChain; + this.currentTable = nextTable; + + return nextTable; + } else { + ResizeChain currChain = chain.next; + if (currChain == null) { + // noinspection unchecked + final TableEntry[] ret = (TableEntry[])entry.getValuePlain(); + currChain = new ResizeChain<>(ret, chain, null); + chain.next = currChain; + + this.increment = table.length; + this.resizeChain = currChain; + this.currentTable = ret; + + return ret; + } else { + this.increment = table.length; + this.resizeChain = currChain; + return this.currentTable = currChain.table; + } + } + } + + protected final TableEntry findNext() { + for (;;) { + final TableEntry last = this.last; + if (last != null) { + final TableEntry next = last.getNextVolatile(); + if (next != null) { + this.last = next; + if (next.getValuePlain() == null) continue; + return next; + } + } + + TableEntry[] table = this.currentTable; + if (table == null) return null; + + int idx = this.nextBin; + int increment = this.increment; + for (;;) { + if (idx >= table.length) { + table = this.pullResizeChain(idx); + idx = this.nextBin; + increment = this.increment; + if (table != null) continue; + this.last = null; + return null; + } + + final TableEntry entry = getAtIndexVolatile(table, idx); + if (entry == null) { + idx += increment; + continue; + } + + if (entry.resize) { + table = this.pushResizeChain(table, entry); + increment = this.increment; + continue; + } + + this.last = entry; + this.nextBin = idx + increment; + if (entry.getValuePlain() != null) return entry; + break; + } + } + } + + protected static final class ResizeChain { + public final TableEntry[] table; + public final ResizeChain prev; + public ResizeChain next; + + public ResizeChain(final TableEntry[] table, final ResizeChain prev, final ResizeChain next) { + this.table = table; + this.prev = prev; + this.next = next; + } + } + } + + protected static abstract class BaseCollection implements Collection { + protected final ConcurrentUUID2ReferenceChainedHashTable map; + + protected BaseCollection(final ConcurrentUUID2ReferenceChainedHashTable map) { + this.map = map; + } + + @Override public int size() { return this.map.size(); } + @Override public boolean isEmpty() { return this.map.isEmpty(); } + @Override public void forEach(final Consumer action) { this.iterator().forEachRemaining(action); } + + private List asList() { + final List ret = new ArrayList<>(this.map.size()); + for (final E element : this) ret.add(element); + return ret; + } + + @Override public Object[] toArray() { return this.asList().toArray(); } + @Override public T[] toArray(final T[] a) { return this.asList().toArray(a); } + + @Override public boolean containsAll(final Collection collection) { + for (final Object value : collection) { + if (!this.contains(value)) return false; + } + return true; + } + + @Override public boolean add(final E value) { throw new UnsupportedOperationException(); } + @Override public boolean remove(final Object value) { throw new UnsupportedOperationException(); } + @Override public boolean addAll(final Collection collection) { throw new UnsupportedOperationException(); } + @Override public boolean removeAll(final Collection collection) { throw new UnsupportedOperationException(); } + @Override public boolean removeIf(final Predicate filter) { throw new UnsupportedOperationException(); } + @Override public boolean retainAll(final Collection collection) { throw new UnsupportedOperationException(); } + @Override public void clear() { throw new UnsupportedOperationException(); } + } + + protected static class Values extends BaseCollection { + public Values(final ConcurrentUUID2ReferenceChainedHashTable map) { super(map); } + @Override public boolean contains(final Object value) { return this.map.containsValue((V)value); } + @Override public Iterator iterator() { return this.map.valueIterator(); } + } + + protected static class EntrySet extends BaseCollection> implements Set> { + protected EntrySet(final ConcurrentUUID2ReferenceChainedHashTable map) { super(map); } + @Override public boolean contains(final Object value) { + if (!(value instanceof TableEntry entry)) return false; + final V mapped = this.map.get((UUID)entry.getKey()); + return mapped != null && mapped == value; + } + @Override public Iterator> iterator() { return this.map.entryIterator(); } + } + + public static final class TableEntry { + private static final VarHandle TABLE_ENTRY_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(TableEntry[].class); + + private final boolean resize; + private final UUID key; + + private volatile V value; + private static final VarHandle VALUE_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "value", Object.class); + + private V getValuePlain() { return (V)VALUE_HANDLE.get(this); } + private V getValueAcquire() { return (V)VALUE_HANDLE.getAcquire(this); } + private V getValueVolatile() { return (V)VALUE_HANDLE.getVolatile(this); } + private void setValuePlain(final V value) { VALUE_HANDLE.set(this, (Object)value); } + private void setValueRelease(final V value) { VALUE_HANDLE.setRelease(this, (Object)value); } + private void setValueVolatile(final V value) { VALUE_HANDLE.setVolatile(this, (Object)value); } + + private volatile TableEntry next; + private static final VarHandle NEXT_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "next", TableEntry.class); + + private TableEntry getNextPlain() { return (TableEntry)NEXT_HANDLE.get(this); } + private TableEntry getNextVolatile() { return (TableEntry)NEXT_HANDLE.getVolatile(this); } + private void setNextPlain(final TableEntry next) { NEXT_HANDLE.set(this, next); } + private void setNextRelease(final TableEntry next) { NEXT_HANDLE.setRelease(this, next); } + private void setNextVolatile(final TableEntry next) { NEXT_HANDLE.setVolatile(this, next); } + + public TableEntry(final UUID key, final V value) { + this.resize = false; + this.key = key; + this.setValuePlain(value); + } + + public TableEntry(final UUID key, final V value, final boolean resize) { + this.resize = resize; + this.key = key; + this.setValuePlain(value); + } + + public UUID getKey() { return this.key; } + public V getValue() { return this.getValueVolatile(); } + } +} \ No newline at end of file