From 273e750174818f99ff2c15c1387c0d4495df4b76 Mon Sep 17 00:00:00 2001 From: Technici4n <13494793+Technici4n@users.noreply.github.com> Date: Tue, 10 Dec 2024 01:29:31 +0100 Subject: [PATCH 01/15] Attachment sync WIP --- .../server/level/ServerEntity.java.patch | 20 ++++++--- .../attachment/AttachmentInternals.java | 41 +++++++++++++++++++ .../attachment/AttachmentSyncReason.java | 7 ++++ .../neoforge/attachment/AttachmentType.java | 16 +++++++- .../attachment/IAttachmentSyncHandler.java | 13 ++++++ .../network/NetworkInitialization.java | 5 +++ .../bundle/PacketAndPayloadAcceptor.java | 31 -------------- .../handlers/ClientPayloadHandler.java | 5 +++ .../payload/SyncEntityAttachmentsPayload.java | 36 ++++++++++++++++ 9 files changed, 136 insertions(+), 38 deletions(-) create mode 100644 src/main/java/net/neoforged/neoforge/attachment/AttachmentSyncReason.java create mode 100644 src/main/java/net/neoforged/neoforge/attachment/IAttachmentSyncHandler.java delete mode 100644 src/main/java/net/neoforged/neoforge/network/bundle/PacketAndPayloadAcceptor.java create mode 100644 src/main/java/net/neoforged/neoforge/network/payload/SyncEntityAttachmentsPayload.java diff --git a/patches/net/minecraft/server/level/ServerEntity.java.patch b/patches/net/minecraft/server/level/ServerEntity.java.patch index 4f957b4d613..43c8b0d6814 100644 --- a/patches/net/minecraft/server/level/ServerEntity.java.patch +++ b/patches/net/minecraft/server/level/ServerEntity.java.patch @@ -12,7 +12,7 @@ if (mapitemsaveddata != null) { for (ServerPlayer serverplayer : this.level.players()) { mapitemsaveddata.tickCarriedBy(serverplayer, itemstack); -@@ -273,22 +_,25 @@ +@@ -273,6 +_,7 @@ public void removePairing(ServerPlayer p_8535_) { this.entity.stopSeenByPlayer(p_8535_); p_8535_.connection.send(new ClientboundRemoveEntitiesPacket(this.entity.getId())); @@ -20,23 +20,31 @@ } public void addPairing(ServerPlayer p_8542_) { - List> list = new ArrayList<>(); -- this.sendPairingData(p_8542_, list::add); -+ this.sendPairingData(p_8542_, new net.neoforged.neoforge.network.bundle.PacketAndPayloadAcceptor<>(list::add)); +@@ -280,15 +_,17 @@ + this.sendPairingData(p_8542_, list::add); p_8542_.connection.send(new ClientboundBundlePacket(list)); this.entity.startSeenByPlayer(p_8542_); + net.neoforged.neoforge.event.EventHooks.onStartEntityTracking(this.entity, p_8542_); } - public void sendPairingData(ServerPlayer p_289562_, Consumer> p_289563_) { -+ public void sendPairingData(ServerPlayer p_289562_, net.neoforged.neoforge.network.bundle.PacketAndPayloadAcceptor p_289563_) { ++ public void sendPairingData(ServerPlayer p_289562_, Consumer> p_289563_) { if (this.entity.isRemoved()) { LOGGER.warn("Fetching packet for removed entity {}", this.entity); } Packet packet = this.entity.getAddEntityPacket(this); p_289563_.accept(packet); -+ this.entity.sendPairingData(p_289562_, p_289563_::accept); ++ this.entity.sendPairingData(p_289562_, payload -> p_289563_.accept(payload.toVanillaClientbound())); if (this.trackedDataValues != null) { p_289563_.accept(new ClientboundSetEntityDataPacket(this.entity.getId(), this.trackedDataValues)); } +@@ -335,6 +_,8 @@ + if (this.entity instanceof Leashable leashable && leashable.isLeashed()) { + p_289563_.accept(new ClientboundSetEntityLinkPacket(this.entity, leashable.getLeashHolder())); + } ++ ++ net.neoforged.neoforge.attachment.AttachmentInternals.sendEntityPairingData(this.entity, p_289562_, p_289563_); + } + + public Vec3 getPositionBase() { diff --git a/src/main/java/net/neoforged/neoforge/attachment/AttachmentInternals.java b/src/main/java/net/neoforged/neoforge/attachment/AttachmentInternals.java index b4b33eacca6..9fcafee36aa 100644 --- a/src/main/java/net/neoforged/neoforge/attachment/AttachmentInternals.java +++ b/src/main/java/net/neoforged/neoforge/attachment/AttachmentInternals.java @@ -5,16 +5,25 @@ package net.neoforged.neoforge.attachment; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; import java.util.function.Predicate; import net.minecraft.core.HolderLookup; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientGamePacketListener; +import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.Entity; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.neoforge.common.extensions.IEntityExtension; +import net.neoforged.neoforge.common.util.FriendlyByteBufUtil; import net.neoforged.neoforge.event.entity.living.LivingConversionEvent; import net.neoforged.neoforge.event.entity.player.PlayerEvent; import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; +import net.neoforged.neoforge.network.payload.SyncEntityAttachmentsPayload; import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; @ApiStatus.Internal @EventBusSubscriber(modid = NeoForgeVersion.MOD_ID) @@ -63,5 +72,37 @@ public static void onLivingConvert(LivingConversionEvent.Post event) { event.getOutcome().copyAttachmentsFrom(event.getEntity(), true); } + @Nullable + private static SyncEntityAttachmentsPayload syncEntityAttachments(Entity entity, ServerPlayer to, AttachmentSyncReason reason) { + var holder = (AttachmentHolder) entity; + if (holder.attachments == null) { + return null; + } + List> syncedTypes = new ArrayList<>(); + var data = FriendlyByteBufUtil.writeCustomData(buf -> { + for (var entry : holder.attachments.entrySet()) { + AttachmentType type = entry.getKey(); + @SuppressWarnings("unchecked") + var syncHandler = (IAttachmentSyncHandler) type.syncHandler; + if (syncHandler != null) { + int indexBefore = buf.writerIndex(); + syncHandler.write(buf, entry.getValue(), to, reason); + if (indexBefore < buf.writerIndex()) { + // Actually wrote something + syncedTypes.add(type); + } + } + } + }, entity.registryAccess()); + return new SyncEntityAttachmentsPayload(entity.getId(), syncedTypes, data); + } + + public static void sendEntityPairingData(Entity entity, ServerPlayer to, Consumer> packetConsumer) { + var packet = syncEntityAttachments(entity, to, AttachmentSyncReason.NEW_ENTITY); + if (packet != null) { + packetConsumer.accept(packet.toVanillaClientbound()); + } + } + private AttachmentInternals() {} } diff --git a/src/main/java/net/neoforged/neoforge/attachment/AttachmentSyncReason.java b/src/main/java/net/neoforged/neoforge/attachment/AttachmentSyncReason.java new file mode 100644 index 00000000000..f920e8f76b3 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/attachment/AttachmentSyncReason.java @@ -0,0 +1,7 @@ +package net.neoforged.neoforge.attachment; + +public enum AttachmentSyncReason { + // Might not actually be new on the server, but it is now for the client + NEW_ENTITY, + ENTITY_SYNC_REQUESTED, +} diff --git a/src/main/java/net/neoforged/neoforge/attachment/AttachmentType.java b/src/main/java/net/neoforged/neoforge/attachment/AttachmentType.java index f7994a8cc7c..a5d472e3832 100644 --- a/src/main/java/net/neoforged/neoforge/attachment/AttachmentType.java +++ b/src/main/java/net/neoforged/neoforge/attachment/AttachmentType.java @@ -51,7 +51,7 @@ * *

{@link ChunkAccess}-exclusive behavior:

*
    - *
  • Modifications to attachments should be followed by a call to {@link ChunkAccess#setUnsaved(boolean)}.
  • + *
  • Modifications to attachments should be followed by a call to {@link ChunkAccess#markUnsaved}.
  • *
  • Serializable attachments are copied from a {@link ProtoChunk} to a {@link LevelChunk} on promotion.
  • *
*/ @@ -61,12 +61,15 @@ public final class AttachmentType { final IAttachmentSerializer serializer; final boolean copyOnDeath; final IAttachmentCopyHandler copyHandler; + @Nullable + IAttachmentSyncHandler syncHandler; private AttachmentType(Builder builder) { this.defaultValueSupplier = builder.defaultValueSupplier; this.serializer = builder.serializer; this.copyOnDeath = builder.copyOnDeath; this.copyHandler = builder.copyHandler != null ? builder.copyHandler : defaultCopyHandler(serializer); + this.syncHandler = builder.syncHandler; } private static IAttachmentCopyHandler defaultCopyHandler(@Nullable IAttachmentSerializer serializer) { @@ -152,6 +155,8 @@ public static class Builder { private boolean copyOnDeath; @Nullable private IAttachmentCopyHandler copyHandler; + @Nullable + private IAttachmentSyncHandler syncHandler; private Builder(Function defaultValueSupplier) { this.defaultValueSupplier = defaultValueSupplier; @@ -239,6 +244,15 @@ public Builder copyHandler(IAttachmentCopyHandler cloner) { return this; } + /** + * Requests that this attachment be synced to clients. + */ + public Builder syncHandler(IAttachmentSyncHandler syncHandler) { + Objects.requireNonNull(syncHandler); + this.syncHandler = syncHandler; + return this; + } + public AttachmentType build() { return new AttachmentType<>(this); } diff --git a/src/main/java/net/neoforged/neoforge/attachment/IAttachmentSyncHandler.java b/src/main/java/net/neoforged/neoforge/attachment/IAttachmentSyncHandler.java new file mode 100644 index 00000000000..1335904d874 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/attachment/IAttachmentSyncHandler.java @@ -0,0 +1,13 @@ +package net.neoforged.neoforge.attachment; + +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.Nullable; + +public interface IAttachmentSyncHandler { + // TODO: pass target player + void write(RegistryFriendlyByteBuf buf, T attachment, ServerPlayer to, AttachmentSyncReason reason); + + @Nullable + T read(IAttachmentHolder holder, RegistryFriendlyByteBuf buf); +} diff --git a/src/main/java/net/neoforged/neoforge/network/NetworkInitialization.java b/src/main/java/net/neoforged/neoforge/network/NetworkInitialization.java index 699ece2487a..48c38cbc993 100644 --- a/src/main/java/net/neoforged/neoforge/network/NetworkInitialization.java +++ b/src/main/java/net/neoforged/neoforge/network/NetworkInitialization.java @@ -30,6 +30,7 @@ import net.neoforged.neoforge.network.payload.KnownRegistryDataMapsPayload; import net.neoforged.neoforge.network.payload.KnownRegistryDataMapsReplyPayload; import net.neoforged.neoforge.network.payload.RegistryDataMapSyncPayload; +import net.neoforged.neoforge.network.payload.SyncEntityAttachmentsPayload; import net.neoforged.neoforge.network.registration.PayloadRegistrar; import net.neoforged.neoforge.registries.ClientRegistryManager; import net.neoforged.neoforge.registries.RegistryManager; @@ -105,6 +106,10 @@ private static void register(final RegisterPayloadHandlersEvent event) { .playToClient( ClientboundCustomSetTimePayload.TYPE, ClientboundCustomSetTimePayload.STREAM_CODEC, + ClientPayloadHandler::handle) + .playToClient( + SyncEntityAttachmentsPayload.TYPE, + SyncEntityAttachmentsPayload.STREAM_CODEC, ClientPayloadHandler::handle); } } diff --git a/src/main/java/net/neoforged/neoforge/network/bundle/PacketAndPayloadAcceptor.java b/src/main/java/net/neoforged/neoforge/network/bundle/PacketAndPayloadAcceptor.java deleted file mode 100644 index d68959fee75..00000000000 --- a/src/main/java/net/neoforged/neoforge/network/bundle/PacketAndPayloadAcceptor.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) NeoForged and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.network.bundle; - -import java.util.function.Consumer; -import net.minecraft.network.protocol.Packet; -import net.minecraft.network.protocol.common.ClientCommonPacketListener; -import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; -import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -import org.jetbrains.annotations.ApiStatus; - -@ApiStatus.Internal -public class PacketAndPayloadAcceptor { - private final Consumer> consumer; - - public PacketAndPayloadAcceptor(Consumer> consumer) { - this.consumer = consumer; - } - - public PacketAndPayloadAcceptor accept(Packet packet) { - consumer.accept(packet); - return this; - } - - public PacketAndPayloadAcceptor accept(CustomPacketPayload payload) { - return accept(new ClientboundCustomPayloadPacket(payload)); - } -} diff --git a/src/main/java/net/neoforged/neoforge/network/handlers/ClientPayloadHandler.java b/src/main/java/net/neoforged/neoforge/network/handlers/ClientPayloadHandler.java index efd17ee853c..fedc12f8c02 100644 --- a/src/main/java/net/neoforged/neoforge/network/handlers/ClientPayloadHandler.java +++ b/src/main/java/net/neoforged/neoforge/network/handlers/ClientPayloadHandler.java @@ -38,6 +38,7 @@ import net.neoforged.neoforge.network.payload.FrozenRegistryPayload; import net.neoforged.neoforge.network.payload.FrozenRegistrySyncCompletedPayload; import net.neoforged.neoforge.network.payload.FrozenRegistrySyncStartPayload; +import net.neoforged.neoforge.network.payload.SyncEntityAttachmentsPayload; import net.neoforged.neoforge.registries.RegistryManager; import net.neoforged.neoforge.registries.RegistrySnapshot; import org.jetbrains.annotations.ApiStatus; @@ -155,4 +156,8 @@ public static void handle(final ClientboundCustomSetTimePayload payload, final I level.setDayTimeFraction(payload.dayTimeFraction()); level.setDayTimePerTick(payload.dayTimePerTick()); } + + public static void handle(SyncEntityAttachmentsPayload payload, IPayloadContext context) { + // TODO + } } diff --git a/src/main/java/net/neoforged/neoforge/network/payload/SyncEntityAttachmentsPayload.java b/src/main/java/net/neoforged/neoforge/network/payload/SyncEntityAttachmentsPayload.java new file mode 100644 index 00000000000..904dfd8d046 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/payload/SyncEntityAttachmentsPayload.java @@ -0,0 +1,36 @@ +package net.neoforged.neoforge.network.payload; + +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.neoforge.attachment.AttachmentType; +import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; +import net.neoforged.neoforge.network.codec.NeoForgeStreamCodecs; +import net.neoforged.neoforge.registries.NeoForgeRegistries; +import org.jetbrains.annotations.ApiStatus; + +import java.util.List; + +@ApiStatus.Internal +public record SyncEntityAttachmentsPayload( + int entity, + List> types, + byte[] syncPayload) + implements CustomPacketPayload { + public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(NeoForgeVersion.MOD_ID, "sync_entity_attachments"));; + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.VAR_INT, + SyncEntityAttachmentsPayload::entity, + ByteBufCodecs.registry(NeoForgeRegistries.Keys.ATTACHMENT_TYPES).apply(ByteBufCodecs.list()), + SyncEntityAttachmentsPayload::types, + NeoForgeStreamCodecs.UNBOUNDED_BYTE_ARRAY, + SyncEntityAttachmentsPayload::syncPayload, + SyncEntityAttachmentsPayload::new); + + @Override + public Type type() { + return TYPE; + } +} From 3f2d4c2349fd3c7dbd46b9c4cc32f0b881b31d5a Mon Sep 17 00:00:00 2001 From: Technici4n <13494793+Technici4n@users.noreply.github.com> Date: Fri, 13 Dec 2024 21:14:19 +0100 Subject: [PATCH 02/15] WIP 2 --- .../neoforge/attachment/AttachmentType.java | 15 ++------------- .../neoforge/attachment/IAttachmentHolder.java | 1 + .../neoforge/oldtest/AttachmentSyncTest.java | 15 +++++++++++++++ 3 files changed, 18 insertions(+), 13 deletions(-) create mode 100644 tests/src/main/java/net/neoforged/neoforge/oldtest/AttachmentSyncTest.java diff --git a/src/main/java/net/neoforged/neoforge/attachment/AttachmentType.java b/src/main/java/net/neoforged/neoforge/attachment/AttachmentType.java index a5d472e3832..cd226a3af9d 100644 --- a/src/main/java/net/neoforged/neoforge/attachment/AttachmentType.java +++ b/src/main/java/net/neoforged/neoforge/attachment/AttachmentType.java @@ -15,7 +15,6 @@ import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.Tag; import net.minecraft.world.entity.Entity; -import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.chunk.ChunkAccess; @@ -39,12 +38,6 @@ *
  • Serializable entity attachments are not copied on death by default (but they are copied when returning from the end).
  • *
  • Serializable entity attachments can opt into copying on death via {@link Builder#copyOnDeath()}.
  • * - *

    {@link ItemStack}-exclusive behavior:

    - *
      - *
    • Serializable item stack attachments are synced between the server and the client.
    • - *
    • Serializable item stack attachments are copied when an item stack is copied.
    • - *
    • Serializable item stack attachments must match for item stack comparison to succeed.
    • - *
    *

    {@link Level}-exclusive behavior:

    *
      *
    • (nothing)
    • @@ -179,9 +172,6 @@ public Builder serialize(IAttachmentSerializer serializer) { /** * Requests that this attachment be persisted to disk (on the logical server side), using a {@link Codec}. * - *

      Using a {@link Codec} to serialize attachments is discouraged for item stack attachments, - * for performance reasons. Prefer one of the other options. - * *

      Codec-based attachments cannot capture a reference to their holder. * * @param codec The codec to use. @@ -193,9 +183,6 @@ public Builder serialize(Codec codec) { /** * Requests that this attachment be persisted to disk (on the logical server side), using a {@link Codec}. * - *

      Using a {@link Codec} to serialize attachments is discouraged for item stack attachments, - * for performance reasons. Prefer one of the other options. - * *

      Codec-based attachments cannot capture a reference to their holder. * * @param codec The codec to use. @@ -253,6 +240,8 @@ public Builder syncHandler(IAttachmentSyncHandler syncHandler) { return this; } + // TODO: StreamCodec-based sync handler + public AttachmentType build() { return new AttachmentType<>(this); } diff --git a/src/main/java/net/neoforged/neoforge/attachment/IAttachmentHolder.java b/src/main/java/net/neoforged/neoforge/attachment/IAttachmentHolder.java index 11797e3c9ba..9e328255da9 100644 --- a/src/main/java/net/neoforged/neoforge/attachment/IAttachmentHolder.java +++ b/src/main/java/net/neoforged/neoforge/attachment/IAttachmentHolder.java @@ -12,6 +12,7 @@ /** * An object that can hold data attachments. */ +// TODO: needs a method to force re-sync an attachment public interface IAttachmentHolder { /** * Returns {@code true} if there is any data attachments, {@code false} otherwise. diff --git a/tests/src/main/java/net/neoforged/neoforge/oldtest/AttachmentSyncTest.java b/tests/src/main/java/net/neoforged/neoforge/oldtest/AttachmentSyncTest.java new file mode 100644 index 00000000000..19d4cfb3db7 --- /dev/null +++ b/tests/src/main/java/net/neoforged/neoforge/oldtest/AttachmentSyncTest.java @@ -0,0 +1,15 @@ +package net.neoforged.neoforge.oldtest; + +import net.neoforged.bus.api.IEventBus; +import net.neoforged.fml.common.Mod; + +@Mod(AttachmentSyncTest.MOD_ID) +public class AttachmentSyncTest { + public static final String MOD_ID = "attachment_sync_test"; + + public AttachmentSyncTest(IEventBus modBus) { + + } + + +} From 869fe827698d087ff8899acd9967d4cf3d26722b Mon Sep 17 00:00:00 2001 From: Technici4n <13494793+Technici4n@users.noreply.github.com> Date: Sat, 4 Jan 2025 19:23:46 +0100 Subject: [PATCH 03/15] Entity POC --- .../minecraft/world/entity/Entity.java.patch | 6 +- .../attachment/AttachmentInternals.java | 54 ++++++++ .../handlers/ClientPayloadHandler.java | 8 +- .../registries/NeoForgeRegistries.java | 3 +- .../neoforge/oldtest/AttachmentSyncTest.java | 121 ++++++++++++++++++ .../resources/META-INF/neoforge.mods.toml | 2 + 6 files changed, 190 insertions(+), 4 deletions(-) diff --git a/patches/net/minecraft/world/entity/Entity.java.patch b/patches/net/minecraft/world/entity/Entity.java.patch index 35d70c49b2f..689f0c741cf 100644 --- a/patches/net/minecraft/world/entity/Entity.java.patch +++ b/patches/net/minecraft/world/entity/Entity.java.patch @@ -438,7 +438,7 @@ } public void checkDespawn() { -@@ -3627,6 +_,128 @@ +@@ -3627,6 +_,130 @@ public boolean mayInteract(ServerLevel p_376870_, BlockPos p_146844_) { return true; @@ -551,8 +551,10 @@ + @Override + @Nullable + public final T setData(net.neoforged.neoforge.attachment.AttachmentType type, T data) { ++ T previousData = super.setData(type, data); ++ net.neoforged.neoforge.attachment.AttachmentInternals.syncEntityAttachment(this, type, data, net.neoforged.neoforge.attachment.AttachmentSyncReason.ENTITY_SYNC_REQUESTED); + // Entities are always saved, no setChanged() call is necessary. -+ return super.setData(type, data); ++ return previousData; + } + + // Neo: Hookup Capabilities getters to entities diff --git a/src/main/java/net/neoforged/neoforge/attachment/AttachmentInternals.java b/src/main/java/net/neoforged/neoforge/attachment/AttachmentInternals.java index 9fcafee36aa..c7bc8c6130a 100644 --- a/src/main/java/net/neoforged/neoforge/attachment/AttachmentInternals.java +++ b/src/main/java/net/neoforged/neoforge/attachment/AttachmentInternals.java @@ -9,9 +9,14 @@ import java.util.List; import java.util.function.Consumer; import java.util.function.Predicate; + +import io.netty.buffer.Unpooled; import net.minecraft.core.HolderLookup; +import net.minecraft.core.RegistryAccess; +import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.game.ClientGamePacketListener; +import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.Entity; import net.neoforged.bus.api.SubscribeEvent; @@ -21,7 +26,10 @@ import net.neoforged.neoforge.event.entity.living.LivingConversionEvent; import net.neoforged.neoforge.event.entity.player.PlayerEvent; import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; +import net.neoforged.neoforge.network.PacketDistributor; +import net.neoforged.neoforge.network.connection.ConnectionType; import net.neoforged.neoforge.network.payload.SyncEntityAttachmentsPayload; +import net.neoforged.neoforge.registries.NeoForgeRegistries; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; @@ -72,6 +80,26 @@ public static void onLivingConvert(LivingConversionEvent.Post event) { event.getOutcome().copyAttachmentsFrom(event.getEntity(), true); } + public static void syncEntityAttachment(Entity entity, AttachmentType type, T value, AttachmentSyncReason reason) { + if (type.syncHandler == null || !(entity.level() instanceof ServerLevel serverLevel)) { + return; + } + for (var player : serverLevel.players()) { + List> syncedTypes = new ArrayList<>(1); + var data = FriendlyByteBufUtil.writeCustomData(buf -> { + int indexBefore = buf.writerIndex(); + type.syncHandler.write(buf, value, player, reason); + if (indexBefore < buf.writerIndex()) { + // Actually wrote something + syncedTypes.add(type); + } + }, entity.registryAccess()); + if (!syncedTypes.isEmpty()) { + PacketDistributor.sendToPlayer(player, new SyncEntityAttachmentsPayload(entity.getId(), syncedTypes, data)); + } + } + } + @Nullable private static SyncEntityAttachmentsPayload syncEntityAttachments(Entity entity, ServerPlayer to, AttachmentSyncReason reason) { var holder = (AttachmentHolder) entity; @@ -104,5 +132,31 @@ public static void sendEntityPairingData(Entity entity, ServerPlayer to, Consume } } + public static void receiveSyncedDataAttachments(AttachmentHolder holder, RegistryAccess registryAccess, List> types, byte[] bytes) { + var buf = new RegistryFriendlyByteBuf(Unpooled.wrappedBuffer(bytes), registryAccess, ConnectionType.NEOFORGE); + try { + for (var type : types) { + @SuppressWarnings("unchecked") + var syncHandler = (IAttachmentSyncHandler) type.syncHandler; + if (syncHandler == null) { + throw new IllegalArgumentException("Received synced attachment type without a sync handler registered: " + NeoForgeRegistries.ATTACHMENT_TYPES.getKey(type)); + } + // TODO: need to be careful that the right holder is passed! (when delegating!) + var result = syncHandler.read(holder.getExposedHolder(), buf); + if (result == null) { + if (holder.attachments != null) { + holder.attachments.remove(type); + } + } else { + holder.getAttachmentMap().put(type, result); + } + } + } catch (Exception exception) { + throw new RuntimeException("Encountered exception when reading synced data attachments: " + types, exception); + } finally { + buf.release(); + } + } + private AttachmentInternals() {} } diff --git a/src/main/java/net/neoforged/neoforge/network/handlers/ClientPayloadHandler.java b/src/main/java/net/neoforged/neoforge/network/handlers/ClientPayloadHandler.java index fedc12f8c02..21ff67532b2 100644 --- a/src/main/java/net/neoforged/neoforge/network/handlers/ClientPayloadHandler.java +++ b/src/main/java/net/neoforged/neoforge/network/handlers/ClientPayloadHandler.java @@ -24,6 +24,7 @@ import net.minecraft.world.entity.Entity; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.MenuType; +import net.neoforged.neoforge.attachment.AttachmentInternals; import net.neoforged.neoforge.common.world.AuxiliaryLightManager; import net.neoforged.neoforge.common.world.LevelChunkAuxiliaryLightManager; import net.neoforged.neoforge.entity.IEntityWithComplexSpawn; @@ -158,6 +159,11 @@ public static void handle(final ClientboundCustomSetTimePayload payload, final I } public static void handle(SyncEntityAttachmentsPayload payload, IPayloadContext context) { - // TODO + var entity = context.player().level().getEntity(payload.entity()); + if (entity == null) { + LOGGER.warn("Received synced attachments from unknown entity"); + } else { + AttachmentInternals.receiveSyncedDataAttachments(entity, entity.registryAccess(), payload.types(), payload.syncPayload()); + } } } diff --git a/src/main/java/net/neoforged/neoforge/registries/NeoForgeRegistries.java b/src/main/java/net/neoforged/neoforge/registries/NeoForgeRegistries.java index 3e210807966..9a51ed44225 100644 --- a/src/main/java/net/neoforged/neoforge/registries/NeoForgeRegistries.java +++ b/src/main/java/net/neoforged/neoforge/registries/NeoForgeRegistries.java @@ -40,7 +40,8 @@ public class NeoForgeRegistries { public static final Registry> INGREDIENT_TYPES = new RegistryBuilder<>(Keys.INGREDIENT_TYPES).sync(true).create(); public static final Registry> FLUID_INGREDIENT_TYPES = new RegistryBuilder<>(Keys.FLUID_INGREDIENT_TYPES).sync(true).create(); public static final Registry> CONDITION_SERIALIZERS = new RegistryBuilder<>(Keys.CONDITION_CODECS).create(); - public static final Registry> ATTACHMENT_TYPES = new RegistryBuilder<>(Keys.ATTACHMENT_TYPES).create(); + // TODO: enabling sync is potentially a breaking change, problematic for a 1.21.1 backport + public static final Registry> ATTACHMENT_TYPES = new RegistryBuilder<>(Keys.ATTACHMENT_TYPES).sync(true).create(); // Reminder: If you add a registry to NeoForge itself, remember to add it to NeoForgeRegistriesSetup#registerRegistries. diff --git a/tests/src/main/java/net/neoforged/neoforge/oldtest/AttachmentSyncTest.java b/tests/src/main/java/net/neoforged/neoforge/oldtest/AttachmentSyncTest.java index 19d4cfb3db7..466b016dd65 100644 --- a/tests/src/main/java/net/neoforged/neoforge/oldtest/AttachmentSyncTest.java +++ b/tests/src/main/java/net/neoforged/neoforge/oldtest/AttachmentSyncTest.java @@ -1,15 +1,136 @@ package net.neoforged.neoforge.oldtest; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; +import com.mojang.serialization.Codec; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.EntityArgument; +import net.minecraft.commands.arguments.ResourceArgument; +import net.minecraft.commands.arguments.coordinates.BlockPosArgument; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.ChunkPos; +import net.neoforged.api.distmarker.Dist; import net.neoforged.bus.api.IEventBus; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.fml.common.Mod; +import net.neoforged.neoforge.attachment.AttachmentSyncReason; +import net.neoforged.neoforge.attachment.AttachmentType; +import net.neoforged.neoforge.attachment.IAttachmentHolder; +import net.neoforged.neoforge.attachment.IAttachmentSyncHandler; +import net.neoforged.neoforge.client.event.RegisterClientCommandsEvent; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.RegisterCommandsEvent; +import net.neoforged.neoforge.registries.DeferredRegister; +import net.neoforged.neoforge.registries.NeoForgeRegistries; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.function.UnaryOperator; @Mod(AttachmentSyncTest.MOD_ID) public class AttachmentSyncTest { public static final String MOD_ID = "attachment_sync_test"; + private static final DeferredRegister> ATTACHMENT_TYPES = DeferredRegister.create(NeoForgeRegistries.ATTACHMENT_TYPES, MOD_ID); + private static final Supplier> ATTACHMENT_TYPE = ATTACHMENT_TYPES.register("test", + () -> AttachmentType.builder(() -> 0) + .serialize(Codec.INT) + .syncHandler(new IAttachmentSyncHandler() { + @Override + public void write(RegistryFriendlyByteBuf buf, Integer attachment, ServerPlayer to, AttachmentSyncReason reason) { + buf.writeInt(attachment); + } + + @Override + public @Nullable Integer read(IAttachmentHolder holder, RegistryFriendlyByteBuf buf) { + return buf.readInt(); + } + }) + .build()); public AttachmentSyncTest(IEventBus modBus) { + ATTACHMENT_TYPES.register(modBus); + NeoForge.EVENT_BUS.addListener(RegisterCommandsEvent.class, event -> { + registerCommands(event.getDispatcher(), "attachment_sync_test"); + }); } + @EventBusSubscriber(Dist.CLIENT) + static class ClientOnly { + @SubscribeEvent + private static void registerClientCommands(RegisterClientCommandsEvent event) { + registerCommands(event.getDispatcher(), "attachment_sync_test_client"); + } + } + private static final SimpleCommandExceptionType ERROR_NOT_A_BLOCK_ENTITY = new SimpleCommandExceptionType(Component.literal("Not a block entity")); + + private static void registerCommands(CommandDispatcher dispatcher, String commandName) { + dispatcher.register(Commands.literal(commandName) + .requires(source -> source.hasPermission(4)) + .then(Commands.literal("blockentity") + .then( + addGetSet( + Commands.argument("pos", BlockPosArgument.blockPos()), + context -> { + var pos = BlockPosArgument.getBlockPos(context, "pos"); + var blockEntity = context.getSource().getUnsidedLevel().getBlockEntity(pos); + if (blockEntity == null) { + throw ERROR_NOT_A_BLOCK_ENTITY.create(); + } + return blockEntity; + }))) + .then(Commands.literal("chunk") + .then( + addGetSet( + Commands.argument("pos", BlockPosArgument.blockPos()), + context -> { + var pos = BlockPosArgument.getBlockPos(context, "pos"); + return context.getSource().getUnsidedLevel().getChunkAt(pos); + }))) + .then(Commands.literal("entity") + .then( + addGetSet( + Commands.argument("entity", EntityArgument.entity()), + context -> EntityArgument.getEntity(context, "entity") + ))) + .then( + addGetSet( + Commands.literal("level"), + context -> context.getSource().getUnsidedLevel()))); + } + + private interface HolderFinder { + IAttachmentHolder find(CommandContext source) throws CommandSyntaxException; + } + + private static ArgumentBuilder addGetSet(ArgumentBuilder builder, HolderFinder holderFinder) { + return builder + .then(Commands.literal("get") + .executes(context -> { + var holder = holderFinder.find(context); + var data = holder.getExistingData(ATTACHMENT_TYPE).orElse(null); + context.getSource().sendSuccess(() -> Component.literal("Value of data: " + data), false); + return Command.SINGLE_SUCCESS; + })) + .then(Commands.literal("set") + .then(Commands.argument("value", IntegerArgumentType.integer()) + .executes(context -> { + var holder = holderFinder.find(context); + var data = IntegerArgumentType.getInteger(context, "value"); + var previousData = holder.setData(ATTACHMENT_TYPE, data); + context.getSource().sendSuccess(() -> Component.literal("Previous value of data: " + previousData + ". New value: " + data), false); + return Command.SINGLE_SUCCESS; + }))); + } } diff --git a/tests/src/main/resources/META-INF/neoforge.mods.toml b/tests/src/main/resources/META-INF/neoforge.mods.toml index 481bcfb8c27..5f494851df9 100644 --- a/tests/src/main/resources/META-INF/neoforge.mods.toml +++ b/tests/src/main/resources/META-INF/neoforge.mods.toml @@ -32,6 +32,8 @@ license="LGPL v2.1" [[mods]] modId="custom_preset_editor_test" +[[mods]] + modId="attachment_sync_test" [[mods]] modId="custom_break_sound_test" From ecaf2d4926d01117354560cd1a079727fb218b0d Mon Sep 17 00:00:00 2001 From: Technici4n <13494793+Technici4n@users.noreply.github.com> Date: Sat, 4 Jan 2025 19:45:21 +0100 Subject: [PATCH 04/15] StreamCodec helper --- .../neoforge/attachment/AttachmentType.java | 21 +++++++++++++++++-- .../attachment/IAttachmentSyncHandler.java | 1 + .../neoforge/oldtest/AttachmentSyncTest.java | 7 ++----- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/main/java/net/neoforged/neoforge/attachment/AttachmentType.java b/src/main/java/net/neoforged/neoforge/attachment/AttachmentType.java index cd226a3af9d..bb41052f736 100644 --- a/src/main/java/net/neoforged/neoforge/attachment/AttachmentType.java +++ b/src/main/java/net/neoforged/neoforge/attachment/AttachmentType.java @@ -14,6 +14,9 @@ import net.minecraft.core.HolderLookup; import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.Tag; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; @@ -234,13 +237,27 @@ public Builder copyHandler(IAttachmentCopyHandler cloner) { /** * Requests that this attachment be synced to clients. */ - public Builder syncHandler(IAttachmentSyncHandler syncHandler) { + public Builder sync(IAttachmentSyncHandler syncHandler) { Objects.requireNonNull(syncHandler); this.syncHandler = syncHandler; return this; } - // TODO: StreamCodec-based sync handler + // TODO: Predicate version too? Some data is not relevant to other players. + public Builder sync(StreamCodec streamCodec) { + Objects.requireNonNull(streamCodec); + return sync(new IAttachmentSyncHandler<>() { + @Override + public void write(RegistryFriendlyByteBuf buf, T attachment, ServerPlayer to, AttachmentSyncReason reason) { + streamCodec.encode(buf, attachment); + } + + @Override + public T read(IAttachmentHolder holder, RegistryFriendlyByteBuf buf) { + return streamCodec.decode(buf); + } + }); + } public AttachmentType build() { return new AttachmentType<>(this); diff --git a/src/main/java/net/neoforged/neoforge/attachment/IAttachmentSyncHandler.java b/src/main/java/net/neoforged/neoforge/attachment/IAttachmentSyncHandler.java index 1335904d874..ef99f045935 100644 --- a/src/main/java/net/neoforged/neoforge/attachment/IAttachmentSyncHandler.java +++ b/src/main/java/net/neoforged/neoforge/attachment/IAttachmentSyncHandler.java @@ -8,6 +8,7 @@ public interface IAttachmentSyncHandler { // TODO: pass target player void write(RegistryFriendlyByteBuf buf, T attachment, ServerPlayer to, AttachmentSyncReason reason); + // TODO: we could also return void and let the sync handler call .setData(type, xxx). But that means passing the type somehow. @Nullable T read(IAttachmentHolder holder, RegistryFriendlyByteBuf buf); } diff --git a/tests/src/main/java/net/neoforged/neoforge/oldtest/AttachmentSyncTest.java b/tests/src/main/java/net/neoforged/neoforge/oldtest/AttachmentSyncTest.java index 466b016dd65..5d74047127f 100644 --- a/tests/src/main/java/net/neoforged/neoforge/oldtest/AttachmentSyncTest.java +++ b/tests/src/main/java/net/neoforged/neoforge/oldtest/AttachmentSyncTest.java @@ -11,12 +11,10 @@ import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; import net.minecraft.commands.arguments.EntityArgument; -import net.minecraft.commands.arguments.ResourceArgument; import net.minecraft.commands.arguments.coordinates.BlockPosArgument; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.level.ChunkPos; import net.neoforged.api.distmarker.Dist; import net.neoforged.bus.api.IEventBus; import net.neoforged.bus.api.SubscribeEvent; @@ -33,9 +31,7 @@ import net.neoforged.neoforge.registries.NeoForgeRegistries; import org.jetbrains.annotations.Nullable; -import java.util.function.Function; import java.util.function.Supplier; -import java.util.function.UnaryOperator; @Mod(AttachmentSyncTest.MOD_ID) public class AttachmentSyncTest { @@ -44,7 +40,8 @@ public class AttachmentSyncTest { private static final Supplier> ATTACHMENT_TYPE = ATTACHMENT_TYPES.register("test", () -> AttachmentType.builder(() -> 0) .serialize(Codec.INT) - .syncHandler(new IAttachmentSyncHandler() { + // TODO: use streamcodec version at some point + .sync(new IAttachmentSyncHandler() { @Override public void write(RegistryFriendlyByteBuf buf, Integer attachment, ServerPlayer to, AttachmentSyncReason reason) { buf.writeInt(attachment); From 9944bc44fdae2cf0d04b374c64cf7f6fa4ad8bd7 Mon Sep 17 00:00:00 2001 From: Technici4n <13494793+Technici4n@users.noreply.github.com> Date: Sun, 5 Jan 2025 09:36:32 +0100 Subject: [PATCH 05/15] Use separate internal registry for syncing --- .../attachment/AttachmentInternals.java | 23 +++++++++++++++++++ .../attachment/IAttachmentSyncHandler.java | 3 +++ .../payload/SyncEntityAttachmentsPayload.java | 3 ++- .../registries/NeoForgeRegistries.java | 3 +-- .../registries/NeoForgeRegistriesSetup.java | 3 +++ 5 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/neoforged/neoforge/attachment/AttachmentInternals.java b/src/main/java/net/neoforged/neoforge/attachment/AttachmentInternals.java index c7bc8c6130a..07ab5fd6399 100644 --- a/src/main/java/net/neoforged/neoforge/attachment/AttachmentInternals.java +++ b/src/main/java/net/neoforged/neoforge/attachment/AttachmentInternals.java @@ -12,15 +12,20 @@ import io.netty.buffer.Unpooled; import net.minecraft.core.HolderLookup; +import net.minecraft.core.Registry; import net.minecraft.core.RegistryAccess; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.game.ClientGamePacketListener; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.Entity; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.common.NeoForgeMod; import net.neoforged.neoforge.common.extensions.IEntityExtension; import net.neoforged.neoforge.common.util.FriendlyByteBufUtil; import net.neoforged.neoforge.event.entity.living.LivingConversionEvent; @@ -30,6 +35,8 @@ import net.neoforged.neoforge.network.connection.ConnectionType; import net.neoforged.neoforge.network.payload.SyncEntityAttachmentsPayload; import net.neoforged.neoforge.registries.NeoForgeRegistries; +import net.neoforged.neoforge.registries.RegistryBuilder; +import net.neoforged.neoforge.registries.callback.AddCallback; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; @@ -80,6 +87,22 @@ public static void onLivingConvert(LivingConversionEvent.Post event) { event.getOutcome().copyAttachmentsFrom(event.getEntity(), true); } + /** + * Contains all entries added to {@link NeoForgeRegistries#ATTACHMENT_TYPES} with a sync handler. + * Should never be registered against directly. + */ + public static final Registry> SYNCED_ATTACHMENT_TYPES = new RegistryBuilder<>( + ResourceKey.>createRegistryKey( + ResourceLocation.fromNamespaceAndPath(NeoForgeVersion.MOD_ID, "synced_attachment_types"))) + .sync(true) + .create(); + + public static final AddCallback> ATTACHMENT_TYPE_ADD_CALLBACK = (registry, id, key, value) -> { + if (value.syncHandler != null) { + Registry.register(SYNCED_ATTACHMENT_TYPES, key.location(), value); + } + }; + public static void syncEntityAttachment(Entity entity, AttachmentType type, T value, AttachmentSyncReason reason) { if (type.syncHandler == null || !(entity.level() instanceof ServerLevel serverLevel)) { return; diff --git a/src/main/java/net/neoforged/neoforge/attachment/IAttachmentSyncHandler.java b/src/main/java/net/neoforged/neoforge/attachment/IAttachmentSyncHandler.java index ef99f045935..f92bfea1c38 100644 --- a/src/main/java/net/neoforged/neoforge/attachment/IAttachmentSyncHandler.java +++ b/src/main/java/net/neoforged/neoforge/attachment/IAttachmentSyncHandler.java @@ -4,6 +4,9 @@ import net.minecraft.server.level.ServerPlayer; import org.jetbrains.annotations.Nullable; +/** + * Manages how data attachments are written (on the server) and read (on the client) from packets. + */ public interface IAttachmentSyncHandler { // TODO: pass target player void write(RegistryFriendlyByteBuf buf, T attachment, ServerPlayer to, AttachmentSyncReason reason); diff --git a/src/main/java/net/neoforged/neoforge/network/payload/SyncEntityAttachmentsPayload.java b/src/main/java/net/neoforged/neoforge/network/payload/SyncEntityAttachmentsPayload.java index 904dfd8d046..b740a515ab2 100644 --- a/src/main/java/net/neoforged/neoforge/network/payload/SyncEntityAttachmentsPayload.java +++ b/src/main/java/net/neoforged/neoforge/network/payload/SyncEntityAttachmentsPayload.java @@ -5,6 +5,7 @@ import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; import net.minecraft.resources.ResourceLocation; +import net.neoforged.neoforge.attachment.AttachmentInternals; import net.neoforged.neoforge.attachment.AttachmentType; import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; import net.neoforged.neoforge.network.codec.NeoForgeStreamCodecs; @@ -23,7 +24,7 @@ public record SyncEntityAttachmentsPayload( public static final StreamCodec STREAM_CODEC = StreamCodec.composite( ByteBufCodecs.VAR_INT, SyncEntityAttachmentsPayload::entity, - ByteBufCodecs.registry(NeoForgeRegistries.Keys.ATTACHMENT_TYPES).apply(ByteBufCodecs.list()), + ByteBufCodecs.registry(AttachmentInternals.SYNCED_ATTACHMENT_TYPES.key()).apply(ByteBufCodecs.list()), SyncEntityAttachmentsPayload::types, NeoForgeStreamCodecs.UNBOUNDED_BYTE_ARRAY, SyncEntityAttachmentsPayload::syncPayload, diff --git a/src/main/java/net/neoforged/neoforge/registries/NeoForgeRegistries.java b/src/main/java/net/neoforged/neoforge/registries/NeoForgeRegistries.java index 9a51ed44225..3e210807966 100644 --- a/src/main/java/net/neoforged/neoforge/registries/NeoForgeRegistries.java +++ b/src/main/java/net/neoforged/neoforge/registries/NeoForgeRegistries.java @@ -40,8 +40,7 @@ public class NeoForgeRegistries { public static final Registry> INGREDIENT_TYPES = new RegistryBuilder<>(Keys.INGREDIENT_TYPES).sync(true).create(); public static final Registry> FLUID_INGREDIENT_TYPES = new RegistryBuilder<>(Keys.FLUID_INGREDIENT_TYPES).sync(true).create(); public static final Registry> CONDITION_SERIALIZERS = new RegistryBuilder<>(Keys.CONDITION_CODECS).create(); - // TODO: enabling sync is potentially a breaking change, problematic for a 1.21.1 backport - public static final Registry> ATTACHMENT_TYPES = new RegistryBuilder<>(Keys.ATTACHMENT_TYPES).sync(true).create(); + public static final Registry> ATTACHMENT_TYPES = new RegistryBuilder<>(Keys.ATTACHMENT_TYPES).create(); // Reminder: If you add a registry to NeoForge itself, remember to add it to NeoForgeRegistriesSetup#registerRegistries. diff --git a/src/main/java/net/neoforged/neoforge/registries/NeoForgeRegistriesSetup.java b/src/main/java/net/neoforged/neoforge/registries/NeoForgeRegistriesSetup.java index bd505e9fe42..556b22ba262 100644 --- a/src/main/java/net/neoforged/neoforge/registries/NeoForgeRegistriesSetup.java +++ b/src/main/java/net/neoforged/neoforge/registries/NeoForgeRegistriesSetup.java @@ -9,6 +9,7 @@ import net.minecraft.core.Registry; import net.minecraft.core.registries.BuiltInRegistries; import net.neoforged.bus.api.IEventBus; +import net.neoforged.neoforge.attachment.AttachmentInternals; import org.jetbrains.annotations.ApiStatus; @ApiStatus.Internal @@ -71,6 +72,7 @@ private static void registerRegistries(NewRegistryEvent event) { event.register(NeoForgeRegistries.FLUID_INGREDIENT_TYPES); event.register(NeoForgeRegistries.CONDITION_SERIALIZERS); event.register(NeoForgeRegistries.ATTACHMENT_TYPES); + event.register(AttachmentInternals.SYNCED_ATTACHMENT_TYPES); } private static void modifyRegistries(ModifyRegistriesEvent event) { @@ -82,5 +84,6 @@ private static void modifyRegistries(ModifyRegistriesEvent event) { BuiltInRegistries.ITEM.addCallback(NeoForgeRegistryCallbacks.ItemCallbacks.INSTANCE); BuiltInRegistries.ATTRIBUTE.addCallback(NeoForgeRegistryCallbacks.AttributeCallbacks.INSTANCE); BuiltInRegistries.POINT_OF_INTEREST_TYPE.addCallback(NeoForgeRegistryCallbacks.PoiTypeCallbacks.INSTANCE); + NeoForgeRegistries.ATTACHMENT_TYPES.addCallback(AttachmentInternals.ATTACHMENT_TYPE_ADD_CALLBACK); } } From 75ad9c8d47eb175e9c7ea86c666eeeca4ef53ccc Mon Sep 17 00:00:00 2001 From: Technici4n <13494793+Technici4n@users.noreply.github.com> Date: Sun, 5 Jan 2025 09:54:30 +0100 Subject: [PATCH 06/15] Prepare for more generic sync --- .../attachment/AttachmentInternals.java | 10 ++-- .../network/NetworkInitialization.java | 6 +- .../handlers/ClientPayloadHandler.java | 18 +++--- .../payload/SyncAttachmentsPayload.java | 59 +++++++++++++++++++ .../payload/SyncEntityAttachmentsPayload.java | 37 ------------ 5 files changed, 77 insertions(+), 53 deletions(-) create mode 100644 src/main/java/net/neoforged/neoforge/network/payload/SyncAttachmentsPayload.java delete mode 100644 src/main/java/net/neoforged/neoforge/network/payload/SyncEntityAttachmentsPayload.java diff --git a/src/main/java/net/neoforged/neoforge/attachment/AttachmentInternals.java b/src/main/java/net/neoforged/neoforge/attachment/AttachmentInternals.java index 07ab5fd6399..a8dfee218a9 100644 --- a/src/main/java/net/neoforged/neoforge/attachment/AttachmentInternals.java +++ b/src/main/java/net/neoforged/neoforge/attachment/AttachmentInternals.java @@ -24,8 +24,6 @@ import net.minecraft.world.entity.Entity; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; -import net.neoforged.neoforge.common.NeoForge; -import net.neoforged.neoforge.common.NeoForgeMod; import net.neoforged.neoforge.common.extensions.IEntityExtension; import net.neoforged.neoforge.common.util.FriendlyByteBufUtil; import net.neoforged.neoforge.event.entity.living.LivingConversionEvent; @@ -33,7 +31,7 @@ import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; import net.neoforged.neoforge.network.PacketDistributor; import net.neoforged.neoforge.network.connection.ConnectionType; -import net.neoforged.neoforge.network.payload.SyncEntityAttachmentsPayload; +import net.neoforged.neoforge.network.payload.SyncAttachmentsPayload; import net.neoforged.neoforge.registries.NeoForgeRegistries; import net.neoforged.neoforge.registries.RegistryBuilder; import net.neoforged.neoforge.registries.callback.AddCallback; @@ -118,13 +116,13 @@ public static void syncEntityAttachment(Entity entity, AttachmentType typ } }, entity.registryAccess()); if (!syncedTypes.isEmpty()) { - PacketDistributor.sendToPlayer(player, new SyncEntityAttachmentsPayload(entity.getId(), syncedTypes, data)); + PacketDistributor.sendToPlayer(player, new SyncAttachmentsPayload(new SyncAttachmentsPayload.EntityTarget(entity.getId()), syncedTypes, data)); } } } @Nullable - private static SyncEntityAttachmentsPayload syncEntityAttachments(Entity entity, ServerPlayer to, AttachmentSyncReason reason) { + private static SyncAttachmentsPayload syncEntityAttachments(Entity entity, ServerPlayer to, AttachmentSyncReason reason) { var holder = (AttachmentHolder) entity; if (holder.attachments == null) { return null; @@ -145,7 +143,7 @@ private static SyncEntityAttachmentsPayload syncEntityAttachments(Entity entity, } } }, entity.registryAccess()); - return new SyncEntityAttachmentsPayload(entity.getId(), syncedTypes, data); + return new SyncAttachmentsPayload(new SyncAttachmentsPayload.EntityTarget(entity.getId()), syncedTypes, data); } public static void sendEntityPairingData(Entity entity, ServerPlayer to, Consumer> packetConsumer) { diff --git a/src/main/java/net/neoforged/neoforge/network/NetworkInitialization.java b/src/main/java/net/neoforged/neoforge/network/NetworkInitialization.java index 48c38cbc993..4ff2ed90043 100644 --- a/src/main/java/net/neoforged/neoforge/network/NetworkInitialization.java +++ b/src/main/java/net/neoforged/neoforge/network/NetworkInitialization.java @@ -30,7 +30,7 @@ import net.neoforged.neoforge.network.payload.KnownRegistryDataMapsPayload; import net.neoforged.neoforge.network.payload.KnownRegistryDataMapsReplyPayload; import net.neoforged.neoforge.network.payload.RegistryDataMapSyncPayload; -import net.neoforged.neoforge.network.payload.SyncEntityAttachmentsPayload; +import net.neoforged.neoforge.network.payload.SyncAttachmentsPayload; import net.neoforged.neoforge.network.registration.PayloadRegistrar; import net.neoforged.neoforge.registries.ClientRegistryManager; import net.neoforged.neoforge.registries.RegistryManager; @@ -108,8 +108,8 @@ private static void register(final RegisterPayloadHandlersEvent event) { ClientboundCustomSetTimePayload.STREAM_CODEC, ClientPayloadHandler::handle) .playToClient( - SyncEntityAttachmentsPayload.TYPE, - SyncEntityAttachmentsPayload.STREAM_CODEC, + SyncAttachmentsPayload.TYPE, + SyncAttachmentsPayload.STREAM_CODEC, ClientPayloadHandler::handle); } } diff --git a/src/main/java/net/neoforged/neoforge/network/handlers/ClientPayloadHandler.java b/src/main/java/net/neoforged/neoforge/network/handlers/ClientPayloadHandler.java index 21ff67532b2..8260dce07ab 100644 --- a/src/main/java/net/neoforged/neoforge/network/handlers/ClientPayloadHandler.java +++ b/src/main/java/net/neoforged/neoforge/network/handlers/ClientPayloadHandler.java @@ -39,7 +39,7 @@ import net.neoforged.neoforge.network.payload.FrozenRegistryPayload; import net.neoforged.neoforge.network.payload.FrozenRegistrySyncCompletedPayload; import net.neoforged.neoforge.network.payload.FrozenRegistrySyncStartPayload; -import net.neoforged.neoforge.network.payload.SyncEntityAttachmentsPayload; +import net.neoforged.neoforge.network.payload.SyncAttachmentsPayload; import net.neoforged.neoforge.registries.RegistryManager; import net.neoforged.neoforge.registries.RegistrySnapshot; import org.jetbrains.annotations.ApiStatus; @@ -158,12 +158,16 @@ public static void handle(final ClientboundCustomSetTimePayload payload, final I level.setDayTimePerTick(payload.dayTimePerTick()); } - public static void handle(SyncEntityAttachmentsPayload payload, IPayloadContext context) { - var entity = context.player().level().getEntity(payload.entity()); - if (entity == null) { - LOGGER.warn("Received synced attachments from unknown entity"); - } else { - AttachmentInternals.receiveSyncedDataAttachments(entity, entity.registryAccess(), payload.types(), payload.syncPayload()); + public static void handle(SyncAttachmentsPayload payload, IPayloadContext context) { + switch (payload.target()) { + case SyncAttachmentsPayload.EntityTarget target -> { + var entity = context.player().level().getEntity(target.entity()); + if (entity == null) { + LOGGER.warn("Received synced attachments from unknown entity"); + } else { + AttachmentInternals.receiveSyncedDataAttachments(entity, entity.registryAccess(), payload.types(), payload.syncPayload()); + } + } } } } diff --git a/src/main/java/net/neoforged/neoforge/network/payload/SyncAttachmentsPayload.java b/src/main/java/net/neoforged/neoforge/network/payload/SyncAttachmentsPayload.java new file mode 100644 index 00000000000..bbe86c7c05d --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/payload/SyncAttachmentsPayload.java @@ -0,0 +1,59 @@ +package net.neoforged.neoforge.network.payload; + +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.neoforge.attachment.AttachmentInternals; +import net.neoforged.neoforge.attachment.AttachmentType; +import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; +import net.neoforged.neoforge.network.codec.NeoForgeStreamCodecs; +import org.jetbrains.annotations.ApiStatus; + +import java.util.List; + +@ApiStatus.Internal +public record SyncAttachmentsPayload( + Target target, + List> types, + byte[] syncPayload) + implements CustomPacketPayload { + public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(NeoForgeVersion.MOD_ID, "sync_attachments"));; + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + Target.STREAM_CODEC, + SyncAttachmentsPayload::target, + ByteBufCodecs.registry(AttachmentInternals.SYNCED_ATTACHMENT_TYPES.key()).apply(ByteBufCodecs.list()), + SyncAttachmentsPayload::types, + NeoForgeStreamCodecs.UNBOUNDED_BYTE_ARRAY, + SyncAttachmentsPayload::syncPayload, + SyncAttachmentsPayload::new); + + @Override + public Type type() { + return TYPE; + } + + public sealed interface Target { + StreamCodec STREAM_CODEC = StreamCodec.of( + (buf, target) -> { + switch (target) { + case EntityTarget entityTarget -> { + buf.writeByte(0); + buf.writeVarInt(entityTarget.entity()); + } + } + }, + buf -> { + int type = buf.readByte(); + switch (type) { + case 0 -> { + return new EntityTarget(buf.readVarInt()); + } + default -> throw new IllegalArgumentException("Unknown target type: " + type); + } + }); + } + + public record EntityTarget(int entity) implements Target {} +} diff --git a/src/main/java/net/neoforged/neoforge/network/payload/SyncEntityAttachmentsPayload.java b/src/main/java/net/neoforged/neoforge/network/payload/SyncEntityAttachmentsPayload.java deleted file mode 100644 index b740a515ab2..00000000000 --- a/src/main/java/net/neoforged/neoforge/network/payload/SyncEntityAttachmentsPayload.java +++ /dev/null @@ -1,37 +0,0 @@ -package net.neoforged.neoforge.network.payload; - -import net.minecraft.network.RegistryFriendlyByteBuf; -import net.minecraft.network.codec.ByteBufCodecs; -import net.minecraft.network.codec.StreamCodec; -import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -import net.minecraft.resources.ResourceLocation; -import net.neoforged.neoforge.attachment.AttachmentInternals; -import net.neoforged.neoforge.attachment.AttachmentType; -import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; -import net.neoforged.neoforge.network.codec.NeoForgeStreamCodecs; -import net.neoforged.neoforge.registries.NeoForgeRegistries; -import org.jetbrains.annotations.ApiStatus; - -import java.util.List; - -@ApiStatus.Internal -public record SyncEntityAttachmentsPayload( - int entity, - List> types, - byte[] syncPayload) - implements CustomPacketPayload { - public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(NeoForgeVersion.MOD_ID, "sync_entity_attachments"));; - public static final StreamCodec STREAM_CODEC = StreamCodec.composite( - ByteBufCodecs.VAR_INT, - SyncEntityAttachmentsPayload::entity, - ByteBufCodecs.registry(AttachmentInternals.SYNCED_ATTACHMENT_TYPES.key()).apply(ByteBufCodecs.list()), - SyncEntityAttachmentsPayload::types, - NeoForgeStreamCodecs.UNBOUNDED_BYTE_ARRAY, - SyncEntityAttachmentsPayload::syncPayload, - SyncEntityAttachmentsPayload::new); - - @Override - public Type type() { - return TYPE; - } -} From 94ecb064a3437b7757c7d8fc143df4ad915d4823 Mon Sep 17 00:00:00 2001 From: Technici4n <13494793+Technici4n@users.noreply.github.com> Date: Sun, 5 Jan 2025 14:02:37 +0100 Subject: [PATCH 07/15] Initial support for BE, chunk and level incremental attachment syncing --- .../server/level/ServerLevel.java.patch | 7 ++- .../minecraft/world/entity/Entity.java.patch | 13 ++++-- .../level/block/entity/BlockEntity.java.patch | 17 ++++++- .../world/level/chunk/ChunkAccess.java.patch | 7 ++- .../world/level/chunk/LevelChunk.java.patch | 9 +++- .../neoforge/attachment/AttachmentHolder.java | 9 +++- .../attachment/AttachmentInternals.java | 44 ++++++++++++++----- .../attachment/IAttachmentHolder.java | 20 +++++++++ .../attachment/IAttachmentSyncHandler.java | 13 ++++++ .../handlers/ClientPayloadHandler.java | 21 +++++++++ .../payload/SyncAttachmentsPayload.java | 33 +++++++++++++- 11 files changed, 169 insertions(+), 24 deletions(-) diff --git a/patches/net/minecraft/server/level/ServerLevel.java.patch b/patches/net/minecraft/server/level/ServerLevel.java.patch index bce6a22dcce..f45d6758c3b 100644 --- a/patches/net/minecraft/server/level/ServerLevel.java.patch +++ b/patches/net/minecraft/server/level/ServerLevel.java.patch @@ -242,7 +242,7 @@ ServerLevel.this.dragonParts.put(enderdragonpart.getId(), enderdragonpart); } } -@@ -1783,24 +_,101 @@ +@@ -1783,24 +_,106 @@ if (ServerLevel.this.isUpdatingNavigations) { String s = "onTrackingStart called during navigation iteration"; Util.logAndPauseIfInIde( @@ -273,6 +273,11 @@ } } + ++ @Override ++ public final void syncData(net.neoforged.neoforge.attachment.AttachmentType type) { ++ net.neoforged.neoforge.attachment.AttachmentInternals.syncAttachmentUpdate(this, this, type); ++ } ++ + private final net.neoforged.neoforge.capabilities.CapabilityListenerHolder capListenerHolder = new net.neoforged.neoforge.capabilities.CapabilityListenerHolder(); + + @Override diff --git a/patches/net/minecraft/world/entity/Entity.java.patch b/patches/net/minecraft/world/entity/Entity.java.patch index 689f0c741cf..590e29bc27a 100644 --- a/patches/net/minecraft/world/entity/Entity.java.patch +++ b/patches/net/minecraft/world/entity/Entity.java.patch @@ -438,7 +438,7 @@ } public void checkDespawn() { -@@ -3627,6 +_,130 @@ +@@ -3627,6 +_,135 @@ public boolean mayInteract(ServerLevel p_376870_, BlockPos p_146844_) { return true; @@ -551,10 +551,15 @@ + @Override + @Nullable + public final T setData(net.neoforged.neoforge.attachment.AttachmentType type, T data) { -+ T previousData = super.setData(type, data); -+ net.neoforged.neoforge.attachment.AttachmentInternals.syncEntityAttachment(this, type, data, net.neoforged.neoforge.attachment.AttachmentSyncReason.ENTITY_SYNC_REQUESTED); + // Entities are always saved, no setChanged() call is necessary. -+ return previousData; ++ return super.setData(type, data); ++ } ++ ++ @Override ++ public final void syncData(net.neoforged.neoforge.attachment.AttachmentType type) { ++ if (level instanceof ServerLevel serverLevel) { ++ net.neoforged.neoforge.attachment.AttachmentInternals.syncAttachmentUpdate(this, serverLevel, type); ++ } + } + + // Neo: Hookup Capabilities getters to entities diff --git a/patches/net/minecraft/world/level/block/entity/BlockEntity.java.patch b/patches/net/minecraft/world/level/block/entity/BlockEntity.java.patch index c2a70f7e4a1..0ec2897c2e3 100644 --- a/patches/net/minecraft/world/level/block/entity/BlockEntity.java.patch +++ b/patches/net/minecraft/world/level/block/entity/BlockEntity.java.patch @@ -1,6 +1,12 @@ --- a/net/minecraft/world/level/block/entity/BlockEntity.java +++ b/net/minecraft/world/level/block/entity/BlockEntity.java -@@ -26,8 +_,9 @@ +@@ -21,13 +_,15 @@ + import net.minecraft.network.protocol.Packet; + import net.minecraft.network.protocol.game.ClientGamePacketListener; + import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.level.ServerLevel; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.level.Level; import net.minecraft.world.level.block.state.BlockState; import org.slf4j.Logger; @@ -63,7 +69,7 @@ } public boolean triggerEvent(int p_58889_, int p_58890_) { -@@ -234,6 +_,27 @@ +@@ -234,6 +_,34 @@ return this.type; } @@ -87,6 +93,13 @@ + setChanged(); + return super.removeData(type); + } ++ ++ @Override ++ public final void syncData(net.neoforged.neoforge.attachment.AttachmentType type) { ++ if (level instanceof ServerLevel serverLevel) { ++ net.neoforged.neoforge.attachment.AttachmentInternals.syncAttachmentUpdate(this, serverLevel, type); ++ } ++ } + @Deprecated public void setBlockState(BlockState p_155251_) { diff --git a/patches/net/minecraft/world/level/chunk/ChunkAccess.java.patch b/patches/net/minecraft/world/level/chunk/ChunkAccess.java.patch index 303f56095aa..46f1a409f7e 100644 --- a/patches/net/minecraft/world/level/chunk/ChunkAccess.java.patch +++ b/patches/net/minecraft/world/level/chunk/ChunkAccess.java.patch @@ -42,7 +42,7 @@ } } } -@@ -485,6 +_,74 @@ +@@ -485,6 +_,79 @@ public ChunkSkyLightSources getSkyLightSources() { return this.skyLightSources; } @@ -107,6 +107,11 @@ + } + + @org.jetbrains.annotations.ApiStatus.Internal ++ public final void receiveSyncedAttachments(List> types, byte[] bytes) { ++ net.neoforged.neoforge.attachment.AttachmentInternals.receiveSyncedDataAttachments(getAttachmentHolder(), getLevel().registryAccess(), types, bytes); ++ } ++ ++ @org.jetbrains.annotations.ApiStatus.Internal + protected net.neoforged.neoforge.attachment.AttachmentHolder.AsField getAttachmentHolder() { + return attachmentHolder; + } diff --git a/patches/net/minecraft/world/level/chunk/LevelChunk.java.patch b/patches/net/minecraft/world/level/chunk/LevelChunk.java.patch index 4a9a08cd9f0..97a2830b9f5 100644 --- a/patches/net/minecraft/world/level/chunk/LevelChunk.java.patch +++ b/patches/net/minecraft/world/level/chunk/LevelChunk.java.patch @@ -120,7 +120,7 @@ this.blockEntities.values().forEach(p_187988_ -> { if (this.level instanceof ServerLevel serverlevel) { this.addGameEventListener(p_187988_, serverlevel); -@@ -694,6 +_,14 @@ +@@ -694,6 +_,21 @@ return new LevelChunk.BoundTickingBlockEntity<>(p_156376_, p_156377_); } @@ -131,6 +131,13 @@ + public net.neoforged.neoforge.common.world.LevelChunkAuxiliaryLightManager getAuxLightManager(ChunkPos pos) { + return auxLightManager; + } ++ ++ @Override ++ public final void syncData(net.neoforged.neoforge.attachment.AttachmentType type) { ++ if (level instanceof ServerLevel serverLevel) { ++ net.neoforged.neoforge.attachment.AttachmentInternals.syncAttachmentUpdate(getAttachmentHolder(), serverLevel, type); ++ } ++ } + class BoundTickingBlockEntity implements TickingBlockEntity { private final T blockEntity; diff --git a/src/main/java/net/neoforged/neoforge/attachment/AttachmentHolder.java b/src/main/java/net/neoforged/neoforge/attachment/AttachmentHolder.java index e822d369cf1..26276e54def 100644 --- a/src/main/java/net/neoforged/neoforge/attachment/AttachmentHolder.java +++ b/src/main/java/net/neoforged/neoforge/attachment/AttachmentHolder.java @@ -78,6 +78,7 @@ public final T getData(AttachmentType type) { if (ret == null) { ret = type.defaultValueSupplier.apply(getExposedHolder()); attachments.put(type, ret); + syncData(type); } return ret; } @@ -96,7 +97,9 @@ public Optional getExistingData(AttachmentType type) { public @Nullable T setData(AttachmentType type, T data) { validateAttachmentType(type); Objects.requireNonNull(data); - return (T) getAttachmentMap().put(type, data); + var previousData = (T) getAttachmentMap().put(type, data); + syncData(type); + return previousData; } @Override @@ -106,7 +109,9 @@ public Optional getExistingData(AttachmentType type) { if (attachments == null) { return null; } - return (T) attachments.remove(type); + var previousData = (T) attachments.remove(type); + syncData(type); + return previousData; } /** diff --git a/src/main/java/net/neoforged/neoforge/attachment/AttachmentInternals.java b/src/main/java/net/neoforged/neoforge/attachment/AttachmentInternals.java index a8dfee218a9..a0e667403de 100644 --- a/src/main/java/net/neoforged/neoforge/attachment/AttachmentInternals.java +++ b/src/main/java/net/neoforged/neoforge/attachment/AttachmentInternals.java @@ -22,6 +22,9 @@ import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.chunk.LevelChunk; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.neoforge.common.extensions.IEntityExtension; @@ -101,32 +104,49 @@ public static void onLivingConvert(LivingConversionEvent.Post event) { } }; - public static void syncEntityAttachment(Entity entity, AttachmentType type, T value, AttachmentSyncReason reason) { - if (type.syncHandler == null || !(entity.level() instanceof ServerLevel serverLevel)) { + static SyncAttachmentsPayload.Target syncTarget(AttachmentHolder holder) { + return switch (holder) { + case BlockEntity blockEntity -> new SyncAttachmentsPayload.BlockEntityTarget(blockEntity.getBlockPos()); + case AttachmentHolder.AsField asField when asField.getExposedHolder() instanceof LevelChunk chunk -> new SyncAttachmentsPayload.ChunkTarget(chunk.getPos()); + case Entity entity -> new SyncAttachmentsPayload.EntityTarget(entity.getId()); + case Level ignored -> new SyncAttachmentsPayload.LevelTarget(); + default -> throw new UnsupportedOperationException("Attachment holder class is not supported: " + holder); + }; + } + + public static void syncAttachmentUpdate(AttachmentHolder holder, ServerLevel level, AttachmentType type) { + if (type.syncHandler == null) { return; } - for (var player : serverLevel.players()) { + var value = holder.getData(type); // TODO: what if data is missing? + for (var player : level.players()) { // TODO: only sync to relevant players? e.g. only players that see a specific chunk List> syncedTypes = new ArrayList<>(1); var data = FriendlyByteBufUtil.writeCustomData(buf -> { int indexBefore = buf.writerIndex(); - type.syncHandler.write(buf, value, player, reason); + type.syncHandler.write(buf, value, player, AttachmentSyncReason.ENTITY_SYNC_REQUESTED); if (indexBefore < buf.writerIndex()) { // Actually wrote something syncedTypes.add(type); } - }, entity.registryAccess()); + }, level.registryAccess()); if (!syncedTypes.isEmpty()) { - PacketDistributor.sendToPlayer(player, new SyncAttachmentsPayload(new SyncAttachmentsPayload.EntityTarget(entity.getId()), syncedTypes, data)); + PacketDistributor.sendToPlayer(player, new SyncAttachmentsPayload(syncTarget(holder), syncedTypes, data)); } } } @Nullable - private static SyncAttachmentsPayload syncEntityAttachments(Entity entity, ServerPlayer to, AttachmentSyncReason reason) { - var holder = (AttachmentHolder) entity; + private static SyncAttachmentsPayload syncInitialAttachments(AttachmentHolder holder, ServerPlayer to) { if (holder.attachments == null) { return null; } + boolean anySyncableAttachment = false; + for (var attachment : holder.attachments.keySet()) { + anySyncableAttachment = anySyncableAttachment | attachment.syncHandler != null; + } + if (!anySyncableAttachment) { + return null; + } List> syncedTypes = new ArrayList<>(); var data = FriendlyByteBufUtil.writeCustomData(buf -> { for (var entry : holder.attachments.entrySet()) { @@ -135,19 +155,19 @@ private static SyncAttachmentsPayload syncEntityAttachments(Entity entity, Serve var syncHandler = (IAttachmentSyncHandler) type.syncHandler; if (syncHandler != null) { int indexBefore = buf.writerIndex(); - syncHandler.write(buf, entry.getValue(), to, reason); + syncHandler.write(buf, entry.getValue(), to, AttachmentSyncReason.NEW_ENTITY); if (indexBefore < buf.writerIndex()) { // Actually wrote something syncedTypes.add(type); } } } - }, entity.registryAccess()); - return new SyncAttachmentsPayload(new SyncAttachmentsPayload.EntityTarget(entity.getId()), syncedTypes, data); + }, to.registryAccess()); + return new SyncAttachmentsPayload(syncTarget(holder), syncedTypes, data); } public static void sendEntityPairingData(Entity entity, ServerPlayer to, Consumer> packetConsumer) { - var packet = syncEntityAttachments(entity, to, AttachmentSyncReason.NEW_ENTITY); + var packet = syncInitialAttachments(entity, to); if (packet != null) { packetConsumer.accept(packet.toVanillaClientbound()); } diff --git a/src/main/java/net/neoforged/neoforge/attachment/IAttachmentHolder.java b/src/main/java/net/neoforged/neoforge/attachment/IAttachmentHolder.java index 9e328255da9..0f092d850c3 100644 --- a/src/main/java/net/neoforged/neoforge/attachment/IAttachmentHolder.java +++ b/src/main/java/net/neoforged/neoforge/attachment/IAttachmentHolder.java @@ -95,4 +95,24 @@ default Optional getExistingData(Supplier> type) { default @Nullable T removeData(Supplier> type) { return removeData(type.get()); } + + /** + * Syncs a data attachment of the given type with all relevant clients. + * + * @see IAttachmentSyncHandler + */ + // TODO: what happens if there is no such data? + // TODO: auto sync on getData, removeData, other modifications + default void syncData(AttachmentType type) { + // TODO: do nothing by default? + } + + /** + * Syncs a data attachment of the given type with all relevant clients. + * + * @see IAttachmentSyncHandler + */ + default void syncData(Supplier> type) { + syncData(type.get()); + } } diff --git a/src/main/java/net/neoforged/neoforge/attachment/IAttachmentSyncHandler.java b/src/main/java/net/neoforged/neoforge/attachment/IAttachmentSyncHandler.java index f92bfea1c38..90823fd90dc 100644 --- a/src/main/java/net/neoforged/neoforge/attachment/IAttachmentSyncHandler.java +++ b/src/main/java/net/neoforged/neoforge/attachment/IAttachmentSyncHandler.java @@ -4,8 +4,21 @@ import net.minecraft.server.level.ServerPlayer; import org.jetbrains.annotations.Nullable; +import java.util.function.Supplier; + /** * Manages how data attachments are written (on the server) and read (on the client) from packets. + * + *

      Sync is handled automatically in the following cases: + *

        + *
      • A client is receiving initial data for this attachment holder.
      • + *
      • An attachment is default-created through {@link IAttachmentHolder#getData(AttachmentType)}.
      • + *
      • An attachment is updated through {@link IAttachmentHolder#setData(AttachmentType, Object)}.
      • + *
      • An attachment is removed through {@link IAttachmentHolder#removeData(AttachmentType)}.
      • + *
      + * + *

      For other cases such as modifications to mutable synced attachments, + * {@link IAttachmentHolder#syncData(AttachmentType)} can be called to trigger syncing. */ public interface IAttachmentSyncHandler { // TODO: pass target player diff --git a/src/main/java/net/neoforged/neoforge/network/handlers/ClientPayloadHandler.java b/src/main/java/net/neoforged/neoforge/network/handlers/ClientPayloadHandler.java index 8260dce07ab..7e139e89f0a 100644 --- a/src/main/java/net/neoforged/neoforge/network/handlers/ClientPayloadHandler.java +++ b/src/main/java/net/neoforged/neoforge/network/handlers/ClientPayloadHandler.java @@ -24,6 +24,7 @@ import net.minecraft.world.entity.Entity; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.level.chunk.status.ChunkStatus; import net.neoforged.neoforge.attachment.AttachmentInternals; import net.neoforged.neoforge.common.world.AuxiliaryLightManager; import net.neoforged.neoforge.common.world.LevelChunkAuxiliaryLightManager; @@ -160,6 +161,23 @@ public static void handle(final ClientboundCustomSetTimePayload payload, final I public static void handle(SyncAttachmentsPayload payload, IPayloadContext context) { switch (payload.target()) { + case SyncAttachmentsPayload.BlockEntityTarget target -> { + var blockEntity = context.player().level().getBlockEntity(target.pos()); + if (blockEntity == null) { + LOGGER.warn("Received synced attachments from unknown block entity"); + } else { + AttachmentInternals.receiveSyncedDataAttachments(blockEntity, context.player().registryAccess(), payload.types(), payload.syncPayload()); + } + } + case SyncAttachmentsPayload.ChunkTarget target -> { + var pos = target.pos(); + var chunk = context.player().level().getChunk(pos.x, pos.z, ChunkStatus.FULL, false); + if (chunk == null) { + LOGGER.warn("Received synced attachments from unknown chunk"); + } else { + chunk.receiveSyncedAttachments(payload.types(), payload.syncPayload()); + } + } case SyncAttachmentsPayload.EntityTarget target -> { var entity = context.player().level().getEntity(target.entity()); if (entity == null) { @@ -168,6 +186,9 @@ public static void handle(SyncAttachmentsPayload payload, IPayloadContext contex AttachmentInternals.receiveSyncedDataAttachments(entity, entity.registryAccess(), payload.types(), payload.syncPayload()); } } + case SyncAttachmentsPayload.LevelTarget ignored -> { + AttachmentInternals.receiveSyncedDataAttachments(context.player().level(), context.player().registryAccess(), payload.types(), payload.syncPayload()); + } } } } diff --git a/src/main/java/net/neoforged/neoforge/network/payload/SyncAttachmentsPayload.java b/src/main/java/net/neoforged/neoforge/network/payload/SyncAttachmentsPayload.java index bbe86c7c05d..3d77bf4959a 100644 --- a/src/main/java/net/neoforged/neoforge/network/payload/SyncAttachmentsPayload.java +++ b/src/main/java/net/neoforged/neoforge/network/payload/SyncAttachmentsPayload.java @@ -1,15 +1,23 @@ package net.neoforged.neoforge.network.payload; +import net.minecraft.core.BlockPos; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.chunk.LevelChunk; +import net.neoforged.neoforge.attachment.AttachmentHolder; import net.neoforged.neoforge.attachment.AttachmentInternals; import net.neoforged.neoforge.attachment.AttachmentType; import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; import net.neoforged.neoforge.network.codec.NeoForgeStreamCodecs; import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; import java.util.List; @@ -38,22 +46,45 @@ public sealed interface Target { StreamCodec STREAM_CODEC = StreamCodec.of( (buf, target) -> { switch (target) { - case EntityTarget entityTarget -> { + case BlockEntityTarget blockEntityTarget -> { buf.writeByte(0); + buf.writeBlockPos(blockEntityTarget.pos()); + } + case ChunkTarget chunkTarget -> { + buf.writeByte(1); + buf.writeChunkPos(chunkTarget.pos()); + } + case EntityTarget entityTarget -> { + buf.writeByte(2); buf.writeVarInt(entityTarget.entity()); } + case LevelTarget ignored -> { + buf.writeByte(3); + } } }, buf -> { int type = buf.readByte(); switch (type) { case 0 -> { + return new BlockEntityTarget(buf.readBlockPos()); + } + case 1 -> { + return new ChunkTarget(buf.readChunkPos()); + } + case 2 -> { return new EntityTarget(buf.readVarInt()); } + case 3 -> { + return new LevelTarget(); + } default -> throw new IllegalArgumentException("Unknown target type: " + type); } }); } + public record BlockEntityTarget(BlockPos pos) implements Target {} + public record ChunkTarget(ChunkPos pos) implements Target {} public record EntityTarget(int entity) implements Target {} + public record LevelTarget() implements Target {} } From bf0eb55b51338a338e22365095d9db7d8e477a3d Mon Sep 17 00:00:00 2001 From: Technici4n <13494793+Technici4n@users.noreply.github.com> Date: Sun, 5 Jan 2025 14:53:32 +0100 Subject: [PATCH 08/15] Fix chunk data syncing --- .../net/neoforged/neoforge/attachment/AttachmentHolder.java | 5 +++++ .../neoforge/network/payload/SyncAttachmentsPayload.java | 1 + 2 files changed, 6 insertions(+) diff --git a/src/main/java/net/neoforged/neoforge/attachment/AttachmentHolder.java b/src/main/java/net/neoforged/neoforge/attachment/AttachmentHolder.java index 26276e54def..f9633c7867c 100644 --- a/src/main/java/net/neoforged/neoforge/attachment/AttachmentHolder.java +++ b/src/main/java/net/neoforged/neoforge/attachment/AttachmentHolder.java @@ -184,5 +184,10 @@ IAttachmentHolder getExposedHolder() { public void deserializeInternal(HolderLookup.Provider provider, CompoundTag tag) { deserializeAttachments(provider, tag); } + + @Override + public void syncData(AttachmentType type) { + exposedHolder.syncData(type); + } } } diff --git a/src/main/java/net/neoforged/neoforge/network/payload/SyncAttachmentsPayload.java b/src/main/java/net/neoforged/neoforge/network/payload/SyncAttachmentsPayload.java index 3d77bf4959a..af7d67af533 100644 --- a/src/main/java/net/neoforged/neoforge/network/payload/SyncAttachmentsPayload.java +++ b/src/main/java/net/neoforged/neoforge/network/payload/SyncAttachmentsPayload.java @@ -86,5 +86,6 @@ public sealed interface Target { public record BlockEntityTarget(BlockPos pos) implements Target {} public record ChunkTarget(ChunkPos pos) implements Target {} public record EntityTarget(int entity) implements Target {} + // TODO: Should there be a way to sync overworld data while the player is in another level? (For "global" data). public record LevelTarget() implements Target {} } From f34b5b2d10d72fe3eb9bfc17bca8e0e8a626baa6 Mon Sep 17 00:00:00 2001 From: Technici4n <13494793+Technici4n@users.noreply.github.com> Date: Sun, 5 Jan 2025 21:19:39 +0100 Subject: [PATCH 09/15] Hacks to be reverted later to run the entity selector on the client --- .../selector/EntitySelector.java.patch | 54 +++++++++++++++++++ .../client/ClientCommandSourceStack.java | 6 +++ 2 files changed, 60 insertions(+) diff --git a/patches/net/minecraft/commands/arguments/selector/EntitySelector.java.patch b/patches/net/minecraft/commands/arguments/selector/EntitySelector.java.patch index 19b61399be8..640f467f0ad 100644 --- a/patches/net/minecraft/commands/arguments/selector/EntitySelector.java.patch +++ b/patches/net/minecraft/commands/arguments/selector/EntitySelector.java.patch @@ -1,5 +1,24 @@ --- a/net/minecraft/commands/arguments/selector/EntitySelector.java +++ b/net/minecraft/commands/arguments/selector/EntitySelector.java +@@ -10,15 +_,18 @@ + import javax.annotation.Nullable; + import net.minecraft.Util; + import net.minecraft.advancements.critereon.MinMaxBounds; ++import net.minecraft.client.multiplayer.ClientLevel; + import net.minecraft.commands.CommandSourceStack; + import net.minecraft.commands.arguments.EntityArgument; + import net.minecraft.network.chat.Component; + import net.minecraft.network.chat.ComponentUtils; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.util.AbortableIterationConsumer; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntityType; + import net.minecraft.world.flag.FeatureFlagSet; ++import net.minecraft.world.level.Level; + import net.minecraft.world.level.entity.EntityTypeTest; + import net.minecraft.world.phys.AABB; + import net.minecraft.world.phys.Vec3; @@ -105,7 +_,7 @@ } @@ -9,3 +28,38 @@ throw EntityArgument.ERROR_SELECTORS_NOT_ALLOWED.create(); } } +@@ -150,8 +_,9 @@ + } else { + Predicate predicate = this.getPredicate(vec3, aabb, p_121161_.enabledFeatures()); + List list = new ObjectArrayList<>(); ++ // TODO: remove all this messy temp stuff + if (this.isWorldLimited()) { +- this.addEntities(list, p_121161_.getLevel(), aabb, predicate); ++ this.addEntities(list, p_121161_.getUnsidedLevel(), aabb, predicate); + } else { + for (ServerLevel serverlevel : p_121161_.getServer().getAllLevels()) { + this.addEntities(list, serverlevel, aabb, predicate); +@@ -163,13 +_,21 @@ + } + } + +- private void addEntities(List p_121155_, ServerLevel p_121156_, @Nullable AABB p_352947_, Predicate p_121158_) { ++ private void addEntities(List p_121155_, Level p_121156_, @Nullable AABB p_352947_, Predicate p_121158_) { + int i = this.getResultLimit(); + if (p_121155_.size() < i) { + if (p_352947_ != null) { + p_121156_.getEntities(this.type, p_352947_, p_121158_, p_121155_, i); + } else { +- p_121156_.getEntities(this.type, p_121158_, p_121155_, i); ++ if (p_121156_ instanceof ServerLevel serverLevel) { ++ serverLevel.getEntities(this.type, p_121158_, p_121155_, i); ++ } else { ++ ((ClientLevel) p_121156_).entitiesForRendering().forEach(entity -> { ++ if (p_121158_.test(entity) && p_121155_.size() < i) { ++ p_121155_.add(entity); ++ } ++ }); ++ } + } + } + } diff --git a/src/main/java/net/neoforged/neoforge/client/ClientCommandSourceStack.java b/src/main/java/net/neoforged/neoforge/client/ClientCommandSourceStack.java index 471b085f821..29761953e04 100644 --- a/src/main/java/net/neoforged/neoforge/client/ClientCommandSourceStack.java +++ b/src/main/java/net/neoforged/neoforge/client/ClientCommandSourceStack.java @@ -27,6 +27,7 @@ import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.Entity; +import net.minecraft.world.flag.FeatureFlagSet; import net.minecraft.world.level.Level; import net.minecraft.world.phys.Vec2; import net.minecraft.world.phys.Vec3; @@ -95,6 +96,11 @@ public RegistryAccess registryAccess() { return Minecraft.getInstance().getConnection().registryAccess(); } + @Override + public FeatureFlagSet enabledFeatures() { + return Minecraft.getInstance().getConnection().enabledFeatures(); + } + /** * {@return the scoreboard from the client side} */ From b8b1006f7f3eb7c8ef7e61df2b8945975b26e6b5 Mon Sep 17 00:00:00 2001 From: Technici4n <13494793+Technici4n@users.noreply.github.com> Date: Mon, 6 Jan 2025 22:54:58 +0100 Subject: [PATCH 10/15] Only sync to relevant players. Initial BE and chunk sync --- .../server/level/ChunkMap.java.patch | 21 +++++ .../server/level/ServerLevel.java.patch | 2 +- .../minecraft/world/entity/Entity.java.patch | 2 +- .../level/block/entity/BlockEntity.java.patch | 6 +- .../world/level/chunk/ChunkAccess.java.patch | 2 +- .../world/level/chunk/LevelChunk.java.patch | 6 +- .../attachment/AttachmentInternals.java | 85 +++++++++++++++---- .../neoforge/attachment/AttachmentType.java | 3 +- .../attachment/IAttachmentSyncHandler.java | 9 +- .../neoforge/oldtest/AttachmentSyncTest.java | 4 +- 10 files changed, 103 insertions(+), 37 deletions(-) diff --git a/patches/net/minecraft/server/level/ChunkMap.java.patch b/patches/net/minecraft/server/level/ChunkMap.java.patch index d6dcc919098..aaa62346f9c 100644 --- a/patches/net/minecraft/server/level/ChunkMap.java.patch +++ b/patches/net/minecraft/server/level/ChunkMap.java.patch @@ -65,6 +65,27 @@ EntityType entitytype = p_140200_.getType(); int i = entitytype.clientTrackingRange() * 16; if (i != 0) { +@@ -1240,6 +_,20 @@ + }); + } + ++ // Neo: Getter for players watching an entity. ++ public List getPlayersWatching(Entity entity) { ++ var trackedEntity = entityMap.get(entity.getId()); ++ if (trackedEntity != null) { ++ var ret = new java.util.ArrayList(trackedEntity.seenBy.size()); ++ for (var connection : trackedEntity.seenBy) { ++ ret.add(connection.getPlayer()); ++ } ++ return List.copyOf(ret); ++ } else { ++ return List.of(); ++ } ++ } ++ + class DistanceManager extends net.minecraft.server.level.DistanceManager { + protected DistanceManager(Executor p_140459_, Executor p_140460_) { + super(p_140459_, p_140460_); @@ -1354,5 +_,20 @@ this.updatePlayer(serverplayer); } diff --git a/patches/net/minecraft/server/level/ServerLevel.java.patch b/patches/net/minecraft/server/level/ServerLevel.java.patch index f45d6758c3b..1a2a2d43840 100644 --- a/patches/net/minecraft/server/level/ServerLevel.java.patch +++ b/patches/net/minecraft/server/level/ServerLevel.java.patch @@ -275,7 +275,7 @@ + + @Override + public final void syncData(net.neoforged.neoforge.attachment.AttachmentType type) { -+ net.neoforged.neoforge.attachment.AttachmentInternals.syncAttachmentUpdate(this, this, type); ++ net.neoforged.neoforge.attachment.AttachmentInternals.syncLevelUpdate(this, type); + } + + private final net.neoforged.neoforge.capabilities.CapabilityListenerHolder capListenerHolder = new net.neoforged.neoforge.capabilities.CapabilityListenerHolder(); diff --git a/patches/net/minecraft/world/entity/Entity.java.patch b/patches/net/minecraft/world/entity/Entity.java.patch index 590e29bc27a..b520604a7bb 100644 --- a/patches/net/minecraft/world/entity/Entity.java.patch +++ b/patches/net/minecraft/world/entity/Entity.java.patch @@ -558,7 +558,7 @@ + @Override + public final void syncData(net.neoforged.neoforge.attachment.AttachmentType type) { + if (level instanceof ServerLevel serverLevel) { -+ net.neoforged.neoforge.attachment.AttachmentInternals.syncAttachmentUpdate(this, serverLevel, type); ++ net.neoforged.neoforge.attachment.AttachmentInternals.syncEntityUpdate(this, type); + } + } + diff --git a/patches/net/minecraft/world/level/block/entity/BlockEntity.java.patch b/patches/net/minecraft/world/level/block/entity/BlockEntity.java.patch index 0ec2897c2e3..cb4477821f8 100644 --- a/patches/net/minecraft/world/level/block/entity/BlockEntity.java.patch +++ b/patches/net/minecraft/world/level/block/entity/BlockEntity.java.patch @@ -69,7 +69,7 @@ } public boolean triggerEvent(int p_58889_, int p_58890_) { -@@ -234,6 +_,34 @@ +@@ -234,6 +_,32 @@ return this.type; } @@ -96,9 +96,7 @@ + + @Override + public final void syncData(net.neoforged.neoforge.attachment.AttachmentType type) { -+ if (level instanceof ServerLevel serverLevel) { -+ net.neoforged.neoforge.attachment.AttachmentInternals.syncAttachmentUpdate(this, serverLevel, type); -+ } ++ net.neoforged.neoforge.attachment.AttachmentInternals.syncBlockEntityUpdate(this, type); + } + @Deprecated diff --git a/patches/net/minecraft/world/level/chunk/ChunkAccess.java.patch b/patches/net/minecraft/world/level/chunk/ChunkAccess.java.patch index 46f1a409f7e..1bbf750336b 100644 --- a/patches/net/minecraft/world/level/chunk/ChunkAccess.java.patch +++ b/patches/net/minecraft/world/level/chunk/ChunkAccess.java.patch @@ -112,7 +112,7 @@ + } + + @org.jetbrains.annotations.ApiStatus.Internal -+ protected net.neoforged.neoforge.attachment.AttachmentHolder.AsField getAttachmentHolder() { ++ public net.neoforged.neoforge.attachment.AttachmentHolder.AsField getAttachmentHolder() { + return attachmentHolder; + } + diff --git a/patches/net/minecraft/world/level/chunk/LevelChunk.java.patch b/patches/net/minecraft/world/level/chunk/LevelChunk.java.patch index 97a2830b9f5..4614d2d008a 100644 --- a/patches/net/minecraft/world/level/chunk/LevelChunk.java.patch +++ b/patches/net/minecraft/world/level/chunk/LevelChunk.java.patch @@ -120,7 +120,7 @@ this.blockEntities.values().forEach(p_187988_ -> { if (this.level instanceof ServerLevel serverlevel) { this.addGameEventListener(p_187988_, serverlevel); -@@ -694,6 +_,21 @@ +@@ -694,6 +_,19 @@ return new LevelChunk.BoundTickingBlockEntity<>(p_156376_, p_156377_); } @@ -134,9 +134,7 @@ + + @Override + public final void syncData(net.neoforged.neoforge.attachment.AttachmentType type) { -+ if (level instanceof ServerLevel serverLevel) { -+ net.neoforged.neoforge.attachment.AttachmentInternals.syncAttachmentUpdate(getAttachmentHolder(), serverLevel, type); -+ } ++ net.neoforged.neoforge.attachment.AttachmentInternals.syncChunkUpdate(this, getAttachmentHolder(), type); + } + class BoundTickingBlockEntity implements TickingBlockEntity { diff --git a/src/main/java/net/neoforged/neoforge/attachment/AttachmentInternals.java b/src/main/java/net/neoforged/neoforge/attachment/AttachmentInternals.java index a0e667403de..13ef54f1f34 100644 --- a/src/main/java/net/neoforged/neoforge/attachment/AttachmentInternals.java +++ b/src/main/java/net/neoforged/neoforge/attachment/AttachmentInternals.java @@ -17,11 +17,13 @@ import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.game.ClientGamePacketListener; +import net.minecraft.network.protocol.game.ClientboundBundlePacket; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.chunk.LevelChunk; @@ -31,8 +33,8 @@ import net.neoforged.neoforge.common.util.FriendlyByteBufUtil; import net.neoforged.neoforge.event.entity.living.LivingConversionEvent; import net.neoforged.neoforge.event.entity.player.PlayerEvent; +import net.neoforged.neoforge.event.level.ChunkWatchEvent; import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; -import net.neoforged.neoforge.network.PacketDistributor; import net.neoforged.neoforge.network.connection.ConnectionType; import net.neoforged.neoforge.network.payload.SyncAttachmentsPayload; import net.neoforged.neoforge.registries.NeoForgeRegistries; @@ -114,27 +116,58 @@ static SyncAttachmentsPayload.Target syncTarget(AttachmentHolder holder) { }; } - public static void syncAttachmentUpdate(AttachmentHolder holder, ServerLevel level, AttachmentType type) { - if (type.syncHandler == null) { + private static void syncUpdate(AttachmentHolder holder, AttachmentType type, List players) { + RegistryAccess registryAccess = null; + for (var player : players) { + if (type.syncHandler.sendToPlayer(holder.getExposedHolder(), player)) { + registryAccess = player.registryAccess(); + break; + } + } + // This also serves as a short-circuit if there are no players to sync data to. + if (registryAccess == null) { return; } - var value = holder.getData(type); // TODO: what if data is missing? - for (var player : level.players()) { // TODO: only sync to relevant players? e.g. only players that see a specific chunk - List> syncedTypes = new ArrayList<>(1); - var data = FriendlyByteBufUtil.writeCustomData(buf -> { - int indexBefore = buf.writerIndex(); - type.syncHandler.write(buf, value, player, AttachmentSyncReason.ENTITY_SYNC_REQUESTED); - if (indexBefore < buf.writerIndex()) { - // Actually wrote something - syncedTypes.add(type); - } - }, level.registryAccess()); - if (!syncedTypes.isEmpty()) { - PacketDistributor.sendToPlayer(player, new SyncAttachmentsPayload(syncTarget(holder), syncedTypes, data)); + var data = FriendlyByteBufUtil.writeCustomData(buf -> { + // TODO: what if data is missing? + type.syncHandler.write(buf, holder.getData(type), false); + }, registryAccess); + var packet = new SyncAttachmentsPayload(syncTarget(holder), List.of(type), data).toVanillaClientbound(); + for (var player : players) { + if (type.syncHandler.sendToPlayer(holder.getExposedHolder(), player)) { + player.connection.send(packet); } } } + public static void syncBlockEntityUpdate(BlockEntity blockEntity, AttachmentType type) { + if (type.syncHandler == null || !(blockEntity.getLevel() instanceof ServerLevel serverLevel)) { + return; + } + syncUpdate(blockEntity, type, serverLevel.getChunkSource().chunkMap.getPlayers(new ChunkPos(blockEntity.getBlockPos()), false)); + } + + public static void syncChunkUpdate(LevelChunk chunk, AttachmentHolder.AsField holder, AttachmentType type) { + if (type.syncHandler == null || !(chunk.getLevel() instanceof ServerLevel serverLevel)) { + return; + } + syncUpdate(holder, type, serverLevel.getChunkSource().chunkMap.getPlayers(chunk.getPos(), false)); + } + + public static void syncEntityUpdate(Entity entity, AttachmentType type) { + if (type.syncHandler == null || !(entity.level() instanceof ServerLevel serverLevel)) { + return; + } + syncUpdate(entity, type, serverLevel.getChunkSource().chunkMap.getPlayersWatching(entity)); + } + + public static void syncLevelUpdate(ServerLevel level, AttachmentType type) { + if (type.syncHandler == null) { + return; + } + syncUpdate(level, type, level.players()); + } + @Nullable private static SyncAttachmentsPayload syncInitialAttachments(AttachmentHolder holder, ServerPlayer to) { if (holder.attachments == null) { @@ -155,7 +188,7 @@ private static SyncAttachmentsPayload syncInitialAttachments(AttachmentHolder ho var syncHandler = (IAttachmentSyncHandler) type.syncHandler; if (syncHandler != null) { int indexBefore = buf.writerIndex(); - syncHandler.write(buf, entry.getValue(), to, AttachmentSyncReason.NEW_ENTITY); + syncHandler.write(buf, entry.getValue(), true); if (indexBefore < buf.writerIndex()) { // Actually wrote something syncedTypes.add(type); @@ -166,6 +199,24 @@ private static SyncAttachmentsPayload syncInitialAttachments(AttachmentHolder ho return new SyncAttachmentsPayload(syncTarget(holder), syncedTypes, data); } + @SubscribeEvent + public static void onChunkSent(ChunkWatchEvent.Sent event) { + List> packets = new ArrayList<>(); + var chunkPayload = syncInitialAttachments(event.getChunk().getAttachmentHolder(), event.getPlayer()); + if (chunkPayload != null) { + packets.add(chunkPayload.toVanillaClientbound()); + } + for (var blockEntity : event.getChunk().getBlockEntities().values()) { + var blockEntityPayload = syncInitialAttachments(blockEntity, event.getPlayer()); + if (blockEntityPayload != null) { + packets.add(blockEntityPayload.toVanillaClientbound()); + } + } + if (!packets.isEmpty()) { + event.getPlayer().connection.send(new ClientboundBundlePacket(packets)); + } + } + public static void sendEntityPairingData(Entity entity, ServerPlayer to, Consumer> packetConsumer) { var packet = syncInitialAttachments(entity, to); if (packet != null) { diff --git a/src/main/java/net/neoforged/neoforge/attachment/AttachmentType.java b/src/main/java/net/neoforged/neoforge/attachment/AttachmentType.java index bb41052f736..0549d7f4f5b 100644 --- a/src/main/java/net/neoforged/neoforge/attachment/AttachmentType.java +++ b/src/main/java/net/neoforged/neoforge/attachment/AttachmentType.java @@ -16,7 +16,6 @@ import net.minecraft.nbt.Tag; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; -import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; @@ -248,7 +247,7 @@ public Builder sync(StreamCodec streamCod Objects.requireNonNull(streamCodec); return sync(new IAttachmentSyncHandler<>() { @Override - public void write(RegistryFriendlyByteBuf buf, T attachment, ServerPlayer to, AttachmentSyncReason reason) { + public void write(RegistryFriendlyByteBuf buf, T attachment, boolean initialSync) { streamCodec.encode(buf, attachment); } diff --git a/src/main/java/net/neoforged/neoforge/attachment/IAttachmentSyncHandler.java b/src/main/java/net/neoforged/neoforge/attachment/IAttachmentSyncHandler.java index 90823fd90dc..4c89c504043 100644 --- a/src/main/java/net/neoforged/neoforge/attachment/IAttachmentSyncHandler.java +++ b/src/main/java/net/neoforged/neoforge/attachment/IAttachmentSyncHandler.java @@ -4,8 +4,6 @@ import net.minecraft.server.level.ServerPlayer; import org.jetbrains.annotations.Nullable; -import java.util.function.Supplier; - /** * Manages how data attachments are written (on the server) and read (on the client) from packets. * @@ -21,8 +19,11 @@ * {@link IAttachmentHolder#syncData(AttachmentType)} can be called to trigger syncing. */ public interface IAttachmentSyncHandler { - // TODO: pass target player - void write(RegistryFriendlyByteBuf buf, T attachment, ServerPlayer to, AttachmentSyncReason reason); + default boolean sendToPlayer(IAttachmentHolder holder, ServerPlayer to) { + return true; + } + + void write(RegistryFriendlyByteBuf buf, T attachment, boolean initialSync); // TODO: we could also return void and let the sync handler call .setData(type, xxx). But that means passing the type somehow. @Nullable diff --git a/tests/src/main/java/net/neoforged/neoforge/oldtest/AttachmentSyncTest.java b/tests/src/main/java/net/neoforged/neoforge/oldtest/AttachmentSyncTest.java index 5d74047127f..cd628972c73 100644 --- a/tests/src/main/java/net/neoforged/neoforge/oldtest/AttachmentSyncTest.java +++ b/tests/src/main/java/net/neoforged/neoforge/oldtest/AttachmentSyncTest.java @@ -14,13 +14,11 @@ import net.minecraft.commands.arguments.coordinates.BlockPosArgument; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.chat.Component; -import net.minecraft.server.level.ServerPlayer; import net.neoforged.api.distmarker.Dist; import net.neoforged.bus.api.IEventBus; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.fml.common.Mod; -import net.neoforged.neoforge.attachment.AttachmentSyncReason; import net.neoforged.neoforge.attachment.AttachmentType; import net.neoforged.neoforge.attachment.IAttachmentHolder; import net.neoforged.neoforge.attachment.IAttachmentSyncHandler; @@ -43,7 +41,7 @@ public class AttachmentSyncTest { // TODO: use streamcodec version at some point .sync(new IAttachmentSyncHandler() { @Override - public void write(RegistryFriendlyByteBuf buf, Integer attachment, ServerPlayer to, AttachmentSyncReason reason) { + public void write(RegistryFriendlyByteBuf buf, Integer attachment, boolean initialSync) { buf.writeInt(attachment); } From 81c19e0bba988990c042b57fb184568b41d210da Mon Sep 17 00:00:00 2001 From: Technici4n <13494793+Technici4n@users.noreply.github.com> Date: Mon, 6 Jan 2025 23:30:30 +0100 Subject: [PATCH 11/15] Level and player initial sync --- .../server/players/PlayerList.java.patch | 8 ++++ .../attachment/AttachmentInternals.java | 38 ++++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/patches/net/minecraft/server/players/PlayerList.java.patch b/patches/net/minecraft/server/players/PlayerList.java.patch index a3c0ab67185..69c7ef093c8 100644 --- a/patches/net/minecraft/server/players/PlayerList.java.patch +++ b/patches/net/minecraft/server/players/PlayerList.java.patch @@ -116,6 +116,14 @@ p_11230_.connection.send(new ClientboundSetDefaultSpawnPositionPacket(p_11231_.getSharedSpawnPos(), p_11231_.getSharedSpawnAngle())); if (p_11231_.isRaining()) { p_11230_.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.START_RAINING, 0.0F)); +@@ -670,6 +_,7 @@ + + p_11230_.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.LEVEL_CHUNKS_LOAD_START, 0.0F)); + this.server.tickRateManager().updateJoiningPlayer(p_11230_); ++ net.neoforged.neoforge.attachment.AttachmentInternals.sendLevelInfo(p_11231_, p_11230_); + } + + public void sendAllPlayerInfo(ServerPlayer p_11293_) { @@ -785,13 +_,6 @@ if (serverstatscounter == null) { File file1 = this.server.getWorldPath(LevelResource.PLAYER_STATS_DIR).toFile(); diff --git a/src/main/java/net/neoforged/neoforge/attachment/AttachmentInternals.java b/src/main/java/net/neoforged/neoforge/attachment/AttachmentInternals.java index 13ef54f1f34..9793b3f5c93 100644 --- a/src/main/java/net/neoforged/neoforge/attachment/AttachmentInternals.java +++ b/src/main/java/net/neoforged/neoforge/attachment/AttachmentInternals.java @@ -158,7 +158,15 @@ public static void syncEntityUpdate(Entity entity, AttachmentType type) { if (type.syncHandler == null || !(entity.level() instanceof ServerLevel serverLevel)) { return; } - syncUpdate(entity, type, serverLevel.getChunkSource().chunkMap.getPlayersWatching(entity)); + var players = serverLevel.getChunkSource().chunkMap.getPlayersWatching(entity); + if (entity instanceof ServerPlayer serverPlayer) { + // Players do not track themselves + var newPlayers = new ArrayList(players.size() + 1); + newPlayers.addAll(players); + newPlayers.add(serverPlayer); + players = newPlayers; + } + syncUpdate(entity, type, players); } public static void syncLevelUpdate(ServerLevel level, AttachmentType type) { @@ -199,6 +207,9 @@ private static SyncAttachmentsPayload syncInitialAttachments(AttachmentHolder ho return new SyncAttachmentsPayload(syncTarget(holder), syncedTypes, data); } + /** + * Handles initial syncing of block entity and chunk attachments. + */ @SubscribeEvent public static void onChunkSent(ChunkWatchEvent.Sent event) { List> packets = new ArrayList<>(); @@ -217,6 +228,9 @@ public static void onChunkSent(ChunkWatchEvent.Sent event) { } } + /** + * Handles initial syncing of entity attachments, except for a player's own attachments. + */ public static void sendEntityPairingData(Entity entity, ServerPlayer to, Consumer> packetConsumer) { var packet = syncInitialAttachments(entity, to); if (packet != null) { @@ -224,6 +238,28 @@ public static void sendEntityPairingData(Entity entity, ServerPlayer to, Consume } } + /** + * Handles initial syncing of a player's own attachments. + */ + @SubscribeEvent + public static void onPlayerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) { + var player = (ServerPlayer) event.getEntity(); + var packet = syncInitialAttachments(event.getEntity(), player); + if (packet != null) { + player.connection.send(packet.toVanillaClientbound()); + } + } + + /** + * Handles initial syncing of level attachments. Needs to be called for login, respawn and teleports. + */ + public static void sendLevelInfo(ServerLevel level, ServerPlayer to) { + var packet = syncInitialAttachments(level, to); + if (packet != null) { + to.connection.send(packet.toVanillaClientbound()); + } + } + public static void receiveSyncedDataAttachments(AttachmentHolder holder, RegistryAccess registryAccess, List> types, byte[] bytes) { var buf = new RegistryFriendlyByteBuf(Unpooled.wrappedBuffer(bytes), registryAccess, ConnectionType.NEOFORGE); try { From 410b52eb8a498a805da4d78045d039f52fe65a54 Mon Sep 17 00:00:00 2001 From: Technici4n <13494793+Technici4n@users.noreply.github.com> Date: Mon, 6 Jan 2025 23:34:21 +0100 Subject: [PATCH 12/15] Delete unused AttachmentSyncReason --- .../neoforge/attachment/AttachmentSyncReason.java | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 src/main/java/net/neoforged/neoforge/attachment/AttachmentSyncReason.java diff --git a/src/main/java/net/neoforged/neoforge/attachment/AttachmentSyncReason.java b/src/main/java/net/neoforged/neoforge/attachment/AttachmentSyncReason.java deleted file mode 100644 index f920e8f76b3..00000000000 --- a/src/main/java/net/neoforged/neoforge/attachment/AttachmentSyncReason.java +++ /dev/null @@ -1,7 +0,0 @@ -package net.neoforged.neoforge.attachment; - -public enum AttachmentSyncReason { - // Might not actually be new on the server, but it is now for the client - NEW_ENTITY, - ENTITY_SYNC_REQUESTED, -} From a1e31ef37e9f85854409506a6fd9b528a785d5ed Mon Sep 17 00:00:00 2001 From: Technici4n <13494793+Technici4n@users.noreply.github.com> Date: Mon, 6 Jan 2025 23:42:01 +0100 Subject: [PATCH 13/15] Move syncing internals to new AttachmentSync internal class --- .../server/level/ServerEntity.java.patch | 2 +- .../server/level/ServerLevel.java.patch | 2 +- .../server/players/PlayerList.java.patch | 2 +- .../minecraft/world/entity/Entity.java.patch | 6 +- .../level/block/entity/BlockEntity.java.patch | 2 +- .../world/level/chunk/ChunkAccess.java.patch | 7 +- .../world/level/chunk/LevelChunk.java.patch | 2 +- .../attachment/AttachmentInternals.java | 196 --------------- .../neoforge/attachment/AttachmentSync.java | 237 ++++++++++++++++++ .../handlers/ClientPayloadHandler.java | 10 +- .../payload/SyncAttachmentsPayload.java | 3 +- .../registries/NeoForgeRegistriesSetup.java | 5 +- 12 files changed, 256 insertions(+), 218 deletions(-) create mode 100644 src/main/java/net/neoforged/neoforge/attachment/AttachmentSync.java diff --git a/patches/net/minecraft/server/level/ServerEntity.java.patch b/patches/net/minecraft/server/level/ServerEntity.java.patch index 43c8b0d6814..b62d7872a27 100644 --- a/patches/net/minecraft/server/level/ServerEntity.java.patch +++ b/patches/net/minecraft/server/level/ServerEntity.java.patch @@ -44,7 +44,7 @@ p_289563_.accept(new ClientboundSetEntityLinkPacket(this.entity, leashable.getLeashHolder())); } + -+ net.neoforged.neoforge.attachment.AttachmentInternals.sendEntityPairingData(this.entity, p_289562_, p_289563_); ++ net.neoforged.neoforge.attachment.AttachmentSync.sendEntityPairingData(this.entity, p_289562_, p_289563_); } public Vec3 getPositionBase() { diff --git a/patches/net/minecraft/server/level/ServerLevel.java.patch b/patches/net/minecraft/server/level/ServerLevel.java.patch index 1a2a2d43840..80c2e8261c8 100644 --- a/patches/net/minecraft/server/level/ServerLevel.java.patch +++ b/patches/net/minecraft/server/level/ServerLevel.java.patch @@ -275,7 +275,7 @@ + + @Override + public final void syncData(net.neoforged.neoforge.attachment.AttachmentType type) { -+ net.neoforged.neoforge.attachment.AttachmentInternals.syncLevelUpdate(this, type); ++ net.neoforged.neoforge.attachment.AttachmentSync.syncLevelUpdate(this, type); + } + + private final net.neoforged.neoforge.capabilities.CapabilityListenerHolder capListenerHolder = new net.neoforged.neoforge.capabilities.CapabilityListenerHolder(); diff --git a/patches/net/minecraft/server/players/PlayerList.java.patch b/patches/net/minecraft/server/players/PlayerList.java.patch index 69c7ef093c8..460e2d8fc01 100644 --- a/patches/net/minecraft/server/players/PlayerList.java.patch +++ b/patches/net/minecraft/server/players/PlayerList.java.patch @@ -120,7 +120,7 @@ p_11230_.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.LEVEL_CHUNKS_LOAD_START, 0.0F)); this.server.tickRateManager().updateJoiningPlayer(p_11230_); -+ net.neoforged.neoforge.attachment.AttachmentInternals.sendLevelInfo(p_11231_, p_11230_); ++ net.neoforged.neoforge.attachment.AttachmentSync.sendLevelInfo(p_11231_, p_11230_); } public void sendAllPlayerInfo(ServerPlayer p_11293_) { diff --git a/patches/net/minecraft/world/entity/Entity.java.patch b/patches/net/minecraft/world/entity/Entity.java.patch index b520604a7bb..381f757ccdf 100644 --- a/patches/net/minecraft/world/entity/Entity.java.patch +++ b/patches/net/minecraft/world/entity/Entity.java.patch @@ -438,7 +438,7 @@ } public void checkDespawn() { -@@ -3627,6 +_,135 @@ +@@ -3627,6 +_,133 @@ public boolean mayInteract(ServerLevel p_376870_, BlockPos p_146844_) { return true; @@ -557,9 +557,7 @@ + + @Override + public final void syncData(net.neoforged.neoforge.attachment.AttachmentType type) { -+ if (level instanceof ServerLevel serverLevel) { -+ net.neoforged.neoforge.attachment.AttachmentInternals.syncEntityUpdate(this, type); -+ } ++ net.neoforged.neoforge.attachment.AttachmentSync.syncEntityUpdate(this, type); + } + + // Neo: Hookup Capabilities getters to entities diff --git a/patches/net/minecraft/world/level/block/entity/BlockEntity.java.patch b/patches/net/minecraft/world/level/block/entity/BlockEntity.java.patch index cb4477821f8..a42ab602dc0 100644 --- a/patches/net/minecraft/world/level/block/entity/BlockEntity.java.patch +++ b/patches/net/minecraft/world/level/block/entity/BlockEntity.java.patch @@ -96,7 +96,7 @@ + + @Override + public final void syncData(net.neoforged.neoforge.attachment.AttachmentType type) { -+ net.neoforged.neoforge.attachment.AttachmentInternals.syncBlockEntityUpdate(this, type); ++ net.neoforged.neoforge.attachment.AttachmentSync.syncBlockEntityUpdate(this, type); + } + @Deprecated diff --git a/patches/net/minecraft/world/level/chunk/ChunkAccess.java.patch b/patches/net/minecraft/world/level/chunk/ChunkAccess.java.patch index 1bbf750336b..c02b6d9a96d 100644 --- a/patches/net/minecraft/world/level/chunk/ChunkAccess.java.patch +++ b/patches/net/minecraft/world/level/chunk/ChunkAccess.java.patch @@ -42,7 +42,7 @@ } } } -@@ -485,6 +_,79 @@ +@@ -485,6 +_,74 @@ public ChunkSkyLightSources getSkyLightSources() { return this.skyLightSources; } @@ -107,11 +107,6 @@ + } + + @org.jetbrains.annotations.ApiStatus.Internal -+ public final void receiveSyncedAttachments(List> types, byte[] bytes) { -+ net.neoforged.neoforge.attachment.AttachmentInternals.receiveSyncedDataAttachments(getAttachmentHolder(), getLevel().registryAccess(), types, bytes); -+ } -+ -+ @org.jetbrains.annotations.ApiStatus.Internal + public net.neoforged.neoforge.attachment.AttachmentHolder.AsField getAttachmentHolder() { + return attachmentHolder; + } diff --git a/patches/net/minecraft/world/level/chunk/LevelChunk.java.patch b/patches/net/minecraft/world/level/chunk/LevelChunk.java.patch index 4614d2d008a..ca0ecdca58d 100644 --- a/patches/net/minecraft/world/level/chunk/LevelChunk.java.patch +++ b/patches/net/minecraft/world/level/chunk/LevelChunk.java.patch @@ -134,7 +134,7 @@ + + @Override + public final void syncData(net.neoforged.neoforge.attachment.AttachmentType type) { -+ net.neoforged.neoforge.attachment.AttachmentInternals.syncChunkUpdate(this, getAttachmentHolder(), type); ++ net.neoforged.neoforge.attachment.AttachmentSync.syncChunkUpdate(this, getAttachmentHolder(), type); + } + class BoundTickingBlockEntity implements TickingBlockEntity { diff --git a/src/main/java/net/neoforged/neoforge/attachment/AttachmentInternals.java b/src/main/java/net/neoforged/neoforge/attachment/AttachmentInternals.java index 9793b3f5c93..b74a7aa1e9b 100644 --- a/src/main/java/net/neoforged/neoforge/attachment/AttachmentInternals.java +++ b/src/main/java/net/neoforged/neoforge/attachment/AttachmentInternals.java @@ -90,201 +90,5 @@ public static void onLivingConvert(LivingConversionEvent.Post event) { event.getOutcome().copyAttachmentsFrom(event.getEntity(), true); } - /** - * Contains all entries added to {@link NeoForgeRegistries#ATTACHMENT_TYPES} with a sync handler. - * Should never be registered against directly. - */ - public static final Registry> SYNCED_ATTACHMENT_TYPES = new RegistryBuilder<>( - ResourceKey.>createRegistryKey( - ResourceLocation.fromNamespaceAndPath(NeoForgeVersion.MOD_ID, "synced_attachment_types"))) - .sync(true) - .create(); - - public static final AddCallback> ATTACHMENT_TYPE_ADD_CALLBACK = (registry, id, key, value) -> { - if (value.syncHandler != null) { - Registry.register(SYNCED_ATTACHMENT_TYPES, key.location(), value); - } - }; - - static SyncAttachmentsPayload.Target syncTarget(AttachmentHolder holder) { - return switch (holder) { - case BlockEntity blockEntity -> new SyncAttachmentsPayload.BlockEntityTarget(blockEntity.getBlockPos()); - case AttachmentHolder.AsField asField when asField.getExposedHolder() instanceof LevelChunk chunk -> new SyncAttachmentsPayload.ChunkTarget(chunk.getPos()); - case Entity entity -> new SyncAttachmentsPayload.EntityTarget(entity.getId()); - case Level ignored -> new SyncAttachmentsPayload.LevelTarget(); - default -> throw new UnsupportedOperationException("Attachment holder class is not supported: " + holder); - }; - } - - private static void syncUpdate(AttachmentHolder holder, AttachmentType type, List players) { - RegistryAccess registryAccess = null; - for (var player : players) { - if (type.syncHandler.sendToPlayer(holder.getExposedHolder(), player)) { - registryAccess = player.registryAccess(); - break; - } - } - // This also serves as a short-circuit if there are no players to sync data to. - if (registryAccess == null) { - return; - } - var data = FriendlyByteBufUtil.writeCustomData(buf -> { - // TODO: what if data is missing? - type.syncHandler.write(buf, holder.getData(type), false); - }, registryAccess); - var packet = new SyncAttachmentsPayload(syncTarget(holder), List.of(type), data).toVanillaClientbound(); - for (var player : players) { - if (type.syncHandler.sendToPlayer(holder.getExposedHolder(), player)) { - player.connection.send(packet); - } - } - } - - public static void syncBlockEntityUpdate(BlockEntity blockEntity, AttachmentType type) { - if (type.syncHandler == null || !(blockEntity.getLevel() instanceof ServerLevel serverLevel)) { - return; - } - syncUpdate(blockEntity, type, serverLevel.getChunkSource().chunkMap.getPlayers(new ChunkPos(blockEntity.getBlockPos()), false)); - } - - public static void syncChunkUpdate(LevelChunk chunk, AttachmentHolder.AsField holder, AttachmentType type) { - if (type.syncHandler == null || !(chunk.getLevel() instanceof ServerLevel serverLevel)) { - return; - } - syncUpdate(holder, type, serverLevel.getChunkSource().chunkMap.getPlayers(chunk.getPos(), false)); - } - - public static void syncEntityUpdate(Entity entity, AttachmentType type) { - if (type.syncHandler == null || !(entity.level() instanceof ServerLevel serverLevel)) { - return; - } - var players = serverLevel.getChunkSource().chunkMap.getPlayersWatching(entity); - if (entity instanceof ServerPlayer serverPlayer) { - // Players do not track themselves - var newPlayers = new ArrayList(players.size() + 1); - newPlayers.addAll(players); - newPlayers.add(serverPlayer); - players = newPlayers; - } - syncUpdate(entity, type, players); - } - - public static void syncLevelUpdate(ServerLevel level, AttachmentType type) { - if (type.syncHandler == null) { - return; - } - syncUpdate(level, type, level.players()); - } - - @Nullable - private static SyncAttachmentsPayload syncInitialAttachments(AttachmentHolder holder, ServerPlayer to) { - if (holder.attachments == null) { - return null; - } - boolean anySyncableAttachment = false; - for (var attachment : holder.attachments.keySet()) { - anySyncableAttachment = anySyncableAttachment | attachment.syncHandler != null; - } - if (!anySyncableAttachment) { - return null; - } - List> syncedTypes = new ArrayList<>(); - var data = FriendlyByteBufUtil.writeCustomData(buf -> { - for (var entry : holder.attachments.entrySet()) { - AttachmentType type = entry.getKey(); - @SuppressWarnings("unchecked") - var syncHandler = (IAttachmentSyncHandler) type.syncHandler; - if (syncHandler != null) { - int indexBefore = buf.writerIndex(); - syncHandler.write(buf, entry.getValue(), true); - if (indexBefore < buf.writerIndex()) { - // Actually wrote something - syncedTypes.add(type); - } - } - } - }, to.registryAccess()); - return new SyncAttachmentsPayload(syncTarget(holder), syncedTypes, data); - } - - /** - * Handles initial syncing of block entity and chunk attachments. - */ - @SubscribeEvent - public static void onChunkSent(ChunkWatchEvent.Sent event) { - List> packets = new ArrayList<>(); - var chunkPayload = syncInitialAttachments(event.getChunk().getAttachmentHolder(), event.getPlayer()); - if (chunkPayload != null) { - packets.add(chunkPayload.toVanillaClientbound()); - } - for (var blockEntity : event.getChunk().getBlockEntities().values()) { - var blockEntityPayload = syncInitialAttachments(blockEntity, event.getPlayer()); - if (blockEntityPayload != null) { - packets.add(blockEntityPayload.toVanillaClientbound()); - } - } - if (!packets.isEmpty()) { - event.getPlayer().connection.send(new ClientboundBundlePacket(packets)); - } - } - - /** - * Handles initial syncing of entity attachments, except for a player's own attachments. - */ - public static void sendEntityPairingData(Entity entity, ServerPlayer to, Consumer> packetConsumer) { - var packet = syncInitialAttachments(entity, to); - if (packet != null) { - packetConsumer.accept(packet.toVanillaClientbound()); - } - } - - /** - * Handles initial syncing of a player's own attachments. - */ - @SubscribeEvent - public static void onPlayerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) { - var player = (ServerPlayer) event.getEntity(); - var packet = syncInitialAttachments(event.getEntity(), player); - if (packet != null) { - player.connection.send(packet.toVanillaClientbound()); - } - } - - /** - * Handles initial syncing of level attachments. Needs to be called for login, respawn and teleports. - */ - public static void sendLevelInfo(ServerLevel level, ServerPlayer to) { - var packet = syncInitialAttachments(level, to); - if (packet != null) { - to.connection.send(packet.toVanillaClientbound()); - } - } - - public static void receiveSyncedDataAttachments(AttachmentHolder holder, RegistryAccess registryAccess, List> types, byte[] bytes) { - var buf = new RegistryFriendlyByteBuf(Unpooled.wrappedBuffer(bytes), registryAccess, ConnectionType.NEOFORGE); - try { - for (var type : types) { - @SuppressWarnings("unchecked") - var syncHandler = (IAttachmentSyncHandler) type.syncHandler; - if (syncHandler == null) { - throw new IllegalArgumentException("Received synced attachment type without a sync handler registered: " + NeoForgeRegistries.ATTACHMENT_TYPES.getKey(type)); - } - // TODO: need to be careful that the right holder is passed! (when delegating!) - var result = syncHandler.read(holder.getExposedHolder(), buf); - if (result == null) { - if (holder.attachments != null) { - holder.attachments.remove(type); - } - } else { - holder.getAttachmentMap().put(type, result); - } - } - } catch (Exception exception) { - throw new RuntimeException("Encountered exception when reading synced data attachments: " + types, exception); - } finally { - buf.release(); - } - } - private AttachmentInternals() {} } diff --git a/src/main/java/net/neoforged/neoforge/attachment/AttachmentSync.java b/src/main/java/net/neoforged/neoforge/attachment/AttachmentSync.java new file mode 100644 index 00000000000..2d494b61eb3 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/attachment/AttachmentSync.java @@ -0,0 +1,237 @@ +package net.neoforged.neoforge.attachment; + +import io.netty.buffer.Unpooled; +import net.minecraft.core.Registry; +import net.minecraft.core.RegistryAccess; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientGamePacketListener; +import net.minecraft.network.protocol.game.ClientboundBundlePacket; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.chunk.LevelChunk; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.common.util.FriendlyByteBufUtil; +import net.neoforged.neoforge.event.entity.player.PlayerEvent; +import net.neoforged.neoforge.event.level.ChunkWatchEvent; +import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; +import net.neoforged.neoforge.network.connection.ConnectionType; +import net.neoforged.neoforge.network.payload.SyncAttachmentsPayload; +import net.neoforged.neoforge.registries.NeoForgeRegistries; +import net.neoforged.neoforge.registries.RegistryBuilder; +import net.neoforged.neoforge.registries.callback.AddCallback; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +@ApiStatus.Internal +@EventBusSubscriber(modid = NeoForgeVersion.MOD_ID) +public final class AttachmentSync { + /** + * Contains all entries added to {@link NeoForgeRegistries#ATTACHMENT_TYPES} with a sync handler. + * Should never be registered against directly. + */ + public static final Registry> SYNCED_ATTACHMENT_TYPES = new RegistryBuilder<>( + ResourceKey.>createRegistryKey( + ResourceLocation.fromNamespaceAndPath(NeoForgeVersion.MOD_ID, "synced_attachment_types"))) + .sync(true) + .create(); + + public static final AddCallback> ATTACHMENT_TYPE_ADD_CALLBACK = (registry, id, key, value) -> { + if (value.syncHandler != null) { + Registry.register(SYNCED_ATTACHMENT_TYPES, key.location(), value); + } + }; + + static SyncAttachmentsPayload.Target syncTarget(AttachmentHolder holder) { + return switch (holder) { + case BlockEntity blockEntity -> new SyncAttachmentsPayload.BlockEntityTarget(blockEntity.getBlockPos()); + case AttachmentHolder.AsField asField when asField.getExposedHolder() instanceof LevelChunk chunk -> new SyncAttachmentsPayload.ChunkTarget(chunk.getPos()); + case Entity entity -> new SyncAttachmentsPayload.EntityTarget(entity.getId()); + case Level ignored -> new SyncAttachmentsPayload.LevelTarget(); + default -> throw new UnsupportedOperationException("Attachment holder class is not supported: " + holder); + }; + } + + private static void syncUpdate(AttachmentHolder holder, AttachmentType type, List players) { + RegistryAccess registryAccess = null; + for (var player : players) { + if (type.syncHandler.sendToPlayer(holder.getExposedHolder(), player)) { + registryAccess = player.registryAccess(); + break; + } + } + // This also serves as a short-circuit if there are no players to sync data to. + if (registryAccess == null) { + return; + } + var data = FriendlyByteBufUtil.writeCustomData(buf -> { + // TODO: what if data is missing? + type.syncHandler.write(buf, holder.getData(type), false); + }, registryAccess); + var packet = new SyncAttachmentsPayload(syncTarget(holder), List.of(type), data).toVanillaClientbound(); + for (var player : players) { + if (type.syncHandler.sendToPlayer(holder.getExposedHolder(), player)) { + player.connection.send(packet); + } + } + } + + public static void syncBlockEntityUpdate(BlockEntity blockEntity, AttachmentType type) { + if (type.syncHandler == null || !(blockEntity.getLevel() instanceof ServerLevel serverLevel)) { + return; + } + syncUpdate(blockEntity, type, serverLevel.getChunkSource().chunkMap.getPlayers(new ChunkPos(blockEntity.getBlockPos()), false)); + } + + public static void syncChunkUpdate(LevelChunk chunk, AttachmentHolder.AsField holder, AttachmentType type) { + if (type.syncHandler == null || !(chunk.getLevel() instanceof ServerLevel serverLevel)) { + return; + } + syncUpdate(holder, type, serverLevel.getChunkSource().chunkMap.getPlayers(chunk.getPos(), false)); + } + + public static void syncEntityUpdate(Entity entity, AttachmentType type) { + if (type.syncHandler == null || !(entity.level() instanceof ServerLevel serverLevel)) { + return; + } + var players = serverLevel.getChunkSource().chunkMap.getPlayersWatching(entity); + if (entity instanceof ServerPlayer serverPlayer) { + // Players do not track themselves + var newPlayers = new ArrayList(players.size() + 1); + newPlayers.addAll(players); + newPlayers.add(serverPlayer); + players = newPlayers; + } + syncUpdate(entity, type, players); + } + + public static void syncLevelUpdate(ServerLevel level, AttachmentType type) { + if (type.syncHandler == null) { + return; + } + syncUpdate(level, type, level.players()); + } + + @Nullable + private static SyncAttachmentsPayload syncInitialAttachments(AttachmentHolder holder, ServerPlayer to) { + if (holder.attachments == null) { + return null; + } + boolean anySyncableAttachment = false; + for (var attachment : holder.attachments.keySet()) { + anySyncableAttachment = anySyncableAttachment | attachment.syncHandler != null; + } + if (!anySyncableAttachment) { + return null; + } + List> syncedTypes = new ArrayList<>(); + var data = FriendlyByteBufUtil.writeCustomData(buf -> { + for (var entry : holder.attachments.entrySet()) { + AttachmentType type = entry.getKey(); + @SuppressWarnings("unchecked") + var syncHandler = (IAttachmentSyncHandler) type.syncHandler; + if (syncHandler != null) { + int indexBefore = buf.writerIndex(); + syncHandler.write(buf, entry.getValue(), true); + if (indexBefore < buf.writerIndex()) { + // Actually wrote something + syncedTypes.add(type); + } + } + } + }, to.registryAccess()); + return new SyncAttachmentsPayload(syncTarget(holder), syncedTypes, data); + } + + /** + * Handles initial syncing of block entity and chunk attachments. + */ + @SubscribeEvent + public static void onChunkSent(ChunkWatchEvent.Sent event) { + List> packets = new ArrayList<>(); + var chunkPayload = syncInitialAttachments(event.getChunk().getAttachmentHolder(), event.getPlayer()); + if (chunkPayload != null) { + packets.add(chunkPayload.toVanillaClientbound()); + } + for (var blockEntity : event.getChunk().getBlockEntities().values()) { + var blockEntityPayload = syncInitialAttachments(blockEntity, event.getPlayer()); + if (blockEntityPayload != null) { + packets.add(blockEntityPayload.toVanillaClientbound()); + } + } + if (!packets.isEmpty()) { + event.getPlayer().connection.send(new ClientboundBundlePacket(packets)); + } + } + + /** + * Handles initial syncing of entity attachments, except for a player's own attachments. + */ + public static void sendEntityPairingData(Entity entity, ServerPlayer to, Consumer> packetConsumer) { + var packet = syncInitialAttachments(entity, to); + if (packet != null) { + packetConsumer.accept(packet.toVanillaClientbound()); + } + } + + /** + * Handles initial syncing of a player's own attachments. + */ + @SubscribeEvent + public static void onPlayerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) { + var player = (ServerPlayer) event.getEntity(); + var packet = syncInitialAttachments(event.getEntity(), player); + if (packet != null) { + player.connection.send(packet.toVanillaClientbound()); + } + } + + /** + * Handles initial syncing of level attachments. Needs to be called for login, respawn and teleports. + */ + public static void sendLevelInfo(ServerLevel level, ServerPlayer to) { + var packet = syncInitialAttachments(level, to); + if (packet != null) { + to.connection.send(packet.toVanillaClientbound()); + } + } + + public static void receiveSyncedDataAttachments(AttachmentHolder holder, RegistryAccess registryAccess, List> types, byte[] bytes) { + var buf = new RegistryFriendlyByteBuf(Unpooled.wrappedBuffer(bytes), registryAccess, ConnectionType.NEOFORGE); + try { + for (var type : types) { + @SuppressWarnings("unchecked") + var syncHandler = (IAttachmentSyncHandler) type.syncHandler; + if (syncHandler == null) { + throw new IllegalArgumentException("Received synced attachment type without a sync handler registered: " + NeoForgeRegistries.ATTACHMENT_TYPES.getKey(type)); + } + // TODO: need to be careful that the right holder is passed! (when delegating!) + var result = syncHandler.read(holder.getExposedHolder(), buf); + if (result == null) { + if (holder.attachments != null) { + holder.attachments.remove(type); + } + } else { + holder.getAttachmentMap().put(type, result); + } + } + } catch (Exception exception) { + throw new RuntimeException("Encountered exception when reading synced data attachments: " + types, exception); + } finally { + buf.release(); + } + } + + private AttachmentSync() {} +} diff --git a/src/main/java/net/neoforged/neoforge/network/handlers/ClientPayloadHandler.java b/src/main/java/net/neoforged/neoforge/network/handlers/ClientPayloadHandler.java index 7e139e89f0a..e4da7edfbaf 100644 --- a/src/main/java/net/neoforged/neoforge/network/handlers/ClientPayloadHandler.java +++ b/src/main/java/net/neoforged/neoforge/network/handlers/ClientPayloadHandler.java @@ -24,8 +24,10 @@ import net.minecraft.world.entity.Entity; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.status.ChunkStatus; import net.neoforged.neoforge.attachment.AttachmentInternals; +import net.neoforged.neoforge.attachment.AttachmentSync; import net.neoforged.neoforge.common.world.AuxiliaryLightManager; import net.neoforged.neoforge.common.world.LevelChunkAuxiliaryLightManager; import net.neoforged.neoforge.entity.IEntityWithComplexSpawn; @@ -166,7 +168,7 @@ public static void handle(SyncAttachmentsPayload payload, IPayloadContext contex if (blockEntity == null) { LOGGER.warn("Received synced attachments from unknown block entity"); } else { - AttachmentInternals.receiveSyncedDataAttachments(blockEntity, context.player().registryAccess(), payload.types(), payload.syncPayload()); + AttachmentSync.receiveSyncedDataAttachments(blockEntity, context.player().registryAccess(), payload.types(), payload.syncPayload()); } } case SyncAttachmentsPayload.ChunkTarget target -> { @@ -175,7 +177,7 @@ public static void handle(SyncAttachmentsPayload payload, IPayloadContext contex if (chunk == null) { LOGGER.warn("Received synced attachments from unknown chunk"); } else { - chunk.receiveSyncedAttachments(payload.types(), payload.syncPayload()); + AttachmentSync.receiveSyncedDataAttachments(chunk.getAttachmentHolder(), chunk.getLevel().registryAccess(), payload.types(), payload.syncPayload()); } } case SyncAttachmentsPayload.EntityTarget target -> { @@ -183,11 +185,11 @@ public static void handle(SyncAttachmentsPayload payload, IPayloadContext contex if (entity == null) { LOGGER.warn("Received synced attachments from unknown entity"); } else { - AttachmentInternals.receiveSyncedDataAttachments(entity, entity.registryAccess(), payload.types(), payload.syncPayload()); + AttachmentSync.receiveSyncedDataAttachments(entity, entity.registryAccess(), payload.types(), payload.syncPayload()); } } case SyncAttachmentsPayload.LevelTarget ignored -> { - AttachmentInternals.receiveSyncedDataAttachments(context.player().level(), context.player().registryAccess(), payload.types(), payload.syncPayload()); + AttachmentSync.receiveSyncedDataAttachments(context.player().level(), context.player().registryAccess(), payload.types(), payload.syncPayload()); } } } diff --git a/src/main/java/net/neoforged/neoforge/network/payload/SyncAttachmentsPayload.java b/src/main/java/net/neoforged/neoforge/network/payload/SyncAttachmentsPayload.java index af7d67af533..eda022682db 100644 --- a/src/main/java/net/neoforged/neoforge/network/payload/SyncAttachmentsPayload.java +++ b/src/main/java/net/neoforged/neoforge/network/payload/SyncAttachmentsPayload.java @@ -13,6 +13,7 @@ import net.minecraft.world.level.chunk.LevelChunk; import net.neoforged.neoforge.attachment.AttachmentHolder; import net.neoforged.neoforge.attachment.AttachmentInternals; +import net.neoforged.neoforge.attachment.AttachmentSync; import net.neoforged.neoforge.attachment.AttachmentType; import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; import net.neoforged.neoforge.network.codec.NeoForgeStreamCodecs; @@ -31,7 +32,7 @@ public record SyncAttachmentsPayload( public static final StreamCodec STREAM_CODEC = StreamCodec.composite( Target.STREAM_CODEC, SyncAttachmentsPayload::target, - ByteBufCodecs.registry(AttachmentInternals.SYNCED_ATTACHMENT_TYPES.key()).apply(ByteBufCodecs.list()), + ByteBufCodecs.registry(AttachmentSync.SYNCED_ATTACHMENT_TYPES.key()).apply(ByteBufCodecs.list()), SyncAttachmentsPayload::types, NeoForgeStreamCodecs.UNBOUNDED_BYTE_ARRAY, SyncAttachmentsPayload::syncPayload, diff --git a/src/main/java/net/neoforged/neoforge/registries/NeoForgeRegistriesSetup.java b/src/main/java/net/neoforged/neoforge/registries/NeoForgeRegistriesSetup.java index 556b22ba262..0d3538b94b2 100644 --- a/src/main/java/net/neoforged/neoforge/registries/NeoForgeRegistriesSetup.java +++ b/src/main/java/net/neoforged/neoforge/registries/NeoForgeRegistriesSetup.java @@ -10,6 +10,7 @@ import net.minecraft.core.registries.BuiltInRegistries; import net.neoforged.bus.api.IEventBus; import net.neoforged.neoforge.attachment.AttachmentInternals; +import net.neoforged.neoforge.attachment.AttachmentSync; import org.jetbrains.annotations.ApiStatus; @ApiStatus.Internal @@ -72,7 +73,7 @@ private static void registerRegistries(NewRegistryEvent event) { event.register(NeoForgeRegistries.FLUID_INGREDIENT_TYPES); event.register(NeoForgeRegistries.CONDITION_SERIALIZERS); event.register(NeoForgeRegistries.ATTACHMENT_TYPES); - event.register(AttachmentInternals.SYNCED_ATTACHMENT_TYPES); + event.register(AttachmentSync.SYNCED_ATTACHMENT_TYPES); } private static void modifyRegistries(ModifyRegistriesEvent event) { @@ -84,6 +85,6 @@ private static void modifyRegistries(ModifyRegistriesEvent event) { BuiltInRegistries.ITEM.addCallback(NeoForgeRegistryCallbacks.ItemCallbacks.INSTANCE); BuiltInRegistries.ATTRIBUTE.addCallback(NeoForgeRegistryCallbacks.AttributeCallbacks.INSTANCE); BuiltInRegistries.POINT_OF_INTEREST_TYPE.addCallback(NeoForgeRegistryCallbacks.PoiTypeCallbacks.INSTANCE); - NeoForgeRegistries.ATTACHMENT_TYPES.addCallback(AttachmentInternals.ATTACHMENT_TYPE_ADD_CALLBACK); + NeoForgeRegistries.ATTACHMENT_TYPES.addCallback(AttachmentSync.ATTACHMENT_TYPE_ADD_CALLBACK); } } From dabefbb1960dc367d3a78f64bc2f37f62abaf9ff Mon Sep 17 00:00:00 2001 From: Technici4n <13494793+Technici4n@users.noreply.github.com> Date: Tue, 7 Jan 2025 00:07:53 +0100 Subject: [PATCH 14/15] Update read signature and document --- .../neoforge/attachment/AttachmentSync.java | 4 +-- .../neoforge/attachment/AttachmentType.java | 2 +- .../attachment/IAttachmentHolder.java | 1 - .../attachment/IAttachmentSyncHandler.java | 33 +++++++++++++++++-- .../neoforge/oldtest/AttachmentSyncTest.java | 2 +- 5 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/main/java/net/neoforged/neoforge/attachment/AttachmentSync.java b/src/main/java/net/neoforged/neoforge/attachment/AttachmentSync.java index 2d494b61eb3..a42a6feb22e 100644 --- a/src/main/java/net/neoforged/neoforge/attachment/AttachmentSync.java +++ b/src/main/java/net/neoforged/neoforge/attachment/AttachmentSync.java @@ -216,8 +216,8 @@ public static void receiveSyncedDataAttachments(AttachmentHolder holder, Registr if (syncHandler == null) { throw new IllegalArgumentException("Received synced attachment type without a sync handler registered: " + NeoForgeRegistries.ATTACHMENT_TYPES.getKey(type)); } - // TODO: need to be careful that the right holder is passed! (when delegating!) - var result = syncHandler.read(holder.getExposedHolder(), buf); + var previousValue = holder.attachments == null ? null : holder.attachments.get(type); + var result = syncHandler.read(holder.getExposedHolder(), buf, previousValue); if (result == null) { if (holder.attachments != null) { holder.attachments.remove(type); diff --git a/src/main/java/net/neoforged/neoforge/attachment/AttachmentType.java b/src/main/java/net/neoforged/neoforge/attachment/AttachmentType.java index 0549d7f4f5b..40e760e3927 100644 --- a/src/main/java/net/neoforged/neoforge/attachment/AttachmentType.java +++ b/src/main/java/net/neoforged/neoforge/attachment/AttachmentType.java @@ -252,7 +252,7 @@ public void write(RegistryFriendlyByteBuf buf, T attachment, boolean initialSync } @Override - public T read(IAttachmentHolder holder, RegistryFriendlyByteBuf buf) { + public T read(IAttachmentHolder holder, RegistryFriendlyByteBuf buf, @Nullable T previousValue) { return streamCodec.decode(buf); } }); diff --git a/src/main/java/net/neoforged/neoforge/attachment/IAttachmentHolder.java b/src/main/java/net/neoforged/neoforge/attachment/IAttachmentHolder.java index 0f092d850c3..7aec4819440 100644 --- a/src/main/java/net/neoforged/neoforge/attachment/IAttachmentHolder.java +++ b/src/main/java/net/neoforged/neoforge/attachment/IAttachmentHolder.java @@ -12,7 +12,6 @@ /** * An object that can hold data attachments. */ -// TODO: needs a method to force re-sync an attachment public interface IAttachmentHolder { /** * Returns {@code true} if there is any data attachments, {@code false} otherwise. diff --git a/src/main/java/net/neoforged/neoforge/attachment/IAttachmentSyncHandler.java b/src/main/java/net/neoforged/neoforge/attachment/IAttachmentSyncHandler.java index 4c89c504043..66eada72100 100644 --- a/src/main/java/net/neoforged/neoforge/attachment/IAttachmentSyncHandler.java +++ b/src/main/java/net/neoforged/neoforge/attachment/IAttachmentSyncHandler.java @@ -19,13 +19,42 @@ * {@link IAttachmentHolder#syncData(AttachmentType)} can be called to trigger syncing. */ public interface IAttachmentSyncHandler { + /** + * Decides whether data should be sent to some player that can see the holder. + * + *

      By default, all players that can see the holder are sent the data. + * A typical use case for this method is to only send player-specific data to that player. + * + *

      The returned value should be consistent for a given holder and player. + * + * @param holder the holder for the attachment, can be cast if the subtype is known + * @param to the player that might receive the data + * @return {@code true} to send data to the player, {@code false} otherwise + */ default boolean sendToPlayer(IAttachmentHolder holder, ServerPlayer to) { return true; } + /** + * Writes attachment data to a buffer. + * + *

      If {@code initialSync} is {@code true}, + * the data should be written in full because the client does not have any previous data. + * + *

      If {@code initialSync} is {@code false}, + * the client already received a previous version of the data. + * In this case, this method is only called once for the attachment, + * and the resulting data is broadcast to all relevant players. + */ void write(RegistryFriendlyByteBuf buf, T attachment, boolean initialSync); - // TODO: we could also return void and let the sync handler call .setData(type, xxx). But that means passing the type somehow. + /** + * Reads attachment data on the client side. + * + * @param holder the attachment holder, can be cast if the subtype is known + * @param previousValue the previous value of the attachment, or {@code null} if there was no previous value + * @return the new value of the attachment, or {@code null} if the attachment should be removed + */ @Nullable - T read(IAttachmentHolder holder, RegistryFriendlyByteBuf buf); + T read(IAttachmentHolder holder, RegistryFriendlyByteBuf buf, @Nullable T previousValue); } diff --git a/tests/src/main/java/net/neoforged/neoforge/oldtest/AttachmentSyncTest.java b/tests/src/main/java/net/neoforged/neoforge/oldtest/AttachmentSyncTest.java index cd628972c73..40aa6e66d57 100644 --- a/tests/src/main/java/net/neoforged/neoforge/oldtest/AttachmentSyncTest.java +++ b/tests/src/main/java/net/neoforged/neoforge/oldtest/AttachmentSyncTest.java @@ -46,7 +46,7 @@ public void write(RegistryFriendlyByteBuf buf, Integer attachment, boolean initi } @Override - public @Nullable Integer read(IAttachmentHolder holder, RegistryFriendlyByteBuf buf) { + public Integer read(IAttachmentHolder holder, RegistryFriendlyByteBuf buf, @Nullable Integer previousValue) { return buf.readInt(); } }) From 8842c6223cb6226065c08dbd29acc7dbca016c8f Mon Sep 17 00:00:00 2001 From: Technici4n <13494793+Technici4n@users.noreply.github.com> Date: Tue, 7 Jan 2025 00:09:55 +0100 Subject: [PATCH 15/15] Format! --- .../selector/EntitySelector.java.patch | 23 ++-------------- .../level/block/entity/BlockEntity.java.patch | 8 +----- .../attachment/AttachmentInternals.java | 27 ------------------- .../neoforge/attachment/AttachmentSync.java | 16 ++++++----- .../attachment/IAttachmentSyncHandler.java | 17 +++++++----- .../handlers/ClientPayloadHandler.java | 2 -- .../payload/SyncAttachmentsPayload.java | 22 +++++++-------- .../registries/NeoForgeRegistriesSetup.java | 1 - .../neoforge/oldtest/AttachmentSyncTest.java | 11 +++++--- 9 files changed, 42 insertions(+), 85 deletions(-) diff --git a/patches/net/minecraft/commands/arguments/selector/EntitySelector.java.patch b/patches/net/minecraft/commands/arguments/selector/EntitySelector.java.patch index 640f467f0ad..ec955e8a61b 100644 --- a/patches/net/minecraft/commands/arguments/selector/EntitySelector.java.patch +++ b/patches/net/minecraft/commands/arguments/selector/EntitySelector.java.patch @@ -1,24 +1,5 @@ --- a/net/minecraft/commands/arguments/selector/EntitySelector.java +++ b/net/minecraft/commands/arguments/selector/EntitySelector.java -@@ -10,15 +_,18 @@ - import javax.annotation.Nullable; - import net.minecraft.Util; - import net.minecraft.advancements.critereon.MinMaxBounds; -+import net.minecraft.client.multiplayer.ClientLevel; - import net.minecraft.commands.CommandSourceStack; - import net.minecraft.commands.arguments.EntityArgument; - import net.minecraft.network.chat.Component; - import net.minecraft.network.chat.ComponentUtils; - import net.minecraft.server.level.ServerLevel; - import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.util.AbortableIterationConsumer; - import net.minecraft.world.entity.Entity; - import net.minecraft.world.entity.EntityType; - import net.minecraft.world.flag.FeatureFlagSet; -+import net.minecraft.world.level.Level; - import net.minecraft.world.level.entity.EntityTypeTest; - import net.minecraft.world.phys.AABB; - import net.minecraft.world.phys.Vec3; @@ -105,7 +_,7 @@ } @@ -44,7 +25,7 @@ } - private void addEntities(List p_121155_, ServerLevel p_121156_, @Nullable AABB p_352947_, Predicate p_121158_) { -+ private void addEntities(List p_121155_, Level p_121156_, @Nullable AABB p_352947_, Predicate p_121158_) { ++ private void addEntities(List p_121155_, net.minecraft.world.level.Level p_121156_, @Nullable AABB p_352947_, Predicate p_121158_) { int i = this.getResultLimit(); if (p_121155_.size() < i) { if (p_352947_ != null) { @@ -54,7 +35,7 @@ + if (p_121156_ instanceof ServerLevel serverLevel) { + serverLevel.getEntities(this.type, p_121158_, p_121155_, i); + } else { -+ ((ClientLevel) p_121156_).entitiesForRendering().forEach(entity -> { ++ ((net.minecraft.client.multiplayer.ClientLevel) p_121156_).entitiesForRendering().forEach(entity -> { + if (p_121158_.test(entity) && p_121155_.size() < i) { + p_121155_.add(entity); + } diff --git a/patches/net/minecraft/world/level/block/entity/BlockEntity.java.patch b/patches/net/minecraft/world/level/block/entity/BlockEntity.java.patch index a42ab602dc0..e3b8517af8d 100644 --- a/patches/net/minecraft/world/level/block/entity/BlockEntity.java.patch +++ b/patches/net/minecraft/world/level/block/entity/BlockEntity.java.patch @@ -1,12 +1,6 @@ --- a/net/minecraft/world/level/block/entity/BlockEntity.java +++ b/net/minecraft/world/level/block/entity/BlockEntity.java -@@ -21,13 +_,15 @@ - import net.minecraft.network.protocol.Packet; - import net.minecraft.network.protocol.game.ClientGamePacketListener; - import net.minecraft.resources.ResourceLocation; -+import net.minecraft.server.level.ServerLevel; - import net.minecraft.world.item.ItemStack; - import net.minecraft.world.level.Level; +@@ -26,8 +_,9 @@ import net.minecraft.world.level.block.state.BlockState; import org.slf4j.Logger; diff --git a/src/main/java/net/neoforged/neoforge/attachment/AttachmentInternals.java b/src/main/java/net/neoforged/neoforge/attachment/AttachmentInternals.java index b74a7aa1e9b..b4b33eacca6 100644 --- a/src/main/java/net/neoforged/neoforge/attachment/AttachmentInternals.java +++ b/src/main/java/net/neoforged/neoforge/attachment/AttachmentInternals.java @@ -5,43 +5,16 @@ package net.neoforged.neoforge.attachment; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; import java.util.function.Predicate; - -import io.netty.buffer.Unpooled; import net.minecraft.core.HolderLookup; -import net.minecraft.core.Registry; -import net.minecraft.core.RegistryAccess; -import net.minecraft.network.RegistryFriendlyByteBuf; -import net.minecraft.network.protocol.Packet; -import net.minecraft.network.protocol.game.ClientGamePacketListener; -import net.minecraft.network.protocol.game.ClientboundBundlePacket; -import net.minecraft.resources.ResourceKey; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.Entity; -import net.minecraft.world.level.ChunkPos; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.chunk.LevelChunk; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.neoforge.common.extensions.IEntityExtension; -import net.neoforged.neoforge.common.util.FriendlyByteBufUtil; import net.neoforged.neoforge.event.entity.living.LivingConversionEvent; import net.neoforged.neoforge.event.entity.player.PlayerEvent; -import net.neoforged.neoforge.event.level.ChunkWatchEvent; import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; -import net.neoforged.neoforge.network.connection.ConnectionType; -import net.neoforged.neoforge.network.payload.SyncAttachmentsPayload; -import net.neoforged.neoforge.registries.NeoForgeRegistries; -import net.neoforged.neoforge.registries.RegistryBuilder; -import net.neoforged.neoforge.registries.callback.AddCallback; import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.Nullable; @ApiStatus.Internal @EventBusSubscriber(modid = NeoForgeVersion.MOD_ID) diff --git a/src/main/java/net/neoforged/neoforge/attachment/AttachmentSync.java b/src/main/java/net/neoforged/neoforge/attachment/AttachmentSync.java index a42a6feb22e..8e60708a05c 100644 --- a/src/main/java/net/neoforged/neoforge/attachment/AttachmentSync.java +++ b/src/main/java/net/neoforged/neoforge/attachment/AttachmentSync.java @@ -1,6 +1,14 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + package net.neoforged.neoforge.attachment; import io.netty.buffer.Unpooled; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; import net.minecraft.core.Registry; import net.minecraft.core.RegistryAccess; import net.minecraft.network.RegistryFriendlyByteBuf; @@ -30,10 +38,6 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - @ApiStatus.Internal @EventBusSubscriber(modid = NeoForgeVersion.MOD_ID) public final class AttachmentSync { @@ -44,8 +48,8 @@ public final class AttachmentSync { public static final Registry> SYNCED_ATTACHMENT_TYPES = new RegistryBuilder<>( ResourceKey.>createRegistryKey( ResourceLocation.fromNamespaceAndPath(NeoForgeVersion.MOD_ID, "synced_attachment_types"))) - .sync(true) - .create(); + .sync(true) + .create(); public static final AddCallback> ATTACHMENT_TYPE_ADD_CALLBACK = (registry, id, key, value) -> { if (value.syncHandler != null) { diff --git a/src/main/java/net/neoforged/neoforge/attachment/IAttachmentSyncHandler.java b/src/main/java/net/neoforged/neoforge/attachment/IAttachmentSyncHandler.java index 66eada72100..b90c8a4505d 100644 --- a/src/main/java/net/neoforged/neoforge/attachment/IAttachmentSyncHandler.java +++ b/src/main/java/net/neoforged/neoforge/attachment/IAttachmentSyncHandler.java @@ -1,3 +1,8 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + package net.neoforged.neoforge.attachment; import net.minecraft.network.RegistryFriendlyByteBuf; @@ -9,10 +14,10 @@ * *

      Sync is handled automatically in the following cases: *

        - *
      • A client is receiving initial data for this attachment holder.
      • - *
      • An attachment is default-created through {@link IAttachmentHolder#getData(AttachmentType)}.
      • - *
      • An attachment is updated through {@link IAttachmentHolder#setData(AttachmentType, Object)}.
      • - *
      • An attachment is removed through {@link IAttachmentHolder#removeData(AttachmentType)}.
      • + *
      • A client is receiving initial data for this attachment holder.
      • + *
      • An attachment is default-created through {@link IAttachmentHolder#getData(AttachmentType)}.
      • + *
      • An attachment is updated through {@link IAttachmentHolder#setData(AttachmentType, Object)}.
      • + *
      • An attachment is removed through {@link IAttachmentHolder#removeData(AttachmentType)}.
      • *
      * *

      For other cases such as modifications to mutable synced attachments, @@ -28,7 +33,7 @@ public interface IAttachmentSyncHandler { *

      The returned value should be consistent for a given holder and player. * * @param holder the holder for the attachment, can be cast if the subtype is known - * @param to the player that might receive the data + * @param to the player that might receive the data * @return {@code true} to send data to the player, {@code false} otherwise */ default boolean sendToPlayer(IAttachmentHolder holder, ServerPlayer to) { @@ -51,7 +56,7 @@ default boolean sendToPlayer(IAttachmentHolder holder, ServerPlayer to) { /** * Reads attachment data on the client side. * - * @param holder the attachment holder, can be cast if the subtype is known + * @param holder the attachment holder, can be cast if the subtype is known * @param previousValue the previous value of the attachment, or {@code null} if there was no previous value * @return the new value of the attachment, or {@code null} if the attachment should be removed */ diff --git a/src/main/java/net/neoforged/neoforge/network/handlers/ClientPayloadHandler.java b/src/main/java/net/neoforged/neoforge/network/handlers/ClientPayloadHandler.java index e4da7edfbaf..4d2461c0bb9 100644 --- a/src/main/java/net/neoforged/neoforge/network/handlers/ClientPayloadHandler.java +++ b/src/main/java/net/neoforged/neoforge/network/handlers/ClientPayloadHandler.java @@ -24,9 +24,7 @@ import net.minecraft.world.entity.Entity; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.MenuType; -import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.status.ChunkStatus; -import net.neoforged.neoforge.attachment.AttachmentInternals; import net.neoforged.neoforge.attachment.AttachmentSync; import net.neoforged.neoforge.common.world.AuxiliaryLightManager; import net.neoforged.neoforge.common.world.LevelChunkAuxiliaryLightManager; diff --git a/src/main/java/net/neoforged/neoforge/network/payload/SyncAttachmentsPayload.java b/src/main/java/net/neoforged/neoforge/network/payload/SyncAttachmentsPayload.java index eda022682db..083527d228d 100644 --- a/src/main/java/net/neoforged/neoforge/network/payload/SyncAttachmentsPayload.java +++ b/src/main/java/net/neoforged/neoforge/network/payload/SyncAttachmentsPayload.java @@ -1,26 +1,23 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + package net.neoforged.neoforge.network.payload; +import java.util.List; import net.minecraft.core.BlockPos; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.entity.Entity; import net.minecraft.world.level.ChunkPos; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.chunk.LevelChunk; -import net.neoforged.neoforge.attachment.AttachmentHolder; -import net.neoforged.neoforge.attachment.AttachmentInternals; import net.neoforged.neoforge.attachment.AttachmentSync; import net.neoforged.neoforge.attachment.AttachmentType; import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; import net.neoforged.neoforge.network.codec.NeoForgeStreamCodecs; import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.Nullable; - -import java.util.List; @ApiStatus.Internal public record SyncAttachmentsPayload( @@ -28,6 +25,7 @@ public record SyncAttachmentsPayload( List> types, byte[] syncPayload) implements CustomPacketPayload { + public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(NeoForgeVersion.MOD_ID, "sync_attachments"));; public static final StreamCodec STREAM_CODEC = StreamCodec.composite( Target.STREAM_CODEC, @@ -42,7 +40,6 @@ public record SyncAttachmentsPayload( public Type type() { return TYPE; } - public sealed interface Target { StreamCodec STREAM_CODEC = StreamCodec.of( (buf, target) -> { @@ -60,7 +57,7 @@ public sealed interface Target { buf.writeVarInt(entityTarget.entity()); } case LevelTarget ignored -> { - buf.writeByte(3); + buf.writeByte(3); } } }, @@ -85,8 +82,11 @@ public sealed interface Target { } public record BlockEntityTarget(BlockPos pos) implements Target {} + public record ChunkTarget(ChunkPos pos) implements Target {} + public record EntityTarget(int entity) implements Target {} + // TODO: Should there be a way to sync overworld data while the player is in another level? (For "global" data). public record LevelTarget() implements Target {} } diff --git a/src/main/java/net/neoforged/neoforge/registries/NeoForgeRegistriesSetup.java b/src/main/java/net/neoforged/neoforge/registries/NeoForgeRegistriesSetup.java index 0d3538b94b2..dba99f3ee5a 100644 --- a/src/main/java/net/neoforged/neoforge/registries/NeoForgeRegistriesSetup.java +++ b/src/main/java/net/neoforged/neoforge/registries/NeoForgeRegistriesSetup.java @@ -9,7 +9,6 @@ import net.minecraft.core.Registry; import net.minecraft.core.registries.BuiltInRegistries; import net.neoforged.bus.api.IEventBus; -import net.neoforged.neoforge.attachment.AttachmentInternals; import net.neoforged.neoforge.attachment.AttachmentSync; import org.jetbrains.annotations.ApiStatus; diff --git a/tests/src/main/java/net/neoforged/neoforge/oldtest/AttachmentSyncTest.java b/tests/src/main/java/net/neoforged/neoforge/oldtest/AttachmentSyncTest.java index 40aa6e66d57..7e3065dda61 100644 --- a/tests/src/main/java/net/neoforged/neoforge/oldtest/AttachmentSyncTest.java +++ b/tests/src/main/java/net/neoforged/neoforge/oldtest/AttachmentSyncTest.java @@ -1,3 +1,8 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + package net.neoforged.neoforge.oldtest; import com.mojang.brigadier.Command; @@ -8,6 +13,7 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; import com.mojang.serialization.Codec; +import java.util.function.Supplier; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; import net.minecraft.commands.arguments.EntityArgument; @@ -29,8 +35,6 @@ import net.neoforged.neoforge.registries.NeoForgeRegistries; import org.jetbrains.annotations.Nullable; -import java.util.function.Supplier; - @Mod(AttachmentSyncTest.MOD_ID) public class AttachmentSyncTest { public static final String MOD_ID = "attachment_sync_test"; @@ -97,8 +101,7 @@ private static void registerCommands(CommandDispatcher dispa .then( addGetSet( Commands.argument("entity", EntityArgument.entity()), - context -> EntityArgument.getEntity(context, "entity") - ))) + context -> EntityArgument.getEntity(context, "entity")))) .then( addGetSet( Commands.literal("level"),