mirror of
https://github.com/Winds-Studio/Leaf.git
synced 2025-12-26 02:19:19 +00:00
Update changes from ver/1.21.4 branch
This commit is contained in:
@@ -1,9 +0,0 @@
|
||||
package org.dreeam.leaf.async;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
public class AsyncChunkSending {
|
||||
|
||||
public static final Logger LOGGER = LogManager.getLogger(AsyncChunkSending.class.getSimpleName());
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.dreeam.leaf.async;
|
||||
|
||||
import net.minecraft.Util;
|
||||
import org.dreeam.leaf.config.modules.async.AsyncPlayerDataSave;
|
||||
|
||||
import java.util.Optional;
|
||||
@@ -17,7 +18,7 @@ public class AsyncPlayerDataSaving {
|
||||
new com.google.common.util.concurrent.ThreadFactoryBuilder()
|
||||
.setPriority(Thread.NORM_PRIORITY - 2)
|
||||
.setNameFormat("Leaf IO Thread")
|
||||
.setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER))
|
||||
.setUncaughtExceptionHandler(Util::onThreadException)
|
||||
.build(),
|
||||
new ThreadPoolExecutor.DiscardPolicy()
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.dreeam.leaf.async.ai;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.Mob;
|
||||
@@ -8,80 +9,99 @@ import org.apache.logging.log4j.Logger;
|
||||
import org.dreeam.leaf.config.modules.async.AsyncTargetFinding;
|
||||
import org.dreeam.leaf.util.queue.SpscIntQueue;
|
||||
|
||||
import java.util.OptionalInt;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
public class AsyncGoalExecutor {
|
||||
|
||||
public static final Logger LOGGER = LogManager.getLogger("Leaf Async Goal");
|
||||
|
||||
protected static final Logger LOGGER = LogManager.getLogger("Leaf Async Goal");
|
||||
protected final SpscIntQueue queue;
|
||||
protected final SpscIntQueue wake;
|
||||
protected final IntArrayList submit;
|
||||
private final AsyncGoalThread thread;
|
||||
private final ServerLevel serverLevel;
|
||||
private boolean dirty = false;
|
||||
private long tickCount = 0L;
|
||||
private static final int SPIN_LIMIT = 100;
|
||||
private final ServerLevel world;
|
||||
private long midTickCount = 0L;
|
||||
|
||||
public AsyncGoalExecutor(AsyncGoalThread thread, ServerLevel serverLevel) {
|
||||
this.serverLevel = serverLevel;
|
||||
public AsyncGoalExecutor(AsyncGoalThread thread, ServerLevel world) {
|
||||
this.world = world;
|
||||
this.queue = new SpscIntQueue(AsyncTargetFinding.queueSize);
|
||||
this.wake = new SpscIntQueue(AsyncTargetFinding.queueSize);
|
||||
this.submit = new IntArrayList();
|
||||
this.thread = thread;
|
||||
}
|
||||
|
||||
boolean wake(int id) {
|
||||
Entity entity = this.serverLevel.getEntities().get(id);
|
||||
Entity entity = this.world.getEntities().get(id);
|
||||
if (entity == null || entity.isRemoved() || !(entity instanceof Mob mob)) {
|
||||
return false;
|
||||
}
|
||||
mob.goalSelector.wake();
|
||||
mob.targetSelector.wake();
|
||||
mob.goalSelector.ctx.wake();
|
||||
mob.targetSelector.ctx.wake();
|
||||
return true;
|
||||
}
|
||||
|
||||
public final void submit(int entityId) {
|
||||
if (!this.queue.send(entityId)) {
|
||||
int spinCount = 0;
|
||||
while (!this.queue.send(entityId)) {
|
||||
spinCount++;
|
||||
// Unpark the thread after some spinning to help clear the queue
|
||||
if (spinCount > SPIN_LIMIT) {
|
||||
unpark();
|
||||
spinCount = 0;
|
||||
}
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
}
|
||||
dirty = true;
|
||||
this.submit.add(entityId);
|
||||
}
|
||||
|
||||
public final void unpark() {
|
||||
if (dirty) LockSupport.unpark(thread);
|
||||
dirty = false;
|
||||
public final void tick() {
|
||||
batchSubmit();
|
||||
LockSupport.unpark(thread);
|
||||
}
|
||||
|
||||
private void batchSubmit() {
|
||||
if (submit.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
int[] raw = submit.elements();
|
||||
int size = submit.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
int id = raw[i];
|
||||
if (poll(id) && !this.queue.send(id)) {
|
||||
do {
|
||||
wake(id);
|
||||
} while (poll(id));
|
||||
}
|
||||
}
|
||||
this.submit.clear();
|
||||
}
|
||||
|
||||
public final void midTick() {
|
||||
boolean didWork = false;
|
||||
while (true) {
|
||||
int id = this.wake.recv();
|
||||
if (id == Integer.MAX_VALUE) {
|
||||
OptionalInt result = this.wake.recv();
|
||||
if (result.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
didWork = true;
|
||||
Entity entity = this.serverLevel.getEntities().get(id);
|
||||
if (entity == null || !entity.isAlive() || !(entity instanceof Mob mob)) {
|
||||
continue;
|
||||
int id = result.getAsInt();
|
||||
if (poll(id) && !this.queue.send(id)) {
|
||||
do {
|
||||
wake(id);
|
||||
} while (poll(id));
|
||||
}
|
||||
}
|
||||
if (AsyncTargetFinding.threshold <= 0L || (midTickCount % AsyncTargetFinding.threshold) == 0L) {
|
||||
batchSubmit();
|
||||
}
|
||||
|
||||
midTickCount += 1;
|
||||
}
|
||||
|
||||
private boolean poll(int id) {
|
||||
Entity entity = this.world.getEntities().get(id);
|
||||
if (entity == null || entity.isRemoved() || !(entity instanceof Mob mob)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
mob.tickingTarget = true;
|
||||
boolean a = mob.targetSelector.poll();
|
||||
mob.tickingTarget = false;
|
||||
boolean b = mob.goalSelector.poll();
|
||||
if (a || b) {
|
||||
submit(id);
|
||||
}
|
||||
return a || b;
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Exception while polling", e);
|
||||
// retry
|
||||
return true;
|
||||
}
|
||||
if (didWork || (tickCount & 15L) == 0L) unpark();
|
||||
tickCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,11 @@ import net.minecraft.Util;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
|
||||
import java.util.OptionalInt;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
public class AsyncGoalThread extends Thread {
|
||||
|
||||
private static final int SPIN_TRIES = 1000;
|
||||
|
||||
public AsyncGoalThread(final MinecraftServer server) {
|
||||
super(() -> run(server), "Leaf Async Goal Thread");
|
||||
this.setDaemon(false);
|
||||
@@ -19,38 +18,29 @@ public class AsyncGoalThread extends Thread {
|
||||
}
|
||||
|
||||
private static void run(MinecraftServer server) {
|
||||
int emptySpins = 0;
|
||||
|
||||
while (server.isRunning()) {
|
||||
boolean didWork = false;
|
||||
boolean retry = false;
|
||||
for (ServerLevel level : server.getAllLevels()) {
|
||||
var exec = level.asyncGoalExecutor;
|
||||
boolean levelWork = false;
|
||||
while (true) {
|
||||
int id = exec.queue.recv();
|
||||
if (id == Integer.MAX_VALUE) {
|
||||
OptionalInt result = exec.queue.recv();
|
||||
if (result.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
levelWork = true;
|
||||
int id = result.getAsInt();
|
||||
retry = true;
|
||||
if (exec.wake(id)) {
|
||||
while (!exec.wake.send(id)) {
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
}
|
||||
}
|
||||
didWork |= levelWork;
|
||||
|
||||
Thread.yield();
|
||||
}
|
||||
// Adaptive parking
|
||||
if (didWork) {
|
||||
emptySpins = 0; // Reset counter when work was done
|
||||
} else {
|
||||
emptySpins++;
|
||||
if (emptySpins > SPIN_TRIES) {
|
||||
LockSupport.park(); // Only park after several empty spins
|
||||
emptySpins = 0;
|
||||
} else {
|
||||
Thread.onSpinWait(); // Yield to other threads but don't park
|
||||
}
|
||||
|
||||
if (!retry) {
|
||||
LockSupport.parkNanos(10_000L);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.dreeam.leaf.async.ai;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface VWaker {
|
||||
@Nullable Object wake();
|
||||
}
|
||||
@@ -5,14 +5,26 @@ import org.jetbrains.annotations.Nullable;
|
||||
public class Waker {
|
||||
|
||||
@Nullable
|
||||
public volatile Runnable wake = null;
|
||||
public volatile VWaker wake = null;
|
||||
@Nullable
|
||||
public volatile Object result = null;
|
||||
public volatile boolean state = true;
|
||||
public boolean state = true;
|
||||
|
||||
public final @Nullable Object result() {
|
||||
Object result = this.result;
|
||||
this.result = null;
|
||||
return result;
|
||||
}
|
||||
|
||||
final void wake() {
|
||||
final var wake = this.wake;
|
||||
if (wake != null) {
|
||||
try {
|
||||
this.result = wake.wake();
|
||||
} catch (Exception e) {
|
||||
AsyncGoalExecutor.LOGGER.error("Exception while wake", e);
|
||||
}
|
||||
this.wake = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.dreeam.leaf.async.chunk;
|
||||
|
||||
import net.minecraft.Util;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class AsyncChunkSend {
|
||||
|
||||
public static final ExecutorService POOL = new ThreadPoolExecutor(
|
||||
1, 1, 0L, TimeUnit.MILLISECONDS,
|
||||
new LinkedBlockingQueue<>(),
|
||||
new com.google.common.util.concurrent.ThreadFactoryBuilder()
|
||||
.setPriority(Thread.NORM_PRIORITY - 2)
|
||||
.setNameFormat("Leaf Async Chunk Send Thread")
|
||||
.setUncaughtExceptionHandler(Util::onThreadException)
|
||||
.setThreadFactory(AsyncChunkSendThread::new)
|
||||
.build(),
|
||||
new ThreadPoolExecutor.DiscardPolicy()
|
||||
);
|
||||
public static final Logger LOGGER = LogManager.getLogger("Leaf Async Chunk Send");
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.dreeam.leaf.async.chunk;
|
||||
|
||||
public class AsyncChunkSendThread extends Thread {
|
||||
|
||||
protected AsyncChunkSendThread(Runnable task) {
|
||||
super(task);
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,12 @@ import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.level.chunk.ChunkGenerator;
|
||||
import net.minecraft.world.level.levelgen.structure.Structure;
|
||||
|
||||
import java.util.concurrent.*;
|
||||
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;
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
package org.dreeam.leaf.async.path;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.world.level.pathfinder.Path;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -12,7 +10,13 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.RejectedExecutionHandler;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
@@ -97,4 +101,5 @@ public class AsyncPathProcessor {
|
||||
final int queueCapacity = org.dreeam.leaf.config.modules.async.AsyncPathfinding.asyncPathfindingQueueSize;
|
||||
|
||||
return new LinkedBlockingQueue<>(queueCapacity);
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.dreeam.leaf.async.path;
|
||||
|
||||
import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue;
|
||||
import net.minecraft.world.level.pathfinder.NodeEvaluator;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package org.dreeam.leaf.async.path;
|
||||
|
||||
import net.minecraft.world.level.pathfinder.*;
|
||||
import net.minecraft.world.level.pathfinder.AmphibiousNodeEvaluator;
|
||||
import net.minecraft.world.level.pathfinder.FlyNodeEvaluator;
|
||||
import net.minecraft.world.level.pathfinder.NodeEvaluator;
|
||||
import net.minecraft.world.level.pathfinder.SwimNodeEvaluator;
|
||||
|
||||
public enum NodeEvaluatorType {
|
||||
WALK,
|
||||
|
||||
@@ -2,12 +2,14 @@ package org.dreeam.leaf.async.tracker;
|
||||
|
||||
import ca.spottedleaf.moonrise.common.list.ReferenceList;
|
||||
import ca.spottedleaf.moonrise.common.misc.NearbyPlayers;
|
||||
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 com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import it.unimi.dsi.fastutil.objects.ReferenceArrayList;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.server.level.ChunkMap;
|
||||
import net.minecraft.server.level.FullChunkStatus;
|
||||
import net.minecraft.server.level.ServerEntity;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
@@ -16,30 +18,48 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.RejectedExecutionHandler;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class MultithreadedTracker {
|
||||
|
||||
private static final String THREAD_PREFIX = "Leaf Async Tracker";
|
||||
private static final Logger LOGGER = LogManager.getLogger(THREAD_PREFIX);
|
||||
private static long lastWarnMillis = System.currentTimeMillis();
|
||||
private static final ThreadPoolExecutor trackerExecutor = new ThreadPoolExecutor(
|
||||
getCorePoolSize(),
|
||||
getMaxPoolSize(),
|
||||
getKeepAliveTime(), TimeUnit.SECONDS,
|
||||
getQueueImpl(),
|
||||
getThreadFactory(),
|
||||
getRejectedPolicy()
|
||||
);
|
||||
private static ThreadPoolExecutor TRACKER_EXECUTOR = null;
|
||||
|
||||
private record SendChanges(Object[] entities, int size) implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
for (int i = 0; i < size; i++) {
|
||||
((ServerEntity) entities[i]).sendDirtyEntityData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private MultithreadedTracker() {
|
||||
}
|
||||
|
||||
public static Executor getTrackerExecutor() {
|
||||
return trackerExecutor;
|
||||
public static void init() {
|
||||
if (TRACKER_EXECUTOR == null) {
|
||||
TRACKER_EXECUTOR = new ThreadPoolExecutor(
|
||||
getCorePoolSize(),
|
||||
getMaxPoolSize(),
|
||||
getKeepAliveTime(), TimeUnit.SECONDS,
|
||||
getQueueImpl(),
|
||||
getThreadFactory(),
|
||||
getRejectedPolicy()
|
||||
);
|
||||
} else {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
public static void tick(ChunkSystemServerLevel level) {
|
||||
public static void tick(ServerLevel level) {
|
||||
try {
|
||||
if (!org.dreeam.leaf.config.modules.async.MultithreadedTracker.compatModeEnabled) {
|
||||
tickAsync(level);
|
||||
@@ -51,7 +71,7 @@ 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();
|
||||
|
||||
@@ -59,7 +79,8 @@ public class MultithreadedTracker {
|
||||
final Entity[] trackerEntitiesRaw = trackerEntities.getRawDataUnchecked();
|
||||
|
||||
// Move tracking to off-main
|
||||
trackerExecutor.execute(() -> {
|
||||
TRACKER_EXECUTOR.execute(() -> {
|
||||
ReferenceArrayList<ServerEntity> sendDirty = new ReferenceArrayList<>();
|
||||
for (final Entity entity : trackerEntitiesRaw) {
|
||||
if (entity == null) continue;
|
||||
|
||||
@@ -67,19 +88,30 @@ public class MultithreadedTracker {
|
||||
|
||||
if (tracker == null) continue;
|
||||
|
||||
tracker.moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition()));
|
||||
tracker.serverEntity.sendChanges();
|
||||
// Don't Parallel Tick Tracker of Entity
|
||||
synchronized (tracker.sync) {
|
||||
tracker.moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition()));
|
||||
tracker.serverEntity.sendChanges();
|
||||
if (tracker.serverEntity.wantSendDirtyEntityData) {
|
||||
tracker.serverEntity.wantSendDirtyEntityData = false;
|
||||
sendDirty.add(tracker.serverEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!sendDirty.isEmpty()) {
|
||||
level.getServer().execute(new SendChanges(sendDirty.elements(), sendDirty.size()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -89,30 +121,54 @@ 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.sync) {
|
||||
tickTask[index] = tracker.leafTickCompact(nearbyPlayers.getChunk(entity.chunkPosition()));
|
||||
sendChangesTasks[index] = () -> tracker.serverEntity.sendChanges(); // Collect send changes to task array
|
||||
}
|
||||
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;
|
||||
|
||||
sendChanges.run();
|
||||
}
|
||||
|
||||
ReferenceArrayList<ServerEntity> sendDirty = new ReferenceArrayList<>();
|
||||
for (final Entity entity : trackerEntitiesRaw) {
|
||||
if (entity == null) continue;
|
||||
|
||||
final ChunkMap.TrackedEntity tracker = ((EntityTrackerEntity) entity).moonrise$getTrackedEntity();
|
||||
|
||||
if (tracker == null) continue;
|
||||
if (tracker.serverEntity.wantSendDirtyEntityData) {
|
||||
tracker.serverEntity.wantSendDirtyEntityData = false;
|
||||
sendDirty.add(tracker.serverEntity);
|
||||
}
|
||||
}
|
||||
if (!sendDirty.isEmpty()) {
|
||||
level.getServer().execute(new SendChanges(sendDirty.elements(), sendDirty.size()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Original ChunkMap#newTrackerTick of Paper
|
||||
// Just for diff usage for future update
|
||||
private static void tickOriginal(ServerLevel level) {
|
||||
final ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup entityLookup = (ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup) ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel) level).moonrise$getEntityLookup();
|
||||
final ServerEntityLookup entityLookup = (ServerEntityLookup) ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel) level).moonrise$getEntityLookup();
|
||||
|
||||
final ca.spottedleaf.moonrise.common.list.ReferenceList<net.minecraft.world.entity.Entity> trackerEntities = entityLookup.trackerEntities;
|
||||
final ReferenceList<Entity> trackerEntities = entityLookup.trackerEntities;
|
||||
final Entity[] trackerEntitiesRaw = trackerEntities.getRawDataUnchecked();
|
||||
for (int i = 0, len = trackerEntities.size(); i < len; ++i) {
|
||||
final Entity entity = trackerEntitiesRaw[i];
|
||||
final ChunkMap.TrackedEntity tracker = ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity) entity).moonrise$getTrackedEntity();
|
||||
final ChunkMap.TrackedEntity tracker = ((EntityTrackerEntity) entity).moonrise$getTrackedEntity();
|
||||
if (tracker == null) {
|
||||
continue;
|
||||
}
|
||||
@@ -147,6 +203,7 @@ public class MultithreadedTracker {
|
||||
.setThreadFactory(MultithreadedTrackerThread::new)
|
||||
.setNameFormat(THREAD_PREFIX + " Thread - %d")
|
||||
.setPriority(Thread.NORM_PRIORITY - 2)
|
||||
.setUncaughtExceptionHandler(Util::onThreadException)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,15 @@ import io.papermc.paper.configuration.GlobalConfiguration;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.minecraft.Util;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.dreeam.leaf.config.modules.misc.SentryDSN;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -30,13 +36,6 @@ import java.util.concurrent.CompletableFuture;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
/*
|
||||
* Yoinked from: https://github.com/xGinko/AnarchyExploitFixes/ & https://github.com/LuminolMC/Luminol
|
||||
* @author: @xGinko & @MrHua269
|
||||
@@ -223,6 +222,11 @@ public class LeafConfig {
|
||||
"config/gale-world-defaults.yml"
|
||||
));
|
||||
|
||||
@Nullable String existing = System.getProperty("spark.serverconfigs.extra");
|
||||
if (existing != null) {
|
||||
extraConfigs.addAll(Arrays.asList(existing.split(",")));
|
||||
}
|
||||
|
||||
for (World world : Bukkit.getWorlds()) {
|
||||
extraConfigs.add(world.getWorldFolder().getName() + "/gale-world.yml"); // Gale world config
|
||||
}
|
||||
@@ -230,10 +234,13 @@ public class LeafConfig {
|
||||
return extraConfigs;
|
||||
}
|
||||
|
||||
private static String[] buildSparkHiddenPaths() {
|
||||
return new String[]{
|
||||
SentryDSN.sentryDsnConfigPath // Hide Sentry DSN key
|
||||
};
|
||||
private static List<String> buildSparkHiddenPaths() {
|
||||
@Nullable String existing = System.getProperty("spark.serverconfigs.hiddenpaths");
|
||||
|
||||
List<String> extraHidden = existing != null ? new ArrayList<>(Arrays.asList(existing.split(","))) : new ArrayList<>();
|
||||
extraHidden.add(SentryDSN.sentryDsnConfigPath); // Hide Sentry DSN key
|
||||
|
||||
return extraHidden;
|
||||
}
|
||||
|
||||
public static void regSparkExtraConfig() {
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package org.dreeam.leaf.config.annotations;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Indicates that a feature is experimental and may be removed or changed in the future.
|
||||
|
||||
@@ -10,6 +10,7 @@ public class AsyncChunkSend extends ConfigModules {
|
||||
}
|
||||
|
||||
public static boolean enabled = false;
|
||||
private static boolean asyncChunkSendInitialized;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
@@ -20,6 +21,12 @@ public class AsyncChunkSend extends ConfigModules {
|
||||
使区块数据包准备和发送异步化以提高服务器性能.
|
||||
当许多玩家同时加载区块时, 这可以显著减少主线程负载.""");
|
||||
|
||||
if (asyncChunkSendInitialized) {
|
||||
config.getConfigSection(getBasePath());
|
||||
return;
|
||||
}
|
||||
asyncChunkSendInitialized = true;
|
||||
|
||||
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,15 +21,15 @@ public class AsyncPathfinding extends ConfigModules {
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
config.addCommentRegionBased(getBasePath() + ".reject-policy", """
|
||||
The policy to use when the queue is full and a new task is submitted.
|
||||
FLUSH_ALL: All pending tasks will be run on server thread.
|
||||
CALLER_RUNS: Newly submitted task will be run on server thread.
|
||||
DISCARD: Newly submitted task will be dropped directly.""",
|
||||
The policy to use when the queue is full and a new task is submitted.
|
||||
FLUSH_ALL: All pending tasks will be run on server thread.
|
||||
CALLER_RUNS: Newly submitted task will be run on server thread.
|
||||
DISCARD: Newly submitted task will be dropped directly.""",
|
||||
"""
|
||||
当队列满时, 新提交的任务将使用以下策略处理.
|
||||
FLUSH_ALL: 所有等待中的任务都将在主线程上运行.
|
||||
CALLER_RUNS: 新提交的任务将在主线程上运行.
|
||||
DISCARD: 新提交的任务会被直接丢弃."""
|
||||
当队列满时, 新提交的任务将使用以下策略处理.
|
||||
FLUSH_ALL: 所有等待中的任务都将在主线程上运行.
|
||||
CALLER_RUNS: 新提交的任务将在主线程上运行.
|
||||
DISCARD: 新提交的任务会被直接丢弃."""
|
||||
);
|
||||
if (asyncPathfindingInitialized) {
|
||||
config.getConfigSection(getBasePath());
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.dreeam.leaf.config.modules.async;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
import org.dreeam.leaf.config.annotations.Experimental;
|
||||
|
||||
public class AsyncPlayerDataSave extends ConfigModules {
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.dreeam.leaf.config.modules.async;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
import org.dreeam.leaf.config.annotations.Experimental;
|
||||
|
||||
public class AsyncTargetFinding extends ConfigModules {
|
||||
|
||||
@@ -16,6 +15,7 @@ public class AsyncTargetFinding extends ConfigModules {
|
||||
public static boolean searchBlock = true;
|
||||
public static boolean searchEntity = true;
|
||||
public static int queueSize = 4096;
|
||||
public static long threshold = 10L;
|
||||
private static boolean asyncTargetFindingInitialized;
|
||||
|
||||
@Override
|
||||
@@ -36,11 +36,15 @@ public class AsyncTargetFinding extends ConfigModules {
|
||||
alertOther = config.getBoolean(getBasePath() + ".async-alert-other", true);
|
||||
searchBlock = config.getBoolean(getBasePath() + ".async-search-block", true);
|
||||
searchEntity = config.getBoolean(getBasePath() + ".async-search-entity", true);
|
||||
queueSize = config.getInt(getBasePath() + ".queue-size", 4096);
|
||||
queueSize = config.getInt(getBasePath() + ".queue-size", 0);
|
||||
threshold = config.getLong(getBasePath() + ".threshold", 0);
|
||||
|
||||
if (queueSize <= 0) {
|
||||
queueSize = 4096;
|
||||
}
|
||||
if (threshold == 0L) {
|
||||
threshold = 10L;
|
||||
}
|
||||
if (!enabled) {
|
||||
alertOther = false;
|
||||
searchEntity = false;
|
||||
|
||||
@@ -57,5 +57,8 @@ public class MultithreadedTracker extends ConfigModules {
|
||||
|
||||
if (asyncEntityTrackerQueueSize <= 0)
|
||||
asyncEntityTrackerQueueSize = asyncEntityTrackerMaxThreads * 384;
|
||||
if (enabled) {
|
||||
org.dreeam.leaf.async.tracker.MultithreadedTracker.init();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ public class ConfigurableInventoryOverflowEvent extends ConfigModules {
|
||||
}
|
||||
|
||||
public static boolean enabled = false;
|
||||
public static String listenerClass = "com.example.package.PlayerInventoryOverflowEvent" ;
|
||||
public static String listenerClass = "com.example.package.PlayerInventoryOverflowEvent";
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.dreeam.leaf.config.modules.gameplay;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class DeathItemDropKnockback extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.GAMEPLAY.getBaseKeyName() + ".death-item-drop-knockback";
|
||||
}
|
||||
|
||||
public static boolean dropAround = true;
|
||||
public static double horizontalForce = 0.5;
|
||||
public static double verticalForce = 0.2;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
dropAround = config.getBoolean(getBasePath() + ".drop-around", dropAround,
|
||||
config.pickStringRegionBased(
|
||||
"If true, items will drop randomly around the player on death.",
|
||||
"如果为 “true”,物品会在玩家死亡时随机掉落在其周围."
|
||||
));
|
||||
|
||||
horizontalForce = config.getDouble(getBasePath() + ".horizontal-force", horizontalForce,
|
||||
config.pickStringRegionBased(
|
||||
"Base speed for horizontal velocity when randomly dropping items.",
|
||||
"随机掉落物品时水平速度的基本速度."
|
||||
));
|
||||
|
||||
verticalForce = config.getDouble(getBasePath() + ".vertical-force", verticalForce,
|
||||
config.pickStringRegionBased(
|
||||
"Upward motion for randomly dropped items.",
|
||||
"随机掉落物品的向上运动."
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package org.dreeam.leaf.config.modules.gameplay;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
import org.dreeam.leaf.config.annotations.Experimental;
|
||||
|
||||
public class Knockback extends ConfigModules {
|
||||
|
||||
@@ -12,6 +13,8 @@ public class Knockback extends ConfigModules {
|
||||
public static boolean snowballCanKnockback = false;
|
||||
public static boolean eggCanKnockback = false;
|
||||
public static boolean canPlayerKnockbackZombie = true;
|
||||
@Experimental
|
||||
public static boolean flushKnockback = false;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
@@ -30,5 +33,6 @@ public class Knockback extends ConfigModules {
|
||||
"Make players can knockback zombie.",
|
||||
"使玩家可以击退僵尸."
|
||||
));
|
||||
flushKnockback = config.getBoolean(getBasePath() + ".flush-location-while-knockback-player", flushKnockback);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ public class SpawnerSettings extends ConfigModules {
|
||||
public static boolean checkForNearbyPlayers = true;
|
||||
public static boolean spawnerBlockChecks = false;
|
||||
public static boolean waterPreventSpawnCheck = false;
|
||||
public static boolean ignoreSpawnRules = false;
|
||||
|
||||
public static int minSpawnDelay = 200;
|
||||
public static int maxSpawnDelay = 800;
|
||||
@@ -60,8 +61,8 @@ public class SpawnerSettings extends ConfigModules {
|
||||
|
||||
spawnerBlockChecks = config.getBoolean(getBasePath() + ".checks.spawner-block-checks", spawnerBlockChecks,
|
||||
config.pickStringRegionBased(
|
||||
"Check if there are blocks blocking the spawner to spawn the mob",
|
||||
"检查是否有方块阻挡刷怪笼生成怪物"
|
||||
"Check if there are physical blocks obstructing the spawn location, or if custom spawn rules (isValidPosition) fail due to block conditions.",
|
||||
"检查是否有物理方块阻挡生成位置, 或自定义生成规则(isValidPosition)因方块条件失败."
|
||||
));
|
||||
|
||||
waterPreventSpawnCheck = config.getBoolean(getBasePath() + ".checks.water-prevent-spawn-check", waterPreventSpawnCheck,
|
||||
@@ -69,6 +70,11 @@ public class SpawnerSettings extends ConfigModules {
|
||||
"Checks if there is water around that prevents spawning",
|
||||
"检查周围是否有水阻止生成"
|
||||
));
|
||||
ignoreSpawnRules = config.getBoolean(getBasePath() + ".checks.ignore-spawn-rules", ignoreSpawnRules,
|
||||
config.pickStringRegionBased(
|
||||
"Ignore mob-specific spawn rules, like animals needing grass or specific biomes/blocks (does not affect light level or physical obstruction checks).",
|
||||
"忽略特定于生物的生成规则, 例如动物需要草方块或特定的生物群系/方块 (不影响光照等级或物理障碍物检查)."
|
||||
));
|
||||
|
||||
// Delay settings
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ public class Cache extends ConfigModules {
|
||||
return EnumConfigCategory.MISC.getBaseKeyName() + ".cache";
|
||||
}
|
||||
|
||||
public static boolean cachePlayerProfileResult = true;
|
||||
public static boolean cachePlayerProfileResult = false;
|
||||
public static int cachePlayerProfileResultTimeout = 1440;
|
||||
|
||||
@Override
|
||||
|
||||
@@ -9,15 +9,19 @@ public class UnknownCommandMessage extends ConfigModules {
|
||||
return EnumConfigCategory.MISC.getBaseKeyName() + ".message";
|
||||
}
|
||||
|
||||
public static String unknownCommandMessage = "<red><lang:command.unknown.command><newline><detail>";
|
||||
public static String unknownCommandMessage = "default";
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
unknownCommandMessage = config.getString(getBasePath() + ".unknown-command", unknownCommandMessage, config.pickStringRegionBased("""
|
||||
Unknown command message, using MiniMessage format, set to "default" to use vanilla message,
|
||||
placeholder: <detail>, shows detail of the unknown command information.""",
|
||||
placeholder:
|
||||
<message>, show message of the command exception.
|
||||
<detail>, shows detail of the command exception.""",
|
||||
"""
|
||||
发送未知命令时的消息, 使用 MiniMessage 格式, 设置为 "default" 使用原版消息.
|
||||
变量: <detail>, 显示未知命令详细信息."""));
|
||||
变量:
|
||||
<message>, 显示命令错误所附提示消息.
|
||||
<detail>, 显示命令错误详细信息."""));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.dreeam.leaf.config.modules.network;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class AlternativeJoin extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.NETWORK.getBaseKeyName();
|
||||
}
|
||||
|
||||
public static boolean enabled = false;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
enabled = config.getBoolean(getBasePath() + ".async-switch-state", enabled, config.pickStringRegionBased(
|
||||
"Async switch connection state.",
|
||||
"异步切换连接状态."));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.dreeam.leaf.config.modules.opt;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class CheckSurvivalBeforeGrowth extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.PERF.getBaseKeyName() + ".check-survival-before-growth";
|
||||
}
|
||||
|
||||
public static boolean cactusCheckSurvivalBeforeGrowth = false;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
cactusCheckSurvivalBeforeGrowth = config.getBoolean(getBasePath() + ".cactus-check-survival", cactusCheckSurvivalBeforeGrowth,
|
||||
config.pickStringRegionBased("""
|
||||
Check if a cactus can survive before growing.""",
|
||||
"""
|
||||
在仙人掌生长前检查其是否能够存活。"""));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.dreeam.leaf.config.modules.opt;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class EyeFluidCache extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.PERF.getBaseKeyName();
|
||||
}
|
||||
|
||||
public static boolean enabled = false;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
enabled = config.getBoolean(getBasePath() + ".cache-eye-fluid-status", enabled,
|
||||
config.pickStringRegionBased(
|
||||
"Whether to cache the isEyeInFluid method to improve performance and reduce memory usage.",
|
||||
"是否为 isEyeInFluid 方法启用缓存,以优化性能并减少内存使用."));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.dreeam.leaf.config.modules.opt;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class OptimiseBlockEntities extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.PERF.getBaseKeyName();
|
||||
}
|
||||
|
||||
public static boolean enabled = true;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
enabled = config.getBoolean(getBasePath() + ".optimise-block-entities", enabled);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.dreeam.leaf.config.modules.opt;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class OptimizeItemTicking extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.PERF.getBaseKeyName();
|
||||
}
|
||||
|
||||
public static boolean onlyTickItemsInHand = false;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
onlyTickItemsInHand = config.getBoolean(getBasePath() + ".only-tick-items-in-hand", onlyTickItemsInHand, config.pickStringRegionBased("""
|
||||
Whether to only tick / update items in main hand and offhand instead of the entire inventory.""",
|
||||
"""
|
||||
是否只对主手和副手中的物品进行 tick / 更新,而不是整个物品栏中的所有物品。"""));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.dreeam.leaf.config.modules.opt;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class OptimizePlayerMovementProcessing extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.PERF.getBaseKeyName();
|
||||
}
|
||||
|
||||
public static boolean enabled = true;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
enabled = config.getBoolean(getBasePath() + ".optimize-player-movement", enabled, config.pickStringRegionBased("""
|
||||
Whether to optimize player movement processing by skipping unnecessary edge checks and avoiding redundant view distance updates.""",
|
||||
"""
|
||||
是否优化玩家移动处理,跳过不必要的边缘检查并避免冗余的视距更新。"""));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.dreeam.leaf.config.modules.opt;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class ReduceChunkSourceUpdates extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.PERF.getBaseKeyName() + ".reduce-chunk-source-updates";
|
||||
}
|
||||
|
||||
public static boolean enabled = false;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
enabled = config.getBoolean(getBasePath() + ".enabled", enabled,
|
||||
config.pickStringRegionBased(
|
||||
"Reduces chunk source updates on inter-chunk player moves. (Recommended to enable)",
|
||||
"减少玩家跨区块移动时的区块源更新。"
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package org.dreeam.leaf.protocol;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.codec.ByteBufCodecs;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class DoABarrelRollPackets {
|
||||
|
||||
private static <T extends LeafCustomPayload> LeafCustomPayload.@NotNull Type<T> createType(String path) {
|
||||
return new LeafCustomPayload.Type<>(ResourceLocation.fromNamespaceAndPath(DoABarrelRollProtocol.NAMESPACE, path));
|
||||
}
|
||||
|
||||
public record ConfigResponseC2SPacket(int protocolVersion, boolean success) implements LeafCustomPayload {
|
||||
public static final Type<ConfigResponseC2SPacket> TYPE = createType("config_response");
|
||||
public static final StreamCodec<FriendlyByteBuf, ConfigResponseC2SPacket> STREAM_CODEC = StreamCodec.composite(
|
||||
ByteBufCodecs.INT, ConfigResponseC2SPacket::protocolVersion,
|
||||
ByteBufCodecs.BOOL, ConfigResponseC2SPacket::success,
|
||||
ConfigResponseC2SPacket::new
|
||||
);
|
||||
|
||||
@Override
|
||||
public @NotNull Type<ConfigResponseC2SPacket> type() {
|
||||
return TYPE;
|
||||
}
|
||||
}
|
||||
|
||||
public record ConfigSyncS2CPacket(int protocolVersion,
|
||||
LimitedModConfigServer applicableConfig,
|
||||
boolean isLimited,
|
||||
ModConfigServer fullConfig
|
||||
) implements LeafCustomPayload {
|
||||
public static final Type<ConfigSyncS2CPacket> TYPE = createType("config_sync");
|
||||
public static final StreamCodec<FriendlyByteBuf, ConfigSyncS2CPacket> STREAM_CODEC = StreamCodec.composite(
|
||||
ByteBufCodecs.INT, ConfigSyncS2CPacket::protocolVersion,
|
||||
LimitedModConfigServer.getCodec(), ConfigSyncS2CPacket::applicableConfig,
|
||||
ByteBufCodecs.BOOL, ConfigSyncS2CPacket::isLimited,
|
||||
ModConfigServer.PACKET_CODEC, ConfigSyncS2CPacket::fullConfig,
|
||||
ConfigSyncS2CPacket::new
|
||||
);
|
||||
|
||||
@Override
|
||||
public @NotNull Type<ConfigSyncS2CPacket> type() {
|
||||
return TYPE;
|
||||
}
|
||||
}
|
||||
|
||||
public record ConfigUpdateAckS2CPacket(int protocolVersion, boolean success) implements LeafCustomPayload {
|
||||
public static final Type<ConfigUpdateAckS2CPacket> TYPE = createType("config_update_ack");
|
||||
public static final StreamCodec<FriendlyByteBuf, ConfigUpdateAckS2CPacket> STREAM_CODEC = StreamCodec.composite(
|
||||
ByteBufCodecs.INT, ConfigUpdateAckS2CPacket::protocolVersion,
|
||||
ByteBufCodecs.BOOL, ConfigUpdateAckS2CPacket::success,
|
||||
ConfigUpdateAckS2CPacket::new
|
||||
);
|
||||
|
||||
@Override
|
||||
public @NotNull Type<ConfigUpdateAckS2CPacket> type() {
|
||||
return TYPE;
|
||||
}
|
||||
}
|
||||
|
||||
public record ConfigUpdateC2SPacket(int protocolVersion, ModConfigServer config) implements LeafCustomPayload {
|
||||
public static final Type<ConfigUpdateC2SPacket> TYPE = createType("config_update");
|
||||
public static final StreamCodec<FriendlyByteBuf, ConfigUpdateC2SPacket> STREAM_CODEC = StreamCodec.composite(
|
||||
ByteBufCodecs.INT, ConfigUpdateC2SPacket::protocolVersion,
|
||||
ModConfigServer.PACKET_CODEC, ConfigUpdateC2SPacket::config,
|
||||
ConfigUpdateC2SPacket::new
|
||||
);
|
||||
|
||||
@Override
|
||||
public @NotNull Type<ConfigUpdateC2SPacket> type() {
|
||||
return TYPE;
|
||||
}
|
||||
}
|
||||
|
||||
public record RollSyncC2SPacket(boolean rolling, float roll) implements LeafCustomPayload {
|
||||
public static final Type<RollSyncC2SPacket> TYPE = createType("roll_sync");
|
||||
public static final StreamCodec<FriendlyByteBuf, RollSyncC2SPacket> STREAM_CODEC = StreamCodec.composite(
|
||||
ByteBufCodecs.BOOL, RollSyncC2SPacket::rolling,
|
||||
ByteBufCodecs.FLOAT, RollSyncC2SPacket::roll,
|
||||
RollSyncC2SPacket::new
|
||||
);
|
||||
|
||||
@Override
|
||||
public @NotNull Type<RollSyncC2SPacket> type() {
|
||||
return TYPE;
|
||||
}
|
||||
}
|
||||
|
||||
public record RollSyncS2CPacket(int entityId, boolean rolling, float roll) implements LeafCustomPayload {
|
||||
public static final Type<RollSyncS2CPacket> TYPE = createType("roll_sync");
|
||||
public static final StreamCodec<FriendlyByteBuf, RollSyncS2CPacket> STREAM_CODEC = StreamCodec.composite(
|
||||
ByteBufCodecs.INT, RollSyncS2CPacket::entityId,
|
||||
ByteBufCodecs.BOOL, RollSyncS2CPacket::rolling,
|
||||
ByteBufCodecs.FLOAT, RollSyncS2CPacket::roll,
|
||||
RollSyncS2CPacket::new
|
||||
);
|
||||
|
||||
@Override
|
||||
public @NotNull Type<RollSyncS2CPacket> type() {
|
||||
return TYPE;
|
||||
}
|
||||
}
|
||||
|
||||
public interface LimitedModConfigServer {
|
||||
boolean allowThrusting();
|
||||
|
||||
boolean forceEnabled();
|
||||
|
||||
static StreamCodec<ByteBuf, LimitedModConfigServer> getCodec() {
|
||||
return StreamCodec.composite(
|
||||
ByteBufCodecs.BOOL, LimitedModConfigServer::allowThrusting,
|
||||
ByteBufCodecs.BOOL, LimitedModConfigServer::forceEnabled,
|
||||
Impl::new
|
||||
);
|
||||
}
|
||||
|
||||
record Impl(boolean allowThrusting, boolean forceEnabled) implements LimitedModConfigServer {
|
||||
}
|
||||
}
|
||||
|
||||
public record ModConfigServer(boolean allowThrusting,
|
||||
boolean forceEnabled,
|
||||
boolean forceInstalled,
|
||||
int installedTimeout,
|
||||
KineticDamage kineticDamage
|
||||
) implements LimitedModConfigServer {
|
||||
public static final StreamCodec<ByteBuf, ModConfigServer> PACKET_CODEC = StreamCodec.composite(
|
||||
ByteBufCodecs.BOOL, ModConfigServer::allowThrusting,
|
||||
ByteBufCodecs.BOOL, ModConfigServer::forceEnabled,
|
||||
ByteBufCodecs.BOOL, ModConfigServer::forceInstalled,
|
||||
ByteBufCodecs.INT, ModConfigServer::installedTimeout,
|
||||
KineticDamage.CODEC, ModConfigServer::kineticDamage,
|
||||
ModConfigServer::new
|
||||
);
|
||||
}
|
||||
|
||||
public enum KineticDamage {
|
||||
VANILLA,
|
||||
HIGH_SPEED,
|
||||
NONE,
|
||||
INSTANT_KILL;
|
||||
|
||||
public static final StreamCodec<ByteBuf, KineticDamage> CODEC =
|
||||
ByteBufCodecs.STRING_UTF8.map(KineticDamage::valueOf, KineticDamage::name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
package org.dreeam.leaf.protocol;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2BooleanMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2BooleanMaps;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2BooleanOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2FloatMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2FloatMaps;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2FloatOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.server.network.ServerGamePacketListenerImpl;
|
||||
import net.minecraft.server.network.ServerPlayerConnection;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.dreeam.leaf.protocol.DoABarrelRollPackets.ConfigResponseC2SPacket;
|
||||
import org.dreeam.leaf.protocol.DoABarrelRollPackets.ConfigSyncS2CPacket;
|
||||
import org.dreeam.leaf.protocol.DoABarrelRollPackets.ConfigUpdateAckS2CPacket;
|
||||
import org.dreeam.leaf.protocol.DoABarrelRollPackets.ConfigUpdateC2SPacket;
|
||||
import org.dreeam.leaf.protocol.DoABarrelRollPackets.KineticDamage;
|
||||
import org.dreeam.leaf.protocol.DoABarrelRollPackets.ModConfigServer;
|
||||
import org.dreeam.leaf.protocol.DoABarrelRollPackets.RollSyncC2SPacket;
|
||||
import org.dreeam.leaf.protocol.DoABarrelRollPackets.RollSyncS2CPacket;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.bukkit.event.player.PlayerKickEvent;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.OptionalInt;
|
||||
|
||||
public class DoABarrelRollProtocol implements Protocol {
|
||||
|
||||
protected static final String NAMESPACE = "do_a_barrel_roll";
|
||||
private static final Logger LOGGER = LogManager.getLogger(NAMESPACE);
|
||||
private static final int PROTOCOL_VERSION = 4;
|
||||
private static final ModConfigServer DEFAULT = new ModConfigServer(false, false, false, 40, KineticDamage.VANILLA);
|
||||
private static final Component SYNC_TIMEOUT_MESSAGE = Component.literal("Please install Do a Barrel Roll 2.4.0 or later to play on this server.");
|
||||
private static DoABarrelRollProtocol INSTANCE = null;
|
||||
|
||||
private final List<Protocols.TypeAndCodec<FriendlyByteBuf, ? extends LeafCustomPayload>> c2s = ImmutableList.of(
|
||||
new Protocols.TypeAndCodec<>(ConfigUpdateC2SPacket.TYPE, ConfigUpdateC2SPacket.STREAM_CODEC),
|
||||
new Protocols.TypeAndCodec<>(ConfigResponseC2SPacket.TYPE, ConfigResponseC2SPacket.STREAM_CODEC),
|
||||
new Protocols.TypeAndCodec<>(RollSyncC2SPacket.TYPE, RollSyncC2SPacket.STREAM_CODEC));
|
||||
|
||||
private final List<Protocols.TypeAndCodec<FriendlyByteBuf, ? extends LeafCustomPayload>> s2c = ImmutableList.of(
|
||||
new Protocols.TypeAndCodec<>(ConfigUpdateAckS2CPacket.TYPE, ConfigUpdateAckS2CPacket.STREAM_CODEC),
|
||||
new Protocols.TypeAndCodec<>(ConfigSyncS2CPacket.TYPE, ConfigSyncS2CPacket.STREAM_CODEC),
|
||||
new Protocols.TypeAndCodec<>(RollSyncS2CPacket.TYPE, RollSyncS2CPacket.STREAM_CODEC)
|
||||
);
|
||||
|
||||
private ModConfigServer config = DEFAULT;
|
||||
private boolean configUpdated = false;
|
||||
|
||||
private final Reference2ReferenceMap<ServerGamePacketListenerImpl, ClientInfo> syncStates = new Reference2ReferenceOpenHashMap<>();
|
||||
private final Reference2ReferenceMap<ServerGamePacketListenerImpl, DelayedRunnable> scheduledKicks = new Reference2ReferenceOpenHashMap<>();
|
||||
public final Reference2BooleanMap<ServerGamePacketListenerImpl> isRollingMap = Reference2BooleanMaps.synchronize(new Reference2BooleanOpenHashMap<>());
|
||||
public final Reference2FloatMap<ServerGamePacketListenerImpl> rollMap = Reference2FloatMaps.synchronize(new Reference2FloatOpenHashMap<>());
|
||||
public final Reference2BooleanMap<ServerGamePacketListenerImpl> lastIsRollingMap = Reference2BooleanMaps.synchronize(new Reference2BooleanOpenHashMap<>());
|
||||
public final Reference2FloatMap<ServerGamePacketListenerImpl> lastRollMap = Reference2FloatMaps.synchronize(new Reference2FloatOpenHashMap<>());
|
||||
|
||||
public static void deinit() {
|
||||
if (INSTANCE != null) {
|
||||
INSTANCE = null;
|
||||
Protocols.unregister(INSTANCE);
|
||||
}
|
||||
}
|
||||
|
||||
public static void init(
|
||||
boolean allowThrusting,
|
||||
boolean forceEnabled,
|
||||
boolean forceInstalled,
|
||||
int installedTimeout,
|
||||
KineticDamage kineticDamage
|
||||
) {
|
||||
if (INSTANCE == null) {
|
||||
INSTANCE = new DoABarrelRollProtocol();
|
||||
Protocols.register(INSTANCE);
|
||||
}
|
||||
INSTANCE.config = new ModConfigServer(allowThrusting, forceEnabled, forceInstalled, installedTimeout, kineticDamage);
|
||||
INSTANCE.configUpdated = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String namespace() {
|
||||
return NAMESPACE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Protocols.TypeAndCodec<FriendlyByteBuf, ? extends LeafCustomPayload>> c2s() {
|
||||
return c2s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Protocols.TypeAndCodec<FriendlyByteBuf, ? extends LeafCustomPayload>> s2c() {
|
||||
return s2c;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(ServerPlayer player, @NotNull LeafCustomPayload payload) {
|
||||
switch (payload) {
|
||||
case ConfigUpdateC2SPacket ignored ->
|
||||
player.connection.send(Protocols.createPacket(new ConfigUpdateAckS2CPacket(PROTOCOL_VERSION, false)));
|
||||
case ConfigResponseC2SPacket configResponseC2SPacket -> {
|
||||
var reply = clientReplied(player.connection, configResponseC2SPacket);
|
||||
if (reply == HandshakeState.RESEND) {
|
||||
sendHandshake(player);
|
||||
}
|
||||
}
|
||||
case RollSyncC2SPacket rollSyncC2SPacket -> {
|
||||
var state = getHandshakeState(player.connection);
|
||||
if (state.state != HandshakeState.ACCEPTED) {
|
||||
return;
|
||||
}
|
||||
var rolling = rollSyncC2SPacket.rolling();
|
||||
var roll = rollSyncC2SPacket.roll();
|
||||
isRollingMap.put(player.connection, rolling);
|
||||
if (Float.isInfinite(roll)) {
|
||||
roll = 0.0F;
|
||||
}
|
||||
rollMap.put(player.connection, roll);
|
||||
}
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnected(ServerPlayer player) {
|
||||
final var handler = player.connection;
|
||||
syncStates.remove(handler);
|
||||
isRollingMap.removeBoolean(handler);
|
||||
rollMap.removeFloat(handler);
|
||||
lastIsRollingMap.removeBoolean(handler);
|
||||
lastRollMap.removeFloat(handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tickTracker(ServerPlayer player) {
|
||||
if (!isRollingMap.containsKey(player.connection)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var isRolling = isRollingMap.getBoolean(player.connection);
|
||||
var roll = rollMap.getFloat(player.connection);
|
||||
var lastIsRolling = lastIsRollingMap.getBoolean(player.connection);
|
||||
var lastRoll = lastRollMap.getFloat(player.connection);
|
||||
if (isRolling == lastIsRolling && roll == lastRoll) {
|
||||
return;
|
||||
}
|
||||
var payload = new RollSyncS2CPacket(player.getId(), isRolling, roll);
|
||||
var packet = Protocols.createPacket(payload);
|
||||
for (ServerPlayerConnection seenBy : player.moonrise$getTrackedEntity().seenBy()) {
|
||||
if (seenBy instanceof ServerGamePacketListenerImpl conn
|
||||
&& getHandshakeState(conn).state == HandshakeState.ACCEPTED) {
|
||||
seenBy.send(packet);
|
||||
}
|
||||
}
|
||||
lastIsRollingMap.put(player.connection, isRolling);
|
||||
lastRollMap.put(player.connection, roll);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tickPlayer(ServerPlayer player) {
|
||||
if (getHandshakeState(player.connection).state == HandshakeState.NOT_SENT) {
|
||||
sendHandshake(player);
|
||||
}
|
||||
if (!isRollingMap.containsKey(player.connection)) {
|
||||
return;
|
||||
}
|
||||
if (!isRollingMap.getBoolean(player.connection)) {
|
||||
rollMap.put(player.connection, 0.0F);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tickServer(MinecraftServer server) {
|
||||
var it = scheduledKicks.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
var entry = it.next();
|
||||
if (entry.getValue().isDone()) {
|
||||
it.remove();
|
||||
} else {
|
||||
entry.getValue().tick();
|
||||
}
|
||||
}
|
||||
|
||||
if (configUpdated) {
|
||||
configUpdated = false;
|
||||
for (ServerPlayer player : server.getPlayerList().players) {
|
||||
sendHandshake(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private OptionalInt getSyncTimeout(ModConfigServer config) {
|
||||
return config.forceInstalled() ? OptionalInt.of(config.installedTimeout()) : OptionalInt.empty();
|
||||
}
|
||||
|
||||
private void sendHandshake(ServerPlayer player) {
|
||||
player.connection.send(Protocols.createPacket(initiateConfigSync(player.connection)));
|
||||
configSentToClient(player.connection);
|
||||
}
|
||||
|
||||
private void configSentToClient(ServerGamePacketListenerImpl handler) {
|
||||
getHandshakeState(handler).state = HandshakeState.SENT;
|
||||
|
||||
OptionalInt timeout = getSyncTimeout(config);
|
||||
if (timeout.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
scheduledKicks.put(handler, new DelayedRunnable(timeout.getAsInt(), () -> {
|
||||
if (getHandshakeState(handler).state != HandshakeState.ACCEPTED) {
|
||||
LOGGER.warn(
|
||||
"{} did not accept config syncing, config indicates we kick them.",
|
||||
handler.getPlayer().getName().getString()
|
||||
);
|
||||
handler.disconnect(SYNC_TIMEOUT_MESSAGE, PlayerKickEvent.Cause.PLUGIN);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private HandshakeState clientReplied(ServerGamePacketListenerImpl handler, ConfigResponseC2SPacket packet) {
|
||||
var info = getHandshakeState(handler);
|
||||
var player = handler.getPlayer();
|
||||
|
||||
if (info.state == HandshakeState.SENT) {
|
||||
var protocolVersion = packet.protocolVersion();
|
||||
if (protocolVersion < 1 || protocolVersion > PROTOCOL_VERSION) {
|
||||
LOGGER.warn(
|
||||
"{} sent unknown protocol version, expected range 1-{}, got {}. Will attempt to proceed anyway.",
|
||||
player.getName().getString(),
|
||||
PROTOCOL_VERSION,
|
||||
protocolVersion
|
||||
);
|
||||
}
|
||||
|
||||
if (protocolVersion == 2 && info.protocolVersion != 2) {
|
||||
LOGGER.info("{} is using an older protocol version, resending.", player.getName().getString());
|
||||
info.state = HandshakeState.RESEND;
|
||||
} else if (packet.success()) {
|
||||
LOGGER.info("{} accepted server config.", player.getName().getString());
|
||||
info.state = HandshakeState.ACCEPTED;
|
||||
} else {
|
||||
LOGGER.warn(
|
||||
"{} failed to process server config, check client logs find what went wrong.",
|
||||
player.getName().getString());
|
||||
info.state = HandshakeState.FAILED;
|
||||
}
|
||||
info.protocolVersion = protocolVersion;
|
||||
}
|
||||
|
||||
return info.state;
|
||||
}
|
||||
|
||||
private boolean isLimited(ServerGamePacketListenerImpl net) {
|
||||
return true;
|
||||
// return net.getPlayer().getBukkitEntity().hasPermission(DoABarrelRoll.MODID + ".configure");
|
||||
}
|
||||
|
||||
private ClientInfo getHandshakeState(ServerGamePacketListenerImpl handler) {
|
||||
return syncStates.computeIfAbsent(handler, key -> new ClientInfo(HandshakeState.NOT_SENT, PROTOCOL_VERSION, true));
|
||||
}
|
||||
|
||||
private ConfigSyncS2CPacket initiateConfigSync(ServerGamePacketListenerImpl handler) {
|
||||
var isLimited = isLimited(handler);
|
||||
getHandshakeState(handler).isLimited = isLimited;
|
||||
return new ConfigSyncS2CPacket(PROTOCOL_VERSION, config, isLimited, isLimited ? DEFAULT : config);
|
||||
}
|
||||
|
||||
private static class ClientInfo {
|
||||
private HandshakeState state;
|
||||
private int protocolVersion;
|
||||
private boolean isLimited;
|
||||
|
||||
private ClientInfo(HandshakeState state, int protocolVersion, boolean isLimited) {
|
||||
this.state = state;
|
||||
this.protocolVersion = protocolVersion;
|
||||
this.isLimited = isLimited;
|
||||
}
|
||||
}
|
||||
|
||||
private static class DelayedRunnable {
|
||||
private final Runnable runnable;
|
||||
private final int delay;
|
||||
private int ticks = 0;
|
||||
|
||||
private DelayedRunnable(int delay, Runnable runnable) {
|
||||
this.runnable = runnable;
|
||||
this.delay = delay;
|
||||
}
|
||||
|
||||
private void tick() {
|
||||
if (++ticks >= delay) {
|
||||
runnable.run();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isDone() {
|
||||
return ticks >= delay;
|
||||
}
|
||||
}
|
||||
|
||||
private enum HandshakeState {
|
||||
NOT_SENT,
|
||||
SENT,
|
||||
ACCEPTED,
|
||||
FAILED,
|
||||
RESEND
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.dreeam.leaf.protocol;
|
||||
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface LeafCustomPayload extends CustomPacketPayload {
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
Type<? extends LeafCustomPayload> type();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.dreeam.leaf.protocol;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
interface Protocol {
|
||||
|
||||
String namespace();
|
||||
|
||||
List<Protocols.TypeAndCodec<FriendlyByteBuf, ? extends LeafCustomPayload>> c2s();
|
||||
|
||||
List<Protocols.TypeAndCodec<FriendlyByteBuf, ? extends LeafCustomPayload>> s2c();
|
||||
|
||||
void tickServer(MinecraftServer server);
|
||||
|
||||
void tickPlayer(ServerPlayer player);
|
||||
|
||||
void tickTracker(ServerPlayer player);
|
||||
|
||||
void disconnected(ServerPlayer conn);
|
||||
|
||||
void handle(ServerPlayer player, @NotNull LeafCustomPayload payload);
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package org.dreeam.leaf.protocol;
|
||||
|
||||
import io.netty.buffer.Unpooled;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket;
|
||||
import net.minecraft.network.protocol.common.custom.DiscardedPayload;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class Protocols {
|
||||
|
||||
private static final ObjectArrayList<Protocol> PROTOCOLS = new ObjectArrayList<>();
|
||||
|
||||
static void register(Protocol protocol) {
|
||||
PROTOCOLS.add(protocol);
|
||||
}
|
||||
|
||||
static void unregister(Protocol protocol) {
|
||||
PROTOCOLS.remove(protocol);
|
||||
}
|
||||
|
||||
public record TypeAndCodec<B extends FriendlyByteBuf, T extends LeafCustomPayload>(LeafCustomPayload.Type<T> type,
|
||||
StreamCodec<B, T> codec) {
|
||||
}
|
||||
|
||||
public static <B extends FriendlyByteBuf> void write(B byteBuf, LeafCustomPayload payload) {
|
||||
for (Protocol protocol : PROTOCOLS) {
|
||||
if (protocol.namespace().equals(payload.type().id().getNamespace())) {
|
||||
encode(byteBuf, payload, protocol);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void handle(ServerPlayer player, @NotNull DiscardedPayload payload) {
|
||||
for (Protocol protocol : PROTOCOLS) {
|
||||
if (payload.type().id().getNamespace().equals(protocol.namespace())) {
|
||||
var leafCustomPayload = decode(protocol, payload);
|
||||
if (leafCustomPayload != null) {
|
||||
protocol.handle(player, leafCustomPayload);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void tickServer(MinecraftServer server) {
|
||||
for (Protocol protocol : PROTOCOLS) {
|
||||
protocol.tickServer(server);
|
||||
}
|
||||
}
|
||||
|
||||
public static void tickPlayer(ServerPlayer player) {
|
||||
for (Protocol protocol : PROTOCOLS) {
|
||||
protocol.tickPlayer(player);
|
||||
}
|
||||
}
|
||||
|
||||
public static void tickTracker(ServerPlayer player) {
|
||||
for (Protocol protocol : PROTOCOLS) {
|
||||
protocol.tickTracker(player);
|
||||
}
|
||||
}
|
||||
|
||||
public static void disconnected(ServerPlayer conn) {
|
||||
for (Protocol protocol : PROTOCOLS) {
|
||||
protocol.disconnected(conn);
|
||||
}
|
||||
}
|
||||
|
||||
@Contract("_ -> new")
|
||||
public static @NotNull ClientboundCustomPayloadPacket createPacket(LeafCustomPayload payload) {
|
||||
return new ClientboundCustomPayloadPacket(payload);
|
||||
}
|
||||
|
||||
private static <B extends FriendlyByteBuf> void encode(B byteBuf, LeafCustomPayload payload, Protocol protocol) {
|
||||
for (var codec : protocol.s2c()) {
|
||||
if (codec.type().id().equals(payload.type().id())) {
|
||||
byteBuf.writeResourceLocation(payload.type().id());
|
||||
//noinspection unchecked,rawtypes
|
||||
((StreamCodec) codec.codec()).encode(byteBuf, payload);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable LeafCustomPayload decode(Protocol protocol, DiscardedPayload payload) {
|
||||
for (var packet : protocol.c2s()) {
|
||||
if (packet.type().id().equals(payload.type().id())) {
|
||||
return packet.codec().decode(new FriendlyByteBuf(Unpooled.wrappedBuffer(payload.data())));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package org.dreeam.leaf.util;
|
||||
|
||||
import net.minecraft.world.entity.Entity;
|
||||
|
||||
import java.lang.reflect.Array; // Required for Array.newInstance
|
||||
import java.util.List;
|
||||
|
||||
public class FastBitRadixSort {
|
||||
|
||||
private static final int SMALL_ARRAY_THRESHOLD = 2;
|
||||
private Entity[] entityBuffer = new Entity[0];
|
||||
private long[] bitsBuffer = new long[0];
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends Entity, T_REF extends Entity> T[] sort(List<T> entities, T_REF referenceEntity, Class<T> entityClass) {
|
||||
int size = entities.size();
|
||||
if (size <= 1) {
|
||||
T[] resultArray = (T[]) Array.newInstance(entityClass, size);
|
||||
return entities.toArray(resultArray);
|
||||
}
|
||||
|
||||
if (this.entityBuffer.length < size) {
|
||||
this.entityBuffer = new Entity[size];
|
||||
this.bitsBuffer = new long[size];
|
||||
}
|
||||
for (int i = 0; i < size; i++) {
|
||||
this.entityBuffer[i] = entities.get(i);
|
||||
this.bitsBuffer[i] = Double.doubleToRawLongBits(
|
||||
referenceEntity.distanceToSqr(entities.get(i))
|
||||
);
|
||||
}
|
||||
|
||||
fastRadixSort(this.entityBuffer, this.bitsBuffer, 0, size - 1, 62);
|
||||
|
||||
T[] resultArray = (T[]) Array.newInstance(entityClass, size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
resultArray[i] = entityClass.cast(this.entityBuffer[i]);
|
||||
}
|
||||
return resultArray;
|
||||
}
|
||||
|
||||
private void fastRadixSort(
|
||||
Entity[] ents,
|
||||
long[] bits,
|
||||
int low,
|
||||
int high,
|
||||
int bit
|
||||
) {
|
||||
if (bit < 0 || low >= high) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (high - low <= SMALL_ARRAY_THRESHOLD) {
|
||||
insertionSort(ents, bits, low, high);
|
||||
return;
|
||||
}
|
||||
|
||||
int i = low;
|
||||
int j = high;
|
||||
final long mask = 1L << bit;
|
||||
|
||||
while (i <= j) {
|
||||
while (i <= j && (bits[i] & mask) == 0) {
|
||||
i++;
|
||||
}
|
||||
while (i <= j && (bits[j] & mask) != 0) {
|
||||
j--;
|
||||
}
|
||||
if (i < j) {
|
||||
swap(ents, bits, i++, j--);
|
||||
}
|
||||
}
|
||||
|
||||
if (low < j) {
|
||||
fastRadixSort(ents, bits, low, j, bit - 1);
|
||||
}
|
||||
if (i < high) {
|
||||
fastRadixSort(ents, bits, i, high, bit - 1);
|
||||
}
|
||||
}
|
||||
|
||||
private void insertionSort(
|
||||
Entity[] ents,
|
||||
long[] bits,
|
||||
int low,
|
||||
int high
|
||||
) {
|
||||
for (int i = low + 1; i <= high; i++) {
|
||||
int j = i;
|
||||
Entity currentEntity = ents[j];
|
||||
long currentBits = bits[j];
|
||||
|
||||
while (j > low && bits[j - 1] > currentBits) {
|
||||
ents[j] = ents[j - 1];
|
||||
bits[j] = bits[j - 1];
|
||||
j--;
|
||||
}
|
||||
ents[j] = currentEntity;
|
||||
bits[j] = currentBits;
|
||||
}
|
||||
}
|
||||
|
||||
private void swap(Entity[] ents, long[] bits, int a, int b) {
|
||||
Entity tempEntity = ents[a];
|
||||
ents[a] = ents[b];
|
||||
ents[b] = tempEntity;
|
||||
|
||||
long tempBits = bits[a];
|
||||
bits[a] = bits[b];
|
||||
bits[b] = tempBits;
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,12 @@ package org.dreeam.leaf.util.cache;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import it.unimi.dsi.fastutil.longs.LongList;
|
||||
import net.minecraft.core.BlockPos;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
|
||||
/**
|
||||
* @author 2No2Name, original implemenation by SuperCoder7979 and Gegy1000
|
||||
*/
|
||||
|
||||
@@ -2,11 +2,10 @@ package org.dreeam.leaf.util.cache;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.LongIterator;
|
||||
import it.unimi.dsi.fastutil.longs.LongList;
|
||||
import net.minecraft.core.BlockPos;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
|
||||
/**
|
||||
* @author 2No2Name
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,303 @@
|
||||
package org.dreeam.leaf.util.list;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ReferenceArrayList;
|
||||
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/**
|
||||
* A List implementation that maintains a hash-based counter for O(1) element lookup.
|
||||
* Combines an array-based list for order with a hash map for fast containment checks.
|
||||
*/
|
||||
public class HashedReferenceList<T> implements List<T> {
|
||||
|
||||
// The actual ordered storage of elements
|
||||
private final ReferenceArrayList<T> list = new ReferenceArrayList<>();
|
||||
// Tracks occurrence count of each element for O(1) contains checks
|
||||
private final Reference2IntOpenHashMap<T> counter;
|
||||
|
||||
/**
|
||||
* Creates a new HashedReferenceList containing all elements from the provided list
|
||||
* while building a counter map for fast lookups.
|
||||
*/
|
||||
public HashedReferenceList(List<T> list) {
|
||||
this.list.addAll(list);
|
||||
this.counter = new Reference2IntOpenHashMap<>();
|
||||
this.counter.defaultReturnValue(0);
|
||||
for (T obj : this.list) {
|
||||
this.counter.addTo(obj, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return this.list.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return this.list.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an element exists in the list in O(1) time using the counter map.
|
||||
*/
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
return this.counter.containsKey(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return this.listIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
return this.list.toArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T1> T1[] toArray(T1 @NotNull [] a) {
|
||||
return this.list.toArray(a);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an element and updates the counter map.
|
||||
*/
|
||||
@Override
|
||||
public boolean add(T t) {
|
||||
this.trackReferenceAdded(t);
|
||||
return this.list.add(t);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an element and updates the counter map.
|
||||
*/
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
this.trackReferenceRemoved(o);
|
||||
return this.list.remove(o);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if all elements of the collection exist in this list.
|
||||
*/
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> c) {
|
||||
for (Object obj : c) {
|
||||
if (this.counter.containsKey(obj)) continue;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends T> c) {
|
||||
for (T obj : c) {
|
||||
this.trackReferenceAdded(obj);
|
||||
}
|
||||
return this.list.addAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(int index, Collection<? extends T> c) {
|
||||
for (T obj : c) {
|
||||
this.trackReferenceAdded(obj);
|
||||
}
|
||||
return this.list.addAll(index, c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimizes removal by converting to a hash set for large operations.
|
||||
*/
|
||||
@Override
|
||||
public boolean removeAll(@NotNull Collection<?> c) {
|
||||
if (this.size() >= 2 && c.size() > 4 && c instanceof List) {
|
||||
c = new ReferenceOpenHashSet<>(c);
|
||||
}
|
||||
this.counter.keySet().removeAll(c);
|
||||
return this.list.removeAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(@NotNull Collection<?> c) {
|
||||
this.counter.keySet().retainAll(c);
|
||||
return this.list.retainAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
this.counter.clear();
|
||||
this.list.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get(int index) {
|
||||
return this.list.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an element at specific index while maintaining accurate counts.
|
||||
*/
|
||||
@Override
|
||||
public T set(int index, T element) {
|
||||
T prev = this.list.set(index, element);
|
||||
if (prev != element) {
|
||||
if (prev != null) {
|
||||
this.trackReferenceRemoved(prev);
|
||||
}
|
||||
this.trackReferenceAdded(element);
|
||||
}
|
||||
return prev;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(int index, T element) {
|
||||
this.trackReferenceAdded(element);
|
||||
this.list.add(index, element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T remove(int index) {
|
||||
T prev = this.list.remove(index);
|
||||
if (prev != null) {
|
||||
this.trackReferenceRemoved(prev);
|
||||
}
|
||||
return prev;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexOf(Object o) {
|
||||
return this.list.indexOf(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lastIndexOf(Object o) {
|
||||
return this.list.lastIndexOf(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListIterator<T> listIterator() {
|
||||
return this.listIterator(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom ListIterator implementation that maintains counter consistency.
|
||||
*/
|
||||
@Override
|
||||
public ListIterator<T> listIterator(final int index) {
|
||||
return new ListIterator<>() {
|
||||
private final ListIterator<T> inner;
|
||||
|
||||
{
|
||||
this.inner = HashedReferenceList.this.list.listIterator(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return this.inner.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
return this.inner.next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPrevious() {
|
||||
return this.inner.hasPrevious();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T previous() {
|
||||
return this.inner.previous();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextIndex() {
|
||||
return this.inner.nextIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int previousIndex() {
|
||||
return this.inner.previousIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the current element and updates counter.
|
||||
*/
|
||||
@Override
|
||||
public void remove() {
|
||||
int last = this.previousIndex();
|
||||
if (last == -1) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
Object prev = HashedReferenceList.this.get(last);
|
||||
if (prev != null) {
|
||||
HashedReferenceList.this.trackReferenceRemoved(prev);
|
||||
}
|
||||
this.inner.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current element and updates counter.
|
||||
*/
|
||||
@Override
|
||||
public void set(T t) {
|
||||
int last = this.previousIndex();
|
||||
if (last == -1) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
Object prev = HashedReferenceList.this.get(last);
|
||||
if (prev != t) {
|
||||
if (prev != null) {
|
||||
HashedReferenceList.this.trackReferenceRemoved(prev);
|
||||
}
|
||||
HashedReferenceList.this.trackReferenceAdded(t);
|
||||
}
|
||||
this.inner.remove();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(T t) {
|
||||
HashedReferenceList.this.trackReferenceAdded(t);
|
||||
this.inner.add(t);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<T> subList(int fromIndex, int toIndex) {
|
||||
return this.list.subList(fromIndex, toIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the reference counter for an added element.
|
||||
*/
|
||||
private void trackReferenceAdded(T t) {
|
||||
this.counter.addTo(t, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrements the reference counter and removes if count reaches 0.
|
||||
*/
|
||||
private void trackReferenceRemoved(Object o) {
|
||||
if (this.counter.addTo((T) o, -1) <= 1) {
|
||||
this.counter.removeInt(o);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a HashedReferenceList from an existing list.
|
||||
*/
|
||||
public static <T> HashedReferenceList<T> wrapper(List<T> list) {
|
||||
return new HashedReferenceList<>(list);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
package org.dreeam.leaf.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;
|
||||
|
||||
// fast array backend map with O(1) get & put & remove
|
||||
public class AttributeInstanceArrayMap implements Map<Holder<Attribute>, AttributeInstance>, Cloneable {
|
||||
|
||||
private int size = 0;
|
||||
private transient AttributeInstance[] a = new AttributeInstance[32];
|
||||
private transient KeySet keys;
|
||||
private transient Values values;
|
||||
private transient EntrySet entries;
|
||||
|
||||
public AttributeInstanceArrayMap() {
|
||||
if (BuiltInRegistries.ATTRIBUTE.size() != 32) {
|
||||
throw new IllegalStateException("Registered custom attribute");
|
||||
}
|
||||
}
|
||||
|
||||
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 final int size() {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean isEmpty() {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final 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 final boolean containsValue(Object value) {
|
||||
for (final AttributeInstance instance : a) {
|
||||
if (Objects.equals(value, instance)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final AttributeInstance get(Object key) {
|
||||
return key instanceof Holder<?> holder && holder.value() instanceof Attribute attribute ? a[attribute.uid] : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final AttributeInstance put(@NotNull Holder<Attribute> key, AttributeInstance value) {
|
||||
int uid = key.value().uid;
|
||||
AttributeInstance prev = a[uid];
|
||||
setByIndex(uid, value);
|
||||
return prev;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final 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 final 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 final void clear() {
|
||||
Arrays.fill(a, null);
|
||||
size = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final @NotNull Set<Holder<Attribute>> keySet() {
|
||||
if (keys == null) {
|
||||
keys = new KeySet();
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final @NotNull Collection<AttributeInstance> values() {
|
||||
if (values == null) {
|
||||
values = new Values();
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final @NotNull Set<Entry<Holder<Attribute>, AttributeInstance>> entrySet() {
|
||||
if (entries == null) {
|
||||
entries = new EntrySet();
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean equals(Object o) {
|
||||
if (!(o instanceof AttributeInstanceArrayMap that)) return false;
|
||||
return size == that.size && Arrays.equals(a, that.a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final 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.asHolderIdMap().byIdOrThrow(currentIndex);
|
||||
}
|
||||
|
||||
@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.asHolderIdMap().byIdOrThrow(nextIndex);
|
||||
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;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,7 @@
|
||||
package org.dreeam.leaf.util.queue;
|
||||
|
||||
import java.util.OptionalInt;
|
||||
|
||||
/// Lock-free Single Producer Single Consumer Queue
|
||||
public class SpscIntQueue {
|
||||
|
||||
@@ -33,14 +35,14 @@ public class SpscIntQueue {
|
||||
}
|
||||
|
||||
|
||||
public final int recv() {
|
||||
public final OptionalInt recv() {
|
||||
final int idx = consumerIdx.getOpaque();
|
||||
int cachedIdx = producerCachedIdx.getPlain();
|
||||
if (idx == cachedIdx) {
|
||||
cachedIdx = producerIdx.getAcquire();
|
||||
producerCachedIdx.setPlain(cachedIdx);
|
||||
if (idx == cachedIdx) {
|
||||
return Integer.MAX_VALUE;
|
||||
return OptionalInt.empty();
|
||||
}
|
||||
}
|
||||
int e = data[idx];
|
||||
@@ -49,7 +51,7 @@ public class SpscIntQueue {
|
||||
nextIdx = 0;
|
||||
}
|
||||
consumerIdx.setRelease(nextIdx);
|
||||
return e;
|
||||
return OptionalInt.of(e);
|
||||
}
|
||||
|
||||
public final int size() {
|
||||
@@ -57,8 +59,28 @@ public class SpscIntQueue {
|
||||
}
|
||||
|
||||
static class PaddedAtomicInteger extends java.util.concurrent.atomic.AtomicInteger {
|
||||
// @formatter:off
|
||||
@SuppressWarnings("unused")
|
||||
private int i1, i2, i3, i4, i5, i6, i7, i8,
|
||||
i9, i10, i11, i12, i13, i14, i15;
|
||||
private byte
|
||||
i0, i1, i2, i3, i4, i5, i6, i7, i8, i9, i10, i11, i12, i13, i14, i15,
|
||||
j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15,
|
||||
k0, k1, k2, k3, k4, k5, k6, k7, k8, k9, k10, k11, k12, k13, k14, k15,
|
||||
l0, l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15,
|
||||
|
||||
m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15,
|
||||
n0, n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13, n14, n15,
|
||||
o0, o1, o2, o3, o4, o5, o6, o7, o8, o9, o10, o11, o12, o13, o14, o15,
|
||||
p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15,
|
||||
|
||||
q0, q1, q2, q3, q4, q5, q6, q7, q8, q9, q10, q11, q12, q13, q14, q15,
|
||||
r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15,
|
||||
s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15,
|
||||
t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15,
|
||||
|
||||
u0, u1, u2, u3, u4, u5, u6, u7, u8, u9, u10, u11, u12, u13, u14, u15,
|
||||
v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15,
|
||||
w0, w1, w2, w3, w4, w5, w6, w7, w8, w9, w10, w11, w12, w13, w14, w15,
|
||||
x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11;
|
||||
// @formatter:on
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user