9
0
mirror of https://github.com/Winds-Studio/Leaf.git synced 2025-12-28 19:39:17 +00:00

Update changes from ver/1.21.4 branch

This commit is contained in:
Dreeam
2025-06-10 12:34:55 +08:00
155 changed files with 2833 additions and 1026 deletions

View File

@@ -4,10 +4,11 @@ import io.papermc.paper.PaperBootstrap;
import joptsimple.OptionSet;
public class LeafBootstrap {
public static final boolean enableFMA = Boolean.parseBoolean(System.getProperty("Leaf.enableFMA", "false")); // Leaf - FMA feature
public static void boot(final OptionSet options) {
runPreBootTasks();
//runPreBootTasks();
PaperBootstrap.boot(options);
}

View File

@@ -0,0 +1,60 @@
package org.dreeam.leaf.async;
import net.minecraft.server.MinecraftServer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dreeam.leaf.async.ai.AsyncGoalThread;
import org.dreeam.leaf.async.path.AsyncPathProcessor;
import org.dreeam.leaf.async.tracker.MultithreadedTracker;
import java.util.concurrent.TimeUnit;
public class ShutdownExecutors {
public static final Logger LOGGER = LogManager.getLogger("Leaf");
public static void shutdown(MinecraftServer server) {
if (server.mobSpawnExecutor != null) {
LOGGER.info("Waiting for mob spawning thread to shutdown...");
try {
server.mobSpawnExecutor.join(3000L);
} catch (InterruptedException ignored) {
}
}
if (AsyncPlayerDataSaving.IO_POOL != null) {
LOGGER.info("Waiting for player I/O executor to shutdown...");
AsyncPlayerDataSaving.IO_POOL.shutdown();
try {
AsyncPlayerDataSaving.IO_POOL.awaitTermination(60L, TimeUnit.SECONDS);
} catch (InterruptedException ignored) {
}
}
if (server.asyncGoalThread != null) {
LOGGER.info("Waiting for mob target finding thread to shutdown...");
AsyncGoalThread.RUNNING = false;
try {
server.asyncGoalThread.join(3000L);
} catch (InterruptedException ignored) {
}
}
if (MultithreadedTracker.TRACKER_EXECUTOR != null) {
LOGGER.info("Waiting for mob tracker executor to shutdown...");
MultithreadedTracker.TRACKER_EXECUTOR.shutdown();
try {
MultithreadedTracker.TRACKER_EXECUTOR.awaitTermination(10L, TimeUnit.SECONDS);
} catch (InterruptedException ignored) {
}
}
if (AsyncPathProcessor.pathProcessingExecutor != null) {
LOGGER.info("Waiting for mob pathfinding executor to shutdown...");
AsyncPathProcessor.pathProcessingExecutor.shutdown();
try {
AsyncPathProcessor.pathProcessingExecutor.awaitTermination(10L, TimeUnit.SECONDS);
} catch (InterruptedException ignored) {
}
}
}
}

View File

@@ -9,6 +9,7 @@ import java.util.concurrent.locks.LockSupport;
public class AsyncGoalThread extends Thread {
public static volatile boolean RUNNING = true;
public AsyncGoalThread(final MinecraftServer server) {
super(() -> run(server), "Leaf Async Goal Thread");
this.setDaemon(false);
@@ -18,7 +19,7 @@ public class AsyncGoalThread extends Thread {
}
private static void run(MinecraftServer server) {
while (server.isRunning()) {
while (RUNNING) {
boolean retry = false;
for (ServerLevel level : server.getAllLevels()) {
var exec = level.asyncGoalExecutor;

View File

@@ -28,7 +28,7 @@ public class AsyncPathProcessor {
private static final String THREAD_PREFIX = "Leaf Async Pathfinding";
private static final Logger LOGGER = LogManager.getLogger(THREAD_PREFIX);
private static long lastWarnMillis = System.currentTimeMillis();
private static final ThreadPoolExecutor pathProcessingExecutor = new ThreadPoolExecutor(
public static final ThreadPoolExecutor pathProcessingExecutor = new ThreadPoolExecutor(
1,
AsyncPathfinding.asyncPathfindingMaxThreads,
AsyncPathfinding.asyncPathfindingKeepalive, TimeUnit.SECONDS,

View File

@@ -30,7 +30,7 @@ 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 ThreadPoolExecutor TRACKER_EXECUTOR = null;
public static ThreadPoolExecutor TRACKER_EXECUTOR = null;
private MultithreadedTracker() {
}

View File

@@ -3,6 +3,7 @@ package org.dreeam.leaf.config.modules.async;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
import org.dreeam.leaf.config.LeafConfig;
public class AsyncTargetFinding extends ConfigModules {
@@ -33,6 +34,11 @@ public class AsyncTargetFinding extends ConfigModules {
asyncTargetFindingInitialized = true;
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
// Disable if parallel world ticking is enabled, as they are incompatible.
if (enabled && SparklyPaperParallelWorldTicking.enabled) {
LeafConfig.LOGGER.warn("Async Target Finding is incompatible with Parallel World Ticking. Disabling Async Target Finding automatically.");
enabled = false;
}
alertOther = config.getBoolean(getBasePath() + ".async-alert-other", true);
searchBlock = config.getBoolean(getBasePath() + ".async-search-block", true);
searchEntity = config.getBoolean(getBasePath() + ".async-search-entity", true);

View File

@@ -17,7 +17,7 @@ public class SparklyPaperParallelWorldTicking extends ConfigModules {
public static boolean logContainerCreationStacktraces = false;
public static boolean disableHardThrow = false;
@Deprecated
public static boolean runAsyncTasksSync = false;
public static Boolean runAsyncTasksSync;
// STRICT, BUFFERED, DISABLED
public static String asyncUnsafeReadHandling = "BUFFERED";
@@ -52,15 +52,14 @@ public class SparklyPaperParallelWorldTicking extends ConfigModules {
asyncUnsafeReadHandling = "DISABLED";
}
runAsyncTasksSync = config.getBoolean(getBasePath() + ".run-async-tasks-sync", false); // Default to false now
if (runAsyncTasksSync) {
LeafConfig.LOGGER.warn("The setting '{}.run-async-tasks-sync' is deprecated. Use 'async-unsafe-read-handling: STRICT' for similar safety checks or 'BUFFERED' for buffered reads.", getBasePath());
// Transfer old config
runAsyncTasksSync = config.getBoolean(getBasePath() + ".run-async-tasks-sync");
if (runAsyncTasksSync != null && runAsyncTasksSync) {
LeafConfig.LOGGER.warn("The setting '{}.run-async-tasks-sync' is deprecated, removed automatically. Use 'async-unsafe-read-handling: BUFFERED' for buffered reads instead.", getBasePath());
}
if (enabled) {
LeafConfig.LOGGER.info("Using {} threads for Parallel World Ticking", threads);
}
runAsyncTasksSync = enabled && runAsyncTasksSync; // Auto-disable if main feature is off
}
}

View File

@@ -15,6 +15,7 @@ public class Knockback extends ConfigModules {
public static boolean canPlayerKnockbackZombie = true;
@Experimental
public static boolean flushKnockback = false;
public static boolean oldBlastProtectionKnockbackBehavior = false;
@Override
public void onLoaded() {
@@ -34,5 +35,6 @@ public class Knockback extends ConfigModules {
"使玩家可以击退僵尸."
));
flushKnockback = config.getBoolean(getBasePath() + ".flush-location-while-knockback-player", flushKnockback);
oldBlastProtectionKnockbackBehavior = config.getBoolean(getBasePath() + ".old-blast-protection-explosion-knockback", oldBlastProtectionKnockbackBehavior);
}
}

View File

@@ -1,28 +0,0 @@
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 SmoothTeleport extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.GAMEPLAY.getBaseKeyName() + ".smooth-teleport";
}
@Experimental
public static boolean enabled = false;
@Override
public void onLoaded() {
enabled = config.getBoolean(getBasePath(), enabled, config.pickStringRegionBased("""
**Experimental feature**
Whether to make a "smooth teleport" when players changing dimension.
This requires original world and target world have same logical height to work.""",
"""
**实验性功能**
是否在玩家切换世界时尝试使用 "平滑传送".
此项要求源世界和目标世界逻辑高度相同才会生效."""
));
}
}

View File

@@ -29,7 +29,6 @@ public class ProtocolSupport extends ConfigModules {
public static boolean doABarrelRollForceEnabled = false;
public static boolean doABarrelRollForceInstalled = false;
public static int doABarrelRollInstalledTimeout = 40;
public static DoABarrelRollPackets.KineticDamage doABarrelRollKineticDamage = DoABarrelRollPackets.KineticDamage.VANILLA;
@Override
public void onLoaded() {
@@ -53,7 +52,6 @@ public class ProtocolSupport extends ConfigModules {
doABarrelRollForceEnabled = config.getBoolean(getBasePath() + ".do-a-barrel-roll-force-enabled", doABarrelRollForceEnabled);
doABarrelRollForceInstalled = config.getBoolean(getBasePath() + ".do-a-barrel-roll-force-installed", doABarrelRollForceInstalled);
doABarrelRollInstalledTimeout = config.getInt(getBasePath() + ".do-a-barrel-roll-installed-timeout", 0);
doABarrelRollKineticDamage = DoABarrelRollPackets.KineticDamage.valueOf(config.getString(getBasePath() + ".do-a-barrel-roll-kinetic-damage", doABarrelRollKineticDamage.name()));
if (doABarrelRollInstalledTimeout <= 0) {
doABarrelRollInstalledTimeout = 40;
}
@@ -63,7 +61,7 @@ public class ProtocolSupport extends ConfigModules {
doABarrelRollForceEnabled,
doABarrelRollForceInstalled,
doABarrelRollInstalledTimeout,
doABarrelRollKineticDamage
DoABarrelRollPackets.KineticDamage.VANILLA
);
} else {
DoABarrelRollProtocol.deinit();

View File

@@ -1,21 +0,0 @@
package org.dreeam.leaf.config.modules.opt;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
public class BrainRunningBehaviorCacheUpdate extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.PERF.getBaseKeyName();
}
public static int interval = 5;
@Override
public void onLoaded() {
interval = config.getInt(getBasePath() + ".entity-running-behavior-cache-update-interval", interval,
config.pickStringRegionBased(
"How often entity update current brain running behavior list.",
"生物更新现有 Brain Behavior 列表缓存的间隔."));
}
}

View File

@@ -1,18 +0,0 @@
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 OptimizeBiome extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.PERF.getBaseKeyName() + ".cache-biome";
}
public static boolean enabled = false;
public static boolean mobSpawn = false;
public static boolean advancement = false;
@Override
public void onLoaded() {
enabled = config().getBoolean(getBasePath() + ".enabled", enabled);
mobSpawn = config.getBoolean(getBasePath() + ".mob-spawning", false);
advancement = config.getBoolean(getBasePath() + ".advancements", false);
}
}

View File

@@ -0,0 +1,24 @@
package org.dreeam.leaf.config.modules.opt;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
public class OptimizeBlockEntities extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.PERF.getBaseKeyName();
}
public static boolean enabled = true;
@Override
public void onLoaded() {
// Transfer old config
Boolean optimiseBlockEntities = config.getBoolean(getBasePath() + ".optimise-block-entities");
if (optimiseBlockEntities != null && optimiseBlockEntities) {
enabled = true;
}
enabled = config.getBoolean(getBasePath() + ".optimize-block-entities", enabled);
}
}

View File

@@ -17,6 +17,5 @@ public class OptimizePlayerMovementProcessing extends ConfigModules {
Whether to optimize player movement processing by skipping unnecessary edge checks and avoiding redundant view distance updates.""",
"""
是否优化玩家移动处理,跳过不必要的边缘检查并避免冗余的视距更新。"""));
}
}

View File

@@ -0,0 +1,17 @@
package org.dreeam.leaf.config.modules.opt;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
public class PreloadNaturalMobSpawning extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.PERF.getBaseKeyName() + ".preload-mob-spawning-position";
}
public static boolean enabled = false;
@Override
public void onLoaded() {
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
}
}

View File

@@ -15,7 +15,7 @@ public class ReduceChunkSourceUpdates extends ConfigModules {
public void onLoaded() {
enabled = config.getBoolean(getBasePath() + ".enabled", enabled,
config.pickStringRegionBased(
"Reduces chunk source updates on inter-chunk player moves. (Recommended to enable)",
"Reduces chunk source updates on inter-chunk player moves.",
"减少玩家跨区块移动时的区块源更新。"
)
);

View File

@@ -0,0 +1,31 @@
package org.dreeam.leaf.config.modules.opt;
import net.minecraft.world.entity.MobCategory;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
public class ThrottleNaturalMobSpawning extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.PERF.getBaseKeyName() + ".throttle-mob-spawning";
}
public static boolean enabled = false;
public static long[] failedAttempts;
public static int[] spawnChance;
@Override
public void onLoaded() {
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
MobCategory[] categories = MobCategory.values();
failedAttempts = new long[categories.length];
spawnChance = new int[categories.length];
for (int i = 0; i < categories.length; i++) {
String category = getBasePath() + "." + categories[i].getSerializedName();
long attempts = config.getLong(category + ".min-failed", 8);
double chance = config.getDouble(category + ".spawn-chance", 25.0);
failedAttempts[i] = Math.max(-1, attempts);
spawnChance[i] = Math.clamp(0, (int) Math.round(chance * 10.24), 1024);
}
}
}

View File

@@ -1,21 +0,0 @@
package org.dreeam.leaf.config.modules.opt;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
public class VT4DownloadPool extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.PERF.getBaseKeyName();
}
public static boolean enabled = true;
@Override
public void onLoaded() {
enabled = config.getBoolean(getBasePath() + ".use-virtual-thread-for-download-pool", enabled,
config.pickStringRegionBased(
"Use the new Virtual Thread introduced in JDK 21 for download worker pool.",
"是否为下载工作线程池使用虚拟线程(如果可用)。"));
}
}

View File

@@ -1,14 +1,7 @@
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 it.unimi.dsi.fastutil.objects.*;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.server.MinecraftServer;
@@ -25,7 +18,6 @@ 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;
@@ -54,8 +46,8 @@ public class DoABarrelRollProtocol implements Protocol {
private ModConfigServer config = DEFAULT;
private boolean configUpdated = false;
private final Reference2ReferenceMap<ServerGamePacketListenerImpl, ClientInfo> syncStates = new Reference2ReferenceOpenHashMap<>();
private final Reference2ReferenceMap<ServerGamePacketListenerImpl, DelayedRunnable> scheduledKicks = new Reference2ReferenceOpenHashMap<>();
private final Reference2ReferenceMap<ServerGamePacketListenerImpl, ClientInfo> syncStates = Reference2ReferenceMaps.synchronize(new Reference2ReferenceOpenHashMap<>());
private final Reference2ReferenceMap<ServerGamePacketListenerImpl, DelayedRunnable> scheduledKicks = Reference2ReferenceMaps.synchronize(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<>());
@@ -99,14 +91,14 @@ public class DoABarrelRollProtocol implements Protocol {
}
@Override
public void handle(ServerPlayer player, @NotNull LeafCustomPayload payload) {
public void handle(ServerPlayer player, 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);
sendHandshake(player.connection);
}
}
case RollSyncC2SPacket rollSyncC2SPacket -> {
@@ -138,41 +130,39 @@ public class DoABarrelRollProtocol implements Protocol {
}
@Override
public void tickTracker(ServerPlayer player) {
if (!isRollingMap.containsKey(player.connection)) {
public void tickPlayer(ServerPlayer player) {
ServerGamePacketListenerImpl connection = player.connection;
if (getHandshakeState(connection).state == HandshakeState.NOT_SENT) {
sendHandshake(connection);
}
if (!isRollingMap.containsKey(connection)) {
return;
}
if (!isRollingMap.getBoolean(connection)) {
rollMap.put(connection, 0.0F);
}
var isRolling = isRollingMap.getBoolean(player.connection);
var roll = rollMap.getFloat(player.connection);
var lastIsRolling = lastIsRollingMap.getBoolean(player.connection);
var lastRoll = lastRollMap.getFloat(player.connection);
boolean isRolling = isRollingMap.getBoolean(connection);
float roll = rollMap.getFloat(connection);
boolean lastIsRolling = lastIsRollingMap.getBoolean(connection);
float lastRoll = lastRollMap.getFloat(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()) {
var tracked = player.moonrise$getTrackedEntity();
if (tracked == null) {
return;
}
for (ServerPlayerConnection seenBy : tracked.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);
}
lastIsRollingMap.put(connection, isRolling);
lastRollMap.put(connection, roll);
}
@Override
@@ -190,7 +180,7 @@ public class DoABarrelRollProtocol implements Protocol {
if (configUpdated) {
configUpdated = false;
for (ServerPlayer player : server.getPlayerList().players) {
sendHandshake(player);
sendHandshake(player.connection);
}
}
}
@@ -199,9 +189,9 @@ public class DoABarrelRollProtocol implements Protocol {
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 sendHandshake(ServerGamePacketListenerImpl connection) {
connection.send(Protocols.createPacket(initiateConfigSync(connection)));
configSentToClient(connection);
}
private void configSentToClient(ServerGamePacketListenerImpl handler) {
@@ -255,7 +245,7 @@ public class DoABarrelRollProtocol implements Protocol {
return info.state;
}
private boolean isLimited(ServerGamePacketListenerImpl net) {
private boolean isLimited(ServerGamePacketListenerImpl ignore) {
return true;
// return net.getPlayer().getBukkitEntity().hasPermission(DoABarrelRoll.MODID + ".configure");
}
@@ -266,19 +256,19 @@ public class DoABarrelRollProtocol implements Protocol {
private ConfigSyncS2CPacket initiateConfigSync(ServerGamePacketListenerImpl handler) {
var isLimited = isLimited(handler);
getHandshakeState(handler).isLimited = isLimited;
// 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 boolean isLimited;
private ClientInfo(HandshakeState state, int protocolVersion, boolean isLimited) {
private ClientInfo(HandshakeState state, int protocolVersion, boolean ignore) {
this.state = state;
this.protocolVersion = protocolVersion;
this.isLimited = isLimited;
// this.isLimited = isLimited;
}
}

View File

@@ -3,7 +3,6 @@ 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;
@@ -19,9 +18,7 @@ interface Protocol {
void tickPlayer(ServerPlayer player);
void tickTracker(ServerPlayer player);
void disconnected(ServerPlayer conn);
void handle(ServerPlayer player, @NotNull LeafCustomPayload payload);
void handle(ServerPlayer player, LeafCustomPayload payload);
}

View File

@@ -61,12 +61,6 @@ public class Protocols {
}
}
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);

View File

@@ -0,0 +1,215 @@
package org.dreeam.leaf.util.list;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.AbstractList;
import java.util.BitSet;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Spliterator;
import java.util.function.Consumer;
/**
* A specialized list that allows for efficient hiding and showing of elements
* without physically removing them from the backing store.
* <p>
* Iteration only processes "visible" elements, and visibility can be toggled in O(1) time.
* This is useful for managing lists of tasks or objects where a large set exists,
* but only a small subset is active at any given time.
*
* @param <E> The type of elements in this list.
*/
public class ActivationList<E> extends AbstractList<E> {
private final ObjectArrayList<E> elements;
private final BitSet visibilityMask;
private final Object2IntOpenHashMap<E> elementToIndexMap;
private final boolean isVisibleByDefault;
private int removedSlotCount;
/**
* Constructs a new, empty MaskedList.
*
* @param isVisibleByDefault The default visibility for elements added to this list.
*/
public ActivationList(boolean isVisibleByDefault) {
this.elements = new ObjectArrayList<>();
this.visibilityMask = new BitSet();
this.elementToIndexMap = new Object2IntOpenHashMap<>();
this.elementToIndexMap.defaultReturnValue(-1);
this.isVisibleByDefault = isVisibleByDefault;
}
/**
* Constructs a new, empty MaskedList with default visibility set to true.
*/
public ActivationList() {
this(true);
}
/**
* Adds an element to the list or, if it already exists, updates its visibility.
*
* @param element The element to add or update.
* @param visible The desired visibility of the element.
*/
public void addOrUpdate(E element, boolean visible) {
int index = this.elementToIndexMap.getInt(element);
if (index == -1) {
index = this.elements.size();
this.elements.add(element);
this.elementToIndexMap.put(element, index);
}
this.visibilityMask.set(index, visible);
}
/**
* Sets the visibility of an existing element.
*
* @param element The element whose visibility to change.
* @param visible True to make the element visible, false to hide it.
*/
public void setVisibility(E element, boolean visible) {
int index = this.elementToIndexMap.getInt(element);
if (index != -1) {
this.visibilityMask.set(index, visible);
}
}
@Override
public boolean add(E element) {
if (this.elementToIndexMap.containsKey(element)) {
throw new IllegalArgumentException("MaskedList cannot contain duplicate elements: " + element);
}
this.addOrUpdate(element, this.isVisibleByDefault);
return true;
}
@Override
public boolean remove(Object o) {
int index = this.elementToIndexMap.removeInt(o);
if (index == -1) {
return false;
}
this.visibilityMask.clear(index);
this.elements.set(index, null);
this.removedSlotCount++;
if (this.removedSlotCount > 0 && this.removedSlotCount * 2 >= this.elements.size()) {
compact();
}
return true;
}
/**
* Rebuilds the internal list (wow)
*/
private void compact() {
int writeIndex = 0;
for (int readIndex = 0; readIndex < this.elements.size(); readIndex++) {
E element = this.elements.get(readIndex);
if (element != null) {
if (readIndex != writeIndex) {
this.elements.set(writeIndex, element);
this.elementToIndexMap.put(element, writeIndex);
this.visibilityMask.set(writeIndex, this.visibilityMask.get(readIndex));
}
writeIndex++;
}
}
int oldSize = this.elements.size();
if (writeIndex < oldSize) {
this.elements.removeElements(writeIndex, oldSize);
this.visibilityMask.clear(writeIndex, oldSize);
}
this.removedSlotCount = 0;
}
@Override
public int size() {
return this.visibilityMask.cardinality();
}
@Override
public E get(int index) {
if (index < 0 || index >= this.size()) {
throw new IndexOutOfBoundsException("Index: " + index + ", Visible Size: " + this.size());
}
int setBitIndex = -1;
for (int i = 0; i <= index; i++) {
setBitIndex = this.visibilityMask.nextSetBit(setBitIndex + 1);
}
return this.elements.get(setBitIndex);
}
@Override
public Iterator<E> iterator() {
return new MaskedIterator();
}
private class MaskedIterator implements Iterator<E> {
private int nextVisibleIndex;
MaskedIterator() {
this.nextVisibleIndex = ActivationList.this.visibilityMask.nextSetBit(0);
}
@Override
public boolean hasNext() {
return this.nextVisibleIndex != -1;
}
@Override
public E next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
E element = ActivationList.this.elements.get(this.nextVisibleIndex);
this.nextVisibleIndex = ActivationList.this.visibilityMask.nextSetBit(this.nextVisibleIndex + 1);
return element;
}
}
@Override
public Spliterator<E> spliterator() {
return new MaskedSpliterator();
}
private class MaskedSpliterator implements Spliterator<E> {
private int currentIndex;
MaskedSpliterator() {
this.currentIndex = ActivationList.this.visibilityMask.nextSetBit(0);
}
@Override
public boolean tryAdvance(Consumer<? super E> action) {
if (this.currentIndex != -1) {
action.accept(ActivationList.this.elements.get(this.currentIndex));
this.currentIndex = ActivationList.this.visibilityMask.nextSetBit(this.currentIndex + 1);
return true;
}
return false;
}
@Override
public Spliterator<E> trySplit() {
return null; // This spliterator does not support splitting.
}
@Override
public long estimateSize() {
return ActivationList.this.size();
}
@Override
public int characteristics() {
return Spliterator.ORDERED | Spliterator.DISTINCT | Spliterator.NONNULL | Spliterator.SIZED;
}
}
}