9
0
mirror of https://github.com/Winds-Studio/Leaf.git synced 2025-12-25 18:09:17 +00:00

Merge Gale

This commit is contained in:
Dreeam
2025-03-28 04:11:37 -04:00
parent 236010caba
commit b7864dfcac
337 changed files with 10292 additions and 417 deletions

View File

@@ -1,133 +0,0 @@
package gg.pufferfish.pufferfish.sentry;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import io.sentry.Breadcrumb;
import io.sentry.Sentry;
import io.sentry.SentryEvent;
import io.sentry.SentryLevel;
import io.sentry.protocol.Message;
import io.sentry.protocol.User;
import java.util.Map;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.filter.AbstractFilter;
import org.dreeam.leaf.config.modules.misc.SentryDSN;
public class PufferfishSentryAppender extends AbstractAppender {
private static final org.apache.logging.log4j.Logger LOGGER = LogManager.getLogger(PufferfishSentryAppender.class.getSimpleName());
private static final Gson GSON = new Gson();
private final Level logLevel;
public PufferfishSentryAppender(Level logLevel) {
super("PufferfishSentryAdapter", new SentryFilter(), null);
this.logLevel = logLevel;
}
@Override
public void append(LogEvent logEvent) {
if (logEvent.getLevel().isMoreSpecificThan(logLevel) && (logEvent.getThrown() != null || !SentryDSN.onlyLogThrown)) {
try {
logException(logEvent);
} catch (Exception e) {
LOGGER.warn("Failed to log event with sentry", e);
}
} else {
try {
logBreadcrumb(logEvent);
} catch (Exception e) {
LOGGER.warn("Failed to log event with sentry", e);
}
}
}
private void logException(LogEvent e) {
SentryEvent event = new SentryEvent(e.getThrown());
Message sentryMessage = new Message();
sentryMessage.setMessage(e.getMessage().getFormattedMessage());
event.setThrowable(e.getThrown());
event.setLevel(getLevel(e.getLevel()));
event.setLogger(e.getLoggerName());
event.setTransaction(e.getLoggerName());
event.setExtra("thread_name", e.getThreadName());
boolean hasContext = e.getContextData() != null;
if (hasContext && e.getContextData().containsKey("pufferfishsentry_playerid")) {
User user = new User();
user.setId(e.getContextData().getValue("pufferfishsentry_playerid"));
user.setUsername(e.getContextData().getValue("pufferfishsentry_playername"));
event.setUser(user);
}
if (hasContext && e.getContextData().containsKey("pufferfishsentry_pluginname")) {
event.setExtra("plugin.name", e.getContextData().getValue("pufferfishsentry_pluginname"));
event.setExtra("plugin.version", e.getContextData().getValue("pufferfishsentry_pluginversion"));
event.setTransaction(e.getContextData().getValue("pufferfishsentry_pluginname"));
}
if (hasContext && e.getContextData().containsKey("pufferfishsentry_eventdata")) {
Map<String, String> eventFields = GSON.fromJson((String) e.getContextData().getValue("pufferfishsentry_eventdata"), new TypeToken<Map<String, String>>() {
}.getType());
if (eventFields != null) {
event.setExtra("event", eventFields);
}
}
Sentry.captureEvent(event);
}
private void logBreadcrumb(LogEvent e) {
Breadcrumb breadcrumb = new Breadcrumb();
breadcrumb.setLevel(getLevel(e.getLevel()));
breadcrumb.setCategory(e.getLoggerName());
breadcrumb.setType(e.getLoggerName());
breadcrumb.setMessage(e.getMessage().getFormattedMessage());
Sentry.addBreadcrumb(breadcrumb);
}
private SentryLevel getLevel(Level level) {
return switch (level.getStandardLevel()) {
case TRACE, DEBUG -> SentryLevel.DEBUG;
case WARN -> SentryLevel.WARNING;
case ERROR -> SentryLevel.ERROR;
case FATAL -> SentryLevel.FATAL;
default -> SentryLevel.INFO;
};
}
private static class SentryFilter extends AbstractFilter {
@Override
public Result filter(Logger logger, org.apache.logging.log4j.Level level, Marker marker, String msg,
Object... params) {
return this.filter(logger.getName());
}
@Override
public Result filter(Logger logger, org.apache.logging.log4j.Level level, Marker marker, Object msg, Throwable t) {
return this.filter(logger.getName());
}
@Override
public Result filter(LogEvent event) {
return this.filter(event == null ? null : event.getLoggerName());
}
private Result filter(String loggerName) {
return loggerName != null && loggerName.startsWith("gg.castaway.pufferfish.sentry") ? Result.DENY
: Result.NEUTRAL;
}
}
}

View File

@@ -1,44 +0,0 @@
package gg.pufferfish.pufferfish.sentry;
import io.sentry.Sentry;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class SentryManager {
private static final Logger LOGGER = LogManager.getLogger(SentryManager.class);
private SentryManager() {
}
private static boolean initialized = false;
public static synchronized void init(Level logLevel) {
if (initialized) {
return;
}
if (logLevel == null) {
LOGGER.error("Invalid log level, defaulting to WARN.");
logLevel = Level.WARN;
}
try {
initialized = true;
Sentry.init(options -> {
options.setDsn(org.dreeam.leaf.config.modules.misc.SentryDSN.sentryDsn);
options.setMaxBreadcrumbs(100);
});
PufferfishSentryAppender appender = new PufferfishSentryAppender(logLevel);
appender.start();
((org.apache.logging.log4j.core.Logger) LogManager.getRootLogger()).addAppender(appender);
LOGGER.info("Sentry logging started!");
} catch (Exception e) {
LOGGER.warn("Failed to initialize sentry!", e);
initialized = false;
}
}
}

View File

@@ -1,74 +0,0 @@
package gg.pufferfish.pufferfish.util;
import com.google.common.collect.Queues;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class AsyncExecutor implements Runnable {
private final Logger LOGGER = LogManager.getLogger("Leaf");
private final Queue<Runnable> jobs = Queues.newArrayDeque();
private final Lock mutex = new ReentrantLock();
private final Condition cond = mutex.newCondition();
private final Thread thread;
private volatile boolean killswitch = false;
public AsyncExecutor(String threadName) {
this.thread = new Thread(this, threadName);
}
public void start() {
thread.start();
}
public void kill() {
killswitch = true;
cond.signalAll();
}
public void submit(Runnable runnable) {
mutex.lock();
try {
jobs.offer(runnable);
cond.signalAll();
} finally {
mutex.unlock();
}
}
@Override
public void run() {
while (!killswitch) {
try {
Runnable runnable = takeRunnable();
if (runnable != null) {
runnable.run();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (Exception e) {
LOGGER.error("Failed to execute async job for thread {}", thread.getName(), e);
}
}
}
private Runnable takeRunnable() throws InterruptedException {
mutex.lock();
try {
while (jobs.isEmpty() && !killswitch) {
cond.await();
}
if (jobs.isEmpty()) return null; // We've set killswitch
return jobs.remove();
} finally {
mutex.unlock();
}
}
}

View File

@@ -1,13 +0,0 @@
package gg.pufferfish.pufferfish.util;
import java.util.Iterator;
import org.jetbrains.annotations.NotNull;
public record IterableWrapper<T>(Iterator<T> iterator) implements Iterable<T> {
@NotNull
@Override
public Iterator<T> iterator() {
return iterator;
}
}

View File

@@ -1,19 +0,0 @@
// Gale - JettPack - reduce array allocations
package me.titaniumtown;
public final class ArrayConstants {
private ArrayConstants() {}
public static final Object[] emptyObjectArray = new Object[0];
public static final short[] emptyShortArray = new short[0];
public static final int[] emptyIntArray = new int[0];
public static final int[] zeroSingletonIntArray = new int[]{0};
public static final byte[] emptyByteArray = new byte[0];
public static final String[] emptyStringArray = new String[0];
public static final long[] emptyLongArray = new long[0];
public static final org.bukkit.entity.Entity[] emptyBukkitEntityArray = new org.bukkit.entity.Entity[0];
public static final net.minecraft.world.entity.Entity[] emptyEntityArray = new net.minecraft.world.entity.Entity[0];
//public static final net.minecraft.server.level.ServerLevel[] emptyServerLevelArray = new net.minecraft.server.level.ServerLevel[0];
}

View File

@@ -1,17 +0,0 @@
package net.caffeinemc.mods.lithium.common.entity;
import net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber;
import net.minecraft.world.item.ItemStack;
public interface EquipmentEntity {
void onEquipmentReplaced(ItemStack oldStack, ItemStack newStack);
interface EquipmentTrackingEntity {
void onEquipmentChanged();
}
interface TickableEnchantmentTrackingEntity extends ChangeSubscriber.EnchantmentSubscriber<ItemStack> {
void updateHasTickableEnchantments(ItemStack oldStack, ItemStack newStack);
}
}

View File

@@ -1,18 +0,0 @@
package net.caffeinemc.mods.lithium.common.util.change_tracking;
import net.minecraft.world.item.ItemStack;
public interface ChangePublisher<T> {
void subscribe(ChangeSubscriber<T> subscriber, int subscriberData);
int unsubscribe(ChangeSubscriber<T> subscriber);
default void unsubscribeWithData(ChangeSubscriber<T> subscriber, int index) {
throw new UnsupportedOperationException("Only implemented for ItemStacks");
}
default boolean isSubscribedWithData(ChangeSubscriber<ItemStack> subscriber, int subscriberData) {
throw new UnsupportedOperationException("Only implemented for ItemStacks");
}
}

View File

@@ -1,195 +0,0 @@
package net.caffeinemc.mods.lithium.common.util.change_tracking;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import net.minecraft.world.item.ItemStack;
public interface ChangeSubscriber<T> {
static <T> ChangeSubscriber<T> combine(ChangeSubscriber<T> prevSubscriber, int prevSData, @NotNull ChangeSubscriber<T> newSubscriber, int newSData) {
if (prevSubscriber == null) {
return newSubscriber;
} else if (prevSubscriber instanceof Multi) {
ArrayList<ChangeSubscriber<T>> subscribers = new ArrayList<>(((Multi<T>) prevSubscriber).subscribers);
IntArrayList subscriberDatas = new IntArrayList(((Multi<T>) prevSubscriber).subscriberDatas);
subscribers.add(newSubscriber);
subscriberDatas.add(newSData);
return new Multi<>(subscribers, subscriberDatas);
} else {
ArrayList<ChangeSubscriber<T>> subscribers = new ArrayList<>();
IntArrayList subscriberDatas = new IntArrayList();
subscribers.add(prevSubscriber);
subscriberDatas.add(prevSData);
subscribers.add(newSubscriber);
subscriberDatas.add(newSData);
return new Multi<>(subscribers, subscriberDatas);
}
}
static <T> ChangeSubscriber<T> without(ChangeSubscriber<T> prevSubscriber, ChangeSubscriber<T> removedSubscriber) {
return without(prevSubscriber, removedSubscriber, 0, false);
}
static <T> ChangeSubscriber<T> without(ChangeSubscriber<T> prevSubscriber, ChangeSubscriber<T> removedSubscriber, int removedSubscriberData, boolean matchData) {
if (prevSubscriber == removedSubscriber) {
return null;
} else if (prevSubscriber instanceof Multi<T> multi) {
int index = multi.indexOf(removedSubscriber, removedSubscriberData, matchData);
if (index != -1) {
if (multi.subscribers.size() == 2) {
return multi.subscribers.get(1 - index);
} else {
ArrayList<ChangeSubscriber<T>> subscribers = new ArrayList<>(multi.subscribers);
IntArrayList subscriberDatas = new IntArrayList(multi.subscriberDatas);
subscribers.remove(index);
subscriberDatas.removeInt(index);
return new Multi<>(subscribers, subscriberDatas);
}
} else {
return prevSubscriber;
}
} else {
return prevSubscriber;
}
}
static <T> int dataWithout(ChangeSubscriber<T> prevSubscriber, ChangeSubscriber<T> removedSubscriber, int subscriberData) {
return dataWithout(prevSubscriber, removedSubscriber, subscriberData, 0, false);
}
static <T> int dataWithout(ChangeSubscriber<T> prevSubscriber, ChangeSubscriber<T> removedSubscriber, int subscriberData, int removedSubscriberData, boolean matchData) {
if (prevSubscriber instanceof Multi<T> multi) {
int index = multi.indexOf(removedSubscriber, removedSubscriberData, matchData);
if (index != -1) {
if (multi.subscribers.size() == 2) {
return multi.subscriberDatas.getInt(1 - index);
} else {
return subscriberData;
}
} else {
return subscriberData;
}
}
return prevSubscriber == removedSubscriber ? 0 : subscriberData;
}
static int dataOf(ChangeSubscriber<?> subscribers, ChangeSubscriber<?> subscriber, int subscriberData) {
return subscribers instanceof Multi<?> multi ? multi.subscriberDatas.getInt(multi.subscribers.indexOf(subscriber)) : subscriberData;
}
static boolean containsSubscriber(ChangeSubscriber<ItemStack> subscriber, int subscriberData, ChangeSubscriber<ItemStack> subscriber1, int subscriberData1) {
if (subscriber instanceof Multi<ItemStack> multi) {
return multi.indexOf(subscriber1, subscriberData1, true) != -1;
}
return subscriber == subscriber1 && subscriberData == subscriberData1;
}
/**
* Notify the subscriber that the publisher will be changed immediately after this call.
*
* @param publisher The publisher that is about to change
* @param subscriberData The data associated with the subscriber, given when the subscriber was added
*/
void notify(@Nullable T publisher, int subscriberData);
/**
* Notify the subscriber about being unsubscribed from the publisher. Used when the publisher becomes invalid.
* The subscriber should not attempt to unsubscribe itself from the publisher in this method.
*
* @param publisher The publisher unsubscribed from
* @param subscriberData The data associated with the subscriber, given when the subscriber was added
*/
void forceUnsubscribe(T publisher, int subscriberData);
interface CountChangeSubscriber<T> extends ChangeSubscriber<T> {
/**
* Notify the subscriber that the publisher's count data will be changed immediately after this call.
*
* @param publisher The publisher that is about to change
* @param subscriberData The data associated with the subscriber, given when the subscriber was added
* @param newCount The new count of the publisher
*/
void notifyCount(T publisher, int subscriberData, int newCount);
}
interface EnchantmentSubscriber<T> extends ChangeSubscriber<T> {
/**
* Notify the subscriber that the publisher's enchantment data has been changed immediately before this call.
*
* @param publisher The publisher that has changed
* @param subscriberData The data associated with the subscriber, given when the subscriber was added
*/
void notifyAfterEnchantmentChange(T publisher, int subscriberData);
}
class Multi<T> implements CountChangeSubscriber<T>, EnchantmentSubscriber<T> {
private final ArrayList<ChangeSubscriber<T>> subscribers;
private final IntArrayList subscriberDatas;
public Multi(ArrayList<ChangeSubscriber<T>> subscribers, IntArrayList subscriberDatas) {
this.subscribers = subscribers;
this.subscriberDatas = subscriberDatas;
}
@Override
public void notify(T publisher, int subscriberData) {
ArrayList<ChangeSubscriber<T>> changeSubscribers = this.subscribers;
for (int i = 0; i < changeSubscribers.size(); i++) {
ChangeSubscriber<T> subscriber = changeSubscribers.get(i);
subscriber.notify(publisher, this.subscriberDatas.getInt(i));
}
}
@Override
public void forceUnsubscribe(T publisher, int subscriberData) {
ArrayList<ChangeSubscriber<T>> changeSubscribers = this.subscribers;
for (int i = 0; i < changeSubscribers.size(); i++) {
ChangeSubscriber<T> subscriber = changeSubscribers.get(i);
subscriber.forceUnsubscribe(publisher, this.subscriberDatas.getInt(i));
}
}
@Override
public void notifyCount(T publisher, int subscriberData, int newCount) {
ArrayList<ChangeSubscriber<T>> changeSubscribers = this.subscribers;
for (int i = 0; i < changeSubscribers.size(); i++) {
ChangeSubscriber<T> subscriber = changeSubscribers.get(i);
if (subscriber instanceof ChangeSubscriber.CountChangeSubscriber<T> countChangeSubscriber) {
countChangeSubscriber.notifyCount(publisher, this.subscriberDatas.getInt(i), newCount);
}
}
}
int indexOf(ChangeSubscriber<T> subscriber, int subscriberData, boolean matchData) {
if (!matchData) {
return this.subscribers.indexOf(subscriber);
} else {
for (int i = 0; i < this.subscribers.size(); i++) {
if (this.subscribers.get(i) == subscriber && this.subscriberDatas.getInt(i) == subscriberData) {
return i;
}
}
return -1;
}
}
@Override
public void notifyAfterEnchantmentChange(T publisher, int subscriberData) {
ArrayList<ChangeSubscriber<T>> changeSubscribers = this.subscribers;
for (int i = 0; i < changeSubscribers.size(); i++) {
ChangeSubscriber<T> subscriber = changeSubscribers.get(i);
if (subscriber instanceof ChangeSubscriber.EnchantmentSubscriber<T> enchantmentSubscriber) {
enchantmentSubscriber.notifyAfterEnchantmentChange(publisher, this.subscriberDatas.getInt(i));
}
}
}
}
}

View File

@@ -1,209 +0,0 @@
// Gale - Lithium - faster chunk serialization
package net.caffeinemc.mods.lithium.common.world.chunk;
import it.unimi.dsi.fastutil.HashCommon;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
import net.minecraft.CrashReport;
import net.minecraft.CrashReportCategory;
import net.minecraft.ReportedException;
import net.minecraft.core.IdMap;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.VarInt;
import net.minecraft.world.level.chunk.MissingPaletteEntryException;
import net.minecraft.world.level.chunk.Palette;
import net.minecraft.world.level.chunk.PaletteResize;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import static it.unimi.dsi.fastutil.Hash.FAST_LOAD_FACTOR;
/**
* Generally provides better performance over the vanilla {@link net.minecraft.world.level.chunk.HashMapPalette} when calling
* {@link LithiumHashPalette#idFor(Object)} through using a faster backing map and reducing pointer chasing.
*/
public class LithiumHashPalette<T> implements Palette<T> {
private static final int ABSENT_VALUE = -1;
private final IdMap<T> idList;
private final PaletteResize<T> resizeHandler;
private final int indexBits;
private final Reference2IntOpenHashMap<T> table;
private T[] entries;
private int size = 0;
private LithiumHashPalette(IdMap<T> idList, PaletteResize<T> resizeHandler, int indexBits, T[] entries, Reference2IntOpenHashMap<T> table, int size) {
this.idList = idList;
this.resizeHandler = resizeHandler;
this.indexBits = indexBits;
this.entries = entries;
this.table = table;
this.size = size;
}
public LithiumHashPalette(IdMap<T> idList, int bits, PaletteResize<T> resizeHandler, List<T> list) {
this(idList, bits, resizeHandler);
for (T t : list) {
this.addEntry(t);
}
}
@SuppressWarnings("unchecked")
public LithiumHashPalette(IdMap<T> idList, int bits, PaletteResize<T> resizeHandler) {
this.idList = idList;
this.indexBits = bits;
this.resizeHandler = resizeHandler;
int capacity = 1 << bits;
this.entries = (T[]) new Object[capacity];
this.table = new Reference2IntOpenHashMap<>(capacity, FAST_LOAD_FACTOR);
this.table.defaultReturnValue(ABSENT_VALUE);
}
@Override
public int idFor(@NotNull T obj) {
int id = this.table.getInt(obj);
if (id == ABSENT_VALUE) {
id = this.computeEntry(obj);
}
return id;
}
@Override
public boolean maybeHas(@NotNull Predicate<T> predicate) {
for (int i = 0; i < this.size; ++i) {
if (predicate.test(this.entries[i])) {
return true;
}
}
return false;
}
private int computeEntry(T obj) {
int id = this.addEntry(obj);
if (id >= 1 << this.indexBits) {
if (this.resizeHandler == null) {
throw new IllegalStateException("Cannot grow");
} else {
id = this.resizeHandler.onResize(this.indexBits + 1, obj);
}
}
return id;
}
private int addEntry(T obj) {
int nextId = this.size;
if (nextId >= this.entries.length) {
this.resize(this.size);
}
this.table.put(obj, nextId);
this.entries[nextId] = obj;
this.size++;
return nextId;
}
private void resize(int neededCapacity) {
this.entries = Arrays.copyOf(this.entries, HashCommon.nextPowerOfTwo(neededCapacity + 1));
}
@Override
public @NotNull T valueFor(int id) {
T[] entries = this.entries;
T entry = null;
if (id >= 0 && id < entries.length) {
entry = entries[id];
}
if (entry != null) {
return entry;
} else {
throw this.missingPaletteEntryCrash(id);
}
}
private ReportedException missingPaletteEntryCrash(int id) {
try {
throw new MissingPaletteEntryException(id);
} catch (MissingPaletteEntryException e) {
CrashReport crashReport = CrashReport.forThrowable(e, "[Lithium] Getting Palette Entry");
CrashReportCategory crashReportCategory = crashReport.addCategory("Chunk section");
crashReportCategory.setDetail("IndexBits", this.indexBits);
crashReportCategory.setDetail("Entries", this.entries.length + " Elements: " + Arrays.toString(this.entries));
crashReportCategory.setDetail("Table", this.table.size() + " Elements: " + this.table);
return new ReportedException(crashReport);
}
}
@Override
public void read(FriendlyByteBuf buf) {
this.clear();
int entryCount = buf.readVarInt();
for (int i = 0; i < entryCount; ++i) {
this.addEntry(this.idList.byIdOrThrow(buf.readVarInt()));
}
}
@Override
public void write(FriendlyByteBuf buf) {
int size = this.size;
buf.writeVarInt(size);
for (int i = 0; i < size; ++i) {
buf.writeVarInt(this.idList.getId(this.valueFor(i)));
}
}
@Override
public int getSerializedSize() {
int size = VarInt.getByteSize(this.size);
for (int i = 0; i < this.size; ++i) {
size += VarInt.getByteSize(this.idList.getId(this.valueFor(i)));
}
return size;
}
@Override
public int getSize() {
return this.size;
}
@Override
public @NotNull Palette<T> copy(@NotNull PaletteResize<T> resizeHandler) {
return new LithiumHashPalette<>(this.idList, resizeHandler, this.indexBits, this.entries.clone(), this.table.clone(), this.size);
}
private void clear() {
Arrays.fill(this.entries, null);
this.table.clear();
this.size = 0;
}
public List<T> getElements() {
T[] copy = Arrays.copyOf(this.entries, this.size);
return Arrays.asList(copy);
}
public static <A> Palette<A> create(int bits, IdMap<A> idList, PaletteResize<A> listener, List<A> list) {
return new LithiumHashPalette<>(idList, bits, listener, list);
}
}

View File

@@ -1,17 +0,0 @@
package org.dreeam.leaf;
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();
PaperBootstrap.boot(options);
}
private static void runPreBootTasks() {
}
}

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,24 +0,0 @@
package org.dreeam.leaf.async;
import net.minecraft.Util;
import org.dreeam.leaf.config.modules.async.AsyncPlayerDataSave;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
public class AsyncPlayerDataSaving {
private AsyncPlayerDataSaving() {
}
public static void saveAsync(Runnable runnable) {
if (!AsyncPlayerDataSave.enabled) {
runnable.run();
return;
}
ExecutorService ioExecutor = Util.backgroundExecutor().service();
CompletableFuture.runAsync(runnable, ioExecutor);
}
}

