diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/PayloadTypeRegistry.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/PayloadTypeRegistry.java
index 65f5f91045..af320c29ee 100644
--- a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/PayloadTypeRegistry.java
+++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/PayloadTypeRegistry.java
@@ -16,6 +16,8 @@
package net.fabricmc.fabric.api.networking.v1;
+import java.util.function.IntSupplier;
+
import org.jetbrains.annotations.ApiStatus;
import net.minecraft.network.FriendlyByteBuf;
@@ -50,7 +52,7 @@ public interface PayloadTypeRegistry {
* and before registering a packet handler.
*
*
Payload types registered with this method will be split into multiple packets,
- * allowing to send packets larger than vanilla limited size.
+ * allowing to send packets larger than the vanilla limited size.
*
* @param type the payload type
* @param codec the codec for the payload type
@@ -60,6 +62,27 @@ public interface PayloadTypeRegistry {
*/
CustomPacketPayload.TypeAndCodec super B, T> registerLarge(CustomPacketPayload.Type type, StreamCodec super B, T> codec, int maxPacketSize);
+ /**
+ * Registers a large custom payload type.
+ *
+ * This must be done on both the sending and receiving side, usually during mod initialization
+ * and before registering a packet handler.
+ *
+ *
Payload types registered with this method will be split into multiple packets,
+ * allowing to send packets larger than the vanilla limited size.
+ *
+ *
The {@code maxPacketSizeSupplier} will be called once, right before the first packet of this payload type
+ * is sent/received on either side. This allows mods some leeway particularly during mod initialization to
+ * dynamically determine a suitable max size.
+ *
+ * @param type the payload type
+ * @param codec the codec for the payload type
+ * @param maxPacketSizeSupplier the function that returns the max size of payload packet
+ * @param the payload type
+ * @return the registered payload type
+ */
+ CustomPacketPayload.TypeAndCodec super B, T> registerLarge(CustomPacketPayload.Type type, StreamCodec super B, T> codec, IntSupplier maxPacketSizeSupplier);
+
/**
* @return the {@link PayloadTypeRegistry} instance for the serverbound (client to server) configuration channel.
*/
diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/PayloadTypeRegistryImpl.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/PayloadTypeRegistryImpl.java
index 944e7e7fa5..3d12e595ee 100644
--- a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/PayloadTypeRegistryImpl.java
+++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/PayloadTypeRegistryImpl.java
@@ -19,10 +19,13 @@
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
+import java.util.function.IntSupplier;
import io.netty.buffer.ByteBufUtil;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import org.jspecify.annotations.Nullable;
import net.minecraft.network.ConnectionProtocol;
@@ -44,7 +47,8 @@ public class PayloadTypeRegistryImpl implements Paylo
public static final PayloadTypeRegistryImpl SERVERBOUND_PLAY = new PayloadTypeRegistryImpl<>(ConnectionProtocol.PLAY, PacketFlow.SERVERBOUND);
public static final PayloadTypeRegistryImpl CLIENTBOUND_PLAY = new PayloadTypeRegistryImpl<>(ConnectionProtocol.PLAY, PacketFlow.CLIENTBOUND);
private final Map> packetTypes = new HashMap<>();
- private final Object2IntMap maxPacketSize = new Object2IntOpenHashMap<>();
+ private final Object2IntMap maxPacketSizes = new Object2IntOpenHashMap<>();
+ private final Object2ObjectMap pendingMaxPacketSizes = new Object2ObjectOpenHashMap<>();
private final ConnectionProtocol protocol;
private final PacketFlow flow;
private final int minimalSplittableSize;
@@ -66,7 +70,7 @@ public static PayloadTypeRegistryImpl> get(ProtocolInfo> state) {
@Override
public CustomPacketPayload.TypeAndCodec super B, T> register(CustomPacketPayload.Type type, StreamCodec super B, T> codec) {
- Objects.requireNonNull(type, "id");
+ Objects.requireNonNull(type, "type");
Objects.requireNonNull(codec, "codec");
final CustomPacketPayload.TypeAndCodec payloadType = new CustomPacketPayload.TypeAndCodec<>(type, codec.cast());
@@ -80,15 +84,30 @@ public CustomPacketPayload.TypeAndCodec super
}
@Override
- public CustomPacketPayload.TypeAndCodec super B, T> registerLarge(CustomPacketPayload.Type type, StreamCodec super B, T> codec, int maxPayloadSize) {
- if (maxPayloadSize < 0) {
- throw new IllegalArgumentException("Provided maxPayloadSize needs to be positive!");
+ public CustomPacketPayload.TypeAndCodec super B, T> registerLarge(CustomPacketPayload.Type type, StreamCodec super B, T> codec, int maxPacketSize) {
+ if (maxPacketSize < 0) {
+ throw new IllegalArgumentException("Provided maxPacketSize needs to be positive!");
}
CustomPacketPayload.TypeAndCodec super B, T> typeAndCodec = register(type, codec);
+ padAndSetMaxPacketSize(type.id(), maxPacketSize);
+ return typeAndCodec;
+ }
+
+ @Override
+ public CustomPacketPayload.TypeAndCodec super B, T> registerLarge(CustomPacketPayload.Type type, StreamCodec super B, T> codec, IntSupplier maxPacketSizeSupplier) {
+ Objects.requireNonNull(maxPacketSizeSupplier, "maxPacketSizeSupplier");
+
+ CustomPacketPayload.TypeAndCodec super B, T> typeAndCodec = register(type, codec);
+ pendingMaxPacketSizes.put(type.id(), maxPacketSizeSupplier);
+ return typeAndCodec;
+ }
+
+ private void padAndSetMaxPacketSize(Identifier id, int maxSize) {
// Defines max packet size, increased by length of packet's Identifier to cover full size of CustomPayloadX2YPackets.
- int identifierSize = ByteBufUtil.utf8MaxBytes(type.id().toString());
- int maxPacketSize = maxPayloadSize + VarInt.getByteSize(identifierSize) + identifierSize + 5 * 2;
+ int identifierSize = ByteBufUtil.utf8MaxBytes(id.toString());
+ int paddingSize = VarInt.getByteSize(identifierSize) + identifierSize + 5 * 2;
+ int maxPacketSize = maxSize + paddingSize;
// Prevent overflow
if (maxPacketSize < 0) {
@@ -97,23 +116,36 @@ public CustomPacketPayload.TypeAndCodec super
// No need to enable splitting, if packet's max size is smaller than chunk
if (maxPacketSize > this.minimalSplittableSize) {
- this.maxPacketSize.put(type.id(), maxPacketSize);
+ this.maxPacketSizes.put(id, maxPacketSize);
}
-
- return typeAndCodec;
}
public CustomPacketPayload.@Nullable TypeAndCodec get(Identifier id) {
return packetTypes.get(id);
}
- public CustomPacketPayload.@Nullable TypeAndCodec get(CustomPacketPayload.Type id) {
+ public CustomPacketPayload.@Nullable TypeAndCodec get(CustomPacketPayload.Type type) {
//noinspection unchecked
- return (CustomPacketPayload.TypeAndCodec) packetTypes.get(id.id());
+ return (CustomPacketPayload.TypeAndCodec) packetTypes.get(type.id());
}
- public int getMaxPacketSize(Identifier id) {
- return this.maxPacketSize.getOrDefault(id, -1);
+ /**
+ * @return the max packet size, or -1 if the payload type does not need splitting.
+ */
+ public int getMaxPacketSizeForSplitting(Identifier id) {
+ IntSupplier supplier = this.pendingMaxPacketSizes.remove(id);
+
+ if (supplier != null) {
+ int maxPacketSize = supplier.getAsInt();
+
+ if (maxPacketSize < 0) {
+ throw new IllegalArgumentException("maxPacketSize supplier for packet type " + id + ": must be positive!");
+ }
+
+ padAndSetMaxPacketSize(id, maxPacketSize);
+ }
+
+ return this.maxPacketSizes.getOrDefault(id, -1);
}
public ConnectionProtocol getProtocol() {
diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/splitter/FabricPacketMerger.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/splitter/FabricPacketMerger.java
index e8ffb6ae9e..fc64416b3c 100644
--- a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/splitter/FabricPacketMerger.java
+++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/splitter/FabricPacketMerger.java
@@ -82,7 +82,7 @@ protected void decode(ChannelHandlerContext channelHandlerContext, Packet> pac
Identifier payloadId = Identifier.STREAM_CODEC.decode(payload.byteBuf());
buf.readerIndex(readerIndex);
- int maxSize = payloadTypeRegistry.getMaxPacketSize(payloadId);
+ int maxSize = payloadTypeRegistry.getMaxPacketSizeForSplitting(payloadId);
if (maxSize == -1) {
throw new DecoderException("Received '" + payloadId + "' packet doesn't support splitting, but received split data!");
diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ClientboundCustomPayloadPacketMixin.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ClientboundCustomPayloadPacketMixin.java
index 9ad3d6967d..8688934e3b 100644
--- a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ClientboundCustomPayloadPacketMixin.java
+++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ClientboundCustomPayloadPacketMixin.java
@@ -79,7 +79,7 @@ private static StreamCodec wrapConfigCodec
@Override
public void fabric_split(PayloadTypeRegistryImpl> payloadTypeRegistry, ChannelHandlerContext channelHandlerContext, PacketEncoder> encoder, Packet> packet, Consumer> consumer) throws Exception {
- int size = payloadTypeRegistry.getMaxPacketSize(this.payload.type().id());
+ int size = payloadTypeRegistry.getMaxPacketSizeForSplitting(this.payload.type().id());
if (size == -1) {
consumer.accept((Packet>) this);
diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ServerboundCustomPayloadPacketMixin.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ServerboundCustomPayloadPacketMixin.java
index fc9343f570..279b0b1eb0 100644
--- a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ServerboundCustomPayloadPacketMixin.java
+++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ServerboundCustomPayloadPacketMixin.java
@@ -70,7 +70,7 @@ private static StreamCodec wrapCodec(Custo
@Override
public void fabric_split(PayloadTypeRegistryImpl> payloadTypeRegistry, ChannelHandlerContext channelHandlerContext, PacketEncoder> encoder, Packet> packet, Consumer> consumer) throws Exception {
- int size = payloadTypeRegistry.getMaxPacketSize(this.payload.type().id());
+ int size = payloadTypeRegistry.getMaxPacketSizeForSplitting(this.payload.type().id());
if (size == -1) {
consumer.accept((Packet>) this);
diff --git a/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/splitter/NetworkingSplitterTest.java b/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/splitter/NetworkingSplitterTest.java
index a088ae9302..9e6bf9fba6 100644
--- a/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/splitter/NetworkingSplitterTest.java
+++ b/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/splitter/NetworkingSplitterTest.java
@@ -34,11 +34,11 @@
import net.fabricmc.fabric.test.networking.NetworkingTestmods;
public class NetworkingSplitterTest implements ModInitializer {
- private static final Logger LOGGER = LoggerFactory.getLogger(NetworkingSplitterTest.class);
+ public static final Logger LOGGER = LoggerFactory.getLogger(NetworkingSplitterTest.class);
- private static final int DATA_SIZE = 20 * 1024 * 1024;
+ public static final int DATA_SIZE = 20 * 1024 * 1024;
- // 20 MB of random data source
+ // 20 MiB of random data source
private static final int[][] RANDOM_DATA = {
IntStream.generate(RandomSource.create(24534)::nextInt).limit(20).toArray(),
IntStream.generate(RandomSource.create(24533)::nextInt).limit(DATA_SIZE / 4).toArray()
@@ -48,7 +48,7 @@ public class NetworkingSplitterTest implements ModInitializer {
public void onInitialize() {
// Register the payload on both sides for play and configuration
PayloadTypeRegistry.clientboundPlay().registerLarge(LargePayload.TYPE, LargePayload.CODEC, DATA_SIZE + 14);
- PayloadTypeRegistry.serverboundPlay().registerLarge(LargePayload.TYPE, LargePayload.CODEC, DATA_SIZE + 14);
+ PayloadTypeRegistry.serverboundPlay().registerLarge(LargePayload.TYPE, LargePayload.CODEC, () -> DATA_SIZE + 14);
// When the client joins, send a packet expecting it to be validated and echoed back
ServerPlayConnectionEvents.JOIN.register((listener, sender, server) -> sender.sendPacket(new LargePayload(0, RANDOM_DATA[0])));
@@ -62,7 +62,7 @@ public void onInitialize() {
public static void validateLargePacketData(int index, int[] data, String side) {
if (Arrays.equals(RANDOM_DATA[index], data)) {
- NetworkingTestmods.LOGGER.info("Successfully received large packet [" + index + "] on " + side);
+ LOGGER.info("Successfully received large packet [{}] on {}", index, side);
return;
}