Skip to content

Report about network cost about two model plugin (ModelEngine, BetterModel)

Davis1216 edited this page Aug 30, 2025 · 1 revision

目标数据包

正如您所知,ModelEngine 和 BetterModel 都使用物品显示数据包。
因此,我们需要对 minecraft_set_entity_data 数据包进行性能分析,以假设网络使用情况。

这个数据包发送一些实体的数据列表。

  • 共享特性
  • 实体的原始特性

物品显示也有这个实体数据。

  • 物品
  • 变换(位置、旋转、缩放)
  • 发光、插值持续时间/延迟、旗帜、亮度等。

您可以很容易地估计,变换相关的实体数据可能会非常重,消耗大量网络资源。

性能分析代码

仅从特定模型插件派生的最终缓冲区大小进行性能分析非常困难。
但我们可以通过 Netty 通道注入来估算。

@SuppressWarnings("unused")
public final class ModelProfiler extends JavaPlugin {
    private static final DecimalFormat FORMAT = new DecimalFormat("#,###");
    private static final String VANILLA_ENCODER = "encoder";
    private static final Method ENCODE_METHOD;

    static {
        try {
            ENCODE_METHOD = MessageToByteEncoder.class
                    .getDeclaredMethod("encode", ChannelHandlerContext.class, Object.class, ByteBuf.class);
            ENCODE_METHOD.setAccessible(true);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private final Map<UUID, PlayerProfiler> profilerMap = new ConcurrentHashMap<>();

    @Override
    public void onEnable() {
        Bukkit.getPluginManager().registerEvents(new Listener() {
            @EventHandler
            public void join(PlayerJoinEvent event) {
                var player = event.getPlayer();
                var channel = ((CraftPlayer) player).getHandle().connection.connection.channel;
                channel.eventLoop().submit(() -> profilerMap.computeIfAbsent(player.getUniqueId(), uuid -> new PlayerProfiler(player, channel.pipeline())));
            }
            @EventHandler
            public void quit(PlayerQuitEvent event) {
                var handler = profilerMap.remove(event.getPlayer().getUniqueId());
                if (handler != null) handler.remove();
            }
        }, this);
    }

    private record TimedPacketSize(long time, int bytes) {}

    private class PlayerProfiler extends MessageToByteEncoder<Packet<?>> {
        private final Queue<TimedPacketSize> byteQueue = new ConcurrentLinkedQueue<>();
        private final ScheduledTask actionbar;
        private final MessageToByteEncoder<?> delegate;

        private PlayerProfiler(@NotNull Player player, @NotNull ChannelPipeline pipeline) {
            actionbar = Bukkit.getAsyncScheduler().runAtFixedRate(ModelProfiler.this, task -> {
                var time = System.currentTimeMillis();
                byteQueue.removeIf(t -> time - t.time > 1000);
                if (!byteQueue.isEmpty()) {
                    player.sendActionBar(Component.text("当前流量使用: " + FORMAT.format(byteQueue.stream()
                            .mapToInt(i -> i.bytes)
                            .sum() / 1024) + " kB/s"));
                }
            }, 50, 50, TimeUnit.MILLISECONDS);
            delegate = (MessageToByteEncoder<?>) pipeline.replace(VANILLA_ENCODER, VANILLA_ENCODER, this);
        }

        private void remove() {
            actionbar.cancel();
        }

        @Override
        protected void encode(ChannelHandlerContext ctx, Packet<?> msg, ByteBuf out) throws Exception {
            var before = out.writerIndex();
            ENCODE_METHOD.invoke(delegate, ctx, msg, out);
            if (msg instanceof ClientboundSetEntityDataPacket) {
                byteQueue.add(new TimedPacketSize(System.currentTimeMillis(), out.writerIndex() - before));
            }
        }
    }
}

如您所见,它可以过滤所有 ClientboundSetEntityDataPacket 并收集每个玩家的原始数据包缓冲区大小。
它不是完全准确的网络资源使用情况,但它可以是一个检查某些模型插件效率的好工具。

性能分析工具

性能分析步骤

  1. 以灵魂骑士的身份召唤傀儡,测试空闲和伤害着色
  2. 生产 5 个灵魂骑士并运行 90 秒以获取网络摘要

性能分析 - ModelEngine (4.0.9 - build 2150)

您可以在 我的 Youtube 上看到我的性能分析视频。

meg_1 meg_2

一般来说,ModelEngine 的流量是稳定的且较高的。
即使在空闲状态下,它也会使用大约 >=20kb 的数据包。

{
 "total_time": "90002.0ms",
 "average_traffic": {
  "in": "0kbps",
  "out": "449kbps"
 },
 "global": {
  "client_bound": {
   "minecraft_level_chunk_with_light": {
    "value": 26878,
    "percentage": "66.41%"
   },
   "minecraft_set_entity_data": {
    "value": 10802,
    "percentage": "26.69%"
   },
   "minecraft_entity_position_sync": {
    "value": 2009,
    "percentage": "4.965%"
   },
   "..."
  }
 }
}

90 秒的摘要是 10,802kb 的数据,和 2,009 个位置同步数据包。 (=12,811kb)

性能分析 - BetterModel (1.9.2 - build 230)

您可以在 我的 Youtube 上看到我的性能分析视频。

bm_1 bm_2

一般来说,BetterModel 的流量是动态的(低 ~ 中等高)。
但在所有动画中,它比 ModelEngine 的数据包使用量更低。

{
 "total_time": "90002.0ms",
 "average_traffic": {
  "in": "0kbps",
  "out": "449kbps"
 },
 "global": {
  "client_bound": {
   "minecraft_level_chunk_with_light": {
    "value": 27780,
    "percentage": "84.286%"
   },
   "minecraft_set_entity_data": {
    "value": 3769,
    "percentage": "11.436%"
   },
   "minecraft_move_entity_rot": {
    "value": 338,
    "percentage": "1.027%"
   },
   "..."
  }
 }
}

90 秒的摘要是 3,769kb 的数据,和 338 个旋转数据包。 (=4,107kb)
您可以看到,BetterModel 消耗的 32.05% 数据包大小与 ModelEngine 相比。

结论

ModelEngine
数据包使用量稳定但持续较高。
例如,空闲动画产生的流量与行走动画几乎相同。

视锥剔除似乎效率不高。
它会响应玩家的偏航,但未能正确考虑俯仰,导致即使模型不可见也会产生不必要的网络流量。

BetterModel
数据包使用量较低且更动态。
空闲动画产生的流量比行走动画少一半。

总体而言,所有动画产生的流量比 ModelEngine 中的流量显著少。

似乎 BetterModel 比 ModelEngine 更优化网络使用。
也许它是一个减少网络成本的好选择。

Clone this wiki locally