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 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 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 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 syncWith(StreamCodec 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 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 registerLarge(CustomPacketPayload.Type id, StreamCodec 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 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); }); }