View File

@@ -1,167 +0,0 @@
package org.dreeam.leaf.async.locate;
import ca.spottedleaf.moonrise.common.util.TickThread;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.mojang.datafixers.util.Pair;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderSet;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.tags.TagKey;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.levelgen.structure.Structure;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
// Original project: https://github.com/thebrightspark/AsyncLocator
public class AsyncLocator {
private static final ExecutorService LOCATING_EXECUTOR_SERVICE;
private AsyncLocator() {
}
public static class AsyncLocatorThread extends TickThread {
private static final AtomicInteger THREAD_COUNTER = new AtomicInteger(0);
public AsyncLocatorThread(Runnable run, String name) {
super(run, name, THREAD_COUNTER.incrementAndGet());
}
@Override
public void run() {
super.run();
}
}
static {
int threads = org.dreeam.leaf.config.modules.async.AsyncLocator.asyncLocatorThreads;
LOCATING_EXECUTOR_SERVICE = new ThreadPoolExecutor(
1,
threads,
org.dreeam.leaf.config.modules.async.AsyncLocator.asyncLocatorKeepalive,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),
new ThreadFactoryBuilder()
.setThreadFactory(
r -> new AsyncLocatorThread(r, "Leaf Async Locator Thread") {
@Override
public void run() {
r.run();
}
}
)
.setNameFormat("Leaf Async Locator Thread - %d")
.setPriority(Thread.NORM_PRIORITY - 2)
.build()
);
}
public static void shutdownExecutorService() {
if (LOCATING_EXECUTOR_SERVICE != null) {
LOCATING_EXECUTOR_SERVICE.shutdown();
}
}
/**
* Queues a task to locate a feature using {@link ServerLevel#findNearestMapStructure(TagKey, BlockPos, int, boolean)}
* and returns a {@link LocateTask} with the futures for it.
*/
public static LocateTask<BlockPos> locate(
ServerLevel level,
TagKey<Structure> structureTag,
BlockPos pos,
int searchRadius,
boolean skipKnownStructures
) {
CompletableFuture<BlockPos> completableFuture = new CompletableFuture<>();
Future<?> future = LOCATING_EXECUTOR_SERVICE.submit(
() -> doLocateLevel(completableFuture, level, structureTag, pos, searchRadius, skipKnownStructures)
);
return new LocateTask<>(level.getServer(), completableFuture, future);
}
/**
* Queues a task to locate a feature using
* {@link ChunkGenerator#findNearestMapStructure(ServerLevel, HolderSet, BlockPos, int, boolean)} and returns a
* {@link LocateTask} with the futures for it.
*/
public static LocateTask<Pair<BlockPos, Holder<Structure>>> locate(
ServerLevel level,
HolderSet<Structure> structureSet,
BlockPos pos,
int searchRadius,
boolean skipKnownStructures
) {
CompletableFuture<Pair<BlockPos, Holder<Structure>>> completableFuture = new CompletableFuture<>();
Future<?> future = LOCATING_EXECUTOR_SERVICE.submit(
() -> doLocateChunkGenerator(completableFuture, level, structureSet, pos, searchRadius, skipKnownStructures)
);
return new LocateTask<>(level.getServer(), completableFuture, future);
}
private static void doLocateLevel(
CompletableFuture<BlockPos> completableFuture,
ServerLevel level,
TagKey<Structure> structureTag,
BlockPos pos,
int searchRadius,
boolean skipExistingChunks
) {
BlockPos foundPos = level.findNearestMapStructure(structureTag, pos, searchRadius, skipExistingChunks);
completableFuture.complete(foundPos);
}
private static void doLocateChunkGenerator(
CompletableFuture<Pair<BlockPos, Holder<Structure>>> completableFuture,
ServerLevel level,
HolderSet<Structure> structureSet,
BlockPos pos,
int searchRadius,
boolean skipExistingChunks
) {
Pair<BlockPos, Holder<Structure>> foundPair = level.getChunkSource().getGenerator()
.findNearestMapStructure(level, structureSet, pos, searchRadius, skipExistingChunks);
completableFuture.complete(foundPair);
}
/**
* Holder of the futures for an async locate task as well as providing some helper functions.
* The completableFuture will be completed once the call to
* {@link ServerLevel#findNearestMapStructure(TagKey, BlockPos, int, boolean)} has completed, and will hold the
* result of it.
* The taskFuture is the future for the {@link Runnable} itself in the executor service.
*/
public record LocateTask<T>(MinecraftServer server, CompletableFuture<T> completableFuture, Future<?> taskFuture) {
/**
* Helper function that calls {@link CompletableFuture#thenAccept(Consumer)} with the given action.
* Bear in mind that the action will be executed from the task's thread. If you intend to change any game data,
* it's strongly advised you use {@link #thenOnServerThread(Consumer)} instead so that it's queued and executed
* on the main server thread instead.
*/
public LocateTask<T> then(Consumer<T> action) {
completableFuture.thenAccept(action);
return this;
}
/**
* Helper function that calls {@link CompletableFuture#thenAccept(Consumer)} with the given action on the server
* thread.
*/
public LocateTask<T> thenOnServerThread(Consumer<T> action) {
completableFuture.thenAccept(pos -> server.scheduleOnMain(() -> action.accept(pos)));
return this;
}
/**
* Helper function that cancels both completableFuture and taskFuture.
*/
public void cancel() {
taskFuture.cancel(true);
completableFuture.cancel(false);
}
}
}

View File

@@ -1,288 +0,0 @@
package org.dreeam.leaf.async.path;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.pathfinder.Node;
import net.minecraft.world.level.pathfinder.Path;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
/**
* i'll be using this to represent a path that not be processed yet!
*/
public class AsyncPath extends Path {
/**
* marks whether this async path has been processed
*/
private volatile PathProcessState processState = PathProcessState.WAITING;
/**
* runnables waiting for this to be processed
*/
private final List<Runnable> postProcessing = new ArrayList<>(0);
/**
* a list of positions that this path could path towards
*/
private final Set<BlockPos> positions;
/**
* the supplier of the real processed path
*/
private final Supplier<Path> pathSupplier;
/*
* Processed values
*/
/**
* this is a reference to the nodes list in the parent `Path` object
*/
private final List<Node> nodes;
/**
* the block we're trying to path to
* <p>
* while processing, we have no idea where this is so consumers of `Path` should check that the path is processed before checking the target block
*/
private @Nullable BlockPos target;
/**
* how far we are to the target
* <p>
* while processing, the target could be anywhere but theoretically we're always "close" to a theoretical target so default is 0
*/
private float distToTarget = 0;
/**
* whether we can reach the target
* <p>
* while processing, we can always theoretically reach the target so default is true
*/
private boolean canReach = true;
public AsyncPath(@NotNull List<Node> emptyNodeList, @NotNull Set<BlockPos> positions, @NotNull Supplier<Path> pathSupplier) {
//noinspection ConstantConditions
super(emptyNodeList, null, false);
this.nodes = emptyNodeList;
this.positions = positions;
this.pathSupplier = pathSupplier;
AsyncPathProcessor.queue(this);
}
@Override
public boolean isProcessed() {
return this.processState == PathProcessState.COMPLETED;
}
/**
* returns the future representing the processing state of this path
*/
public synchronized void postProcessing(@NotNull Runnable runnable) {
if (isProcessed()) {
runnable.run();
} else {
this.postProcessing.add(runnable);
}
}
/**
* an easy way to check if this processing path is the same as an attempted new path
*
* @param positions - the positions to compare against
* @return true if we are processing the same positions
*/
public boolean hasSameProcessingPositions(final Set<BlockPos> positions) {
if (this.positions.size() != positions.size()) {
return false;
}
return this.positions.containsAll(positions);
}
/**
* starts processing this path
*/
public synchronized void process() {
if (this.processState == PathProcessState.COMPLETED ||
this.processState == PathProcessState.PROCESSING) {
return;
}
processState = PathProcessState.PROCESSING;
final Path bestPath = this.pathSupplier.get();
this.nodes.addAll(bestPath.nodes); // we mutate this list to reuse the logic in Path
this.target = bestPath.getTarget();
this.distToTarget = bestPath.getDistToTarget();
this.canReach = bestPath.canReach();
processState = PathProcessState.COMPLETED;
for (Runnable runnable : this.postProcessing) {
runnable.run();
} // Run tasks after processing
}
/**
* if this path is accessed while it hasn't processed, just process it in-place
*/
private void checkProcessed() {
if (this.processState == PathProcessState.WAITING ||
this.processState == PathProcessState.PROCESSING) { // Block if we are on processing
this.process();
}
}
/*
* overrides we need for final fields that we cannot modify after processing
*/
@Override
public @NotNull BlockPos getTarget() {
this.checkProcessed();
return this.target;
}
@Override
public float getDistToTarget() {
this.checkProcessed();
return this.distToTarget;
}
@Override
public boolean canReach() {
this.checkProcessed();
return this.canReach;
}
/*
* overrides to ensure we're processed first
*/
@Override
public boolean isDone() {
return this.processState == PathProcessState.COMPLETED && super.isDone();
}
@Override
public void advance() {
this.checkProcessed();
super.advance();
}
@Override
public boolean notStarted() {
this.checkProcessed();
return super.notStarted();
}
@Nullable
@Override
public Node getEndNode() {
this.checkProcessed();
return super.getEndNode();
}
@Override
public Node getNode(int index) {
this.checkProcessed();
return super.getNode(index);
}
@Override
public void truncateNodes(int length) {
this.checkProcessed();
super.truncateNodes(length);
}
@Override
public void replaceNode(int index, Node node) {
this.checkProcessed();
super.replaceNode(index, node);
}
@Override
public int getNodeCount() {
this.checkProcessed();
return super.getNodeCount();
}
@Override
public int getNextNodeIndex() {
this.checkProcessed();
return super.getNextNodeIndex();
}
@Override
public void setNextNodeIndex(int nodeIndex) {
this.checkProcessed();
super.setNextNodeIndex(nodeIndex);
}
@Override
public Vec3 getEntityPosAtNode(Entity entity, int index) {
this.checkProcessed();
return super.getEntityPosAtNode(entity, index);
}
@Override
public BlockPos getNodePos(int index) {
this.checkProcessed();
return super.getNodePos(index);
}
@Override
public Vec3 getNextEntityPos(Entity entity) {
this.checkProcessed();
return super.getNextEntityPos(entity);
}
@Override
public BlockPos getNextNodePos() {
this.checkProcessed();
return super.getNextNodePos();
}
@Override
public Node getNextNode() {
this.checkProcessed();
return super.getNextNode();
}
@Nullable
@Override
public Node getPreviousNode() {
this.checkProcessed();
return super.getPreviousNode();
}
public PathProcessState getProcessState() {
return processState;
}
}

View File

@@ -1,100 +0,0 @@
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;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.function.Consumer;
/**
* used to handle the scheduling of async path processing
*/
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(
1,
org.dreeam.leaf.config.modules.async.AsyncPathfinding.asyncPathfindingMaxThreads,
org.dreeam.leaf.config.modules.async.AsyncPathfinding.asyncPathfindingKeepalive, TimeUnit.SECONDS,
getQueueImpl(),
new ThreadFactoryBuilder()
.setNameFormat(THREAD_PREFIX + " Thread - %d")
.setPriority(Thread.NORM_PRIORITY - 2)
.build(),
new RejectedTaskHandler()
);
private static class RejectedTaskHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable rejectedTask, ThreadPoolExecutor executor) {
BlockingQueue<Runnable> workQueue = executor.getQueue();
if (!executor.isShutdown()) {
switch (org.dreeam.leaf.config.modules.async.AsyncPathfinding.asyncPathfindingRejectPolicy) {
case FLUSH_ALL -> {
if (!workQueue.isEmpty()) {
List<Runnable> pendingTasks = new ArrayList<>(workQueue.size());
workQueue.drainTo(pendingTasks);
for (Runnable pendingTask : pendingTasks) {
pendingTask.run();
}
}
rejectedTask.run();
}
case CALLER_RUNS -> rejectedTask.run();
}
}
if (System.currentTimeMillis() - lastWarnMillis > 30000L) {
LOGGER.warn("Async pathfinding processor is busy! Pathfinding tasks will be treated as policy defined in config. Increasing max-threads in Leaf config may help.");
lastWarnMillis = System.currentTimeMillis();
}
}
}
protected static CompletableFuture<Void> queue(@NotNull AsyncPath path) {
return CompletableFuture.runAsync(path::process, pathProcessingExecutor)
.orTimeout(60L, TimeUnit.SECONDS)
.exceptionally(throwable -> {
if (throwable instanceof TimeoutException e) {
LOGGER.warn("Async Pathfinding process timed out", e);
} else LOGGER.warn("Error occurred while processing async path", throwable);
return null;
});
}
/**
* takes a possibly unprocessed path, and waits until it is completed
* the consumer will be immediately invoked if the path is already processed
* the consumer will always be called on the main thread
*
* @param path a path to wait on
* @param afterProcessing a consumer to be called
*/
public static void awaitProcessing(@Nullable Path path, Consumer<@Nullable Path> afterProcessing) {
if (path != null && !path.isProcessed() && path instanceof AsyncPath asyncPath) {
asyncPath.postProcessing(() ->
MinecraftServer.getServer().scheduleOnMain(() -> afterProcessing.accept(path))
);
} else {
afterProcessing.accept(path);
}
}
private static BlockingQueue<Runnable> getQueueImpl() {
final int queueCapacity = org.dreeam.leaf.config.modules.async.AsyncPathfinding.asyncPathfindingQueueSize;
return new LinkedBlockingQueue<>(queueCapacity);
}}

View File

@@ -1,46 +0,0 @@
package org.dreeam.leaf.async.path;
import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue;
import net.minecraft.world.level.pathfinder.NodeEvaluator;
import org.apache.commons.lang.Validate;
import org.jetbrains.annotations.NotNull;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
public class NodeEvaluatorCache {
private static final Map<NodeEvaluatorFeatures, MultiThreadedQueue<NodeEvaluator>> threadLocalNodeEvaluators = new ConcurrentHashMap<>();
private static final Map<NodeEvaluator, NodeEvaluatorGenerator> nodeEvaluatorToGenerator = new ConcurrentHashMap<>();
private static @NotNull Queue<NodeEvaluator> getQueueForFeatures(@NotNull NodeEvaluatorFeatures nodeEvaluatorFeatures) {
return threadLocalNodeEvaluators.computeIfAbsent(nodeEvaluatorFeatures, key -> new MultiThreadedQueue<>());
}
public static @NotNull NodeEvaluator takeNodeEvaluator(@NotNull NodeEvaluatorGenerator generator, @NotNull NodeEvaluator localNodeEvaluator) {
final NodeEvaluatorFeatures nodeEvaluatorFeatures = NodeEvaluatorFeatures.fromNodeEvaluator(localNodeEvaluator);
NodeEvaluator nodeEvaluator = getQueueForFeatures(nodeEvaluatorFeatures).poll();
if (nodeEvaluator == null) {
nodeEvaluator = generator.generate(nodeEvaluatorFeatures);
}
nodeEvaluatorToGenerator.put(nodeEvaluator, generator);
return nodeEvaluator;
}
public static void returnNodeEvaluator(@NotNull NodeEvaluator nodeEvaluator) {
final NodeEvaluatorGenerator generator = nodeEvaluatorToGenerator.remove(nodeEvaluator);
Validate.notNull(generator, "NodeEvaluator already returned");
final NodeEvaluatorFeatures nodeEvaluatorFeatures = NodeEvaluatorFeatures.fromNodeEvaluator(nodeEvaluator);
getQueueForFeatures(nodeEvaluatorFeatures).offer(nodeEvaluator);
}
public static void removeNodeEvaluator(@NotNull NodeEvaluator nodeEvaluator) {
nodeEvaluatorToGenerator.remove(nodeEvaluator);
}
}

View File

@@ -1,23 +0,0 @@
package org.dreeam.leaf.async.path;
import net.minecraft.world.level.pathfinder.NodeEvaluator;
import net.minecraft.world.level.pathfinder.SwimNodeEvaluator;
public record NodeEvaluatorFeatures(
NodeEvaluatorType type,
boolean canPassDoors,
boolean canFloat,
boolean canWalkOverFences,
boolean canOpenDoors,
boolean allowBreaching
) {
public static NodeEvaluatorFeatures fromNodeEvaluator(NodeEvaluator nodeEvaluator) {
NodeEvaluatorType type = NodeEvaluatorType.fromNodeEvaluator(nodeEvaluator);
boolean canPassDoors = nodeEvaluator.canPassDoors();
boolean canFloat = nodeEvaluator.canFloat();
boolean canWalkOverFences = nodeEvaluator.canWalkOverFences();
boolean canOpenDoors = nodeEvaluator.canOpenDoors();
boolean allowBreaching = nodeEvaluator instanceof SwimNodeEvaluator swimNodeEvaluator && swimNodeEvaluator.allowBreaching;
return new NodeEvaluatorFeatures(type, canPassDoors, canFloat, canWalkOverFences, canOpenDoors, allowBreaching);
}
}

View File

@@ -1,8 +0,0 @@
package org.dreeam.leaf.async.path;
import net.minecraft.world.level.pathfinder.NodeEvaluator;
import org.jetbrains.annotations.NotNull;
public interface NodeEvaluatorGenerator {
@NotNull NodeEvaluator generate(NodeEvaluatorFeatures nodeEvaluatorFeatures);
}

View File

@@ -1,17 +0,0 @@
package org.dreeam.leaf.async.path;
import net.minecraft.world.level.pathfinder.*;
public enum NodeEvaluatorType {
WALK,
SWIM,
AMPHIBIOUS,
FLY;
public static NodeEvaluatorType fromNodeEvaluator(NodeEvaluator nodeEvaluator) {
if (nodeEvaluator instanceof SwimNodeEvaluator) return SWIM;
if (nodeEvaluator instanceof FlyNodeEvaluator) return FLY;
if (nodeEvaluator instanceof AmphibiousNodeEvaluator) return AMPHIBIOUS;
return WALK;
}
}

View File

@@ -1,7 +0,0 @@
package org.dreeam.leaf.async.path;
public enum PathProcessState {
WAITING,
PROCESSING,
COMPLETED,
}

View File

