Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Syncable Data Attachments #1823

Draft
wants to merge 15 commits into
base: 1.21.x
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions patches/net/minecraft/server/level/ServerEntity.java.patch
Original file line number Diff line number Diff line change
Expand Up @@ -12,31 +12,39 @@
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()));
+ net.neoforged.neoforge.event.EventHooks.onStopEntityTracking(this.entity, p_8535_);
}

public void addPairing(ServerPlayer p_8542_) {
List<Packet<? super ClientGamePacketListener>> 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<Packet<ClientGamePacketListener>> p_289563_) {
+ public void sendPairingData(ServerPlayer p_289562_, net.neoforged.neoforge.network.bundle.PacketAndPayloadAcceptor<net.minecraft.network.protocol.game.ClientGamePacketListener> p_289563_) {
+ public void sendPairingData(ServerPlayer p_289562_, Consumer<Packet<? super ClientGamePacketListener>> p_289563_) {
Shadows-of-Fire marked this conversation as resolved.
Show resolved Hide resolved
if (this.entity.isRemoved()) {
LOGGER.warn("Fetching packet for removed entity {}", this.entity);
}

Packet<ClientGamePacketListener> 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() {
6 changes: 4 additions & 2 deletions patches/net/minecraft/world/entity/Entity.java.patch
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@
}

public void checkDespawn() {
@@ -3627,6 +_,128 @@
@@ -3627,6 +_,130 @@

public boolean mayInteract(ServerLevel p_376870_, BlockPos p_146844_) {
return true;
Expand Down Expand Up @@ -551,8 +551,10 @@
+ @Override
+ @Nullable
+ public final <T> T setData(net.neoforged.neoforge.attachment.AttachmentType<T> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,33 @@

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.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;
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.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;

@ApiStatus.Internal
@EventBusSubscriber(modid = NeoForgeVersion.MOD_ID)
Expand Down Expand Up @@ -63,5 +80,83 @@ public static void onLivingConvert(LivingConversionEvent.Post event) {
event.getOutcome().copyAttachmentsFrom(event.getEntity(), true);
}

public static <T> void syncEntityAttachment(Entity entity, AttachmentType<T> type, T value, AttachmentSyncReason reason) {
if (type.syncHandler == null || !(entity.level() instanceof ServerLevel serverLevel)) {
return;
}
for (var player : serverLevel.players()) {
List<AttachmentType<?>> 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;
if (holder.attachments == null) {
return null;
}
List<AttachmentType<?>> syncedTypes = new ArrayList<>();
var data = FriendlyByteBufUtil.writeCustomData(buf -> {
for (var entry : holder.attachments.entrySet()) {
AttachmentType<?> type = entry.getKey();
@SuppressWarnings("unchecked")
var syncHandler = (IAttachmentSyncHandler<Object>) 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<Packet<? super ClientGamePacketListener>> packetConsumer) {
var packet = syncEntityAttachments(entity, to, AttachmentSyncReason.NEW_ENTITY);
if (packet != null) {
packetConsumer.accept(packet.toVanillaClientbound());
}
}

public static void receiveSyncedDataAttachments(AttachmentHolder holder, RegistryAccess registryAccess, List<AttachmentType<?>> types, byte[] bytes) {
var buf = new RegistryFriendlyByteBuf(Unpooled.wrappedBuffer(bytes), registryAccess, ConnectionType.NEOFORGE);
try {
for (var type : types) {
@SuppressWarnings("unchecked")
var syncHandler = (IAttachmentSyncHandler<Object>) 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() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package net.neoforged.neoforge.attachment;

public enum AttachmentSyncReason {
Technici4n marked this conversation as resolved.
Show resolved Hide resolved
// Might not actually be new on the server, but it is now for the client
NEW_ENTITY,
ENTITY_SYNC_REQUESTED,
}
48 changes: 34 additions & 14 deletions src/main/java/net/neoforged/neoforge/attachment/AttachmentType.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@
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.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.chunk.ChunkAccess;
Expand All @@ -39,19 +41,13 @@
* <li>Serializable entity attachments are not copied on death by default (but they are copied when returning from the end).</li>
* <li>Serializable entity attachments can opt into copying on death via {@link Builder#copyOnDeath()}.</li>
* </ul>
* <h3>{@link ItemStack}-exclusive behavior:</h3>
* <ul>
* <li>Serializable item stack attachments are synced between the server and the client.</li>
* <li>Serializable item stack attachments are copied when an item stack is copied.</li>
* <li>Serializable item stack attachments must match for item stack comparison to succeed.</li>
* </ul>
* <h3>{@link Level}-exclusive behavior:</h3>
* <ul>
* <li>(nothing)</li>
* </ul>
* <h3>{@link ChunkAccess}-exclusive behavior:</h3>
* <ul>
* <li>Modifications to attachments should be followed by a call to {@link ChunkAccess#setUnsaved(boolean)}.</li>
* <li>Modifications to attachments should be followed by a call to {@link ChunkAccess#markUnsaved}.</li>
* <li>Serializable attachments are copied from a {@link ProtoChunk} to a {@link LevelChunk} on promotion.</li>
* </ul>
*/
Expand All @@ -61,12 +57,15 @@ public final class AttachmentType<T> {
final IAttachmentSerializer<?, T> serializer;
final boolean copyOnDeath;
final IAttachmentCopyHandler<T> copyHandler;
@Nullable
IAttachmentSyncHandler<T> syncHandler;

private AttachmentType(Builder<T> 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 <T, H extends Tag> IAttachmentCopyHandler<T> defaultCopyHandler(@Nullable IAttachmentSerializer<H, T> serializer) {
Expand Down Expand Up @@ -152,6 +151,8 @@ public static class Builder<T> {
private boolean copyOnDeath;
@Nullable
private IAttachmentCopyHandler<T> copyHandler;
@Nullable
private IAttachmentSyncHandler<T> syncHandler;

private Builder(Function<IAttachmentHolder, T> defaultValueSupplier) {
this.defaultValueSupplier = defaultValueSupplier;
Expand All @@ -174,9 +175,6 @@ public Builder<T> serialize(IAttachmentSerializer<?, T> serializer) {
/**
* Requests that this attachment be persisted to disk (on the logical server side), using a {@link Codec}.
*
* <p>Using a {@link Codec} to serialize attachments is discouraged for item stack attachments,
* for performance reasons. Prefer one of the other options.
*
* <p>Codec-based attachments cannot capture a reference to their holder.
*
* @param codec The codec to use.
Expand All @@ -188,9 +186,6 @@ public Builder<T> serialize(Codec<T> codec) {
/**
* Requests that this attachment be persisted to disk (on the logical server side), using a {@link Codec}.
*
* <p>Using a {@link Codec} to serialize attachments is discouraged for item stack attachments,
* for performance reasons. Prefer one of the other options.
*
* <p>Codec-based attachments cannot capture a reference to their holder.
*
* @param codec The codec to use.
Expand Down Expand Up @@ -239,6 +234,31 @@ public Builder<T> copyHandler(IAttachmentCopyHandler<T> cloner) {
return this;
}

/**
* Requests that this attachment be synced to clients.
*/
public Builder<T> sync(IAttachmentSyncHandler<T> syncHandler) {
Objects.requireNonNull(syncHandler);
this.syncHandler = syncHandler;
return this;
}

// TODO: Predicate<ServerPlayer> version too? Some data is not relevant to other players.
public Builder<T> sync(StreamCodec<? super RegistryFriendlyByteBuf, T> 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<T> build() {
return new AttachmentType<>(this);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package net.neoforged.neoforge.attachment;

import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.Nullable;

public interface IAttachmentSyncHandler<T> {
// TODO: pass target player
void write(RegistryFriendlyByteBuf buf, T attachment, ServerPlayer to, AttachmentSyncReason reason);
Technici4n marked this conversation as resolved.
Show resolved Hide resolved

// 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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}

This file was deleted.

Loading
Loading