minecraft-src/net/minecraft/network/syncher/SynchedEntityData.java
2025-07-04 01:41:11 +03:00

272 lines
8.5 KiB
Java

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 <T> EntityDataAccessor<T> defineId(Class<? extends SyncedDataHolder> clazz, EntityDataSerializer<T> 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 <T> SynchedEntityData.DataItem<T> getItem(EntityDataAccessor<T> key) {
return (SynchedEntityData.DataItem<T>)this.itemsById[key.id()];
}
/**
* Get the value of the given key for this entity.
*/
public <T> T get(EntityDataAccessor<T> key) {
return this.getItem(key).getValue();
}
/**
* Set the value of the given key for this entity.
*/
public <T> void set(EntityDataAccessor<T> key, T value) {
this.set(key, value, false);
}
public <T> void set(EntityDataAccessor<T> key, T value, boolean force) {
SynchedEntityData.DataItem<T> 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<SynchedEntityData.DataValue<?>> packDirty() {
if (!this.isDirty) {
return null;
} else {
this.isDirty = false;
List<SynchedEntityData.DataValue<?>> list = new ArrayList();
for (SynchedEntityData.DataItem<?> dataItem : this.itemsById) {
if (dataItem.isDirty()) {
dataItem.setDirty(false);
list.add(dataItem.value());
}
}
return list;
}
}
@Nullable
public List<SynchedEntityData.DataValue<?>> getNonDefaultValues() {
List<SynchedEntityData.DataValue<?>> 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<SynchedEntityData.DataValue<?>> 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 <T> void assignValue(SynchedEntityData.DataItem<T> 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 <T> SynchedEntityData.Builder define(EntityDataAccessor<T> 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<T> {
final EntityDataAccessor<T> accessor;
T value;
private final T initialValue;
private boolean dirty;
public DataItem(EntityDataAccessor<T> accessor, T value) {
this.accessor = accessor;
this.initialValue = value;
this.value = value;
}
public EntityDataAccessor<T> 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<T> value() {
return SynchedEntityData.DataValue.create(this.accessor, this.value);
}
}
public record DataValue<T>(int id, EntityDataSerializer<T> serializer, T value) {
public static <T> SynchedEntityData.DataValue<T> create(EntityDataAccessor<T> dataAccessor, T value) {
EntityDataSerializer<T> 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 <T> SynchedEntityData.DataValue<T> read(RegistryFriendlyByteBuf buffer, int id, EntityDataSerializer<T> serializer) {
return new SynchedEntityData.DataValue<>(id, serializer, serializer.codec().decode(buffer));
}
}
}