From c59478e891745d246829cd55481ad46aa9326a5c Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sat, 29 Nov 2025 01:46:31 +0800 Subject: [PATCH 01/11] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=AE=9E=E4=BD=93?= =?UTF-8?q?=E5=89=94=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin/network/BukkitNetworkManager.java | 9 ++ .../plugin/user/BukkitServerPlayer.java | 47 +++++++- common-files/src/main/resources/config.yml | 5 +- .../core/block/AbstractBlockManager.java | 24 +++- .../core/block/BlockStateAppearance.java | 5 +- .../core/block/ImmutableBlockState.java | 14 ++- .../render/ConstantBlockEntityRenderer.java | 17 ++- .../core/entity/player/Player.java | 2 +- .../core/plugin/config/Config.java | 6 + .../plugin/entityculling/CullingData.java | 27 +++++ .../plugin/entityculling/EntityCulling.java | 112 +++++++++++++++--- .../scheduler/AbstractJavaScheduler.java | 8 ++ .../core/plugin/scheduler/LazyAsyncTask.java | 21 ++++ .../plugin/scheduler/SchedulerAdapter.java | 3 + .../craftengine/core/world/Cullable.java | 8 +- .../craftengine/core/world/chunk/CEChunk.java | 12 +- 16 files changed, 270 insertions(+), 50 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/CullingData.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/plugin/scheduler/LazyAsyncTask.java 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 c49b423e6..ba75b5f5e 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 @@ -128,6 +128,7 @@ 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; @@ -461,6 +462,14 @@ 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 a08fea4dc..e1b19a350 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 @@ -32,6 +32,7 @@ import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.context.CooldownData; +import net.momirealms.craftengine.core.plugin.entityculling.CullingData; import net.momirealms.craftengine.core.plugin.entityculling.EntityCulling; import net.momirealms.craftengine.core.plugin.locale.TranslationManager; import net.momirealms.craftengine.core.plugin.network.ConnectionState; @@ -157,7 +158,7 @@ public class BukkitServerPlayer extends Player { } } } - this.culling = new EntityCulling(this, 64, 0.5); + this.culling = new EntityCulling(this); } public void setPlayer(org.bukkit.entity.Player player) { @@ -506,6 +507,9 @@ 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 @@ -563,14 +567,42 @@ public class BukkitServerPlayer extends Player { this.predictNextBlockToMine(); } } + } + + public void asyncTick() { if (Config.enableEntityCulling()) { long nano1 = System.nanoTime(); for (VirtualCullableObject cullableObject : this.trackedBlockEntityRenderers.values()) { - boolean visible = this.culling.isVisible(cullableObject.cullable.aabb(), LocationUtils.toVec3d(platformPlayer().getEyeLocation())); - cullableObject.setShown(this, visible); + CullingData cullingData = cullableObject.cullable.cullingData(); + if (cullingData != null) { + Vec3d vec3d = LocationUtils.toVec3d(platformPlayer().getEyeLocation()); + boolean visible = this.culling.isVisible(cullingData, vec3d, vec3d); + cullableObject.setShown(this, visible); + } else { + cullableObject.setShown(this, true); + } + this.culling.resetCache(); } long nano2 = System.nanoTime(); - //CraftEngine.instance().logger().info("EntityCulling took " + (nano2 - nano1) / 1_000_000d + "ms"); + + 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"); + } } } @@ -1200,6 +1232,9 @@ public class BukkitServerPlayer extends Player { @Override public void removeTrackedChunk(long chunkPos) { this.trackedChunks.remove(chunkPos); + if (Config.enableEntityCulling()) { + this.culling.removeLastVisitChunkIfMatches((int) chunkPos, (int) (chunkPos >> 32)); + } } @Override @@ -1317,8 +1352,8 @@ public class BukkitServerPlayer extends Player { } @Override - public void addTrackedBlockEntity(BlockPos blockPos, ConstantBlockEntityRenderer renderer) { - this.trackedBlockEntityRenderers.put(blockPos, new VirtualCullableObject(renderer)); + public VirtualCullableObject addTrackedBlockEntity(BlockPos blockPos, ConstantBlockEntityRenderer renderer) { + return this.trackedBlockEntityRenderers.put(blockPos, new VirtualCullableObject(renderer)); } @Override diff --git a/common-files/src/main/resources/config.yml b/common-files/src/main/resources/config.yml index 0d6efafb4..d2a376c04 100644 --- a/common-files/src/main/resources/config.yml +++ b/common-files/src/main/resources/config.yml @@ -553,9 +553,10 @@ chunk-system: convert: {} client-optimization: - # Using server-side ray tracing algorithms to hide certain entities and reduce client-side rendering pressure. + # Using server-side ray tracing algorithms to hide block entities/furniture and reduce client-side rendering pressure. entity-culling: - enable: false + enable: true + view-distance: 64 # Enables or disables debug mode debug: diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java index 1b0b09185..0570149e7 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java @@ -31,6 +31,7 @@ import net.momirealms.craftengine.core.plugin.context.Context; import net.momirealms.craftengine.core.plugin.context.event.EventFunctions; import net.momirealms.craftengine.core.plugin.context.event.EventTrigger; import net.momirealms.craftengine.core.plugin.context.function.Function; +import net.momirealms.craftengine.core.plugin.entityculling.CullingData; import net.momirealms.craftengine.core.plugin.locale.LocalizedException; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.plugin.logger.Debugger; @@ -38,6 +39,7 @@ import net.momirealms.craftengine.core.registry.BuiltInRegistries; import net.momirealms.craftengine.core.registry.Holder; import net.momirealms.craftengine.core.registry.WritableRegistry; import net.momirealms.craftengine.core.util.*; +import net.momirealms.craftengine.core.world.collision.AABB; import net.momirealms.sparrow.nbt.CompoundTag; import org.incendo.cloud.suggestion.Suggestion; import org.jetbrains.annotations.NotNull; @@ -52,6 +54,7 @@ import java.util.concurrent.ExecutionException; public abstract class AbstractBlockManager extends AbstractModelGenerator implements BlockManager { private static final JsonElement EMPTY_VARIANT_MODEL = MiscUtils.init(new JsonObject(), o -> o.addProperty("model", "minecraft:block/empty")); + private static final AABB DEFAULT_BLOCK_ENTITY_AABB = new AABB(-.5, -.5, -.5, .5, .5, .5); protected final BlockParser blockParser; protected final BlockStateMappingParser blockStateMappingParser; // 根据id获取自定义方块 @@ -603,7 +606,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem BlockStateAppearance blockStateAppearance = new BlockStateAppearance( visualBlockState, parseBlockEntityRender(appearanceSection.get("entity-renderer")), - ResourceConfigUtils.getAsAABB(appearanceSection.getOrDefault("aabb", 1), "aabb") + parseCullingData(appearanceSection.get("entity-culling")) ); appearances.put(appearanceName, blockStateAppearance); if (anyAppearance == null) { @@ -643,7 +646,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem } for (ImmutableBlockState possibleState : possibleStates) { possibleState.setVisualBlockState(appearance.blockState()); - possibleState.setEstimatedBoundingBox(appearance.estimateAABB()); + possibleState.setCullingData(appearance.cullingData()); appearance.blockEntityRenderer().ifPresent(possibleState::setConstantRenderers); } } @@ -667,7 +670,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem if (visualState == null) { visualState = anyAppearance.blockState(); state.setVisualBlockState(visualState); - state.setEstimatedBoundingBox(anyAppearance.estimateAABB()); + state.setCullingData(anyAppearance.cullingData()); anyAppearance.blockEntityRenderer().ifPresent(state::setConstantRenderers); } int appearanceId = visualState.registryId(); @@ -707,6 +710,21 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem }, () -> GsonHelper.get().toJson(section))); } + private CullingData parseCullingData(Object arguments) { + if (arguments instanceof Boolean b && !b) { + return null; + } + if (!(arguments instanceof Map)) { + return new CullingData(DEFAULT_BLOCK_ENTITY_AABB, Config.entityCullingViewDistance(), 0.5); + } + Map argumentsMap = ResourceConfigUtils.getAsMap(arguments, "entity-culling"); + return new CullingData( + ResourceConfigUtils.getAsAABB(argumentsMap.getOrDefault("aabb", 1), "aabb"), + ResourceConfigUtils.getAsInt(argumentsMap.getOrDefault("view-distance", Config.entityCullingViewDistance()), "view-distance"), + ResourceConfigUtils.getAsDouble(argumentsMap.getOrDefault("aabb-expansion", 0.5), "aabb-expansion") + ); + } + @SuppressWarnings("unchecked") private Optional[]> parseBlockEntityRender(Object arguments) { if (arguments == null) return Optional.empty(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateAppearance.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateAppearance.java index d2b8d0189..d3e7f8c85 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateAppearance.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateAppearance.java @@ -2,11 +2,12 @@ package net.momirealms.craftengine.core.block; import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement; import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig; -import net.momirealms.craftengine.core.world.collision.AABB; +import net.momirealms.craftengine.core.plugin.entityculling.CullingData; +import org.jetbrains.annotations.Nullable; import java.util.Optional; public record BlockStateAppearance(BlockStateWrapper blockState, Optional[]> blockEntityRenderer, - AABB estimateAABB) { + @Nullable CullingData cullingData) { } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java b/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java index 9a4010ef8..a112628f5 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java @@ -13,11 +13,11 @@ import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.loot.LootTable; import net.momirealms.craftengine.core.plugin.context.ContextHolder; import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; +import net.momirealms.craftengine.core.plugin.entityculling.CullingData; import net.momirealms.craftengine.core.registry.Holder; import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.world.CEWorld; import net.momirealms.craftengine.core.world.World; -import net.momirealms.craftengine.core.world.collision.AABB; import net.momirealms.sparrow.nbt.CompoundTag; import net.momirealms.sparrow.nbt.NBT; import net.momirealms.sparrow.nbt.Tag; @@ -43,7 +43,8 @@ public final class ImmutableBlockState { private BlockEntityType blockEntityType; @Nullable private BlockEntityElementConfig[] renderers; - private AABB estimatedBoundingBox; + @Nullable + private CullingData cullingData; ImmutableBlockState( Holder.Reference owner, @@ -89,12 +90,13 @@ public final class ImmutableBlockState { this.renderers = renderers; } - public void setEstimatedBoundingBox(AABB aabb) { - this.estimatedBoundingBox = aabb; + @Nullable + public CullingData cullingData() { + return cullingData; } - public AABB estimatedBoundingBox() { - return estimatedBoundingBox; + public void setCullingData(@Nullable CullingData cullingData) { + this.cullingData = cullingData; } public boolean hasBlockEntity() { diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/ConstantBlockEntityRenderer.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/ConstantBlockEntityRenderer.java index 4551ed667..461331e96 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/ConstantBlockEntityRenderer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/ConstantBlockEntityRenderer.java @@ -2,18 +2,19 @@ package net.momirealms.craftengine.core.block.entity.render; import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement; import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.plugin.entityculling.CullingData; import net.momirealms.craftengine.core.world.Cullable; -import net.momirealms.craftengine.core.world.collision.AABB; import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; @ApiStatus.Experimental public class ConstantBlockEntityRenderer implements Cullable { private final BlockEntityElement[] elements; - public final AABB aabb; + public final CullingData cullingData; - public ConstantBlockEntityRenderer(BlockEntityElement[] elements, AABB aabb) { + public ConstantBlockEntityRenderer(BlockEntityElement[] elements, @Nullable CullingData cullingData) { this.elements = elements; - this.aabb = aabb; + this.cullingData = cullingData; } @Override @@ -55,7 +56,11 @@ public class ConstantBlockEntityRenderer implements Cullable { } @Override - public AABB aabb() { - return this.aabb; + public CullingData cullingData() { + return this.cullingData; + } + + public boolean canCull() { + return this.cullingData != null; } } 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 36e3fcb76..25b098bb1 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 @@ -203,7 +203,7 @@ public abstract class Player extends AbstractEntity implements NetWorkUser { public abstract void addTrackedBlockEntities(Map renders); - public abstract void addTrackedBlockEntity(BlockPos blockPos, ConstantBlockEntityRenderer renderer); + public abstract VirtualCullableObject addTrackedBlockEntity(BlockPos blockPos, ConstantBlockEntityRenderer renderer); public abstract VirtualCullableObject getTrackedBlockEntity(BlockPos blockPos); 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 3720a4268..d0b501b11 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 @@ -204,6 +204,7 @@ public class Config { protected int emoji$max_emojis_per_parse; protected boolean client_optimization$entity_culling$enable; + protected int client_optimization$entity_culling$view_distance; public Config(CraftEngine plugin) { this.plugin = plugin; @@ -566,6 +567,7 @@ public class Config { // client optimization client_optimization$entity_culling$enable = config.getBoolean("client-optimization.entity-culling.enable", false); + client_optimization$entity_culling$view_distance = config.getInt("client-optimization.entity-culling.view-distance", 64); firstTime = false; } @@ -1161,6 +1163,10 @@ public class Config { return instance.client_optimization$entity_culling$enable; } + public static int entityCullingViewDistance() { + return instance.client_optimization$entity_culling$view_distance; + } + 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/CullingData.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/CullingData.java new file mode 100644 index 000000000..8d67a1349 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/CullingData.java @@ -0,0 +1,27 @@ +package net.momirealms.craftengine.core.plugin.entityculling; + +import net.momirealms.craftengine.core.world.collision.AABB; + +public final class CullingData { + public final AABB aabb; + public final int maxDistance; + public final double aabbExpansion; + + public CullingData(AABB aabb, int maxDistance, double aabbExpansion) { + this.aabb = aabb; + this.maxDistance = maxDistance; + this.aabbExpansion = aabbExpansion; + } + + public AABB aabb() { + return aabb; + } + + public int maxDistance() { + return maxDistance; + } + + public double aabbExpansion() { + return aabbExpansion; + } +} 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 2b16695bf..b9e7d87b9 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 @@ -13,38 +13,42 @@ import java.util.Arrays; public final class EntityCulling { public static final int MAX_SAMPLES = 14; private final Player player; - private final int maxDistance; - private final double aabbExpansion; private final boolean[] dotSelectors = new boolean[MAX_SAMPLES]; private final MutableVec3d[] targetPoints = new MutableVec3d[MAX_SAMPLES]; private final int[] lastHitBlock = new int[MAX_SAMPLES * 3]; private final boolean[] canCheckLastHitBlock = new boolean[MAX_SAMPLES]; + // 相机附近的点位大概率会多次重复获取 + private final long[] occlusionCache = new long[32 * 32 * 32 / 32]; private int hitBlockCount = 0; private int lastVisitChunkX = Integer.MAX_VALUE; private int lastVisitChunkZ = Integer.MAX_VALUE; private ClientChunk lastVisitChunk = null; - public EntityCulling(Player player, int maxDistance, double aabbExpansion) { + public EntityCulling(Player player) { this.player = player; - this.maxDistance = maxDistance; - this.aabbExpansion = aabbExpansion; for (int i = 0; i < MAX_SAMPLES; i++) { this.targetPoints[i] = new MutableVec3d(0,0,0); } } - public boolean isVisible(AABB aabb, Vec3d cameraPos) { + public void resetCache() { + Arrays.fill(this.occlusionCache, 0); + } + + public boolean isVisible(CullingData cullable, Vec3d cameraPos, Vec3d playerHeadPos) { // 情空标志位 Arrays.fill(this.canCheckLastHitBlock, false); this.hitBlockCount = 0; + AABB aabb = cullable.aabb; + double aabbExpansion = cullable.aabbExpansion; // 根据AABB获取能包裹此AABB的最小长方体 - int minX = MiscUtils.floor(aabb.minX - this.aabbExpansion); - int minY = MiscUtils.floor(aabb.minY - this.aabbExpansion); - int minZ = MiscUtils.floor(aabb.minZ - this.aabbExpansion); - int maxX = MiscUtils.ceil(aabb.maxX + this.aabbExpansion); - int maxY = MiscUtils.ceil(aabb.maxY + this.aabbExpansion); - int maxZ = MiscUtils.ceil(aabb.maxZ + this.aabbExpansion); + int minX = MiscUtils.floor(aabb.minX - aabbExpansion); + int minY = MiscUtils.floor(aabb.minY - aabbExpansion); + int minZ = MiscUtils.floor(aabb.minZ - aabbExpansion); + int maxX = MiscUtils.ceil(aabb.maxX + aabbExpansion); + int maxY = MiscUtils.ceil(aabb.maxY + aabbExpansion); + int maxZ = MiscUtils.ceil(aabb.maxZ + aabbExpansion); double cameraX = cameraPos.x; double cameraY = cameraPos.y; @@ -60,7 +64,8 @@ public final class EntityCulling { } // 如果设置了最大距离 - if (this.maxDistance > 0) { + int maxDistance = cullable.maxDistance; + if (maxDistance > 0) { // 计算AABB到相机的最小距离 double distanceSq = 0.0; // 计算XYZ轴方向的距离 @@ -68,7 +73,7 @@ public final class EntityCulling { distanceSq += distanceSq(minY, maxY, cameraY, relY); distanceSq += distanceSq(minZ, maxZ, cameraZ, relZ); // 检查距离是否超过最大值 - double maxDistanceSq = this.maxDistance * this.maxDistance; + double maxDistanceSq = maxDistance * maxDistance; // 超过最大距离,剔除 if (distanceSq > maxDistanceSq) { return false; @@ -277,21 +282,76 @@ public final class EntityCulling { return false; } - private boolean stepRay(int currentBlockX, int currentBlockY, int currentBlockZ, + // 0 = 没缓存 + // 1 = 没遮挡 + // 2 = 有遮挡 + private int getCacheState(int index) { + int arrayIndex = index / 32; // 获取数组下标 + int offset = (index % 32) * 2; // 每个状态占2bit,计算位偏移 + if (arrayIndex >= 0 && arrayIndex < this.occlusionCache.length) { + long cacheValue = this.occlusionCache[arrayIndex]; + // 提取2bit的状态值 (0-3) + return (int) ((cacheValue >> offset) & 0b11); + } + return 0; // 索引越界时返回默认状态 + } + + private void setCacheState(int index, int state) { + long[] cache = this.occlusionCache; + int arrayIndex = index / 32; + int offset = (index % 32) * 2; + + if (arrayIndex >= 0 && arrayIndex < cache.length) { + long mask = ~(0b11L << offset); // 创建清除掩码 + long newValue = (cache[arrayIndex] & mask) | ((state & 0b11L) << offset); + cache[arrayIndex] = newValue; + } + } + + private boolean stepRay(int startingX, int startingY, int startingZ, double stepSizeX, double stepSizeY, double stepSizeZ, - int remainingSteps, int stepDirectionX, int stepDirectionY, - int stepDirectionZ, double nextStepTimeY, double nextStepTimeX, - double nextStepTimeZ) { + int remainingSteps, + int stepDirectionX, int stepDirectionY, int stepDirectionZ, + double nextStepTimeY, double nextStepTimeX, double nextStepTimeZ) { + + int currentBlockX = startingX; + int currentBlockY = startingY; + int currentBlockZ = startingZ; // 遍历射线路径上的所有方块(跳过最后一个目标方块) for (; remainingSteps > 1; remainingSteps--) { + int cacheIndex = getCacheIndex(currentBlockX, currentBlockY, currentBlockZ, startingX, startingY, startingZ); + if (cacheIndex != -1) { + int cacheState = getCacheState(cacheIndex); + // 没遮挡 + if (cacheState == 1) { + continue; + } + // 有遮挡 + else if (cacheState == 2) { + this.lastHitBlock[this.hitBlockCount * 3] = currentBlockX; + this.lastHitBlock[this.hitBlockCount * 3 + 1] = currentBlockY; + this.lastHitBlock[this.hitBlockCount * 3 + 2] = currentBlockZ; + return false; + } + } + // 检查当前方块是否遮挡视线 if (isOccluding(currentBlockX, currentBlockY, currentBlockZ)) { this.lastHitBlock[this.hitBlockCount * 3] = currentBlockX; this.lastHitBlock[this.hitBlockCount * 3 + 1] = currentBlockY; this.lastHitBlock[this.hitBlockCount * 3 + 2] = currentBlockZ; + // 设置缓存 + if (cacheIndex != -1) { + setCacheState(cacheIndex, 2); + } return false; // 视线被遮挡,立即返回 + } else { + // 设置缓存 + if (cacheIndex != -1) { + setCacheState(cacheIndex, 1); + } } // 基于时间参数选择下一个要遍历的方块方向 @@ -315,6 +375,22 @@ public final class EntityCulling { return true; } + private int getCacheIndex(int x, int y, int z, int startX, int startY, int startZ) { + int deltaX = startX + 16 - x; + if (deltaX < 0 || deltaX >= 32) { + return -1; + } + int deltaY = startY + 16 - y; + if (deltaY < 0 || deltaY >= 32) { + return -1; + } + int deltaZ = startZ + 16 - z; + if (deltaZ < 0 || deltaZ >= 32) { + return -1; + } + return deltaX + 32 * deltaY + 32 * 32 * deltaZ; + } + private double distanceSq(int min, int max, double camera, Relative rel) { if (rel == Relative.NEGATIVE) { double dx = camera - max; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/scheduler/AbstractJavaScheduler.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/scheduler/AbstractJavaScheduler.java index c728459ac..8e2ab4818 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/scheduler/AbstractJavaScheduler.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/scheduler/AbstractJavaScheduler.java @@ -6,6 +6,7 @@ import java.lang.Thread.UncaughtExceptionHandler; import java.util.Arrays; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -46,6 +47,13 @@ public abstract class AbstractJavaScheduler implements SchedulerAdapter { return new AsyncTask(future); } + @Override + public SchedulerTask asyncRepeating(Consumer task, long delay, long interval, TimeUnit unit) { + LazyAsyncTask asyncTask = new LazyAsyncTask(); + asyncTask.future = this.scheduler.scheduleAtFixedRate(() -> this.worker.execute(() -> task.accept(asyncTask)), delay, interval, unit); + return asyncTask; + } + @Override public void shutdownScheduler() { this.scheduler.shutdown(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/scheduler/LazyAsyncTask.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/scheduler/LazyAsyncTask.java new file mode 100644 index 000000000..84ad86199 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/scheduler/LazyAsyncTask.java @@ -0,0 +1,21 @@ +package net.momirealms.craftengine.core.plugin.scheduler; + +import java.util.concurrent.ScheduledFuture; + +public class LazyAsyncTask implements SchedulerTask { + + public ScheduledFuture future; + + @Override + public void cancel() { + if (future != null) { + future.cancel(false); + } + } + + @Override + public boolean cancelled() { + if (future == null) return false; + return future.isCancelled(); + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/scheduler/SchedulerAdapter.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/scheduler/SchedulerAdapter.java index fd7af51e3..786ea491c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/scheduler/SchedulerAdapter.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/scheduler/SchedulerAdapter.java @@ -2,6 +2,7 @@ package net.momirealms.craftengine.core.plugin.scheduler; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; public interface SchedulerAdapter { @@ -25,6 +26,8 @@ public interface SchedulerAdapter { SchedulerTask asyncRepeating(Runnable task, long delay, long interval, TimeUnit unit); + SchedulerTask asyncRepeating(Consumer task, long delay, long interval, TimeUnit unit); + void shutdownScheduler(); void shutdownExecutor(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/Cullable.java b/core/src/main/java/net/momirealms/craftengine/core/world/Cullable.java index 700442fb3..404594503 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/Cullable.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/Cullable.java @@ -1,13 +1,15 @@ package net.momirealms.craftengine.core.world; import net.momirealms.craftengine.core.entity.player.Player; -import net.momirealms.craftengine.core.world.collision.AABB; +import net.momirealms.craftengine.core.plugin.entityculling.CullingData; +import org.jetbrains.annotations.Nullable; public interface Cullable { - AABB aabb(); - void show(Player player); void hide(Player player); + + @Nullable + CullingData cullingData(); } 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 4ca2fe5df..572904823 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 @@ -12,6 +12,7 @@ import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityEl import net.momirealms.craftengine.core.block.entity.tick.*; import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.plugin.config.Config; +import net.momirealms.craftengine.core.plugin.entityculling.CullingData; import net.momirealms.craftengine.core.plugin.logger.Debugger; import net.momirealms.craftengine.core.world.*; import net.momirealms.craftengine.core.world.chunk.client.VirtualCullableObject; @@ -139,7 +140,12 @@ public class CEChunk { BlockEntityElementConfig[] renderers = state.constantRenderers(); if (renderers != null && renderers.length > 0) { BlockEntityElement[] elements = new BlockEntityElement[renderers.length]; - ConstantBlockEntityRenderer renderer = new ConstantBlockEntityRenderer(elements, state.estimatedBoundingBox().move(pos)); + ConstantBlockEntityRenderer renderer = new ConstantBlockEntityRenderer( + elements, + Optional.ofNullable(state.cullingData()) + .map(data -> new CullingData(data.aabb.move(pos), data.maxDistance, data.aabbExpansion)) + .orElse(null) + ); World wrappedWorld = this.world.world(); List trackedBy = getTrackedBy(); boolean hasTrackedBy = trackedBy != null && !trackedBy.isEmpty(); @@ -159,7 +165,7 @@ public class CEChunk { // 如果启用实体剔除,那么只对已经渲染的进行变换 if (Config.enableEntityCulling()) { for (Player player : trackedBy) { - VirtualCullableObject trackedBlockEntity = player.getTrackedBlockEntity(pos); + VirtualCullableObject trackedBlockEntity = player.addTrackedBlockEntity(pos, renderer); if (trackedBlockEntity == null || trackedBlockEntity.isShown()) { element.transform(player); } @@ -207,7 +213,7 @@ public class CEChunk { // 如果启用实体剔除,那么只对已经渲染的进行变换 if (Config.enableEntityCulling()) { for (Player player : trackedBy) { - VirtualCullableObject trackedBlockEntity = player.getTrackedBlockEntity(pos); + VirtualCullableObject trackedBlockEntity = player.addTrackedBlockEntity(pos, renderer); if (trackedBlockEntity == null || trackedBlockEntity.isShown()) { newElement.transform(player); } From 19d1cb2a21ffdb159af6da3b606658b5271f3964 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sat, 29 Nov 2025 02:13:06 +0800 Subject: [PATCH 02/11] =?UTF-8?q?=E7=BC=93=E5=AD=98=E6=98=AF=E8=B4=9F?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin/user/BukkitServerPlayer.java | 3 +- .../plugin/entityculling/EntityCulling.java | 59 +------------------ 2 files changed, 2 insertions(+), 60 deletions(-) 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 e1b19a350..dd4c4e247 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 @@ -576,12 +576,11 @@ public class BukkitServerPlayer extends Player { CullingData cullingData = cullableObject.cullable.cullingData(); if (cullingData != null) { Vec3d vec3d = LocationUtils.toVec3d(platformPlayer().getEyeLocation()); - boolean visible = this.culling.isVisible(cullingData, vec3d, vec3d); + boolean visible = this.culling.isVisible(cullingData, vec3d); cullableObject.setShown(this, visible); } else { cullableObject.setShown(this, true); } - this.culling.resetCache(); } long nano2 = System.nanoTime(); 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 b9e7d87b9..ecb9d9fdd 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 @@ -17,8 +17,6 @@ public final class EntityCulling { private final MutableVec3d[] targetPoints = new MutableVec3d[MAX_SAMPLES]; private final int[] lastHitBlock = new int[MAX_SAMPLES * 3]; private final boolean[] canCheckLastHitBlock = new boolean[MAX_SAMPLES]; - // 相机附近的点位大概率会多次重复获取 - private final long[] occlusionCache = new long[32 * 32 * 32 / 32]; private int hitBlockCount = 0; private int lastVisitChunkX = Integer.MAX_VALUE; private int lastVisitChunkZ = Integer.MAX_VALUE; @@ -31,11 +29,7 @@ public final class EntityCulling { } } - public void resetCache() { - Arrays.fill(this.occlusionCache, 0); - } - - public boolean isVisible(CullingData cullable, Vec3d cameraPos, Vec3d playerHeadPos) { + public boolean isVisible(CullingData cullable, Vec3d cameraPos) { // 情空标志位 Arrays.fill(this.canCheckLastHitBlock, false); this.hitBlockCount = 0; @@ -282,32 +276,6 @@ public final class EntityCulling { return false; } - // 0 = 没缓存 - // 1 = 没遮挡 - // 2 = 有遮挡 - private int getCacheState(int index) { - int arrayIndex = index / 32; // 获取数组下标 - int offset = (index % 32) * 2; // 每个状态占2bit,计算位偏移 - if (arrayIndex >= 0 && arrayIndex < this.occlusionCache.length) { - long cacheValue = this.occlusionCache[arrayIndex]; - // 提取2bit的状态值 (0-3) - return (int) ((cacheValue >> offset) & 0b11); - } - return 0; // 索引越界时返回默认状态 - } - - private void setCacheState(int index, int state) { - long[] cache = this.occlusionCache; - int arrayIndex = index / 32; - int offset = (index % 32) * 2; - - if (arrayIndex >= 0 && arrayIndex < cache.length) { - long mask = ~(0b11L << offset); // 创建清除掩码 - long newValue = (cache[arrayIndex] & mask) | ((state & 0b11L) << offset); - cache[arrayIndex] = newValue; - } - } - private boolean stepRay(int startingX, int startingY, int startingZ, double stepSizeX, double stepSizeY, double stepSizeZ, int remainingSteps, @@ -321,37 +289,12 @@ public final class EntityCulling { // 遍历射线路径上的所有方块(跳过最后一个目标方块) for (; remainingSteps > 1; remainingSteps--) { - int cacheIndex = getCacheIndex(currentBlockX, currentBlockY, currentBlockZ, startingX, startingY, startingZ); - if (cacheIndex != -1) { - int cacheState = getCacheState(cacheIndex); - // 没遮挡 - if (cacheState == 1) { - continue; - } - // 有遮挡 - else if (cacheState == 2) { - this.lastHitBlock[this.hitBlockCount * 3] = currentBlockX; - this.lastHitBlock[this.hitBlockCount * 3 + 1] = currentBlockY; - this.lastHitBlock[this.hitBlockCount * 3 + 2] = currentBlockZ; - return false; - } - } - // 检查当前方块是否遮挡视线 if (isOccluding(currentBlockX, currentBlockY, currentBlockZ)) { this.lastHitBlock[this.hitBlockCount * 3] = currentBlockX; this.lastHitBlock[this.hitBlockCount * 3 + 1] = currentBlockY; this.lastHitBlock[this.hitBlockCount * 3 + 2] = currentBlockZ; - // 设置缓存 - if (cacheIndex != -1) { - setCacheState(cacheIndex, 2); - } return false; // 视线被遮挡,立即返回 - } else { - // 设置缓存 - if (cacheIndex != -1) { - setCacheState(cacheIndex, 1); - } } // 基于时间参数选择下一个要遍历的方块方向 From b77dbdfca95c632c0c57aa7a0d2c1b0911116e88 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sat, 29 Nov 2025 04:20:49 +0800 Subject: [PATCH 03/11] =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=89=94=E9=99=A4?= =?UTF-8?q?=E7=BA=BF=E7=A8=8B=E8=B0=83=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin/network/BukkitNetworkManager.java | 10 +-- .../plugin/user/BukkitServerPlayer.java | 45 +++---------- common-files/src/main/resources/config.yml | 6 +- .../entity/tick/TickingBlockEntityImpl.java | 2 +- .../core/entity/player/Player.java | 2 + .../craftengine/core/plugin/CraftEngine.java | 7 ++ .../core/plugin/config/Config.java | 17 +++-- .../plugin/entityculling/EntityCulling.java | 38 +++++------ .../entityculling/EntityCullingManager.java | 6 ++ .../EntityCullingManagerImpl.java | 32 +++++++++ .../entityculling/EntityCullingThread.java | 65 +++++++++++++++++++ .../core/plugin/logger/Debugger.java | 2 +- .../craftengine/core/world/chunk/CEChunk.java | 2 +- .../DefaultBlockEntitySerializer.java | 2 +- 14 files changed, 164 insertions(+), 72 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCullingManager.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCullingManagerImpl.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCullingThread.java 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); From 2a552fb6fa6d36833cef53a1ad7606fb525dbb5e Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sat, 29 Nov 2025 04:23:22 +0800 Subject: [PATCH 04/11] Update gradle.properties --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 102014893..bf5da7ca3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ org.gradle.jvmargs=-Xmx1G # Project settings -project_version=0.0.65.13.1 +project_version=0.0.65.14 config_version=58 lang_version=40 project_group=net.momirealms From 3410cd6e8829429c782d315ef5993052a587a1b5 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sat, 29 Nov 2025 17:01:01 +0800 Subject: [PATCH 05/11] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BB=A4=E7=89=8C?= =?UTF-8?q?=E6=A1=B6=E9=99=90=E9=80=9F+=E8=87=AA=E5=AE=9A=E4=B9=89?= =?UTF-8?q?=E8=A7=86=E8=B7=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin/command/BukkitCommandManager.java | 1 + .../SetEntityViewDistanceScaleCommand.java | 52 +++++ .../plugin/user/BukkitServerPlayer.java | 22 +- common-files/src/main/resources/commands.yml | 6 + common-files/src/main/resources/config.yml | 8 + .../src/main/resources/translations/en.yml | 1 + .../core/entity/player/Player.java | 2 + .../core/plugin/config/Config.java | 22 +- .../plugin/entityculling/EntityCulling.java | 25 +- .../plugin/entityculling/VoxelIterator.java | 216 ------------------ .../core/plugin/locale/MessageConstants.java | 1 + .../chunk/client/VirtualCullableObject.java | 2 +- gradle.properties | 4 +- 13 files changed, 139 insertions(+), 223 deletions(-) create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/SetEntityViewDistanceScaleCommand.java delete mode 100644 core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/VoxelIterator.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitCommandManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitCommandManager.java index 6ce68c38d..00dbb694b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitCommandManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitCommandManager.java @@ -42,6 +42,7 @@ public class BukkitCommandManager extends AbstractCommandManager new SearchUsageAdminCommand(this, plugin), new TestCommand(this, plugin), new SetLocaleCommand(this, plugin), + new SetEntityViewDistanceScaleCommand(this, plugin), new UnsetLocaleCommand(this, plugin), new DebugGetBlockStateRegistryIdCommand(this, plugin), new DebugGetBlockInternalIdCommand(this, plugin), diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/SetEntityViewDistanceScaleCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/SetEntityViewDistanceScaleCommand.java new file mode 100644 index 000000000..bd24341e3 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/SetEntityViewDistanceScaleCommand.java @@ -0,0 +1,52 @@ +package net.momirealms.craftengine.bukkit.plugin.command.feature; + +import net.kyori.adventure.text.Component; +import net.momirealms.craftengine.bukkit.api.BukkitAdaptors; +import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature; +import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager; +import net.momirealms.craftengine.core.plugin.command.FlagKeys; +import net.momirealms.craftengine.core.plugin.locale.MessageConstants; +import net.momirealms.craftengine.core.plugin.locale.TranslationManager; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.incendo.cloud.Command; +import org.incendo.cloud.bukkit.parser.PlayerParser; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.parser.standard.DoubleParser; +import org.incendo.cloud.parser.standard.StringParser; +import org.incendo.cloud.suggestion.Suggestion; +import org.incendo.cloud.suggestion.SuggestionProvider; + +import java.util.Locale; +import java.util.concurrent.CompletableFuture; + +public class SetEntityViewDistanceScaleCommand extends BukkitCommandFeature { + + public SetEntityViewDistanceScaleCommand(CraftEngineCommandManager commandManager, CraftEngine plugin) { + super(commandManager, plugin); + } + + @Override + public Command.Builder assembleCommand(org.incendo.cloud.CommandManager manager, Command.Builder builder) { + return builder + .flag(FlagKeys.SILENT_FLAG) + .required("player", PlayerParser.playerParser()) + .required("scale", DoubleParser.doubleParser(0.125, 8)) + .handler(context -> { + Player player = context.get("player"); + double scale = context.get("scale"); + BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(player); + serverPlayer.setEntityCullingViewDistanceScale(scale); + handleFeedback(context, MessageConstants.COMMAND_ENTITY_VIEW_DISTANCE_SCALE_SET_SUCCESS, Component.text(scale), Component.text(player.getName())); + }); + } + + @Override + public String getFeatureID() { + return "set_entity_view_distance_scale"; + } +} 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 2bd1ed888..9ecf68413 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 @@ -72,6 +72,7 @@ import java.util.concurrent.ConcurrentHashMap; public class BukkitServerPlayer extends Player { public static final Key SELECTED_LOCALE_KEY = Key.of("craftengine:locale"); + public static final Key ENTITY_CULLING_VIEW_DISTANCE_SCALE = Key.of("craftengine:entity_culling_view_distance_scale"); private final BukkitCraftEngine plugin; // connection state @@ -143,7 +144,6 @@ public class BukkitServerPlayer extends Player { private int lastStopMiningTick; // 跟踪到的方块实体渲染器 private final Map trackedBlockEntityRenderers = new ConcurrentHashMap<>(); - private final EntityCulling culling; public BukkitServerPlayer(BukkitCraftEngine plugin, @Nullable Channel channel) { @@ -170,6 +170,8 @@ public class BukkitServerPlayer extends Player { this.isNameVerified = true; byte[] bytes = player.getPersistentDataContainer().get(KeyUtils.toNamespacedKey(CooldownData.COOLDOWN_KEY), PersistentDataType.BYTE_ARRAY); String locale = player.getPersistentDataContainer().get(KeyUtils.toNamespacedKey(SELECTED_LOCALE_KEY), PersistentDataType.STRING); + Double scale = player.getPersistentDataContainer().get(KeyUtils.toNamespacedKey(ENTITY_CULLING_VIEW_DISTANCE_SCALE), PersistentDataType.DOUBLE); + this.culling.setDistanceScale(Optional.ofNullable(scale).orElse(1.0)); this.selectedLocale = TranslationManager.parseLocale(locale); this.trackedChunks = ConcurrentLong2ReferenceChainedHashTable.createWithCapacity(512, 0.5f); this.entityTypeView = new ConcurrentHashMap<>(256); @@ -568,12 +570,21 @@ public class BukkitServerPlayer extends Player { @Override public void entityCullingTick() { + this.culling.restoreTokenOnTick(); 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); + if (visible != cullableObject.isShown) { + if (Config.enableEntityCullingRateLimiting() && visible) { + if (this.culling.takeToken()) { + cullableObject.setShown(this, true); + } + } else { + cullableObject.setShown(this, visible); + } + } } else { cullableObject.setShown(this, true); } @@ -1285,6 +1296,13 @@ public class BukkitServerPlayer extends Player { } } + @Override + public void setEntityCullingViewDistanceScale(double value) { + value = Math.min(Math.max(0.125, value), 8); + this.culling.setDistanceScale(value); + platformPlayer().getPersistentDataContainer().set(KeyUtils.toNamespacedKey(ENTITY_CULLING_VIEW_DISTANCE_SCALE), PersistentDataType.DOUBLE, value); + } + @Override public void giveExperiencePoints(int xpPoints) { platformPlayer().giveExp(xpPoints); diff --git a/common-files/src/main/resources/commands.yml b/common-files/src/main/resources/commands.yml index a2bfdbeef..ffd2eadee 100644 --- a/common-files/src/main/resources/commands.yml +++ b/common-files/src/main/resources/commands.yml @@ -129,6 +129,12 @@ unset_locale: usage: - /ce feature locale unset +set_entity_view_distance_scale: + enable: true + permission: ce.command.admin.set_entity_view_distance_scale + usage: + - /ce feature entity-view-distance-scale set + # Debug commands debug_set_block: enable: true diff --git a/common-files/src/main/resources/config.yml b/common-files/src/main/resources/config.yml index 06785ab16..c458a8f05 100644 --- a/common-files/src/main/resources/config.yml +++ b/common-files/src/main/resources/config.yml @@ -552,13 +552,21 @@ chunk-system: remove: [] convert: {} +# [Premium Exclusive] client-optimization: # Using server-side ray tracing algorithms to hide block entities/furniture and reduce client-side rendering pressure. + # Requires a restart to fully apply. entity-culling: enable: true view-distance: 64 # Determining the number of threads to execute these raytrace operations threads: 1 + # Limit the maximum number of entities with visibility changes per tick for one player + # This helps mitigate client-side performance impacts and server-side bandwidth spikes caused by a large number of entities appearing. + rate-limiting: + enable: true + bucket-size: 1000 + restore-per-tick: 25 # Enables or disables debug mode debug: diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index cd0751c7b..fce2f7ea7 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -74,6 +74,7 @@ command.send_resource_pack.success.multiple: "Send resource packs to Invalid locale format: " command.locale.set.success: "Updated selected locale to for " command.locale.unset.success: "Cleared selected locale for " +command.entity_view_distance_scale.set.success: "Updated entity view distance scale to for " warning.network.resource_pack.unverified_uuid: "Player is attempting to request a resource pack using a UUID () that is not authenticated by the server." warning.config.pack.duplicated_files: "Duplicated files Found. Please resolve them through config.yml 'resource-pack.duplicated-files-handler' section." warning.config.yaml.duplicated_key: "Issue found in file - Found duplicated key '' at line , this might cause unexpected results." 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 7c0be0248..8c63646b2 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 @@ -191,6 +191,8 @@ public abstract class Player extends AbstractEntity implements NetWorkUser { public abstract void setSelectedLocale(@Nullable Locale locale); + public abstract void setEntityCullingViewDistanceScale(double value); + public abstract void giveExperiencePoints(int xpPoints); public abstract void giveExperienceLevels(int levels); 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 9b7473db3..db7db9bab 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 @@ -208,6 +208,9 @@ 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; + protected boolean client_optimization$entity_culling$rate_limiting$enable; + protected int client_optimization$entity_culling$rate_limiting$bucket_size; + protected int client_optimization$entity_culling$rate_limiting$restore_per_tick; public Config(CraftEngine plugin) { this.plugin = plugin; @@ -571,8 +574,13 @@ public class Config { emoji$max_emojis_per_parse = config.getInt("emoji.max-emojis-per-parse", 32); // client optimization - client_optimization$entity_culling$enable = config.getBoolean("client-optimization.entity-culling.enable", false); + if (firstTime) { + client_optimization$entity_culling$enable = VersionHelper.PREMIUM && config.getBoolean("client-optimization.entity-culling.enable", false); + } client_optimization$entity_culling$view_distance = config.getInt("client-optimization.entity-culling.view-distance", 64); + client_optimization$entity_culling$threads = config.getInt("client-optimization.entity-culling.threads", 1); + client_optimization$entity_culling$rate_limiting$bucket_size = config.getInt("client-optimization.entity-culling.rate-limiting.bucket-size", 300); + client_optimization$entity_culling$rate_limiting$restore_per_tick = config.getInt("client-optimization.entity-culling.rate-limiting.restore-per-tick", 5); firstTime = false; } @@ -1176,6 +1184,18 @@ public class Config { return instance.client_optimization$entity_culling$threads; } + public static boolean enableEntityCullingRateLimiting() { + return instance.client_optimization$entity_culling$rate_limiting$enable; + } + + public static int entityCullingRateLimitingBucketSize() { + return instance.client_optimization$entity_culling$rate_limiting$bucket_size; + } + + public static int entityCullingRateLimitingRestorePerTick() { + return instance.client_optimization$entity_culling$rate_limiting$restore_per_tick; + } + 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 4304dcc72..21efad1c6 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 @@ -1,6 +1,7 @@ package net.momirealms.craftengine.core.plugin.entityculling; import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.world.ChunkPos; import net.momirealms.craftengine.core.world.MutableVec3d; @@ -21,6 +22,8 @@ public final class EntityCulling { private int lastVisitChunkX = Integer.MAX_VALUE; private int lastVisitChunkZ = Integer.MAX_VALUE; private ClientChunk lastVisitChunk = null; + private int currentTokens = Config.entityCullingRateLimitingBucketSize(); + private double distanceScale = 1d; public EntityCulling(Player player) { this.player = player; @@ -29,6 +32,26 @@ public final class EntityCulling { } } + public void setDistanceScale(double distanceScale) { + this.distanceScale = distanceScale; + } + + public double distanceScale() { + return distanceScale; + } + + public void restoreTokenOnTick() { + this.currentTokens = Math.min(Config.entityCullingRateLimitingBucketSize(), this.currentTokens + Config.entityCullingRateLimitingRestorePerTick()); + } + + public boolean takeToken() { + if (this.currentTokens > 0) { + this.currentTokens--; + return true; + } + return false; + } + public boolean isVisible(CullingData cullable, Vec3d cameraPos) { // 情空标志位 Arrays.fill(this.canCheckLastHitBlock, false); @@ -58,7 +81,7 @@ public final class EntityCulling { } // 如果设置了最大距离 - int maxDistance = cullable.maxDistance; + double maxDistance = cullable.maxDistance * this.distanceScale; if (maxDistance > 0) { // 计算AABB到相机的最小距离 double distanceSq = 0.0; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/VoxelIterator.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/VoxelIterator.java deleted file mode 100644 index a77160fa1..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/VoxelIterator.java +++ /dev/null @@ -1,216 +0,0 @@ -package net.momirealms.craftengine.core.plugin.entityculling; - -import java.util.Iterator; -import java.util.NoSuchElementException; - -// Amanatides, J., & Woo, A. A Fast Voxel Traversal Algorithm for Ray Tracing. http://www.cse.yorku.ca/~amana/research/grid.pdf. -public final class VoxelIterator implements Iterator { - private int x; - private int y; - private int z; - private int stepX; - private int stepY; - private int stepZ; - private double tMax; - private double tMaxX; - private double tMaxY; - private double tMaxZ; - private double tDeltaX; - private double tDeltaY; - private double tDeltaZ; - private int[] ref = new int[3]; // This implementation always returns ref or refSwap to avoid garbage. Can easily be changed if needed. - private int[] refSwap = new int[3]; - private int[] next; - - public VoxelIterator(double startX, double startY, double startZ, double endX, double endY, double endZ) { - initialize(startX, startY, startZ, endX, endY, endZ); - } - - public VoxelIterator(int x, int y, int z, double startX, double startY, double startZ, double endX, double endY, double endZ) { - initialize(x, y, z, startX, startY, startZ, endX, endY, endZ); - } - - public VoxelIterator(double startX, double startY, double startZ, double directionX, double directionY, double directionZ, double distance) { - initialize(startX, startY, startZ, directionX, directionY, directionZ, distance); - } - - public VoxelIterator(int x, int y, int z, double startX, double startY, double startZ, double directionX, double directionY, double directionZ, double distance) { - initialize(x, y, z, startX, startY, startZ, directionX, directionY, directionZ, distance); - } - - public VoxelIterator(double startX, double startY, double startZ, double directionX, double directionY, double directionZ, double distance, boolean normalized) { - if (normalized) { - initializeNormalized(startX, startY, startZ, directionX, directionY, directionZ, distance); - } else { - initialize(startX, startY, startZ, directionX, directionY, directionZ, distance); - } - } - - public VoxelIterator(int x, int y, int z, double startX, double startY, double startZ, double directionX, double directionY, double directionZ, double distance, boolean normalized) { - if (normalized) { - initializeNormalized(x, y, z, startX, startY, startZ, directionX, directionY, directionZ, distance); - } else { - initialize(x, y, z, startX, startY, startZ, directionX, directionY, directionZ, distance); - } - } - - public VoxelIterator initialize(double startX, double startY, double startZ, double endX, double endY, double endZ) { - return initialize(floor(startX), floor(startY), floor(startZ), startX, startY, startZ, endX, endY, endZ); - } - - public VoxelIterator initialize(int x, int y, int z, double startX, double startY, double startZ, double endX, double endY, double endZ) { - double directionX = endX - startX; - double directionY = endY - startY; - double directionZ = endZ - startZ; - double distance = Math.sqrt(directionX * directionX + directionY * directionY + directionZ * directionZ); - double fixedDistance = distance == 0. ? Double.NaN : distance; - directionX /= fixedDistance; - directionY /= fixedDistance; - directionZ /= fixedDistance; - return initializeNormalized(x, y, z, startX, startY, startZ, directionX, directionY, directionZ, distance); - } - - public VoxelIterator initialize(double startX, double startY, double startZ, double directionX, double directionY, double directionZ, double distance) { - return initialize(floor(startX), floor(startY), floor(startZ), startX, startY, startZ, directionX, directionY, directionZ, distance); - } - - public VoxelIterator initialize(int x, int y, int z, double startX, double startY, double startZ, double directionX, double directionY, double directionZ, double distance) { - double signum = Math.signum(distance); - directionX *= signum; - directionY *= signum; - directionZ *= signum; - double length = Math.sqrt(directionX * directionX + directionY * directionY + directionZ * directionZ); - - if (length == 0.) { - length = Double.NaN; - } - - directionX /= length; - directionY /= length; - directionZ /= length; - return initializeNormalized(x, y, z, startX, startY, startZ, directionX, directionY, directionZ, Math.abs(distance)); - } - - public VoxelIterator initializeNormalized(double startX, double startY, double startZ, double directionX, double directionY, double directionZ, double distance) { - return initializeNormalized(floor(startX), floor(startY), floor(startZ), startX, startY, startZ, directionX, directionY, directionZ, Math.abs(distance)); - } - - public VoxelIterator initializeNormalized(int x, int y, int z, double startX, double startY, double startZ, double directionX, double directionY, double directionZ, double distance) { - this.x = x; - this.y = y; - this.z = z; - tMax = distance; - stepX = directionX < 0. ? -1 : 1; - stepY = directionY < 0. ? -1 : 1; - stepZ = directionZ < 0. ? -1 : 1; - tMaxX = directionX == 0. ? Double.POSITIVE_INFINITY : (x + (stepX + 1) / 2 - startX) / directionX; - tMaxY = directionY == 0. ? Double.POSITIVE_INFINITY : (y + (stepY + 1) / 2 - startY) / directionY; - tMaxZ = directionZ == 0. ? Double.POSITIVE_INFINITY : (z + (stepZ + 1) / 2 - startZ) / directionZ; - tDeltaX = 1. / Math.abs(directionX); - tDeltaY = 1. / Math.abs(directionY); - tDeltaZ = 1. / Math.abs(directionZ); - next = ref; - ref[0] = x; - ref[1] = y; - ref[2] = z; - return this; - } - - public int[] calculateNext() { - if (tMaxX < tMaxY) { - if (tMaxZ < tMaxX) { - if (tMaxZ <= tMax) { - z += stepZ; - // next = new int[] { x, y, z }; - ref[0] = x; - ref[1] = y; - ref[2] = z; - tMaxZ += tDeltaZ; - } else { - next = null; - } - } else { - if (tMaxX <= tMax) { - if (tMaxZ == tMaxX) { - z += stepZ; - tMaxZ += tDeltaZ; - } - - x += stepX; - // next = new int[] { x, y, z }; - ref[0] = x; - ref[1] = y; - ref[2] = z; - tMaxX += tDeltaX; - } else { - next = null; - } - } - } else if (tMaxY < tMaxZ) { - if (tMaxY <= tMax) { - if (tMaxX == tMaxY) { - x += stepX; - tMaxX += tDeltaX; - } - - y += stepY; - // next = new int[] { x, y, z }; - ref[0] = x; - ref[1] = y; - ref[2] = z; - tMaxY += tDeltaY; - } else { - next = null; - } - } else { - if (tMaxZ <= tMax) { - if (tMaxX == tMaxZ) { - x += stepX; - tMaxX += tDeltaX; - } - - if (tMaxY == tMaxZ) { - y += stepY; - tMaxY += tDeltaY; - } - - z += stepZ; - // next = new int[] { x, y, z }; - ref[0] = x; - ref[1] = y; - ref[2] = z; - tMaxZ += tDeltaZ; - } else { - next = null; - } - } - - return next; - } - - @Override - public boolean hasNext() { - return next != null; - } - - @Override - public int[] next() { - int[] next = this.next; - - if (next == null) { - throw new NoSuchElementException(); - } - - int[] temp = ref; - ref = refSwap; - refSwap = temp; - this.next = ref; - calculateNext(); - return next; - } - - private static int floor(double value) { - int i = (int) value; - return value < (double) i ? i - 1 : i; - } -} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/MessageConstants.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/MessageConstants.java index b488bde08..81174c5d0 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/MessageConstants.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/MessageConstants.java @@ -41,4 +41,5 @@ public interface MessageConstants { TranslatableComponent.Builder COMMAND_ITEM_CLEAR_FAILED_MULTIPLE = Component.translatable().key("command.item.clear.failed.multiple"); TranslatableComponent.Builder COMMAND_ITEM_CLEAR_TEST_SINGLE = Component.translatable().key("command.item.clear.test.single"); TranslatableComponent.Builder COMMAND_ITEM_CLEAR_TEST_MULTIPLE = Component.translatable().key("command.item.clear.test.multiple"); + TranslatableComponent.Builder COMMAND_ENTITY_VIEW_DISTANCE_SCALE_SET_SUCCESS = Component.translatable().key("command.entity_view_distance_scale.set.success"); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/VirtualCullableObject.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/VirtualCullableObject.java index 80b6f9ef2..44af9f41b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/VirtualCullableObject.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/VirtualCullableObject.java @@ -5,7 +5,7 @@ import net.momirealms.craftengine.core.world.Cullable; public class VirtualCullableObject { public final Cullable cullable; - private boolean isShown; + public boolean isShown; public VirtualCullableObject(Cullable cullable) { this.cullable = cullable; diff --git a/gradle.properties b/gradle.properties index bf5da7ca3..1f7888077 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,8 +2,8 @@ org.gradle.jvmargs=-Xmx1G # Project settings project_version=0.0.65.14 -config_version=58 -lang_version=40 +config_version=60 +lang_version=41 project_group=net.momirealms latest_supported_version=1.21.10 From 0b3d52b39b48e1f355729547f8b58a848b4fb1e9 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sat, 29 Nov 2025 17:03:09 +0800 Subject: [PATCH 06/11] Update Config.java --- .../net/momirealms/craftengine/core/plugin/config/Config.java | 1 + 1 file changed, 1 insertion(+) 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 db7db9bab..4addc3d9e 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 @@ -579,6 +579,7 @@ public class Config { } client_optimization$entity_culling$view_distance = config.getInt("client-optimization.entity-culling.view-distance", 64); client_optimization$entity_culling$threads = config.getInt("client-optimization.entity-culling.threads", 1); + client_optimization$entity_culling$rate_limiting$enable = config.getBoolean("client-optimization.entity-culling.rate-limiting.enable", true); client_optimization$entity_culling$rate_limiting$bucket_size = config.getInt("client-optimization.entity-culling.rate-limiting.bucket-size", 300); client_optimization$entity_culling$rate_limiting$restore_per_tick = config.getInt("client-optimization.entity-culling.rate-limiting.restore-per-tick", 5); From 8d9be05f112c952ade846d700cc2b56ede35bae5 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sat, 29 Nov 2025 21:28:02 +0800 Subject: [PATCH 07/11] =?UTF-8?q?=E5=AE=8C=E6=88=90=E7=AC=AC=E4=B8=89?= =?UTF-8?q?=E4=BA=BA=E7=A7=B0=E5=85=89=E7=BA=BF=E8=BF=BD=E8=B8=AA=EF=BC=8C?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=9C=BC=E7=9D=9B=E4=BD=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SetEntityViewDistanceScaleCommand.java | 10 -- .../plugin/network/BukkitNetworkManager.java | 9 +- .../plugin/user/BukkitServerPlayer.java | 132 ++++++++++++++---- common-files/src/main/resources/config.yml | 6 +- .../core/block/AbstractBlockManager.java | 5 +- .../core/entity/player/Player.java | 2 + .../core/plugin/config/Config.java | 6 + .../plugin/entityculling/CullingData.java | 14 +- .../plugin/entityculling/EntityCulling.java | 27 ++-- .../entityculling/EntityCullingThread.java | 12 +- .../craftengine/core/world/chunk/CEChunk.java | 2 +- gradle.properties | 2 +- 12 files changed, 152 insertions(+), 75 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/SetEntityViewDistanceScaleCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/SetEntityViewDistanceScaleCommand.java index bd24341e3..17d5ca2e3 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/SetEntityViewDistanceScaleCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/SetEntityViewDistanceScaleCommand.java @@ -8,21 +8,11 @@ import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager; import net.momirealms.craftengine.core.plugin.command.FlagKeys; import net.momirealms.craftengine.core.plugin.locale.MessageConstants; -import net.momirealms.craftengine.core.plugin.locale.TranslationManager; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; -import org.checkerframework.checker.nullness.qual.NonNull; import org.incendo.cloud.Command; import org.incendo.cloud.bukkit.parser.PlayerParser; -import org.incendo.cloud.context.CommandContext; -import org.incendo.cloud.context.CommandInput; import org.incendo.cloud.parser.standard.DoubleParser; -import org.incendo.cloud.parser.standard.StringParser; -import org.incendo.cloud.suggestion.Suggestion; -import org.incendo.cloud.suggestion.SuggestionProvider; - -import java.util.Locale; -import java.util.concurrent.CompletableFuture; public class SetEntityViewDistanceScaleCommand extends BukkitCommandFeature { 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 df5990e82..8ea7189f7 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,7 +84,6 @@ 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.*; @@ -2036,7 +2035,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes boolean hasGlobalPalette = false; // 创建客户端侧世界(只在开启实体情况下创建) - ClientSection[] clientSections = Config.enableEntityCulling() ? new ClientSection[count] : null; + ClientSection[] clientSections = Config.entityCullingRayTracing() ? new ClientSection[count] : null; for (int i = 0; i < count; i++) { MCSection mcSection = new MCSection(user.clientBlockList(), this.blockList, this.biomeList); @@ -2215,7 +2214,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes // 获取客户端侧区域 ClientSection clientSection = null; - if (Config.enableEntityCulling()) { + if (Config.entityCullingRayTracing()) { SectionPos sectionPos = SectionPos.of(sPos); ClientChunk trackedChunk = user.getTrackedChunk(sectionPos.asChunkPos().longKey); clientSection = trackedChunk.sectionById(sectionPos.y); @@ -2261,7 +2260,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes FriendlyByteBuf buf = event.getBuffer(); BlockPos pos = buf.readBlockPos(); int before = buf.readVarInt(); - if (Config.enableEntityCulling()) { + if (Config.entityCullingRayTracing()) { ClientChunk trackedChunk = user.getTrackedChunk(ChunkPos.asLong(pos.x >> 4, pos.z >> 4)); if (trackedChunk != null) { trackedChunk.setOccluding(pos.x, pos.y, pos.z, this.occlusionPredicate.test(before)); @@ -2441,7 +2440,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes BlockPos blockPos = buf.readBlockPos(); int state = buf.readInt(); // 移除不透明设置 - if (Config.enableEntityCulling()) { + if (Config.entityCullingRayTracing()) { ClientChunk trackedChunk = user.getTrackedChunk(ChunkPos.asLong(blockPos.x >> 4, blockPos.z >> 4)); if (trackedChunk != null) { trackedChunk.setOccluding(blockPos.x, blockPos.y, blockPos.z, 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 9ecf68413..d6ac569ac 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 @@ -52,6 +52,7 @@ import org.bukkit.block.Block; import org.bukkit.damage.DamageSource; import org.bukkit.damage.DamageType; import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; import org.bukkit.event.player.PlayerCommandPreprocessEvent; import org.bukkit.event.player.PlayerTeleportEvent; import org.bukkit.inventory.EquipmentSlot; @@ -116,8 +117,6 @@ public class BukkitServerPlayer extends Player { private IntIdentityList blockList = new IntIdentityList(BlockStateUtils.vanillaBlockStateCount()); // cache if player can break blocks private boolean clientSideCanBreak = true; - // prevent AFK players from consuming too much CPU resource on predicting - private Location previousEyeLocation; // a cooldown for better breaking experience private int lastSuccessfulBreak; // player's game tick @@ -145,6 +144,10 @@ public class BukkitServerPlayer extends Player { // 跟踪到的方块实体渲染器 private final Map trackedBlockEntityRenderers = new ConcurrentHashMap<>(); private final EntityCulling culling; + private Vec3d firstPersonCameraVec3; + private Vec3d thirdPersonCameraVec3; + // 玩家眼睛所在位置 + private Location eyeLocation; public BukkitServerPlayer(BukkitCraftEngine plugin, @Nullable Channel channel) { this.channel = channel; @@ -512,10 +515,11 @@ public class BukkitServerPlayer extends Player { @Override public void tick() { // not fully online - if (serverPlayer() == null) return; + Object serverPlayer = serverPlayer(); + if (serverPlayer == null) return; + org.bukkit.entity.Player bukkitPlayer = platformPlayer(); if (VersionHelper.isFolia()) { try { - Object serverPlayer = serverPlayer(); Object gameMode = FastNMS.INSTANCE.field$ServerPlayer$gameMode(serverPlayer); this.gameTicks = (int) CoreReflections.field$ServerPlayerGameMode$gameTicks.get(gameMode); } catch (ReflectiveOperationException e) { @@ -527,12 +531,30 @@ public class BukkitServerPlayer extends Player { if (this.gameTicks % 20 == 0) { this.updateGUI(); } + + // 更新眼睛位置 + { + Location unsureEyeLocation = bukkitPlayer.getEyeLocation(); + Entity vehicle = bukkitPlayer.getVehicle(); + if (vehicle != null) { + Object mountPos = FastNMS.INSTANCE.method$Entity$getPassengerRidingPosition(FastNMS.INSTANCE.method$CraftEntity$getHandle(vehicle), serverPlayer); + unsureEyeLocation.set(FastNMS.INSTANCE.field$Vec3$x(mountPos), FastNMS.INSTANCE.field$Vec3$y(mountPos) + bukkitPlayer.getEyeHeight(), FastNMS.INSTANCE.field$Vec3$z(mountPos)); + } + if (Config.predictBreaking() && !this.isDestroyingCustomBlock && !unsureEyeLocation.equals(this.eyeLocation)) { + // if it's not destroying blocks, we do predict + if ((gameTicks() + entityID()) % Config.predictBreakingInterval() == 0) { + this.predictNextBlockToMine(); + } + } + this.eyeLocation = unsureEyeLocation; + } + if (hasSwingHand()) { if (this.isDestroyingBlock) { this.tickBlockDestroy(); } else if (this.lastStopMiningPos != null && this.gameTicks - this.lastStopMiningTick <= 5) { double range = getCachedInteractionRange(); - RayTraceResult result = platformPlayer().rayTraceBlocks(range, FluidCollisionMode.NEVER); + RayTraceResult result = rayTrace(this.eyeLocation, range, FluidCollisionMode.NEVER); if (result != null) { Block hitBlock = result.getHitBlock(); if (hitBlock != null) { @@ -555,36 +577,57 @@ public class BukkitServerPlayer extends Player { this.isHackedBreak = false; } } - if (Config.predictBreaking() && !this.isDestroyingCustomBlock) { - // if it's not destroying blocks, we do predict - if ((gameTicks() + entityID()) % Config.predictBreakingInterval() == 0) { - Location eyeLocation = platformPlayer().getEyeLocation(); - if (eyeLocation.equals(this.previousEyeLocation)) { - return; + + if (Config.entityCullingRayTracing()) { + org.bukkit.entity.Player player = platformPlayer(); + Location eyeLocation = this.eyeLocation.clone(); + this.firstPersonCameraVec3 = LocationUtils.toVec3d(eyeLocation); + int distance = 4; + if (VersionHelper.isOrAbove1_21_6()) { + Entity vehicle = player.getVehicle(); + if (vehicle != null && vehicle.getType() == EntityType.HAPPY_GHAST) { + distance = 8; } - this.previousEyeLocation = eyeLocation; - this.predictNextBlockToMine(); } + this.thirdPersonCameraVec3 = LocationUtils.toVec3d(eyeLocation.subtract(eyeLocation.getDirection().multiply(distance))); } } @Override public void entityCullingTick() { this.culling.restoreTokenOnTick(); + boolean useRayTracing = Config.entityCullingRayTracing(); 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); - if (visible != cullableObject.isShown) { - if (Config.enableEntityCullingRateLimiting() && visible) { - if (this.culling.takeToken()) { - cullableObject.setShown(this, true); - } - } else { - cullableObject.setShown(this, visible); + boolean firstPersonVisible = this.culling.isVisible(cullingData, this.firstPersonCameraVec3, useRayTracing); + // 之前可见 + if (cullableObject.isShown) { + boolean thirdPersonVisible = this.culling.isVisible(cullingData, this.thirdPersonCameraVec3, useRayTracing); + if (!firstPersonVisible && !thirdPersonVisible) { + cullableObject.setShown(this, false); } } + // 之前不可见 + else { + // 但是第一人称可见了 + if (firstPersonVisible) { + // 下次再说 + if (Config.enableEntityCullingRateLimiting() && !this.culling.takeToken()) { + continue; + } + cullableObject.setShown(this, true); + continue; + } + if (this.culling.isVisible(cullingData, this.thirdPersonCameraVec3, useRayTracing)) { + // 下次再说 + if (Config.enableEntityCullingRateLimiting() && !this.culling.takeToken()) { + continue; + } + cullableObject.setShown(this, true); + } + // 仍然不可见 + } } else { cullableObject.setShown(this, true); } @@ -607,17 +650,12 @@ public class BukkitServerPlayer extends Player { public boolean canInteractWithBlock(BlockPos pos, double distance) { double d = this.getCachedInteractionRange() + distance; - return (new AABB(pos)).distanceToSqr(this.getEyePosition()) < d * d; + return (new AABB(pos)).distanceToSqr(this.getEyePos()) < d * d; } public boolean canInteractPoint(Vec3d pos, double distance) { double d = this.getCachedInteractionRange() + distance; - return Vec3d.distanceToSqr(this.getEyePosition(), pos) < d * d; - } - - public final Vec3d getEyePosition() { - Location eyeLocation = this.platformPlayer().getEyeLocation(); - return new Vec3d(eyeLocation.getX(), eyeLocation.getY(), eyeLocation.getZ()); + return Vec3d.distanceToSqr(this.getEyePos(), pos) < d * d; } @Override @@ -777,7 +815,7 @@ public class BukkitServerPlayer extends Player { try { org.bukkit.entity.Player player = platformPlayer(); double range = getCachedInteractionRange(); - RayTraceResult result = player.rayTraceBlocks(range, FluidCollisionMode.NEVER); + RayTraceResult result = rayTrace(this.eyeLocation, range, FluidCollisionMode.NEVER); if (result == null) return; Block hitBlock = result.getHitBlock(); if (hitBlock == null) return; @@ -1217,7 +1255,7 @@ public class BukkitServerPlayer extends Player { @Override public void removeTrackedChunk(long chunkPos) { this.trackedChunks.remove(chunkPos); - if (Config.enableEntityCulling()) { + if (Config.entityCullingRayTracing()) { this.culling.removeLastVisitChunkIfMatches((int) chunkPos, (int) (chunkPos >> 32)); } } @@ -1367,4 +1405,36 @@ public class BukkitServerPlayer extends Player { public void clearTrackedBlockEntities() { this.trackedBlockEntityRenderers.clear(); } + + @Override + public WorldPosition eyePosition() { + return LocationUtils.toWorldPosition(this.getEyeLocation()); + } + + public Location getEyeLocation() { + org.bukkit.entity.Player player = platformPlayer(); + Location eyeLocation = player.getEyeLocation(); + Entity vehicle = player.getVehicle(); + if (vehicle != null) { + Object mountPos = FastNMS.INSTANCE.method$Entity$getPassengerRidingPosition(FastNMS.INSTANCE.method$CraftEntity$getHandle(vehicle), serverPlayer()); + eyeLocation.set(FastNMS.INSTANCE.field$Vec3$x(mountPos), FastNMS.INSTANCE.field$Vec3$y(mountPos) + player.getEyeHeight(), FastNMS.INSTANCE.field$Vec3$z(mountPos)); + } + return eyeLocation; + } + + public Vec3d getEyePos() { + org.bukkit.entity.Player player = platformPlayer(); + Entity vehicle = player.getVehicle(); + if (vehicle != null) { + Object mountPos = FastNMS.INSTANCE.method$Entity$getPassengerRidingPosition(FastNMS.INSTANCE.method$CraftEntity$getHandle(vehicle), serverPlayer()); + return new Vec3d(FastNMS.INSTANCE.field$Vec3$x(mountPos), FastNMS.INSTANCE.field$Vec3$y(mountPos) + player.getEyeHeight(), FastNMS.INSTANCE.field$Vec3$z(mountPos)); + } else { + Location location = player.getLocation(); + return new Vec3d(location.getX(), location.getY() + player.getEyeHeight(), location.getZ()); + } + } + + private RayTraceResult rayTrace(Location start, double range, FluidCollisionMode mode) { + return start.getWorld().rayTraceBlocks(start, start.getDirection(), range, mode); + } } diff --git a/common-files/src/main/resources/config.yml b/common-files/src/main/resources/config.yml index c458a8f05..6c3d4bf47 100644 --- a/common-files/src/main/resources/config.yml +++ b/common-files/src/main/resources/config.yml @@ -554,11 +554,13 @@ chunk-system: # [Premium Exclusive] client-optimization: - # Using server-side ray tracing algorithms to hide block entities/furniture and reduce client-side rendering pressure. # Requires a restart to fully apply. entity-culling: enable: true - view-distance: 64 + # Using server-side ray tracing algorithms to hide block entities/furniture and reduce client-side rendering pressure. + ray-tracing: true + # Cull entities based on distance + view-distance: 64 # -1 = no limit # Determining the number of threads to execute these raytrace operations threads: 1 # Limit the maximum number of entities with visibility changes per tick for one player diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java index 0570149e7..5b0dbf02b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java @@ -715,13 +715,14 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem return null; } if (!(arguments instanceof Map)) { - return new CullingData(DEFAULT_BLOCK_ENTITY_AABB, Config.entityCullingViewDistance(), 0.5); + return new CullingData(DEFAULT_BLOCK_ENTITY_AABB, Config.entityCullingViewDistance(), 0.5, true); } Map argumentsMap = ResourceConfigUtils.getAsMap(arguments, "entity-culling"); return new CullingData( ResourceConfigUtils.getAsAABB(argumentsMap.getOrDefault("aabb", 1), "aabb"), ResourceConfigUtils.getAsInt(argumentsMap.getOrDefault("view-distance", Config.entityCullingViewDistance()), "view-distance"), - ResourceConfigUtils.getAsDouble(argumentsMap.getOrDefault("aabb-expansion", 0.5), "aabb-expansion") + ResourceConfigUtils.getAsDouble(argumentsMap.getOrDefault("aabb-expansion", 0.5), "aabb-expansion"), + ResourceConfigUtils.getAsBoolean(argumentsMap.getOrDefault("ray-tracing", true), "ray-tracing") ); } 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 8c63646b2..6826bbc12 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 @@ -218,4 +218,6 @@ public abstract class Player extends AbstractEntity implements NetWorkUser { @Override public void remove() { } + + public abstract WorldPosition eyePosition(); } 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 4addc3d9e..df09a8b08 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 @@ -208,6 +208,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; + protected boolean client_optimization$entity_culling$ray_tracing; protected boolean client_optimization$entity_culling$rate_limiting$enable; protected int client_optimization$entity_culling$rate_limiting$bucket_size; protected int client_optimization$entity_culling$rate_limiting$restore_per_tick; @@ -579,6 +580,7 @@ public class Config { } client_optimization$entity_culling$view_distance = config.getInt("client-optimization.entity-culling.view-distance", 64); client_optimization$entity_culling$threads = config.getInt("client-optimization.entity-culling.threads", 1); + client_optimization$entity_culling$ray_tracing = client_optimization$entity_culling$enable && config.getBoolean("client-optimization.entity-culling.ray-tracing", true); client_optimization$entity_culling$rate_limiting$enable = config.getBoolean("client-optimization.entity-culling.rate-limiting.enable", true); client_optimization$entity_culling$rate_limiting$bucket_size = config.getInt("client-optimization.entity-culling.rate-limiting.bucket-size", 300); client_optimization$entity_culling$rate_limiting$restore_per_tick = config.getInt("client-optimization.entity-culling.rate-limiting.restore-per-tick", 5); @@ -1197,6 +1199,10 @@ public class Config { return instance.client_optimization$entity_culling$rate_limiting$restore_per_tick; } + public static boolean entityCullingRayTracing() { + return instance.client_optimization$entity_culling$ray_tracing; + } + 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/CullingData.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/CullingData.java index 8d67a1349..67c7131f0 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/CullingData.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/CullingData.java @@ -6,22 +6,28 @@ public final class CullingData { public final AABB aabb; public final int maxDistance; public final double aabbExpansion; + public final boolean rayTracing; - public CullingData(AABB aabb, int maxDistance, double aabbExpansion) { + public CullingData(AABB aabb, int maxDistance, double aabbExpansion, boolean rayTracing) { this.aabb = aabb; this.maxDistance = maxDistance; this.aabbExpansion = aabbExpansion; + this.rayTracing = rayTracing; } public AABB aabb() { - return aabb; + return this.aabb; } public int maxDistance() { - return maxDistance; + return this.maxDistance; } public double aabbExpansion() { - return aabbExpansion; + return this.aabbExpansion; + } + + public boolean rayTracing() { + return this.rayTracing; } } 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 21efad1c6..5ced80c59 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 @@ -16,8 +16,8 @@ public final class EntityCulling { private final Player player; private final boolean[] dotSelectors = new boolean[MAX_SAMPLES]; private final MutableVec3d[] targetPoints = new MutableVec3d[MAX_SAMPLES]; - private final int[] lastHitBlock = new int[MAX_SAMPLES * 3]; - private final boolean[] canCheckLastHitBlock = new boolean[MAX_SAMPLES]; + private final int[] lastHitBlock = new int[3]; + private boolean canCheckLastHitBlock = false; private int hitBlockCount = 0; private int lastVisitChunkX = Integer.MAX_VALUE; private int lastVisitChunkZ = Integer.MAX_VALUE; @@ -52,9 +52,9 @@ public final class EntityCulling { return false; } - public boolean isVisible(CullingData cullable, Vec3d cameraPos) { + public boolean isVisible(CullingData cullable, Vec3d cameraPos, boolean rayTracing) { // 情空标志位 - Arrays.fill(this.canCheckLastHitBlock, false); + this.canCheckLastHitBlock = false; this.hitBlockCount = 0; AABB aabb = cullable.aabb; double aabbExpansion = cullable.aabbExpansion; @@ -97,6 +97,10 @@ public final class EntityCulling { } } + if (!rayTracing || !cullable.rayTracing) { + return true; + } + // 清空之前的缓存 Arrays.fill(this.dotSelectors, false); if (relX == Relative.POSITIVE) { @@ -190,7 +194,7 @@ public final class EntityCulling { int startBlockZ = MiscUtils.floor(start.z); // 遍历所有目标点进行视线检测 - outer: for (int targetIndex = 0; targetIndex < targetCount; targetIndex++) { + for (int targetIndex = 0; targetIndex < targetCount; targetIndex++) { MutableVec3d currentTarget = targets[targetIndex]; // 计算起点到目标的相对向量(世界坐标差) @@ -199,14 +203,9 @@ public final class EntityCulling { double deltaZ = start.z - currentTarget.z; // 检查之前命中的方块,大概率还是命中 - for (int i = 0; i < MAX_SAMPLES; i++) { - if (this.canCheckLastHitBlock[i]) { - int offset = i * 3; - if (rayIntersection(this.lastHitBlock[offset], this.lastHitBlock[offset + 1], this.lastHitBlock[offset + 2], start, new MutableVec3d(deltaX, deltaY, deltaZ).normalize())) { - continue outer; - } - } else { - break; + if (this.canCheckLastHitBlock) { + if (rayIntersection(this.lastHitBlock[0], this.lastHitBlock[1], this.lastHitBlock[2], start, new MutableVec3d(deltaX, deltaY, deltaZ).normalize())) { + continue; } } @@ -292,7 +291,7 @@ public final class EntityCulling { if (isLineOfSightClear) { return true; } else { - this.canCheckLastHitBlock[this.hitBlockCount++] = true; + this.canCheckLastHitBlock = true; } } 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 index fefaa7896..7eb6f1e69 100644 --- 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 @@ -2,6 +2,7 @@ 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.config.Config; import net.momirealms.craftengine.core.plugin.logger.Debugger; import java.util.concurrent.Executors; @@ -14,6 +15,7 @@ public class EntityCullingThread { private final AtomicBoolean isRunning = new AtomicBoolean(false); private final int id; private final int threads; + private int timer; public EntityCullingThread(int id, int threads) { this.id = id; @@ -35,7 +37,7 @@ public class EntityCullingThread { this.scheduler.execute(() -> { try { int processed = 0; - long startTime = System.currentTimeMillis(); + long startTime = System.nanoTime(); for (Player player : CraftEngine.instance().networkManager().onlineUsers()) { // 使用绝对值确保非负,使用 threads 而不是 threads-1 确保均匀分布 @@ -45,10 +47,10 @@ public class EntityCullingThread { } } - 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); + long duration = System.nanoTime() - startTime; + if (Config.debugEntityCulling() && this.timer++ % 20 == 0) { + String value = String.format("EntityCullingThread-%d processed %d players in %sms", + this.id, processed, String.format("%.2f", duration / 1_000_000.0)); Debugger.ENTITY_CULLING.debug(() -> value); } } catch (Exception e) { 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 e8061a7d8..5e59155d6 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 @@ -143,7 +143,7 @@ public class CEChunk { ConstantBlockEntityRenderer renderer = new ConstantBlockEntityRenderer( elements, Optional.ofNullable(state.cullingData()) - .map(data -> new CullingData(data.aabb.move(pos), data.maxDistance, data.aabbExpansion)) + .map(data -> new CullingData(data.aabb.move(pos), data.maxDistance, data.aabbExpansion, data.rayTracing)) .orElse(null) ); World wrappedWorld = this.world.world(); diff --git a/gradle.properties b/gradle.properties index 1f7888077..3691ff1e9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -48,7 +48,7 @@ byte_buddy_version=1.18.1 ahocorasick_version=0.6.3 snake_yaml_version=2.5 anti_grief_version=1.0.5 -nms_helper_version=1.0.137 +nms_helper_version=1.0.138 evalex_version=3.5.0 reactive_streams_version=1.0.4 amazon_awssdk_version=2.38.7 From d00f4789c49e9b36cad56f43da6926011079e2f9 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sat, 29 Nov 2025 21:48:48 +0800 Subject: [PATCH 08/11] Update BukkitServerPlayer.java --- .../craftengine/bukkit/plugin/user/BukkitServerPlayer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d6ac569ac..2348ca5fc 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 @@ -675,7 +675,7 @@ public class BukkitServerPlayer extends Player { private void predictNextBlockToMine() { double range = getCachedInteractionRange() + Config.extendedInteractionRange(); - RayTraceResult result = platformPlayer().rayTraceBlocks(range, FluidCollisionMode.NEVER); + RayTraceResult result = rayTrace(this.eyeLocation, range, FluidCollisionMode.NEVER); if (result == null) { if (!this.clientSideCanBreak) { setClientSideCanBreakBlock(true); From e8ef466fce5be13f8ede4eff131e9500a0b788f9 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sun, 30 Nov 2025 00:49:37 +0800 Subject: [PATCH 09/11] Update CEChunk.java --- .../net/momirealms/craftengine/core/world/chunk/CEChunk.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 5e59155d6..67695fac8 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 @@ -187,7 +187,10 @@ public class CEChunk { // 如果启用实体剔除,那么只添加记录 if (Config.enableEntityCulling()) { for (Player player : trackedBy) { - player.addTrackedBlockEntity(pos, renderer); + VirtualCullableObject trackedBlockEntity = player.addTrackedBlockEntity(pos, renderer); + if (trackedBlockEntity != null && trackedBlockEntity.isShown()) { + trackedBlockEntity.setShown(player, false); + } } } // 否则直接显示 From ff920b74df9602682a635accc54069d4382f75cd Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sun, 30 Nov 2025 01:20:11 +0800 Subject: [PATCH 10/11] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=97=8B=E8=BD=AC?= =?UTF-8?q?=E8=BF=87=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ItemDisplayBlockEntityElementConfig.java | 24 +++++++------------ .../TextDisplayBlockEntityElementConfig.java | 18 ++++---------- .../default/configuration/blocks/sofa.yml | 18 +++++++------- 3 files changed, 22 insertions(+), 38 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemDisplayBlockEntityElementConfig.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemDisplayBlockEntityElementConfig.java index a8842a81f..e28d98421 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemDisplayBlockEntityElementConfig.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemDisplayBlockEntityElementConfig.java @@ -61,14 +61,14 @@ public class ItemDisplayBlockEntityElementConfig implements BlockEntityElementCo this.shadowStrength = shadowStrength; this.lazyMetadataPacket = player -> { List dataValues = new ArrayList<>(); - ItemDisplayEntityData.DisplayedItem.addEntityDataIfNotDefaultValue(item.apply(player).getLiteralObject(), dataValues); - ItemDisplayEntityData.Scale.addEntityDataIfNotDefaultValue(this.scale, dataValues); - ItemDisplayEntityData.RotationLeft.addEntityDataIfNotDefaultValue(this.rotation, dataValues); - ItemDisplayEntityData.BillboardConstraints.addEntityDataIfNotDefaultValue(this.billboard.id(), dataValues); - ItemDisplayEntityData.Translation.addEntityDataIfNotDefaultValue(this.translation, dataValues); - ItemDisplayEntityData.DisplayType.addEntityDataIfNotDefaultValue(this.displayContext.id(), dataValues); - ItemDisplayEntityData.ShadowRadius.addEntityDataIfNotDefaultValue(this.shadowRadius, dataValues); - ItemDisplayEntityData.ShadowStrength.addEntityDataIfNotDefaultValue(this.shadowStrength, dataValues); + ItemDisplayEntityData.DisplayedItem.addEntityData(item.apply(player).getLiteralObject(), dataValues); + ItemDisplayEntityData.Scale.addEntityData(this.scale, dataValues); + ItemDisplayEntityData.RotationLeft.addEntityData(this.rotation, dataValues); + ItemDisplayEntityData.BillboardConstraints.addEntityData(this.billboard.id(), dataValues); + ItemDisplayEntityData.Translation.addEntityData(this.translation, dataValues); + ItemDisplayEntityData.DisplayType.addEntityData(this.displayContext.id(), dataValues); + ItemDisplayEntityData.ShadowRadius.addEntityData(this.shadowRadius, dataValues); + ItemDisplayEntityData.ShadowStrength.addEntityData(this.shadowStrength, dataValues); return dataValues; }; } @@ -80,14 +80,6 @@ public class ItemDisplayBlockEntityElementConfig implements BlockEntityElementCo @Override public ItemDisplayBlockEntityElement create(World world, BlockPos pos, ItemDisplayBlockEntityElement previous) { - Quaternionf previousRotation = previous.config.rotation; - if (previousRotation.x != 0 || previousRotation.y != 0 || previousRotation.z != 0 || previousRotation.w != 1) { - return null; - } - Vector3f translation = previous.config.translation; - if (translation.x != 0 || translation.y != 0 || translation.z != 0) { - return null; - } return new ItemDisplayBlockEntityElement(this, pos, previous.entityId, previous.config.yRot != this.yRot || previous.config.xRot != this.xRot || diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/TextDisplayBlockEntityElementConfig.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/TextDisplayBlockEntityElementConfig.java index 43b6af680..ebf46a890 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/TextDisplayBlockEntityElementConfig.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/TextDisplayBlockEntityElementConfig.java @@ -52,11 +52,11 @@ public class TextDisplayBlockEntityElementConfig implements BlockEntityElementCo this.billboard = billboard; this.lazyMetadataPacket = player -> { List dataValues = new ArrayList<>(); - TextDisplayEntityData.Text.addEntityDataIfNotDefaultValue(ComponentUtils.adventureToMinecraft(text(player)), dataValues); - TextDisplayEntityData.Scale.addEntityDataIfNotDefaultValue(this.scale, dataValues); - TextDisplayEntityData.RotationLeft.addEntityDataIfNotDefaultValue(this.rotation, dataValues); - TextDisplayEntityData.BillboardConstraints.addEntityDataIfNotDefaultValue(this.billboard.id(), dataValues); - TextDisplayEntityData.Translation.addEntityDataIfNotDefaultValue(this.translation, dataValues); + TextDisplayEntityData.Text.addEntityData(ComponentUtils.adventureToMinecraft(text(player)), dataValues); + TextDisplayEntityData.Scale.addEntityData(this.scale, dataValues); + TextDisplayEntityData.RotationLeft.addEntityData(this.rotation, dataValues); + TextDisplayEntityData.BillboardConstraints.addEntityData(this.billboard.id(), dataValues); + TextDisplayEntityData.Translation.addEntityData(this.translation, dataValues); return dataValues; }; } @@ -68,14 +68,6 @@ public class TextDisplayBlockEntityElementConfig implements BlockEntityElementCo @Override public TextDisplayBlockEntityElement create(World world, BlockPos pos, TextDisplayBlockEntityElement previous) { - Quaternionf previousRotation = previous.config.rotation; - if (previousRotation.x != 0 || previousRotation.y != 0 || previousRotation.z != 0 || previousRotation.w != 1) { - return null; - } - Vector3f translation = previous.config.translation; - if (translation.x != 0 || translation.y != 0 || translation.z != 0) { - return null; - } return new TextDisplayBlockEntityElement(this, pos, previous.entityId, previous.config.yRot != this.yRot || previous.config.xRot != this.xRot || diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/sofa.yml b/common-files/src/main/resources/resources/default/configuration/blocks/sofa.yml index 5aba431e7..120c02811 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/sofa.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/sofa.yml @@ -93,7 +93,7 @@ items: state: barrier entity-renderer: item: default:sofa - yaw: 90 + rotation: 90 facing=north,shape=straight: state: barrier entity-renderer: @@ -102,12 +102,12 @@ items: state: barrier entity-renderer: item: default:sofa - yaw: 180 + rotation: 180 facing=west,shape=straight: state: barrier entity-renderer: item: default:sofa - yaw: 270 + rotation: 270 facing=east,shape=inner_left: state: barrier entity-renderer: @@ -116,22 +116,22 @@ items: state: barrier entity-renderer: item: default:sofa_inner - yaw: 270 + rotation: 270 facing=south,shape=inner_left: state: barrier entity-renderer: item: default:sofa_inner - yaw: 90 + rotation: 90 facing=west,shape=inner_left: state: barrier entity-renderer: item: default:sofa_inner - yaw: 180 + rotation: 180 facing=east,shape=inner_right: state: barrier entity-renderer: item: default:sofa_inner - yaw: 90 + rotation: 90 facing=north,shape=inner_right: state: barrier entity-renderer: @@ -140,12 +140,12 @@ items: state: barrier entity-renderer: item: default:sofa_inner - yaw: 180 + rotation: 180 facing=west,shape=inner_right: state: barrier entity-renderer: item: default:sofa_inner - yaw: 270 + rotation: 270 variants: facing=east,shape=inner_left: appearance: facing=east,shape=inner_left From 12106c80c016bce07837272dd1b5eda95319a83c Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sun, 30 Nov 2025 02:56:01 +0800 Subject: [PATCH 11/11] =?UTF-8?q?=E6=94=B9=E8=BF=9Bentity=20renderer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../element/ItemBlockEntityElementConfig.java | 14 ++ .../ItemDisplayBlockEntityElementConfig.java | 19 ++ .../TextDisplayBlockEntityElementConfig.java | 19 ++ .../plugin/user/BukkitServerPlayer.java | 4 +- .../configuration/templates/block_states.yml | 192 +++++++++--------- .../element/BlockEntityElementConfig.java | 4 + .../core/entity/player/Player.java | 2 +- .../craftengine/core/world/chunk/CEChunk.java | 147 +++++++++----- .../chunk/client/VirtualCullableObject.java | 6 +- gradle.properties | 2 +- 10 files changed, 262 insertions(+), 147 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemBlockEntityElementConfig.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemBlockEntityElementConfig.java index 96873d757..b68c2af5d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemBlockEntityElementConfig.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemBlockEntityElementConfig.java @@ -45,6 +45,14 @@ public class ItemBlockEntityElementConfig implements BlockEntityElementConfig elementClass() { return ItemBlockEntityElement.class; @@ -62,6 +70,12 @@ public class ItemBlockEntityElementConfig implements BlockEntityElementConfig elementClass() { return ItemDisplayBlockEntityElement.class; @@ -140,6 +149,16 @@ public class ItemDisplayBlockEntityElementConfig implements BlockEntityElementCo return this.lazyMetadataPacket.apply(player); } + @Override + public boolean equals(Object o) { + if (!(o instanceof ItemDisplayBlockEntityElementConfig that)) return false; + return Float.compare(xRot, that.xRot) == 0 && + Float.compare(yRot, that.yRot) == 0 && + Objects.equal(position, that.position) && + Objects.equal(translation, that.translation) && + Objects.equal(rotation, that.rotation); + } + public static class Factory implements BlockEntityElementConfigFactory { @SuppressWarnings("unchecked") diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/TextDisplayBlockEntityElementConfig.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/TextDisplayBlockEntityElementConfig.java index ebf46a890..12012724d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/TextDisplayBlockEntityElementConfig.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/TextDisplayBlockEntityElementConfig.java @@ -1,5 +1,6 @@ package net.momirealms.craftengine.bukkit.block.entity.renderer.element; +import com.google.common.base.Objects; import net.kyori.adventure.text.Component; import net.momirealms.craftengine.bukkit.entity.data.TextDisplayEntityData; import net.momirealms.craftengine.bukkit.util.ComponentUtils; @@ -75,6 +76,14 @@ public class TextDisplayBlockEntityElementConfig implements BlockEntityElementCo ); } + @Override + public TextDisplayBlockEntityElement createExact(World world, BlockPos pos, TextDisplayBlockEntityElement previous) { + if (!previous.config.equals(this)) { + return null; + } + return new TextDisplayBlockEntityElement(this, pos, previous.entityId, false); + } + @Override public Class elementClass() { return TextDisplayBlockEntityElement.class; @@ -116,6 +125,16 @@ public class TextDisplayBlockEntityElementConfig implements BlockEntityElementCo return this.lazyMetadataPacket.apply(player); } + @Override + public boolean equals(Object o) { + if (!(o instanceof TextDisplayBlockEntityElementConfig that)) return false; + return Float.compare(xRot, that.xRot) == 0 && + Float.compare(yRot, that.yRot) == 0 && + Objects.equal(position, that.position) && + Objects.equal(translation, that.translation) && + Objects.equal(rotation, that.rotation); + } + public static class Factory implements BlockEntityElementConfigFactory { @SuppressWarnings("unchecked") 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 2348ca5fc..fc12b179f 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 @@ -1382,8 +1382,8 @@ public class BukkitServerPlayer extends Player { } @Override - public VirtualCullableObject addTrackedBlockEntity(BlockPos blockPos, ConstantBlockEntityRenderer renderer) { - return this.trackedBlockEntityRenderers.put(blockPos, new VirtualCullableObject(renderer)); + public void addTrackedBlockEntity(BlockPos blockPos, ConstantBlockEntityRenderer renderer) { + this.trackedBlockEntityRenderers.put(blockPos, new VirtualCullableObject(renderer)); } @Override diff --git a/common-files/src/main/resources/resources/default/configuration/templates/block_states.yml b/common-files/src/main/resources/resources/default/configuration/templates/block_states.yml index 3550304a2..c254f682e 100644 --- a/common-files/src/main/resources/resources/default/configuration/templates/block_states.yml +++ b/common-files/src/main/resources/resources/default/configuration/templates/block_states.yml @@ -1834,354 +1834,354 @@ templates: state: ${base_block}[east=false,north=false,south=false,waterlogged=false,west=false] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 east=true,north=false,south=false,waterlogged=false,west=false: state: ${base_block}[east=true,north=false,south=false,waterlogged=false,west=false] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 270 + rotation: 270 east=false,north=true,south=false,waterlogged=false,west=false: state: ${base_block}[east=false,north=true,south=false,waterlogged=false,west=false] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 180 + rotation: 180 east=false,north=false,south=true,waterlogged=false,west=false: state: ${base_block}[east=false,north=false,south=true,waterlogged=false,west=false] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 0 + rotation: 0 east=false,north=false,south=false,waterlogged=false,west=true: state: ${base_block}[east=false,north=false,south=false,waterlogged=false,west=true] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 90 + rotation: 90 east=true,north=true,south=false,waterlogged=false,west=false: state: ${base_block}[east=true,north=true,south=false,waterlogged=false,west=false] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 180 + rotation: 180 - item: ${fence_side_item} - yaw: 270 + rotation: 270 east=true,north=false,south=true,waterlogged=false,west=false: state: ${base_block}[east=true,north=false,south=true,waterlogged=false,west=false] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 0 + rotation: 0 - item: ${fence_side_item} - yaw: 270 + rotation: 270 east=true,north=false,south=false,waterlogged=false,west=true: state: ${base_block}[east=true,north=false,south=false,waterlogged=false,west=true] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 90 + rotation: 90 - item: ${fence_side_item} - yaw: 270 + rotation: 270 east=false,north=true,south=true,waterlogged=false,west=false: state: ${base_block}[east=false,north=true,south=true,waterlogged=false,west=false] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 0 + rotation: 0 - item: ${fence_side_item} - yaw: 180 + rotation: 180 east=false,north=true,south=false,waterlogged=false,west=true: state: ${base_block}[east=false,north=true,south=false,waterlogged=false,west=true] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 90 + rotation: 90 - item: ${fence_side_item} - yaw: 180 + rotation: 180 east=false,north=false,south=true,waterlogged=false,west=true: state: ${base_block}[east=false,north=false,south=true,waterlogged=false,west=true] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 0 + rotation: 0 - item: ${fence_side_item} - yaw: 90 + rotation: 90 east=true,north=true,south=true,waterlogged=false,west=false: state: ${base_block}[east=true,north=true,south=true,waterlogged=false,west=false] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 0 + rotation: 0 - item: ${fence_side_item} - yaw: 180 + rotation: 180 - item: ${fence_side_item} - yaw: 270 + rotation: 270 east=true,north=true,south=false,waterlogged=false,west=true: state: ${base_block}[east=true,north=true,south=false,waterlogged=false,west=true] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 90 + rotation: 90 - item: ${fence_side_item} - yaw: 180 + rotation: 180 - item: ${fence_side_item} - yaw: 270 + rotation: 270 east=true,north=false,south=true,waterlogged=false,west=true: state: ${base_block}[east=true,north=false,south=true,waterlogged=false,west=true] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 0 + rotation: 0 - item: ${fence_side_item} - yaw: 90 + rotation: 90 - item: ${fence_side_item} - yaw: 270 + rotation: 270 east=false,north=true,south=true,waterlogged=false,west=true: state: ${base_block}[east=false,north=true,south=true,waterlogged=false,west=true] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 0 + rotation: 0 - item: ${fence_side_item} - yaw: 90 + rotation: 90 - item: ${fence_side_item} - yaw: 180 + rotation: 180 east=true,north=true,south=true,waterlogged=false,west=true: state: ${base_block}[east=true,north=true,south=true,waterlogged=false,west=true] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 0 + rotation: 0 - item: ${fence_side_item} - yaw: 90 + rotation: 90 - item: ${fence_side_item} - yaw: 180 + rotation: 180 - item: ${fence_side_item} - yaw: 270 + rotation: 270 east=false,north=false,south=false,waterlogged=true,west=false: state: ${base_block}[east=false,north=false,south=false,waterlogged=true,west=false] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 east=true,north=false,south=false,waterlogged=true,west=false: state: ${base_block}[east=true,north=false,south=false,waterlogged=true,west=false] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 270 + rotation: 270 east=false,north=true,south=false,waterlogged=true,west=false: state: ${base_block}[east=false,north=true,south=false,waterlogged=true,west=false] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 180 + rotation: 180 east=false,north=false,south=true,waterlogged=true,west=false: state: ${base_block}[east=false,north=false,south=true,waterlogged=true,west=false] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 0 + rotation: 0 east=false,north=false,south=false,waterlogged=true,west=true: state: ${base_block}[east=false,north=false,south=false,waterlogged=true,west=true] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 90 + rotation: 90 east=true,north=true,south=false,waterlogged=true,west=false: state: ${base_block}[east=true,north=true,south=false,waterlogged=true,west=false] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 180 + rotation: 180 - item: ${fence_side_item} - yaw: 270 + rotation: 270 east=true,north=false,south=true,waterlogged=true,west=false: state: ${base_block}[east=true,north=false,south=true,waterlogged=true,west=false] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 0 + rotation: 0 - item: ${fence_side_item} - yaw: 270 + rotation: 270 east=true,north=false,south=false,waterlogged=true,west=true: state: ${base_block}[east=true,north=false,south=false,waterlogged=true,west=true] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 90 + rotation: 90 - item: ${fence_side_item} - yaw: 270 + rotation: 270 east=false,north=true,south=true,waterlogged=true,west=false: state: ${base_block}[east=false,north=true,south=true,waterlogged=true,west=false] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 0 + rotation: 0 - item: ${fence_side_item} - yaw: 180 + rotation: 180 east=false,north=true,south=false,waterlogged=true,west=true: state: ${base_block}[east=false,north=true,south=false,waterlogged=true,west=true] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 90 + rotation: 90 - item: ${fence_side_item} - yaw: 180 + rotation: 180 east=false,north=false,south=true,waterlogged=true,west=true: state: ${base_block}[east=false,north=false,south=true,waterlogged=true,west=true] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 0 + rotation: 0 - item: ${fence_side_item} - yaw: 90 + rotation: 90 east=true,north=true,south=true,waterlogged=true,west=false: state: ${base_block}[east=true,north=true,south=true,waterlogged=true,west=false] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 0 + rotation: 0 - item: ${fence_side_item} - yaw: 180 + rotation: 180 - item: ${fence_side_item} - yaw: 270 + rotation: 270 east=true,north=true,south=false,waterlogged=true,west=true: state: ${base_block}[east=true,north=true,south=false,waterlogged=true,west=true] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 90 + rotation: 90 - item: ${fence_side_item} - yaw: 180 + rotation: 180 - item: ${fence_side_item} - yaw: 270 + rotation: 270 east=true,north=false,south=true,waterlogged=true,west=true: state: ${base_block}[east=true,north=false,south=true,waterlogged=true,west=true] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 0 + rotation: 0 - item: ${fence_side_item} - yaw: 90 + rotation: 90 - item: ${fence_side_item} - yaw: 270 + rotation: 270 east=false,north=true,south=true,waterlogged=true,west=true: state: ${base_block}[east=false,north=true,south=true,waterlogged=true,west=true] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 0 + rotation: 0 - item: ${fence_side_item} - yaw: 90 + rotation: 90 - item: ${fence_side_item} - yaw: 180 + rotation: 180 east=true,north=true,south=true,waterlogged=true,west=true: state: ${base_block}[east=true,north=true,south=true,waterlogged=true,west=true] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 0 + rotation: 0 - item: ${fence_side_item} - yaw: 90 + rotation: 90 - item: ${fence_side_item} - yaw: 180 + rotation: 180 - item: ${fence_side_item} - yaw: 270 + rotation: 270 variants: waterlogged=true: settings: diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfig.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfig.java index 0d3c320ad..99346e5bc 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfig.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfig.java @@ -11,5 +11,9 @@ public interface BlockEntityElementConfig { return null; } + default E createExact(World world, BlockPos pos, E previous) { + return null; + } + Class elementClass(); } 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 6826bbc12..847fe6d1c 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 @@ -207,7 +207,7 @@ public abstract class Player extends AbstractEntity implements NetWorkUser { public abstract void addTrackedBlockEntities(Map renders); - public abstract VirtualCullableObject addTrackedBlockEntity(BlockPos blockPos, ConstantBlockEntityRenderer renderer); + public abstract void addTrackedBlockEntity(BlockPos blockPos, ConstantBlockEntityRenderer renderer); public abstract VirtualCullableObject getTrackedBlockEntity(BlockPos blockPos); 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 67695fac8..50eda3140 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 @@ -153,6 +153,12 @@ public class CEChunk { if (previous != null) { // 由于entity-render的体量基本都很小,所以考虑一个特殊情况,即前后都是1个renderer,对此情况进行简化和优化 BlockEntityElement[] previousElements = previous.elements().clone(); + + /* + * + * 1 对 1,命中率最高 + * + */ if (previousElements.length == 1 && renderers.length == 1) { BlockEntityElement previousElement = previousElements[0]; BlockEntityElementConfig config = renderers[0]; @@ -162,18 +168,19 @@ public class CEChunk { if (element != null) { elements[0] = element; if (hasTrackedBy) { - // 如果启用实体剔除,那么只对已经渲染的进行变换 - if (Config.enableEntityCulling()) { - for (Player player : trackedBy) { - VirtualCullableObject trackedBlockEntity = player.addTrackedBlockEntity(pos, renderer); - if (trackedBlockEntity == null || trackedBlockEntity.isShown()) { + for (Player player : trackedBy) { + // 如果启用剔除,则暂时保留原先可见度,因为大概率可见度不发生变化 + if (Config.enableEntityCulling()) { + VirtualCullableObject trackedBlockEntity = player.getTrackedBlockEntity(pos); + if (trackedBlockEntity == null || trackedBlockEntity.isShown) { element.transform(player); } - } - } - // 否则直接变换 - else { - for (Player player : trackedBy) { + if (trackedBlockEntity != null) { + trackedBlockEntity.setCullable(renderer); + } else { + player.addTrackedBlockEntity(pos, renderer); + } + } else { element.transform(player); } } @@ -184,47 +191,56 @@ public class CEChunk { BlockEntityElement element = config.create(wrappedWorld, pos); elements[0] = element; if (hasTrackedBy) { - // 如果启用实体剔除,那么只添加记录 - if (Config.enableEntityCulling()) { - for (Player player : trackedBy) { - VirtualCullableObject trackedBlockEntity = player.addTrackedBlockEntity(pos, renderer); - if (trackedBlockEntity != null && trackedBlockEntity.isShown()) { - trackedBlockEntity.setShown(player, false); + for (Player player : trackedBy) { + if (Config.enableEntityCulling()) { + VirtualCullableObject trackedBlockEntity = player.getTrackedBlockEntity(pos); + if (trackedBlockEntity != null) { + if (trackedBlockEntity.isShown) { + trackedBlockEntity.setShown(player, false); + } + trackedBlockEntity.setCullable(renderer); + } else { + player.addTrackedBlockEntity(pos, renderer); } - } - } - // 否则直接显示 - else { - for (Player player : trackedBy) { + } else { previousElement.hide(player); element.show(player); } } } } - } else { + } + /* + * + * 1 对 多, 多 对 多 + * + */ + else { + + VirtualCullableObject[] previousObjects = hasTrackedBy ? new VirtualCullableObject[trackedBy.size()] : null; + if (hasTrackedBy) { + for (int j = 0; j < previousObjects.length; j++) { + previousObjects[j] = trackedBy.get(j).getTrackedBlockEntity(pos); + } + } + outer: for (int i = 0; i < elements.length; i++) { BlockEntityElementConfig config = renderers[i]; + /* + * 严格可变换部分 + */ for (int j = 0; j < previousElements.length; j++) { BlockEntityElement previousElement = previousElements[j]; if (previousElement != null && config.elementClass().isInstance(previousElement)) { - BlockEntityElement newElement = ((BlockEntityElementConfig) config).create(wrappedWorld, pos, previousElement); + BlockEntityElement newElement = ((BlockEntityElementConfig) config).createExact(wrappedWorld, pos, previousElement); if (newElement != null) { previousElements[j] = null; elements[i] = newElement; if (hasTrackedBy) { - // 如果启用实体剔除,那么只对已经渲染的进行变换 - if (Config.enableEntityCulling()) { - for (Player player : trackedBy) { - VirtualCullableObject trackedBlockEntity = player.addTrackedBlockEntity(pos, renderer); - if (trackedBlockEntity == null || trackedBlockEntity.isShown()) { - newElement.transform(player); - } - } - } - // 否则直接变换 - else { - for (Player player : trackedBy) { + for (int k = 0; k < trackedBy.size(); k++) { + Player player = trackedBy.get(k); + VirtualCullableObject cullableObject = previousObjects[k]; + if (cullableObject == null || cullableObject.isShown) { newElement.transform(player); } } @@ -233,21 +249,48 @@ public class CEChunk { } } } + /* + * 可变换部分 + */ + for (int j = 0; j < previousElements.length; j++) { + BlockEntityElement previousElement = previousElements[j]; + if (previousElement != null && config.elementClass().isInstance(previousElement)) { + BlockEntityElement newElement = ((BlockEntityElementConfig) config).create(wrappedWorld, pos, previousElement); + if (newElement != null) { + previousElements[j] = null; + elements[i] = newElement; + if (hasTrackedBy) { + for (int k = 0; k < trackedBy.size(); k++) { + Player player = trackedBy.get(k); + VirtualCullableObject cullableObject = previousObjects[k]; + if (cullableObject == null || cullableObject.isShown) { + newElement.transform(player); + } + } + } + continue outer; + } + } + } + /* + * 不可变换的直接生成 + */ BlockEntityElement newElement = config.create(wrappedWorld, pos); elements[i] = newElement; if (hasTrackedBy) { - if (Config.enableEntityCulling()) { - for (Player player : trackedBy) { - player.addTrackedBlockEntity(pos, renderer); - } - } else { - for (Player player : trackedBy) { + for (int k = 0; k < trackedBy.size(); k++) { + Player player = trackedBy.get(k); + VirtualCullableObject cullableObject = previousObjects[k]; + if (cullableObject == null || cullableObject.isShown) { newElement.show(player); } } } } if (hasTrackedBy) { + /* + * 未能完成变化的,需要直接删除 + */ for (int i = 0; i < previousElements.length; i++) { BlockEntityElement previousElement = previousElements[i]; if (previousElement != null) { @@ -256,19 +299,31 @@ public class CEChunk { } } } + // 添加 track + for (int i = 0; i < previousObjects.length; i++) { + VirtualCullableObject previousObject = previousObjects[i]; + if (previousObject != null) { + previousObject.setCullable(renderer); + } else { + trackedBy.get(i).addTrackedBlockEntity(pos, renderer); + } + } } } } else { + /* + * + * 全新方块实体 + * + */ for (int i = 0; i < elements.length; i++) { elements[i] = renderers[i].create(wrappedWorld, pos); } if (hasTrackedBy) { - if (Config.enableEntityCulling()) { - for (Player player : trackedBy) { + for (Player player : trackedBy) { + if (Config.enableEntityCulling()) { player.addTrackedBlockEntity(pos, renderer); - } - } else { - for (Player player : trackedBy) { + } else { renderer.show(player); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/VirtualCullableObject.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/VirtualCullableObject.java index 44af9f41b..fef5e547d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/VirtualCullableObject.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/VirtualCullableObject.java @@ -4,7 +4,7 @@ import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.world.Cullable; public class VirtualCullableObject { - public final Cullable cullable; + public Cullable cullable; public boolean isShown; public VirtualCullableObject(Cullable cullable) { @@ -12,6 +12,10 @@ public class VirtualCullableObject { this.isShown = false; } + public void setCullable(Cullable cullable) { + this.cullable = cullable; + } + public Cullable cullable() { return cullable; } diff --git a/gradle.properties b/gradle.properties index 3691ff1e9..46db05f12 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ org.gradle.jvmargs=-Xmx1G # Project settings -project_version=0.0.65.14 +project_version=0.0.65.15 config_version=60 lang_version=41 project_group=net.momirealms