9
0
mirror of https://github.com/Winds-Studio/Leaf.git synced 2025-12-29 03:49:21 +00:00

Some work

This commit is contained in:
Dreeam
2025-04-04 16:06:47 -04:00
parent 91887de295
commit f96b3e9f76
39 changed files with 135 additions and 224 deletions

View File

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

@@ -0,0 +1,33 @@
package org.dreeam.leaf.async;
import org.dreeam.leaf.config.modules.async.AsyncPlayerDataSave;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class AsyncPlayerDataSaving {
public static final ExecutorService IO_POOL = new ThreadPoolExecutor(
1, 1, 0, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(),
new com.google.common.util.concurrent.ThreadFactoryBuilder()
.setPriority(Thread.NORM_PRIORITY - 2)
.setNameFormat("Leaf IO Thread")
.setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER))
.build(),
new ThreadPoolExecutor.DiscardPolicy()
);
private AsyncPlayerDataSaving() {
}
public static void save(Runnable runnable) {
if (!AsyncPlayerDataSave.enabled) {
runnable.run();
} else {
IO_POOL.execute(runnable);
}
}
}

View File

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

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

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

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

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

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

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

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

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

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

@@ -87,6 +87,9 @@ public class GaleWorldConfiguration extends ConfigurationPart {
public int duration = 100;
public int nearbyItemMaxAge = 1200;
public int checkForMinecartNearItemInterval = 20;
// Still recommend to turn-off `checkForMinecartNearItemWhileActive`
// Since `Reduce-hopper-item-checks.patch` will cause lag under massive dropped items
public boolean checkForMinecartNearItemWhileActive = false; // Leaf - Reduce active items finding hopper nearby check
public boolean checkForMinecartNearItemWhileInactive = true;
public double maxItemHorizontalDistance = 24.0;
public double maxItemVerticalDistance = 4.0;
@@ -132,7 +135,7 @@ public class GaleWorldConfiguration extends ConfigurationPart {
}
public boolean arrowMovementResetsDespawnCounter = true; // Gale - Purpur - make arrow movement resetting despawn counter configurable
public boolean arrowMovementResetsDespawnCounter = false; // Gale - Purpur - make arrow movement resetting despawn counter configurable // Leaf - KeYi - Disable arrow despawn counter by default
public boolean entitiesCanRandomStrollIntoNonTickingChunks = true; // Gale - MultiPaper - prevent entities random strolling into non-ticking chunks
public double entityWakeUpDurationRatioStandardDeviation = 0.2; // Gale - variable entity wake-up duration
public boolean hideFlamesOnEntitiesWithFireResistance = false; // Gale - Slice - hide flames on entities with fire resistance

View File

@@ -101,10 +101,10 @@ public abstract class AbstractPaperVersionFetcher implements VersionFetcher {
// Gale end - branding changes - version fetcher
return switch (distance) {
case DISTANCE_ERROR -> text("Error obtaining version information", NamedTextColor.YELLOW);
case 0 -> text("You are running the latest version", NamedTextColor.GREEN);
case DISTANCE_UNKNOWN -> text("Unknown version", NamedTextColor.YELLOW);
default -> text("You are " + distance + " version(s) behind", NamedTextColor.YELLOW)
case DISTANCE_ERROR -> text("* Error obtaining version information", NamedTextColor.RED); // Purpur - Rebrand
case 0 -> text("* You are running the latest version", NamedTextColor.GREEN); // Purpur - Rebrand
case DISTANCE_UNKNOWN -> text("* Unknown version", NamedTextColor.YELLOW); // Purpur - Rebrand
default -> text("* You are " + distance + " version(s) behind", NamedTextColor.YELLOW) // Purpur - Rebrand
.append(Component.newline())
.append(text("Download the new version at: ")
.append(text(this.downloadPage, NamedTextColor.GOLD) // Gale - branding changes - version fetcher
@@ -149,6 +149,6 @@ public abstract class AbstractPaperVersionFetcher implements VersionFetcher {
return null;
}
return text("Previous version: " + oldVersion, NamedTextColor.GRAY, TextDecoration.ITALIC);
return text("Previous: " + oldVersion, NamedTextColor.GRAY, TextDecoration.ITALIC); // Purpur - Rebrand
}
}