9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-19 15:09:15 +00:00

完善剔除线程调度

This commit is contained in:
XiaoMoMi
2025-11-29 04:20:49 +08:00
parent 19d1cb2a21
commit b77dbdfca9
14 changed files with 164 additions and 72 deletions

View File

@@ -84,6 +84,7 @@ import net.momirealms.craftengine.core.plugin.context.NetworkTextReplaceContext;
import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext;
import net.momirealms.craftengine.core.plugin.context.event.EventTrigger; import net.momirealms.craftengine.core.plugin.context.event.EventTrigger;
import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters;
import net.momirealms.craftengine.core.plugin.entityculling.EntityCullingThread;
import net.momirealms.craftengine.core.plugin.locale.TranslationManager; import net.momirealms.craftengine.core.plugin.locale.TranslationManager;
import net.momirealms.craftengine.core.plugin.logger.Debugger; import net.momirealms.craftengine.core.plugin.logger.Debugger;
import net.momirealms.craftengine.core.plugin.network.*; import net.momirealms.craftengine.core.plugin.network.*;
@@ -128,7 +129,6 @@ import java.nio.charset.StandardCharsets;
import java.time.Instant; import java.time.Instant;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Predicate; import java.util.function.Predicate;
@@ -462,14 +462,6 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
player.getScheduler().runAtFixedRate(plugin.javaPlugin(), (t) -> user.tick(), player.getScheduler().runAtFixedRate(plugin.javaPlugin(), (t) -> user.tick(),
() -> {}, 1, 1); () -> {}, 1, 1);
} }
// 安排异步tick任务
this.plugin.scheduler().asyncRepeating(t -> {
if (!user.isOnline()) {
t.cancel();
return;
}
user.asyncTick();
}, 50, 50, TimeUnit.MILLISECONDS);
user.sendPacket(TotemAnimationCommand.FIX_TOTEM_SOUND_PACKET, false); user.sendPacket(TotemAnimationCommand.FIX_TOTEM_SOUND_PACKET, false);
} }
} }

View File

@@ -507,9 +507,6 @@ public class BukkitServerPlayer extends Player {
this.encoderState = encoderState; this.encoderState = encoderState;
} }
private final Queue<Long> recentDurations = new LinkedList<>();
private static final int SAMPLE_SIZE = 100;
@Override @Override
public void tick() { public void tick() {
// not fully online // not fully online
@@ -569,38 +566,16 @@ public class BukkitServerPlayer extends Player {
} }
} }
public void asyncTick() { @Override
if (Config.enableEntityCulling()) { public void entityCullingTick() {
long nano1 = System.nanoTime(); for (VirtualCullableObject cullableObject : this.trackedBlockEntityRenderers.values()) {
for (VirtualCullableObject cullableObject : this.trackedBlockEntityRenderers.values()) { CullingData cullingData = cullableObject.cullable.cullingData();
CullingData cullingData = cullableObject.cullable.cullingData(); if (cullingData != null) {
if (cullingData != null) { Vec3d vec3d = LocationUtils.toVec3d(platformPlayer().getEyeLocation());
Vec3d vec3d = LocationUtils.toVec3d(platformPlayer().getEyeLocation()); boolean visible = this.culling.isVisible(cullingData, vec3d);
boolean visible = this.culling.isVisible(cullingData, vec3d); cullableObject.setShown(this, visible);
cullableObject.setShown(this, visible); } else {
} else { cullableObject.setShown(this, true);
cullableObject.setShown(this, true);
}
}
long nano2 = System.nanoTime();
long duration = nano2 - nano1;
// 添加到队列
recentDurations.offer(duration);
// 保持队列大小为100
if (recentDurations.size() > SAMPLE_SIZE) {
recentDurations.poll();
}
// 每100次输出一次平均耗时
if (recentDurations.size() == SAMPLE_SIZE) {
double averageMs = recentDurations.stream()
.mapToLong(Long::longValue)
.average()
.orElse(0) / 1_000_000d;
CraftEngine.instance().logger().info("EntityCulling 最近100次平均耗时: " + averageMs + "ms");
} }
} }
} }

View File

@@ -557,6 +557,8 @@ client-optimization:
entity-culling: entity-culling:
enable: true enable: true
view-distance: 64 view-distance: 64
# Determining the number of threads to execute these raytrace operations
threads: 1
# Enables or disables debug mode # Enables or disables debug mode
debug: debug:
@@ -564,4 +566,6 @@ debug:
packet: false packet: false
furniture: false furniture: false
item: false item: false
resource-pack: false resource-pack: false
block: false
entity-culling: false

