mirror of
https://github.com/Minestom/Minestom.git
synced 2025-03-31 17:05:54 +02:00
Tag nbt conversion (#901)
This commit is contained in:
parent
dd26d4ceb8
commit
af43c977bd
@ -10,7 +10,9 @@ import net.minestom.server.utils.NBTUtils;
|
||||
import net.minestom.server.utils.validate.Check;
|
||||
import org.jetbrains.annotations.*;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBT;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTByte;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTString;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -85,9 +87,9 @@ public final class ItemStack implements TagReadable, HoverEventSource<HoverEvent
|
||||
Check.notNull(material, "Unknown material: {0}", id);
|
||||
|
||||
Byte amount = nbtCompound.getByte("Count");
|
||||
return fromNBT(material,
|
||||
nbtCompound.getCompound("tag"),
|
||||
amount == null ? 1 : amount);
|
||||
if (amount == null) amount = 1;
|
||||
final NBTCompound tag = nbtCompound.getCompound("tag");
|
||||
return tag != null ? fromNBT(material, tag, amount) : of(material, amount);
|
||||
}
|
||||
|
||||
@Contract(pure = true)
|
||||
@ -231,10 +233,11 @@ public final class ItemStack implements TagReadable, HoverEventSource<HoverEvent
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
public @NotNull NBTCompound toItemNBT() {
|
||||
return NBT.Compound(Map.of(
|
||||
"id", NBT.String(getMaterial().name()),
|
||||
"Count", NBT.Byte(getAmount()),
|
||||
"tag", getMeta().toNBT()));
|
||||
final NBTString material = NBT.String(getMaterial().name());
|
||||
final NBTByte amount = NBT.Byte(getAmount());
|
||||
final NBTCompound nbt = getMeta().toNBT();
|
||||
if (nbt.isEmpty()) return NBT.Compound(Map.of("id", material, "Count", amount));
|
||||
return NBT.Compound(Map.of("id", material, "Count", amount, "tag", nbt));
|
||||
}
|
||||
|
||||
@Contract(value = "-> new", pure = true)
|
||||
|
@ -5,8 +5,11 @@ import org.jglrxavpok.hephaistos.nbt.*;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Basic serializers for {@link Tag tags}.
|
||||
*/
|
||||
final class Serializers {
|
||||
static final Entry<?, ?> VOID = new Entry<>(null, null);
|
||||
static final Entry<TagHandlerImpl, NBTCompound> PATH = new Entry<>(TagHandlerImpl::fromCompound, TagHandlerImpl::asCompound);
|
||||
|
||||
static final Entry<Byte, NBTByte> BYTE = new Entry<>(NBTByte::getValue, NBT::Byte);
|
||||
static final Entry<Short, NBTShort> SHORT = new Entry<>(NBTShort::getValue, NBT::Short);
|
||||
@ -19,7 +22,7 @@ final class Serializers {
|
||||
|
||||
static final Entry<ItemStack, NBTCompound> ITEM = new Entry<>(ItemStack::fromItemNBT, ItemStack::toItemNBT);
|
||||
|
||||
static <T> Entry<T,?> fromTagSerializer(TagSerializer<T> serializer) {
|
||||
static <T> Entry<T, ?> fromTagSerializer(TagSerializer<T> serializer) {
|
||||
return new Serializers.Entry<>(
|
||||
(NBTCompound compound) -> {
|
||||
if (compound.isEmpty()) return null;
|
||||
|
@ -14,6 +14,7 @@ import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.function.UnaryOperator;
|
||||
@ -93,6 +94,8 @@ public class Tag<T> {
|
||||
@Contract(value = "_, _ -> new", pure = true)
|
||||
public <R> Tag<R> map(@NotNull Function<T, R> readMap,
|
||||
@NotNull Function<R, T> writeMap) {
|
||||
if (entry == Serializers.NBT_ENTRY)
|
||||
throw new IllegalArgumentException("Cannot create a list from a NBT entry");
|
||||
var entry = this.entry;
|
||||
final Function<NBT, R> readFunction = entry.read().andThen(t -> {
|
||||
if (t == null) return null;
|
||||
@ -109,6 +112,8 @@ public class Tag<T> {
|
||||
@ApiStatus.Experimental
|
||||
@Contract(value = "-> new", pure = true)
|
||||
public Tag<List<T>> list() {
|
||||
if (entry == Serializers.NBT_ENTRY)
|
||||
throw new IllegalArgumentException("Cannot create a list from a NBT entry");
|
||||
var entry = this.entry;
|
||||
var readFunction = entry.read();
|
||||
var writeFunction = entry.write();
|
||||
@ -219,6 +224,24 @@ public class Tag<T> {
|
||||
return supplier != null ? supplier.get() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof Tag<?> tag)) return false;
|
||||
return index == tag.index &&
|
||||
listScope == tag.listScope &&
|
||||
readComparator.equals(tag.readComparator) &&
|
||||
Objects.equals(defaultValue, tag.defaultValue) &&
|
||||
Arrays.equals(path, tag.path) && Objects.equals(copy, tag.copy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = Objects.hash(index, readComparator, defaultValue, copy, listScope);
|
||||
result = 31 * result + Arrays.hashCode(path);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static @NotNull Tag<Byte> Byte(@NotNull String key) {
|
||||
return tag(key, Serializers.BYTE);
|
||||
}
|
||||
|
@ -16,6 +16,16 @@ final class TagHandlerImpl implements TagHandler {
|
||||
private volatile Entry<?>[] entries = new Entry[0];
|
||||
private Cache cache;
|
||||
|
||||
static TagHandlerImpl fromCompound(NBTCompoundLike compound) {
|
||||
TagHandlerImpl handler = new TagHandlerImpl();
|
||||
for (var entry : compound) {
|
||||
final Tag<NBT> tag = Tag.NBT(entry.getKey());
|
||||
final NBT nbt = entry.getValue();
|
||||
handler.setTag(tag, nbt);
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
|
||||
if (tag.isView()) return tag.read(asCompound());
|
||||
@ -29,11 +39,19 @@ final class TagHandlerImpl implements TagHandler {
|
||||
tag.writeUnsafe(viewCompound, value);
|
||||
updateContent(viewCompound);
|
||||
} else {
|
||||
if (value != null) {
|
||||
final UnaryOperator<T> copy = tag.copy;
|
||||
if (copy != null) value = copy.apply(value);
|
||||
if (value instanceof NBT nbt) {
|
||||
synchronized (this) {
|
||||
write(tag, null);
|
||||
TagNbtSeparator.separate(tag.getKey(), nbt,
|
||||
entry -> write(entry.tag(), entry.value()));
|
||||
}
|
||||
} else {
|
||||
if (value != null) {
|
||||
final UnaryOperator<T> copy = tag.copy;
|
||||
if (copy != null) value = copy.apply(value);
|
||||
}
|
||||
write(tag, value);
|
||||
}
|
||||
write(tag, value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,7 +75,6 @@ final class TagHandlerImpl implements TagHandler {
|
||||
}
|
||||
|
||||
public synchronized <T> void write(@NotNull Tag<T> tag, @Nullable T value) {
|
||||
// Normal write
|
||||
int tagIndex = tag.index;
|
||||
TagHandlerImpl local = this;
|
||||
Entry<?>[] entries = this.entries;
|
||||
@ -80,12 +97,17 @@ final class TagHandlerImpl implements TagHandler {
|
||||
if (value == null) return;
|
||||
// Empty path, create a new handler
|
||||
local = new TagHandlerImpl();
|
||||
entries[pathIndex] = new Entry(Tag.tag(path.name(), Serializers.VOID), local);
|
||||
} else if (entry.value instanceof TagHandlerImpl handler) {
|
||||
entries[pathIndex] = new PathEntry(path.name(), local);
|
||||
} else if (entry instanceof PathEntry pathEntry) {
|
||||
// Existing path, continue navigating
|
||||
local = handler;
|
||||
local = pathEntry.value;
|
||||
} else {
|
||||
throw new IllegalStateException("Cannot set a path-able tag on a non-path-able entry: " + entry.value.getClass());
|
||||
// Probably is a Structure entry,
|
||||
// convert it to nbt to allow mutation (and drop the cached object)
|
||||
if (value == null) return;
|
||||
final NBT nbt = entry.updatedNbt();
|
||||
local = nbt instanceof NBTCompound compound ? fromCompound(compound) : new TagHandlerImpl();
|
||||
entries[pathIndex] = new PathEntry(path.name(), local);
|
||||
}
|
||||
entries = local.entries;
|
||||
pathHandlers[i] = local;
|
||||
@ -117,7 +139,7 @@ final class TagHandlerImpl implements TagHandler {
|
||||
if (value == null) return;
|
||||
local.entries = entries = Arrays.copyOf(entries, tagIndex + 1);
|
||||
}
|
||||
entries[tagIndex] = value != null ? new Entry<>(tag, value) : null;
|
||||
entries[tagIndex] = value != null ? new TagEntry<>(tag, value) : null;
|
||||
this.cache = null;
|
||||
if (pathHandlers != null) {
|
||||
for (var handler : pathHandlers) handler.cache = null;
|
||||
@ -127,44 +149,58 @@ final class TagHandlerImpl implements TagHandler {
|
||||
private synchronized Cache updatedCache() {
|
||||
Cache cache = this.cache;
|
||||
if (cache == null) {
|
||||
Entry<?>[] entries = this.entries;
|
||||
final Entry<?>[] entries = this.entries;
|
||||
if (entries.length > 0) {
|
||||
entries = entries.clone();
|
||||
MutableNBTCompound tmp = new MutableNBTCompound();
|
||||
for (Entry<?> entry : entries) {
|
||||
if (entry == null) continue;
|
||||
final Tag<?> tag = entry.tag;
|
||||
final Object value = entry.value;
|
||||
if (value instanceof TagHandler handler) {
|
||||
// Path-able entry
|
||||
tmp.put(tag.getKey(), handler.asCompound());
|
||||
} else {
|
||||
tag.writeUnsafe(tmp, value);
|
||||
}
|
||||
if (entry != null) tmp.put(entry.tag().getKey(), entry.updatedNbt());
|
||||
}
|
||||
cache = !tmp.isEmpty() ? new Cache(entries, tmp.toCompound()) : Cache.EMPTY;
|
||||
} else {
|
||||
cache = Cache.EMPTY;
|
||||
}
|
||||
cache = !tmp.isEmpty() ? new Cache(entries.clone(), tmp.toCompound()) : Cache.EMPTY;
|
||||
} else cache = Cache.EMPTY;
|
||||
this.cache = cache;
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
private static final class Entry<T> {
|
||||
final Tag<T> tag;
|
||||
final T value; // TagHandler type for path-able tags
|
||||
volatile NBT nbt;
|
||||
|
||||
Entry(Tag<T> tag, T value) {
|
||||
this.tag = tag;
|
||||
this.value = value;
|
||||
private static <T> T read(Entry<?>[] entries, Tag<T> tag) {
|
||||
Entry<?> entry;
|
||||
final var paths = tag.path;
|
||||
if (paths != null) {
|
||||
// Must be a path-able entry
|
||||
for (var path : paths) {
|
||||
final int pathIndex = path.index();
|
||||
if (pathIndex >= entries.length || (entry = entries[pathIndex]) == null) {
|
||||
return tag.createDefault();
|
||||
}
|
||||
if (entry instanceof PathEntry pathEntry) {
|
||||
entries = pathEntry.value.entries;
|
||||
} else if (entry.updatedNbt() instanceof NBTCompound compound) {
|
||||
// Slow path forcing a conversion of the structure to NBTCompound
|
||||
// TODO should the handler be cached inside the entry?
|
||||
TagHandlerImpl tmp = fromCompound(compound);
|
||||
entries = tmp.entries;
|
||||
} else {
|
||||
// Entry is not path-able
|
||||
return tag.createDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NBT updatedNbt() {
|
||||
NBT nbt = this.nbt;
|
||||
if (nbt == null) this.nbt = nbt = tag.entry.write().apply(value);
|
||||
return nbt;
|
||||
final int index = tag.index;
|
||||
if (index >= entries.length || (entry = entries[index]) == null) {
|
||||
return tag.createDefault();
|
||||
}
|
||||
if (entry.tag().shareValue(tag)) {
|
||||
// The tag used to write the entry is compatible with the one used to get
|
||||
// return the value directly
|
||||
//noinspection unchecked
|
||||
return (T) entry.value();
|
||||
}
|
||||
// Value must be parsed from nbt if the tag is different
|
||||
final NBT nbt = entry.updatedNbt();
|
||||
try {
|
||||
return tag.entry.read().apply(nbt);
|
||||
} catch (ClassCastException e) {
|
||||
return tag.createDefault();
|
||||
}
|
||||
}
|
||||
|
||||
@ -178,53 +214,51 @@ final class TagHandlerImpl implements TagHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> T read(Entry<?>[] entries, Tag<T> tag) {
|
||||
final int index = tag.index;
|
||||
Entry<?> entry;
|
||||
final var paths = tag.path;
|
||||
if (paths != null) {
|
||||
// Must be a path-able entry
|
||||
for (var path : paths) {
|
||||
final int pathIndex = path.index();
|
||||
if (pathIndex >= entries.length || (entry = entries[pathIndex]) == null) {
|
||||
return tag.createDefault();
|
||||
}
|
||||
if (entry.value instanceof TagHandlerImpl handler) {
|
||||
entries = handler.entries;
|
||||
} else if (entry.updatedNbt() instanceof NBTCompound compound) {
|
||||
TagHandlerImpl tmp = fromCompound(compound);
|
||||
entries = tmp.entries;
|
||||
}
|
||||
}
|
||||
private sealed interface Entry<T> permits TagEntry, PathEntry {
|
||||
Tag<T> tag();
|
||||
|
||||
T value();
|
||||
|
||||
NBT updatedNbt();
|
||||
}
|
||||
|
||||
private static final class TagEntry<T> implements Entry<T> {
|
||||
final Tag<T> tag;
|
||||
final T value;
|
||||
volatile NBT nbt;
|
||||
|
||||
TagEntry(Tag<T> tag, T value) {
|
||||
this.tag = tag;
|
||||
this.value = value;
|
||||
}
|
||||
if (index >= entries.length || (entry = entries[index]) == null) {
|
||||
return tag.createDefault();
|
||||
|
||||
@Override
|
||||
public Tag<T> tag() {
|
||||
return tag;
|
||||
}
|
||||
final Object value = entry.value;
|
||||
if (value instanceof TagHandlerImpl)
|
||||
throw new IllegalStateException("Cannot read path-able tag " + tag.getKey());
|
||||
final Tag entryTag = entry.tag;
|
||||
if (entryTag.shareValue(tag)) {
|
||||
// Tag is the same, return the value
|
||||
//noinspection unchecked
|
||||
return (T) value;
|
||||
|
||||
@Override
|
||||
public T value() {
|
||||
return value;
|
||||
}
|
||||
// Value must be parsed from nbt if the tag is different
|
||||
final NBT nbt = entry.updatedNbt();
|
||||
try {
|
||||
return tag.entry.read().apply(nbt);
|
||||
} catch (ClassCastException e) {
|
||||
return tag.createDefault();
|
||||
|
||||
@Override
|
||||
public NBT updatedNbt() {
|
||||
NBT nbt = this.nbt;
|
||||
if (nbt == null) this.nbt = nbt = tag.entry.write().apply(value);
|
||||
return nbt;
|
||||
}
|
||||
}
|
||||
|
||||
static TagHandlerImpl fromCompound(NBTCompoundLike compound) {
|
||||
TagHandlerImpl handler = new TagHandlerImpl();
|
||||
for (var entry : compound) {
|
||||
final Tag<NBT> tag = Tag.NBT(entry.getKey());
|
||||
final NBT nbt = entry.getValue();
|
||||
handler.setTag(tag, nbt);
|
||||
private record PathEntry(Tag<TagHandlerImpl> tag,
|
||||
TagHandlerImpl value) implements Entry<TagHandlerImpl> {
|
||||
PathEntry(String key, TagHandlerImpl value) {
|
||||
this(Tag.tag(key, Serializers.PATH), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NBT updatedNbt() {
|
||||
return value.asCompound();
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
|
101
src/main/java/net/minestom/server/tag/TagNbtSeparator.java
Normal file
101
src/main/java/net/minestom/server/tag/TagNbtSeparator.java
Normal file
@ -0,0 +1,101 @@
|
||||
package net.minestom.server.tag;
|
||||
|
||||
import org.jglrxavpok.hephaistos.nbt.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static java.util.Map.entry;
|
||||
|
||||
/**
|
||||
* Handles conversion of {@link NBT} subtypes into one or multiple primitive {@link Tag tags}.
|
||||
*/
|
||||
final class TagNbtSeparator {
|
||||
static final Map<NBTType<?>, Function<String, Tag<?>>> SUPPORTED_TYPES = Map.ofEntries(
|
||||
entry(NBTType.TAG_Byte, Tag::Byte),
|
||||
entry(NBTType.TAG_Short, Tag::Short),
|
||||
entry(NBTType.TAG_Int, Tag::Integer),
|
||||
entry(NBTType.TAG_Long, Tag::Long),
|
||||
entry(NBTType.TAG_Float, Tag::Float),
|
||||
entry(NBTType.TAG_Double, Tag::Double),
|
||||
entry(NBTType.TAG_String, Tag::String));
|
||||
|
||||
static void separate(String key, NBT nbt, Consumer<Entry> consumer) {
|
||||
convert(new ArrayList<>(), key, nbt, consumer);
|
||||
}
|
||||
|
||||
private static void convert(List<String> path, String key, NBT nbt, Consumer<Entry> consumer) {
|
||||
if (nbt instanceof NBTByte nbtByte) {
|
||||
consumer.accept(makeEntry(path, Tag.Byte(key), nbtByte.getValue()));
|
||||
} else if (nbt instanceof NBTShort nbtShort) {
|
||||
consumer.accept(makeEntry(path, Tag.Short(key), nbtShort.getValue()));
|
||||
} else if (nbt instanceof NBTInt nbtInt) {
|
||||
consumer.accept(makeEntry(path, Tag.Integer(key), nbtInt.getValue()));
|
||||
} else if (nbt instanceof NBTLong nbtLong) {
|
||||
consumer.accept(makeEntry(path, Tag.Long(key), nbtLong.getValue()));
|
||||
} else if (nbt instanceof NBTFloat nbtFloat) {
|
||||
consumer.accept(makeEntry(path, Tag.Float(key), nbtFloat.getValue()));
|
||||
} else if (nbt instanceof NBTDouble nbtDouble) {
|
||||
consumer.accept(makeEntry(path, Tag.Double(key), nbtDouble.getValue()));
|
||||
} else if (nbt instanceof NBTString nbtString) {
|
||||
consumer.accept(makeEntry(path, Tag.String(key), nbtString.getValue()));
|
||||
} else if (nbt instanceof NBTCompound nbtCompound) {
|
||||
for (var ent : nbtCompound) {
|
||||
var newPath = new ArrayList<>(path);
|
||||
newPath.add(key);
|
||||
convert(newPath, ent.getKey(), ent.getValue(), consumer);
|
||||
}
|
||||
} else if (nbt instanceof NBTList<?> nbtList) {
|
||||
var tagFunction = SUPPORTED_TYPES.get(nbtList.getSubtagType());
|
||||
if (tagFunction == null) {
|
||||
// Invalid list subtype, fallback to nbt
|
||||
consumer.accept(makeEntry(path, Tag.NBT(key), nbt));
|
||||
} else {
|
||||
try {
|
||||
var tag = tagFunction.apply(key).list();
|
||||
Object[] values = new Object[nbtList.getSize()];
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
values[i] = nbtValue(nbtList.get(i));
|
||||
}
|
||||
consumer.accept(makeEntry(path, Tag.class.cast(tag), List.of(values)));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
consumer.accept(makeEntry(path, Tag.NBT(key), nbt));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// TODO array support
|
||||
consumer.accept(makeEntry(path, Tag.NBT(key), nbt));
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> Entry<?> makeEntry(List<String> path, Tag<T> tag, T value) {
|
||||
return new Entry<>(tag.path(path.toArray(String[]::new)), value);
|
||||
}
|
||||
|
||||
record Entry<T>(Tag<T> tag, T value) {
|
||||
}
|
||||
|
||||
private static Object nbtValue(NBT nbt) throws IllegalArgumentException {
|
||||
if (nbt instanceof NBTByte nbtByte) {
|
||||
return nbtByte.getValue();
|
||||
} else if (nbt instanceof NBTShort nbtShort) {
|
||||
return nbtShort.getValue();
|
||||
} else if (nbt instanceof NBTInt nbtInt) {
|
||||
return nbtInt.getValue();
|
||||
} else if (nbt instanceof NBTLong nbtLong) {
|
||||
return nbtLong.getValue();
|
||||
} else if (nbt instanceof NBTFloat nbtFloat) {
|
||||
return nbtFloat.getValue();
|
||||
} else if (nbt instanceof NBTDouble nbtDouble) {
|
||||
return nbtDouble.getValue();
|
||||
} else if (nbt instanceof NBTString nbtString) {
|
||||
return nbtString.getValue();
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported NBT type: " + nbt.getClass().getName());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,115 +1,67 @@
|
||||
package net.minestom.server.tag;
|
||||
|
||||
import net.minestom.server.coordinate.Vec;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.function.Function;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
* Test tags that can share cached values.
|
||||
*/
|
||||
public class TagEqualityTest {
|
||||
|
||||
record Entry(int value) {
|
||||
@Test
|
||||
public void sameType() {
|
||||
var tag1 = Tag.Integer("key");
|
||||
var tag2 = Tag.Integer("key");
|
||||
assertEquals(tag1, tag1);
|
||||
assertEquals(tag2, tag2);
|
||||
assertEquals(tag1, tag2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void same() {
|
||||
var tag = Tag.String("test");
|
||||
assertTrue(tag.shareValue(tag));
|
||||
public void differentKey() {
|
||||
var tag1 = Tag.Integer("key1");
|
||||
var tag2 = Tag.Integer("key2");
|
||||
assertNotEquals(tag1, tag2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void similar() {
|
||||
var tag = Tag.String("test");
|
||||
var tag2 = Tag.String("test");
|
||||
assertTrue(tag.shareValue(tag2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void differentDefault() {
|
||||
var tag = Tag.String("test").defaultValue("test2");
|
||||
var tag2 = Tag.String("test").defaultValue("test3");
|
||||
assertTrue(tag.shareValue(tag2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void differentType() {
|
||||
var tag = Tag.String("test");
|
||||
var tag2 = Tag.Integer("test");
|
||||
assertFalse(tag.shareValue(tag2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mapSame() {
|
||||
// Force identical functions
|
||||
Function<Integer, Entry> t1 = Entry::new;
|
||||
Function<Entry, Integer> t2 = Entry::value;
|
||||
|
||||
var tag = Tag.Integer("key");
|
||||
var map1 = tag.map(t1, t2);
|
||||
var map2 = tag.map(t1, t2);
|
||||
assertTrue(map1.shareValue(map2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mapChild() {
|
||||
var intTag = Tag.Integer("key");
|
||||
var tag = intTag.map(Entry::new, Entry::value);
|
||||
assertFalse(intTag.shareValue(tag));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void list() {
|
||||
var tag = Tag.String("test").list();
|
||||
assertTrue(tag.shareValue(tag));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void listScope() {
|
||||
var tag = Tag.String("test");
|
||||
assertFalse(tag.shareValue(tag.list()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void similarList() {
|
||||
var tag = Tag.String("test").list();
|
||||
var tag2 = Tag.String("test").list();
|
||||
assertTrue(tag.shareValue(tag2));
|
||||
assertTrue(tag.list().shareValue(tag2.list()));
|
||||
public void sameList() {
|
||||
var tag1 = Tag.Integer("key").list();
|
||||
var tag2 = Tag.Integer("key").list();
|
||||
assertEquals(tag1, tag2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void differentList() {
|
||||
var tag = Tag.String("test").list();
|
||||
var tag2 = Tag.String("test").list();
|
||||
assertFalse(tag.shareValue(tag2.list()));
|
||||
assertFalse(tag.list().shareValue(tag2.list().list()));
|
||||
var tag1 = Tag.Integer("key").list();
|
||||
var tag2 = Tag.Integer("key");
|
||||
assertNotEquals(tag1, tag2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void differentListType() {
|
||||
var tag = Tag.String("test").list();
|
||||
var tag2 = Tag.Integer("test").list();
|
||||
assertFalse(tag.shareValue(tag2));
|
||||
assertFalse(tag.list().shareValue(tag2.list()));
|
||||
public void unmatchedList() {
|
||||
var tag1 = Tag.Integer("key").list().list();
|
||||
var tag2 = Tag.Integer("key").list();
|
||||
assertNotEquals(tag1, tag2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void recordStructure() {
|
||||
var tag = Tag.Structure("test", Vec.class);
|
||||
var tag2 = Tag.Structure("test", Vec.class);
|
||||
assertTrue(tag.shareValue(tag2));
|
||||
public void samePath() {
|
||||
var tag1 = Tag.Integer("key").path("path");
|
||||
var tag2 = Tag.Integer("key").path("path");
|
||||
assertEquals(tag1, tag2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void recordStructureList() {
|
||||
var tag = Tag.Structure("test", Vec.class).list();
|
||||
var tag2 = Tag.Structure("test", Vec.class).list();
|
||||
assertTrue(tag.shareValue(tag2));
|
||||
assertTrue(tag.list().shareValue(tag2.list()));
|
||||
public void differentPath() {
|
||||
var tag1 = Tag.Integer("key").path("path");
|
||||
var tag2 = Tag.Integer("key").path("path2");
|
||||
assertNotEquals(tag1, tag2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unmatchedPath() {
|
||||
var tag1 = Tag.Integer("key").path("path", "path2");
|
||||
var tag2 = Tag.Integer("key").path("path");
|
||||
assertNotEquals(tag1, tag2);
|
||||
}
|
||||
}
|
||||
|
@ -102,8 +102,7 @@ public class TagItemTest {
|
||||
{
|
||||
"item": {
|
||||
"id":"minecraft:diamond",
|
||||
"Count":1B,
|
||||
"tag":{}
|
||||
"Count":1B
|
||||
}
|
||||
}
|
||||
""", handler.asCompound());
|
||||
|
@ -0,0 +1,58 @@
|
||||
package net.minestom.server.tag;
|
||||
|
||||
import org.jglrxavpok.hephaistos.nbt.NBT;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTType;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class TagNbtSeparatorTest {
|
||||
|
||||
@Test
|
||||
public void primitives() {
|
||||
assertSeparation(new TagNbtSeparator.Entry<>(Tag.Byte("key"), (byte) 1),
|
||||
"key", NBT.Byte(1));
|
||||
assertSeparation(new TagNbtSeparator.Entry<>(Tag.Short("key"), (short) 1),
|
||||
"key", NBT.Short(1));
|
||||
assertSeparation(new TagNbtSeparator.Entry<>(Tag.Integer("key"), 1),
|
||||
"key", NBT.Int(1));
|
||||
assertSeparation(new TagNbtSeparator.Entry<>(Tag.Long("key"), 1L),
|
||||
"key", NBT.Long(1));
|
||||
assertSeparation(new TagNbtSeparator.Entry<>(Tag.Float("key"), 1f),
|
||||
"key", NBT.Float(1));
|
||||
assertSeparation(new TagNbtSeparator.Entry<>(Tag.Double("key"), 1d),
|
||||
"key", NBT.Double(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compound() {
|
||||
assertSeparation(new TagNbtSeparator.Entry<>(Tag.Byte("key").path("path"), (byte) 1),
|
||||
"path", NBT.Compound(Map.of("key", NBT.Byte(1))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void list() {
|
||||
assertSeparation(new TagNbtSeparator.Entry<>(Tag.Integer("key").list(), List.of(1)),
|
||||
"key", NBT.List(NBTType.TAG_Int, NBT.Int(1)));
|
||||
}
|
||||
|
||||
void assertSeparation(List<TagNbtSeparator.Entry<?>> expected, String key, NBT nbt) {
|
||||
assertEquals(expected, retrieve(key, nbt));
|
||||
}
|
||||
|
||||
void assertSeparation(TagNbtSeparator.Entry<?> expected, String key, NBT nbt) {
|
||||
var entries = retrieve(key, nbt);
|
||||
assertEquals(1, entries.size());
|
||||
assertEquals(expected, entries.get(0));
|
||||
}
|
||||
|
||||
List<TagNbtSeparator.Entry<?>> retrieve(String key, NBT nbt) {
|
||||
List<TagNbtSeparator.Entry<?>> entries = new ArrayList<>();
|
||||
TagNbtSeparator.separate(key, nbt, entries::add);
|
||||
return entries;
|
||||
}
|
||||
}
|
@ -1,17 +1,29 @@
|
||||
package net.minestom.server.tag;
|
||||
|
||||
import org.jglrxavpok.hephaistos.nbt.NBT;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTType;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Ensure that NBT tag can be read from other tags properly.
|
||||
*/
|
||||
public class TagNbtTest {
|
||||
|
||||
@Test
|
||||
public void invalidListTag() {
|
||||
assertThrows(IllegalArgumentException.class, () -> Tag.NBT("nbt").list());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidMapTag() {
|
||||
assertThrows(IllegalArgumentException.class, () -> Tag.NBT("nbt").map(nbt -> 1, NBT::Int));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compoundRead() {
|
||||
var handler = TagHandler.newHandler();
|
||||
@ -37,4 +49,50 @@ public class TagNbtTest {
|
||||
var path = Tag.Integer("key").path("path1", "path2");
|
||||
assertEquals(5, handler.getTag(path));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compoundWrite() {
|
||||
var handler = TagHandler.newHandler();
|
||||
var nbtTag = Tag.NBT("path1");
|
||||
|
||||
var nbt = NBT.Compound(Map.of("key", NBT.Int(5)));
|
||||
handler.setTag(nbtTag, nbt);
|
||||
assertEquals(nbt, handler.getTag(nbtTag));
|
||||
|
||||
var path = Tag.Integer("key").path("path1");
|
||||
handler.setTag(path, 10);
|
||||
assertEquals(10, handler.getTag(path));
|
||||
assertEquals(NBT.Compound(Map.of("key", NBT.Int(10))), handler.getTag(nbtTag));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rawList() {
|
||||
var handler = TagHandler.newHandler();
|
||||
var nbtTag = Tag.NBT("list");
|
||||
var list = NBT.List(NBTType.TAG_Int, NBT.Int(1));
|
||||
handler.setTag(nbtTag, list);
|
||||
assertEquals(list, handler.getTag(nbtTag));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void listConversion() {
|
||||
var handler = TagHandler.newHandler();
|
||||
var nbtTag = Tag.NBT("list");
|
||||
var listTag = Tag.Integer("list").list();
|
||||
var list = NBT.List(NBTType.TAG_Int, NBT.Int(1));
|
||||
handler.setTag(nbtTag, list);
|
||||
|
||||
assertEquals(list, handler.getTag(nbtTag));
|
||||
assertNotSame(list, handler.getTag(nbtTag));
|
||||
assertEquals(List.of(1), handler.getTag(listTag));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rawArray() {
|
||||
var handler = TagHandler.newHandler();
|
||||
var nbtTag = Tag.NBT("array");
|
||||
var array = NBT.IntArray(1, 2, 3);
|
||||
handler.setTag(nbtTag, array);
|
||||
assertEquals(array, handler.getTag(nbtTag));
|
||||
}
|
||||
}
|
||||
|
@ -133,7 +133,14 @@ public class TagPathTest {
|
||||
}
|
||||
""", handler.asCompound());
|
||||
|
||||
assertThrows(IllegalStateException.class, () -> handler.setTag(tag1, 5));
|
||||
handler.setTag(tag1, 2);
|
||||
assertEqualsSNBT("""
|
||||
{
|
||||
"key": {
|
||||
"value":2
|
||||
}
|
||||
}
|
||||
""", handler.asCompound());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -142,7 +149,7 @@ public class TagPathTest {
|
||||
var tag = Tag.Integer("key");
|
||||
var path = Tag.Integer("value").path("key");
|
||||
handler.setTag(path, 5);
|
||||
assertThrows(IllegalStateException.class, () -> handler.getTag(tag));
|
||||
assertNull(handler.getTag(tag));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -254,8 +261,16 @@ public class TagPathTest {
|
||||
}
|
||||
""", handler.asCompound());
|
||||
|
||||
// Cannot enter a structure from a path tag
|
||||
assertThrows(IllegalStateException.class, () -> handler.setTag(tag.path("struct"), 5));
|
||||
handler.setTag(tag.path("struct"), 2);
|
||||
assertEqualsSNBT("""
|
||||
{
|
||||
value:5,
|
||||
"struct": {
|
||||
"value":2
|
||||
}
|
||||
}
|
||||
""", handler.asCompound());
|
||||
assertEquals(new Entry(2), handler.getTag(struct));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -269,6 +284,15 @@ public class TagPathTest {
|
||||
"key":5
|
||||
}
|
||||
""", handler.asCompound());
|
||||
assertThrows(IllegalStateException.class, () -> handler.setTag(path, 5));
|
||||
handler.setTag(path, 2);
|
||||
assertEqualsSNBT("""
|
||||
{
|
||||
"key": {
|
||||
"second": {
|
||||
"value":2
|
||||
}
|
||||
}
|
||||
}
|
||||
""", handler.asCompound());
|
||||
}
|
||||
}
|
||||
|
115
src/test/java/net/minestom/server/tag/TagValueShareTest.java
Normal file
115
src/test/java/net/minestom/server/tag/TagValueShareTest.java
Normal file
@ -0,0 +1,115 @@
|
||||
package net.minestom.server.tag;
|
||||
|
||||
import net.minestom.server.coordinate.Vec;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
* Test tags that can share cached values.
|
||||
*/
|
||||
public class TagValueShareTest {
|
||||
|
||||
record Entry(int value) {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void same() {
|
||||
var tag = Tag.String("test");
|
||||
assertTrue(tag.shareValue(tag));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void similar() {
|
||||
var tag = Tag.String("test");
|
||||
var tag2 = Tag.String("test");
|
||||
assertTrue(tag.shareValue(tag2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void differentDefault() {
|
||||
var tag = Tag.String("test").defaultValue("test2");
|
||||
var tag2 = Tag.String("test").defaultValue("test3");
|
||||
assertTrue(tag.shareValue(tag2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void differentType() {
|
||||
var tag = Tag.String("test");
|
||||
var tag2 = Tag.Integer("test");
|
||||
assertFalse(tag.shareValue(tag2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mapSame() {
|
||||
// Force identical functions
|
||||
Function<Integer, Entry> t1 = Entry::new;
|
||||
Function<Entry, Integer> t2 = Entry::value;
|
||||
|
||||
var tag = Tag.Integer("key");
|
||||
var map1 = tag.map(t1, t2);
|
||||
var map2 = tag.map(t1, t2);
|
||||
assertTrue(map1.shareValue(map2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mapChild() {
|
||||
var intTag = Tag.Integer("key");
|
||||
var tag = intTag.map(Entry::new, Entry::value);
|
||||
assertFalse(intTag.shareValue(tag));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void list() {
|
||||
var tag = Tag.String("test").list();
|
||||
assertTrue(tag.shareValue(tag));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void listScope() {
|
||||
var tag = Tag.String("test");
|
||||
assertFalse(tag.shareValue(tag.list()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void similarList() {
|
||||
var tag = Tag.String("test").list();
|
||||
var tag2 = Tag.String("test").list();
|
||||
assertTrue(tag.shareValue(tag2));
|
||||
assertTrue(tag.list().shareValue(tag2.list()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void differentList() {
|
||||
var tag = Tag.String("test").list();
|
||||
var tag2 = Tag.String("test").list();
|
||||
assertFalse(tag.shareValue(tag2.list()));
|
||||
assertFalse(tag.list().shareValue(tag2.list().list()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void differentListType() {
|
||||
var tag = Tag.String("test").list();
|
||||
var tag2 = Tag.Integer("test").list();
|
||||
assertFalse(tag.shareValue(tag2));
|
||||
assertFalse(tag.list().shareValue(tag2.list()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void recordStructure() {
|
||||
var tag = Tag.Structure("test", Vec.class);
|
||||
var tag2 = Tag.Structure("test", Vec.class);
|
||||
assertTrue(tag.shareValue(tag2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void recordStructureList() {
|
||||
var tag = Tag.Structure("test", Vec.class).list();
|
||||
var tag2 = Tag.Structure("test", Vec.class).list();
|
||||
assertTrue(tag.shareValue(tag2));
|
||||
assertTrue(tag.list().shareValue(tag2.list()));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user