This commit is contained in:
TheMode 2022-03-24 09:03:30 +01:00 committed by GitHub
parent 2ab3bca6d7
commit 2301ad9976
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 289 additions and 13 deletions

View File

@ -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) {

View File

@ -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;

View File

@ -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);

View 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());
}
}