View File

@@ -32,7 +32,7 @@ public class TickingBlockEntityImpl<T extends BlockEntity> implements TickingBlo
// 不是合法方块 // 不是合法方块
if (!this.blockEntity.isValidBlockState(state)) { if (!this.blockEntity.isValidBlockState(state)) {
this.chunk.removeBlockEntity(pos); this.chunk.removeBlockEntity(pos);
Debugger.BLOCK_ENTITY.warn(() -> "Invalid block entity(" + this.blockEntity.getClass().getSimpleName() + ") with state " + state + " found at world " + this.chunk.world().name() + " " + pos, null); Debugger.BLOCK.warn(() -> "Invalid block entity(" + this.blockEntity.getClass().getSimpleName() + ") with state " + state + " found at world " + this.chunk.world().name() + " " + pos, null);
return; return;
} }
try { try {

View File

@@ -38,6 +38,8 @@ public abstract class Player extends AbstractEntity implements NetWorkUser {
public abstract void setClientSideWorld(World world); public abstract void setClientSideWorld(World world);
public abstract void entityCullingTick();
public abstract float getDestroyProgress(Object blockState, BlockPos pos); public abstract float getDestroyProgress(Object blockState, BlockPos pos);
public abstract void setClientSideCanBreakBlock(boolean canBreak); public abstract void setClientSideCanBreakBlock(boolean canBreak);

View File

@@ -26,6 +26,8 @@ import net.momirealms.craftengine.core.plugin.dependency.Dependencies;
import net.momirealms.craftengine.core.plugin.dependency.Dependency; import net.momirealms.craftengine.core.plugin.dependency.Dependency;
import net.momirealms.craftengine.core.plugin.dependency.DependencyManager; import net.momirealms.craftengine.core.plugin.dependency.DependencyManager;
import net.momirealms.craftengine.core.plugin.dependency.DependencyManagerImpl; import net.momirealms.craftengine.core.plugin.dependency.DependencyManagerImpl;
import net.momirealms.craftengine.core.plugin.entityculling.EntityCullingManager;
import net.momirealms.craftengine.core.plugin.entityculling.EntityCullingManagerImpl;
import net.momirealms.craftengine.core.plugin.gui.GuiManager; import net.momirealms.craftengine.core.plugin.gui.GuiManager;
import net.momirealms.craftengine.core.plugin.gui.category.ItemBrowserManager; import net.momirealms.craftengine.core.plugin.gui.category.ItemBrowserManager;
import net.momirealms.craftengine.core.plugin.gui.category.ItemBrowserManagerImpl; import net.momirealms.craftengine.core.plugin.gui.category.ItemBrowserManagerImpl;
@@ -79,6 +81,7 @@ public abstract class CraftEngine implements Plugin {
protected GlobalVariableManager globalVariableManager; protected GlobalVariableManager globalVariableManager;
protected ProjectileManager projectileManager; protected ProjectileManager projectileManager;
protected SeatManager seatManager; protected SeatManager seatManager;
protected EntityCullingManager entityCullingManager;
private final PluginTaskRegistry beforeEnableTaskRegistry = new PluginTaskRegistry(); private final PluginTaskRegistry beforeEnableTaskRegistry = new PluginTaskRegistry();
private final PluginTaskRegistry afterEnableTaskRegistry = new PluginTaskRegistry(); private final PluginTaskRegistry afterEnableTaskRegistry = new PluginTaskRegistry();
@@ -118,6 +121,8 @@ public abstract class CraftEngine implements Plugin {
this.globalVariableManager = new GlobalVariableManager(); this.globalVariableManager = new GlobalVariableManager();
// 初始化物品浏览器 // 初始化物品浏览器
this.itemBrowserManager = new ItemBrowserManagerImpl(this); this.itemBrowserManager = new ItemBrowserManagerImpl(this);
// 初始化实体剔除器
this.entityCullingManager = new EntityCullingManagerImpl();
} }
public void setUpConfigAndLocale() { public void setUpConfigAndLocale() {
@@ -158,6 +163,7 @@ public abstract class CraftEngine implements Plugin {
this.advancementManager.reload(); this.advancementManager.reload();
this.projectileManager.reload(); this.projectileManager.reload();
this.seatManager.reload(); this.seatManager.reload();
this.entityCullingManager.reload();
} }
private void runDelayTasks(boolean reloadRecipe) { private void runDelayTasks(boolean reloadRecipe) {
@@ -349,6 +355,7 @@ public abstract class CraftEngine implements Plugin {
if (this.translationManager != null) this.translationManager.disable(); if (this.translationManager != null) this.translationManager.disable();
if (this.globalVariableManager != null) this.globalVariableManager.disable(); if (this.globalVariableManager != null) this.globalVariableManager.disable();
if (this.projectileManager != null) this.projectileManager.disable(); if (this.projectileManager != null) this.projectileManager.disable();
if (this.entityCullingManager != null) this.entityCullingManager.disable();
if (this.scheduler != null) this.scheduler.shutdownScheduler(); if (this.scheduler != null) this.scheduler.shutdownScheduler();
if (this.scheduler != null) this.scheduler.shutdownExecutor(); if (this.scheduler != null) this.scheduler.shutdownExecutor();
if (this.commandManager != null) this.commandManager.unregisterFeatures(); if (this.commandManager != null) this.commandManager.unregisterFeatures();

View File

@@ -54,6 +54,8 @@ public class Config {
protected boolean debug$item; protected boolean debug$item;
protected boolean debug$furniture; protected boolean debug$furniture;
protected boolean debug$resource_pack; protected boolean debug$resource_pack;
protected boolean debug$block;
protected boolean debug$entity_culling;
protected boolean resource_pack$remove_tinted_leaves_particle; protected boolean resource_pack$remove_tinted_leaves_particle;
protected boolean resource_pack$generate_mod_assets; protected boolean resource_pack$generate_mod_assets;
@@ -205,6 +207,7 @@ public class Config {
protected boolean client_optimization$entity_culling$enable; protected boolean client_optimization$entity_culling$enable;
protected int client_optimization$entity_culling$view_distance; protected int client_optimization$entity_culling$view_distance;
protected int client_optimization$entity_culling$threads;
public Config(CraftEngine plugin) { public Config(CraftEngine plugin) {
this.plugin = plugin; this.plugin = plugin;
@@ -308,6 +311,8 @@ public class Config {
debug$item = config.getBoolean("debug.item", false); debug$item = config.getBoolean("debug.item", false);
debug$furniture = config.getBoolean("debug.furniture", false); debug$furniture = config.getBoolean("debug.furniture", false);
debug$resource_pack = config.getBoolean("debug.resource-pack", false); debug$resource_pack = config.getBoolean("debug.resource-pack", false);
debug$block = config.getBoolean("debug.block", false);
debug$entity_culling = config.getBoolean("debug.entity-culling", false);
// resource pack // resource pack
resource_pack$path = resolvePath(config.getString("resource-pack.path", "./generated/resource_pack.zip")); resource_pack$path = resolvePath(config.getString("resource-pack.path", "./generated/resource_pack.zip"));
@@ -606,12 +611,12 @@ public class Config {
return instance.debug$item; return instance.debug$item;
} }
public static boolean debugBlockEntity() { public static boolean debugBlock() {
return false; return instance.debug$block;
} }
public static boolean debugBlock() { public static boolean debugEntityCulling() {
return false; return instance.debug$entity_culling;
} }
public static boolean debugFurniture() { public static boolean debugFurniture() {
@@ -1167,6 +1172,10 @@ public class Config {
return instance.client_optimization$entity_culling$view_distance; return instance.client_optimization$entity_culling$view_distance;
} }
public static int entityCullingThreads() {
return instance.client_optimization$entity_culling$threads;
}
public YamlDocument loadOrCreateYamlData(String fileName) { public YamlDocument loadOrCreateYamlData(String fileName) {
Path path = this.plugin.dataFolderPath().resolve(fileName); Path path = this.plugin.dataFolderPath().resolve(fileName);
if (!Files.exists(path)) { if (!Files.exists(path)) {

View File

@@ -93,24 +93,24 @@ public final class EntityCulling {
} }
int size = 0; int size = 0;
if (this.dotSelectors[0]) targetPoints[size++].set(minX, minY, minZ); if (this.dotSelectors[0]) targetPoints[size++].set(minX + 0.05, minY + 0.05, minZ + 0.05);
if (this.dotSelectors[1]) targetPoints[size++].set(maxX, minY, minZ); if (this.dotSelectors[1]) targetPoints[size++].set(maxX - 0.05, minY + 0.05, minZ + 0.05);
if (this.dotSelectors[2]) targetPoints[size++].set(minX, minY, maxZ); if (this.dotSelectors[2]) targetPoints[size++].set(minX + 0.05, minY + 0.05, maxZ - 0.05);
if (this.dotSelectors[3]) targetPoints[size++].set(maxX, minY, maxZ); if (this.dotSelectors[3]) targetPoints[size++].set(maxX - 0.05, minY + 0.05, maxZ - 0.05);
if (this.dotSelectors[4]) targetPoints[size++].set(minX, maxY, minZ); if (this.dotSelectors[4]) targetPoints[size++].set(minX + 0.05, maxY - 0.05, minZ + 0.05);
if (this.dotSelectors[5]) targetPoints[size++].set(maxX, maxY, minZ); if (this.dotSelectors[5]) targetPoints[size++].set(maxX - 0.05, maxY - 0.05, minZ + 0.05);
if (this.dotSelectors[6]) targetPoints[size++].set(minX, maxY, maxZ); if (this.dotSelectors[6]) targetPoints[size++].set(minX + 0.05, maxY - 0.05, maxZ - 0.05);
if (this.dotSelectors[7]) targetPoints[size++].set(maxX, maxY, maxZ); if (this.dotSelectors[7]) targetPoints[size++].set(maxX - 0.05, maxY - 0.05, maxZ - 0.05);
// 面中心点 // 面中心点
double averageX = (minX + maxX) / 2.0; double averageX = (minX + maxX) / 2.0;
double averageY = (minY + maxY) / 2.0; double averageY = (minY + maxY) / 2.0;
double averageZ = (minZ + maxZ) / 2.0; double averageZ = (minZ + maxZ) / 2.0;
if (this.dotSelectors[8]) targetPoints[size++].set(averageX, averageY, minZ); if (this.dotSelectors[8]) targetPoints[size++].set(averageX, averageY, minZ + 0.05);
if (this.dotSelectors[9]) targetPoints[size++].set(averageX, averageY, maxZ); if (this.dotSelectors[9]) targetPoints[size++].set(averageX, averageY, maxZ - 0.05);
if (this.dotSelectors[10]) targetPoints[size++].set(minX, averageY, averageZ); if (this.dotSelectors[10]) targetPoints[size++].set(minX + 0.05, averageY, averageZ);
if (this.dotSelectors[11]) targetPoints[size++].set(maxX, averageY, averageZ); if (this.dotSelectors[11]) targetPoints[size++].set(maxX - 0.05, averageY, averageZ);
if (this.dotSelectors[12]) targetPoints[size++].set(averageX, minY, averageZ); if (this.dotSelectors[12]) targetPoints[size++].set(averageX, minY + 0.05, averageZ);
if (this.dotSelectors[13]) targetPoints[size].set(averageX, maxY, averageZ); if (this.dotSelectors[13]) targetPoints[size].set(averageX, maxY - 0.05, averageZ);
return isVisible(cameraPos, this.targetPoints, size); return isVisible(cameraPos, this.targetPoints, size);
} }
@@ -194,9 +194,9 @@ public final class EntityCulling {
// 预计算每单位距离在各方块边界上的步进增量 // 预计算每单位距离在各方块边界上的步进增量
// 这些值表示射线穿过一个方块所需的时间分数 // 这些值表示射线穿过一个方块所需的时间分数
double stepIncrementX = 1.0 / (absDeltaX + 1e-10); // 避免除0 double stepIncrementX = 1.0 / absDeltaX;
double stepIncrementY = 1.0 / (absDeltaY + 1e-10); double stepIncrementY = 1.0 / absDeltaY;
double stepIncrementZ = 1.0 / (absDeltaZ + 1e-10); double stepIncrementZ = 1.0 / absDeltaZ;
// 射线将穿过的总方块数量(包括起点和终点) // 射线将穿过的总方块数量(包括起点和终点)
int totalBlocksToCheck = 1; int totalBlocksToCheck = 1;
@@ -286,8 +286,8 @@ public final class EntityCulling {
int currentBlockY = startingY; int currentBlockY = startingY;
int currentBlockZ = startingZ; int currentBlockZ = startingZ;
// 遍历射线路径上的所有方块(跳过最后一个目标方块) // 遍历射线路径上的所有方块
for (; remainingSteps > 1; remainingSteps--) { for (; remainingSteps > 0; remainingSteps--) {
// 检查当前方块是否遮挡视线 // 检查当前方块是否遮挡视线
if (isOccluding(currentBlockX, currentBlockY, currentBlockZ)) { if (isOccluding(currentBlockX, currentBlockY, currentBlockZ)) {

View File

@@ -0,0 +1,6 @@
package net.momirealms.craftengine.core.plugin.entityculling;
import net.momirealms.craftengine.core.plugin.Manageable;
public interface EntityCullingManager extends Manageable {
}

View File

@@ -0,0 +1,32 @@
package net.momirealms.craftengine.core.plugin.entityculling;
import net.momirealms.craftengine.core.plugin.config.Config;
import java.util.ArrayList;
import java.util.List;
public class EntityCullingManagerImpl implements EntityCullingManager {
private final List<EntityCullingThread> threads = new ArrayList<>();
@Override
public void load() {
if (Config.enableEntityCulling()) {
int threads = Math.min(64, Math.max(Config.entityCullingThreads(), 1));
for (int i = 0; i < threads; i++) {
EntityCullingThread thread = new EntityCullingThread(i, threads);
this.threads.add(thread);
thread.start();
}
}
}
@Override
public void unload() {
if (!this.threads.isEmpty()) {
for (EntityCullingThread thread : this.threads) {
thread.stop();
}
this.threads.clear();
}
}
}

View File

@@ -0,0 +1,65 @@
package net.momirealms.craftengine.core.plugin.entityculling;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.logger.Debugger;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public class EntityCullingThread {
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
private final AtomicBoolean isRunning = new AtomicBoolean(false);
private final int id;
private final int threads;
public EntityCullingThread(int id, int threads) {
this.id = id;
this.threads = threads;
}
public void start() {
// 错开线程启动时间,避免所有线程同时执行
long initialDelay = this.id * (50L / this.threads);
this.scheduler.scheduleAtFixedRate(this::scheduleTask, initialDelay, 50, TimeUnit.MILLISECONDS);
}
private void scheduleTask() {
// 使用CAS操作更安全
if (!this.isRunning.compareAndSet(false, true)) {
return;
}
this.scheduler.execute(() -> {
try {
int processed = 0;
long startTime = System.currentTimeMillis();
for (Player player : CraftEngine.instance().networkManager().onlineUsers()) {
// 使用绝对值确保非负,使用 threads 而不是 threads-1 确保均匀分布
if (Math.abs(player.uuid().hashCode()) % this.threads == this.id) {
player.entityCullingTick();
processed++;
}
}
long duration = System.currentTimeMillis() - startTime;
if (duration > 45) {
String value = String.format("EntityCullingThread-%d processed %d players in %dms (over 45ms)",
this.id, processed, duration);
Debugger.ENTITY_CULLING.debug(() -> value);
}
} catch (Exception e) {
CraftEngine.instance().logger().severe("Failed to run entity culling tick: " + e.getMessage());
} finally {
this.isRunning.set(false);
}
});
}
public void stop() {
this.scheduler.shutdown();
}
}

View File

@@ -12,7 +12,7 @@ public enum Debugger {
RESOURCE_PACK(Config::debugResourcePack), RESOURCE_PACK(Config::debugResourcePack),
ITEM(Config::debugItem), ITEM(Config::debugItem),
BLOCK(Config::debugBlock), BLOCK(Config::debugBlock),
BLOCK_ENTITY(Config::debugBlockEntity); ENTITY_CULLING(Config::debugEntityCulling);
private final Supplier<Boolean> condition; private final Supplier<Boolean> condition;

View File

@@ -462,7 +462,7 @@ public class CEChunk {
BlockPos pos = blockEntity.pos(); BlockPos pos = blockEntity.pos();
ImmutableBlockState blockState = this.getBlockState(pos); ImmutableBlockState blockState = this.getBlockState(pos);
if (!blockState.hasBlockEntity()) { if (!blockState.hasBlockEntity()) {
Debugger.BLOCK_ENTITY.debug(() -> "Failed to add invalid block entity " + blockEntity.saveAsTag() + " at " + pos); Debugger.BLOCK.debug(() -> "Failed to add invalid block entity " + blockEntity.saveAsTag() + " at " + pos);
return; return;
} }
// 设置方块实体所在世界 // 设置方块实体所在世界

View File

@@ -38,7 +38,7 @@ public final class DefaultBlockEntitySerializer {
Key id = Key.of(data.getString("id")); Key id = Key.of(data.getString("id"));
BlockEntityType<?> type = BuiltInRegistries.BLOCK_ENTITY_TYPE.getValue(id); BlockEntityType<?> type = BuiltInRegistries.BLOCK_ENTITY_TYPE.getValue(id);
if (type == null) { if (type == null) {
Debugger.BLOCK_ENTITY.debug(() -> "Unknown block entity type: " + id); Debugger.BLOCK.debug(() -> "Unknown block entity type: " + id);
} else { } else {
BlockPos pos = BlockEntity.readPosAndVerify(data, chunk.chunkPos()); BlockPos pos = BlockEntity.readPosAndVerify(data, chunk.chunkPos());
ImmutableBlockState blockState = chunk.getBlockState(pos); ImmutableBlockState blockState = chunk.getBlockState(pos);