mirror of
https://github.com/BX-Team/DivineMC.git
synced 2025-12-21 07:49:18 +00:00
update multithreaded tracker and pwt, some fixes
This commit is contained in:
@@ -3,7 +3,6 @@ package org.bxteam.divinemc.entity.tracking;
|
||||
import ca.spottedleaf.moonrise.common.list.ReferenceList;
|
||||
import ca.spottedleaf.moonrise.common.misc.NearbyPlayers;
|
||||
import ca.spottedleaf.moonrise.common.util.TickThread;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup;
|
||||
import ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity;
|
||||
import net.minecraft.server.level.ChunkMap;
|
||||
@@ -19,7 +18,6 @@ import org.jetbrains.annotations.NotNull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.RejectedExecutionHandler;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
@@ -31,7 +29,7 @@ public class MultithreadedTracker {
|
||||
private static final Logger LOGGER = LogManager.getLogger(THREAD_PREFIX);
|
||||
|
||||
private static long lastWarnMillis = System.currentTimeMillis();
|
||||
private static final ThreadPoolExecutor trackerExecutor = new ThreadPoolExecutor(
|
||||
private static final ThreadPoolExecutor TRACKER_EXECUTOR = new ThreadPoolExecutor(
|
||||
getCorePoolSize(),
|
||||
getMaxPoolSize(),
|
||||
getKeepAliveTime(), TimeUnit.SECONDS,
|
||||
@@ -40,11 +38,7 @@ public class MultithreadedTracker {
|
||||
getRejectedPolicy()
|
||||
);
|
||||
|
||||
public static Executor getTrackerExecutor() {
|
||||
return trackerExecutor;
|
||||
}
|
||||
|
||||
public static void tick(ChunkSystemServerLevel level) {
|
||||
public static void tick(ServerLevel level) {
|
||||
try {
|
||||
if (!DivineConfig.AsyncCategory.multithreadedCompatModeEnabled) {
|
||||
tickAsync(level);
|
||||
@@ -56,15 +50,14 @@ public class MultithreadedTracker {
|
||||
}
|
||||
}
|
||||
|
||||
private static void tickAsync(ChunkSystemServerLevel level) {
|
||||
private static void tickAsync(ServerLevel level) {
|
||||
final NearbyPlayers nearbyPlayers = level.moonrise$getNearbyPlayers();
|
||||
final ServerEntityLookup entityLookup = (ServerEntityLookup) level.moonrise$getEntityLookup();
|
||||
|
||||
final ReferenceList<Entity> trackerEntities = entityLookup.trackerEntities;
|
||||
final Entity[] trackerEntitiesRaw = trackerEntities.getRawDataUnchecked();
|
||||
|
||||
// Move tracking to off-main
|
||||
trackerExecutor.execute(() -> {
|
||||
TRACKER_EXECUTOR.execute(() -> {
|
||||
for (final Entity entity : trackerEntitiesRaw) {
|
||||
if (entity == null) continue;
|
||||
|
||||
@@ -72,19 +65,23 @@ public class MultithreadedTracker {
|
||||
|
||||
if (tracker == null) continue;
|
||||
|
||||
tracker.moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition()));
|
||||
tracker.serverEntity.sendChanges();
|
||||
synchronized (tracker) {
|
||||
var trackedChunk = nearbyPlayers.getChunk(entity.chunkPosition());
|
||||
tracker.moonrise$tick(trackedChunk);
|
||||
tracker.serverEntity.sendChanges();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void tickAsyncWithCompatMode(ChunkSystemServerLevel level) {
|
||||
private static void tickAsyncWithCompatMode(ServerLevel level) {
|
||||
final NearbyPlayers nearbyPlayers = level.moonrise$getNearbyPlayers();
|
||||
final ServerEntityLookup entityLookup = (ServerEntityLookup) level.moonrise$getEntityLookup();
|
||||
|
||||
final ReferenceList<Entity> trackerEntities = entityLookup.trackerEntities;
|
||||
final Entity[] trackerEntitiesRaw = trackerEntities.getRawDataUnchecked();
|
||||
final Runnable[] sendChangesTasks = new Runnable[trackerEntitiesRaw.length];
|
||||
final Runnable[] tickTask = new Runnable[trackerEntitiesRaw.length];
|
||||
int index = 0;
|
||||
|
||||
for (final Entity entity : trackerEntitiesRaw) {
|
||||
@@ -94,12 +91,19 @@ public class MultithreadedTracker {
|
||||
|
||||
if (tracker == null) continue;
|
||||
|
||||
tracker.moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition()));
|
||||
sendChangesTasks[index++] = () -> tracker.serverEntity.sendChanges(); // Collect send changes to task array
|
||||
synchronized (tracker) {
|
||||
tickTask[index] = tracker.tickCompact(nearbyPlayers.getChunk(entity.chunkPosition()));
|
||||
sendChangesTasks[index] = () -> tracker.serverEntity.sendChanges();
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
// batch submit tasks
|
||||
trackerExecutor.execute(() -> {
|
||||
TRACKER_EXECUTOR.execute(() -> {
|
||||
for (final Runnable tick : tickTask) {
|
||||
if (tick == null) continue;
|
||||
|
||||
tick.run();
|
||||
}
|
||||
for (final Runnable sendChanges : sendChangesTasks) {
|
||||
if (sendChanges == null) continue;
|
||||
|
||||
|
||||
@@ -0,0 +1,326 @@
|
||||
package org.bxteam.divinemc.util.map;
|
||||
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.world.entity.ai.attributes.Attribute;
|
||||
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.AbstractMap.SimpleEntry;
|
||||
|
||||
/**
|
||||
* @author hayanesuru
|
||||
*/
|
||||
public final class AttributeInstanceArrayMap implements Map<Holder<Attribute>, AttributeInstance>, Cloneable {
|
||||
private static final int VANILLA_ATTRIBUTE_SIZE = 35; // 1.21.6
|
||||
|
||||
private int size = 0;
|
||||
private transient AttributeInstance[] a = new AttributeInstance[VANILLA_ATTRIBUTE_SIZE];
|
||||
private transient KeySet keys;
|
||||
private transient Values values;
|
||||
private transient EntrySet entries;
|
||||
|
||||
public AttributeInstanceArrayMap() {
|
||||
if (BuiltInRegistries.ATTRIBUTE.size() != VANILLA_ATTRIBUTE_SIZE) {
|
||||
throw new IllegalStateException("Unexpected registry minecraft:attribute size");
|
||||
}
|
||||
}
|
||||
|
||||
public AttributeInstanceArrayMap(final @NotNull Map<Holder<Attribute>, AttributeInstance> m) {
|
||||
this();
|
||||
putAll(m);
|
||||
}
|
||||
|
||||
private void setByIndex(int index, @Nullable AttributeInstance instance) {
|
||||
boolean empty = a[index] == null;
|
||||
if (instance == null) {
|
||||
if (!empty) {
|
||||
size--;
|
||||
a[index] = null;
|
||||
}
|
||||
} else {
|
||||
if (empty) {
|
||||
size++;
|
||||
}
|
||||
a[index] = instance;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
if (key instanceof Holder<?> holder && holder.value() instanceof Attribute attribute) {
|
||||
int uid = attribute.uid;
|
||||
return uid >= 0 && uid < a.length && a[uid] != null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
return value instanceof AttributeInstance val && Objects.equals(getInstance(val.getAttribute().value().uid), val);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeInstance get(Object key) {
|
||||
return key instanceof Holder<?> holder && holder.value() instanceof Attribute attribute ? a[attribute.uid] : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public AttributeInstance getInstance(int key) {
|
||||
return a[key];
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeInstance put(@NotNull Holder<Attribute> key, AttributeInstance value) {
|
||||
int uid = key.value().uid;
|
||||
AttributeInstance prev = a[uid];
|
||||
setByIndex(uid, value);
|
||||
return prev;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeInstance remove(Object key) {
|
||||
if (!(key instanceof Holder<?> holder) || !(holder.value() instanceof Attribute attribute)) return null;
|
||||
int uid = attribute.uid;
|
||||
AttributeInstance prev = a[uid];
|
||||
setByIndex(uid, null);
|
||||
return prev;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(@NotNull Map<? extends Holder<Attribute>, ? extends AttributeInstance> m) {
|
||||
for (AttributeInstance e : m.values()) {
|
||||
if (e != null) {
|
||||
setByIndex(e.getAttribute().value().uid, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
Arrays.fill(a, null);
|
||||
size = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Set<Holder<Attribute>> keySet() {
|
||||
if (keys == null) {
|
||||
keys = new KeySet();
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Collection<AttributeInstance> values() {
|
||||
if (values == null) {
|
||||
values = new Values();
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Set<Entry<Holder<Attribute>, AttributeInstance>> entrySet() {
|
||||
if (entries == null) {
|
||||
entries = new EntrySet();
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == this) return true;
|
||||
if (!(o instanceof Map<?, ?> s)) return false;
|
||||
if (s.size() != size()) return false;
|
||||
if (o instanceof AttributeInstanceArrayMap that) {
|
||||
return Arrays.equals(a, that.a);
|
||||
}
|
||||
for (Entry<?, ?> e : s.entrySet()) {
|
||||
if (!Objects.equals(get(e.getKey()), e.getValue())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeInstanceArrayMap clone() {
|
||||
AttributeInstanceArrayMap c;
|
||||
try {
|
||||
c = (AttributeInstanceArrayMap) super.clone();
|
||||
} catch (CloneNotSupportedException cantHappen) {
|
||||
throw new InternalError();
|
||||
}
|
||||
c.a = a.clone();
|
||||
c.entries = null;
|
||||
c.keys = null;
|
||||
c.values = null;
|
||||
return c;
|
||||
}
|
||||
|
||||
private final class KeySet extends AbstractSet<Holder<Attribute>> {
|
||||
@Override
|
||||
public @NotNull Iterator<Holder<Attribute>> iterator() {
|
||||
return new KeyIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
return AttributeInstanceArrayMap.this.containsKey(o);
|
||||
}
|
||||
}
|
||||
|
||||
private final class KeyIterator implements Iterator<Holder<Attribute>> {
|
||||
private int currentIndex = -1;
|
||||
private int nextIndex = findNextOccupied(0);
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return nextIndex != -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Holder<Attribute> next() {
|
||||
if (!hasNext()) throw new NoSuchElementException();
|
||||
currentIndex = nextIndex;
|
||||
nextIndex = findNextOccupied(nextIndex + 1);
|
||||
return BuiltInRegistries.ATTRIBUTE.get(currentIndex).orElseThrow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
if (currentIndex == -1) throw new IllegalStateException();
|
||||
setByIndex(currentIndex, null);
|
||||
currentIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
private final class Values extends AbstractCollection<AttributeInstance> {
|
||||
@Override
|
||||
public @NotNull Iterator<AttributeInstance> iterator() {
|
||||
return new ValueIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
return containsValue(o);
|
||||
}
|
||||
}
|
||||
|
||||
private final class ValueIterator implements Iterator<AttributeInstance> {
|
||||
private int currentIndex = -1;
|
||||
private int nextIndex = findNextOccupied(0);
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return nextIndex != -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeInstance next() {
|
||||
if (!hasNext()) throw new NoSuchElementException();
|
||||
currentIndex = nextIndex;
|
||||
AttributeInstance value = a[nextIndex];
|
||||
nextIndex = findNextOccupied(nextIndex + 1);
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
if (currentIndex == -1) throw new IllegalStateException();
|
||||
setByIndex(currentIndex, null);
|
||||
currentIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
private final class EntrySet extends AbstractSet<Entry<Holder<Attribute>, AttributeInstance>> {
|
||||
@Override
|
||||
public @NotNull Iterator<Entry<Holder<Attribute>, AttributeInstance>> iterator() {
|
||||
return new EntryIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
if (!(o instanceof Entry<?, ?> e)) {
|
||||
return false;
|
||||
}
|
||||
return Objects.equals(get(e.getKey()), e.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private final class EntryIterator implements Iterator<Entry<Holder<Attribute>, AttributeInstance>> {
|
||||
private int currentIndex = -1;
|
||||
private int nextIndex = findNextOccupied(0);
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return nextIndex != -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<Holder<Attribute>, AttributeInstance> next() {
|
||||
if (!hasNext()) throw new NoSuchElementException();
|
||||
currentIndex = nextIndex;
|
||||
Holder<Attribute> key = BuiltInRegistries.ATTRIBUTE.get(nextIndex).orElseThrow();
|
||||
AttributeInstance value = a[nextIndex];
|
||||
nextIndex = findNextOccupied(nextIndex + 1);
|
||||
return new SimpleEntry<>(key, value) {
|
||||
@Override
|
||||
public AttributeInstance setValue(AttributeInstance newValue) {
|
||||
AttributeInstance old = put(key, newValue);
|
||||
super.setValue(newValue);
|
||||
return old;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
if (currentIndex == -1) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
setByIndex(currentIndex, null);
|
||||
currentIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
private int findNextOccupied(int start) {
|
||||
for (int i = start; i < a.length; i++) {
|
||||
if (a[i] != null) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package org.bxteam.divinemc.util.map;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntArraySet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author hayanesuru
|
||||
*/
|
||||
public final class AttributeInstanceSet extends AbstractCollection<AttributeInstance> implements Set<AttributeInstance> {
|
||||
public final IntSet inner;
|
||||
public final AttributeInstanceArrayMap map;
|
||||
|
||||
public AttributeInstanceSet(AttributeInstanceArrayMap map) {
|
||||
this.map = map;
|
||||
inner = new IntArraySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(AttributeInstance instance) {
|
||||
return inner.add(instance.getAttribute().value().uid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
return o instanceof AttributeInstance instance && inner.remove(instance.getAttribute().value().uid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Iterator<AttributeInstance> iterator() {
|
||||
return new CloneIterator(inner.toIntArray(), map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return inner.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return inner.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
inner.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
if (o instanceof AttributeInstance instance) {
|
||||
return inner.contains(instance.getAttribute().value().uid);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeInstance @NotNull [] toArray() {
|
||||
int[] innerClone = inner.toIntArray();
|
||||
AttributeInstance[] arr = new AttributeInstance[innerClone.length];
|
||||
for (int i = 0; i < arr.length; i++) {
|
||||
arr[i] = map.getInstance(innerClone[i]);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@Override
|
||||
public <T> T @NotNull [] toArray(T[] a) {
|
||||
if (a == null || (a.getClass() == AttributeInstance[].class && a.length == 0)) {
|
||||
return (T[]) toArray();
|
||||
}
|
||||
if (a.length < size()) {
|
||||
a = (T[]) Array.newInstance(a.getClass().getComponentType(), size());
|
||||
}
|
||||
System.arraycopy((T[]) toArray(), 0, a, 0, size());
|
||||
if (a.length > size()) {
|
||||
a[size()] = null;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
static class CloneIterator implements Iterator<AttributeInstance> {
|
||||
private final int[] array;
|
||||
private int index = 0;
|
||||
private final AttributeInstanceArrayMap map;
|
||||
|
||||
CloneIterator(int[] array, AttributeInstanceArrayMap map) {
|
||||
this.array = array;
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return index < array.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeInstance next() {
|
||||
if (!hasNext()) throw new NoSuchElementException();
|
||||
return map.getInstance(array[index++]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == this) return true;
|
||||
if (!(o instanceof Set<?> s)) return false;
|
||||
if (s.size() != size()) return false;
|
||||
return containsAll(s);
|
||||
}
|
||||
}
|
||||
@@ -6,102 +6,136 @@ import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.longs.LongSet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* A thread-safe implementation of {@link LongOpenHashSet} using ConcurrentHashMap.KeySetView as backing storage.
|
||||
* This implementation provides concurrent access and high performance for concurrent operations.
|
||||
* Optimized thread-safe implementation of {@link LongSet} that uses striped locking
|
||||
* and primitive long arrays to minimize boxing/unboxing overhead.
|
||||
*
|
||||
* @author HaHaWTH at Leaf
|
||||
*/
|
||||
@SuppressWarnings({"unused", "deprecation"})
|
||||
public final class ConcurrentLongHashSet extends LongOpenHashSet implements LongSet {
|
||||
private final ConcurrentHashMap.KeySetView<Long, Boolean> backing;
|
||||
// Number of lock stripes - higher number means more concurrency but more memory
|
||||
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
|
||||
|
||||
// Load factor - when to resize the hash table
|
||||
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
|
||||
|
||||
// Initial capacity per stripe
|
||||
private static final int DEFAULT_INITIAL_CAPACITY = 16;
|
||||
|
||||
// Array of segments (stripes)
|
||||
private final Segment[] segments;
|
||||
|
||||
// Total size, cached for faster size() operation
|
||||
private final AtomicInteger size;
|
||||
|
||||
/**
|
||||
* Creates a new empty concurrent long set.
|
||||
* Creates a new empty concurrent long set with default parameters.
|
||||
*/
|
||||
public ConcurrentLongHashSet() {
|
||||
this.backing = ConcurrentHashMap.newKeySet();
|
||||
this(DEFAULT_CONCURRENCY_LEVEL * DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new concurrent long set with the specified parameters.
|
||||
*
|
||||
* @param initialCapacity the initial capacity
|
||||
* @param loadFactor the load factor
|
||||
* @param concurrencyLevel the concurrency level
|
||||
*/
|
||||
public ConcurrentLongHashSet(int initialCapacity, float loadFactor, int concurrencyLevel) {
|
||||
// Need to call super() even though we don't use its state
|
||||
super();
|
||||
|
||||
// Validate parameters
|
||||
if (initialCapacity < 0) {
|
||||
throw new IllegalArgumentException("Initial capacity must be positive");
|
||||
}
|
||||
if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
|
||||
throw new IllegalArgumentException("Load factor must be positive");
|
||||
}
|
||||
if (concurrencyLevel <= 0) {
|
||||
throw new IllegalArgumentException("Concurrency level must be positive");
|
||||
}
|
||||
|
||||
// Calculate segment count (power of 2)
|
||||
int segmentCount = 1;
|
||||
while (segmentCount < concurrencyLevel) {
|
||||
segmentCount <<= 1;
|
||||
}
|
||||
|
||||
// Calculate capacity per segment
|
||||
int segmentCapacity = Math.max(initialCapacity / segmentCount, DEFAULT_INITIAL_CAPACITY);
|
||||
|
||||
// Create segments
|
||||
this.segments = new Segment[segmentCount];
|
||||
for (int i = 0; i < segmentCount; i++) {
|
||||
this.segments[i] = new Segment(segmentCapacity, loadFactor);
|
||||
}
|
||||
|
||||
this.size = new AtomicInteger(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return backing.size();
|
||||
return size.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return backing.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull LongIterator iterator() {
|
||||
return new WrappingLongIterator(backing.iterator());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Object @NotNull [] toArray() {
|
||||
return backing.toArray();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public <T> T @NotNull [] toArray(@NotNull T @NotNull [] array) {
|
||||
Objects.requireNonNull(array, "Array cannot be null");
|
||||
return backing.toArray(array);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(@NotNull Collection<?> collection) {
|
||||
Objects.requireNonNull(collection, "Collection cannot be null");
|
||||
return backing.containsAll(collection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(@NotNull Collection<? extends Long> collection) {
|
||||
Objects.requireNonNull(collection, "Collection cannot be null");
|
||||
return backing.addAll(collection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(@NotNull Collection<?> collection) {
|
||||
Objects.requireNonNull(collection, "Collection cannot be null");
|
||||
return backing.removeAll(collection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(@NotNull Collection<?> collection) {
|
||||
Objects.requireNonNull(collection, "Collection cannot be null");
|
||||
return backing.retainAll(collection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
backing.clear();
|
||||
return size.get() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(long key) {
|
||||
return backing.add(key);
|
||||
Segment segment = segmentFor(key);
|
||||
int delta = segment.add(key) ? 1 : 0;
|
||||
if (delta > 0) {
|
||||
size.addAndGet(delta);
|
||||
}
|
||||
return delta > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(long key) {
|
||||
return backing.contains(key);
|
||||
return segmentFor(key).contains(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(long key) {
|
||||
Segment segment = segmentFor(key);
|
||||
int delta = segment.remove(key) ? -1 : 0;
|
||||
if (delta < 0) {
|
||||
size.addAndGet(delta);
|
||||
}
|
||||
return delta < 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
for (Segment segment : segments) {
|
||||
segment.clear();
|
||||
}
|
||||
size.set(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull LongIterator iterator() {
|
||||
return new ConcurrentLongIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long[] toLongArray() {
|
||||
int size = backing.size();
|
||||
long[] result = new long[size];
|
||||
int i = 0;
|
||||
for (Long value : backing) {
|
||||
result[i++] = value;
|
||||
long[] result = new long[size()];
|
||||
int index = 0;
|
||||
for (Segment segment : segments) {
|
||||
index = segment.toLongArray(result, index);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -120,6 +154,104 @@ public final class ConcurrentLongHashSet extends LongOpenHashSet implements Long
|
||||
return array;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Object @NotNull [] toArray() {
|
||||
Long[] result = new Long[size()];
|
||||
int index = 0;
|
||||
for (Segment segment : segments) {
|
||||
index = segment.toObjectArray(result, index);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public <T> T @NotNull [] toArray(@NotNull T @NotNull [] array) {
|
||||
Objects.requireNonNull(array, "Array cannot be null");
|
||||
Long[] result = new Long[size()];
|
||||
int index = 0;
|
||||
for (Segment segment : segments) {
|
||||
index = segment.toObjectArray(result, index);
|
||||
}
|
||||
|
||||
if (array.length < result.length) {
|
||||
return (T[]) result;
|
||||
}
|
||||
|
||||
System.arraycopy(result, 0, array, 0, result.length);
|
||||
if (array.length > result.length) {
|
||||
array[result.length] = null;
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(@NotNull Collection<?> collection) {
|
||||
Objects.requireNonNull(collection, "Collection cannot be null");
|
||||
for (Object o : collection) {
|
||||
if (o instanceof Long) {
|
||||
if (!contains(((Long) o).longValue())) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(@NotNull Collection<? extends Long> collection) {
|
||||
Objects.requireNonNull(collection, "Collection cannot be null");
|
||||
boolean modified = false;
|
||||
for (Long value : collection) {
|
||||
modified |= add(value);
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(@NotNull Collection<?> collection) {
|
||||
Objects.requireNonNull(collection, "Collection cannot be null");
|
||||
boolean modified = false;
|
||||
for (Object o : collection) {
|
||||
if (o instanceof Long) {
|
||||
modified |= remove(((Long) o).longValue());
|
||||
}
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(@NotNull Collection<?> collection) {
|
||||
Objects.requireNonNull(collection, "Collection cannot be null");
|
||||
|
||||
// Convert collection to a set of longs for faster lookups
|
||||
LongOpenHashSet toRetain = new LongOpenHashSet();
|
||||
for (Object o : collection) {
|
||||
if (o instanceof Long) {
|
||||
toRetain.add(((Long) o).longValue());
|
||||
}
|
||||
}
|
||||
|
||||
boolean modified = false;
|
||||
for (Segment segment : segments) {
|
||||
modified |= segment.retainAll(toRetain);
|
||||
}
|
||||
|
||||
if (modified) {
|
||||
// Recalculate size
|
||||
int newSize = 0;
|
||||
for (Segment segment : segments) {
|
||||
newSize += segment.size();
|
||||
}
|
||||
size.set(newSize);
|
||||
}
|
||||
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(LongCollection c) {
|
||||
Objects.requireNonNull(c, "Collection cannot be null");
|
||||
@@ -157,12 +289,23 @@ public final class ConcurrentLongHashSet extends LongOpenHashSet implements Long
|
||||
@Override
|
||||
public boolean retainAll(LongCollection c) {
|
||||
Objects.requireNonNull(c, "Collection cannot be null");
|
||||
return backing.retainAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(long k) {
|
||||
return backing.remove(k);
|
||||
// For LongCollection we can directly use it
|
||||
boolean modified = false;
|
||||
for (Segment segment : segments) {
|
||||
modified |= segment.retainAll(c);
|
||||
}
|
||||
|
||||
if (modified) {
|
||||
// Recalculate size
|
||||
int newSize = 0;
|
||||
for (Segment segment : segments) {
|
||||
newSize += segment.size();
|
||||
}
|
||||
size.set(newSize);
|
||||
}
|
||||
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -175,39 +318,382 @@ public final class ConcurrentLongHashSet extends LongOpenHashSet implements Long
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return backing.hashCode();
|
||||
int hash = 0;
|
||||
for (Segment segment : segments) {
|
||||
hash += segment.hashCode();
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return backing.toString();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append('[');
|
||||
|
||||
LongIterator it = iterator();
|
||||
boolean hasNext = it.hasNext();
|
||||
while (hasNext) {
|
||||
sb.append(it.nextLong());
|
||||
hasNext = it.hasNext();
|
||||
if (hasNext) {
|
||||
sb.append(", ");
|
||||
}
|
||||
}
|
||||
|
||||
sb.append(']');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
static class WrappingLongIterator implements LongIterator {
|
||||
private final Iterator<Long> backing;
|
||||
/**
|
||||
* Find the segment for a given key.
|
||||
*/
|
||||
private Segment segmentFor(long key) {
|
||||
// Use high bits of hash to determine segment
|
||||
// This helps spread keys more evenly across segments
|
||||
return segments[(int) ((spread(key) >>> segmentShift()) & segmentMask())];
|
||||
}
|
||||
|
||||
WrappingLongIterator(Iterator<Long> backing) {
|
||||
this.backing = Objects.requireNonNull(backing);
|
||||
/**
|
||||
* Spread bits to reduce clustering for keys with similar hash codes.
|
||||
*/
|
||||
private static long spread(long key) {
|
||||
long h = key;
|
||||
h ^= h >>> 32;
|
||||
h ^= h >>> 16;
|
||||
h ^= h >>> 8;
|
||||
return h;
|
||||
}
|
||||
|
||||
private int segmentShift() {
|
||||
return Integer.numberOfLeadingZeros(segments.length);
|
||||
}
|
||||
|
||||
private int segmentMask() {
|
||||
return segments.length - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* A segment is a striped portion of the hash set with its own lock.
|
||||
*/
|
||||
private static class Segment {
|
||||
private final ReentrantLock lock = new ReentrantLock();
|
||||
private long[] keys;
|
||||
private boolean[] used;
|
||||
private int size;
|
||||
private int threshold;
|
||||
private final float loadFactor;
|
||||
|
||||
Segment(int initialCapacity, float loadFactor) {
|
||||
int capacity = MathUtil.nextPowerOfTwo(initialCapacity);
|
||||
this.keys = new long[capacity];
|
||||
this.used = new boolean[capacity];
|
||||
this.size = 0;
|
||||
this.loadFactor = loadFactor;
|
||||
this.threshold = (int) (capacity * loadFactor);
|
||||
}
|
||||
|
||||
int size() {
|
||||
lock.lock();
|
||||
try {
|
||||
return size;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
boolean contains(long key) {
|
||||
lock.lock();
|
||||
try {
|
||||
int index = indexOf(key);
|
||||
return used[index] && keys[index] == key;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
boolean add(long key) {
|
||||
lock.lock();
|
||||
try {
|
||||
int index = indexOf(key);
|
||||
|
||||
// Key already exists
|
||||
if (used[index] && keys[index] == key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Insert key
|
||||
keys[index] = key;
|
||||
if (!used[index]) {
|
||||
used[index] = true;
|
||||
size++;
|
||||
|
||||
// Check if rehash is needed
|
||||
if (size > threshold) {
|
||||
rehash();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
boolean remove(long key) {
|
||||
lock.lock();
|
||||
try {
|
||||
int index = indexOf(key);
|
||||
|
||||
// Key not found
|
||||
if (!used[index] || keys[index] != key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Mark slot as unused
|
||||
used[index] = false;
|
||||
size--;
|
||||
|
||||
// If the next slot is also used, we need to handle the removal properly
|
||||
// to maintain the open addressing property
|
||||
// This rehashing serves as a "cleanup" after removal
|
||||
if (size > 0) {
|
||||
rehashFromIndex(index);
|
||||
}
|
||||
|
||||
return true;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void clear() {
|
||||
lock.lock();
|
||||
try {
|
||||
Arrays.fill(used, false);
|
||||
size = 0;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
int toLongArray(long[] array, int offset) {
|
||||
lock.lock();
|
||||
try {
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
if (used[i]) {
|
||||
array[offset++] = keys[i];
|
||||
}
|
||||
}
|
||||
return offset;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
int toObjectArray(Long[] array, int offset) {
|
||||
lock.lock();
|
||||
try {
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
if (used[i]) {
|
||||
array[offset++] = keys[i];
|
||||
}
|
||||
}
|
||||
return offset;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
boolean retainAll(LongCollection toRetain) {
|
||||
lock.lock();
|
||||
try {
|
||||
boolean modified = false;
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
if (used[i] && !toRetain.contains(keys[i])) {
|
||||
used[i] = false;
|
||||
size--;
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Rehash to clean up if needed
|
||||
if (modified && size > 0) {
|
||||
rehash();
|
||||
}
|
||||
|
||||
return modified;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the index where a key should be stored.
|
||||
* Uses linear probing for collision resolution.
|
||||
*/
|
||||
private int indexOf(long key) {
|
||||
int mask = keys.length - 1;
|
||||
int index = (int) (spread(key) & mask);
|
||||
|
||||
while (used[index] && keys[index] != key) {
|
||||
index = (index + 1) & mask;
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rehash the segment with a larger capacity.
|
||||
*/
|
||||
private void rehash() {
|
||||
int oldCapacity = keys.length;
|
||||
int newCapacity = oldCapacity * 2;
|
||||
|
||||
long[] oldKeys = keys;
|
||||
boolean[] oldUsed = used;
|
||||
|
||||
keys = new long[newCapacity];
|
||||
used = new boolean[newCapacity];
|
||||
size = 0;
|
||||
threshold = (int) (newCapacity * loadFactor);
|
||||
|
||||
// Re-add all keys
|
||||
for (int i = 0; i < oldCapacity; i++) {
|
||||
if (oldUsed[i]) {
|
||||
add(oldKeys[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rehash from a specific index after removal to maintain proper open addressing.
|
||||
*/
|
||||
private void rehashFromIndex(int startIndex) {
|
||||
int mask = keys.length - 1;
|
||||
int currentIndex = startIndex;
|
||||
int nextIndex = (currentIndex + 1) & mask;
|
||||
|
||||
// For each cluster of used slots following the removal point
|
||||
while (used[nextIndex]) {
|
||||
long key = keys[nextIndex];
|
||||
int targetIndex = (int) (spread(key) & mask);
|
||||
|
||||
// If the key's ideal position is between the removal point and the current position,
|
||||
// move it to the removal point
|
||||
if ((targetIndex <= currentIndex && currentIndex < nextIndex) ||
|
||||
(nextIndex < targetIndex && targetIndex <= currentIndex) ||
|
||||
(currentIndex < nextIndex && nextIndex < targetIndex)) {
|
||||
|
||||
keys[currentIndex] = keys[nextIndex];
|
||||
used[currentIndex] = true;
|
||||
used[nextIndex] = false;
|
||||
currentIndex = nextIndex;
|
||||
}
|
||||
|
||||
nextIndex = (nextIndex + 1) & mask;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
lock.lock();
|
||||
try {
|
||||
int hash = 0;
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
if (used[i]) {
|
||||
hash += Long.hashCode(keys[i]);
|
||||
}
|
||||
}
|
||||
return hash;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Concurrent iterator for the set.
|
||||
*/
|
||||
private class ConcurrentLongIterator implements LongIterator {
|
||||
private int segmentIndex;
|
||||
private int keyIndex;
|
||||
private long lastReturned;
|
||||
private boolean lastReturnedValid;
|
||||
|
||||
ConcurrentLongIterator() {
|
||||
segmentIndex = 0;
|
||||
keyIndex = 0;
|
||||
lastReturnedValid = false;
|
||||
advance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return backing.hasNext();
|
||||
return segmentIndex < segments.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long nextLong() {
|
||||
return backing.next();
|
||||
if (!hasNext()) {
|
||||
throw new java.util.NoSuchElementException();
|
||||
}
|
||||
|
||||
lastReturned = segments[segmentIndex].keys[keyIndex];
|
||||
lastReturnedValid = true;
|
||||
advance();
|
||||
return lastReturned;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long next() {
|
||||
return backing.next();
|
||||
return nextLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
backing.remove();
|
||||
if (!lastReturnedValid) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
ConcurrentLongHashSet.this.remove(lastReturned);
|
||||
lastReturnedValid = false;
|
||||
}
|
||||
|
||||
private void advance() {
|
||||
while (segmentIndex < segments.length) {
|
||||
Segment segment = segments[segmentIndex];
|
||||
|
||||
// Lock the segment to get a consistent view
|
||||
segment.lock.lock();
|
||||
try {
|
||||
while (keyIndex < segment.keys.length) {
|
||||
if (segment.used[keyIndex]) {
|
||||
// Found next element
|
||||
return;
|
||||
}
|
||||
keyIndex++;
|
||||
}
|
||||
} finally {
|
||||
segment.lock.unlock();
|
||||
}
|
||||
|
||||
// Move to next segment
|
||||
segmentIndex++;
|
||||
keyIndex = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility class for math operations.
|
||||
*/
|
||||
private static class MathUtil {
|
||||
/**
|
||||
* Returns the next power of two greater than or equal to the given value.
|
||||
*/
|
||||
static int nextPowerOfTwo(int value) {
|
||||
int highestBit = Integer.highestOneBit(value);
|
||||
return value > highestBit ? highestBit << 1 : value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user