mirror of
https://github.com/Minestom/Minestom.git
synced 2025-01-04 07:28:19 +01:00
Tag list (#801)
This commit is contained in:
parent
2ab3bca6d7
commit
2301ad9976
@ -13,6 +13,7 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
/**
|
||||
* Represents a key to retrieve or change a value.
|
||||
@ -28,32 +29,35 @@ public class Tag<T> {
|
||||
record PathEntry(String name, int index) {
|
||||
}
|
||||
|
||||
final int index;
|
||||
private final String key;
|
||||
final Function<NBT, T> readFunction;
|
||||
final Function<T, NBT> writeFunction;
|
||||
private final Supplier<T> defaultValue;
|
||||
|
||||
final int index;
|
||||
|
||||
final List<PathEntry> path;
|
||||
final UnaryOperator<T> copy;
|
||||
|
||||
protected Tag(@NotNull String key,
|
||||
@NotNull Function<NBT, T> readFunction,
|
||||
@NotNull Function<T, NBT> writeFunction,
|
||||
@Nullable Supplier<T> defaultValue,
|
||||
@Nullable List<PathEntry> path) {
|
||||
this.index = INDEX_MAP.get(key);
|
||||
Tag(int index, String key,
|
||||
Function<NBT, T> readFunction,
|
||||
Function<T, NBT> writeFunction,
|
||||
@Nullable Supplier<T> defaultValue, @Nullable List<PathEntry> path,
|
||||
@Nullable UnaryOperator<T> copy) {
|
||||
assert index == INDEX_MAP.get(key);
|
||||
this.index = index;
|
||||
this.key = key;
|
||||
this.readFunction = readFunction;
|
||||
this.writeFunction = writeFunction;
|
||||
this.defaultValue = defaultValue;
|
||||
this.path = path;
|
||||
this.copy = copy;
|
||||
}
|
||||
|
||||
static <T, N extends NBT> Tag<T> tag(@NotNull String key,
|
||||
@NotNull Function<N, T> readFunction,
|
||||
@NotNull Function<T, N> writeFunction) {
|
||||
return new Tag<>(key, (Function<NBT, T>) readFunction, (Function<T, NBT>) writeFunction, null, null);
|
||||
return new Tag<>(INDEX_MAP.get(key), key, (Function<NBT, T>) readFunction, (Function<T, NBT>) writeFunction,
|
||||
null, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -67,7 +71,7 @@ public class Tag<T> {
|
||||
|
||||
@Contract(value = "_ -> new", pure = true)
|
||||
public Tag<T> defaultValue(@NotNull Supplier<T> defaultValue) {
|
||||
return new Tag<>(key, readFunction, writeFunction, defaultValue, path);
|
||||
return new Tag<>(index, key, readFunction, writeFunction, defaultValue, path, copy);
|
||||
}
|
||||
|
||||
@Contract(value = "_ -> new", pure = true)
|
||||
@ -78,7 +82,7 @@ public class Tag<T> {
|
||||
@Contract(value = "_, _ -> new", pure = true)
|
||||
public <R> Tag<R> map(@NotNull Function<T, R> readMap,
|
||||
@NotNull Function<R, T> writeMap) {
|
||||
return new Tag<>(key,
|
||||
return new Tag<>(index, key,
|
||||
// Read
|
||||
readFunction.andThen(t -> {
|
||||
if (t == null) return null;
|
||||
@ -88,14 +92,55 @@ public class Tag<T> {
|
||||
writeMap.andThen(writeFunction),
|
||||
// Default value
|
||||
() -> readMap.apply(createDefault()),
|
||||
path);
|
||||
path, null);
|
||||
}
|
||||
|
||||
@ApiStatus.Experimental
|
||||
@Contract(value = "-> new", pure = true)
|
||||
public Tag<List<T>> list() {
|
||||
return new Tag<>(index, key,
|
||||
read -> {
|
||||
var list = (NBTList<?>) read;
|
||||
final int size = list.getSize();
|
||||
if (size == 0)
|
||||
return List.of();
|
||||
T[] array = (T[]) new Object[size];
|
||||
for (int i = 0; i < size; i++) {
|
||||
array[i] = readFunction.apply(list.get(i));
|
||||
}
|
||||
return List.of(array);
|
||||
},
|
||||
write -> {
|
||||
final int size = write.size();
|
||||
if (size == 0)
|
||||
return new NBTList<>(NBTType.TAG_String); // String is the default type for lists
|
||||
NBTType<NBT> type = null;
|
||||
NBT[] array = new NBT[size];
|
||||
for (int i = 0; i < size; i++) {
|
||||
final NBT nbt = writeFunction.apply(write.get(i));
|
||||
if (type == null) {
|
||||
type = (NBTType<NBT>) nbt.getID();
|
||||
} else if (type != nbt.getID()) {
|
||||
throw new IllegalArgumentException("All elements of the list must have the same type");
|
||||
}
|
||||
array[i] = nbt;
|
||||
}
|
||||
return NBT.List(type, List.of(array));
|
||||
}, null, path,
|
||||
copy != null ? ts -> {
|
||||
T[] array = (T[]) new Object[ts.size()];
|
||||
for (int i = 0; i < ts.size(); i++) {
|
||||
array[i] = copy.apply(ts.get(i));
|
||||
}
|
||||
return List.of(array);
|
||||
} : List::copyOf);
|
||||
}
|
||||
|
||||
@ApiStatus.Experimental
|
||||
@Contract(value = "_ -> new", pure = true)
|
||||
public Tag<T> path(@NotNull String @Nullable ... path) {
|
||||
final List<PathEntry> entries = path != null ? Arrays.stream(path).map(s -> new PathEntry(s, INDEX_MAP.get(s))).toList() : null;
|
||||
return new Tag<>(key, readFunction, writeFunction, defaultValue, entries);
|
||||
return new Tag<>(index, key, readFunction, writeFunction, defaultValue, entries, copy);
|
||||
}
|
||||
|
||||
public @Nullable T read(@NotNull NBTCompoundLike nbt) {
|
||||
|
@ -10,6 +10,7 @@ import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound;
|
||||
|
||||
import java.lang.invoke.VarHandle;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
final class TagHandlerImpl implements TagHandler {
|
||||
private Entry<?>[] entries = new Entry[0];
|
||||
@ -23,6 +24,12 @@ final class TagHandlerImpl implements TagHandler {
|
||||
|
||||
@Override
|
||||
public synchronized <T> void setTag(@NotNull Tag<T> tag, @Nullable T value) {
|
||||
// Convert value to fit the tag (e.g. list copies)
|
||||
if (value != null) {
|
||||
final UnaryOperator<T> copy = tag.copy;
|
||||
if (copy != null) value = copy.apply(value);
|
||||
}
|
||||
|
||||
VarHandle.acquireFence();
|
||||
int tagIndex = tag.index;
|
||||
TagHandlerImpl local = this;
|
||||
|
@ -2,6 +2,7 @@ package net.minestom.server.utils.collection;
|
||||
|
||||
import it.unimi.dsi.fastutil.HashCommon;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static it.unimi.dsi.fastutil.HashCommon.arraySize;
|
||||
@ -24,6 +25,7 @@ public final class IndexMap<K> {
|
||||
value = new int[n + 1];
|
||||
}
|
||||
|
||||
@Contract(pure = true)
|
||||
public int get(@NotNull K key) {
|
||||
final int hash = HashCommon.mix(key.hashCode());
|
||||
int index = getInt(key, hash);
|
||||
|
222
src/test/java/net/minestom/server/tag/TagListTest.java
Normal file
222
src/test/java/net/minestom/server/tag/TagListTest.java
Normal file
@ -0,0 +1,222 @@
|
||||
package net.minestom.server.tag;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static net.minestom.server.api.TestUtils.assertEqualsSNBT;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class TagListTest {
|
||||
|
||||
@Test
|
||||
public void basic() {
|
||||
var handler = TagHandler.newHandler();
|
||||
Tag<Integer> tag = Tag.Integer("number");
|
||||
Tag<List<Integer>> list = tag.list();
|
||||
|
||||
handler.setTag(tag, 5);
|
||||
assertEquals(5, handler.getTag(tag));
|
||||
assertNull(handler.getTag(list));
|
||||
|
||||
handler.setTag(list, List.of(1, 2, 3));
|
||||
assertEquals(List.of(1, 2, 3), handler.getTag(list));
|
||||
assertNull(handler.getTag(tag));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void snbt() {
|
||||
var handler = TagHandler.newHandler();
|
||||
Tag<List<Integer>> tag = Tag.Integer("numbers").list();
|
||||
|
||||
handler.setTag(tag, List.of(1, 2, 3));
|
||||
assertEqualsSNBT("""
|
||||
{
|
||||
"numbers": [1,2,3]
|
||||
}
|
||||
""", handler.asCompound());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void empty() {
|
||||
var handler = TagHandler.newHandler();
|
||||
Tag<List<Integer>> tag = Tag.Integer("numbers").list();
|
||||
handler.setTag(tag, List.of());
|
||||
assertEquals(List.of(), handler.getTag(tag));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptySnbt() {
|
||||
var handler = TagHandler.newHandler();
|
||||
Tag<List<Integer>> tag = Tag.Integer("numbers").list();
|
||||
handler.setTag(tag, List.of());
|
||||
assertEqualsSNBT("""
|
||||
{
|
||||
"numbers":[]
|
||||
}
|
||||
""", handler.asCompound());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removal() {
|
||||
var handler = TagHandler.newHandler();
|
||||
Tag<List<Integer>> tag = Tag.Integer("numbers").list();
|
||||
handler.setTag(tag, List.of(1));
|
||||
assertEquals(List.of(1), handler.getTag(tag));
|
||||
handler.removeTag(tag);
|
||||
assertNull(handler.getTag(tag));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removalSnbt() {
|
||||
var handler = TagHandler.newHandler();
|
||||
Tag<List<Integer>> tag = Tag.Integer("numbers").list();
|
||||
handler.setTag(tag, List.of(1));
|
||||
assertEqualsSNBT("""
|
||||
{
|
||||
"numbers": [1]
|
||||
}
|
||||
""", handler.asCompound());
|
||||
handler.removeTag(tag);
|
||||
assertEqualsSNBT("{}", handler.asCompound());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void chaining() {
|
||||
var handler = TagHandler.newHandler();
|
||||
Tag<List<List<Integer>>> tag = Tag.Integer("numbers").list().list();
|
||||
var integers = List.of(List.of(1, 2, 3), List.of(4, 5, 6));
|
||||
handler.setTag(tag, integers);
|
||||
assertEquals(integers, handler.getTag(tag));
|
||||
handler.removeTag(tag);
|
||||
assertNull(handler.getTag(tag));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void chainingSnbt() {
|
||||
var handler = TagHandler.newHandler();
|
||||
Tag<List<List<Integer>>> tag = Tag.Integer("numbers").list().list();
|
||||
var integers = List.of(List.of(1, 2, 3), List.of(4, 5, 6));
|
||||
handler.setTag(tag, integers);
|
||||
assertEqualsSNBT("""
|
||||
{
|
||||
"numbers":[
|
||||
[1,2,3],
|
||||
[4,5,6]
|
||||
]
|
||||
}
|
||||
""", handler.asCompound());
|
||||
handler.removeTag(tag);
|
||||
assertEqualsSNBT("{}", handler.asCompound());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultValue() {
|
||||
var handler = TagHandler.newHandler();
|
||||
var val = List.of(1, 2, 3);
|
||||
var tag = Tag.Integer("number").list().defaultValue(val);
|
||||
assertEquals(List.of(1, 2, 3), handler.getTag(tag));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultValueReset() {
|
||||
var handler = TagHandler.newHandler();
|
||||
var tag = Tag.Integer("number").defaultValue(5);
|
||||
var list = tag.list();
|
||||
assertNull(handler.getTag(list));
|
||||
assertEquals(List.of(1, 2, 3), handler.getTag(list.defaultValue(List.of(1, 2, 3))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void immutability() {
|
||||
var handler = TagHandler.newHandler();
|
||||
var tag = Tag.Integer("number").list();
|
||||
List<Integer> val = new ArrayList<>();
|
||||
val.add(1);
|
||||
|
||||
handler.setTag(tag, val);
|
||||
assertNotSame(val, handler.getTag(tag));
|
||||
assertEquals(List.of(1), handler.getTag(tag));
|
||||
|
||||
val.add(2); // Must not modify the nbt
|
||||
assertNotSame(val, handler.getTag(tag));
|
||||
assertEquals(List.of(1), handler.getTag(tag));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void chainingImmutability() {
|
||||
var handler = TagHandler.newHandler();
|
||||
Tag<List<List<Integer>>> tag = Tag.Integer("numbers").list().list();
|
||||
List<List<Integer>> val = new ArrayList<>();
|
||||
val.add(new ArrayList<>(Arrays.asList(1, 2, 3)));
|
||||
val.add(new ArrayList<>(Arrays.asList(4, 5, 6)));
|
||||
|
||||
handler.setTag(tag, val);
|
||||
assertNotSame(val, handler.getTag(tag));
|
||||
assertEquals(List.of(List.of(1, 2, 3), List.of(4, 5, 6)), handler.getTag(tag));
|
||||
|
||||
// Must not modify the nbt
|
||||
val.get(0).add(7);
|
||||
val.get(1).add(8);
|
||||
val.add(new ArrayList<>(Arrays.asList(9, 10, 11)));
|
||||
assertNotSame(val, handler.getTag(tag));
|
||||
assertEquals(List.of(List.of(1, 2, 3), List.of(4, 5, 6)), handler.getTag(tag));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void immutabilitySnbt() {
|
||||
var handler = TagHandler.newHandler();
|
||||
var tag = Tag.Integer("numbers").list();
|
||||
List<Integer> val = new ArrayList<>();
|
||||
val.add(1);
|
||||
|
||||
handler.setTag(tag, val);
|
||||
assertEqualsSNBT("""
|
||||
{
|
||||
"numbers": [1]
|
||||
}
|
||||
""", handler.asCompound());
|
||||
|
||||
val.add(2); // Must not modify the nbt
|
||||
assertEqualsSNBT("""
|
||||
{
|
||||
"numbers": [1]
|
||||
}
|
||||
""", handler.asCompound());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void chainingImmutabilitySnbt() {
|
||||
var handler = TagHandler.newHandler();
|
||||
Tag<List<List<Integer>>> tag = Tag.Integer("numbers").list().list();
|
||||
List<List<Integer>> val = new ArrayList<>();
|
||||
val.add(new ArrayList<>(Arrays.asList(1, 2, 3)));
|
||||
val.add(new ArrayList<>(Arrays.asList(4, 5, 6)));
|
||||
|
||||
handler.setTag(tag, val);
|
||||
assertEqualsSNBT("""
|
||||
{
|
||||
"numbers":[
|
||||
[1,2,3],
|
||||
[4,5,6]
|
||||
]
|
||||
}
|
||||
""", handler.asCompound());
|
||||
|
||||
|
||||
// Must not modify the nbt
|
||||
val.get(0).add(7);
|
||||
val.get(1).add(8);
|
||||
val.add(new ArrayList<>(Arrays.asList(9, 10, 11)));
|
||||
assertEqualsSNBT("""
|
||||
{
|
||||
"numbers":[
|
||||
[1,2,3],
|
||||
[4,5,6]
|
||||
]
|
||||
}
|
||||
""", handler.asCompound());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user