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);
}
void setValue(final T value);
@Nullable T value();
void write(final ByteBuf buffer);
void setValue(final T value);
void setId(final int id);
StructuredDataKey<T> key();
@Nullable T value();
/**
* 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
*/
boolean isEmpty();
void write(final ByteBuf buffer);
}

View File

@ -33,6 +33,26 @@ import java.util.Map;
import java.util.function.Function;
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 {
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 <T> data type
* @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
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 <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
final StructuredData<T> data = (StructuredData<T>) this.data.get(key);
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;
return data != null && data.isPresent() ? (StructuredData<T>) data : null;
}
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) {
this.set(key, Unit.INSTANCE);
}
public void addEmpty(final StructuredDataKey<?> key) {
public void setEmpty(final StructuredDataKey<?> key) {
// Empty optional to override the Minecraft default
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
* @return removed structured data
*/
public @Nullable <T> StructuredData<T> remove(final StructuredDataKey<T> key) {
final StructuredData<?> data = this.data.remove(key);
//noinspection unchecked
return data != null ? (StructuredData<T>) data : null;
public <T> void replace(final StructuredDataKey<T> key, final Function<T, @Nullable T> valueMapper) {
final StructuredData<T> data = this.getNonEmptyData(key);
if (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);
}
/**
* 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.
*

View File

@ -416,7 +416,7 @@ public final class BlockItemPacketRewriter1_20_5 extends ItemRewriter<Clientboun
final StructuredDataContainer data = item.dataContainer();
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 DataItem dataItem = new DataItem(item.identifier(), (byte) item.amount(), tag);
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.CompoundTag;
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.StructuredDataKey;
import com.viaversion.viaversion.api.minecraft.item.Item;
@ -123,7 +122,7 @@ public final class BlockItemPacketRewriter1_21 extends StructuredItemRewriter<Cl
updateItemData(item);
final StructuredDataContainer dataContainer = item.dataContainer();
if (dataContainer.contains(StructuredDataKey.RARITY)) {
if (dataContainer.has(StructuredDataKey.RARITY)) {
return item;
}
@ -194,14 +193,14 @@ public final class BlockItemPacketRewriter1_21 extends StructuredItemRewriter<Cl
public static void resetRarityValues(final Item item, final String tagName) {
final StructuredDataContainer dataContainer = item.dataContainer();
final StructuredData<CompoundTag> customData = dataContainer.getNonEmpty(StructuredDataKey.CUSTOM_DATA);
final CompoundTag customData = dataContainer.get(StructuredDataKey.CUSTOM_DATA);
if (customData == null) {
return;
}
if (customData.value().remove(tagName) != null) {
if (customData.remove(tagName) != null) {
dataContainer.remove(StructuredDataKey.RARITY);
if (customData.value().isEmpty()) {
if (customData.isEmpty()) {
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();
if (mappingData.getItemMappings() != null) {
final Int2IntFunction itemIdRewriter = clientbound ? mappingData::getNewItemId : mappingData::getOldItemId;
container.updateIfPresent(StructuredDataKey.TRIM, value -> value.rewrite(itemIdRewriter));
container.updateIfPresent(StructuredDataKey.POT_DECORATIONS, value -> value.rewrite(itemIdRewriter));
container.replace(StructuredDataKey.TRIM, value -> value.rewrite(itemIdRewriter));
container.replace(StructuredDataKey.POT_DECORATIONS, value -> value.rewrite(itemIdRewriter));
}
if (mappingData.getBlockMappings() != null) {
final Int2IntFunction blockIdRewriter = clientbound ? mappingData::getNewBlockId : mappingData::getOldBlockId;
container.updateIfPresent(StructuredDataKey.TOOL, value -> value.rewrite(blockIdRewriter));
container.updateIfPresent(StructuredDataKey.CAN_PLACE_ON, value -> value.rewrite(blockIdRewriter));
container.updateIfPresent(StructuredDataKey.CAN_BREAK, value -> value.rewrite(blockIdRewriter));
container.replace(StructuredDataKey.TOOL, value -> value.rewrite(blockIdRewriter));
container.replace(StructuredDataKey.CAN_PLACE_ON, value -> value.rewrite(blockIdRewriter));
container.replace(StructuredDataKey.CAN_BREAK, value -> value.rewrite(blockIdRewriter));
}
if (mappingData.getSoundMappings() != null) {
final Int2IntFunction soundIdRewriter = clientbound ? mappingData::getNewSoundId : mappingData::getOldSoundId;
container.updateIfPresent(StructuredDataKey.INSTRUMENT, value -> value.isDirect() ? Holder.of(value.value().rewrite(soundIdRewriter)) : value);
container.updateIfPresent(StructuredDataKey.JUKEBOX_PLAYABLE, value -> value.rewrite(soundIdRewriter));
container.replace(StructuredDataKey.INSTRUMENT, value -> value.isDirect() ? Holder.of(value.value().rewrite(soundIdRewriter)) : value);
container.replace(StructuredDataKey.JUKEBOX_PLAYABLE, value -> value.rewrite(soundIdRewriter));
}
if (clientbound && protocol.getComponentRewriter() != null) {
updateComponent(connection, item, StructuredDataKey.ITEM_NAME, "item_name");
updateComponent(connection, item, StructuredDataKey.CUSTOM_NAME, "custom_name");
final StructuredData<Tag[]> loreData = container.getNonEmpty(StructuredDataKey.LORE);
if (loreData != null) {
for (final Tag tag : loreData.value()) {
final Tag[] lore = container.get(StructuredDataKey.LORE);
if (lore != null) {
for (final Tag tag : lore) {
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) {
final StructuredData<Tag> name = item.dataContainer().getNonEmpty(key);
final Tag name = item.dataContainer().get(key);
if (name == null) {
return;
}
final Tag originalName = name.value().copy();
protocol.getComponentRewriter().processTag(connection, name.value());
if (!name.value().equals(originalName)) {
final Tag originalName = name.copy();
protocol.getComponentRewriter().processTag(connection, name);
if (!name.equals(originalName)) {
saveTag(createCustomTag(item), originalName, backupKey);
}
}
protected void restoreTextComponents(final Item item) {
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) {
return;
}
// 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);
} else {
final Tag customName = removeBackupTag(customData.value(), "custom_name");
final Tag customName = removeBackupTag(customData, "custom_name");
if (customName != null) {
data.set(StructuredDataKey.CUSTOM_NAME, customName);
}
final Tag itemName = removeBackupTag(customData.value(), "item_name");
final Tag itemName = removeBackupTag(customData, "item_name");
if (itemName != null) {
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) {
final StructuredDataContainer data = item.dataContainer();
final StructuredData<CompoundTag> customData = data.getNonEmpty(StructuredDataKey.CUSTOM_DATA);
if (customData != null) {
return customData.value();
CompoundTag customData = data.get(StructuredDataKey.CUSTOM_DATA);
if (customData == null) {
customData = new CompoundTag();
data.set(StructuredDataKey.CUSTOM_DATA, customData);
}
final CompoundTag tag = new CompoundTag();
data.set(StructuredDataKey.CUSTOM_DATA, tag);
return tag;
return customData;
}
protected void saveTag(final CompoundTag customData, final Tag tag, final String name) {