From 2b537d7833140ee8a0814691db8823081705e16d Mon Sep 17 00:00:00 2001 From: Dennis Ochulor Date: Sun, 4 Jan 2026 13:06:14 +0800 Subject: [PATCH 1/4] change att sync packet to only handle one attachment --- .../client/AttachmentSyncClient.java | 16 +++----- .../attachment/sync/AttachmentChange.java | 40 ------------------- .../impl/attachment/sync/AttachmentSync.java | 28 ++++++++++--- .../ClientboundAttachmentSyncPayload.java | 12 +++--- .../attachment/PlayerChunkSenderMixin.java | 3 +- 5 files changed, 36 insertions(+), 63 deletions(-) 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 4a9021da88..8648c4078d 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.clientbound.ClientboundAttachmentSyncPayload; @@ -37,16 +36,13 @@ public void onInitializeClient() { // play ClientPlayNetworking.registerGlobalReceiver( - ClientboundAttachmentSyncPayload.ID, + ClientboundAttachmentSyncPayload.TYPE, (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.getComponent()); - break; - } + try { + payload.attachment().tryApply(context.client().level); + } catch (AttachmentSyncException e) { + AttachmentEntrypoint.LOGGER.error("Error accepting attachment changes", e); + context.responseSender().disconnect(e.getComponent()); } } ); 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 c15be9eff9..db05ab1d53 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,14 @@ 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.FriendlyByteBufs; -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.clientbound.ClientboundAttachmentSyncPayload; 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( @@ -90,37 +81,6 @@ public static AttachmentChange create(AttachmentTargetInfo targetInfo, Attach return new AttachmentChange(targetInfo, type, encoded); } - public static void partitionAndSendPackets(List changes, ServerPlayer player) { - Set supported = ((SupportedAttachmentsConnection) ((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 ClientboundAttachmentSyncPayload(packetChanges)); - packetChanges.clear(); - byteSize = maxVarIntSize; - } - - packetChanges.add(change); - byteSize += size; - } - - if (!packetChanges.isEmpty()) { - ServerPlayNetworking.send(player, new ClientboundAttachmentSyncPayload(packetChanges)); - } - } - @SuppressWarnings("unchecked") @Nullable public Object decodeValue(RegistryAccess registryAccess) { 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 e219424991..1a0a47def2 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 @@ -24,6 +24,8 @@ import net.minecraft.network.Connection; 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; @@ -56,7 +58,23 @@ public static void trySync(AttachmentChange change, ServerPlayer player) { .fabric_getSupportedAttachments(); if (supported.contains(change.type().identifier())) { - ServerPlayNetworking.send(player, new ClientboundAttachmentSyncPayload(List.of(change))); + ServerPlayNetworking.send(player, new ClientboundAttachmentSyncPayload(change)); + } + } + + public static void trySync(List changes, ServerPlayer player) { + Set supported = ((SupportedAttachmentsConnection) ((ServerCommonPacketListenerImplAccessor) player.connection).getConnection()) + .fabric_getSupportedAttachments(); + + List> syncableChanges = new ArrayList<>(); + changes.forEach(change -> { + if (supported.contains(change.type().identifier())) { + syncableChanges.add(ServerPlayNetworking.createClientboundPacket(new ClientboundAttachmentSyncPayload(change))); + } + }); + + if (!syncableChanges.isEmpty()) { + ServerPlayNetworking.getSender(player).sendPacket(new ClientboundBundlePacket(syncableChanges)); } } @@ -106,7 +124,7 @@ public void onInitialize() { // Play PayloadTypeRegistry.clientboundPlay().register( - ClientboundAttachmentSyncPayload.ID, ClientboundAttachmentSyncPayload.CODEC); + ClientboundAttachmentSyncPayload.TYPE, ClientboundAttachmentSyncPayload.CODEC); ServerPlayerEvents.JOIN.register((player) -> { List changes = new ArrayList<>(); @@ -116,7 +134,7 @@ public void onInitialize() { ((AttachmentTargetImpl) player).fabric_computeInitialSyncChanges(player, changes::add); if (!changes.isEmpty()) { - AttachmentChange.partitionAndSendPackets(changes, player); + trySync(changes, player); } }); @@ -127,7 +145,7 @@ public void onInitialize() { ((AttachmentTargetImpl) destination).fabric_computeInitialSyncChanges(player, changes::add); if (!changes.isEmpty()) { - AttachmentChange.partitionAndSendPackets(changes, player); + trySync(changes, player); } }); @@ -136,7 +154,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/clientbound/ClientboundAttachmentSyncPayload.java b/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/impl/attachment/sync/clientbound/ClientboundAttachmentSyncPayload.java index c5d918e55e..6aa155b92d 100644 --- a/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/impl/attachment/sync/clientbound/ClientboundAttachmentSyncPayload.java +++ b/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/impl/attachment/sync/clientbound/ClientboundAttachmentSyncPayload.java @@ -16,26 +16,24 @@ package net.fabricmc.fabric.impl.attachment.sync.clientbound; -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 ClientboundAttachmentSyncPayload(List attachments) implements CustomPacketPayload { +public record ClientboundAttachmentSyncPayload(AttachmentChange attachment) implements CustomPacketPayload { public static final StreamCodec CODEC = StreamCodec.composite( - AttachmentChange.PACKET_CODEC.apply(ByteBufCodecs.list()), ClientboundAttachmentSyncPayload::attachments, + AttachmentChange.PACKET_CODEC, + ClientboundAttachmentSyncPayload::attachment, ClientboundAttachmentSyncPayload::new ); public static final Identifier PACKET_ID = Identifier.fromNamespaceAndPath("fabric", "attachment_sync_v1"); - public static final Type ID = new Type<>(PACKET_ID); + public static final Type TYPE = new Type<>(PACKET_ID); @Override public Type type() { - return ID; + return TYPE; } } 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 17a23f918d..02e5f50e2b 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); } } } From 109b50f5f958353586989a64a0230bc12f589b50 Mon Sep 17 00:00:00 2001 From: Dennis Ochulor Date: Fri, 16 Jan 2026 21:41:13 +0800 Subject: [PATCH 2/4] add large attachments --- .../api/attachment/v1/AttachmentRegistry.java | 12 ++++++ .../attachment/AttachmentRegistryImpl.java | 39 ++++++++++++++++++- .../impl/attachment/AttachmentTypeImpl.java | 3 +- .../attachment/sync/AttachmentChange.java | 12 +++--- .../impl/attachment/sync/AttachmentSync.java | 19 ++++++++- ...ientboundCustomPayloadPacketAccessor.java} | 6 +-- .../fabric-data-attachment-api-v1.mixins.json | 12 +++--- .../test/attachment/AttachmentTestMod.java | 18 +++++++++ .../client/gametest/SyncGametest.java | 2 + 9 files changed, 103 insertions(+), 20 deletions(-) rename fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/mixin/attachment/{ServerboundCustomPayloadPacketAccessor.java => ClientboundCustomPayloadPacketAccessor.java} (84%) 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 c6e73d3668..b87c241f23 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 streamCodec, 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 streamCodec the codec used to serialize the attachment data over the network + * @param syncPredicate an {@link AttachmentSyncPredicate} determining with which clients to synchronize data + * @param maxSyncSize 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 streamCodec, AttachmentSyncPredicate syncPredicate, int maxSyncSize); + /** * 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 e8bc32c4b9..eb50eff8cc 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 @@ -43,6 +43,7 @@ public final class AttachmentRegistryImpl { 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 maxSyncPacketSize = AttachmentSync.DEFAULT_ATTACHMENT_SYNC_PACKET_SIZE; public static void register(Identifier id, AttachmentType attachmentType) { AttachmentType existing = attachmentRegistry.put(id, attachmentType); @@ -74,6 +75,16 @@ public static AttachmentRegistry.Builder builder() { return new BuilderImpl<>(); } + public static int getMaxSyncPacketSize() { + if (maxSyncPacketSize == -1) { + throw new IllegalStateException("getMaxSyncPacketSize should only be called ONCE!"); + } + + int maxSize = maxSyncPacketSize; + maxSyncPacketSize = -1; + return maxSize; + } + public static class BuilderImpl implements AttachmentRegistry.Builder { @Nullable private Supplier defaultInitializer = null; @@ -84,6 +95,7 @@ public static class BuilderImpl implements AttachmentRegistry.Builder { @Nullable private AttachmentSyncPredicate syncPredicate = null; private boolean copyOnDeath = false; + private int maxSyncSize = -1; @Override public AttachmentRegistry.Builder persistent(Codec codec) { @@ -107,7 +119,7 @@ public AttachmentRegistry.Builder initializer(Supplier initializer) { return this; } - @Deprecated + @Override public AttachmentRegistry.Builder syncWith(StreamCodec streamCodec, AttachmentSyncPredicate syncPredicate) { Objects.requireNonNull(streamCodec, "stream codec cannot be null"); Objects.requireNonNull(syncPredicate, "sync predicate cannot be null"); @@ -117,6 +129,18 @@ public AttachmentRegistry.Builder syncWith(StreamCodec syncWith(StreamCodec streamCodec, AttachmentSyncPredicate syncPredicate, int maxSyncSize) { + if (maxSyncSize < 0) { + throw new IllegalArgumentException("maxSyncSize must be positive!"); + } + + syncWith(streamCodec, syncPredicate); + this.maxSyncSize = maxSyncSize; + + return this; + } + @Override public AttachmentType buildAndRegister(Identifier id) { Objects.requireNonNull(id, "identifier cannot be null"); @@ -130,13 +154,24 @@ public AttachmentType buildAndRegister(Identifier id) { ); } + if (maxSyncSize <= AttachmentSync.DEFAULT_MAX_DATA_SIZE) { + maxSyncSize = AttachmentSync.DEFAULT_MAX_DATA_SIZE; + } else if (maxSyncPacketSize == -1) { + throw new IllegalStateException("Large attachment " + id + " registered too late! Must be registered during mod initialization."); + } else { + int newMaxPacketSize = maxSyncSize + AttachmentSync.MAX_PADDING_SIZE_IN_BYTES; + newMaxPacketSize = newMaxPacketSize < 0 ? Integer.MAX_VALUE : newMaxPacketSize; // prevent overflow + maxSyncPacketSize = Math.max(newMaxPacketSize, maxSyncPacketSize); + } + var attachment = new AttachmentTypeImpl<>( id, defaultInitializer, persistenceCodec, streamCodec, syncPredicate, - copyOnDeath + copyOnDeath, + maxSyncSize ); register(id, attachment); return 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 b503e4b20a..0580f06de8 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,7 +34,8 @@ public record AttachmentTypeImpl( @Nullable Codec persistenceCodec, @Nullable StreamCodec streamCodec, @Nullable AttachmentSyncPredicate syncPredicate, - boolean copyOnDeath + boolean copyOnDeath, + int maxSyncSize ) implements AttachmentType { @Override public boolean isSynced() { 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 db05ab1d53..8469725c0f 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 @@ -38,7 +38,6 @@ import net.fabricmc.fabric.api.networking.v1.FriendlyByteBufs; import net.fabricmc.fabric.impl.attachment.AttachmentRegistryImpl; import net.fabricmc.fabric.impl.attachment.AttachmentTypeImpl; -import net.fabricmc.fabric.mixin.attachment.ServerboundCustomPayloadPacketAccessor; public record AttachmentChange(AttachmentTargetInfo targetInfo, AttachmentType type, byte[] data) { public static final StreamCodec PACKET_CODEC = StreamCodec.composite( @@ -50,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 registryAccess) { @@ -68,13 +65,16 @@ public static AttachmentChange create(AttachmentTargetInfo targetInfo, Attach buf.writeBoolean(false); } - byte[] encoded = buf.array(); + // buf.array() returns the backing array directly, which often contains unused space + byte[] encoded = new byte[buf.readableBytes()]; + buf.readBytes(encoded); + int maxDataSize = ((AttachmentTypeImpl) type).maxSyncSize(); - if (encoded.length > MAX_DATA_SIZE_IN_BYTES) { + if (encoded.length > maxDataSize) { throw new IllegalArgumentException("Data for attachment '%s' was too big (%d bytes, over maximum %d)".formatted( type.identifier(), encoded.length, - MAX_DATA_SIZE_IN_BYTES + maxDataSize )); } 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 1a0a47def2..e5405f7a02 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,7 +22,10 @@ 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; @@ -44,10 +47,22 @@ import net.fabricmc.fabric.impl.attachment.sync.clientbound.ClientboundAttachmentSyncPayload; import net.fabricmc.fabric.impl.attachment.sync.clientbound.ClientboundRequestAcceptedAttachmentsPayload; import net.fabricmc.fabric.impl.attachment.sync.serverbound.ServerboundAcceptedAttachmentsPayload; +import net.fabricmc.fabric.mixin.attachment.ClientboundCustomPayloadPacketAccessor; 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 MAX_PADDING_SIZE_IN_BYTES = AttachmentTargetInfo.MAX_SIZE_IN_BYTES + MAX_IDENTIFIER_SIZE; + public static final int DEFAULT_MAX_DATA_SIZE; + public static final int DEFAULT_ATTACHMENT_SYNC_PACKET_SIZE; + + static { + // ensure no splitting by default + int identifierSize = ByteBufUtil.utf8MaxBytes(ClientboundAttachmentSyncPayload.PACKET_ID.toString()); + int networkingApiPaddingSize = VarInt.getByteSize(identifierSize) + identifierSize + 5 * 2; + DEFAULT_MAX_DATA_SIZE = ClientboundCustomPayloadPacketAccessor.getMaxPayloadSize() - MAX_PADDING_SIZE_IN_BYTES - networkingApiPaddingSize; + DEFAULT_ATTACHMENT_SYNC_PACKET_SIZE = MAX_PADDING_SIZE_IN_BYTES + DEFAULT_MAX_DATA_SIZE; + } public static ServerboundAcceptedAttachmentsPayload createResponsePayload() { return new ServerboundAcceptedAttachmentsPayload(AttachmentRegistryImpl.getSyncableAttachments()); @@ -123,8 +138,8 @@ public void onInitialize() { }); // Play - PayloadTypeRegistry.clientboundPlay().register( - ClientboundAttachmentSyncPayload.TYPE, ClientboundAttachmentSyncPayload.CODEC); + PayloadTypeRegistry.clientboundPlay().registerLarge( + ClientboundAttachmentSyncPayload.TYPE, ClientboundAttachmentSyncPayload.CODEC, AttachmentRegistryImpl::getMaxSyncPacketSize); ServerPlayerEvents.JOIN.register((player) -> { List changes = new ArrayList<>(); 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/ClientboundCustomPayloadPacketAccessor.java similarity index 84% rename from fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/mixin/attachment/ServerboundCustomPayloadPacketAccessor.java rename to fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/mixin/attachment/ClientboundCustomPayloadPacketAccessor.java index c58700ad29..9f51ac0b31 100644 --- 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/ClientboundCustomPayloadPacketAccessor.java @@ -19,10 +19,10 @@ import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; -import net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket; +import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; -@Mixin(ServerboundCustomPayloadPacket.class) -public interface ServerboundCustomPayloadPacketAccessor { +@Mixin(ClientboundCustomPayloadPacket.class) +public interface ClientboundCustomPayloadPacketAccessor { @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 0eac96cefb..5d51b16d4a 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 @@ -6,17 +6,17 @@ "AttachmentTargetsMixin", "BannerBlockEntityMixin", "BlockEntityMixin", - "PlayerChunkSenderMixin", "ChunkAccessMixin", + "ClientboundCustomPayloadPacketAccessor", "ConnectionMixin", - "ServerboundCustomPayloadPacketAccessor", "EntityMixin", - "SerializableChunkDataMixin", - "ServerLevelMixin", - "VarIntAccessor", + "ImposterProtoChunkMixin", "LevelChunkMixin", "LevelMixin", - "ImposterProtoChunkMixin" + "PlayerChunkSenderMixin", + "SerializableChunkDataMixin", + "ServerLevelMixin", + "VarIntAccessor" ], "injectors": { "defaultRequire": 1 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 95558dfd3b..ee3e19c672 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 = level.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-data-attachment-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/attachment/client/gametest/SyncGametest.java b/fabric-data-attachment-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/attachment/client/gametest/SyncGametest.java index 3d47dc00ee..609e67cfc8 100644 --- a/fabric-data-attachment-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/attachment/client/gametest/SyncGametest.java +++ b/fabric-data-attachment-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/attachment/client/gametest/SyncGametest.java @@ -112,6 +112,7 @@ public void runTest(ClientGameTestContext context) { level.addFreshEntity(villager); setSyncedWithAll(villager); set(villager, AttachmentTestMod.SYNCED_WITH_TARGET); + villager.setAttached(AttachmentTestMod.SYNCED_LARGE, AttachmentTestMod.LARGE_DATA); LevelChunk originChunk = level.getChunk(0, 0); setSyncedWithAll(originChunk); @@ -153,6 +154,7 @@ public void runTest(ClientGameTestContext context) { assertHasSyncedWithAll(client.player); assertHasSynced(client.player, AttachmentTestMod.SYNCED_CREATIVE_ONLY); assertHasSynced(client.player, AttachmentTestMod.SYNCED_ITEM); + assertHasSynced(villager, AttachmentTestMod.SYNCED_LARGE); // `level` is the overworld here assertHasNotSynced(level, AttachmentTestMod.SYNCED_WITH_ALL); From 0e058d6dc2a73dcfe2f74ad15bce34d2dd30d03a Mon Sep 17 00:00:00 2001 From: Dennis Ochulor Date: Sat, 17 Jan 2026 09:58:12 +0800 Subject: [PATCH 3/4] fix gametest failure due to persistence problem --- .../fabric/test/attachment/AttachmentTestMod.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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 ee3e19c672..8413b42d54 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,7 @@ package net.fabricmc.fabric.test.attachment; +import java.util.List; import java.util.stream.LongStream; import com.mojang.serialization.Codec; @@ -98,12 +99,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( + public static final List LARGE_DATA = LongStream.generate(RandomSource.create(16554)::nextLong).limit((10 * 1024 * 1024) / 8).boxed().toList(); + 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 + .persistent(Codec.LONG.listOf()) + .syncWith(ByteBufCodecs.LONG.apply(ByteBufCodecs.list()), AttachmentSyncPredicate.all(), 10 * 1024 * 1024 + 3) // 10 MiB + int length ); @Override From e0076f6f499575a80fcd2d6d459590ccfb7c88fb Mon Sep 17 00:00:00 2001 From: Dennis Ochulor Date: Sat, 17 Jan 2026 10:03:44 +0800 Subject: [PATCH 4/4] woops fix int length from testing --- .../net/fabricmc/fabric/test/attachment/AttachmentTestMod.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8413b42d54..894ee5e7d3 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 @@ -105,7 +105,7 @@ public class AttachmentTestMod implements ModInitializer { builder -> builder .initializer(() -> LARGE_DATA) .persistent(Codec.LONG.listOf()) - .syncWith(ByteBufCodecs.LONG.apply(ByteBufCodecs.list()), AttachmentSyncPredicate.all(), 10 * 1024 * 1024 + 3) // 10 MiB + int length + .syncWith(ByteBufCodecs.LONG.apply(ByteBufCodecs.list()), AttachmentSyncPredicate.all(), 10 * 1024 * 1024 + 4) // 10 MiB + int length ); @Override