diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java index ba75b5f5e..df5990e82 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java @@ -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.event.EventTrigger; 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.logger.Debugger; import net.momirealms.craftengine.core.plugin.network.*; @@ -128,7 +129,6 @@ import java.nio.charset.StandardCharsets; import java.time.Instant; import java.util.*; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; import java.util.function.BiFunction; import java.util.function.Predicate; @@ -462,14 +462,6 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes player.getScheduler().runAtFixedRate(plugin.javaPlugin(), (t) -> user.tick(), () -> {}, 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); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java index dd4c4e247..2bd1ed888 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java @@ -507,9 +507,6 @@ public class BukkitServerPlayer extends Player { this.encoderState = encoderState; } - private final Queue recentDurations = new LinkedList<>(); - private static final int SAMPLE_SIZE = 100; - @Override public void tick() { // not fully online @@ -569,38 +566,16 @@ public class BukkitServerPlayer extends Player { } } - public void asyncTick() { - if (Config.enableEntityCulling()) { - long nano1 = System.nanoTime(); - for (VirtualCullableObject cullableObject : this.trackedBlockEntityRenderers.values()) { - CullingData cullingData = cullableObject.cullable.cullingData(); - if (cullingData != null) { - Vec3d vec3d = LocationUtils.toVec3d(platformPlayer().getEyeLocation()); - boolean visible = this.culling.isVisible(cullingData, vec3d); - cullableObject.setShown(this, visible); - } else { - 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"); + @Override + public void entityCullingTick() { + for (VirtualCullableObject cullableObject : this.trackedBlockEntityRenderers.values()) { + CullingData cullingData = cullableObject.cullable.cullingData(); + if (cullingData != null) { + Vec3d vec3d = LocationUtils.toVec3d(platformPlayer().getEyeLocation()); + boolean visible = this.culling.isVisible(cullingData, vec3d); + cullableObject.setShown(this, visible); + } else { + cullableObject.setShown(this, true); } } } diff --git a/common-files/src/main/resources/config.yml b/common-files/src/main/resources/config.yml index d2a376c04..06785ab16 100644 --- a/common-files/src/main/resources/config.yml +++ b/common-files/src/main/resources/config.yml @@ -557,6 +557,8 @@ client-optimization: entity-culling: enable: true view-distance: 64 + # Determining the number of threads to execute these raytrace operations + threads: 1 # Enables or disables debug mode debug: @@ -564,4 +566,6 @@ debug: packet: false furniture: false item: false - resource-pack: false \ No newline at end of file + resource-pack: false + block: false + entity-culling: false \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/tick/TickingBlockEntityImpl.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/tick/TickingBlockEntityImpl.java index 12d09ee8c..85d4a1aba 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/tick/TickingBlockEntityImpl.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/tick/TickingBlockEntityImpl.java @@ -32,7 +32,7 @@ public class TickingBlockEntityImpl implements TickingBlo // 不是合法方块 if (!this.blockEntity.isValidBlockState(state)) { 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; } try { diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java b/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java index 25b098bb1..7c0be0248 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java @@ -38,6 +38,8 @@ public abstract class Player extends AbstractEntity implements NetWorkUser { public abstract void setClientSideWorld(World world); + public abstract void entityCullingTick(); + public abstract float getDestroyProgress(Object blockState, BlockPos pos); public abstract void setClientSideCanBreakBlock(boolean canBreak); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java index 2c32adc8b..3075ea81e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java @@ -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.DependencyManager; 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.category.ItemBrowserManager; import net.momirealms.craftengine.core.plugin.gui.category.ItemBrowserManagerImpl; @@ -79,6 +81,7 @@ public abstract class CraftEngine implements Plugin { protected GlobalVariableManager globalVariableManager; protected ProjectileManager projectileManager; protected SeatManager seatManager; + protected EntityCullingManager entityCullingManager; private final PluginTaskRegistry beforeEnableTaskRegistry = new PluginTaskRegistry(); private final PluginTaskRegistry afterEnableTaskRegistry = new PluginTaskRegistry(); @@ -118,6 +121,8 @@ public abstract class CraftEngine implements Plugin { this.globalVariableManager = new GlobalVariableManager(); // 初始化物品浏览器 this.itemBrowserManager = new ItemBrowserManagerImpl(this); + // 初始化实体剔除器 + this.entityCullingManager = new EntityCullingManagerImpl(); } public void setUpConfigAndLocale() { @@ -158,6 +163,7 @@ public abstract class CraftEngine implements Plugin { this.advancementManager.reload(); this.projectileManager.reload(); this.seatManager.reload(); + this.entityCullingManager.reload(); } private void runDelayTasks(boolean reloadRecipe) { @@ -349,6 +355,7 @@ public abstract class CraftEngine implements Plugin { if (this.translationManager != null) this.translationManager.disable(); if (this.globalVariableManager != null) this.globalVariableManager.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.shutdownExecutor(); if (this.commandManager != null) this.commandManager.unregisterFeatures(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java index d0b501b11..9b7473db3 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java @@ -54,6 +54,8 @@ public class Config { protected boolean debug$item; protected boolean debug$furniture; 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$generate_mod_assets; @@ -205,6 +207,7 @@ public class Config { protected boolean client_optimization$entity_culling$enable; protected int client_optimization$entity_culling$view_distance; + protected int client_optimization$entity_culling$threads; public Config(CraftEngine plugin) { this.plugin = plugin; @@ -308,6 +311,8 @@ public class Config { debug$item = config.getBoolean("debug.item", false); debug$furniture = config.getBoolean("debug.furniture", 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$path = resolvePath(config.getString("resource-pack.path", "./generated/resource_pack.zip")); @@ -606,12 +611,12 @@ public class Config { return instance.debug$item; } - public static boolean debugBlockEntity() { - return false; + public static boolean debugBlock() { + return instance.debug$block; } - public static boolean debugBlock() { - return false; + public static boolean debugEntityCulling() { + return instance.debug$entity_culling; } public static boolean debugFurniture() { @@ -1167,6 +1172,10 @@ public class Config { return instance.client_optimization$entity_culling$view_distance; } + public static int entityCullingThreads() { + return instance.client_optimization$entity_culling$threads; + } + public YamlDocument loadOrCreateYamlData(String fileName) { Path path = this.plugin.dataFolderPath().resolve(fileName); if (!Files.exists(path)) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCulling.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCulling.java index ecb9d9fdd..4304dcc72 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCulling.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCulling.java @@ -93,24 +93,24 @@ public final class EntityCulling { } int size = 0; - if (this.dotSelectors[0]) targetPoints[size++].set(minX, minY, minZ); - if (this.dotSelectors[1]) targetPoints[size++].set(maxX, minY, minZ); - if (this.dotSelectors[2]) targetPoints[size++].set(minX, minY, maxZ); - if (this.dotSelectors[3]) targetPoints[size++].set(maxX, minY, maxZ); - if (this.dotSelectors[4]) targetPoints[size++].set(minX, maxY, minZ); - if (this.dotSelectors[5]) targetPoints[size++].set(maxX, maxY, minZ); - if (this.dotSelectors[6]) targetPoints[size++].set(minX, maxY, maxZ); - if (this.dotSelectors[7]) targetPoints[size++].set(maxX, maxY, maxZ); + if (this.dotSelectors[0]) targetPoints[size++].set(minX + 0.05, minY + 0.05, minZ + 0.05); + if (this.dotSelectors[1]) targetPoints[size++].set(maxX - 0.05, minY + 0.05, minZ + 0.05); + if (this.dotSelectors[2]) targetPoints[size++].set(minX + 0.05, minY + 0.05, maxZ - 0.05); + if (this.dotSelectors[3]) targetPoints[size++].set(maxX - 0.05, minY + 0.05, maxZ - 0.05); + if (this.dotSelectors[4]) targetPoints[size++].set(minX + 0.05, maxY - 0.05, minZ + 0.05); + if (this.dotSelectors[5]) targetPoints[size++].set(maxX - 0.05, maxY - 0.05, minZ + 0.05); + if (this.dotSelectors[6]) targetPoints[size++].set(minX + 0.05, maxY - 0.05, maxZ - 0.05); + if (this.dotSelectors[7]) targetPoints[size++].set(maxX - 0.05, maxY - 0.05, maxZ - 0.05); // 面中心点 double averageX = (minX + maxX) / 2.0; double averageY = (minY + maxY) / 2.0; double averageZ = (minZ + maxZ) / 2.0; - if (this.dotSelectors[8]) targetPoints[size++].set(averageX, averageY, minZ); - if (this.dotSelectors[9]) targetPoints[size++].set(averageX, averageY, maxZ); - if (this.dotSelectors[10]) targetPoints[size++].set(minX, averageY, averageZ); - if (this.dotSelectors[11]) targetPoints[size++].set(maxX, averageY, averageZ); - if (this.dotSelectors[12]) targetPoints[size++].set(averageX, minY, averageZ); - if (this.dotSelectors[13]) targetPoints[size].set(averageX, maxY, averageZ); + if (this.dotSelectors[8]) targetPoints[size++].set(averageX, averageY, minZ + 0.05); + if (this.dotSelectors[9]) targetPoints[size++].set(averageX, averageY, maxZ - 0.05); + if (this.dotSelectors[10]) targetPoints[size++].set(minX + 0.05, averageY, averageZ); + if (this.dotSelectors[11]) targetPoints[size++].set(maxX - 0.05, averageY, averageZ); + if (this.dotSelectors[12]) targetPoints[size++].set(averageX, minY + 0.05, averageZ); + if (this.dotSelectors[13]) targetPoints[size].set(averageX, maxY - 0.05, averageZ); return isVisible(cameraPos, this.targetPoints, size); } @@ -194,9 +194,9 @@ public final class EntityCulling { // 预计算每单位距离在各方块边界上的步进增量 // 这些值表示射线穿过一个方块所需的时间分数 - double stepIncrementX = 1.0 / (absDeltaX + 1e-10); // 避免除0 - double stepIncrementY = 1.0 / (absDeltaY + 1e-10); - double stepIncrementZ = 1.0 / (absDeltaZ + 1e-10); + double stepIncrementX = 1.0 / absDeltaX; + double stepIncrementY = 1.0 / absDeltaY; + double stepIncrementZ = 1.0 / absDeltaZ; // 射线将穿过的总方块数量(包括起点和终点) int totalBlocksToCheck = 1; @@ -286,8 +286,8 @@ public final class EntityCulling { int currentBlockY = startingY; int currentBlockZ = startingZ; - // 遍历射线路径上的所有方块(跳过最后一个目标方块) - for (; remainingSteps > 1; remainingSteps--) { + // 遍历射线路径上的所有方块 + for (; remainingSteps > 0; remainingSteps--) { // 检查当前方块是否遮挡视线 if (isOccluding(currentBlockX, currentBlockY, currentBlockZ)) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCullingManager.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCullingManager.java new file mode 100644 index 000000000..123cfbdee --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCullingManager.java @@ -0,0 +1,6 @@ +package net.momirealms.craftengine.core.plugin.entityculling; + +import net.momirealms.craftengine.core.plugin.Manageable; + +public interface EntityCullingManager extends Manageable { +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCullingManagerImpl.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCullingManagerImpl.java new file mode 100644 index 000000000..e01602e81 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCullingManagerImpl.java @@ -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 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(); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCullingThread.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCullingThread.java new file mode 100644 index 000000000..fefaa7896 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCullingThread.java @@ -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(); + } +} \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/logger/Debugger.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/logger/Debugger.java index 49dcb3ef7..3dd1cf3f2 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/logger/Debugger.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/logger/Debugger.java @@ -12,7 +12,7 @@ public enum Debugger { RESOURCE_PACK(Config::debugResourcePack), ITEM(Config::debugItem), BLOCK(Config::debugBlock), - BLOCK_ENTITY(Config::debugBlockEntity); + ENTITY_CULLING(Config::debugEntityCulling); private final Supplier condition; diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java index 572904823..e8061a7d8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java @@ -462,7 +462,7 @@ public class CEChunk { BlockPos pos = blockEntity.pos(); ImmutableBlockState blockState = this.getBlockState(pos); 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; } // 设置方块实体所在世界 diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultBlockEntitySerializer.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultBlockEntitySerializer.java index 5c2a49712..3c331d193 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultBlockEntitySerializer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultBlockEntitySerializer.java @@ -38,7 +38,7 @@ public final class DefaultBlockEntitySerializer { Key id = Key.of(data.getString("id")); BlockEntityType type = BuiltInRegistries.BLOCK_ENTITY_TYPE.getValue(id); if (type == null) { - Debugger.BLOCK_ENTITY.debug(() -> "Unknown block entity type: " + id); + Debugger.BLOCK.debug(() -> "Unknown block entity type: " + id); } else { BlockPos pos = BlockEntity.readPosAndVerify(data, chunk.chunkPos()); ImmutableBlockState blockState = chunk.getBlockState(pos);