mirror of
https://github.com/Xiao-MoMi/craft-engine.git
synced 2025-12-19 15:09:15 +00:00
完善剔除线程调度
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -507,9 +507,6 @@ public class BukkitServerPlayer extends Player {
|
||||
this.encoderState = encoderState;
|
||||
}
|
||||
|
||||
private final Queue<Long> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
@@ -565,3 +567,5 @@ debug:
|
||||
furniture: false
|
||||
item: false
|
||||
resource-pack: false
|
||||
block: false
|
||||
entity-culling: false
|
||||
@@ -32,7 +32,7 @@ public class TickingBlockEntityImpl<T extends BlockEntity> 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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package net.momirealms.craftengine.core.plugin.entityculling;
|
||||
|
||||
import net.momirealms.craftengine.core.plugin.Manageable;
|
||||
|
||||
public interface EntityCullingManager extends Manageable {
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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<Boolean> condition;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
// 设置方块实体所在世界
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user