272 lines
8.5 KiB
Java
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));
|
|
}
|
|
}
|
|
}
|