From df86e822dbededc648e5c6c44a4c6ec1898f6cc5 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Sun, 18 May 2025 00:18:22 +0800 Subject: [PATCH] =?UTF-8?q?feat(bukkit):=20=E6=B7=BB=E5=8A=A0=E9=99=90?= =?UTF-8?q?=E5=88=B6=E5=8F=AF=E8=A7=86=E5=AE=B6=E5=85=B7=E6=95=B0=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin/command/feature/TestCommand.java | 32 ++-- .../plugin/network/PacketConsumers.java | 27 ++- .../plugin/user/BukkitServerPlayer.java | 29 ++++ .../core/plugin/network/NetWorkUser.java | 3 + .../core/util/DynamicPriorityTracker.java | 161 ++++++++++++++++++ 5 files changed, 238 insertions(+), 14 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/util/DynamicPriorityTracker.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/TestCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/TestCommand.java index ab1b59966..33a9ac417 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/TestCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/TestCommand.java @@ -1,6 +1,9 @@ package net.momirealms.craftengine.bukkit.plugin.command.feature; +import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurnitureManager; +import net.momirealms.craftengine.bukkit.entity.furniture.LoadedFurniture; 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 org.bukkit.Material; @@ -38,19 +41,26 @@ public class TestCommand extends BukkitCommandFeature { public Command.Builder assembleCommand(org.incendo.cloud.CommandManager manager, Command.Builder builder) { return builder .senderType(Player.class) - .required("reset", BooleanParser.booleanParser()) - .required("setTag", NamespacedKeyParser.namespacedKeyParser()) - .required("targetBlock", StringParser.stringComponent(StringParser.StringMode.GREEDY_FLAG_YIELDING).suggestionProvider(new SuggestionProvider<>() { - @Override - public @NonNull CompletableFuture> suggestionsFuture(@NonNull CommandContext context, @NonNull CommandInput input) { - return CompletableFuture.completedFuture(TARGET_BLOCK_SUGGESTIONS); - } - })) + // .required("reset", BooleanParser.booleanParser()) + // .required("setTag", NamespacedKeyParser.namespacedKeyParser()) + // .required("targetBlock", StringParser.stringComponent(StringParser.StringMode.GREEDY_FLAG_YIELDING).suggestionProvider(new SuggestionProvider<>() { + // @Override + // public @NonNull CompletableFuture> suggestionsFuture(@NonNull CommandContext context, @NonNull CommandInput input) { + // return CompletableFuture.completedFuture(TARGET_BLOCK_SUGGESTIONS); + // } + // })) .handler(context -> { Player player = context.sender(); - player.sendMessage("开始测试"); - NamespacedKey key = context.get("setTag"); - player.sendMessage("结束测试"); + BukkitServerPlayer cePlayer = plugin().adapt(player); + player.sendMessage("visualFurnitureView1: " + cePlayer.visualFurnitureView().getTotalMembers()); + cePlayer.visualFurnitureView().getAllElements().forEach(element -> { + LoadedFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(element.entityId()); + if (furniture == null || !player.canSee(furniture.baseEntity())) { + cePlayer.visualFurnitureView().removeByEntityId(element.entityId()); + player.sendMessage("remove: " + element.entityId()); + } + }); + player.sendMessage("visualFurnitureView2: " + cePlayer.visualFurnitureView().getTotalMembers()); }); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java index abd76f731..37948856d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java @@ -1598,8 +1598,28 @@ public class PacketConsumers { // Furniture LoadedFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(entityId); if (furniture != null) { - user.entityPacketHandlers().computeIfAbsent(entityId, k -> new FurniturePacketHandler(furniture.fakeEntityIds())); - user.sendPacket(furniture.spawnPacket((Player) user.platformPlayer()), false); + Player player = (Player) user.platformPlayer(); + List fakeEntityIds = furniture.fakeEntityIds(); + user.entityPacketHandlers().computeIfAbsent(entityId, k -> new FurniturePacketHandler(fakeEntityIds)); + if (user.visualFurnitureView().getTotalMembers() <= 100) { + user.sendPacket(furniture.spawnPacket(player), false); + } + int[] entityIdsArray = new int[fakeEntityIds.size() + 1]; + entityIdsArray[0] = entityId; + for (int i = 0; i < fakeEntityIds.size(); i++) { + entityIdsArray[i + 1] = fakeEntityIds.get(i); + } + double distance = player.getLocation().distance(furniture.location()); + Object removePacket = Reflections.constructor$ClientboundRemoveEntitiesPacket.newInstance(entityIdsArray); + DynamicPriorityTracker.UpdateResult result = user.visualFurnitureView().addOrUpdateElement(new DynamicPriorityTracker.Element(entityId, distance, removePacket)); + for (DynamicPriorityTracker.Element element : result.getEntered()) { + LoadedFurniture updateFurniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(element.entityId()); + if (updateFurniture == null || !updateFurniture.isValid()) continue; + user.sendPacket(updateFurniture.spawnPacket(player), false); + } + for (DynamicPriorityTracker.Element element : result.getExited()) { + user.sendPacket(element.removePacket(), false); + } if (Config.hideBaseEntity() && !furniture.hasExternalModel()) { event.setCancelled(true); } @@ -1645,6 +1665,7 @@ public class PacketConsumers { int entityId = intList.getInt(i); EntityPacketHandler handler = user.entityPacketHandlers().remove(entityId); if (handler != null && handler.handleEntitiesRemove(intList)) { + // user.visualFurnitureView().removeByEntityId(entityId); isChange = true; } } @@ -1726,7 +1747,7 @@ public class PacketConsumers { .withParameter(DirectContextParameters.FURNITURE, furniture) .withParameter(DirectContextParameters.POSITION, new WorldPosition(furniture.world(), furniture.position())) ); - furniture.config().execute(context, EventTrigger.RIGHT_CLICK);; + furniture.config().execute(context, EventTrigger.RIGHT_CLICK); if (player.isSneaking()) { // try placing another furniture above it 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 5e045570f..97d23c5e2 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 @@ -4,6 +4,8 @@ import com.google.common.collect.Lists; import io.netty.channel.Channel; import net.kyori.adventure.text.Component; import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; +import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurnitureManager; +import net.momirealms.craftengine.bukkit.entity.furniture.LoadedFurniture; import net.momirealms.craftengine.bukkit.item.BukkitItemManager; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; @@ -23,6 +25,7 @@ import net.momirealms.craftengine.core.plugin.network.ConnectionState; import net.momirealms.craftengine.core.plugin.network.EntityPacketHandler; import net.momirealms.craftengine.core.plugin.network.ProtocolVersion; import net.momirealms.craftengine.core.util.Direction; +import net.momirealms.craftengine.core.util.DynamicPriorityTracker; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.world.BlockPos; @@ -97,6 +100,7 @@ public class BukkitServerPlayer extends Player { private double cachedInteractionRange; private final Map entityTypeView = new ConcurrentHashMap<>(); + private final DynamicPriorityTracker visualFurnitureView = new DynamicPriorityTracker(100); public BukkitServerPlayer(BukkitCraftEngine plugin, Channel channel) { this.channel = channel; @@ -344,6 +348,7 @@ public class BukkitServerPlayer extends Player { } if (this.gameTicks % 30 == 0) { this.updateGUI(); + this.updateVisualFurnitureView(); } if (this.isDestroyingBlock) { this.tickBlockDestroy(); @@ -368,6 +373,25 @@ public class BukkitServerPlayer extends Player { } } + private void updateVisualFurnitureView() { + if (visualFurnitureView().getTotalMembers() <= 100) return; + for (DynamicPriorityTracker.Element element : visualFurnitureView().getAllElements()) { + LoadedFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(element.entityId()); + if (furniture == null || !furniture.isValid()) continue; + double distance = platformPlayer().getLocation().distance(furniture.location()); + DynamicPriorityTracker.Element newElement = new DynamicPriorityTracker.Element(element.entityId(), distance, element.removePacket()); + DynamicPriorityTracker.UpdateResult result = visualFurnitureView().addOrUpdateElement(newElement); + for (DynamicPriorityTracker.Element resultElement : result.getEntered()) { + LoadedFurniture updateFurniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(resultElement.entityId()); + if (updateFurniture == null || !updateFurniture.isValid()) continue; + sendPacket(updateFurniture.spawnPacket(platformPlayer()), false); + } + for (DynamicPriorityTracker.Element resultElement : result.getExited()) { + sendPacket(resultElement.removePacket(), false); + } + } + } + @Override public float getDestroyProgress(Object blockState, BlockPos pos) { return FastNMS.INSTANCE.method$BlockStateBase$getDestroyProgress(blockState, serverPlayer(), FastNMS.INSTANCE.field$CraftWorld$ServerLevel(platformPlayer().getWorld()), LocationUtils.toBlockPos(pos)); @@ -738,6 +762,11 @@ public class BukkitServerPlayer extends Player { return this.entityTypeView; } + @Override + public DynamicPriorityTracker visualFurnitureView() { + return this.visualFurnitureView; + } + public void setResendSound() { resentSoundTick = gameTicks(); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java index cf9442fee..d442edb97 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java @@ -2,6 +2,7 @@ package net.momirealms.craftengine.core.plugin.network; import io.netty.channel.Channel; import net.momirealms.craftengine.core.plugin.Plugin; +import net.momirealms.craftengine.core.util.DynamicPriorityTracker; import net.momirealms.craftengine.core.util.Key; import org.jetbrains.annotations.ApiStatus; @@ -43,6 +44,8 @@ public interface NetWorkUser { Map entityPacketHandlers(); + DynamicPriorityTracker visualFurnitureView(); + boolean clientModEnabled(); void setClientModState(boolean enable); diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/DynamicPriorityTracker.java b/core/src/main/java/net/momirealms/craftengine/core/util/DynamicPriorityTracker.java new file mode 100644 index 000000000..02e7f7a67 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/DynamicPriorityTracker.java @@ -0,0 +1,161 @@ +package net.momirealms.craftengine.core.util; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantLock; + +public class DynamicPriorityTracker { + + public static class Element { + private final int entityId; + private volatile double distance; + private final Object removePacket; + + public Element(int entityId, double distance, Object removePacket) { + this.entityId = entityId; + this.distance = distance; + this.removePacket = removePacket; + } + + public int entityId() { + return entityId; + } + public double distance() { + return distance; + } + public void setDistance(double distance) { + this.distance = distance; + } + public Object removePacket() { + return removePacket; + } + } + + public static class UpdateResult { + private final List entered = new ArrayList<>(); + private final List exited = new ArrayList<>(); + + public List getEntered() { + return entered; + } + public List getExited() { + return exited; + } + + void addEntered(Element e) { + entered.add(e); + } + void addExited(Element e) { + exited.add(e); + } + } + + private final int capacity; + private final PriorityQueue maxHeap; + private final Map elementMap = new ConcurrentHashMap<>(); + private final Set inHeapSet = ConcurrentHashMap.newKeySet(); + private final ReentrantLock heapLock = new ReentrantLock(); + + public DynamicPriorityTracker(int capacity) { + this.capacity = capacity; + this.maxHeap = new PriorityQueue<>((a, b) -> Double.compare(b.distance, a.distance)); + } + + public UpdateResult addOrUpdateElement(Element newElement) { + UpdateResult result = new UpdateResult(); + heapLock.lock(); + try { + Element existing = elementMap.get(newElement.entityId); + + if (existing != null) { + return handleExistingElement(existing, newElement, result); + } else { + return handleNewElement(newElement, result); + } + } finally { + heapLock.unlock(); + } + } + + private UpdateResult handleNewElement(Element newElement, UpdateResult result) { + elementMap.put(newElement.entityId, newElement); + + if (maxHeap.size() < capacity) { + maxHeap.offer(newElement); + inHeapSet.add(newElement.entityId); + result.addEntered(newElement); + } else if (maxHeap.peek() != null && newElement.distance < maxHeap.peek().distance) { + Element removed = maxHeap.poll(); + inHeapSet.remove(removed.entityId); + result.addExited(removed); + + maxHeap.offer(newElement); + inHeapSet.add(newElement.entityId); + result.addEntered(newElement); + } + return result; + } + + private UpdateResult handleExistingElement(Element existing, Element newElement, UpdateResult result) { + existing.setDistance(newElement.distance); + + boolean wasInHeap = inHeapSet.contains(existing.entityId); + boolean nowInHeap = checkIfShouldBeInHeap(existing.distance); + + if (wasInHeap) { + maxHeap.remove(existing); + maxHeap.offer(existing); + } else if (nowInHeap) { + if (maxHeap.size() < capacity) { + maxHeap.offer(existing); + inHeapSet.add(existing.entityId); + result.addEntered(existing); + } else if (maxHeap.peek() != null && existing.distance < maxHeap.peek().distance) { + Element removed = maxHeap.poll(); + inHeapSet.remove(removed.entityId); + result.addExited(removed); + + maxHeap.offer(existing); + inHeapSet.add(existing.entityId); + result.addEntered(existing); + } + } + return result; + } + + private boolean checkIfShouldBeInHeap(double distance) { + if (maxHeap.size() < capacity) return true; + return maxHeap.peek() != null && distance < maxHeap.peek().distance; + } + + public int getTotalMembers() { + heapLock.lock(); + try { + return elementMap.size(); + } finally { + heapLock.unlock(); + } + } + + public List getAllElements() { + heapLock.lock(); + try { + return List.copyOf(elementMap.values()); + } finally { + heapLock.unlock(); + } + } + + public Element removeByEntityId(int entityId) { + heapLock.lock(); + try { + Element removed = elementMap.remove(entityId); + if (removed != null) { + maxHeap.remove(removed); + } + return removed; + } finally { + heapLock.unlock(); + } + } +}