@@ -1,19 +0,0 @@
package org.dreeam.leaf.async.path;
import org.dreeam.leaf.config.LeafConfig;
import java.util.Locale;
public enum PathfindTaskRejectPolicy {
FLUSH_ALL,
CALLER_RUNS;
public static PathfindTaskRejectPolicy fromString(String policy) {
try {
return PathfindTaskRejectPolicy.valueOf(policy.toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException e) {
LeafConfig.LOGGER.warn("Invalid pathfind task reject policy: {}, falling back to {}.", policy, FLUSH_ALL.toString());
return FLUSH_ALL;
}
}
}

View File

@@ -1,184 +0,0 @@
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 net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.FullChunkStatus;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
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 MultithreadedTracker() {
}
public static Executor getTrackerExecutor() {
return trackerExecutor;
}
public static void tick(ChunkSystemServerLevel level) {
try {
if (!org.dreeam.leaf.config.modules.async.MultithreadedTracker.compatModeEnabled) {
tickAsync(level);
} else {
tickAsyncWithCompatMode(level);
}
} catch (Exception e) {
LOGGER.error("Error occurred while executing async task.", e);
}
}
private static void tickAsync(ChunkSystemServerLevel level) {
final NearbyPlayers nearbyPlayers = level.moonrise$getNearbyPlayers();
final ServerEntityLookup entityLookup = (ServerEntityLookup) level.moonrise$getEntityLookup();
final ReferenceList<Entity> trackerEntities = entityLookup.trackerEntities;
final Entity[] trackerEntitiesRaw = trackerEntities.getRawDataUnchecked();
// Move tracking to off-main
trackerExecutor.execute(() -> {
for (final Entity entity : trackerEntitiesRaw) {
if (entity == null) continue;
final ChunkMap.TrackedEntity tracker = ((EntityTrackerEntity) entity).moonrise$getTrackedEntity();
if (tracker == null) continue;
tracker.moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition()));
tracker.serverEntity.sendChanges();
}
});
}
private static void tickAsyncWithCompatMode(ChunkSystemServerLevel 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];
int index = 0;
for (final Entity entity : trackerEntitiesRaw) {
if (entity == null) continue;
final ChunkMap.TrackedEntity tracker = ((EntityTrackerEntity) entity).moonrise$getTrackedEntity();
if (tracker == null) continue;
tracker.moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition()));
sendChangesTasks[index++] = () -> tracker.serverEntity.sendChanges(); // Collect send changes to task array
}
// batch submit tasks
trackerExecutor.execute(() -> {
for (final Runnable sendChanges : sendChangesTasks) {
if (sendChanges == null) continue;
sendChanges.run();
}
});
}
// 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 ca.spottedleaf.moonrise.common.list.ReferenceList<net.minecraft.world.entity.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();
if (tracker == null) {
continue;
}
((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity) tracker).moonrise$tick(((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity) entity).moonrise$getChunkData().nearbyPlayers);
if (((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity) tracker).moonrise$hasPlayers()
|| ((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity) entity).moonrise$getChunkStatus().isOrAfter(FullChunkStatus.ENTITY_TICKING)) {
tracker.serverEntity.sendChanges();
}
}
}
private static int getCorePoolSize() {
return 1;
}
private static int getMaxPoolSize() {
return org.dreeam.leaf.config.modules.async.MultithreadedTracker.asyncEntityTrackerMaxThreads;
}
private static long getKeepAliveTime() {
return org.dreeam.leaf.config.modules.async.MultithreadedTracker.asyncEntityTrackerKeepalive;
}
private static BlockingQueue<Runnable> getQueueImpl() {
final int queueCapacity = org.dreeam.leaf.config.modules.async.MultithreadedTracker.asyncEntityTrackerQueueSize;
return new LinkedBlockingQueue<>(queueCapacity);
}
private static @NotNull ThreadFactory getThreadFactory() {
return new ThreadFactoryBuilder()
.setThreadFactory(MultithreadedTrackerThread::new)
.setNameFormat(THREAD_PREFIX + " Thread - %d")
.setPriority(Thread.NORM_PRIORITY - 2)
.build();
}
private static @NotNull RejectedExecutionHandler getRejectedPolicy() {
return (rejectedTask, executor) -> {
BlockingQueue<Runnable> workQueue = executor.getQueue();
if (!executor.isShutdown()) {
if (!workQueue.isEmpty()) {
List<Runnable> pendingTasks = new ArrayList<>(workQueue.size());
workQueue.drainTo(pendingTasks);
for (Runnable pendingTask : pendingTasks) {
pendingTask.run();
}
}
rejectedTask.run();
}
if (System.currentTimeMillis() - lastWarnMillis > 30000L) {
LOGGER.warn("Async entity tracker is busy! Tracking tasks will be done in the server thread. Increasing max-threads in Leaf config may help.");
lastWarnMillis = System.currentTimeMillis();
}
};
}
public static class MultithreadedTrackerThread extends Thread {
public MultithreadedTrackerThread(Runnable runnable) {
super(runnable);
}
}
}

View File

@@ -1,30 +0,0 @@
package org.dreeam.leaf.async.world;
import ca.spottedleaf.moonrise.common.util.TickThread;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.ThreadFactory;
public class SparklyPaperServerLevelTickExecutorThreadFactory implements ThreadFactory {
private final String worldName;
public SparklyPaperServerLevelTickExecutorThreadFactory(final String worldName) {
this.worldName = worldName;
}
@Override
public Thread newThread(@NotNull Runnable runnable) {
TickThread.ServerLevelTickThread tickThread = new TickThread.ServerLevelTickThread(runnable, "Leaf World Ticking Thread - " + this.worldName);
if (tickThread.isDaemon()) {
tickThread.setDaemon(false);
}
if (tickThread.getPriority() != 5) {
tickThread.setPriority(5);
}
return tickThread;
}
}

View File

@@ -1,177 +0,0 @@
package org.dreeam.leaf.command;
import io.papermc.paper.command.CommandUtil;
import it.unimi.dsi.fastutil.Pair;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.minecraft.Util;
import org.dreeam.leaf.command.subcommands.MSPTCommand;
import org.dreeam.leaf.command.subcommands.ReloadCommand;
import org.dreeam.leaf.command.subcommands.VersionCommand;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
import org.bukkit.plugin.PluginManager;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
public final class LeafCommand extends Command {
public static final String COMMAND_LABEL = "leaf";
public static final String BASE_PERM = LeafCommands.COMMAND_BASE_PERM + "." + COMMAND_LABEL;
private static final Permission basePermission = new Permission(BASE_PERM, PermissionDefault.OP);
// subcommand label -> subcommand
private static final LeafSubcommand RELOAD_SUBCOMMAND = new ReloadCommand();
private static final LeafSubcommand VERSION_SUBCOMMAND = new VersionCommand();
private static final LeafSubcommand MSPT_SUBCOMMAND = new MSPTCommand();
private static final Map<String, LeafSubcommand> SUBCOMMANDS = Util.make(() -> {
final Map<Set<String>, LeafSubcommand> commands = new HashMap<>();
commands.put(Set.of(ReloadCommand.LITERAL_ARGUMENT), RELOAD_SUBCOMMAND);
commands.put(Set.of(VersionCommand.LITERAL_ARGUMENT), VERSION_SUBCOMMAND);
commands.put(Set.of(MSPTCommand.LITERAL_ARGUMENT), MSPT_SUBCOMMAND);
return commands.entrySet().stream()
.flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue())))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
});
// alias -> subcommand label
private static final Map<String, String> ALIASES = Util.make(() -> {
final Map<String, Set<String>> aliases = new HashMap<>();
aliases.put(VersionCommand.LITERAL_ARGUMENT, Set.of("ver"));
return aliases.entrySet().stream()
.flatMap(entry -> entry.getValue().stream().map(s -> Map.entry(s, entry.getKey())))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
});
private String createUsageMessage(Collection<String> arguments) {
return "/" + COMMAND_LABEL + " [" + String.join(" | ", arguments) + "]";
}
public LeafCommand() {
super(COMMAND_LABEL);
this.description = "Leaf related commands";
this.usageMessage = this.createUsageMessage(SUBCOMMANDS.keySet());
final List<Permission> permissions = SUBCOMMANDS.values().stream().map(LeafSubcommand::getPermission).filter(Objects::nonNull).toList();
this.setPermission(BASE_PERM);
final PluginManager pluginManager = Bukkit.getServer().getPluginManager();
pluginManager.addPermission(basePermission);
for (final Permission permission : permissions) {
pluginManager.addPermission(permission);
}
}
@Override
public @NotNull List<String> tabComplete(
final @NotNull CommandSender sender,
final @NotNull String alias,
final String[] args,
final @Nullable Location location
) throws IllegalArgumentException {
if (args.length <= 1) {
List<String> subCommandArguments = new ArrayList<>(SUBCOMMANDS.size());
for (Map.Entry<String, LeafSubcommand> subCommandEntry : SUBCOMMANDS.entrySet()) {
if (subCommandEntry.getValue().testPermission(sender)) {
subCommandArguments.add(subCommandEntry.getKey());
}
}
return CommandUtil.getListMatchingLast(sender, args, subCommandArguments);
}
final @Nullable Pair<String, LeafSubcommand> subCommand = resolveCommand(args[0]);
if (subCommand != null && subCommand.second().testPermission(sender)) {
return subCommand.second().tabComplete(sender, subCommand.first(), Arrays.copyOfRange(args, 1, args.length));
}
return Collections.emptyList();
}
private boolean testHasOnePermission(CommandSender sender) {
for (Map.Entry<String, LeafSubcommand> subCommandEntry : SUBCOMMANDS.entrySet()) {
if (subCommandEntry.getValue().testPermission(sender)) {
return true;
}
}
return false;
}
@Override
public boolean execute(
final CommandSender sender,
final @NotNull String commandLabel,
final String[] args
) {
// Check if the sender has the base permission and at least one specific permission
if (!sender.hasPermission(basePermission) || !this.testHasOnePermission(sender)) {
sender.sendMessage(Bukkit.permissionMessage());
return true;
}
// Determine the usage message with the subcommands they can perform
List<String> subCommandArguments = new ArrayList<>(SUBCOMMANDS.size());
for (Map.Entry<String, LeafSubcommand> subCommandEntry : SUBCOMMANDS.entrySet()) {
if (subCommandEntry.getValue().testPermission(sender)) {
subCommandArguments.add(subCommandEntry.getKey());
}
}
String specificUsageMessage = this.createUsageMessage(subCommandArguments);
// If they did not give a subcommand
if (args.length == 0) {
sender.sendMessage(Component.text("Command usage: " + specificUsageMessage, NamedTextColor.GRAY));
return false;
}
// If they do not have permission for the subcommand they gave, or the argument is not a valid subcommand
final @Nullable Pair<String, LeafSubcommand> subCommand = resolveCommand(args[0]);
if (subCommand == null || !subCommand.second().testPermission(sender)) {
sender.sendMessage(Component.text("Usage: " + specificUsageMessage, NamedTextColor.RED));
return false;
}
// Execute the subcommand
final String[] choppedArgs = Arrays.copyOfRange(args, 1, args.length);
return subCommand.second().execute(sender, subCommand.first(), choppedArgs);
}
private static @Nullable Pair<String, LeafSubcommand> resolveCommand(String label) {
label = label.toLowerCase(Locale.ENGLISH);
@Nullable LeafSubcommand subCommand = SUBCOMMANDS.get(label);
if (subCommand == null) {
final @Nullable String command = ALIASES.get(label);
if (command != null) {
label = command;
subCommand = SUBCOMMANDS.get(command);
}
}
if (subCommand != null) {
return Pair.of(label, subCommand);
}
return null;
}
}

View File

@@ -1,29 +0,0 @@
package org.dreeam.leaf.command;
import net.minecraft.server.MinecraftServer;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.framework.qual.DefaultQualifier;
import org.bukkit.command.Command;
import org.bukkit.craftbukkit.util.permissions.CraftDefaultPermissions;
import java.util.HashMap;
import java.util.Map;
@DefaultQualifier(NonNull.class)
public final class LeafCommands {
public static final String COMMAND_BASE_PERM = CraftDefaultPermissions.LEAF_ROOT + ".command";
private LeafCommands() {
}
private static final Map<String, Command> COMMANDS = new HashMap<>();
static {
COMMANDS.put(LeafCommand.COMMAND_LABEL, new LeafCommand());
}
public static void registerCommands(final MinecraftServer server) {
COMMANDS.forEach((s, command) -> server.server.getCommandMap().register(s, "Leaf", command));
}
}

View File

@@ -1,24 +0,0 @@
package org.dreeam.leaf.command;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.framework.qual.DefaultQualifier;
import org.jetbrains.annotations.Nullable;
import org.bukkit.command.CommandSender;
import org.bukkit.permissions.Permission;
import java.util.Collections;
import java.util.List;
@DefaultQualifier(NonNull.class)
public interface LeafSubcommand {
boolean execute(CommandSender sender, String subCommand, String[] args);
default List<String> tabComplete(final CommandSender sender, final String subCommand, final String[] args) {
return Collections.emptyList();
}
boolean testPermission(CommandSender sender);
@Nullable Permission getPermission();
}

View File

@@ -1,30 +0,0 @@
package org.dreeam.leaf.command;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.bukkit.command.CommandSender;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
public abstract class PermissionedLeafSubcommand implements LeafSubcommand {
public final Permission permission;
protected PermissionedLeafSubcommand(Permission permission) {
this.permission = permission;
}
protected PermissionedLeafSubcommand(String permission, PermissionDefault permissionDefault) {
this(new Permission(permission, permissionDefault));
}
@Override
public boolean testPermission(@NotNull CommandSender sender) {
return sender.hasPermission(this.permission);
}
@Override
public @Nullable Permission getPermission() {
return this.permission;
}
}

View File

@@ -1,244 +0,0 @@
package org.dreeam.leaf.command.subcommands;
import net.kyori.adventure.text.Component;
import net.minecraft.server.MinecraftServer;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.framework.qual.DefaultQualifier;
import org.dreeam.leaf.command.LeafCommand;
import org.dreeam.leaf.command.PermissionedLeafSubcommand;
import org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking;
import org.bukkit.command.CommandSender;
import org.bukkit.permissions.PermissionDefault;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.format.NamedTextColor.*;
@DefaultQualifier(NonNull.class)
public final class MSPTCommand extends PermissionedLeafSubcommand {
public static final String LITERAL_ARGUMENT = "mspt";
public static final String PERM = LeafCommand.BASE_PERM + "." + LITERAL_ARGUMENT;
private static final DecimalFormat DF = new DecimalFormat("########0.0");
private static final Component SLASH = text("/");
public MSPTCommand() {
super(PERM, PermissionDefault.TRUE);
}
@Override
public boolean execute(final CommandSender sender, final String subCommand, final String[] args) {
// Check if parallel world ticking is enabled
if (!SparklyPaperParallelWorldTicking.enabled) {
sender.sendMessage(Component.text()
.content("Per-world MSPT tracking is only available when parallel world ticking is enabled.")
.color(RED)
.build());
sender.sendMessage(Component.text()
.content("Please enable it in your Leaf configuration to use this command.")
.color(GRAY)
.build());
return true;
}
// Check if compact mode is requested
boolean compactMode = args.length > 0 && args[0].equalsIgnoreCase("compact");
MinecraftServer server = MinecraftServer.getServer();
if (compactMode) {
displayCompactStats(sender, server);
} else {
// Display header
sender.sendMessage(Component.text()
.content("━━━━━━━━━━━━━ ")
.color(GOLD)
.append(Component.text("MSPT Statistics").color(YELLOW))
.append(Component.text(" ━━━━━━━━━━━━━").color(GOLD))
.build());
// Overall server MSPT
displayServerMSPT(sender, server);
// Add separator
sender.sendMessage(Component.text(""));
// World-specific MSPT
displayWorldMSPT(sender, server);
}
return true;
}
private void displayCompactStats(CommandSender sender, MinecraftServer server) {
// Get server stats (only 5s data with avg/min/max)
List<Component> serverTimes = eval(server.tickTimes5s.getTimes());
// Display server stats in compact form
sender.sendMessage(Component.text()
.content("Server: ")
.color(GOLD)
.append(serverTimes.get(0)).append(SLASH).append(serverTimes.get(1)).append(SLASH).append(serverTimes.get(2))
.build());
// Display world stats in compact form
for (net.minecraft.server.level.ServerLevel serverLevel : server.getAllLevels()) {
List<Component> worldTimes = eval(serverLevel.tickTimes5s.getTimes());
sender.sendMessage(Component.text()
.content(serverLevel.getWorld().getName() + ": ")
.color(GOLD)
.append(worldTimes.get(0)).append(SLASH).append(worldTimes.get(1)).append(SLASH).append(worldTimes.get(2))
.build());
}
}
private void displayServerMSPT(CommandSender sender, MinecraftServer server) {
List<Component> times = new ArrayList<>();
times.addAll(eval(server.tickTimes5s.getTimes()));
times.addAll(eval(server.tickTimes10s.getTimes()));
times.addAll(eval(server.tickTimes60s.getTimes()));
sender.sendMessage(Component.text()
.content("Server tick times ")
.color(GOLD)
.append(Component.text()
.content("(avg/min/max)")
.color(YELLOW)
)
.build());
sender.sendMessage(Component.text()
.content(" 5s: ")
.color(GOLD)
.append(times.get(0)).append(SLASH).append(times.get(1)).append(SLASH).append(times.get(2))
.build());
sender.sendMessage(Component.text()
.content(" 10s: ")
.color(GOLD)
.append(times.get(3)).append(SLASH).append(times.get(4)).append(SLASH).append(times.get(5))
.build());
sender.sendMessage(Component.text()
.content(" 60s: ")
.color(GOLD)
.append(times.get(6)).append(SLASH).append(times.get(7)).append(SLASH).append(times.get(8))
.build());
}
private void displayWorldMSPT(CommandSender sender, MinecraftServer server) {
sender.sendMessage(Component.text()
.content("World-specific tick times ")
.color(GOLD)
.append(Component.text()
.content("(avg/min/max)")
.color(YELLOW)
)
.build());
for (net.minecraft.server.level.ServerLevel serverLevel : server.getAllLevels()) {
List<Component> worldTimes = new ArrayList<>();
worldTimes.addAll(eval(serverLevel.tickTimes5s.getTimes()));
worldTimes.addAll(eval(serverLevel.tickTimes10s.getTimes()));
worldTimes.addAll(eval(serverLevel.tickTimes60s.getTimes()));
// World name header
sender.sendMessage(Component.text()
.content("")
.color(YELLOW)
.append(Component.text(serverLevel.getWorld().getName()).color(GOLD))
.build());
// Display time periods
sender.sendMessage(Component.text()
.content(" 5s: ")
.color(GRAY)
.append(worldTimes.get(0)).append(SLASH).append(worldTimes.get(1)).append(SLASH).append(worldTimes.get(2))
.build());
sender.sendMessage(Component.text()
.content(" 10s: ")
.color(GRAY)
.append(worldTimes.get(3)).append(SLASH).append(worldTimes.get(4)).append(SLASH).append(worldTimes.get(5))
.build());
sender.sendMessage(Component.text()
.content(" 60s: ")
.color(GRAY)
.append(worldTimes.get(6)).append(SLASH).append(worldTimes.get(7)).append(SLASH).append(worldTimes.get(8))
.build());
boolean hasMoreWorlds = false;
Iterable<net.minecraft.server.level.ServerLevel> levels = server.getAllLevels();
for (net.minecraft.server.level.ServerLevel level : levels) {
if (level != serverLevel) {
hasMoreWorlds = true;
break;
}
}
if (hasMoreWorlds) {
sender.sendMessage(Component.text(""));
}
}
}
private static List<Component> eval(long[] times) {
long min = Integer.MAX_VALUE;
long max = 0L;
long total = 0L;
int count = 0;
for (long value : times) {
if (value > 0L) {
count++;
if (value < min) min = value;
if (value > max) max = value;
total += value;
}
}
if (count == 0) {
// No data available yet
return Arrays.asList(
text("N/A", GRAY),
text("N/A", GRAY),
text("N/A", GRAY)
);
}
double avgD = ((double) total / (double) count) * 1.0E-6D;
double minD = ((double) min) * 1.0E-6D;
double maxD = ((double) max) * 1.0E-6D;
return Arrays.asList(getColoredValue(avgD), getColoredValue(minD), getColoredValue(maxD));
}
private static Component getColoredValue(double value) {
return text(DF.format(value) + "ms",
value >= 50 ? RED :
value >= 40 ? YELLOW :
value >= 30 ? GOLD :
value >= 20 ? GREEN :
AQUA);
}
@Override
public List<String> tabComplete(final CommandSender sender, final String subCommand, final String[] args) {
if (!SparklyPaperParallelWorldTicking.enabled) {
return Collections.emptyList();
}
if (args.length == 1) {
return Collections.singletonList("compact");
}
return Collections.emptyList();
}
}

View File

@@ -1,35 +0,0 @@
package org.dreeam.leaf.command.subcommands;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.framework.qual.DefaultQualifier;
import org.dreeam.leaf.command.LeafCommand;
import org.dreeam.leaf.command.PermissionedLeafSubcommand;
import org.dreeam.leaf.config.LeafConfig;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.permissions.PermissionDefault;
@DefaultQualifier(NonNull.class)
public final class ReloadCommand extends PermissionedLeafSubcommand {
public final static String LITERAL_ARGUMENT = "reload";
public static final String PERM = LeafCommand.BASE_PERM + "." + LITERAL_ARGUMENT;
public ReloadCommand() {
super(PERM, PermissionDefault.OP);
}
@Override
public boolean execute(final CommandSender sender, final String subCommand, final String[] args) {
this.doReload(sender);
return true;
}
private void doReload(final CommandSender sender) {
Command.broadcastCommandMessage(sender, Component.text("Reloading Leaf config...", NamedTextColor.GREEN));
LeafConfig.reloadAsync(sender);
}
}

View File

