9
0
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:
Dreeam
2025-05-24 14:14:34 +08:00
95 changed files with 4201 additions and 3563 deletions

View File

@@ -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());
}

View File

@@ -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()
);

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,8 @@
package org.dreeam.leaf.async.ai;
import org.jetbrains.annotations.Nullable;
@FunctionalInterface
public interface VWaker {
@Nullable Object wake();
}

View File

@@ -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;
}
}
}

View File

@@ -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");
}

View File

@@ -0,0 +1,8 @@
package org.dreeam.leaf.async.chunk;
public class AsyncChunkSendThread extends Thread {
protected AsyncChunkSendThread(Runnable task) {
super(task);
}
}

View File

@@ -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;

View File

@@ -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);
}}
}
}

View File

@@ -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;

View File

@@ -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,

View File

@@ -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();
}

View File

@@ -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() {

View File

@@ -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.

View File

@@ -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);
}
}

View File

@@ -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());

View File

@@ -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 {

View File

@@ -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;

View File

@@ -57,5 +57,8 @@ public class MultithreadedTracker extends ConfigModules {
if (asyncEntityTrackerQueueSize <= 0)
asyncEntityTrackerQueueSize = asyncEntityTrackerMaxThreads * 384;
if (enabled) {
org.dreeam.leaf.async.tracker.MultithreadedTracker.init();
}
}
}

View File

@@ -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() {

View File

@@ -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.",
"随机掉落物品的向上运动."
));
}
}

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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>, 显示命令错误详细信息."""));
}
}

View File

@@ -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.",
"异步切换连接状态."));
}
}

View File

@@ -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.""",
"""
在仙人掌生长前检查其是否能够存活。"""));
}
}

View File

@@ -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 方法启用缓存,以优化性能并减少内存使用."));
}
}

View File

@@ -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);
}
}

View File

@@ -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 / 更新,而不是整个物品栏中的所有物品。"""));
}
}

View File

@@ -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.""",
"""
是否优化玩家移动处理,跳过不必要的边缘检查并避免冗余的视距更新。"""));
}
}

View File

@@ -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)",
"减少玩家跨区块移动时的区块源更新。"
)
);
}
}

View File

@@ -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);
}
}

View File

@@ -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
}
}

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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
*/

View File

@@ -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
*/

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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
}
}