From c59478e891745d246829cd55481ad46aa9326a5c Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sat, 29 Nov 2025 01:46:31 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=AE=9E=E4=BD=93=E5=89=94?= =?UTF-8?q?=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); }