@@ -1,38 +0,0 @@
package org.dreeam.leaf.command.subcommands;
import net.minecraft.server.MinecraftServer;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.framework.qual.DefaultQualifier;
import org.dreeam.leaf.command.LeafCommand;
import org.dreeam.leaf.command.PermissionedLeafSubcommand;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.permissions.PermissionDefault;
@DefaultQualifier(NonNull.class)
public final class VersionCommand extends PermissionedLeafSubcommand {
public final static String LITERAL_ARGUMENT = "version";
public static final String PERM = LeafCommand.BASE_PERM + "." + LITERAL_ARGUMENT;
public VersionCommand() {
super(PERM, PermissionDefault.TRUE);
}
@Override
public boolean execute(final CommandSender sender, final String subCommand, final String[] args) {
final @Nullable Command ver = MinecraftServer.getServer().server.getCommandMap().getCommand("version");
if (ver != null) {
ver.execute(sender, LeafCommand.COMMAND_LABEL, me.titaniumtown.ArrayConstants.emptyStringArray); // Gale - JettPack - reduce array allocations
}
return true;
}
@Override
public boolean testPermission(CommandSender sender) {
return super.testPermission(sender) && sender.hasPermission("bukkit.command.version");
}
}

View File

@@ -1,72 +0,0 @@
package org.dreeam.leaf.config;
import org.dreeam.leaf.config.annotations.Experimental;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public abstract class ConfigModules extends LeafConfig {
private static final Set<ConfigModules> MODULES = new HashSet<>();
public LeafGlobalConfig config;
public ConfigModules() {
this.config = LeafConfig.config();
}
public static void initModules() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
List<Field> enabledExperimentalModules = new ArrayList<>();
for (Class<?> clazz : LeafConfig.getClasses(LeafConfig.I_CONFIG_PKG)) {
ConfigModules module = (ConfigModules) clazz.getConstructor().newInstance();
module.onLoaded();
MODULES.add(module);
for (Field field : getAnnotatedStaticFields(clazz, Experimental.class)) {
if (!(field.get(null) instanceof Boolean enabled)) continue;
if (enabled) {
enabledExperimentalModules.add(field);
}
}
}
if (!enabledExperimentalModules.isEmpty()) {
LeafConfig.LOGGER.warn("You have following experimental module(s) enabled: {}, please proceed with caution!", enabledExperimentalModules.stream().map(f -> f.getDeclaringClass().getSimpleName() + "." + f.getName()).toList());
}
}
public static void loadAfterBootstrap() {
for (ConfigModules module : MODULES) {
module.onPostLoaded();
}
}
private static List<Field> getAnnotatedStaticFields(Class<?> clazz, Class<? extends Annotation> annotation) {
List<Field> fields = new ArrayList<>();
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(annotation) && Modifier.isStatic(field.getModifiers())) {
field.setAccessible(true);
fields.add(field);
}
}
return fields;
}
public static void clearModules() {
MODULES.clear();
}
public abstract void onLoaded();
public void onPostLoaded() {
}
}

View File

@@ -1,25 +0,0 @@
package org.dreeam.leaf.config;
public enum EnumConfigCategory {
ASYNC("async"),
PERF("performance"),
FIXES("fixes"),
GAMEPLAY("gameplay-mechanisms"),
NETWORK("network"),
MISC("misc");
private final String baseKeyName;
private static final EnumConfigCategory[] VALUES = EnumConfigCategory.values();
EnumConfigCategory(String baseKeyName) {
this.baseKeyName = baseKeyName;
}
public String getBaseKeyName() {
return this.baseKeyName;
}
public static EnumConfigCategory[] getCategoryValues() {
return VALUES;
}
}

View File

@@ -1,292 +0,0 @@
package org.dreeam.leaf.config;
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.dreeam.leaf.config.modules.misc.SentryDSN;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
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
*/
public class LeafConfig {
public static final Logger LOGGER = LogManager.getLogger(LeafConfig.class.getSimpleName());
protected static final File I_CONFIG_FOLDER = new File("config");
protected static final String I_CONFIG_PKG = "org.dreeam.leaf.config.modules";
protected static final String I_GLOBAL_CONFIG_FILE = "leaf-global.yml";
protected static final String I_LEVEL_CONFIG_FILE = "leaf-world-defaults.yml"; // Leaf TODO - Per level config
private static LeafGlobalConfig leafGlobalConfig;
//private static int preMajorVer;
private static int preMinorVer;
//private static int currMajorVer;
private static int currMinorVer;
/* Load & Reload */
public static @NotNull CompletableFuture<Void> reloadAsync(CommandSender sender) {
return CompletableFuture.runAsync(() -> {
try {
long begin = System.nanoTime();
ConfigModules.clearModules();
loadConfig(false);
ConfigModules.loadAfterBootstrap();
final String success = String.format("Successfully reloaded config in %sms.", (System.nanoTime() - begin) / 1_000_000);
Command.broadcastCommandMessage(sender, Component.text(success, NamedTextColor.GREEN));
} catch (Exception e) {
Command.broadcastCommandMessage(sender, Component.text("Failed to reload config. See error in console!", NamedTextColor.RED));
LOGGER.error(e);
}
}, Util.ioPool());
}
public static void loadConfig() {
try {
long begin = System.nanoTime();
LOGGER.info("Loading config...");
purgeOutdated();
loadConfig(true);
LOGGER.info("Successfully loaded config in {}ms.", (System.nanoTime() - begin) / 1_000_000);
} catch (Exception e) {
LeafConfig.LOGGER.error("Failed to load config modules!", e);
}
}
/* Load Global Config */
private static void loadConfig(boolean init) throws Exception {
// Create config folder
createDirectory(LeafConfig.I_CONFIG_FOLDER);
leafGlobalConfig = new LeafGlobalConfig(init);
// Load config modules
ConfigModules.initModules();
// Save config to disk
leafGlobalConfig.saveConfig();
}
public static LeafGlobalConfig config() {
return leafGlobalConfig;
}
/* Create config folder */
protected static void createDirectory(File dir) throws IOException {
try {
Files.createDirectories(dir.toPath());
} catch (FileAlreadyExistsException e) { // Thrown if dir exists but is not a directory
if (dir.delete()) createDirectory(dir);
}
}
/* Scan classes under package */
public static @NotNull Set<Class<?>> getClasses(String pack) {
Set<Class<?>> classes = new LinkedHashSet<>();
String packageDirName = pack.replace('.', '/');
Enumeration<URL> dirs;
try {
dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
while (dirs.hasMoreElements()) {
URL url = dirs.nextElement();
String protocol = url.getProtocol();
if ("file".equals(protocol)) {
String filePath = URLDecoder.decode(url.getFile(), StandardCharsets.UTF_8);
findClassesInPackageByFile(pack, filePath, classes);
} else if ("jar".equals(protocol)) {
JarFile jar;
try {
jar = ((JarURLConnection) url.openConnection()).getJarFile();
Enumeration<JarEntry> entries = jar.entries();
findClassesInPackageByJar(pack, entries, packageDirName, classes);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return classes;
}
private static void findClassesInPackageByFile(String packageName, String packagePath, Set<Class<?>> classes) {
File dir = new File(packagePath);
if (!dir.exists() || !dir.isDirectory()) {
return;
}
File[] dirfiles = dir.listFiles((file) -> file.isDirectory() || file.getName().endsWith(".class"));
if (dirfiles != null) {
for (File file : dirfiles) {
if (file.isDirectory()) {
findClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), classes);
} else {
String className = file.getName().substring(0, file.getName().length() - 6);
try {
classes.add(Class.forName(packageName + '.' + className));
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
}
}
private static void findClassesInPackageByJar(String packageName, Enumeration<JarEntry> entries, String packageDirName, Set<Class<?>> classes) {
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String name = entry.getName();
if (name.charAt(0) == '/') {
name = name.substring(1);
}
if (name.startsWith(packageDirName)) {
int idx = name.lastIndexOf('/');
if (idx != -1) {
packageName = name.substring(0, idx).replace('/', '.');
}
if (name.endsWith(".class") && !entry.isDirectory()) {
String className = name.substring(packageName.length() + 1, name.length() - 6);
try {
classes.add(Class.forName(packageName + '.' + className));
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
}
}
// TODO
public static void loadConfigVersion(String preVer, String currVer) {
int currMinor;
int preMinor;
// First time user
if (preVer == null) {
}
}
/* Register Spark profiler extra server configurations */
private static List<String> buildSparkExtraConfigs() {
List<String> extraConfigs = new ArrayList<>(Arrays.asList(
"config/leaf-global.yml",
"config/gale-global.yml",
"config/gale-world-defaults.yml"
));
for (World world : Bukkit.getWorlds()) {
extraConfigs.add(world.getWorldFolder().getName() + "/gale-world.yml"); // Gale world config
}
return extraConfigs;
}
private static String[] buildSparkHiddenPaths() {
return new String[]{
SentryDSN.sentryDsnConfigPath // Hide Sentry DSN key
};
}
public static void regSparkExtraConfig() {
if (GlobalConfiguration.get().spark.enabled || Bukkit.getServer().getPluginManager().getPlugin("spark") != null) {
String extraConfigs = String.join(",", buildSparkExtraConfigs());
String hiddenPaths = String.join(",", buildSparkHiddenPaths());
System.setProperty("spark.serverconfigs.extra", extraConfigs);
System.setProperty("spark.serverconfigs.hiddenpaths", hiddenPaths);
}
}
/* Purge and backup old Leaf config & Pufferfish config */
private static void purgeOutdated() {
boolean foundLegacy = false;
String pufferfishConfig = "pufferfish.yml";
String leafConfigV1 = "leaf.yml";
String leafConfigV2 = "leaf_config";
Date date = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyMMddhhmmss");
String backupDir = "config/backup" + dateFormat.format(date) + "/";
File pufferfishConfigFile = new File(pufferfishConfig);
File leafConfigV1File = new File(leafConfigV1);
File leafConfigV2File = new File(leafConfigV2);
File backupDirFile = new File(backupDir);
try {
if (pufferfishConfigFile.exists() && pufferfishConfigFile.isFile()) {
createDirectory(backupDirFile);
Files.move(pufferfishConfigFile.toPath(), Path.of(backupDir + pufferfishConfig), StandardCopyOption.REPLACE_EXISTING);
foundLegacy = true;
}
if (leafConfigV1File.exists() && leafConfigV1File.isFile()) {
createDirectory(backupDirFile);
Files.move(leafConfigV1File.toPath(), Path.of(backupDir + leafConfigV1), StandardCopyOption.REPLACE_EXISTING);
foundLegacy = true;
}
if (leafConfigV2File.exists() && leafConfigV2File.isDirectory()) {
createDirectory(backupDirFile);
Files.move(leafConfigV2File.toPath(), Path.of(backupDir + leafConfigV2), StandardCopyOption.REPLACE_EXISTING);
foundLegacy = true;
}
if (foundLegacy) {
LOGGER.warn("Found legacy Leaf config files, move to backup directory: {}", backupDir);
LOGGER.warn("New Leaf config located at config/ folder, You need to transfer config to the new one manually and restart the server!");
}
} catch (IOException e) {
LOGGER.error("Failed to purge old configs.", e);
}
}
}

View File

@@ -1,192 +0,0 @@
package org.dreeam.leaf.config;
import io.github.thatsmusic99.configurationmaster.api.ConfigFile;
import io.github.thatsmusic99.configurationmaster.api.ConfigSection;
import java.io.File;
import java.util.List;
import java.util.Locale;
import java.util.Map;
public class LeafGlobalConfig {
private static final String CURRENT_VERSION = "3.0";
private static final String CURRENT_REGION = Locale.getDefault().getCountry().toUpperCase(Locale.ROOT); // It will be in uppercase by default, just make sure
private static final boolean isCN = CURRENT_REGION.equals("CN");
private static ConfigFile configFile;
public LeafGlobalConfig(boolean init) throws Exception {
configFile = ConfigFile.loadConfig(new File(LeafConfig.I_CONFIG_FOLDER, LeafConfig.I_GLOBAL_CONFIG_FILE));
LeafConfig.loadConfigVersion(getString("config-version"), CURRENT_VERSION);
configFile.set("config-version", CURRENT_VERSION);
configFile.addComments("config-version", pickStringRegionBased("""
Leaf Config
GitHub Repo: https://github.com/Winds-Studio/Leaf
Discord: https://discord.com/invite/gfgAwdSEuM""",
"""
Leaf Config
GitHub Repo: https://github.com/Winds-Studio/Leaf
QQ Group: 619278377"""));
// Pre-structure to force order
structureConfig();
}
protected void structureConfig() {
for (EnumConfigCategory configCate : EnumConfigCategory.getCategoryValues()) {
createTitledSection(configCate.name(), configCate.getBaseKeyName());
}
}
public void saveConfig() throws Exception {
configFile.save();
}
// Config Utilities
/* getAndSet */
public void createTitledSection(String title, String path) {
configFile.addSection(title);
configFile.addDefault(path, null);
}
public boolean getBoolean(String path, boolean def, String comment) {
configFile.addDefault(path, def, comment);
return configFile.getBoolean(path, def);
}
public boolean getBoolean(String path, boolean def) {
configFile.addDefault(path, def);
return configFile.getBoolean(path, def);
}
public String getString(String path, String def, String comment) {
configFile.addDefault(path, def, comment);
return configFile.getString(path, def);
}
public String getString(String path, String def) {
configFile.addDefault(path, def);
return configFile.getString(path, def);
}
public double getDouble(String path, double def, String comment) {
configFile.addDefault(path, def, comment);
return configFile.getDouble(path, def);
}
public double getDouble(String path, double def) {
configFile.addDefault(path, def);
return configFile.getDouble(path, def);
}
public int getInt(String path, int def, String comment) {
configFile.addDefault(path, def, comment);
return configFile.getInteger(path, def);
}
public int getInt(String path, int def) {
configFile.addDefault(path, def);
return configFile.getInteger(path, def);
}
public long getLong(String path, long def, String comment) {
configFile.addDefault(path, def, comment);
return configFile.getLong(path, def);
}
public long getLong(String path, long def) {
configFile.addDefault(path, def);
return configFile.getLong(path, def);
}
public List<String> getList(String path, List<String> def, String comment) {
configFile.addDefault(path, def, comment);
return configFile.getStringList(path);
}
public List<String> getList(String path, List<String> def) {
configFile.addDefault(path, def);
return configFile.getStringList(path);
}
public ConfigSection getConfigSection(String path, Map<String, Object> defaultKeyValue, String comment) {
configFile.addDefault(path, null, comment);
configFile.makeSectionLenient(path);
defaultKeyValue.forEach((string, object) -> configFile.addExample(path + "." + string, object));
return configFile.getConfigSection(path);
}
public ConfigSection getConfigSection(String path, Map<String, Object> defaultKeyValue) {
configFile.addDefault(path, null);
configFile.makeSectionLenient(path);
defaultKeyValue.forEach((string, object) -> configFile.addExample(path + "." + string, object));
return configFile.getConfigSection(path);
}
/* get */
public Boolean getBoolean(String path) {
String value = configFile.getString(path, null);
return value == null ? null : Boolean.parseBoolean(value);
}
public String getString(String path) {
return configFile.getString(path, null);
}
public Double getDouble(String path) {
String value = configFile.getString(path, null);
return value == null ? null : Double.parseDouble(value); // TODO: Need to check whether need to handle NFE correctly
}
public Integer getInt(String path) {
String value = configFile.getString(path, null);
return value == null ? null : Integer.parseInt(value); // TODO: Need to check whether need to handle NFE correctly
}
public Long getLong(String path) {
String value = configFile.getString(path, null);
return value == null ? null : Long.parseLong(value); // TODO: Need to check whether need to handle NFE correctly
}
public List<String> getList(String path) {
return configFile.getList(path, null);
}
// TODO, check
public ConfigSection getConfigSection(String path) {
configFile.addDefault(path, null);
configFile.makeSectionLenient(path);
//defaultKeyValue.forEach((string, object) -> configFile.addExample(path + "." + string, object));
return configFile.getConfigSection(path);
}
public void addComment(String path, String comment) {
configFile.addComment(path, comment);
}
public void addCommentIfCN(String path, String comment) {
if (isCN) {
configFile.addComment(path, comment);
}
}
public void addCommentIfNonCN(String path, String comment) {
if (!isCN) {
configFile.addComment(path, comment);
}
}
public void addCommentRegionBased(String path, String en, String cn) {
configFile.addComment(path, isCN ? cn : en);
}
public String pickStringRegionBased(String en, String cn) {
return isCN ? cn : en;
}
}

View File

@@ -1,8 +0,0 @@
package org.dreeam.leaf.config.annotations;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface DoNotLoad {
}

View File

@@ -1,12 +0,0 @@
package org.dreeam.leaf.config.annotations;
import java.lang.annotation.*;
/**
* Indicates that a feature is experimental and may be removed or changed in the future.
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.FIELD})
public @interface Experimental {
}

View File

@@ -1,8 +0,0 @@
package org.dreeam.leaf.config.annotations;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface HotReloadUnsupported {
}

View File

@@ -1,32 +0,0 @@
package org.dreeam.leaf.config.modules.async;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
import org.dreeam.leaf.config.annotations.Experimental;
public class AsyncBlockFinding extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-block-finding";
}
@Experimental
public static boolean enabled = false;
public static boolean asyncBlockFindingInitialized;
@Override
public void onLoaded() {
config.addCommentRegionBased(getBasePath(), """
**Experimental feature**
This moves the expensive search calculations to a background thread while
keeping the actual block validation on the main thread.""",
"""
这会将昂贵的搜索计算移至后台线程, 同时在主线程上保持实际的方块验证.""");
if (!asyncBlockFindingInitialized) {
asyncBlockFindingInitialized = true;
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
}
}
}

View File

@@ -1,27 +0,0 @@
package org.dreeam.leaf.config.modules.async;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
import org.dreeam.leaf.config.annotations.Experimental;
public class AsyncChunkSend extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-chunk-send";
}
public static boolean enabled = false;
@Override
public void onLoaded() {
config.addCommentRegionBased(getBasePath(),
"""
Makes chunk packet preparation and sending asynchronous to improve server performance.
This can significantly reduce main thread load when many players are loading chunks.""",
"""
使区块数据包准备和发送异步化以提高服务器性能.
当许多玩家同时加载区块时, 这可以显著减少主线程负载.""");
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
}
}

View File

@@ -1,37 +0,0 @@
package org.dreeam.leaf.config.modules.async;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
import org.dreeam.leaf.config.LeafConfig;
public class AsyncLocator extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-locator";
}
public static boolean enabled = false;
public static int asyncLocatorThreads = 0;
public static int asyncLocatorKeepalive = 60;
@Override
public void onLoaded() {
config.addCommentRegionBased(getBasePath(), """
Whether or not asynchronous locator should be enabled.
This offloads structure locating to other threads.
Only for locate command, dolphin treasure finding and eye of ender currently.""",
"""
是否启用异步结构搜索.
目前可用于 /locate 指令, 海豚寻宝和末影之眼.""");
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
asyncLocatorThreads = config.getInt(getBasePath() + ".threads", asyncLocatorThreads);
asyncLocatorKeepalive = config.getInt(getBasePath() + ".keepalive", asyncLocatorKeepalive);
if (asyncLocatorThreads <= 0)
asyncLocatorThreads = 1;
if (!enabled)
asyncLocatorThreads = 0;
else
LeafConfig.LOGGER.info("Using {} threads for Async Locator", asyncLocatorThreads);
}
}

View File

@@ -1,34 +0,0 @@
package org.dreeam.leaf.config.modules.async;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
public class AsyncMobSpawning extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-mob-spawning";
}
public static boolean enabled = true;
public static boolean asyncMobSpawningInitialized;
@Override
public void onLoaded() {
config.addCommentRegionBased(getBasePath(), """
Whether or not asynchronous mob spawning should be enabled.
On servers with many entities, this can improve performance by up to 15%. You must have
paper's per-player-mob-spawns setting set to true for this to work.
One quick note - this does not actually spawn mobs async (that would be very unsafe).
This just offloads some expensive calculations that are required for mob spawning.""",
"""
是否异步化生物生成.
在实体较多的服务器上, 异步生成可最高带来15%的性能提升.
须在Paper配置文件中打开 per-player-mob-spawns 才能生效.""");
// This prevents us from changing the value during a reload.
if (!asyncMobSpawningInitialized) {
asyncMobSpawningInitialized = true;
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
}
}
}

View File

@@ -1,51 +0,0 @@
package org.dreeam.leaf.config.modules.async;
import org.dreeam.leaf.async.path.PathfindTaskRejectPolicy;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
import org.dreeam.leaf.config.LeafConfig;
public class AsyncPathfinding extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-pathfinding";
}
public static boolean enabled = false;
public static int asyncPathfindingMaxThreads = 0;
public static int asyncPathfindingKeepalive = 60;
public static int asyncPathfindingQueueSize = 0;
public static PathfindTaskRejectPolicy asyncPathfindingRejectPolicy = PathfindTaskRejectPolicy.FLUSH_ALL;
@Override
public void onLoaded() {
final int availableProcessors = Runtime.getRuntime().availableProcessors();
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
asyncPathfindingMaxThreads = config.getInt(getBasePath() + ".max-threads", asyncPathfindingMaxThreads);
asyncPathfindingKeepalive = config.getInt(getBasePath() + ".keepalive", asyncPathfindingKeepalive);
asyncPathfindingQueueSize = config.getInt(getBasePath() + ".queue-size", asyncPathfindingQueueSize);
if (asyncPathfindingMaxThreads < 0)
asyncPathfindingMaxThreads = Math.max(availableProcessors + asyncPathfindingMaxThreads, 1);
else if (asyncPathfindingMaxThreads == 0)
asyncPathfindingMaxThreads = Math.max(availableProcessors / 4, 1);
if (!enabled)
asyncPathfindingMaxThreads = 0;
else
LeafConfig.LOGGER.info("Using {} threads for Async Pathfinding", asyncPathfindingMaxThreads);
if (asyncPathfindingQueueSize <= 0)
asyncPathfindingQueueSize = asyncPathfindingMaxThreads * 256;
asyncPathfindingRejectPolicy = PathfindTaskRejectPolicy.fromString(config.getString(getBasePath() + ".reject-policy", availableProcessors >= 12 && asyncPathfindingQueueSize < 512 ? PathfindTaskRejectPolicy.FLUSH_ALL.toString() : PathfindTaskRejectPolicy.CALLER_RUNS.toString(), config.pickStringRegionBased(
"""
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.""",
"""
当队列满时, 新提交的任务将使用以下策略处理.
FLUSH_ALL: 所有等待中的任务都将在主线程上运行.
CALLER_RUNS: 新提交的任务将在主线程上运行."""
)));
}
}

View File

@@ -1,28 +0,0 @@
package org.dreeam.leaf.config.modules.async;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
import org.dreeam.leaf.config.annotations.Experimental;
public class AsyncPlayerDataSave extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-playerdata-save";
}
@Experimental
public static boolean enabled = false;
@Override
public void onLoaded() {
config.addCommentRegionBased(getBasePath(),
"""
**Experimental feature, may have data lost in some circumstances!**
Make PlayerData saving asynchronously.""",
"""
**实验性功能, 在部分场景下可能丢失玩家数据!**
异步保存玩家数据.""");
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
}
}

View File

@@ -1,53 +0,0 @@
package org.dreeam.leaf.config.modules.async;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
import org.dreeam.leaf.config.LeafConfig;
public class MultithreadedTracker extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-entity-tracker";
}
public static boolean enabled = false;
public static boolean compatModeEnabled = false;
public static int asyncEntityTrackerMaxThreads = 0;
public static int asyncEntityTrackerKeepalive = 60;
public static int asyncEntityTrackerQueueSize = 0;
@Override
public void onLoaded() {
config.addCommentRegionBased(getBasePath(), """
Make entity tracking saving asynchronously, can improve performance significantly,
especially in some massive entities in small area situations.""",
"""
异步实体跟踪,
在实体数量多且密集的情况下效果明显.""");
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
compatModeEnabled = config.getBoolean(getBasePath() + ".compat-mode", compatModeEnabled, config.pickStringRegionBased("""
Enable compat mode ONLY if Citizens or NPC plugins using real entity has installed,
Compat mode fixed visible issue with player type NPCs of Citizens,
But still recommend to use packet based / virtual entity NPC plugin, e.g. ZNPC Plus, Adyeshach, Fancy NPC or else.""",
"""
是否启用兼容模式,
如果你的服务器安装了 Citizens 或其他类似非发包 NPC 插件, 请开启此项."""));
asyncEntityTrackerMaxThreads = config.getInt(getBasePath() + ".max-threads", asyncEntityTrackerMaxThreads);
asyncEntityTrackerKeepalive = config.getInt(getBasePath() + ".keepalive", asyncEntityTrackerKeepalive);
asyncEntityTrackerQueueSize = config.getInt(getBasePath() + ".queue-size", asyncEntityTrackerQueueSize);
if (asyncEntityTrackerMaxThreads < 0)
asyncEntityTrackerMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() + asyncEntityTrackerMaxThreads, 1);
else if (asyncEntityTrackerMaxThreads == 0)
asyncEntityTrackerMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() / 4, 1);
if (!enabled)
asyncEntityTrackerMaxThreads = 0;
else
LeafConfig.LOGGER.info("Using {} threads for Async Entity Tracker", asyncEntityTrackerMaxThreads);
if (asyncEntityTrackerQueueSize <= 0)
asyncEntityTrackerQueueSize = asyncEntityTrackerMaxThreads * 384;
}
}

View File

@@ -1,41 +0,0 @@
package org.dreeam.leaf.config.modules.async;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
import org.dreeam.leaf.config.annotations.Experimental;
public class SparklyPaperParallelWorldTicking extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.ASYNC.getBaseKeyName() + ".parallel-world-tracking";
} // TODO: Correct config key when stable
@Experimental
public static boolean enabled = false;
public static int threads = 8;
public static boolean logContainerCreationStacktraces = false;
public static boolean disableHardThrow = false;
public static boolean runAsyncTasksSync = false;
@Override
public void onLoaded() {
config.addCommentRegionBased(getBasePath(),
"""
**Experimental feature**
Enables parallel world ticking to improve performance on multi-core systems..""",
"""
**实验性功能**
启用并行世界处理以提高多核系统的性能.""");
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
threads = config.getInt(getBasePath() + ".threads", threads);
threads = enabled ? threads : 0;
logContainerCreationStacktraces = config.getBoolean(getBasePath() + ".log-container-creation-stacktraces", logContainerCreationStacktraces);
logContainerCreationStacktraces = enabled && logContainerCreationStacktraces;
disableHardThrow = config.getBoolean(getBasePath() + ".disable-hard-throw", disableHardThrow);
disableHardThrow = enabled && disableHardThrow;
runAsyncTasksSync = config.getBoolean(getBasePath() + ".run-async-tasks-sync", runAsyncTasksSync);
runAsyncTasksSync = enabled && runAsyncTasksSync;
}
}

View File

@@ -1,25 +0,0 @@
package org.dreeam.leaf.config.modules.fixes;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
public class DontPlacePlayerIfFull extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.FIXES.getBaseKeyName();
}
public static boolean enabled = false;
@Override
public void onLoaded() {
enabled = config.getBoolean(getBasePath() + ".dont-place-player-if-server-full", enabled, config.pickStringRegionBased("""
Don't let player join server if the server is full.
If enable this, you should use 'purpur.joinfullserver' permission instead of
PlayerLoginEvent#allow to let player join full server.""",
"""
服务器已满时禁止玩家加入.
开启后需使用权限 'purpur.joinfullserver' 而不是
PlayerLoginEvent#allow 让玩家进入已满的服务器."""));
}
}

View File

@@ -1,23 +0,0 @@
package org.dreeam.leaf.config.modules.gameplay;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
public class AfkCommand extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.GAMEPLAY.getBaseKeyName() + ".afk-command";
}
public static boolean enabled = false;
@Override
public void onLoaded() {
enabled = config.getBoolean(getBasePath() + ".enabled", enabled, config.pickStringRegionBased("""
The AFK command based on Minecraft built-in idle-timeout mechanism
Rest of AFK settings are in the Purpur config""",
"""
基于原版 idle-timeout 系统的 AFK 指令
剩余配置项在 Purpur 配置里"""));
}
}

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;
public class ConfigurableMaxUseItemDistance extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.GAMEPLAY.getBaseKeyName() + ".player";
}
public static double maxUseItemDistance = 1.0000001;
@Override
public void onLoaded() {
maxUseItemDistance = config.getDouble(getBasePath() + ".max-use-item-distance", maxUseItemDistance, config.pickStringRegionBased("""
The max distance of UseItem for players.
Set to -1 to disable max-distance-check.
NOTE: if set to -1 to disable the check,
players are able to use some packet modules of hack clients,
and NoCom Exploit!!""",
"""
玩家 UseItem 的最大距离.
设置为 -1 来禁用最大距离检测.
注意: 禁用此项后,
玩家可以使用作弊客户端的部分发包模块和 NoCom 漏洞!!"""));
}
}

View File

@@ -1,18 +0,0 @@
package org.dreeam.leaf.config.modules.gameplay;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
public class ConfigurableTripWireDupe extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.GAMEPLAY.getBaseKeyName();
}
public static boolean enabled = false;
@Override
public void onLoaded() {
enabled = config.getBoolean(getBasePath() + ".allow-tripwire-dupe", enabled);
}
}

View File

@@ -1,66 +0,0 @@
package org.dreeam.leaf.config.modules.gameplay;
import net.minecraft.core.component.DataComponentType;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
import org.dreeam.leaf.config.LeafConfig;
import java.util.ArrayList;
import java.util.List;
/**
* HideItemComponent
*
* @author TheFloodDragon
* @since 2025/2/4 18:30
*/
public class HideItemComponent extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.GAMEPLAY.getBaseKeyName() + ".hide-item-component";
}
public static boolean enabled = false;
public static List<String> hiddenTypeStrings = new ArrayList<>();
public static List<DataComponentType<?>> hiddenTypes = List.of();
@Override
public void onLoaded() {
config.addCommentRegionBased(getBasePath(), """
Controls whether specified component information would be sent to clients.
It may break resource packs and mods that rely on the information.
Also, it can avoid some frequent client animations.
Attention: This is not same as Paper's item-obfuscation, we only hide specified component information from player's inventory.""",
"""
控制哪些物品组件信息会被发送至客户端.
可能会导致依赖物品组件的资源包/模组无法正常工作.
可以避免一些客户端动画效果.
注意: 此项与 Paper 的 item-obfuscation 不同, 我们只从玩家背包中隐藏物品指定的组件信息.""");
hiddenTypeStrings = config.getList(getBasePath() + ".hidden-types", new ArrayList<>(), config.pickStringRegionBased("""
Which type of components will be hidden from clients.
It needs a component type list, incorrect things will not work.""",
"""
被隐藏的物品组件类型列表.
该配置项接受一个物品组件列表, 格式不正确将不会启用."""));
enabled = config.getBoolean(getBasePath() + ".enabled", enabled, config.pickStringRegionBased(
"If enabled, specified item component information from player's inventory will be hided.",
"启用后, 玩家背包内物品的指定组件信息会被隐藏."
));
}
@Override
public void onPostLoaded() {
final List<DataComponentType<?>> types = new ArrayList<>(hiddenTypeStrings.size());
for (String componentType : hiddenTypeStrings) {
BuiltInRegistries.DATA_COMPONENT_TYPE.get(ResourceLocation.parse(componentType)).ifPresentOrElse(
optional -> types.add(optional.value()),
() -> LeafConfig.LOGGER.warn("Unknown component type: {}", componentType)
);
}
hiddenTypes = types;
}
}

View File

@@ -1,34 +0,0 @@
package org.dreeam.leaf.config.modules.gameplay;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
public class Knockback extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.GAMEPLAY.getBaseKeyName() + ".knockback";
}
public static boolean snowballCanKnockback = false;
public static boolean eggCanKnockback = false;
public static boolean canPlayerKnockbackZombie = true;
@Override
public void onLoaded() {
snowballCanKnockback = config.getBoolean(getBasePath() + ".snowball-knockback-players", snowballCanKnockback,
config.pickStringRegionBased(
"Make snowball can knockback players.",
"使雪球可以击退玩家."
));
eggCanKnockback = config.getBoolean(getBasePath() + ".egg-knockback-players", eggCanKnockback,
config.pickStringRegionBased(
"Make egg can knockback players.",
"使鸡蛋可以击退玩家."
));
canPlayerKnockbackZombie = config.getBoolean(getBasePath() + ".can-player-knockback-zombie", canPlayerKnockbackZombie,
config.pickStringRegionBased(
"Make players can knockback zombie.",
"使玩家可以击退僵尸."
));
}
}

View File

@@ -1,21 +0,0 @@
package org.dreeam.leaf.config.modules.gameplay;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
public class OnlyPlayerPushable extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.GAMEPLAY.getBaseKeyName() + ".only-player-pushable";
}
public static boolean enabled = false;
@Override
public void onLoaded() {
enabled = config.getBoolean(getBasePath(), enabled, config.pickStringRegionBased(
"Enable to make only player pushable",
"是否只允许玩家被实体推动"
));
}
}

View File

@@ -1,29 +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

@@ -1,87 +0,0 @@
package org.dreeam.leaf.config.modules.gameplay;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
public class SpawnerSettings extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.GAMEPLAY.getBaseKeyName() + ".spawner-settings";
}
// Global toggle
public static boolean enabled = false;
// Default values for spawner settings
public static boolean lightLevelCheck = false;
public static boolean spawnerMaxNearbyCheck = true;
public static boolean checkForNearbyPlayers = true;
public static boolean spawnerBlockChecks = false;
public static boolean waterPreventSpawnCheck = false;
public static int minSpawnDelay = 200;
public static int maxSpawnDelay = 800;
@Override
public void onLoaded() {
config.addCommentRegionBased(getBasePath(),
"This section contains settings for mob spawner blocks.",
"此部分包含刷怪笼生物生成的设置.");
// Global toggle
enabled = config.getBoolean(getBasePath() + ".enabled", enabled,
config.pickStringRegionBased(
"Enable custom spawner settings. Set to true to enable all features below.",
"启用自定义刷怪笼设置. 设为 true 以启用以下所有功能."
));
// Checks section
config.addCommentRegionBased(getBasePath() + ".checks",
"Various checks that can be enabled or disabled for spawner blocks.",
"可以为刷怪笼启用或禁用的各种检查.");
lightLevelCheck = config.getBoolean(getBasePath() + ".checks.light-level-check", lightLevelCheck,
config.pickStringRegionBased(
"Check if there is the required light level to spawn the mob",
"检查是否有所需的光照等级来生成怪物"
));
spawnerMaxNearbyCheck = config.getBoolean(getBasePath() + ".checks.spawner-max-nearby-check", spawnerMaxNearbyCheck,
config.pickStringRegionBased(
"Check if there are the max amount of nearby mobs to spawn the mob",
"检查附近是否已达到最大怪物数量限制"
));
checkForNearbyPlayers = config.getBoolean(getBasePath() + ".checks.check-for-nearby-players", checkForNearbyPlayers,
config.pickStringRegionBased(
"Check if any players are in a radius to spawn the mob",
"检查是否有玩家在生成怪物的半径范围内"
));
spawnerBlockChecks = config.getBoolean(getBasePath() + ".checks.spawner-block-checks", spawnerBlockChecks,
config.pickStringRegionBased(
"Check if there are blocks blocking the spawner to spawn the mob",
"检查是否有方块阻挡刷怪笼生成怪物"
));
waterPreventSpawnCheck = config.getBoolean(getBasePath() + ".checks.water-prevent-spawn-check", waterPreventSpawnCheck,
config.pickStringRegionBased(
"Checks if there is water around that prevents spawning",
"检查周围是否有水阻止生成"
));
// Delay settings
minSpawnDelay = config.getInt(getBasePath() + ".min-spawn-delay", minSpawnDelay,
config.pickStringRegionBased(
"Minimum delay (in ticks) between spawner spawns. Higher values slow down spawners.",
"刷怪笼生成怪物之间的最小延迟 (以刻为单位). 较高的值会减缓刷怪笼的速度."
));
maxSpawnDelay = config.getInt(getBasePath() + ".max-spawn-delay", maxSpawnDelay,
config.pickStringRegionBased(
"Maximum delay (in ticks) between spawner spawns. Higher values slow down spawners.",
"刷怪笼生成怪物之间的最大延迟 (以刻为单位). 较高的值会减缓刷怪笼的速度."
));
}
}

