Make clearer distinction between filled and empty data component patch, simplify direct value getting

This commit is contained in:
Nassim Jahnke 2024-09-29 13:34:32 +02:00
parent 451b3a2637
commit c358c39027
No known key found for this signature in database
GPG Key ID: EF6771C01F6EF02F
5 changed files with 160 additions and 108 deletions

View File

@ -52,16 +52,14 @@ public interface StructuredData<T> extends IdHolder {
return new EmptyStructuredData<>(key, id); return new EmptyStructuredData<>(key, id);
} }
void setValue(final T value); @Nullable T value();
void write(final ByteBuf buffer); void setValue(final T value);
void setId(final int id); void setId(final int id);
StructuredDataKey<T> key(); StructuredDataKey<T> key();
@Nullable T value();
/** /**
* Returns whether the structured data is present. Even if true, the value may be null. * Returns whether the structured data is present. Even if true, the value may be null.
* *
@ -77,4 +75,6 @@ public interface StructuredData<T> extends IdHolder {
* @return true if the structured data is empty * @return true if the structured data is empty
*/ */
boolean isEmpty(); boolean isEmpty();
void write(final ByteBuf buffer);
} }

View File

@ -33,6 +33,26 @@ import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Loosely represents Minecraft's data component patch, but may also be used for an item's full data components.
* <p>
* The most commonly used methods will ignore empty data (aka empty overrides that remove item defaults) since those will rarely be needed.
* These are:
* <ul>
* <li>{@link #get(StructuredDataKey)}</li>
* <li>{@link #set(StructuredDataKey, Object)}</li>
* <li>{@link #set(StructuredDataKey)}</li>
* <li>{@link #getNonEmptyData(StructuredDataKey)}</li>
* <li>{@link #hasValue(StructuredDataKey)}</li>
* </ul>
*
* To interact with empty patches specifically, use:
* <ul>
* <li>{@link #setEmpty(StructuredDataKey)}</li>
* <li>{@link #hasEmpty(StructuredDataKey)}</li>
* </ul>
* Other methods (e.g. {@link #getData(StructuredDataKey)} and {@link #has(StructuredDataKey)}) will handle both empty and non-empty data.
*/
public final class StructuredDataContainer { public final class StructuredDataContainer {
private final Map<StructuredDataKey<?>, StructuredData<?>> data; private final Map<StructuredDataKey<?>, StructuredData<?>> data;
@ -55,66 +75,45 @@ public final class StructuredDataContainer {
} }
/** /**
* Returns structured data by id if present. * Returns the non-empty value by id if present.
*
* @param key serializer id
* @param <T> data type
* @return structured data
* @see #hasEmpty(StructuredDataKey)
*/
public @Nullable <T> T get(final StructuredDataKey<T> key) {
final StructuredData<?> data = this.data.get(key);
if (data == null || data.isEmpty()) {
return null;
}
//noinspection unchecked
return ((StructuredData<T>) data).value();
}
/**
* Returns structured data by id if present, either empty or non-empty.
* *
* @param key serializer id * @param key serializer id
* @param <T> data type * @param <T> data type
* @return structured data * @return structured data
*/ */
public @Nullable <T> StructuredData<T> get(final StructuredDataKey<T> key) { public @Nullable <T> StructuredData<T> getData(final StructuredDataKey<T> key) {
//noinspection unchecked //noinspection unchecked
return (StructuredData<T>) this.data.get(key); return (StructuredData<T>) this.data.get(key);
} }
/** /**
* Returns structured data by id if not empty. * Returns non-empty structured data by id if present.
* *
* @param key serializer id * @param key serializer id
* @param <T> data type * @param <T> data type
* @return structured data if not empty * @return non-empty structured data
*/ */
public @Nullable <T> StructuredData<T> getNonEmpty(final StructuredDataKey<T> key) { public @Nullable <T> StructuredData<T> getNonEmptyData(final StructuredDataKey<T> key) {
final StructuredData<?> data = this.data.get(key);
//noinspection unchecked //noinspection unchecked
final StructuredData<T> data = (StructuredData<T>) this.data.get(key); return data != null && data.isPresent() ? (StructuredData<T>) data : null;
return data != null && data.isPresent() ? data : null;
}
/**
* Returns structured data by id if not empty, or creates it.
*
* @param key serializer id
* @param mappingFunction function to create structured data if not present
* @param <T> data type
* @return structured data if not empty
*/
public <T> StructuredData<T> computeIfAbsent(final StructuredDataKey<T> key, final Function<StructuredDataKey<T>, T> mappingFunction) {
final StructuredData<T> data = this.getNonEmpty(key);
if (data != null) {
return data;
}
final int id = serializerId(key);
final StructuredData<T> empty = StructuredData.of(key, mappingFunction.apply(key), id);
this.data.put(key, empty);
return empty;
}
/**
* Updates and returns the structured data by id if not empty.
*
* @param key serializer id
* @param mappingFunction function to update existing data
* @param <T> data type
* @return updated structured data if not empty
*/
public <T> @Nullable StructuredData<T> updateIfPresent(final StructuredDataKey<T> key, final Function<T, T> mappingFunction) {
final StructuredData<T> data = this.getNonEmpty(key);
if (data == null) {
return null;
}
data.setValue(mappingFunction.apply(data.value()));
return data;
} }
public <T> void set(final StructuredDataKey<T> key, final T value) { public <T> void set(final StructuredDataKey<T> key, final T value) {
@ -124,49 +123,105 @@ public final class StructuredDataContainer {
} }
} }
public <T> void replaceKey(final StructuredDataKey<T> key, final StructuredDataKey<T> toKey) {
replace(key, toKey, Function.identity());
}
public <T, V> void replace(final StructuredDataKey<T> key, final StructuredDataKey<V> toKey, final Function<T, V> valueMapper) {
final StructuredData<T> data = remove(key);
if (data == null) {
return;
}
if (data.isPresent()) {
set(toKey, valueMapper.apply(data.value()));
} else {
addEmpty(toKey);
}
}
public void set(final StructuredDataKey<Unit> key) { public void set(final StructuredDataKey<Unit> key) {
this.set(key, Unit.INSTANCE); this.set(key, Unit.INSTANCE);
} }
public void addEmpty(final StructuredDataKey<?> key) { public void setEmpty(final StructuredDataKey<?> key) {
// Empty optional to override the Minecraft default // Empty optional to override the Minecraft default
this.data.put(key, StructuredData.empty(key, serializerId(key))); this.data.put(key, StructuredData.empty(key, serializerId(key)));
} }
/** /**
* Removes and returns structured data by the given key. * Updates the structured data by id if not empty.
* *
* @param key serializer key * @param key serializer id
* @param valueMapper function to update existing data
* @param <T> data type * @param <T> data type
* @return removed structured data
*/ */
public @Nullable <T> StructuredData<T> remove(final StructuredDataKey<T> key) { public <T> void replace(final StructuredDataKey<T> key, final Function<T, @Nullable T> valueMapper) {
final StructuredData<?> data = this.data.remove(key); final StructuredData<T> data = this.getNonEmptyData(key);
//noinspection unchecked if (data == null) {
return data != null ? (StructuredData<T>) data : null; return;
} }
public boolean contains(final StructuredDataKey<?> key) { final T replacement = valueMapper.apply(data.value());
if (replacement != null) {
data.setValue(replacement);
} else {
this.data.remove(key);
}
}
public <T> void replaceKey(final StructuredDataKey<T> key, final StructuredDataKey<T> toKey) {
replace(key, toKey, Function.identity());
}
public <T, V> void replace(final StructuredDataKey<T> key, final StructuredDataKey<V> toKey, final Function<T, @Nullable V> valueMapper) {
final StructuredData<?> data = this.data.remove(key);
if (data == null) {
return;
}
if (data.isPresent()) {
//noinspection unchecked
final T value = (T) data.value();
final V replacement = valueMapper.apply(value);
if (replacement != null) {
set(toKey, replacement);
}
} else {
// Also replace the key for empty data
setEmpty(toKey);
}
}
/**
* Removes data by the given key.
*
* @param key data key
* @see #replace(StructuredDataKey, Function)
* @see #replace(StructuredDataKey, StructuredDataKey, Function)
* @see #replaceKey(StructuredDataKey, StructuredDataKey)
*/
public void remove(final StructuredDataKey<?> key) {
this.data.remove(key);
}
/**
* Returns whether there is data for the given key, either empty or non-empty.
*
* @param key data key
* @return whether there data for the given key
* @see #hasEmpty(StructuredDataKey)
* @see #hasValue(StructuredDataKey)
*/
public boolean has(final StructuredDataKey<?> key) {
return this.data.containsKey(key); return this.data.containsKey(key);
} }
/**
* Returns whether there is non-empty data for the given key.
*
* @param key data key
* @return whether there is non-empty data for the given key
*/
public boolean hasValue(final StructuredDataKey<?> key) {
final StructuredData<?> data = this.data.get(key);
return data != null && data.isPresent();
}
/**
* Returns whether the structured data has an empty patch/override.
*
* @param key serializer id
* @return whether the structured data has an empty patch/override
*/
public boolean hasEmpty(final StructuredDataKey<?> key) {
final StructuredData<?> data = this.data.get(key);
return data != null && data.isEmpty();
}
/** /**
* Sets the lookup for serializer ids. Required to call most of the other methods. * Sets the lookup for serializer ids. Required to call most of the other methods.
* *

View File

@ -416,7 +416,7 @@ public final class BlockItemPacketRewriter1_20_5 extends ItemRewriter<Clientboun
final StructuredDataContainer data = item.dataContainer(); final StructuredDataContainer data = item.dataContainer();
data.setIdLookup(protocol, true); data.setIdLookup(protocol, true);
final StructuredData<CompoundTag> customData = data.getNonEmpty(StructuredDataKey.CUSTOM_DATA); final StructuredData<CompoundTag> customData = data.getNonEmptyData(StructuredDataKey.CUSTOM_DATA);
final CompoundTag tag = customData != null ? customData.value() : new CompoundTag(); final CompoundTag tag = customData != null ? customData.value() : new CompoundTag();
final DataItem dataItem = new DataItem(item.identifier(), (byte) item.amount(), tag); final DataItem dataItem = new DataItem(item.identifier(), (byte) item.amount(), tag);
if (!dataConverter.backupInconvertibleData() && customData != null && tag.remove(nbtTagName()) != null) { if (!dataConverter.backupInconvertibleData() && customData != null && tag.remove(nbtTagName()) != null) {

View File

@ -20,7 +20,6 @@ package com.viaversion.viaversion.protocols.v1_20_5to1_21.rewriter;
import com.viaversion.nbt.tag.ByteTag; import com.viaversion.nbt.tag.ByteTag;
import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.CompoundTag;
import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.minecraft.data.StructuredData;
import com.viaversion.viaversion.api.minecraft.data.StructuredDataContainer; import com.viaversion.viaversion.api.minecraft.data.StructuredDataContainer;
import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey; import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey;
import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.minecraft.item.Item;
@ -123,7 +122,7 @@ public final class BlockItemPacketRewriter1_21 extends StructuredItemRewriter<Cl
updateItemData(item); updateItemData(item);
final StructuredDataContainer dataContainer = item.dataContainer(); final StructuredDataContainer dataContainer = item.dataContainer();
if (dataContainer.contains(StructuredDataKey.RARITY)) { if (dataContainer.has(StructuredDataKey.RARITY)) {
return item; return item;
} }
@ -194,14 +193,14 @@ public final class BlockItemPacketRewriter1_21 extends StructuredItemRewriter<Cl
public static void resetRarityValues(final Item item, final String tagName) { public static void resetRarityValues(final Item item, final String tagName) {
final StructuredDataContainer dataContainer = item.dataContainer(); final StructuredDataContainer dataContainer = item.dataContainer();
final CompoundTag customData = dataContainer.get(StructuredDataKey.CUSTOM_DATA);
final StructuredData<CompoundTag> customData = dataContainer.getNonEmpty(StructuredDataKey.CUSTOM_DATA);
if (customData == null) { if (customData == null) {
return; return;
} }
if (customData.value().remove(tagName) != null) {
if (customData.remove(tagName) != null) {
dataContainer.remove(StructuredDataKey.RARITY); dataContainer.remove(StructuredDataKey.RARITY);
if (customData.value().isEmpty()) { if (customData.isEmpty()) {
dataContainer.remove(StructuredDataKey.CUSTOM_DATA); dataContainer.remove(StructuredDataKey.CUSTOM_DATA);
} }
} }

View File

@ -114,27 +114,27 @@ public class StructuredItemRewriter<C extends ClientboundPacketType, S extends S
final MappingData mappingData = protocol.getMappingData(); final MappingData mappingData = protocol.getMappingData();
if (mappingData.getItemMappings() != null) { if (mappingData.getItemMappings() != null) {
final Int2IntFunction itemIdRewriter = clientbound ? mappingData::getNewItemId : mappingData::getOldItemId; final Int2IntFunction itemIdRewriter = clientbound ? mappingData::getNewItemId : mappingData::getOldItemId;
container.updateIfPresent(StructuredDataKey.TRIM, value -> value.rewrite(itemIdRewriter)); container.replace(StructuredDataKey.TRIM, value -> value.rewrite(itemIdRewriter));
container.updateIfPresent(StructuredDataKey.POT_DECORATIONS, value -> value.rewrite(itemIdRewriter)); container.replace(StructuredDataKey.POT_DECORATIONS, value -> value.rewrite(itemIdRewriter));
} }
if (mappingData.getBlockMappings() != null) { if (mappingData.getBlockMappings() != null) {
final Int2IntFunction blockIdRewriter = clientbound ? mappingData::getNewBlockId : mappingData::getOldBlockId; final Int2IntFunction blockIdRewriter = clientbound ? mappingData::getNewBlockId : mappingData::getOldBlockId;
container.updateIfPresent(StructuredDataKey.TOOL, value -> value.rewrite(blockIdRewriter)); container.replace(StructuredDataKey.TOOL, value -> value.rewrite(blockIdRewriter));
container.updateIfPresent(StructuredDataKey.CAN_PLACE_ON, value -> value.rewrite(blockIdRewriter)); container.replace(StructuredDataKey.CAN_PLACE_ON, value -> value.rewrite(blockIdRewriter));
container.updateIfPresent(StructuredDataKey.CAN_BREAK, value -> value.rewrite(blockIdRewriter)); container.replace(StructuredDataKey.CAN_BREAK, value -> value.rewrite(blockIdRewriter));
} }
if (mappingData.getSoundMappings() != null) { if (mappingData.getSoundMappings() != null) {
final Int2IntFunction soundIdRewriter = clientbound ? mappingData::getNewSoundId : mappingData::getOldSoundId; final Int2IntFunction soundIdRewriter = clientbound ? mappingData::getNewSoundId : mappingData::getOldSoundId;
container.updateIfPresent(StructuredDataKey.INSTRUMENT, value -> value.isDirect() ? Holder.of(value.value().rewrite(soundIdRewriter)) : value); container.replace(StructuredDataKey.INSTRUMENT, value -> value.isDirect() ? Holder.of(value.value().rewrite(soundIdRewriter)) : value);
container.updateIfPresent(StructuredDataKey.JUKEBOX_PLAYABLE, value -> value.rewrite(soundIdRewriter)); container.replace(StructuredDataKey.JUKEBOX_PLAYABLE, value -> value.rewrite(soundIdRewriter));
} }
if (clientbound && protocol.getComponentRewriter() != null) { if (clientbound && protocol.getComponentRewriter() != null) {
updateComponent(connection, item, StructuredDataKey.ITEM_NAME, "item_name"); updateComponent(connection, item, StructuredDataKey.ITEM_NAME, "item_name");
updateComponent(connection, item, StructuredDataKey.CUSTOM_NAME, "custom_name"); updateComponent(connection, item, StructuredDataKey.CUSTOM_NAME, "custom_name");
final StructuredData<Tag[]> loreData = container.getNonEmpty(StructuredDataKey.LORE); final Tag[] lore = container.get(StructuredDataKey.LORE);
if (loreData != null) { if (lore != null) {
for (final Tag tag : loreData.value()) { for (final Tag tag : lore) {
protocol.getComponentRewriter().processTag(connection, tag); protocol.getComponentRewriter().processTag(connection, tag);
} }
} }
@ -166,35 +166,35 @@ public class StructuredItemRewriter<C extends ClientboundPacketType, S extends S
} }
protected void updateComponent(final UserConnection connection, final Item item, final StructuredDataKey<Tag> key, final String backupKey) { protected void updateComponent(final UserConnection connection, final Item item, final StructuredDataKey<Tag> key, final String backupKey) {
final StructuredData<Tag> name = item.dataContainer().getNonEmpty(key); final Tag name = item.dataContainer().get(key);
if (name == null) { if (name == null) {
return; return;
} }
final Tag originalName = name.value().copy(); final Tag originalName = name.copy();
protocol.getComponentRewriter().processTag(connection, name.value()); protocol.getComponentRewriter().processTag(connection, name);
if (!name.value().equals(originalName)) { if (!name.equals(originalName)) {
saveTag(createCustomTag(item), originalName, backupKey); saveTag(createCustomTag(item), originalName, backupKey);
} }
} }
protected void restoreTextComponents(final Item item) { protected void restoreTextComponents(final Item item) {
final StructuredDataContainer data = item.dataContainer(); final StructuredDataContainer data = item.dataContainer();
final StructuredData<CompoundTag> customData = data.getNonEmpty(StructuredDataKey.CUSTOM_DATA); final CompoundTag customData = data.get(StructuredDataKey.CUSTOM_DATA);
if (customData == null) { if (customData == null) {
return; return;
} }
// Remove custom name // Remove custom name
if (customData.value().remove(nbtTagName("added_custom_name")) != null) { if (customData.remove(nbtTagName("added_custom_name")) != null) {
data.remove(StructuredDataKey.CUSTOM_NAME); data.remove(StructuredDataKey.CUSTOM_NAME);
} else { } else {
final Tag customName = removeBackupTag(customData.value(), "custom_name"); final Tag customName = removeBackupTag(customData, "custom_name");
if (customName != null) { if (customName != null) {
data.set(StructuredDataKey.CUSTOM_NAME, customName); data.set(StructuredDataKey.CUSTOM_NAME, customName);
} }
final Tag itemName = removeBackupTag(customData.value(), "item_name"); final Tag itemName = removeBackupTag(customData, "item_name");
if (itemName != null) { if (itemName != null) {
data.set(StructuredDataKey.ITEM_NAME, itemName); data.set(StructuredDataKey.ITEM_NAME, itemName);
} }
@ -203,14 +203,12 @@ public class StructuredItemRewriter<C extends ClientboundPacketType, S extends S
protected CompoundTag createCustomTag(final Item item) { protected CompoundTag createCustomTag(final Item item) {
final StructuredDataContainer data = item.dataContainer(); final StructuredDataContainer data = item.dataContainer();
final StructuredData<CompoundTag> customData = data.getNonEmpty(StructuredDataKey.CUSTOM_DATA); CompoundTag customData = data.get(StructuredDataKey.CUSTOM_DATA);
if (customData != null) { if (customData == null) {
return customData.value(); customData = new CompoundTag();
data.set(StructuredDataKey.CUSTOM_DATA, customData);
} }
return customData;
final CompoundTag tag = new CompoundTag();
data.set(StructuredDataKey.CUSTOM_DATA, tag);
return tag;
} }
protected void saveTag(final CompoundTag customData, final Tag tag, final String name) { protected void saveTag(final CompoundTag customData, final Tag tag, final String name) {