9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-22 16:39:28 +00:00

实体剔除前置

This commit is contained in:
XiaoMoMi
2025-11-28 02:57:59 +08:00
parent 9bce13475f
commit bced7bfda6
34 changed files with 470 additions and 100 deletions

View File

@@ -88,7 +88,7 @@ public final class BukkitBlockManager extends AbstractBlockManager {
this.markVanillaNoteBlocks();
this.findViewBlockingVanillaBlocks();
Arrays.fill(this.immutableBlockStates, EmptyBlock.INSTANCE.defaultState());
this.plugin.networkManager().registerBlockStatePacketListeners(this.blockStateMappings); // 一定要预先初始化一次预防id超出上限
this.plugin.networkManager().registerBlockStatePacketListeners(this.blockStateMappings, this::isViewBlockingBlock); // 一定要预先初始化一次预防id超出上限
}
public static BukkitBlockManager instance() {
@@ -128,7 +128,7 @@ public final class BukkitBlockManager extends AbstractBlockManager {
@Override
public void delayedLoad() {
this.plugin.networkManager().registerBlockStatePacketListeners(this.blockStateMappings); // 重置方块映射表
this.plugin.networkManager().registerBlockStatePacketListeners(this.blockStateMappings, this::isViewBlockingBlock); // 重置方块映射表
super.delayedLoad();
this.cachedVisualBlockStatePacket = VisualBlockStatePacket.create();
for (BukkitServerPlayer player : BukkitNetworkManager.instance().onlineUsers()) {

View File

@@ -22,6 +22,7 @@ import net.kyori.adventure.nbt.api.BinaryTagHolder;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.DataComponentValue;
import net.kyori.adventure.text.event.HoverEvent;
import net.momirealms.craftengine.bukkit.api.BukkitAdaptors;
import net.momirealms.craftengine.bukkit.api.CraftEngineBlocks;
import net.momirealms.craftengine.bukkit.api.CraftEngineFurniture;
import net.momirealms.craftengine.bukkit.api.event.FurnitureAttemptBreakEvent;
@@ -90,9 +91,12 @@ import net.momirealms.craftengine.core.plugin.text.component.ComponentProvider;
import net.momirealms.craftengine.core.util.*;
import net.momirealms.craftengine.core.world.*;
import net.momirealms.craftengine.core.world.chunk.CEChunk;
import net.momirealms.craftengine.core.world.chunk.ChunkStatus;
import net.momirealms.craftengine.core.world.chunk.Palette;
import net.momirealms.craftengine.core.world.chunk.PalettedContainer;
import net.momirealms.craftengine.core.world.chunk.client.ClientChunk;
import net.momirealms.craftengine.core.world.chunk.client.ClientSection;
import net.momirealms.craftengine.core.world.chunk.client.PackedOcclusionStorage;
import net.momirealms.craftengine.core.world.chunk.client.SingularOcclusionStorage;
import net.momirealms.craftengine.core.world.chunk.packet.BlockEntityData;
import net.momirealms.craftengine.core.world.chunk.packet.MCSection;
import net.momirealms.sparrow.nbt.CompoundTag;
@@ -125,6 +129,7 @@ import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiFunction;
import java.util.function.Predicate;
public class BukkitNetworkManager implements NetworkManager, Listener, PluginMessageListener {
private static BukkitNetworkManager instance;
@@ -288,7 +293,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
}
}
public void registerBlockStatePacketListeners(int[] blockStateMappings) {
public void registerBlockStatePacketListeners(int[] blockStateMappings, Predicate<Integer> occlusionPredicate) {
int stoneId = BlockStateUtils.blockStateToId(MBlocks.STONE$defaultState);
int vanillaBlocks = BlockStateUtils.vanillaBlockStateCount();
int[] newMappings = new int[blockStateMappings.length];
@@ -318,7 +323,8 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
newMappings,
newMappingsMOD,
newMappings.length,
RegistryUtils.currentBiomeRegistrySize()
RegistryUtils.currentBiomeRegistrySize(),
occlusionPredicate
), this.packetIds.clientboundLevelChunkWithLightPacket(), "ClientboundLevelChunkWithLightPacket");
registerS2CGamePacketListener(new SectionBlockUpdateListener(newMappings, newMappingsMOD), this.packetIds.clientboundSectionBlocksUpdatePacket(), "ClientboundSectionBlocksUpdatePacket");
registerS2CGamePacketListener(new BlockUpdateListener(newMappings, newMappingsMOD), this.packetIds.clientboundBlockUpdatePacket(), "ClientboundBlockUpdatePacket");
@@ -1433,9 +1439,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
Object location = FastNMS.INSTANCE.field$ResourceKey$location(dimensionKey);
World world = Bukkit.getWorld(Objects.requireNonNull(NamespacedKey.fromString(location.toString())));
if (world != null) {
int sectionCount = (world.getMaxHeight() - world.getMinHeight()) / 16;
player.setClientSideSectionCount(sectionCount);
player.setClientSideDimension(Key.of(location.toString()));
player.setClientSideWorld(BukkitAdaptors.adapt(world));
} else {
CraftEngine.instance().logger().warn("Failed to handle ClientboundLoginPacket: World " + location + " does not exist");
}
@@ -1463,10 +1467,9 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
Object location = FastNMS.INSTANCE.field$ResourceKey$location(dimensionKey);
World world = Bukkit.getWorld(Objects.requireNonNull(NamespacedKey.fromString(location.toString())));
if (world != null) {
int sectionCount = (world.getMaxHeight() - world.getMinHeight()) / 16;
player.setClientSideSectionCount(sectionCount);
player.setClientSideDimension(Key.of(location.toString()));
player.setClientSideWorld(BukkitAdaptors.adapt(world));
player.clearTrackedChunks();
player.clearTrackedBlockEntities();
} else {
CraftEngine.instance().logger().warn("Failed to handle ClientboundRespawnPacket: World " + location + " does not exist");
}
@@ -1979,13 +1982,15 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
private final IntIdentityList biomeList;
private final IntIdentityList blockList;
private final boolean needsDowngrade;
private final Predicate<Integer> occlusionPredicate;
public LevelChunkWithLightListener(int[] blockStateMapper, int[] modBlockStateMapper, int blockRegistrySize, int biomeRegistrySize) {
public LevelChunkWithLightListener(int[] blockStateMapper, int[] modBlockStateMapper, int blockRegistrySize, int biomeRegistrySize, Predicate<Integer> occlusionPredicate) {
this.blockStateMapper = blockStateMapper;
this.modBlockStateMapper = modBlockStateMapper;
this.biomeList = new IntIdentityList(biomeRegistrySize);
this.blockList = new IntIdentityList(blockRegistrySize);
this.needsDowngrade = MiscUtils.ceilLog2(BlockStateUtils.vanillaBlockStateCount()) != MiscUtils.ceilLog2(blockRegistrySize);
this.occlusionPredicate = occlusionPredicate;
}
public int remapBlockState(int stateId, boolean enableMod) {
@@ -2022,36 +2027,92 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
buf.readBytes(chunkDataBytes);
// 客户端侧section数量很重要不能读取此时玩家所在的真实世界包具有滞后性
int count = player.clientSideSectionCount();
net.momirealms.craftengine.core.world.World clientSideWorld = player.clientSideWorld();
WorldHeight worldHeight = clientSideWorld.worldHeight();
int count = worldHeight.getSectionsCount();
MCSection[] sections = new MCSection[count];
FriendlyByteBuf chunkDataByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(chunkDataBytes));
boolean hasChangedAnyBlock = false;
boolean hasGlobalPalette = false;
// 创建客户端侧世界(只在开启实体情况下创建)
ClientSection[] clientSections = Config.enableEntityCulling() ? new ClientSection[count] : null;
for (int i = 0; i < count; i++) {
MCSection mcSection = new MCSection(user.clientBlockList(), this.blockList, this.biomeList);
mcSection.readPacket(chunkDataByteBuf);
PalettedContainer<Integer> container = mcSection.blockStateContainer();
// 重定向生物群系
if (remapBiomes(user, mcSection.biomeContainer())) {
hasChangedAnyBlock = true;
}
Palette<Integer> palette = container.data().palette();
if (palette.canRemap()) {
// 重定向方块
if (palette.remapAndCheck(s -> remapBlockState(s, user.clientModEnabled()))) {
hasChangedAnyBlock = true;
}
// 处理客户端侧哪些方块有阻挡
if (clientSections != null) {
int size = palette.getSize();
// 单个元素的情况下,使用优化的存储方案
if (size == 1) {
clientSections[i] = new ClientSection(new SingularOcclusionStorage(this.occlusionPredicate.test(palette.get(0))));
} else {
boolean hasOcclusions = false;
boolean hasNoOcclusions = false;
for (int h = 0; h < size; h++) {
int entry = palette.get(h);
if (this.occlusionPredicate.test(entry)) {
hasOcclusions = true;
} else {
hasNoOcclusions = true;
}
}
// 两种情况都有,那么需要一个个遍历处理视线遮挡数据
if (hasOcclusions && hasNoOcclusions) {
PackedOcclusionStorage storage = new PackedOcclusionStorage(false);
for (int j = 0; j < 4096; j++) {
int state = container.get(j);
storage.set(j, this.occlusionPredicate.test(state));
}
}
// 全遮蔽或全透视则使用优化存储方案
else {
clientSections[i] = new ClientSection(new SingularOcclusionStorage(hasOcclusions));
}
}
}
} else {
hasGlobalPalette = true;
PackedOcclusionStorage storage = null;
if (clientSections != null) {
storage = new PackedOcclusionStorage(false);
}
for (int j = 0; j < 4096; j++) {
int state = container.get(j);
// 重定向方块
int newState = remapBlockState(state, user.clientModEnabled());
if (newState != state) {
container.set(j, newState);
hasChangedAnyBlock = true;
}
// 写入视线遮挡数据
if (storage != null) {
storage.set(j, this.occlusionPredicate.test(state));
}
}
}
sections[i] = mcSection;
}
@@ -2116,13 +2177,17 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
}
// 记录加载的区块
player.addTrackedChunk(chunkPos.longKey, new ChunkStatus());
player.addTrackedChunk(chunkPos.longKey, new ClientChunk(clientSections, worldHeight));
// 生成方块实体
CEWorld ceWorld = BukkitWorldManager.instance().getWorld(player.world().uuid());
CEChunk ceChunk = ceWorld.getChunkAtIfLoaded(chunkPos.longKey);
if (ceChunk != null) {
ceChunk.spawnBlockEntities(player);
CEWorld ceWorld = clientSideWorld.storageWorld();
// 世界可能被卸载,因为包滞后
if (ceWorld != null) {
CEChunk ceChunk = ceWorld.getChunkAtIfLoaded(chunkPos.longKey);
if (ceChunk != null) {
// 生成方块实体
ceChunk.spawnBlockEntities(player);
}
}
}
}

View File

@@ -23,6 +23,7 @@ import net.momirealms.craftengine.core.advancement.AdvancementType;
import net.momirealms.craftengine.core.block.BlockStateWrapper;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.entity.BlockEntity;
import net.momirealms.craftengine.core.block.entity.render.ConstantBlockEntityRenderer;
import net.momirealms.craftengine.core.entity.data.EntityData;
import net.momirealms.craftengine.core.entity.player.GameMode;
import net.momirealms.craftengine.core.entity.player.InteractionHand;
@@ -39,7 +40,8 @@ import net.momirealms.craftengine.core.sound.SoundSource;
import net.momirealms.craftengine.core.util.*;
import net.momirealms.craftengine.core.world.*;
import net.momirealms.craftengine.core.world.World;
import net.momirealms.craftengine.core.world.chunk.ChunkStatus;
import net.momirealms.craftengine.core.world.chunk.client.ClientChunk;
import net.momirealms.craftengine.core.world.chunk.client.VirtualCullableObject;
import net.momirealms.craftengine.core.world.collision.AABB;
import org.bukkit.*;
import org.bukkit.attribute.Attribute;
@@ -85,8 +87,7 @@ public class BukkitServerPlayer extends Player {
private Reference<org.bukkit.entity.Player> playerRef;
private Reference<Object> serverPlayerRef;
// client side dimension info
private int sectionCount;
private Key clientSideDimension;
private World clientSideWorld;
// check main hand/offhand interaction
private int lastSuccessfulInteraction;
// to prevent duplicated events
@@ -124,7 +125,7 @@ public class BukkitServerPlayer extends Player {
// cooldown data
private CooldownData cooldownData;
// tracked chunks
private ConcurrentLong2ReferenceChainedHashTable<ChunkStatus> trackedChunks;
private ConcurrentLong2ReferenceChainedHashTable<ClientChunk> trackedChunks;
// entity view
private Map<Integer, EntityPacketHandler> entityTypeView;
// 通过指令或api设定的语言
@@ -138,6 +139,8 @@ public class BukkitServerPlayer extends Player {
private boolean isHackedBreak;
// 上一次停止挖掘包发出的时间
private int lastStopMiningTick;
// 跟踪到的方块实体渲染器
private final Map<BlockPos, VirtualCullableObject> trackedBlockEntityRenderers = new ConcurrentHashMap<>();
public BukkitServerPlayer(BukkitCraftEngine plugin, @Nullable Channel channel) {
this.channel = channel;
@@ -477,21 +480,13 @@ public class BukkitServerPlayer extends Player {
}
@Override
public int clientSideSectionCount() {
return sectionCount;
}
public void setClientSideSectionCount(int sectionCount) {
this.sectionCount = sectionCount;
public World clientSideWorld() {
return this.clientSideWorld;
}
@Override
public Key clientSideDimension() {
return clientSideDimension;
}
public void setClientSideDimension(Key clientSideDimension) {
this.clientSideDimension = clientSideDimension;
public void setClientSideWorld(World world) {
this.clientSideWorld = world;
}
public void setConnectionState(ConnectionState connectionState) {
@@ -1180,12 +1175,12 @@ public class BukkitServerPlayer extends Player {
}
@Override
public ChunkStatus getTrackedChunk(long chunkPos) {
public ClientChunk getTrackedChunk(long chunkPos) {
return this.trackedChunks.get(chunkPos);
}
@Override
public void addTrackedChunk(long chunkPos, ChunkStatus chunkStatus) {
public void addTrackedChunk(long chunkPos, ClientChunk chunkStatus) {
this.trackedChunks.put(chunkPos, chunkStatus);
}
@@ -1300,4 +1295,26 @@ public class BukkitServerPlayer extends Player {
public void sendTotemAnimation(Item<?> totem, @Nullable SoundData sound, boolean silent) {
PlayerUtils.sendTotemAnimation(this, totem, sound, silent);
}
@Override
public void addTrackedBlockEntities(Map<BlockPos, ConstantBlockEntityRenderer> renders) {
for (Map.Entry<BlockPos, ConstantBlockEntityRenderer> entry : renders.entrySet()) {
this.trackedBlockEntityRenderers.put(entry.getKey(), new VirtualCullableObject(entry.getValue()));
}
}
@Override
public void removeTrackedBlockEntities(Collection<BlockPos> renders) {
for (BlockPos render : renders) {
VirtualCullableObject remove = this.trackedBlockEntityRenderers.remove(render);
if (remove != null && remove.isShown()) {
remove.cullable().hide(this);
}
}
}
@Override
public void clearTrackedBlockEntities() {
this.trackedBlockEntityRenderers.clear();
}
}

View File

@@ -24,7 +24,10 @@ import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
import java.lang.ref.WeakReference;
import java.nio.file.Path;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
public class BukkitWorld implements World {
private final WeakReference<org.bukkit.World> world;