mirror of
https://github.com/Winds-Studio/Leaf.git
synced 2025-12-25 09:59:15 +00:00
optimize chunk map (#438)
* rebase * optimize LivingEntity#travel * cleanup * Replace fluid height map * reuse array list in Entity#collide * cleanup * fix fire and liquid collision shape * fix checkInside * inline betweenClosed * cleanup * optimize getOnPos * optimize equals in getOnPos * update mainSupportingBlockPos on dirty * cleanup * rename * merge same patch * rebase and remove properly * [ci skip] cleanup * rebase and rebuild * fix async locator * remove async locator * cleanup * rebase --------- Co-authored-by: Taiyou06 <kaandindar21@gmail.com>
This commit is contained in:
@@ -3,7 +3,6 @@ package org.dreeam.leaf.async;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.dreeam.leaf.async.locate.AsyncLocator;
|
||||
import org.dreeam.leaf.async.path.AsyncPathProcessor;
|
||||
import org.dreeam.leaf.async.tracker.AsyncTracker;
|
||||
|
||||
@@ -48,14 +47,5 @@ public class ShutdownExecutors {
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
if (AsyncLocator.LOCATING_EXECUTOR_SERVICE != null) {
|
||||
LOGGER.info("Waiting for structure locating executor to shutdown...");
|
||||
AsyncLocator.LOCATING_EXECUTOR_SERVICE.shutdown();
|
||||
try {
|
||||
AsyncLocator.LOCATING_EXECUTOR_SERVICE.awaitTermination(60L, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,168 +0,0 @@
|
||||
package org.dreeam.leaf.async.locate;
|
||||
|
||||
import ca.spottedleaf.moonrise.common.util.TickThread;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.HolderSet;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.level.chunk.ChunkGenerator;
|
||||
import net.minecraft.world.level.levelgen.structure.Structure;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
// Original project: https://github.com/thebrightspark/AsyncLocator
|
||||
public class AsyncLocator {
|
||||
|
||||
public static final Logger LOGGER = LogManager.getLogger("Leaf Async Locator");
|
||||
public static final ExecutorService LOCATING_EXECUTOR_SERVICE;
|
||||
|
||||
private AsyncLocator() {
|
||||
}
|
||||
|
||||
public static class AsyncLocatorThread extends TickThread {
|
||||
private static final AtomicInteger THREAD_COUNTER = new AtomicInteger(0);
|
||||
|
||||
public AsyncLocatorThread(Runnable run, String name) {
|
||||
super(run, name, THREAD_COUNTER.incrementAndGet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
super.run();
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
LOCATING_EXECUTOR_SERVICE = !org.dreeam.leaf.config.modules.async.AsyncLocator.enabled ? null : new ThreadPoolExecutor(
|
||||
1,
|
||||
org.dreeam.leaf.config.modules.async.AsyncLocator.asyncLocatorThreads,
|
||||
org.dreeam.leaf.config.modules.async.AsyncLocator.asyncLocatorKeepalive,
|
||||
TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(),
|
||||
new ThreadFactoryBuilder()
|
||||
.setThreadFactory(
|
||||
r -> new AsyncLocatorThread(r, "Leaf Async Locator Thread") {
|
||||
@Override
|
||||
public void run() {
|
||||
r.run();
|
||||
}
|
||||
}
|
||||
)
|
||||
.setNameFormat("Leaf Async Locator Thread - %d")
|
||||
.setPriority(Thread.NORM_PRIORITY - 2)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues a task to locate a feature using {@link ServerLevel#findNearestMapStructure(TagKey, BlockPos, int, boolean)}
|
||||
* and returns a {@link LocateTask} with the futures for it.
|
||||
*/
|
||||
public static LocateTask<BlockPos> locate(
|
||||
ServerLevel level,
|
||||
TagKey<Structure> structureTag,
|
||||
BlockPos pos,
|
||||
int searchRadius,
|
||||
boolean skipKnownStructures
|
||||
) {
|
||||
CompletableFuture<BlockPos> completableFuture = new CompletableFuture<>();
|
||||
Future<?> future = LOCATING_EXECUTOR_SERVICE.submit(
|
||||
() -> doLocateLevel(completableFuture, level, structureTag, pos, searchRadius, skipKnownStructures)
|
||||
);
|
||||
return new LocateTask<>(level.getServer(), completableFuture, future);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues a task to locate a feature using
|
||||
* {@link ChunkGenerator#findNearestMapStructure(ServerLevel, HolderSet, BlockPos, int, boolean)} and returns a
|
||||
* {@link LocateTask} with the futures for it.
|
||||
*/
|
||||
public static LocateTask<Pair<BlockPos, Holder<Structure>>> locate(
|
||||
ServerLevel level,
|
||||
HolderSet<Structure> structureSet,
|
||||
BlockPos pos,
|
||||
int searchRadius,
|
||||
boolean skipKnownStructures
|
||||
) {
|
||||
CompletableFuture<Pair<BlockPos, Holder<Structure>>> completableFuture = new CompletableFuture<>();
|
||||
Future<?> future = LOCATING_EXECUTOR_SERVICE.submit(
|
||||
() -> doLocateChunkGenerator(completableFuture, level, structureSet, pos, searchRadius, skipKnownStructures)
|
||||
);
|
||||
return new LocateTask<>(level.getServer(), completableFuture, future);
|
||||
}
|
||||
|
||||
private static void doLocateLevel(
|
||||
CompletableFuture<BlockPos> completableFuture,
|
||||
ServerLevel level,
|
||||
TagKey<Structure> structureTag,
|
||||
BlockPos pos,
|
||||
int searchRadius,
|
||||
boolean skipExistingChunks
|
||||
) {
|
||||
BlockPos foundPos = level.findNearestMapStructure(structureTag, pos, searchRadius, skipExistingChunks);
|
||||
completableFuture.complete(foundPos);
|
||||
}
|
||||
|
||||
private static void doLocateChunkGenerator(
|
||||
CompletableFuture<Pair<BlockPos, Holder<Structure>>> completableFuture,
|
||||
ServerLevel level,
|
||||
HolderSet<Structure> structureSet,
|
||||
BlockPos pos,
|
||||
int searchRadius,
|
||||
boolean skipExistingChunks
|
||||
) {
|
||||
Pair<BlockPos, Holder<Structure>> foundPair = level.getChunkSource().getGenerator()
|
||||
.findNearestMapStructure(level, structureSet, pos, searchRadius, skipExistingChunks);
|
||||
completableFuture.complete(foundPair);
|
||||
}
|
||||
|
||||
/**
|
||||
* Holder of the futures for an async locate task as well as providing some helper functions.
|
||||
* The completableFuture will be completed once the call to
|
||||
* {@link ServerLevel#findNearestMapStructure(TagKey, BlockPos, int, boolean)} has completed, and will hold the
|
||||
* result of it.
|
||||
* The taskFuture is the future for the {@link Runnable} itself in the executor service.
|
||||
*/
|
||||
public record LocateTask<T>(MinecraftServer server, CompletableFuture<T> completableFuture, Future<?> taskFuture) {
|
||||
/**
|
||||
* Helper function that calls {@link CompletableFuture#thenAccept(Consumer)} with the given action.
|
||||
* Bear in mind that the action will be executed from the task's thread. If you intend to change any game data,
|
||||
* it's strongly advised you use {@link #thenOnServerThread(Consumer)} instead so that it's queued and executed
|
||||
* on the main server thread instead.
|
||||
*/
|
||||
public LocateTask<T> then(Consumer<T> action) {
|
||||
completableFuture.thenAccept(action);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that calls {@link CompletableFuture#thenAccept(Consumer)} with the given action on the server
|
||||
* thread.
|
||||
*/
|
||||
public LocateTask<T> thenOnServerThread(Consumer<T> action) {
|
||||
completableFuture.thenAccept(pos -> server.scheduleOnMain(() -> action.accept(pos)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that cancels both completableFuture and taskFuture.
|
||||
*/
|
||||
public void cancel() {
|
||||
taskFuture.cancel(true);
|
||||
completableFuture.cancel(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.async;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
import org.dreeam.leaf.config.LeafConfig;
|
||||
|
||||
public class AsyncLocator extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-locator";
|
||||
}
|
||||
|
||||
public static boolean enabled = false;
|
||||
public static int asyncLocatorThreads = 0;
|
||||
public static int asyncLocatorKeepalive = 60;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
config.addCommentRegionBased(getBasePath(), """
|
||||
Whether or not asynchronous locator should be enabled.
|
||||
This offloads structure locating to other threads.
|
||||
Only for locate command, dolphin treasure finding and eye of ender currently.""",
|
||||
"""
|
||||
是否启用异步结构搜索.
|
||||
目前可用于 /locate 指令, 海豚寻宝和末影之眼.""");
|
||||
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
|
||||
asyncLocatorThreads = config.getInt(getBasePath() + ".threads", asyncLocatorThreads);
|
||||
asyncLocatorKeepalive = config.getInt(getBasePath() + ".keepalive", asyncLocatorKeepalive);
|
||||
|
||||
if (asyncLocatorThreads <= 0) {
|
||||
asyncLocatorThreads = 1;
|
||||
}
|
||||
if (!enabled) {
|
||||
asyncLocatorThreads = 0;
|
||||
} else {
|
||||
LeafConfig.LOGGER.info("Using {} threads for Async Locator", asyncLocatorThreads);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
package org.dreeam.leaf.util.map;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.AbstractObject2DoubleMap;
|
||||
import it.unimi.dsi.fastutil.objects.AbstractObjectSet;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectIterator;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectSet;
|
||||
import net.minecraft.tags.FluidTags;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.level.material.Fluid;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
public final class FluidHeightMap extends AbstractObject2DoubleMap<TagKey<Fluid>> {
|
||||
private double water = 0.0;
|
||||
private double lava = 0.0;
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectSet<Entry<TagKey<Fluid>>> object2DoubleEntrySet() {
|
||||
return new EntrySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDouble(Object k) {
|
||||
return k == FluidTags.WATER ? water : k == FluidTags.LAVA ? lava : 0.0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double put(TagKey<Fluid> k, double v) {
|
||||
if (k == FluidTags.WATER) {
|
||||
double prev = this.water;
|
||||
this.water = v;
|
||||
return prev;
|
||||
} else if (k == FluidTags.LAVA) {
|
||||
double prev = this.lava;
|
||||
this.lava = v;
|
||||
return prev;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
this.water = 0.0;
|
||||
this.lava = 0.0;
|
||||
}
|
||||
|
||||
private final class EntrySet extends AbstractObjectSet<Entry<TagKey<Fluid>>> {
|
||||
@Override
|
||||
public ObjectIterator<Entry<TagKey<Fluid>>> iterator() {
|
||||
return new EntryIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
if (!(o instanceof Entry<?> entry)) {
|
||||
return false;
|
||||
}
|
||||
Object key = entry.getKey();
|
||||
if (key == FluidTags.WATER) {
|
||||
return entry.getDoubleValue() == water;
|
||||
} else if (key == FluidTags.LAVA) {
|
||||
return entry.getDoubleValue() == lava;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(final Object o) {
|
||||
if (!(o instanceof Entry<?> entry)) {
|
||||
return false;
|
||||
}
|
||||
Object key = entry.getKey();
|
||||
if (key == FluidTags.WATER) {
|
||||
water = 0.0;
|
||||
return true;
|
||||
} else if (key == FluidTags.LAVA) {
|
||||
lava = 0.0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private final class EntryIterator implements ObjectIterator<Entry<TagKey<Fluid>>> {
|
||||
private int index = 0;
|
||||
private Entry<TagKey<Fluid>> entry = null;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return index < 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<TagKey<Fluid>> next() {
|
||||
if (!hasNext()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
if (index == 0) {
|
||||
index++;
|
||||
return entry = new DoubleEntry(FluidTags.WATER);
|
||||
} else {
|
||||
index++;
|
||||
return entry = new DoubleEntry(FluidTags.LAVA);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
if (entry == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
TagKey<Fluid> key = entry.getKey();
|
||||
if (key == FluidTags.WATER) {
|
||||
water = 0.0;
|
||||
} else if (key == FluidTags.LAVA) {
|
||||
lava = 0.0;
|
||||
}
|
||||
entry = null;
|
||||
}
|
||||
}
|
||||
|
||||
private final class DoubleEntry implements Entry<TagKey<Fluid>> {
|
||||
private final TagKey<Fluid> key;
|
||||
|
||||
public DoubleEntry(TagKey<Fluid> key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TagKey<Fluid> getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDoubleValue() {
|
||||
return key == FluidTags.WATER ? water : lava;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double setValue(double value) {
|
||||
double prev;
|
||||
if (key == FluidTags.WATER) {
|
||||
prev = water;
|
||||
water = value;
|
||||
} else {
|
||||
prev = lava;
|
||||
lava = value;
|
||||
}
|
||||
return prev;
|
||||
}
|
||||
}
|
||||
}
|
||||
304
leaf-server/src/main/java/org/dreeam/leaf/world/ChunkCache.java
Normal file
304
leaf-server/src/main/java/org/dreeam/leaf/world/ChunkCache.java
Normal file
@@ -0,0 +1,304 @@
|
||||
package org.dreeam.leaf.world;
|
||||
|
||||
import ca.spottedleaf.moonrise.common.util.TickThread;
|
||||
import it.unimi.dsi.fastutil.HashCommon;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
/// Optimized chunk map for main thread.
|
||||
/// - Single-entry cache: Maintains a fast-access entry cache for the most recently accessed item
|
||||
/// - Thread safety: Enforces single-threaded access with runtime checks
|
||||
///
|
||||
/// This map is designed to be accessed from a single thread at a time.
|
||||
/// All mutating operations will throw [IllegalStateException]
|
||||
/// if the current thread is not the owning thread.
|
||||
///
|
||||
/// @see it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap
|
||||
public final class ChunkCache<V> {
|
||||
private static final long EMPTY_KEY = Long.MIN_VALUE;
|
||||
private static final float FACTOR = 0.5F;
|
||||
private static final int MIN_N = HashCommon.arraySize(1024, FACTOR);
|
||||
|
||||
private long k1 = EMPTY_KEY;
|
||||
private V v1 = null;
|
||||
private Thread thread;
|
||||
|
||||
private transient long[] key;
|
||||
private transient V[] value;
|
||||
private transient int mask;
|
||||
private transient boolean containsNullKey;
|
||||
private transient int n;
|
||||
private transient int maxFill;
|
||||
private int size;
|
||||
|
||||
public ChunkCache(Thread thread) {
|
||||
n = MIN_N;
|
||||
mask = n - 1;
|
||||
maxFill = HashCommon.maxFill(n, FACTOR);
|
||||
key = new long[n + 1];
|
||||
//noinspection unchecked
|
||||
value = (V[]) new Object[n + 1];
|
||||
this.thread = thread;
|
||||
}
|
||||
|
||||
/// Retrieves the value associated with the specified key.
|
||||
///
|
||||
/// This method implements a single-entry cache optimization:
|
||||
///
|
||||
/// If the requested key matches the most recently accessed key,
|
||||
/// the cached value is returned immediately without a hash map lookup.
|
||||
///
|
||||
/// This method does not perform thread checks for performance reasons.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must ensure that the current thread is the owning thread.
|
||||
///
|
||||
/// @param k The key whose associated value is to be returned
|
||||
/// @return The value associated with the key, or `null` if no mapping exists
|
||||
/// @implNote This method updates the single-entry cache on successful lookups
|
||||
/// @see #isSameThread()
|
||||
public V get(long k) {
|
||||
long k1 = this.k1;
|
||||
V v1 = this.v1;
|
||||
if (k1 == k && v1 != null) {
|
||||
return v1;
|
||||
}
|
||||
if (k == 0L) {
|
||||
if (this.containsNullKey) {
|
||||
this.k1 = k;
|
||||
return this.v1 = this.value[this.n];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
long curr;
|
||||
final long[] key = this.key;
|
||||
int pos;
|
||||
if ((curr = key[pos = (int) HashCommon.mix(k) & this.mask]) == 0) {
|
||||
return null;
|
||||
} else if (k == curr) {
|
||||
this.k1 = k;
|
||||
return this.v1 = this.value[pos];
|
||||
} else {
|
||||
while (true) {
|
||||
if ((curr = key[pos = pos + 1 & this.mask]) == 0) {
|
||||
return null;
|
||||
}
|
||||
if (k == curr) {
|
||||
this.k1 = k;
|
||||
return this.v1 = this.value[pos];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes the mapping for the specified key from this map.
|
||||
///
|
||||
/// If the removed key matches the cached key, the single-entry cache is invalidated.
|
||||
///
|
||||
/// @param k The key whose mapping is to be removed
|
||||
/// @return The previous value associated with the key, or `null` if no mapping existed
|
||||
/// @throws IllegalStateException If the current thread is not the owning thread
|
||||
public V remove(long k) {
|
||||
// Safety: throws IllegalStateException for all non-owning threads
|
||||
ensureSameThread();
|
||||
if (k == k1) {
|
||||
v1 = null;
|
||||
k1 = EMPTY_KEY;
|
||||
}
|
||||
if (((k) == (0))) {
|
||||
if (containsNullKey) return removeNullEntry();
|
||||
return null;
|
||||
}
|
||||
long curr;
|
||||
final long[] key = this.key;
|
||||
int pos;
|
||||
if (((curr = key[pos = (int) HashCommon.mix((k)) & mask]) == (0))) return null;
|
||||
if (((k) == (curr))) return removeEntry(pos);
|
||||
while (true) {
|
||||
if (((curr = key[pos = (pos + 1) & mask]) == (0))) return null;
|
||||
if (((k) == (curr))) return removeEntry(pos);
|
||||
}
|
||||
}
|
||||
|
||||
/// Associates the specified entry in this map.
|
||||
///
|
||||
/// If the key matches the cached key, the single-entry cache is invalidated.
|
||||
///
|
||||
/// @param k The key with which the specified value is to be associated
|
||||
/// @param levelChunk The value to be associated with the specified key
|
||||
/// @return The previous value associated with the key, or null if no mapping existed
|
||||
/// @throws IllegalStateException If the current thread is not the owning thread
|
||||
public V put(long k, V levelChunk) {
|
||||
// Safety: throws IllegalStateException for all non-owning threads
|
||||
ensureSameThread();
|
||||
if (k == k1) {
|
||||
v1 = null;
|
||||
k1 = EMPTY_KEY;
|
||||
}
|
||||
final int pos = find(k);
|
||||
if (pos < 0) {
|
||||
insert(-pos - 1, k, levelChunk);
|
||||
return null;
|
||||
}
|
||||
final V oldValue = value[pos];
|
||||
value[pos] = levelChunk;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
/// Removes all elements from this map.
|
||||
///
|
||||
/// This method also clears the single-entry cache.
|
||||
///
|
||||
/// @throws IllegalStateException If the current thread is not the owning thread
|
||||
public void clear() {
|
||||
// Safety: throws IllegalStateException for all non-owning threads
|
||||
ensureSameThread();
|
||||
v1 = null;
|
||||
k1 = EMPTY_KEY;
|
||||
if (size == 0) return;
|
||||
size = 0;
|
||||
containsNullKey = false;
|
||||
Arrays.fill(key, (0));
|
||||
Arrays.fill(value, null);
|
||||
}
|
||||
|
||||
/// Changes the owning thread of this map to the current thread.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must ensure proper happens before relationships
|
||||
/// when transferring ownership between threads.
|
||||
///
|
||||
/// This should be done through proper synchronization mechanisms like
|
||||
/// [Thread#join()] or [Future#get()].
|
||||
///
|
||||
/// @implNote This method does not perform synchronization
|
||||
public void setThread() {
|
||||
this.thread = Thread.currentThread();
|
||||
}
|
||||
|
||||
/// Checks if the current thread is the same as the owning thread.
|
||||
///
|
||||
/// @return The current thread owns this map
|
||||
public boolean isSameThread() {
|
||||
return Thread.currentThread() == this.thread;
|
||||
}
|
||||
|
||||
public boolean isSameThreadFor(ServerLevel serverLevel, int chunkX, int chunkZ) {
|
||||
return Thread.currentThread() == this.thread && TickThread.isTickThreadFor(serverLevel, chunkX, chunkZ);
|
||||
}
|
||||
|
||||
/// Ensure that the current thread is the owning thread.
|
||||
///
|
||||
/// @throws IllegalStateException If the current thread is not the owning thread
|
||||
/// @see #isSameThread()
|
||||
/// @see #setThread()
|
||||
public void ensureSameThread() {
|
||||
if (!isSameThread()) {
|
||||
throw new IllegalStateException("Thread failed main thread check: Cannot update chunk status asynchronously, context=thread=" + Thread.currentThread().getName());
|
||||
}
|
||||
}
|
||||
|
||||
// from fastutil
|
||||
private V removeEntry(final int pos) {
|
||||
final V oldValue = value[pos];
|
||||
value[pos] = null;
|
||||
size--;
|
||||
shiftKeys(pos);
|
||||
if (n > MIN_N && size < maxFill / 4 && n > it.unimi.dsi.fastutil.Hash.DEFAULT_INITIAL_SIZE) {
|
||||
rehash(n / 2);
|
||||
}
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
// from fastutil
|
||||
private void shiftKeys(int pos) {
|
||||
int last, slot;
|
||||
long curr;
|
||||
final long[] key = this.key;
|
||||
final V[] value = this.value;
|
||||
for (;;) {
|
||||
pos = ((last = pos) + 1) & mask;
|
||||
for (;;) {
|
||||
if (((curr = key[pos]) == (0))) {
|
||||
key[last] = (0);
|
||||
value[last] = null;
|
||||
return;
|
||||
}
|
||||
slot = (int) HashCommon.mix((curr)) & mask;
|
||||
if (last <= pos ? last >= slot || slot > pos : last >= slot && slot > pos) break;
|
||||
pos = (pos + 1) & mask;
|
||||
}
|
||||
key[last] = curr;
|
||||
value[last] = value[pos];
|
||||
}
|
||||
}
|
||||
|
||||
// from fastutil
|
||||
private V removeNullEntry() {
|
||||
containsNullKey = false;
|
||||
final V oldValue = value[n];
|
||||
value[n] = null;
|
||||
size--;
|
||||
if (n > MIN_N && size < maxFill / 4 && n > it.unimi.dsi.fastutil.Hash.DEFAULT_INITIAL_SIZE) rehash(n / 2);
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
// from fastutil
|
||||
private void rehash(final int newN) {
|
||||
final long[] key = this.key;
|
||||
final V[] value = this.value;
|
||||
final int mask = newN - 1;
|
||||
final long[] newKey = new long[newN + 1];
|
||||
//noinspection unchecked
|
||||
final V[] newValue = (V[])new Object[newN + 1];
|
||||
int i = n, pos;
|
||||
for (int j = realSize(); j-- != 0;) {
|
||||
//noinspection StatementWithEmptyBody
|
||||
while (((key[--i]) == (0)));
|
||||
if (!((newKey[pos = (int) HashCommon.mix((key[i])) & mask]) == (0)))
|
||||
//noinspection StatementWithEmptyBody
|
||||
while (!((newKey[pos = (pos + 1) & mask]) == (0)));
|
||||
newKey[pos] = key[i];
|
||||
newValue[pos] = value[i];
|
||||
}
|
||||
newValue[newN] = value[n];
|
||||
n = newN;
|
||||
this.mask = mask;
|
||||
maxFill = HashCommon.maxFill(n, FACTOR);
|
||||
this.key = newKey;
|
||||
this.value = newValue;
|
||||
}
|
||||
|
||||
// from fastutil
|
||||
private int realSize() {
|
||||
return containsNullKey ? size - 1 : size;
|
||||
}
|
||||
|
||||
// from fastutil
|
||||
private int find(final long k) {
|
||||
if (((k) == (0))) return containsNullKey ? n : -(n + 1);
|
||||
long curr;
|
||||
final long[] key = this.key;
|
||||
int pos;
|
||||
if (((curr = key[pos = (int) HashCommon.mix((k)) & mask]) == (0))) return -(pos + 1);
|
||||
if (((k) == (curr))) return pos;
|
||||
while (true) {
|
||||
if (((curr = key[pos = (pos + 1) & mask]) == (0))) return -(pos + 1);
|
||||
if (((k) == (curr))) return pos;
|
||||
}
|
||||
}
|
||||
|
||||
// from fastutil
|
||||
private void insert(final int pos, final long k, final V v) {
|
||||
if (pos == n) containsNullKey = true;
|
||||
key[pos] = k;
|
||||
value[pos] = v;
|
||||
if (size++ >= maxFill) rehash(HashCommon.arraySize(size + 1, FACTOR));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.dreeam.leaf.world;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
|
||||
public record EntityCollisionCache(
|
||||
ObjectArrayList<VoxelShape> potentialCollisionsVoxel,
|
||||
ObjectArrayList<AABB> potentialCollisionsBB,
|
||||
ObjectArrayList<AABB> entityAABBs
|
||||
) {
|
||||
public EntityCollisionCache() {
|
||||
this(new ObjectArrayList<>(), new ObjectArrayList<>(), new ObjectArrayList<>());
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
potentialCollisionsVoxel.clear();
|
||||
potentialCollisionsBB.clear();
|
||||
entityAABBs.clear();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user