package net.minecraft.network.syncher; import com.mojang.logging.LogUtils; import io.netty.handler.codec.DecoderException; import io.netty.handler.codec.EncoderException; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Objects; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.util.ClassTreeIdRegistry; import org.apache.commons.lang3.ObjectUtils; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; /** * Keeps data in sync from server to client for an entity. * A maximum of 254 parameters per entity class can be registered. The system then ensures that these values are updated on the client whenever they change on the server. * * Use {@link #defineId} to register a piece of data for your entity class. * Use {@link #define} during {@link Entity#defineSynchedData} to set the default value for a given parameter. */ public class SynchedEntityData { private static final Logger LOGGER = LogUtils.getLogger(); private static final int MAX_ID_VALUE = 254; static final ClassTreeIdRegistry ID_REGISTRY = new ClassTreeIdRegistry(); private final SyncedDataHolder entity; private final SynchedEntityData.DataItem[] itemsById; private boolean isDirty; SynchedEntityData(SyncedDataHolder entity, SynchedEntityData.DataItem[] itemsById) { this.entity = entity; this.itemsById = itemsById; } /** * Register a piece of data to be kept in sync for an entity class. * This method must be called during a static initializer of an entity class and the first parameter of this method must be that entity class. */ public static EntityDataAccessor defineId(Class clazz, EntityDataSerializer serializer) { if (LOGGER.isDebugEnabled()) { try { Class class_ = Class.forName(Thread.currentThread().getStackTrace()[2].getClassName()); if (!class_.equals(clazz)) { LOGGER.debug("defineId called for: {} from {}", clazz, class_, new RuntimeException()); } } catch (ClassNotFoundException var3) { } } int i = ID_REGISTRY.define(clazz); if (i > 254) { throw new IllegalArgumentException("Data value id is too big with " + i + "! (Max is 254)"); } else { return serializer.createAccessor(i); } } private SynchedEntityData.DataItem getItem(EntityDataAccessor key) { return (SynchedEntityData.DataItem)this.itemsById[key.id()]; } /** * Get the value of the given key for this entity. */ public T get(EntityDataAccessor key) { return this.getItem(key).getValue(); } /** * Set the value of the given key for this entity. */ public void set(EntityDataAccessor key, T value) { this.set(key, value, false); } public void set(EntityDataAccessor key, T value, boolean force) { SynchedEntityData.DataItem dataItem = this.getItem(key); if (force || ObjectUtils.notEqual(value, dataItem.getValue())) { dataItem.setValue(value); this.entity.onSyncedDataUpdated(key); dataItem.setDirty(true); this.isDirty = true; } } /** * Whether any keys have changed since the last synchronization packet to the client. */ public boolean isDirty() { return this.isDirty; } /** * Gets all data entries which have changed since the last check and clears their dirty flag. */ @Nullable public List> packDirty() { if (!this.isDirty) { return null; } else { this.isDirty = false; List> list = new ArrayList(); for (SynchedEntityData.DataItem dataItem : this.itemsById) { if (dataItem.isDirty()) { dataItem.setDirty(false); list.add(dataItem.value()); } } return list; } } @Nullable public List> getNonDefaultValues() { List> list = null; for (SynchedEntityData.DataItem dataItem : this.itemsById) { if (!dataItem.isSetToDefault()) { if (list == null) { list = new ArrayList(); } list.add(dataItem.value()); } } return list; } /** * Updates the data using the given entries. Used on the client when the update packet is received. */ public void assignValues(List> entries) { for (SynchedEntityData.DataValue dataValue : entries) { SynchedEntityData.DataItem dataItem = this.itemsById[dataValue.id]; this.assignValue(dataItem, dataValue); this.entity.onSyncedDataUpdated(dataItem.getAccessor()); } this.entity.onSyncedDataUpdated(entries); } private void assignValue(SynchedEntityData.DataItem target, SynchedEntityData.DataValue entry) { if (!Objects.equals(entry.serializer(), target.accessor.serializer())) { throw new IllegalStateException( String.format( Locale.ROOT, "Invalid entity data item type for field %d on entity %s: old=%s(%s), new=%s(%s)", target.accessor.id(), this.entity, target.value, target.value.getClass(), entry.value, entry.value.getClass() ) ); } else { target.setValue((T)entry.value); } } public static class Builder { private final SyncedDataHolder entity; private final SynchedEntityData.DataItem[] itemsById; public Builder(SyncedDataHolder entity) { this.entity = entity; this.itemsById = new SynchedEntityData.DataItem[SynchedEntityData.ID_REGISTRY.getCount(entity.getClass())]; } public SynchedEntityData.Builder define(EntityDataAccessor accessor, T value) { int i = accessor.id(); if (i > this.itemsById.length) { throw new IllegalArgumentException("Data value id is too big with " + i + "! (Max is " + this.itemsById.length + ")"); } else if (this.itemsById[i] != null) { throw new IllegalArgumentException("Duplicate id value for " + i + "!"); } else if (EntityDataSerializers.getSerializedId(accessor.serializer()) < 0) { throw new IllegalArgumentException("Unregistered serializer " + accessor.serializer() + " for " + i + "!"); } else { this.itemsById[accessor.id()] = new SynchedEntityData.DataItem<>(accessor, value); return this; } } public SynchedEntityData build() { for (int i = 0; i < this.itemsById.length; i++) { if (this.itemsById[i] == null) { throw new IllegalStateException("Entity " + this.entity.getClass() + " has not defined synched data value " + i); } } return new SynchedEntityData(this.entity, this.itemsById); } } public static class DataItem { final EntityDataAccessor accessor; T value; private final T initialValue; private boolean dirty; public DataItem(EntityDataAccessor accessor, T value) { this.accessor = accessor; this.initialValue = value; this.value = value; } public EntityDataAccessor getAccessor() { return this.accessor; } public void setValue(T value) { this.value = value; } public T getValue() { return this.value; } public boolean isDirty() { return this.dirty; } public void setDirty(boolean dirty) { this.dirty = dirty; } public boolean isSetToDefault() { return this.initialValue.equals(this.value); } public SynchedEntityData.DataValue value() { return SynchedEntityData.DataValue.create(this.accessor, this.value); } } public record DataValue(int id, EntityDataSerializer serializer, T value) { public static SynchedEntityData.DataValue create(EntityDataAccessor dataAccessor, T value) { EntityDataSerializer entityDataSerializer = dataAccessor.serializer(); return new SynchedEntityData.DataValue<>(dataAccessor.id(), entityDataSerializer, entityDataSerializer.copy(value)); } public void write(RegistryFriendlyByteBuf buffer) { int i = EntityDataSerializers.getSerializedId(this.serializer); if (i < 0) { throw new EncoderException("Unknown serializer type " + this.serializer); } else { buffer.writeByte(this.id); buffer.writeVarInt(i); this.serializer.codec().encode(buffer, this.value); } } public static SynchedEntityData.DataValue read(RegistryFriendlyByteBuf buffer, int id) { int i = buffer.readVarInt(); EntityDataSerializer entityDataSerializer = EntityDataSerializers.getSerializer(i); if (entityDataSerializer == null) { throw new DecoderException("Unknown serializer type " + i); } else { return read(buffer, id, entityDataSerializer); } } private static SynchedEntityData.DataValue read(RegistryFriendlyByteBuf buffer, int id, EntityDataSerializer serializer) { return new SynchedEntityData.DataValue<>(id, serializer, serializer.codec().decode(buffer)); } } }