View File

@@ -1,18 +0,0 @@
package org.dreeam.leaf.config.modules.gameplay;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
public class UseSpigotItemMergingMech extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.GAMEPLAY.getBaseKeyName() + ".use-spigot-item-merging-mechanism";
}
public static boolean enabled = true;
@Override
public void onLoaded() {
enabled = config.getBoolean(getBasePath(), enabled);
}
}

View File

@@ -1,29 +0,0 @@
package org.dreeam.leaf.config.modules.misc;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
public class Cache extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.MISC.getBaseKeyName() + ".cache";
}
public static boolean cachePlayerProfileResult = true;
public static int cachePlayerProfileResultTimeout = 1440;
@Override
public void onLoaded() {
cachePlayerProfileResult = config.getBoolean(getBasePath() + ".cache-player-profile-result", cachePlayerProfileResult, config.pickStringRegionBased("""
Cache the player profile result on they first join.
It's useful if Mojang's verification server is down.""",
"""
玩家首次加入时缓存 PlayerProfile.
正版验证服务器宕机时非常有用."""));
cachePlayerProfileResultTimeout = config.getInt(getBasePath() + ".cache-player-profile-result-timeout", cachePlayerProfileResultTimeout,
config.pickStringRegionBased(
"The timeout of the cache. Unit: Minutes.",
"缓存过期时间. 单位: 分钟."
));
}
}

View File

@@ -1,50 +0,0 @@
package org.dreeam.leaf.config.modules.misc;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
public class ConnectionMessage extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.MISC.getBaseKeyName() + ".connection-message";
}
public static boolean joinEnabled = true;
public static String joinMessage = "default";
public static boolean quitEnabled = true;
public static String quitMessage = "default";
@Override
public void onLoaded() {
config.addCommentRegionBased(getBasePath(), """
Connection message, using MiniMessage format, set to "default" to use vanilla join message.
available placeholders:
%player_name% - player name
%player_displayname% - player display name""",
"""
自定义加入 & 退出消息 (MiniMessage 格式), 设置为 'default' 将使用原版消息.
可用的内置变量:
%player_name% - 玩家名称
%player_displayname% - 玩家显示名称""");
joinEnabled = config.getBoolean(getBasePath() + ".join.enabled", joinEnabled);
joinMessage = config.getString(getBasePath() + ".join.message", joinMessage, config.pickStringRegionBased(
"Join message of player",
"玩家加入服务器时的消息"
));
quitEnabled = config.getBoolean(getBasePath() + ".quit.enabled", quitEnabled);
quitMessage = config.getString(getBasePath() + ".quit.message", quitMessage, config.pickStringRegionBased(
"Quit message of player",
"玩家退出服务器时的消息"));
// Legacy compatibility
// TODO: config migration
joinMessage = joinMessage
.replace("%player_name%", "<player_name>")
.replace("%player_displayname%", "<player_displayname>");
quitMessage = quitMessage
.replace("%player_name%", "<player_name>")
.replace("%player_displayname%", "<player_displayname>");
}
}

View File

@@ -1,18 +0,0 @@
package org.dreeam.leaf.config.modules.misc;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
public class Including5sIngetTPS extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.MISC.getBaseKeyName();
}
public static boolean enabled = true;
@Override
public void onLoaded() {
enabled = config.getBoolean(getBasePath() + ".including-5s-in-get-tps", enabled);
}
}

View File

@@ -1,29 +0,0 @@
package org.dreeam.leaf.config.modules.misc;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
public class LagCompensation extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.MISC.getBaseKeyName() + ".lag-compensation";
}
public static boolean enabled = false;
public static boolean enableForWater = false;
public static boolean enableForLava = false;
@Override
public void onLoaded() {
config.addCommentRegionBased(getBasePath(), """
This section contains lag compensation features,
which could ensure basic playing experience during a lag.""",
"""
这部分包含滞后补偿功能,
可以在卡顿情况下保障基本游戏体验.""");
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
enableForWater = config.getBoolean(getBasePath() + ".enable-for-water", enableForWater);
enableForLava = config.getBoolean(getBasePath() + ".enable-for-lava", enableForLava);
}
}

View File

@@ -1,62 +0,0 @@
package org.dreeam.leaf.config.modules.misc;
import com.mojang.logging.LogUtils;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
import org.dreeam.leaf.config.annotations.DoNotLoad;
import org.slf4j.Logger;
import org.stupidcraft.linearpaper.region.EnumRegionFileExtension;
public class RegionFormatConfig extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.MISC.getBaseKeyName() + ".region-format-settings";
}
@DoNotLoad
private static final Logger logger = LogUtils.getLogger();
@DoNotLoad
public static EnumRegionFileExtension regionFormatType;
public static String regionFormatTypeName = "MCA";
public static int linearCompressionLevel = 1;
public static boolean throwOnUnknownExtension = false;
public static int linearFlushFrequency = 5;
@Override
public void onLoaded() {
config.addCommentRegionBased(getBasePath(), """
Linear is a region file format that uses ZSTD compression instead of
ZLIB.
This format saves about 50% of disk space.
Read Documentation before using: https://github.com/xymb-endcrystalme/LinearRegionFileFormatTools
Disclaimer: This is an experimental feature, there is potential risk to lose chunk data.
So backup your server before switching to Linear.""",
"""
Linear 是一种使用 ZSTD 压缩而非 ZLIB 的区域文件格式.
该格式可节省约 50% 的磁盘空间.
使用前请阅读文档: https://github.com/xymb-endcrystalme/LinearRegionFileFormatTools
免责声明: 实验性功能,有可能导致区块数据丢失.
切换到Linear前请备份服务器.""");
regionFormatTypeName = config.getString(getBasePath() + ".region-format", regionFormatTypeName,
config.pickStringRegionBased(
"Available region formats: MCA, LINEAR",
"可用格式: MCA, LINEAR"));
linearCompressionLevel = config.getInt(getBasePath() + ".linear-compress-level", linearCompressionLevel);
throwOnUnknownExtension = config.getBoolean(getBasePath() + ".throw-on-unknown-extension-detected", throwOnUnknownExtension);
linearFlushFrequency = config.getInt(getBasePath() + ".flush-interval-seconds", linearFlushFrequency);
regionFormatType = EnumRegionFileExtension.fromName(regionFormatTypeName);
if (regionFormatType == EnumRegionFileExtension.UNKNOWN) {
logger.error("Unknown region file type {} ! Falling back to MCA format.", regionFormatTypeName);
regionFormatType = EnumRegionFileExtension.MCA;
}
if (linearCompressionLevel > 23 || linearCompressionLevel < 1) {
logger.error("Linear region compression level should be between 1 and 22 in config: {}", linearCompressionLevel);
logger.error("Falling back to compression level 1.");
linearCompressionLevel = 1;
}
}
}

