diff --git a/fabric-data-attachment-api-v1/src/client/java/net/fabricmc/fabric/impl/attachment/client/AttachmentSyncClient.java b/fabric-data-attachment-api-v1/src/client/java/net/fabricmc/fabric/impl/attachment/client/AttachmentSyncClient.java
index b8a5057267..b150e8e86e 100644
--- a/fabric-data-attachment-api-v1/src/client/java/net/fabricmc/fabric/impl/attachment/client/AttachmentSyncClient.java
+++ b/fabric-data-attachment-api-v1/src/client/java/net/fabricmc/fabric/impl/attachment/client/AttachmentSyncClient.java
@@ -20,7 +20,6 @@
import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationNetworking;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.fabric.impl.attachment.AttachmentEntrypoint;
-import net.fabricmc.fabric.impl.attachment.sync.AttachmentChange;
import net.fabricmc.fabric.impl.attachment.sync.AttachmentSync;
import net.fabricmc.fabric.impl.attachment.sync.AttachmentSyncException;
import net.fabricmc.fabric.impl.attachment.sync.s2c.AttachmentSyncPayloadS2C;
@@ -39,14 +38,11 @@ public void onInitializeClient() {
ClientPlayNetworking.registerGlobalReceiver(
AttachmentSyncPayloadS2C.ID,
(payload, context) -> {
- for (AttachmentChange attachmentChange : payload.attachments()) {
- try {
- attachmentChange.tryApply(context.client().level);
- } catch (AttachmentSyncException e) {
- AttachmentEntrypoint.LOGGER.error("Error accepting attachment changes", e);
- context.responseSender().disconnect(e.getText());
- break;
- }
+ try {
+ payload.attachment().tryApply(context.client().level);
+ } catch (AttachmentSyncException e) {
+ AttachmentEntrypoint.LOGGER.error("Error accepting attachment changes", e);
+ context.responseSender().disconnect(e.getText());
}
}
);
diff --git a/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/api/attachment/v1/AttachmentRegistry.java b/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/api/attachment/v1/AttachmentRegistry.java
index c2d86b6a21..bc4f6912c8 100644
--- a/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/api/attachment/v1/AttachmentRegistry.java
+++ b/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/api/attachment/v1/AttachmentRegistry.java
@@ -159,6 +159,18 @@ public interface Builder {
*/
AttachmentRegistry.Builder syncWith(StreamCodec super RegistryFriendlyByteBuf, A> packetCodec, AttachmentSyncPredicate syncPredicate);
+ /**
+ * Declares that this attachment type may be automatically synchronized with some clients, as determined by {@code syncPredicate}.
+ *
+ * The max size limit should be increased with care, as syncing large amounts of data may result in network lag and excessive bandwidth usage.
+ *
+ * @param packetCodec the codec used to serialize the attachment data over the network
+ * @param syncPredicate an {@link AttachmentSyncPredicate} determining with which clients to synchronize data
+ * @param maxSyncBytes the max number of data bytes that can be synced, defaults to 1 MiB minus some small padding
+ * @return the builder
+ */
+ AttachmentRegistry.Builder syncWith(StreamCodec super RegistryFriendlyByteBuf, A> packetCodec, AttachmentSyncPredicate syncPredicate, int maxSyncBytes);
+
/**
* Builds and registers the {@link AttachmentType}.
*
diff --git a/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/impl/attachment/AttachmentRegistryImpl.java b/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/impl/attachment/AttachmentRegistryImpl.java
index 9eb408d9b0..5ceb4b079b 100644
--- a/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/impl/attachment/AttachmentRegistryImpl.java
+++ b/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/impl/attachment/AttachmentRegistryImpl.java
@@ -25,24 +25,30 @@
import java.util.function.Supplier;
import com.mojang.serialization.Codec;
+import io.netty.buffer.ByteBufUtil;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.minecraft.network.RegistryFriendlyByteBuf;
+import net.minecraft.network.VarInt;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.Identifier;
import net.fabricmc.fabric.api.attachment.v1.AttachmentRegistry;
import net.fabricmc.fabric.api.attachment.v1.AttachmentSyncPredicate;
import net.fabricmc.fabric.api.attachment.v1.AttachmentType;
+import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry;
import net.fabricmc.fabric.impl.attachment.sync.AttachmentSync;
+import net.fabricmc.fabric.impl.attachment.sync.AttachmentTargetInfo;
+import net.fabricmc.fabric.impl.attachment.sync.s2c.AttachmentSyncPayloadS2C;
public final class AttachmentRegistryImpl {
private static final Logger LOGGER = LoggerFactory.getLogger("fabric-data-attachment-api-v1");
private static final Map> attachmentRegistry = new HashMap<>();
private static final Set syncableAttachments = new HashSet<>();
private static final Set syncableView = Collections.unmodifiableSet(syncableAttachments);
+ private static int currentMaxPayloadSize = AttachmentSync.INITIAL_MAX_ATTACHMENT_SYNC_PAYLOAD_SIZE;
public static void register(Identifier id, AttachmentType attachmentType) {
AttachmentType> existing = attachmentRegistry.put(id, attachmentType);
@@ -84,6 +90,7 @@ public static class BuilderImpl implements AttachmentRegistry.Builder {
@Nullable
private AttachmentSyncPredicate syncPredicate = null;
private boolean copyOnDeath = false;
+ private int maxSyncBytes = -1;
@Override
public AttachmentRegistry.Builder persistent(Codec codec) {
@@ -107,7 +114,7 @@ public AttachmentRegistry.Builder initializer(Supplier initializer) {
return this;
}
- @Deprecated
+ @Override
public AttachmentRegistry.Builder syncWith(StreamCodec super RegistryFriendlyByteBuf, A> packetCodec, AttachmentSyncPredicate syncPredicate) {
Objects.requireNonNull(packetCodec, "packet codec cannot be null");
Objects.requireNonNull(syncPredicate, "sync predicate cannot be null");
@@ -117,17 +124,42 @@ public AttachmentRegistry.Builder syncWith(StreamCodec super RegistryFriend
return this;
}
+ @Override
+ public AttachmentRegistry.Builder syncWith(StreamCodec super RegistryFriendlyByteBuf, A> packetCodec, AttachmentSyncPredicate syncPredicate, int maxSyncBytes) {
+ if (maxSyncBytes <= 0) {
+ throw new IllegalArgumentException("max sync bytes must be positive");
+ }
+
+ syncWith(packetCodec, syncPredicate);
+
+ this.maxSyncBytes = maxSyncBytes;
+ return this;
+ }
+
@Override
public AttachmentType buildAndRegister(Identifier id) {
Objects.requireNonNull(id, "identifier cannot be null");
- if (syncPredicate != null && id.toString().length() > AttachmentSync.MAX_IDENTIFIER_SIZE) {
- throw new IllegalArgumentException(
- "Identifier length is too long for a synced attachment type (was %d, maximum is %d)".formatted(
- id.toString().length(),
- AttachmentSync.MAX_IDENTIFIER_SIZE
- )
- );
+ if (syncPredicate != null) {
+ int identifierBytes = ByteBufUtil.utf8MaxBytes(id.toString());
+ int maxPaddingBytes = AttachmentTargetInfo.MAX_SIZE_IN_BYTES + VarInt.getByteSize(identifierBytes) + identifierBytes + 5 * 2;
+
+ if (maxSyncBytes == -1) { // If no custom limit set, then calculate default limit based on id size of the attachment
+ maxSyncBytes = AttachmentSync.INITIAL_MAX_ATTACHMENT_SYNC_PAYLOAD_SIZE - maxPaddingBytes;
+ }
+
+ int maxPayloadBytes = maxSyncBytes + maxPaddingBytes;
+
+ // Prevent overflow
+ if (maxPayloadBytes < 0) {
+ maxPayloadBytes = Integer.MAX_VALUE;
+ maxSyncBytes = Integer.MAX_VALUE - maxPaddingBytes;
+ }
+
+ if (maxPayloadBytes > currentMaxPayloadSize) {
+ currentMaxPayloadSize = maxPayloadBytes;
+ PayloadTypeRegistry.playS2C().modifyLargePayloadMaxSize(AttachmentSyncPayloadS2C.ID, currentMaxPayloadSize);
+ }
}
var attachment = new AttachmentTypeImpl<>(
@@ -136,6 +168,7 @@ public AttachmentType buildAndRegister(Identifier id) {
persistenceCodec,
packetCodec,
syncPredicate,
+ maxSyncBytes,
copyOnDeath
);
register(id, attachment);
diff --git a/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/impl/attachment/AttachmentTypeImpl.java b/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/impl/attachment/AttachmentTypeImpl.java
index 1581356feb..d1ef1fa957 100644
--- a/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/impl/attachment/AttachmentTypeImpl.java
+++ b/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/impl/attachment/AttachmentTypeImpl.java
@@ -34,6 +34,7 @@ public record AttachmentTypeImpl(
@Nullable Codec persistenceCodec,
@Nullable StreamCodec super RegistryFriendlyByteBuf, A> packetCodec,
@Nullable AttachmentSyncPredicate syncPredicate,
+ int maxSyncBytes,
boolean copyOnDeath
) implements AttachmentType {
@Override
diff --git a/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/impl/attachment/sync/AttachmentChange.java b/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/impl/attachment/sync/AttachmentChange.java
index 32623580b8..86f5855890 100644
--- a/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/impl/attachment/sync/AttachmentChange.java
+++ b/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/impl/attachment/sync/AttachmentChange.java
@@ -16,11 +16,7 @@
package net.fabricmc.fabric.impl.attachment.sync;
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.List;
import java.util.Objects;
-import java.util.Set;
import io.netty.buffer.Unpooled;
import org.jspecify.annotations.Nullable;
@@ -35,19 +31,13 @@
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.Identifier;
-import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.Level;
import net.fabricmc.fabric.api.attachment.v1.AttachmentTarget;
import net.fabricmc.fabric.api.attachment.v1.AttachmentType;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
-import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.fabricmc.fabric.impl.attachment.AttachmentRegistryImpl;
import net.fabricmc.fabric.impl.attachment.AttachmentTypeImpl;
-import net.fabricmc.fabric.impl.attachment.sync.s2c.AttachmentSyncPayloadS2C;
-import net.fabricmc.fabric.mixin.attachment.ServerboundCustomPayloadPacketAccessor;
-import net.fabricmc.fabric.mixin.attachment.VarIntAccessor;
-import net.fabricmc.fabric.mixin.networking.accessor.ServerCommonPacketListenerImplAccessor;
public record AttachmentChange(AttachmentTargetInfo> targetInfo, AttachmentType> type, byte[] data) {
public static final StreamCodec PACKET_CODEC = StreamCodec.composite(
@@ -59,8 +49,6 @@ public record AttachmentChange(AttachmentTargetInfo> targetInfo, AttachmentTyp
ByteBufCodecs.BYTE_ARRAY, AttachmentChange::data,
AttachmentChange::new
);
- private static final int MAX_PADDING_SIZE_IN_BYTES = AttachmentTargetInfo.MAX_SIZE_IN_BYTES + AttachmentSync.MAX_IDENTIFIER_SIZE;
- private static final int MAX_DATA_SIZE_IN_BYTES = ServerboundCustomPayloadPacketAccessor.getMaxPayloadSize() - MAX_PADDING_SIZE_IN_BYTES;
@SuppressWarnings("unchecked")
public static AttachmentChange create(AttachmentTargetInfo> targetInfo, AttachmentType> type, @Nullable Object value, RegistryAccess dynamicRegistryManager) {
@@ -77,50 +65,21 @@ public static AttachmentChange create(AttachmentTargetInfo> targetInfo, Attach
buf.writeBoolean(false);
}
- byte[] encoded = buf.array();
+ byte[] encoded = new byte[buf.readableBytes()]; // buf.array() will return the backing array directly, which may contain unused space
+ buf.readBytes(encoded);
+ int maxSyncBytes = ((AttachmentTypeImpl>) type).maxSyncBytes();
- if (encoded.length > MAX_DATA_SIZE_IN_BYTES) {
- throw new IllegalArgumentException("Data for attachment '%s' was too big (%d bytes, over maximum %d)".formatted(
+ if (encoded.length > maxSyncBytes) {
+ throw new IllegalArgumentException("Data for attachment '%s' was too big (%d bytes, over maximum %d). This limit can be configured during attachment registration.".formatted(
type.identifier(),
encoded.length,
- MAX_DATA_SIZE_IN_BYTES
+ maxSyncBytes
));
}
return new AttachmentChange(targetInfo, type, encoded);
}
- public static void partitionAndSendPackets(List changes, ServerPlayer player) {
- Set supported = ((SupportedAttachmentsClientConnection) ((ServerCommonPacketListenerImplAccessor) player.connection).getConnection())
- .fabric_getSupportedAttachments();
- // sort by size to better partition packets
- changes.sort(Comparator.comparingInt(c -> c.data().length));
- List packetChanges = new ArrayList<>();
- int maxVarIntSize = VarIntAccessor.getMaxByteSize();
- int byteSize = maxVarIntSize;
-
- for (AttachmentChange change : changes) {
- if (!supported.contains(change.type.identifier())) {
- continue;
- }
-
- int size = MAX_PADDING_SIZE_IN_BYTES + change.data.length;
-
- if (byteSize + size > MAX_DATA_SIZE_IN_BYTES) {
- ServerPlayNetworking.send(player, new AttachmentSyncPayloadS2C(packetChanges));
- packetChanges.clear();
- byteSize = maxVarIntSize;
- }
-
- packetChanges.add(change);
- byteSize += size;
- }
-
- if (!packetChanges.isEmpty()) {
- ServerPlayNetworking.send(player, new AttachmentSyncPayloadS2C(packetChanges));
- }
- }
-
@SuppressWarnings("unchecked")
@Nullable
public Object decodeValue(RegistryAccess dynamicRegistryManager) {
diff --git a/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/impl/attachment/sync/AttachmentSync.java b/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/impl/attachment/sync/AttachmentSync.java
index 07e36e965e..5e7a1de347 100644
--- a/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/impl/attachment/sync/AttachmentSync.java
+++ b/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/impl/attachment/sync/AttachmentSync.java
@@ -22,8 +22,13 @@
import java.util.function.Consumer;
import java.util.stream.Collectors;
+import io.netty.buffer.ByteBufUtil;
+
import net.minecraft.network.Connection;
+import net.minecraft.network.VarInt;
import net.minecraft.network.protocol.Packet;
+import net.minecraft.network.protocol.game.ClientGamePacketListener;
+import net.minecraft.network.protocol.game.ClientboundBundlePacket;
import net.minecraft.resources.Identifier;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.ConfigurationTask;
@@ -45,7 +50,16 @@
import net.fabricmc.fabric.mixin.networking.accessor.ServerCommonPacketListenerImplAccessor;
public class AttachmentSync implements ModInitializer {
- public static final int MAX_IDENTIFIER_SIZE = 256;
+ public static final int INITIAL_MAX_ATTACHMENT_SYNC_PAYLOAD_SIZE;
+
+ static {
+ int identifierSize = ByteBufUtil.utf8MaxBytes(AttachmentSyncPayloadS2C.ID.toString());
+ // Max vanilla S2C packet size - id size, to ensure no splitting by default
+ INITIAL_MAX_ATTACHMENT_SYNC_PAYLOAD_SIZE = 1024 * 1024 - (VarInt.getByteSize(identifierSize) + identifierSize + 5 * 2);
+
+ // Ensure packet is registered before mods register any large attachments
+ PayloadTypeRegistry.playS2C().registerLarge(AttachmentSyncPayloadS2C.ID, AttachmentSyncPayloadS2C.CODEC, INITIAL_MAX_ATTACHMENT_SYNC_PAYLOAD_SIZE);
+ }
public static AcceptedAttachmentsPayloadC2S createResponsePayload() {
return new AcceptedAttachmentsPayloadC2S(AttachmentRegistryImpl.getSyncableAttachments());
@@ -56,7 +70,23 @@ public static void trySync(AttachmentChange change, ServerPlayer player) {
.fabric_getSupportedAttachments();
if (supported.contains(change.type().identifier())) {
- ServerPlayNetworking.send(player, new AttachmentSyncPayloadS2C(List.of(change)));
+ ServerPlayNetworking.send(player, new AttachmentSyncPayloadS2C(change));
+ }
+ }
+
+ public static void trySync(List changes, ServerPlayer player) {
+ Set supported = ((SupportedAttachmentsClientConnection) ((ServerCommonPacketListenerImplAccessor) player.connection).getConnection())
+ .fabric_getSupportedAttachments();
+
+ List> syncableChanges = new ArrayList<>();
+ changes.forEach(change -> {
+ if (supported.contains(change.type().identifier())) {
+ syncableChanges.add(ServerPlayNetworking.createS2CPacket(new AttachmentSyncPayloadS2C(change)));
+ }
+ });
+
+ if (!syncableChanges.isEmpty()) {
+ ServerPlayNetworking.getSender(player).sendPacket(new ClientboundBundlePacket(syncableChanges));
}
}
@@ -103,8 +133,6 @@ public void onInitialize() {
});
// Play
- PayloadTypeRegistry.playS2C().register(AttachmentSyncPayloadS2C.ID, AttachmentSyncPayloadS2C.CODEC);
-
ServerPlayerEvents.JOIN.register((player) -> {
List changes = new ArrayList<>();
// sync world attachments
@@ -113,7 +141,7 @@ public void onInitialize() {
((AttachmentTargetImpl) player).fabric_computeInitialSyncChanges(player, changes::add);
if (!changes.isEmpty()) {
- AttachmentChange.partitionAndSendPackets(changes, player);
+ trySync(changes, player);
}
});
@@ -124,7 +152,7 @@ public void onInitialize() {
((AttachmentTargetImpl) destination).fabric_computeInitialSyncChanges(player, changes::add);
if (!changes.isEmpty()) {
- AttachmentChange.partitionAndSendPackets(changes, player);
+ trySync(changes, player);
}
});
@@ -133,7 +161,7 @@ public void onInitialize() {
((AttachmentTargetImpl) trackedEntity).fabric_computeInitialSyncChanges(player, changes::add);
if (!changes.isEmpty()) {
- AttachmentChange.partitionAndSendPackets(changes, player);
+ trySync(changes, player);
}
});
}
diff --git a/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/impl/attachment/sync/s2c/AttachmentSyncPayloadS2C.java b/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/impl/attachment/sync/s2c/AttachmentSyncPayloadS2C.java
index 134ef2eb62..372b7118ac 100644
--- a/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/impl/attachment/sync/s2c/AttachmentSyncPayloadS2C.java
+++ b/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/impl/attachment/sync/s2c/AttachmentSyncPayloadS2C.java
@@ -16,19 +16,16 @@
package net.fabricmc.fabric.impl.attachment.sync.s2c;
-import java.util.List;
-
import net.minecraft.network.FriendlyByteBuf;
-import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.Identifier;
import net.fabricmc.fabric.impl.attachment.sync.AttachmentChange;
-public record AttachmentSyncPayloadS2C(List attachments) implements CustomPacketPayload {
+public record AttachmentSyncPayloadS2C(AttachmentChange attachment) implements CustomPacketPayload {
public static final StreamCodec CODEC = StreamCodec.composite(
- AttachmentChange.PACKET_CODEC.apply(ByteBufCodecs.list()), AttachmentSyncPayloadS2C::attachments,
+ AttachmentChange.PACKET_CODEC, AttachmentSyncPayloadS2C::attachment,
AttachmentSyncPayloadS2C::new
);
public static final Identifier PACKET_ID = Identifier.fromNamespaceAndPath("fabric", "attachment_sync_v1");
diff --git a/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/mixin/attachment/PlayerChunkSenderMixin.java b/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/mixin/attachment/PlayerChunkSenderMixin.java
index ea1fe2ff47..d88b72edc0 100644
--- a/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/mixin/attachment/PlayerChunkSenderMixin.java
+++ b/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/mixin/attachment/PlayerChunkSenderMixin.java
@@ -32,6 +32,7 @@
import net.fabricmc.fabric.impl.attachment.AttachmentTargetImpl;
import net.fabricmc.fabric.impl.attachment.sync.AttachmentChange;
+import net.fabricmc.fabric.impl.attachment.sync.AttachmentSync;
@Mixin(PlayerChunkSender.class)
abstract class PlayerChunkSenderMixin {
@@ -49,7 +50,7 @@ private void sendInitialAttachmentData(ServerGamePacketListenerImpl handler, Ser
((AttachmentTargetImpl) chunk).fabric_computeInitialSyncChanges(player, changes::add);
if (!changes.isEmpty()) {
- AttachmentChange.partitionAndSendPackets(changes, player);
+ AttachmentSync.trySync(changes, player);
}
}
}
diff --git a/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/mixin/attachment/ServerboundCustomPayloadPacketAccessor.java b/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/mixin/attachment/ServerboundCustomPayloadPacketAccessor.java
deleted file mode 100644
index c58700ad29..0000000000
--- a/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/mixin/attachment/ServerboundCustomPayloadPacketAccessor.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.fabricmc.fabric.mixin.attachment;
-
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.gen.Accessor;
-
-import net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket;
-
-@Mixin(ServerboundCustomPayloadPacket.class)
-public interface ServerboundCustomPayloadPacketAccessor {
- @Accessor("MAX_PAYLOAD_SIZE")
- static int getMaxPayloadSize() {
- throw new UnsupportedOperationException("Implemented via mixin");
- }
-}
diff --git a/fabric-data-attachment-api-v1/src/main/resources/fabric-data-attachment-api-v1.mixins.json b/fabric-data-attachment-api-v1/src/main/resources/fabric-data-attachment-api-v1.mixins.json
index e4fd124977..c20bf7680d 100644
--- a/fabric-data-attachment-api-v1/src/main/resources/fabric-data-attachment-api-v1.mixins.json
+++ b/fabric-data-attachment-api-v1/src/main/resources/fabric-data-attachment-api-v1.mixins.json
@@ -9,7 +9,6 @@
"PlayerChunkSenderMixin",
"ChunkAccessMixin",
"ConnectionMixin",
- "ServerboundCustomPayloadPacketAccessor",
"EntityMixin",
"SerializableChunkDataMixin",
"ServerLevelMixin",
diff --git a/fabric-data-attachment-api-v1/src/testmod/java/net/fabricmc/fabric/test/attachment/AttachmentTestMod.java b/fabric-data-attachment-api-v1/src/testmod/java/net/fabricmc/fabric/test/attachment/AttachmentTestMod.java
index 714da261c5..091b152299 100644
--- a/fabric-data-attachment-api-v1/src/testmod/java/net/fabricmc/fabric/test/attachment/AttachmentTestMod.java
+++ b/fabric-data-attachment-api-v1/src/testmod/java/net/fabricmc/fabric/test/attachment/AttachmentTestMod.java
@@ -16,6 +16,8 @@
package net.fabricmc.fabric.test.attachment;
+import java.util.stream.LongStream;
+
import com.mojang.serialization.Codec;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -28,6 +30,7 @@
import net.minecraft.resources.Identifier;
import net.minecraft.resources.ResourceKey;
import net.minecraft.util.ExtraCodecs;
+import net.minecraft.util.RandomSource;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
@@ -95,6 +98,13 @@ public class AttachmentTestMod implements ModInitializer {
.persistent(ExtraCodecs.NON_NEGATIVE_INT)
.syncWith(ByteBufCodecs.INT, AttachmentSyncPredicate.targetOnly())
);
+ public static final long[] LARGE_DATA = LongStream.generate(RandomSource.create(16554)::nextLong).limit((10 * 1024 * 1024) / 8).toArray();
+ public static final AttachmentType SYNCED_LARGE = AttachmentRegistry.create(
+ Identifier.fromNamespaceAndPath(MOD_ID, "synced_large"),
+ builder -> builder
+ .initializer(() -> LARGE_DATA)
+ .syncWith(ByteBufCodecs.LONG_ARRAY, AttachmentSyncPredicate.all(), 10 * 1024 * 1024 + 4) // 10 MiB + int length
+ );
@Override
public void onInitialize() {
@@ -115,6 +125,14 @@ public void onInitialize() {
player.displayClientMessage(Component.literal("Attached"), false);
return InteractionResult.SUCCESS;
}
+ } else if (player.getItemInHand(hand).getItem() == Items.GOLDEN_CARROT) {
+ BlockEntity blockEntity = world.getBlockEntity(hitResult.getBlockPos());
+
+ if (blockEntity != null) {
+ blockEntity.getAttachedOrCreate(SYNCED_LARGE);
+ player.displayClientMessage(Component.literal("Attached LARGE"), false);
+ return InteractionResult.SUCCESS;
+ }
}
return InteractionResult.PASS;
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 2c714131ee..199dfa74de 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
@@ -60,6 +60,15 @@ public interface PayloadTypeRegistry {
*/
CustomPacketPayload.TypeAndCodec super B, T> registerLarge(CustomPacketPayload.Type id, StreamCodec super B, T> codec, int maxPacketSize);
+ /**
+ * Modifies the maximum size of an already registered large payload type via {@link PayloadTypeRegistry#registerLarge(CustomPacketPayload.Type, StreamCodec, int)}.
+ *
+ * @param id the id of the payload type
+ * @param maxPacketSize the maximum size of payload packet
+ * @param the payload type
+ */
+ void modifyLargePayloadMaxSize(CustomPacketPayload.Type id, int maxPacketSize);
+
/**
* @return the {@link PayloadTypeRegistry} instance for the 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 ba9935a4b8..b115301a85 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
@@ -86,21 +86,26 @@ public CustomPacketPayload.TypeAndCodec super
}
CustomPacketPayload.TypeAndCodec super B, T> type = register(id, codec);
- // Defines max packet size, increased by length of packet's Identifier to cover full size of CustomPayloadX2YPackets.
- int identifierSize = ByteBufUtil.utf8MaxBytes(id.id().toString());
- int maxPacketSize = maxPayloadSize + VarInt.getByteSize(identifierSize) + identifierSize + 5 * 2;
+ setMaxPacketSize(id, maxPayloadSize);
- // Prevent overflow
- if (maxPacketSize < 0) {
- maxPacketSize = Integer.MAX_VALUE;
+ return type;
+ }
+
+ @Override
+ public void modifyLargePayloadMaxSize(CustomPacketPayload.Type id, int maxPayloadSize) {
+ if (!packetTypes.containsKey(id.id())) {
+ throw new IllegalArgumentException("Packet type " + id + " has not been registered yet!");
}
- // No need to enable splitting, if packet's max size is smaller than chunk
- if (maxPacketSize > this.minimalSplittableSize) {
- this.maxPacketSize.put(id.id(), maxPacketSize);
+ if (this.maxPacketSize.getOrDefault(id.id(), -2) == -2) {
+ throw new IllegalArgumentException("Packet type " + id + " is not registered as a large payload!");
}
- return type;
+ if (maxPayloadSize < 0) {
+ throw new IllegalArgumentException("Provided maxPayloadSize needs to be positive!");
+ }
+
+ setMaxPacketSize(id, maxPayloadSize);
}
public CustomPacketPayload.@Nullable TypeAndCodec get(Identifier id) {
@@ -123,4 +128,22 @@ public ConnectionProtocol getPhase() {
public PacketFlow getSide() {
return side;
}
+
+ private void setMaxPacketSize(CustomPacketPayload.Type id, int maxPayloadSize) {
+ // Defines max packet size, increased by length of packet's Identifier to cover full size of CustomPayloadX2YPackets.
+ int identifierSize = ByteBufUtil.utf8MaxBytes(id.id().toString());
+ int maxPacketSize = maxPayloadSize + VarInt.getByteSize(identifierSize) + identifierSize + 5 * 2;
+
+ // Prevent overflow
+ if (maxPacketSize < 0) {
+ maxPacketSize = Integer.MAX_VALUE;
+ }
+
+ // No need to enable splitting, if packet's max size is smaller than chunk
+ if (maxPacketSize > this.minimalSplittableSize) {
+ this.maxPacketSize.put(id.id(), maxPacketSize);
+ } else {
+ this.maxPacketSize.put(id.id(), -1); // To indicate packet was registered as large, but currently does not require splitting
+ }
+ }
}
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 84702b006e..9c5f47ef4e 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
@@ -28,6 +28,7 @@
import net.minecraft.util.RandomSource;
import net.fabricmc.api.ModInitializer;
+import net.fabricmc.fabric.api.networking.v1.PacketSender;
import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry;
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
@@ -36,19 +37,21 @@
public class NetworkingSplitterTest implements ModInitializer {
private static final Logger LOGGER = LoggerFactory.getLogger(NetworkingSplitterTest.class);
- private static final int DATA_SIZE = 20 * 1024 * 1024;
+ private static final int DATA_SIZE_1 = 20 * 1024 * 1024;
+ private static final int DATA_SIZE_2 = 50 * 1024 * 1024;
- // 20 MB of random data source
+ // 20/50 MB 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()
+ IntStream.generate(RandomSource.create(24533)::nextInt).limit(DATA_SIZE_1 / 4).toArray(),
+ IntStream.generate(RandomSource.create(24532)::nextInt).limit(DATA_SIZE_2 / 4).toArray()
};
@Override
public void onInitialize() {
// Register the payload on both sides for play and configuration
- PayloadTypeRegistry.playS2C().registerLarge(LargePayload.ID, LargePayload.CODEC, DATA_SIZE + 14);
- PayloadTypeRegistry.playC2S().registerLarge(LargePayload.ID, LargePayload.CODEC, DATA_SIZE + 14);
+ PayloadTypeRegistry.playS2C().registerLarge(LargePayload.ID, LargePayload.CODEC, DATA_SIZE_1 + 14);
+ PayloadTypeRegistry.playC2S().registerLarge(LargePayload.ID, LargePayload.CODEC, DATA_SIZE_1 + 14);
// When the client joins, send a packet expecting it to be validated and echoed back
ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> sender.sendPacket(new LargePayload(0, RANDOM_DATA[0])));
@@ -56,13 +59,21 @@ public void onInitialize() {
// Validate received packet
ServerPlayNetworking.registerGlobalReceiver(LargePayload.ID, (payload, context) -> {
- validateLargePacketData(payload.index(), payload.data(), "server");
+ validateLargePacketData(payload.index(), payload.data(), "server", context.responseSender());
});
}
- public static void validateLargePacketData(int index, int[] data, String side) {
+ public static void validateLargePacketData(int index, int[] data, String side, PacketSender sender) {
if (Arrays.equals(RANDOM_DATA[index], data)) {
- NetworkingTestmods.LOGGER.info("Successfully received large packet [" + index + "] on " + side);
+ LOGGER.info("Successfully received large packet [" + index + "] on " + side);
+
+ if (side.equals("server") && index == 1) {
+ LOGGER.info("Increasing max large packet size to 50MB");
+ PayloadTypeRegistry.playS2C().modifyLargePayloadMaxSize(LargePayload.ID, DATA_SIZE_2 + 14);
+ PayloadTypeRegistry.playC2S().modifyLargePayloadMaxSize(LargePayload.ID, DATA_SIZE_2 + 14);
+ sender.sendPacket(new LargePayload(2, RANDOM_DATA[2]));
+ }
+
return;
}
diff --git a/fabric-networking-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/networking/client/splitter/NetworkingSplitterClientTest.java b/fabric-networking-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/networking/client/splitter/NetworkingSplitterClientTest.java
index 1ca0728f3a..310de4fe69 100644
--- a/fabric-networking-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/networking/client/splitter/NetworkingSplitterClientTest.java
+++ b/fabric-networking-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/networking/client/splitter/NetworkingSplitterClientTest.java
@@ -24,7 +24,7 @@ public class NetworkingSplitterClientTest implements ClientModInitializer {
@Override
public void onInitializeClient() {
ClientPlayNetworking.registerGlobalReceiver(NetworkingSplitterTest.LargePayload.ID, (payload, context) -> {
- NetworkingSplitterTest.validateLargePacketData(payload.index(), payload.data(), "client");
+ NetworkingSplitterTest.validateLargePacketData(payload.index(), payload.data(), "client", context.responseSender());
context.responseSender().sendPacket(payload);
});
}