View File

@@ -1,22 +0,0 @@
package org.dreeam.leaf.config.modules.misc;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
public class RemoveChangeNonEditableSignWarning extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.MISC.getBaseKeyName();
}
public static boolean enabled = false;
@Override
public void onLoaded() {
enabled = config.getBoolean(getBasePath() + ".remove-change-non-editable-sign-warning", enabled,
config.pickStringRegionBased(
"Enable to prevent console spam.",
"移除修改无法编辑的告示牌时输出的警告."
));
}
}

View File

@@ -1,22 +0,0 @@
package org.dreeam.leaf.config.modules.misc;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
public class RemoveSpigotCheckBungee extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.MISC.getBaseKeyName() + ".remove-spigot-check-bungee-config";
}
public static boolean enabled = true;
@Override
public void onLoaded() {
enabled = config.getBoolean(getBasePath(), enabled, config.pickStringRegionBased("""
Enable player enter backend server through proxy
without backend server enabling its bungee mode.""",
"""
使服务器无需打开 bungee 模式即可让玩家加入后端服务器."""));
}
}

View File

@@ -1,23 +0,0 @@
package org.dreeam.leaf.config.modules.misc;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
public class RemoveVanillaUsernameCheck extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.MISC.getBaseKeyName() + ".remove-vanilla-username-check";
}
public static boolean enabled = true;
@Override
public void onLoaded() {
enabled = config.getBoolean(getBasePath(), enabled, config.pickStringRegionBased("""
Remove Vanilla username check,
allowing all characters as username.""",
"""
移除原版的用户名验证,
让所有字符均可作为玩家名."""));
}
}

View File

@@ -1,24 +0,0 @@
package org.dreeam.leaf.config.modules.misc;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
public class SecureSeed extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.MISC.getBaseKeyName() + ".secure-seed";
}
public static boolean enabled = false;
@Override
public void onLoaded() {
config.addCommentRegionBased(getBasePath(), """
Once you enable secure seed, all ores and structures are generated with 1024-bit seed
instead of using 64-bit seed in vanilla, made seed cracker become impossible.""",
"""
安全种子开启后, 所有矿物与结构都将使用1024位的种子进行生成, 无法被破解.""");
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
}
}

View File

@@ -1,43 +0,0 @@
package org.dreeam.leaf.config.modules.misc;
import org.apache.logging.log4j.Level;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
public class SentryDSN extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.MISC.getBaseKeyName() + ".sentry";
}
public static String sentryDsnConfigPath;
public static String sentryDsn = "";
public static String logLevel = "WARN";
public static boolean onlyLogThrown = true;
@Override
public void onLoaded() {
String sentryEnvironment = System.getenv("SENTRY_DSN");
String sentryConfig = config.getString(sentryDsnConfigPath = getBasePath() + ".dsn", sentryDsn, config.pickStringRegionBased("""
Sentry DSN for improved error logging, leave blank to disable,
Obtain from https://sentry.io/""",
"""
Sentry DSN (出现严重错误时将发送至配置的Sentry DSN地址) (留空关闭)"""));
sentryDsn = sentryEnvironment == null
? sentryConfig
: sentryEnvironment;
logLevel = config.getString(getBasePath() + ".log-level", logLevel, config.pickStringRegionBased("""
Logs with a level higher than or equal to this level will be recorded.""",
"""
大于等于该等级的日志会被记录."""));
onlyLogThrown = config.getBoolean(getBasePath() + ".only-log-thrown", onlyLogThrown, config.pickStringRegionBased("""
Only log with a Throwable will be recorded after enabling this.""",
"""
是否仅记录带有 Throwable 的日志."""));
if (sentryDsn != null && !sentryDsn.isBlank()) {
gg.pufferfish.pufferfish.sentry.SentryManager.init(Level.getLevel(logLevel));
}
}
}

View File

@@ -1,20 +0,0 @@
package org.dreeam.leaf.config.modules.misc;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
public class ServerBrand extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.MISC.getBaseKeyName() + ".rebrand";
}
public static String serverModName = io.papermc.paper.ServerBuildInfo.buildInfo().brandName();
public static String serverGUIName = "Leaf Console";
@Override
public void onLoaded() {
serverModName = config.getString(getBasePath() + ".server-mod-name", serverModName);
serverGUIName = config.getString(getBasePath() + ".server-gui-name", serverGUIName);
}
}

View File

@@ -1,23 +0,0 @@
package org.dreeam.leaf.config.modules.misc;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
public class UnknownCommandMessage extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.MISC.getBaseKeyName() + ".message";
}
public static String unknownCommandMessage = "<red><lang:command.unknown.command><newline><detail>";
@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.""",
"""
发送未知命令时的消息, 使用 MiniMessage 格式, 设置为 "default" 使用原版消息.
变量: <detail>, 显示未知命令详细信息."""));
}
}

View File

@@ -1,25 +0,0 @@
package org.dreeam.leaf.config.modules.network;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
public class ChatMessageSignature extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.NETWORK.getBaseKeyName();
}
public static boolean enabled = true;
@Override
public void onLoaded() {
enabled = config.getBoolean(getBasePath() + ".chat-message-signature", enabled, config.pickStringRegionBased("""
Whether or not enable chat message signature,
disable will prevent players to report chat messages.
And also disables the popup when joining a server without
'secure chat', such as offline-mode servers.
""",
"""
是否启用聊天签名, 禁用后玩家无法进行聊天举报."""));
}
}

View File

@@ -1,30 +0,0 @@
package org.dreeam.leaf.config.modules.network;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
public class ConnectionFlushQueueRewrite extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.NETWORK.getBaseKeyName();
}
public static boolean enabled = false;
@Override
public void onLoaded() {
enabled = config.getBoolean(getBasePath() + ".connection-flush-queue-rewrite", enabled, config.pickStringRegionBased("""
This replaces ConcurrentLinkedQueue with ArrayDeque for better performance
and uses the Netty event loop to ensure thread safety.
May increase the Netty thread usage and requires server restart to take effect
Default: false
""",
"""
此选项将 ConcurrentLinkedQueue 替换为 ArrayDeque 以提高性能,
并使用 Netty 事件循环以确保线程安全。
默认值: false
"""));
}
}

View File

@@ -1,42 +0,0 @@
package org.dreeam.leaf.config.modules.network;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
import java.util.concurrent.ThreadLocalRandom;
public class ProtocolSupport extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.NETWORK.getBaseKeyName() + ".protocol-support";
}
public static boolean jadeProtocol = false;
public static boolean appleskinProtocol = false;
public static int appleskinSyncTickInterval = 20;
public static boolean asteorBarProtocol = false;
public static boolean chatImageProtocol = false;
public static boolean xaeroMapProtocol = false;
public static int xaeroMapServerID = ThreadLocalRandom.current().nextInt(); // Leaf - Faster Random
public static boolean syncmaticaProtocol = false;
public static boolean syncmaticaQuota = false;
public static int syncmaticaQuotaLimit = 40000000;
@Override
public void onLoaded() {
jadeProtocol = config.getBoolean(getBasePath() + ".jade-protocol", jadeProtocol);
appleskinProtocol = config.getBoolean(getBasePath() + ".appleskin-protocol", appleskinProtocol);
appleskinSyncTickInterval = config.getInt(getBasePath() + ".appleskin-protocol-sync-tick-interval", appleskinSyncTickInterval);
asteorBarProtocol = config.getBoolean(getBasePath() + ".asteorbar-protocol", asteorBarProtocol);
chatImageProtocol = config.getBoolean(getBasePath() + ".chatimage-protocol", chatImageProtocol);
xaeroMapProtocol = config.getBoolean(getBasePath() + ".xaero-map-protocol", xaeroMapProtocol);
xaeroMapServerID = config.getInt(getBasePath() + ".xaero-map-server-id", xaeroMapServerID);
syncmaticaProtocol = config.getBoolean(getBasePath() + ".syncmatica-protocol", syncmaticaProtocol);
syncmaticaQuota = config.getBoolean(getBasePath() + ".syncmatica-quota", syncmaticaQuota);
syncmaticaQuotaLimit = config.getInt(getBasePath() + ".syncmatica-quota-limit", syncmaticaQuotaLimit);
if (syncmaticaProtocol) {
org.leavesmc.leaves.protocol.syncmatica.SyncmaticaProtocol.init();
}
}
}

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,28 +0,0 @@
package org.dreeam.leaf.config.modules.opt;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
public class DontSaveEntity extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.PERF.getBaseKeyName() + ".dont-save-entity";
}
public static boolean dontSavePrimedTNT = false;
public static boolean dontSaveFallingBlock = false;
@Override
public void onLoaded() {
dontSavePrimedTNT = config.getBoolean(getBasePath() + ".dont-save-primed-tnt", dontSavePrimedTNT,
config.pickStringRegionBased(
"""
Disable save primed tnt on chunk unloads.
Useful for redstone/technical servers, can prevent machines from being exploded by TNT,
when player disconnected caused by Internet issue.""",
"""
区块卸载时不保存掉落的方块和激活的 TNT,
可以避免在玩家掉线时机器被炸毁."""));
dontSaveFallingBlock = config.getBoolean(getBasePath() + ".dont-save-falling-block", dontSaveFallingBlock);
}
}

View File

@@ -1,85 +0,0 @@
package org.dreeam.leaf.config.modules.opt;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.EntityType;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
import org.dreeam.leaf.config.LeafConfig;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class DynamicActivationofBrain extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.PERF.getBaseKeyName() + ".dab";
}
public static boolean enabled = true;
public static int startDistance = 12;
public static int startDistanceSquared;
public static int maximumActivationPrio = 20;
public static int activationDistanceMod = 8;
public static boolean dontEnableIfInWater = false;
public static List<String> blackedEntities = new ArrayList<>();
@Override
public void onLoaded() {
config.addCommentRegionBased(getBasePath(), """
Optimizes entity brains when
they're far away from the player""",
"""
根据距离动态优化生物 AI""");
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
dontEnableIfInWater = config.getBoolean(getBasePath() + ".dont-enable-if-in-water", dontEnableIfInWater, config.pickStringRegionBased("""
After enabling this, non-aquatic entities in the water will not be affected by DAB.
This could fix entities suffocate in the water.""",
"""
启用此项后, 在水中的非水生生物将不会被 DAB 影响.
可以避免距离玩家较远的生物在水里淹死."""));
startDistance = config.getInt(getBasePath() + ".start-distance", startDistance, config.pickStringRegionBased("""
This value determines how far away an entity has to be
from the player to start being effected by DEAR.""",
"""
生物距离玩家多少格 DAB 开始生效"""));
maximumActivationPrio = config.getInt(getBasePath() + ".max-tick-freq", maximumActivationPrio, config.pickStringRegionBased("""
This value defines how often in ticks, the furthest entity
will get their pathfinders and behaviors ticked. 20 = 1s""",
"""
最远处的实体每隔多少刻tick一次"""));
activationDistanceMod = config.getInt(getBasePath() + ".activation-dist-mod", activationDistanceMod, """
This value defines how much distance modifies an entity's
tick frequency. freq = (distanceToPlayer^2) / (2^value)",
If you want further away entities to tick less often, use 7.
If you want further away entities to tick more often, try 9.""");
blackedEntities = config.getList(getBasePath() + ".blacklisted-entities", blackedEntities,
config.pickStringRegionBased("A list of entities to ignore for activation",
"不会被 DAB 影响的实体列表"));
startDistanceSquared = startDistance * startDistance;
}
@Override
public void onPostLoaded() {
for (EntityType<?> entityType : BuiltInRegistries.ENTITY_TYPE) {
entityType.dabEnabled = true; // reset all, before setting the ones to true
}
final String DEFAULT_PREFIX = ResourceLocation.DEFAULT_NAMESPACE + ResourceLocation.NAMESPACE_SEPARATOR;
for (String name : blackedEntities) {
// Be compatible with both `minecraft:example` and `example` syntax
// If unknown, show user config value in the logger instead of parsed result
String lowerName = name.toLowerCase(Locale.ROOT);
String typeId = lowerName.startsWith(DEFAULT_PREFIX) ? lowerName : DEFAULT_PREFIX + lowerName;
EntityType.byString(typeId).ifPresentOrElse(entityType ->
entityType.dabEnabled = false,
() -> LeafConfig.LOGGER.warn("Skip unknown entity {}, in {}", name, getBasePath() + ".blacklisted-entities")
);
}
}
}

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 EnableCachedMTBEntityTypeConvert extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.PERF.getBaseKeyName();
}
public static boolean enabled = true;
@Override
public void onLoaded() {
enabled = config.getBoolean(getBasePath() + ".enable-cached-minecraft-to-bukkit-entitytype-convert", enabled, config.pickStringRegionBased("""
Whether to cache expensive CraftEntityType#minecraftToBukkit call.""",
"""
是否缓存Minecraft到Bukkit的实体类型转换."""));
}
}

View File

@@ -1,90 +0,0 @@
package org.dreeam.leaf.config.modules.opt;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
import org.dreeam.leaf.config.LeafConfig;
import java.util.random.RandomGeneratorFactory;
public class FastRNG extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.PERF.getBaseKeyName() + ".faster-random-generator";
}
public static boolean enabled = false;
public static boolean enableForWorldgen = false;
public static String randomGenerator = "Xoroshiro128PlusPlus";
public static boolean warnForSlimeChunk = true;
public static boolean useLegacyForSlimeChunk = false;
public static boolean useDirectImpl = false;
public static boolean worldgenEnabled() {return enabled && enableForWorldgen;} // Helper function
@Override
public void onLoaded() {
config.addCommentRegionBased(getBasePath(), """
Use faster random generator?
Requires a JVM that supports RandomGenerator.
Some JREs don't support this.""",
"""
是否使用更快的随机生成器?
需要支持 RandomGenerator 的 JVM.
一些 JRE 不支持此功能.""");
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
randomGenerator = config.getString(getBasePath() + ".random-generator", randomGenerator,
config.pickStringRegionBased(
"""
Which random generator will be used?
See https://openjdk.org/jeps/356""",
"""
使用什么种类的随机生成器.
请参阅 https://openjdk.org/jeps/356"""));
enableForWorldgen = config.getBoolean(getBasePath() + ".enable-for-worldgen", enableForWorldgen,
config.pickStringRegionBased(
"""
Enable faster random generator for world generation.
WARNING: This will affect world generation!!!""",
"""
是否为世界生成启用更快的随机生成器.
警告: 此项会影响世界生成!!!"""));
warnForSlimeChunk = config.getBoolean(getBasePath() + ".warn-for-slime-chunk", warnForSlimeChunk,
config.pickStringRegionBased(
"Warn if you are not using legacy random source for slime chunk generation.",
"是否在没有为史莱姆区块使用原版随机生成器的情况下进行警告."));
useLegacyForSlimeChunk = config.getBoolean(getBasePath() + ".use-legacy-random-for-slime-chunk", useLegacyForSlimeChunk, config.pickStringRegionBased(
"""
Use legacy random source for slime chunk generation,
to follow vanilla behavior.""",
"""
是否使用原版随机生成器来生成史莱姆区块."""));
useDirectImpl = config.getBoolean(getBasePath() + ".use-direct-implementation", useDirectImpl,
config.pickStringRegionBased(
"""
Use direct random implementation instead of delegating to Java's RandomGenerator.
This may improve performance but potentially changes RNG behavior.""",
"""
使用直接随机实现而不是委托给Java的RandomGenerator.
这可能会提高性能但可能会改变RNG行为。"""));
if (enabled) {
try {
RandomGeneratorFactory.of(randomGenerator);
} catch (Exception e) {
LeafConfig.LOGGER.error("Faster random generator is enabled but {} is not supported by your JVM, " +
"falling back to legacy random source.", randomGenerator);
enabled = false;
}
}
if (enabled && warnForSlimeChunk) {
LeafConfig.LOGGER.warn("You enabled faster random generator, it will offset location of slime chunk");
LeafConfig.LOGGER.warn("If your server has slime farms or facilities need vanilla slime chunk,");
LeafConfig.LOGGER.warn("set performance.faster-random-generator.use-legacy-random-for-slime-chunk " +
"to true to use LegacyRandomSource for slime chunk generation.");
LeafConfig.LOGGER.warn("Set performance.faster-random-generator.warn-for-slime-chunk to false to " +
"disable this warning.");
}
}
}

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 FasterStructureGenFutureSequencing extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.PERF.getBaseKeyName();
}
public static boolean enabled = true;
@Override
public void onLoaded() {
enabled = config.getBoolean(getBasePath() + ".faster-structure-gen-future-sequencing", enabled,
config.pickStringRegionBased(
"May cause the inconsistent order of future compose tasks.",
"更快的结构生成任务分段."));
}
}

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 ReduceUselessPackets extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.PERF.getBaseKeyName() + ".reduce-packets";
}
public static boolean reduceUselessEntityMovePackets = false;
@Override
public void onLoaded() {
reduceUselessEntityMovePackets = config.getBoolean(getBasePath() + ".reduce-entity-move-packets", reduceUselessEntityMovePackets);
}
}

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 SkipAIForNonAwareMob extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.PERF.getBaseKeyName();
}
public static boolean enabled = true;
@Override
public void onLoaded() {
enabled = config.getBoolean(getBasePath() + ".skip-ai-for-non-aware-mob", enabled);
}
}

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 SkipMapItemDataUpdates extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.PERF.getBaseKeyName();
}
public static boolean enabled = true;
@Override
public void onLoaded() {
enabled = config.getBoolean(getBasePath() + ".skip-map-item-data-updates-if-map-does-not-have-craftmaprenderer", enabled);
}
}

View File

@@ -1,26 +0,0 @@
package org.dreeam.leaf.config.modules.opt;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
public class ThrottleHopperWhenFull extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.PERF.getBaseKeyName() + ".throttle-hopper-when-full";
}
public static boolean enabled = false;
public static int skipTicks = 0;
@Override
public void onLoaded() {
enabled = config.getBoolean(getBasePath() + ".enabled", enabled, config.pickStringRegionBased("""
Throttles the hopper if target container is full.""",
"""
是否在目标容器已满时阻塞漏斗."""));
skipTicks = config.getInt(getBasePath() + ".skip-ticks", skipTicks, config.pickStringRegionBased("""
How many ticks to throttle when the Hopper is throttled.""",
"""
每次阻塞多少 tick."""));
}
}

View File

@@ -1,23 +0,0 @@
package org.dreeam.leaf.config.modules.opt;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
public class ThrottleInactiveGoalSelectorTick extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.PERF.getBaseKeyName();
}
public static boolean enabled = true;
@Override
public void onLoaded() {
enabled = config.getBoolean(getBasePath() + ".inactive-goal-selector-throttle", enabled, config.pickStringRegionBased("""
Throttles the AI goal selector in entity inactive ticks.
This can improve performance by a few percent, but has minor gameplay implications.""",
"""
是否在实体不活跃 tick 时阻塞 AI 目标选择器.
有助于提升性能, 但对游戏有轻微影响."""));
}
}

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 TileEntitySnapshotCreation extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.PERF.getBaseKeyName();
}
public static boolean enabled = true;
@Override
public void onLoaded() {
enabled = config.getBoolean(getBasePath() + ".create-snapshot-on-retrieving-blockstate", enabled);
}
}

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 VT4BukkitScheduler 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-async-scheduler", enabled,
config.pickStringRegionBased(
"Use the new Virtual Thread introduced in JDK 21 for CraftAsyncScheduler.",
"是否为异步任务调度器使用虚拟线程."));
}
}

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 VT4ChatExecutor 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-async-chat-executor", enabled,
config.pickStringRegionBased(
"Use the new Virtual Thread introduced in JDK 21 for Async Chat Executor.",
"是否为异步聊天线程使用虚拟线程."));
}
}

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 VT4UserAuthenticator 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-user-authenticator", enabled,
config.pickStringRegionBased(
"Use the new Virtual Thread introduced in JDK 21 for User Authenticator.",
"是否为用户验证器使用虚拟线程."));
}
}

View File

@@ -1,121 +0,0 @@
package org.dreeam.leaf.misc;
import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
import java.util.Collections;
import java.util.List;
public class LagCompensation {
public static float tt20(float ticks, boolean limitZero) {
float newTicks = (float) rawTT20(ticks);
if (limitZero) return newTicks > 0 ? newTicks : 1;
else return newTicks;
}
public static int tt20(int ticks, boolean limitZero) {
int newTicks = (int) Math.ceil(rawTT20(ticks));
if (limitZero) return newTicks > 0 ? newTicks : 1;
else return newTicks;
}
public static double tt20(double ticks, boolean limitZero) {
double newTicks = rawTT20(ticks);
if (limitZero) return newTicks > 0 ? newTicks : 1;
else return newTicks;
}
public static double rawTT20(double ticks) {
return ticks == 0 ? 0 : ticks * TPSCalculator.getMostAccurateTPS() / TPSCalculator.MAX_TPS;
}
public static class TPSCalculator {
public static Long lastTick;
public static Long currentTick;
private static double allMissedTicks = 0;
private static final List<Double> tpsHistory = Collections.synchronizedList(new DoubleArrayList());
private static final int historyLimit = 40;
public static final int MAX_TPS = 20;
public static final int FULL_TICK = 50;
private TPSCalculator() {
}
public static void onTick() {
if (currentTick != null) {
lastTick = currentTick;
}
currentTick = System.currentTimeMillis();
addToHistory(getTPS());
clearMissedTicks();
missedTick();
}
private static void addToHistory(double tps) {
if (tpsHistory.size() >= historyLimit) {
tpsHistory.removeFirst();
}
tpsHistory.add(tps);
}
public static long getMSPT() {
return currentTick - lastTick;
}
public static double getAverageTPS() {
double sum = 0.0;
for (double value : tpsHistory) {
sum += value;
}
return tpsHistory.isEmpty() ? 0.1 : sum / tpsHistory.size();
}
public static double getTPS() {
if (lastTick == null) return -1;
if (getMSPT() <= 0) return 0.1;
double tps = 1000 / (double) getMSPT();
return tps > MAX_TPS ? MAX_TPS : tps;
}
public static void missedTick() {
if (lastTick == null) return;
long mspt = getMSPT() <= 0 ? 50 : getMSPT();
double missedTicks = (mspt / (double) FULL_TICK) - 1;
allMissedTicks += missedTicks <= 0 ? 0 : missedTicks;
}
public static double getMostAccurateTPS() {
return Math.min(getTPS(), getAverageTPS());
}
public double getAllMissedTicks() {
return allMissedTicks;
}
public static int applicableMissedTicks() {
return (int) Math.floor(allMissedTicks);
}
public static void clearMissedTicks() {
allMissedTicks -= applicableMissedTicks();
}
public void resetMissedTicks() {
allMissedTicks = 0;
}
}
}

View File

@@ -1,37 +0,0 @@
package org.dreeam.leaf.util.biome;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.world.level.biome.Biome;
import java.util.function.Function;
import java.util.function.Supplier;
public class PositionalBiomeGetter implements Supplier<Holder<Biome>> {
private final Function<BlockPos, Holder<Biome>> biomeGetter;
private final BlockPos.MutableBlockPos pos;
private int nextX, nextY, nextZ;
private volatile Holder<Biome> curBiome;
public PositionalBiomeGetter(Function<BlockPos, Holder<Biome>> biomeGetter, BlockPos.MutableBlockPos pos) {
this.biomeGetter = biomeGetter;
this.pos = pos;
}
public void update(int nextX, int nextY, int nextZ) {
this.nextX = nextX;
this.nextY = nextY;
this.nextZ = nextZ;
this.curBiome = null;
}
@Override
public Holder<Biome> get() {
Holder<Biome> biome = curBiome;
if (biome == null) {
curBiome = biome = biomeGetter.apply(pos.set(nextX, nextY, nextZ));
}
return biome;
}
}

View File

@@ -1,23 +0,0 @@
package org.dreeam.leaf.util.cache;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.BitSet;
import java.util.function.IntFunction;
public class CachedOrNewBitsGetter {
private static final IntFunction<BitSet> BITSET_CONSTRUCTOR = BitSet::new;
private static final ThreadLocal<Int2ObjectOpenHashMap<BitSet>> BITSETS = ThreadLocal.withInitial(Int2ObjectOpenHashMap::new);
private CachedOrNewBitsGetter() {
}
public static BitSet getCachedOrNewBitSet(int bits) {
final BitSet bitSet = BITSETS.get().computeIfAbsent(bits, BITSET_CONSTRUCTOR);
bitSet.clear();
return bitSet;
}
}

View File

@@ -1,74 +0,0 @@
package org.dreeam.leaf.util.cache;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import it.unimi.dsi.fastutil.longs.LongList;
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
*/
public class IterateOutwardsCache {
//POS_ZERO must not be replaced with BlockPos.ORIGIN, otherwise iterateOutwards at BlockPos.ORIGIN will not use the cache
public static final BlockPos POS_ZERO = new BlockPos(0, 0, 0);
private final ConcurrentHashMap<Long, LongArrayList> table;
private final int capacity;
private final Random random;
public IterateOutwardsCache(int capacity) {
this.capacity = capacity;
this.table = new ConcurrentHashMap<>(31);
this.random = new Random();
}
private void fillPositionsWithIterateOutwards(LongList entry, int xRange, int yRange, int zRange) {
// Add all positions to the cached list
for (BlockPos pos : BlockPos.withinManhattan(POS_ZERO, xRange, yRange, zRange)) {
entry.add(pos.asLong());
}
}
public LongList getOrCompute(int xRange, int yRange, int zRange) {
long key = BlockPos.asLong(xRange, yRange, zRange);
LongArrayList entry = this.table.get(key);
if (entry != null) {
return entry;
}
// Cache miss: compute and store
entry = new LongArrayList(128);
this.fillPositionsWithIterateOutwards(entry, xRange, yRange, zRange);
//decrease the array size, as of now it won't be modified anymore anyways
entry.trim();
//this might overwrite an entry as the same entry could have been computed and added during this thread's computation
//we do not use computeIfAbsent, as it can delay other threads for too long
Object previousEntry = this.table.put(key, entry);
if (previousEntry == null && this.table.size() > this.capacity) {
//prevent a memory leak by randomly removing about 1/8th of the elements when the exceed the desired capacity is exceeded
final Iterator<Long> iterator = this.table.keySet().iterator();
//prevent an unlikely infinite loop caused by another thread filling the table concurrently using counting
for (int i = -this.capacity; iterator.hasNext() && i < 5; i++) {
Long key2 = iterator.next();
//random is not threadsafe, but it doesn't matter here, because we don't need quality random numbers
if (this.random.nextInt(8) == 0 && key2 != key) {
iterator.remove();
}
}
}
return entry;
}
}

View File

@@ -1,47 +0,0 @@
package org.dreeam.leaf.util.cache;
import it.unimi.dsi.fastutil.longs.LongIterator;
import it.unimi.dsi.fastutil.longs.LongList;
import java.util.Iterator;
import net.minecraft.core.BlockPos;
/**
* @author 2No2Name
*/
public class LongList2BlockPosMutableIterable implements Iterable<BlockPos> {
private final LongList positions;
private final int xOffset, yOffset, zOffset;
public LongList2BlockPosMutableIterable(BlockPos offset, LongList posList) {
this.xOffset = offset.getX();
this.yOffset = offset.getY();
this.zOffset = offset.getZ();
this.positions = posList;
}
@Override
public Iterator<BlockPos> iterator() {
return new Iterator<>() {
private final LongIterator it = LongList2BlockPosMutableIterable.this.positions.iterator();
private final BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos();
@Override
public boolean hasNext() {
return it.hasNext();
}
@Override
public net.minecraft.core.BlockPos next() {
long nextPos = this.it.nextLong();
return this.pos.set(
LongList2BlockPosMutableIterable.this.xOffset + BlockPos.getX(nextPos),
LongList2BlockPosMutableIterable.this.yOffset + BlockPos.getY(nextPos),
LongList2BlockPosMutableIterable.this.zOffset + BlockPos.getZ(nextPos));
}
};
}
}

View File

@@ -1,76 +0,0 @@
package org.dreeam.leaf.util.item;
import net.minecraft.core.component.DataComponentMap;
import net.minecraft.core.component.DataComponentType;
import net.minecraft.world.item.ItemStack;
import org.dreeam.leaf.config.modules.gameplay.HideItemComponent;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* ItemStackStripper
*
* @author TheFloodDragon
* @since 2025/2/4 19:04
*/
public class ItemStackStripper {
public static ItemStack strip(final ItemStack itemStack, final boolean copy) {
if (!HideItemComponent.enabled || itemStack.isEmpty() || itemStack.getComponentsPatch().isEmpty())
return itemStack;
final ItemStack copied = copy ? itemStack.copy() : itemStack;
// Remove specified types
for (DataComponentType<?> type : HideItemComponent.hiddenTypes) {
// Only remove, no others
copied.remove(type);
}
return copied;
}
public static List<ItemStack> strip(final List<ItemStack> itemStacks, final boolean copy) {
if (!HideItemComponent.enabled) return itemStacks;
final List<ItemStack> copiedItems = new ArrayList<>();
for (ItemStack itemStack : itemStacks) {
if (itemStack.isEmpty() || itemStack.getComponentsPatch().isEmpty()) {
copiedItems.add(itemStack);
continue;
}
final ItemStack copied = copy ? itemStack.copy() : itemStack;
// Remove specified types
for (DataComponentType<?> type : HideItemComponent.hiddenTypes) {
// Only remove, no others
copied.remove(type);
}
copiedItems.add(copied);
}
return copiedItems;
}
/**
* Check if two ItemStacks are the same after stripping components
*/
public static boolean matchesStripped(ItemStack left, ItemStack right) {
return left == right || (
left.is(right.getItem()) && left.getCount() == right.getCount() &&
(left.isEmpty() && right.isEmpty() || Objects.equals(strip(left.getComponents()), strip(right.getComponents())))
);
}
/**
* @return a new DataComponentMap with all hidden components removed
*/
private static DataComponentMap strip(final DataComponentMap map) {
return map.filter(c -> !HideItemComponent.hiddenTypes.contains(c));
}
}

View File

@@ -1,103 +0,0 @@
package org.dreeam.leaf.util.list;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.minecraft.world.level.block.entity.TickingBlockEntity;
import java.util.Arrays;
import java.util.Collection;
/**
* A list for ServerLevel's blockEntityTickers
* <p>
* This list behaves identically to ObjectArrayList, but it has an additional method, `removeAllByIndex`, that allows a list of integers to be passed indicating what
* indexes should be deleted from the list
* <p>
* This is faster than using removeAll, since we don't need to compare the identity of each block entity, and faster than looping thru each index manually and deleting with remove,
* since we don't need to resize the array every single remove.
*/
public final class BlockEntityTickersList extends ObjectArrayList<TickingBlockEntity> {
private final IntOpenHashSet toRemove = new IntOpenHashSet();
private int startSearchFromIndex = -1;
/**
* Creates a new array list with {@link #DEFAULT_INITIAL_CAPACITY} capacity.
*/
public BlockEntityTickersList() {
super();
}
/**
* Creates a new array list and fills it with a given collection.
*
* @param c a collection that will be used to fill the array list.
*/
public BlockEntityTickersList(final Collection<? extends TickingBlockEntity> c) {
super(c);
}
/**
* Marks an entry as removed
*
* @param index the index of the item on the list to be marked as removed
*/
public void markAsRemoved(final int index) {
// The block entities list always loop starting from 0, so we only need to check if the startSearchFromIndex is -1 and that's it
if (this.startSearchFromIndex == -1)
this.startSearchFromIndex = index;
this.toRemove.add(index);
}
/**
* Removes elements that have been marked as removed.
*/
public void removeMarkedEntries() {
if (this.startSearchFromIndex == -1) // No entries in the list, skip
return;
removeAllByIndex(startSearchFromIndex, toRemove);
toRemove.clear();
this.startSearchFromIndex = -1; // Reset the start search index
}
/**
* Removes elements by their index.
*/
private void removeAllByIndex(final int startSearchFromIndex, final IntOpenHashSet c) { // can't use Set<Integer> because we want to avoid autoboxing when using contains
final int requiredMatches = c.size();
if (requiredMatches == 0)
return; // exit early, we don't need to do anything
final Object[] a = this.a;
int j = startSearchFromIndex;
int matches = 0;
for (int i = startSearchFromIndex; i < size; i++) { // If the user knows the first index to be removed, we can skip a lot of unnecessary comparsions
if (!c.contains(i)) {
// TODO: It can be possible to optimize this loop by tracking the start/finish and then using arraycopy to "skip" the elements,
// this would optimize cases where the index to be removed are far apart, HOWEVER it does have a big performance impact if you are doing
// "arraycopy" for each element
a[j++] = a[i];
} else {
matches++;
}
if (matches == requiredMatches) { // Exit the loop if we already removed everything, we don't need to check anything else
// We need to update the final size here, because we know that we already found everything!
// Because we know that the size must be currentSize - requiredMatches (because we have matched everything), let's update the value
// However, we need to copy the rest of the stuff over
if (i != (size - 1)) { // If it isn't the last index...
// i + 1 because we want to copy the *next* element over
// and the size - i - 1 is because we want to get the current size, minus the current index (which is i), and then - 1 because we want to copy -1 ahead (remember, we are adding +1 to copy the *next* element)
System.arraycopy(a, i + 1, a, j, size - i - 1);
}
j = size - requiredMatches;
break;
}
}
Arrays.fill(a, j, size, null);
size = j;
}
}

View File

@@ -1,697 +0,0 @@
package org.dreeam.leaf.util.map;
import it.unimi.dsi.fastutil.longs.*;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.Iterator;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
/**
* Optimized thread-safe implementation of {@link LongSet} that uses striped locking
* and primitive long arrays to minimize boxing/unboxing overhead.
*/
@SuppressWarnings({"unused", "deprecation"})
public final class ConcurrentLongHashSet extends LongOpenHashSet implements LongSet {
// Number of lock stripes - higher number means more concurrency but more memory
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
// Load factor - when to resize the hash table
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
// Initial capacity per stripe
private static final int DEFAULT_INITIAL_CAPACITY = 16;
// Array of segments (stripes)
private final Segment[] segments;
// Total size, cached for faster size() operation
private final AtomicInteger size;
/**
* Creates a new empty concurrent long set with default parameters.
*/
public ConcurrentLongHashSet() {
this(DEFAULT_CONCURRENCY_LEVEL * DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
}
/**
* Creates a new concurrent long set with the specified parameters.
*
* @param initialCapacity the initial capacity
* @param loadFactor the load factor
* @param concurrencyLevel the concurrency level
*/
public ConcurrentLongHashSet(int initialCapacity, float loadFactor, int concurrencyLevel) {
// Need to call super() even though we don't use its state
super();
// Validate parameters
if (initialCapacity < 0) {
throw new IllegalArgumentException("Initial capacity must be positive");
}
if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
throw new IllegalArgumentException("Load factor must be positive");
}
if (concurrencyLevel <= 0) {
throw new IllegalArgumentException("Concurrency level must be positive");
}
// Calculate segment count (power of 2)
int segmentCount = 1;
while (segmentCount < concurrencyLevel) {
segmentCount <<= 1;
}
// Calculate capacity per segment
int segmentCapacity = Math.max(initialCapacity / segmentCount, DEFAULT_INITIAL_CAPACITY);
// Create segments
this.segments = new Segment[segmentCount];
for (int i = 0; i < segmentCount; i++) {
this.segments[i] = new Segment(segmentCapacity, loadFactor);
}
this.size = new AtomicInteger(0);
}
@Override
public int size() {
return size.get();
}
@Override
public boolean isEmpty() {
return size.get() == 0;
}
@Override
public boolean add(long key) {
Segment segment = segmentFor(key);
int delta = segment.add(key) ? 1 : 0;
if (delta > 0) {
size.addAndGet(delta);
}
return delta > 0;
}
@Override
public boolean contains(long key) {
return segmentFor(key).contains(key);
}
@Override
public boolean remove(long key) {
Segment segment = segmentFor(key);
int delta = segment.remove(key) ? -1 : 0;
if (delta < 0) {
size.addAndGet(delta);
}
return delta < 0;
}
@Override
public void clear() {
for (Segment segment : segments) {
segment.clear();
}
size.set(0);
}
@Override
public @NotNull LongIterator iterator() {
return new ConcurrentLongIterator();
}
@Override
public long[] toLongArray() {
long[] result = new long[size()];
int index = 0;
for (Segment segment : segments) {
index = segment.toLongArray(result, index);
}
return result;
}
@Override
public long[] toArray(long[] array) {
Objects.requireNonNull(array, "Array cannot be null");
long[] result = toLongArray();
if (array.length < result.length) {
return result;
}
System.arraycopy(result, 0, array, 0, result.length);
if (array.length > result.length) {
array[result.length] = 0;
}
return array;
}
@NotNull
@Override
public Object @NotNull [] toArray() {
Long[] result = new Long[size()];
int index = 0;
for (Segment segment : segments) {
index = segment.toObjectArray(result, index);
}
return result;
}
@NotNull
@Override
public <T> T @NotNull [] toArray(@NotNull T @NotNull [] array) {
Objects.requireNonNull(array, "Array cannot be null");
Long[] result = new Long[size()];
int index = 0;
for (Segment segment : segments) {
index = segment.toObjectArray(result, index);
}
if (array.length < result.length) {
return (T[]) result;
}
System.arraycopy(result, 0, array, 0, result.length);
if (array.length > result.length) {
array[result.length] = null;
}
return array;
}
@Override
public boolean containsAll(@NotNull Collection<?> collection) {
Objects.requireNonNull(collection, "Collection cannot be null");
for (Object o : collection) {
if (o instanceof Long) {
if (!contains(((Long) o).longValue())) {
return false;
}
} else {
return false;
}
}
return true;
}
@Override
public boolean addAll(@NotNull Collection<? extends Long> collection) {
Objects.requireNonNull(collection, "Collection cannot be null");
boolean modified = false;
for (Long value : collection) {
modified |= add(value);
}
return modified;
}
@Override
public boolean removeAll(@NotNull Collection<?> collection) {
Objects.requireNonNull(collection, "Collection cannot be null");
boolean modified = false;
for (Object o : collection) {
if (o instanceof Long) {
modified |= remove(((Long) o).longValue());
}
}
return modified;
}
@Override
public boolean retainAll(@NotNull Collection<?> collection) {
Objects.requireNonNull(collection, "Collection cannot be null");
// Convert collection to a set of longs for faster lookups
LongOpenHashSet toRetain = new LongOpenHashSet();
for (Object o : collection) {
if (o instanceof Long) {
toRetain.add(((Long) o).longValue());
}
}
boolean modified = false;
for (Segment segment : segments) {
modified |= segment.retainAll(toRetain);
}
if (modified) {
// Recalculate size
int newSize = 0;
for (Segment segment : segments) {
newSize += segment.size();
}
size.set(newSize);
}
return modified;
}
@Override
public boolean addAll(LongCollection c) {
Objects.requireNonNull(c, "Collection cannot be null");
boolean modified = false;
LongIterator iterator = c.iterator();
while (iterator.hasNext()) {
modified |= add(iterator.nextLong());
}
return modified;
}
@Override
public boolean containsAll(LongCollection c) {
Objects.requireNonNull(c, "Collection cannot be null");
LongIterator iterator = c.iterator();
while (iterator.hasNext()) {
if (!contains(iterator.nextLong())) {
return false;
}
}
return true;
}
@Override
public boolean removeAll(LongCollection c) {
Objects.requireNonNull(c, "Collection cannot be null");
boolean modified = false;
LongIterator iterator = c.iterator();
while (iterator.hasNext()) {
modified |= remove(iterator.nextLong());
}
return modified;
}
@Override
public boolean retainAll(LongCollection c) {
Objects.requireNonNull(c, "Collection cannot be null");
// For LongCollection we can directly use it
boolean modified = false;
for (Segment segment : segments) {
modified |= segment.retainAll(c);
}
if (modified) {
// Recalculate size
int newSize = 0;
for (Segment segment : segments) {
newSize += segment.size();
}
size.set(newSize);
}
return modified;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof LongSet that)) return false;
if (size() != that.size()) return false;
return containsAll(that);
}
@Override
public int hashCode() {
int hash = 0;
for (Segment segment : segments) {
hash += segment.hashCode();
}
return hash;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append('[');
LongIterator it = iterator();
boolean hasNext = it.hasNext();
while (hasNext) {
sb.append(it.nextLong());
hasNext = it.hasNext();
if (hasNext) {
sb.append(", ");
}
}
sb.append(']');
return sb.toString();
}
/**
* Find the segment for a given key.
*/
private Segment segmentFor(long key) {
// Use high bits of hash to determine segment
// This helps spread keys more evenly across segments
return segments[(int) ((spread(key) >>> segmentShift()) & segmentMask())];
}
/**
* Spread bits to reduce clustering for keys with similar hash codes.
*/
private static long spread(long key) {
long h = key;
h ^= h >>> 32;
h ^= h >>> 16;
h ^= h >>> 8;
return h;
}
private int segmentShift() {
return Integer.numberOfLeadingZeros(segments.length);
}
private int segmentMask() {
return segments.length - 1;
}
/**
* A segment is a striped portion of the hash set with its own lock.
*/
private static class Segment {
private final ReentrantLock lock = new ReentrantLock();
private long[] keys;
private boolean[] used;
private int size;
private int threshold;
private final float loadFactor;
Segment(int initialCapacity, float loadFactor) {
int capacity = MathUtil.nextPowerOfTwo(initialCapacity);
this.keys = new long[capacity];
this.used = new boolean[capacity];
this.size = 0;
this.loadFactor = loadFactor;
this.threshold = (int) (capacity * loadFactor);
}
int size() {
lock.lock();
try {
return size;
} finally {
lock.unlock();
}
}
boolean contains(long key) {
lock.lock();
try {
int index = indexOf(key);
return used[index] && keys[index] == key;
} finally {
lock.unlock();
}
}
boolean add(long key) {
lock.lock();
try {
int index = indexOf(key);
// Key already exists
if (used[index] && keys[index] == key) {
return false;
}
// Insert key
keys[index] = key;
if (!used[index]) {
used[index] = true;
size++;
// Check if rehash is needed
if (size > threshold) {
rehash();
}
}
return true;
} finally {
lock.unlock();
}
}
boolean remove(long key) {
lock.lock();
try {
int index = indexOf(key);
// Key not found
if (!used[index] || keys[index] != key) {
return false;
}
// Mark slot as unused
used[index] = false;
size--;
// If the next slot is also used, we need to handle the removal properly
// to maintain the open addressing property
// This rehashing serves as a "cleanup" after removal
if (size > 0) {
rehashFromIndex(index);
}
return true;
} finally {
lock.unlock();
}
}
void clear() {
lock.lock();
try {
for (int i = 0; i < used.length; i++) {
used[i] = false;
}
size = 0;
} finally {
lock.unlock();
}
}
int toLongArray(long[] array, int offset) {
lock.lock();
try {
for (int i = 0; i < keys.length; i++) {
if (used[i]) {
array[offset++] = keys[i];
}
}
return offset;
} finally {
lock.unlock();
}
}
int toObjectArray(Long[] array, int offset) {
lock.lock();
try {
for (int i = 0; i < keys.length; i++) {
if (used[i]) {
array[offset++] = keys[i];
}
}
return offset;
} finally {
lock.unlock();
}
}
boolean retainAll(LongCollection toRetain) {
lock.lock();
try {
boolean modified = false;
for (int i = 0; i < keys.length; i++) {
if (used[i] && !toRetain.contains(keys[i])) {
used[i] = false;
size--;
modified = true;
}
}
// Rehash to clean up if needed
if (modified && size > 0) {
rehash();
}
return modified;
} finally {
lock.unlock();
}
}
/**
* Find the index where a key should be stored.
* Uses linear probing for collision resolution.
*/
private int indexOf(long key) {
int mask = keys.length - 1;
int index = (int) (spread(key) & mask);
while (used[index] && keys[index] != key) {
index = (index + 1) & mask;
}
return index;
}
/**
* Rehash the segment with a larger capacity.
*/
private void rehash() {
int oldCapacity = keys.length;
int newCapacity = oldCapacity * 2;
long[] oldKeys = keys;
boolean[] oldUsed = used;
keys = new long[newCapacity];
used = new boolean[newCapacity];
size = 0;
threshold = (int) (newCapacity * loadFactor);
// Re-add all keys
for (int i = 0; i < oldCapacity; i++) {
if (oldUsed[i]) {
add(oldKeys[i]);
}
}
}
/**
* Rehash from a specific index after removal to maintain proper open addressing.
*/
private void rehashFromIndex(int startIndex) {
int mask = keys.length - 1;
int currentIndex = startIndex;
int nextIndex = (currentIndex + 1) & mask;
// For each cluster of used slots following the removal point
while (used[nextIndex]) {
long key = keys[nextIndex];
int targetIndex = (int) (spread(key) & mask);
// If the key's ideal position is between the removal point and the current position,
// move it to the removal point
if ((targetIndex <= currentIndex && currentIndex < nextIndex) ||
(nextIndex < targetIndex && targetIndex <= currentIndex) ||
(currentIndex < nextIndex && nextIndex < targetIndex)) {
keys[currentIndex] = keys[nextIndex];
used[currentIndex] = true;
used[nextIndex] = false;
currentIndex = nextIndex;
}
nextIndex = (nextIndex + 1) & mask;
}
}
@Override
public int hashCode() {
lock.lock();
try {
int hash = 0;
for (int i = 0; i < keys.length; i++) {
if (used[i]) {
hash += Long.hashCode(keys[i]);
}
}
return hash;
} finally {
lock.unlock();
}
}
}
/**
* Concurrent iterator for the set.
*/
private class ConcurrentLongIterator implements LongIterator {
private int segmentIndex;
private int keyIndex;
private long lastReturned;
private boolean lastReturnedValid;
ConcurrentLongIterator() {
segmentIndex = 0;
keyIndex = 0;
lastReturnedValid = false;
advance();
}
@Override
public boolean hasNext() {
return segmentIndex < segments.length;
}
@Override
public long nextLong() {
if (!hasNext()) {
throw new java.util.NoSuchElementException();
}
lastReturned = segments[segmentIndex].keys[keyIndex];
lastReturnedValid = true;
advance();
return lastReturned;
}
@Override
public Long next() {
return nextLong();
}
@Override
public void remove() {
if (!lastReturnedValid) {
throw new IllegalStateException();
}
ConcurrentLongHashSet.this.remove(lastReturned);
lastReturnedValid = false;
}
private void advance() {
while (segmentIndex < segments.length) {
Segment segment = segments[segmentIndex];
// Lock the segment to get a consistent view
segment.lock.lock();
try {
while (keyIndex < segment.keys.length) {
if (segment.used[keyIndex]) {
// Found next element
return;
}
keyIndex++;
}
} finally {
segment.lock.unlock();
}
// Move to next segment
segmentIndex++;
keyIndex = 0;
}
}
}
/**
* Utility class for math operations.
*/
private static class MathUtil {
/**
* Returns the next power of two greater than or equal to the given value.
*/
static int nextPowerOfTwo(int value) {
int highestBit = Integer.highestOneBit(value);
return value > highestBit ? highestBit << 1 : value;
}
}
}

View File

@@ -1,59 +0,0 @@
package org.dreeam.leaf.util.map;
import com.google.common.collect.Interner;
import com.google.common.collect.Interners;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectIterator;
import java.util.Map;
import java.util.function.Function;
public class StringCanonizingOpenHashMap<T> extends Object2ObjectOpenHashMap<String, T> {
private static final Interner<String> KEY_INTERNER = Interners.newWeakInterner();
private static String intern(String key) {
return key != null ? KEY_INTERNER.intern(key) : null;
}
public StringCanonizingOpenHashMap() {
super();
}
public StringCanonizingOpenHashMap(int expectedSize) {
super(expectedSize);
}
public StringCanonizingOpenHashMap(int expectedSize, float loadFactor) {
super(expectedSize, loadFactor);
}
@Override
public T put(String key, T value) {
return super.put(intern(key), value);
}
@Override
public void putAll(Map<? extends String, ? extends T> m) {
if (m.isEmpty()) return;
Map<String, T> tmp = new Object2ObjectOpenHashMap<>(m.size());
m.forEach((k, v) -> tmp.put(intern(k), v));
super.putAll(tmp);
}
private void putWithoutInterning(String key, T value) {
super.put(key, value);
}
public static <T> StringCanonizingOpenHashMap<T> deepCopy(StringCanonizingOpenHashMap<T> incomingMap, Function<T, T> deepCopier) {
StringCanonizingOpenHashMap<T> newMap = new StringCanonizingOpenHashMap<>(incomingMap.size(), 0.8f);
ObjectIterator<Entry<String, T>> iterator = incomingMap.object2ObjectEntrySet().fastIterator();
while (iterator.hasNext()) {
Map.Entry<String, T> entry = iterator.next();
newMap.putWithoutInterning(entry.getKey(), deepCopier.apply(entry.getValue()));
}
return newMap;
}
}

View File

@@ -1,171 +0,0 @@
package org.dreeam.leaf.util.math.random;
import com.google.common.annotations.VisibleForTesting;
import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.levelgen.BitRandomSource;
import net.minecraft.world.level.levelgen.PositionalRandomFactory;
import org.dreeam.leaf.config.modules.opt.FastRNG;
import java.util.concurrent.ThreadLocalRandom;
import java.util.random.RandomGenerator;
import java.util.random.RandomGeneratorFactory;
public class FasterRandomSource implements BitRandomSource {
private static final int INT_BITS = 48;
private static final long SEED_MASK = 0xFFFFFFFFFFFFL;
private static final long MULTIPLIER = 25214903917L;
private static final long INCREMENT = 11L;
private static final RandomGeneratorFactory<RandomGenerator> RANDOM_GENERATOR_FACTORY = RandomGeneratorFactory.of(FastRNG.randomGenerator);
private static final boolean isSplittableGenerator = RANDOM_GENERATOR_FACTORY.isSplittable();
private long seed;
private boolean useDirectImpl;
private RandomGenerator randomGenerator;
public static final FasterRandomSource SHARED_INSTANCE = new FasterRandomSource(ThreadLocalRandom.current().nextLong());
public FasterRandomSource(long seed) {
this.seed = seed;
this.randomGenerator = RANDOM_GENERATOR_FACTORY.create(seed);
this.useDirectImpl = FastRNG.useDirectImpl; // Get the value from config
}
private FasterRandomSource(long seed, RandomGenerator.SplittableGenerator randomGenerator) {
this.seed = seed;
this.randomGenerator = randomGenerator;
this.useDirectImpl = FastRNG.useDirectImpl;
}
@Override
public final RandomSource fork() {
if (isSplittableGenerator) {
return new FasterRandomSource(seed, ((RandomGenerator.SplittableGenerator) this.randomGenerator).split());
}
return new FasterRandomSource(this.nextLong());
}
@Override
public final PositionalRandomFactory forkPositional() {
return new FasterRandomSourcePositionalRandomFactory(this.seed);
}
@Override
public final void setSeed(long seed) {
this.seed = seed;
this.randomGenerator = RANDOM_GENERATOR_FACTORY.create(seed);
}
@Override
public final int next(int bits) {
if (useDirectImpl) {
// Direct
return (int) ((seed = seed * MULTIPLIER + INCREMENT & SEED_MASK) >>> (INT_BITS - bits));
} else {
// old
return (int) ((seed * MULTIPLIER + INCREMENT & SEED_MASK) >>> (INT_BITS - bits));
}
}
public static class FasterRandomSourcePositionalRandomFactory implements PositionalRandomFactory {
private final long seed;
public FasterRandomSourcePositionalRandomFactory(long seed) {
this.seed = seed;
}
@Override
public RandomSource at(int x, int y, int z) {
long l = Mth.getSeed(x, y, z);
long m = l ^ this.seed;
return new FasterRandomSource(m);
}
@Override
public RandomSource fromHashOf(String seed) {
int i = seed.hashCode();
return new FasterRandomSource((long) i ^ this.seed);
}
@Override
public RandomSource fromSeed(long seed) {
return new FasterRandomSource(seed);
}
@VisibleForTesting
@Override
public void parityConfigString(StringBuilder info) {
info.append("FasterRandomSourcePositionalRandomFactory{").append(this.seed).append("}");
}
}
@Override
public final int nextInt() {
if (useDirectImpl) {
return (int) (((seed = seed * MULTIPLIER + INCREMENT & SEED_MASK) >>> 16) ^
((seed = seed * MULTIPLIER + INCREMENT & SEED_MASK) >>> 32));
} else {
return randomGenerator.nextInt();
}
}
@Override
public final int nextInt(int bound) {
if (useDirectImpl && bound > 0) {
if ((bound & -bound) == bound) {
return (int) ((bound * (long) next(31)) >> 31);
}
int bits, val;
do {
bits = next(31);
val = bits % bound;
} while (bits - val + (bound - 1) < 0);
return val;
} else {
return randomGenerator.nextInt(bound);
}
}
@Override
public final long nextLong() {
if (useDirectImpl) {
return ((long) next(32) << 32) + next(32);
} else {
return randomGenerator.nextLong();
}
}
@Override
public final boolean nextBoolean() {
if (useDirectImpl) {
return next(1) != 0;
} else {
return randomGenerator.nextBoolean();
}
}
@Override
public final float nextFloat() {
if (useDirectImpl) {
return next(24) / ((float) (1 << 24));
} else {
return randomGenerator.nextFloat();
}
}
@Override
public final double nextDouble() {
if (useDirectImpl) {
return (((long) next(26) << 27) + next(27)) / (double) (1L << 53);
} else {
return randomGenerator.nextDouble();
}
}
@Override
public final double nextGaussian() {
// delegate Gaussian distribution to RandomGenerator
// as direct implementation would be complex (i aint doin allat)
return randomGenerator.nextGaussian();
}
}

View File

@@ -1,17 +0,0 @@
package org.dreeam.leaf.version;
import org.galemc.gale.version.AbstractPaperVersionFetcher;
public class LeafVersionFetcher extends AbstractPaperVersionFetcher {
public LeafVersionFetcher() {
super(
"ver/1.21.3",
"https://github.com/Winds-Studio/Leaf",
"Winds-Studio",
"Leaf",
"Winds-Studio",
"Leaf"
);
}
}

View File

@@ -1,179 +0,0 @@
// Gale - Gale commands - /gale command
package org.galemc.gale.command;
import io.papermc.paper.command.CommandUtil;
import it.unimi.dsi.fastutil.Pair;
import net.minecraft.Util;
import org.galemc.gale.command.subcommands.InfoCommand;
import org.galemc.gale.command.subcommands.ReloadCommand;
import org.galemc.gale.command.subcommands.VersionCommand;
import org.jetbrains.annotations.Nullable;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
import org.bukkit.plugin.PluginManager;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import static net.kyori.adventure.text.Component.newline;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
import static net.kyori.adventure.text.format.NamedTextColor.RED;
public final class GaleCommand extends Command {
public static final String COMMAND_LABEL = "gale";
public static final String BASE_PERM = GaleCommands.COMMAND_BASE_PERM + "." + COMMAND_LABEL;
private static final Permission basePermission = new Permission(BASE_PERM, PermissionDefault.TRUE);
// subcommand label -> subcommand
private static final GaleSubcommand RELOAD_SUBCOMMAND = new ReloadCommand();
private static final GaleSubcommand VERSION_SUBCOMMAND = new VersionCommand();
private static final GaleSubcommand INFO_SUBCOMMAND = new InfoCommand();
private static final Map<String, GaleSubcommand> SUBCOMMANDS = Util.make(() -> {
final Map<Set<String>, GaleSubcommand> commands = new HashMap<>();
commands.put(Set.of(ReloadCommand.LITERAL_ARGUMENT), RELOAD_SUBCOMMAND);
commands.put(Set.of(VersionCommand.LITERAL_ARGUMENT), VERSION_SUBCOMMAND);
commands.put(Set.of(InfoCommand.LITERAL_ARGUMENT), INFO_SUBCOMMAND);
return commands.entrySet().stream()
.flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue())))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
});
// alias -> subcommand label
private static final Map<String, String> ALIASES = Util.make(() -> {
final Map<String, Set<String>> aliases = new HashMap<>();
aliases.put(VersionCommand.LITERAL_ARGUMENT, Set.of("ver"));
aliases.put(InfoCommand.LITERAL_ARGUMENT, Set.of("about"));
return aliases.entrySet().stream()
.flatMap(entry -> entry.getValue().stream().map(s -> Map.entry(s, entry.getKey())))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
});
private String createUsageMessage(Collection<String> arguments) {
return "/" + COMMAND_LABEL + " [" + String.join(" | ", arguments) + "]";
}
public GaleCommand() {
super(COMMAND_LABEL);
this.description = "Gale related commands";
this.usageMessage = this.createUsageMessage(SUBCOMMANDS.keySet());
final List<Permission> permissions = SUBCOMMANDS.values().stream().map(GaleSubcommand::getPermission).filter(Objects::nonNull).toList();
this.setPermission(BASE_PERM);
final PluginManager pluginManager = Bukkit.getServer().getPluginManager();
pluginManager.addPermission(basePermission);
for (final Permission permission : permissions) {
pluginManager.addPermission(permission);
}
}
@Override
public List<String> tabComplete(
final CommandSender sender,
final String alias,
final String[] args,
final @Nullable Location location
) throws IllegalArgumentException {
if (args.length <= 1) {
List<String> subCommandArguments = new ArrayList<>(SUBCOMMANDS.size());
for (Map.Entry<String, GaleSubcommand> subCommandEntry : SUBCOMMANDS.entrySet()) {
if (subCommandEntry.getValue().testPermission(sender)) {
subCommandArguments.add(subCommandEntry.getKey());
}
}
return CommandUtil.getListMatchingLast(sender, args, subCommandArguments);
}
final @Nullable Pair<String, GaleSubcommand> subCommand = resolveCommand(args[0]);
if (subCommand != null && subCommand.second().testPermission(sender)) {
return subCommand.second().tabComplete(sender, subCommand.first(), Arrays.copyOfRange(args, 1, args.length));
}
return Collections.emptyList();
}
private boolean testHasOnePermission(CommandSender sender) {
for (Map.Entry<String, GaleSubcommand> subCommandEntry : SUBCOMMANDS.entrySet()) {
if (subCommandEntry.getValue().testPermission(sender)) {
return true;
}
}
return false;
}
@Override
public boolean execute(
final CommandSender sender,
final String commandLabel,
final String[] args
) {
// Check if the sender has the base permission and at least one specific permission
if (!sender.hasPermission(basePermission) || !this.testHasOnePermission(sender)) {
sender.sendMessage(Bukkit.permissionMessage());
return true;
}
// Determine the usage message with the subcommands they can perform
List<String> subCommandArguments = new ArrayList<>(SUBCOMMANDS.size());
for (Map.Entry<String, GaleSubcommand> subCommandEntry : SUBCOMMANDS.entrySet()) {
if (subCommandEntry.getValue().testPermission(sender)) {
subCommandArguments.add(subCommandEntry.getKey());
}
}
String specificUsageMessage = this.createUsageMessage(subCommandArguments);
// If they did not give a subcommand
if (args.length == 0) {
INFO_SUBCOMMAND.execute(sender, InfoCommand.LITERAL_ARGUMENT, me.titaniumtown.ArrayConstants.emptyStringArray); // Gale - JettPack - reduce array allocations
sender.sendMessage(newline().append(text("Command usage: " + specificUsageMessage, GRAY)));
return false;
}
// If they do not have permission for the subcommand they gave, or the argument is not a valid subcommand
final @Nullable Pair<String, GaleSubcommand> subCommand = resolveCommand(args[0]);
if (subCommand == null || !subCommand.second().testPermission(sender)) {
sender.sendMessage(text("Usage: " + specificUsageMessage, RED));
return false;
}
// Execute the subcommand
final String[] choppedArgs = Arrays.copyOfRange(args, 1, args.length);
return subCommand.second().execute(sender, subCommand.first(), choppedArgs);
}
private static @Nullable Pair<String, GaleSubcommand> resolveCommand(String label) {
label = label.toLowerCase(Locale.ENGLISH);
@Nullable GaleSubcommand subCommand = SUBCOMMANDS.get(label);
if (subCommand == null) {
final @Nullable String command = ALIASES.get(label);
if (command != null) {
label = command;
subCommand = SUBCOMMANDS.get(command);
}
}
if (subCommand != null) {
return Pair.of(label, subCommand);
}
return null;
}
}

View File

@@ -1,33 +0,0 @@
// Gale - Gale commands
package org.galemc.gale.command;
import net.minecraft.server.MinecraftServer;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.framework.qual.DefaultQualifier;
import org.bukkit.command.Command;
import org.bukkit.craftbukkit.util.permissions.CraftDefaultPermissions;
import java.util.HashMap;
import java.util.Map;
@DefaultQualifier(NonNull.class)
public final class GaleCommands {
public static final String COMMAND_BASE_PERM = CraftDefaultPermissions.GALE_ROOT + ".command";
private GaleCommands() {
}
private static final Map<String, Command> COMMANDS = new HashMap<>();
static {
COMMANDS.put(GaleCommand.COMMAND_LABEL, new GaleCommand());
}
public static void registerCommands(final MinecraftServer server) {
COMMANDS.forEach((s, command) ->
server.server.getCommandMap().register(s, "Gale", command)
);
}
}

View File

@@ -1,27 +0,0 @@
// Gale - Gale commands
package org.galemc.gale.command;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.framework.qual.DefaultQualifier;
import org.jetbrains.annotations.Nullable;
import org.bukkit.command.CommandSender;
import org.bukkit.permissions.Permission;
import java.util.Collections;
import java.util.List;
@DefaultQualifier(NonNull.class)
public interface GaleSubcommand {
boolean execute(CommandSender sender, String subCommand, String[] args);
default List<String> tabComplete(final CommandSender sender, final String subCommand, final String[] args) {
return Collections.emptyList();
}
boolean testPermission(CommandSender sender);
@Nullable Permission getPermission();
}

View File

@@ -1,32 +0,0 @@
// Gale - Gale commands
package org.galemc.gale.command;
import org.jetbrains.annotations.Nullable;
import org.bukkit.command.CommandSender;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
public abstract class PermissionedGaleSubcommand implements GaleSubcommand {
public final Permission permission;
protected PermissionedGaleSubcommand(Permission permission) {
this.permission = permission;
}
protected PermissionedGaleSubcommand(String permission, PermissionDefault permissionDefault) {
this(new Permission(permission, permissionDefault));
}
@Override
public boolean testPermission(CommandSender sender) {
return sender.hasPermission(this.permission);
}
@Override
public @Nullable Permission getPermission() {
return this.permission;
}
}

Some files were not shown because too many files have changed in this diff Show More