Tag internal rework (#782)

This commit is contained in:
TheMode 2022-03-20 01:47:57 +01:00 committed by GitHub
parent 84871ea93e
commit f2fec73202
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 817 additions and 364 deletions

View File

@ -0,0 +1,48 @@
package net.minestom.jmh.tag;
import net.minestom.server.tag.Tag;
import net.minestom.server.tag.TagHandler;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import java.util.concurrent.TimeUnit;
@Warmup(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 10, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
@Fork(3)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Benchmark)
public class TagReadBenchmark {
static final Tag<String> TAG = Tag.String("key");
@Param({"false", "true"})
public boolean present;
TagHandler tagHandler;
Tag<String> secondTag;
@Setup
public void setup() {
this.tagHandler = TagHandler.newHandler();
if (present) {
tagHandler.setTag(TAG, "value");
}
secondTag = Tag.String("key");
}
@Benchmark
public void readConstantTag(Blackhole blackhole) {
blackhole.consume(tagHandler.getTag(TAG));
}
@Benchmark
public void readDifferentTag(Blackhole blackhole) {
blackhole.consume(tagHandler.getTag(secondTag));
}
@Benchmark
public void readNewTag(Blackhole blackhole) {
blackhole.consume(tagHandler.getTag(Tag.String("key")));
}
}

View File

@ -4,7 +4,7 @@ import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.Component;
import net.minestom.server.entity.Player;
import net.minestom.server.permission.PermissionHandler;
import net.minestom.server.tag.TagHandler;
import net.minestom.server.tag.Taggable;
import org.jetbrains.annotations.NotNull;
/**
@ -12,7 +12,7 @@ import org.jetbrains.annotations.NotNull;
* <p>
* Main implementations are {@link Player} and {@link ConsoleSender}.
*/
public interface CommandSender extends PermissionHandler, Audience, TagHandler {
public interface CommandSender extends PermissionHandler, Audience, Taggable {
/**
* Sends a raw string message.

View File

@ -5,11 +5,8 @@ import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import net.minestom.server.permission.Permission;
import net.minestom.server.tag.Tag;
import net.minestom.server.tag.TagHandler;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -24,7 +21,7 @@ public class ConsoleSender implements CommandSender {
private static final Logger LOGGER = LoggerFactory.getLogger(ConsoleSender.class);
private final Set<Permission> permissions = new CopyOnWriteArraySet<>();
private final MutableNBTCompound nbtCompound = new MutableNBTCompound();
private final TagHandler tagHandler = TagHandler.newHandler();
@Override
public void sendMessage(@NotNull String message) {
@ -54,12 +51,7 @@ public class ConsoleSender implements CommandSender {
}
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return tag.read(nbtCompound);
}
@Override
public <T> void setTag(@NotNull Tag<T> tag, @Nullable T value) {
tag.write(nbtCompound, value);
public @NotNull TagHandler tagHandler() {
return tagHandler;
}
}

View File

@ -3,11 +3,8 @@ package net.minestom.server.command;
import net.kyori.adventure.audience.Audience;
import net.minestom.server.command.builder.CommandContext;
import net.minestom.server.permission.Permission;
import net.minestom.server.tag.Tag;
import net.minestom.server.tag.TagHandler;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound;
import java.util.Collections;
import java.util.HashSet;
@ -23,7 +20,7 @@ import java.util.Set;
public class ServerSender implements CommandSender {
private final Set<Permission> permissions = Collections.unmodifiableSet(new HashSet<>());
private final MutableNBTCompound nbtCompound = new MutableNBTCompound();
private final TagHandler tagHandler = TagHandler.newHandler();
@NotNull
@Override
@ -32,12 +29,7 @@ public class ServerSender implements CommandSender {
}
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return tag.read(nbtCompound);
}
@Override
public <T> void setTag(@NotNull Tag<T> tag, @Nullable T value) {
tag.write(nbtCompound, value);
public @NotNull TagHandler tagHandler() {
return tagHandler;
}
}

View File

@ -42,9 +42,8 @@ import net.minestom.server.potion.TimedPotion;
import net.minestom.server.snapshot.EntitySnapshot;
import net.minestom.server.snapshot.SnapshotUpdater;
import net.minestom.server.snapshot.Snapshotable;
import net.minestom.server.tag.Tag;
import net.minestom.server.tag.TagHandler;
import net.minestom.server.tag.TagReadable;
import net.minestom.server.tag.Taggable;
import net.minestom.server.thread.Acquirable;
import net.minestom.server.timer.Schedulable;
import net.minestom.server.timer.Scheduler;
@ -64,8 +63,6 @@ import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound;
import space.vectrix.flare.fastutil.Int2ObjectSyncMap;
import java.time.Duration;
@ -85,7 +82,7 @@ import java.util.function.UnaryOperator;
* <p>
* To create your own entity you probably want to extends {@link LivingEntity} or {@link EntityCreature} instead.
*/
public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, EventHandler<EntityEvent>, TagHandler, PermissionHandler, HoverEventSource<ShowEntity>, Sound.Emitter {
public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, EventHandler<EntityEvent>, Taggable, PermissionHandler, HoverEventSource<ShowEntity>, Sound.Emitter {
private static final Int2ObjectSyncMap<Entity> ENTITY_BY_ID = Int2ObjectSyncMap.hashmap();
private static final Map<UUID, Entity> ENTITY_BY_UUID = new ConcurrentHashMap<>();
@ -150,7 +147,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
protected final EntityView viewEngine = new EntityView(this);
protected final Set<Player> viewers = viewEngine.set;
private final MutableNBTCompound nbtCompound = new MutableNBTCompound();
private final TagHandler tagHandler = TagHandler.newHandler();
private final Scheduler scheduler = Scheduler.newScheduler();
private final EventNode<EntityEvent> eventNode;
private final Set<Permission> permissions = new CopyOnWriteArraySet<>();
@ -1538,13 +1535,8 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
}
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return tag.read(nbtCompound);
}
@Override
public <T> void setTag(@NotNull Tag<T> tag, @Nullable T value) {
tag.write(nbtCompound, value);
public @NotNull TagHandler tagHandler() {
return tagHandler;
}
@Override
@ -1561,7 +1553,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
return new EntitySnapshotImpl.Entity(entityType, uuid, id, position, velocity,
updater.reference(instance), chunk.getChunkX(), chunk.getChunkZ(),
viewersId, passengersId, vehicle == null ? -1 : vehicle.getEntityId(),
TagReadable.fromCompound(nbtCompound.toCompound()));
tagHandler.readableCopy());
}
@Override

View File

@ -5,12 +5,10 @@ import net.minestom.server.entity.Entity;
import net.minestom.server.entity.LivingEntity;
import net.minestom.server.entity.Player;
import net.minestom.server.sound.SoundEvent;
import net.minestom.server.tag.Tag;
import net.minestom.server.tag.TagHandler;
import net.minestom.server.tag.Taggable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound;
/**
* Represents a type of damage, required when calling {@link LivingEntity#damage(DamageType, float)}
@ -18,7 +16,7 @@ import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound;
* <p>
* This class can be extended if you need to include custom fields and/or methods.
*/
public class DamageType implements TagHandler {
public class DamageType implements Taggable {
public static final DamageType VOID = new DamageType("attack.outOfWorld");
public static final DamageType GRAVITY = new DamageType("attack.fall");
@ -29,8 +27,7 @@ public class DamageType implements TagHandler {
}
};
private final String identifier;
private final Object nbtLock = new Object();
private final MutableNBTCompound nbt = new MutableNBTCompound();
private final TagHandler tagHandler = TagHandler.newHandler();
/**
* Creates a new damage type.
@ -128,16 +125,7 @@ public class DamageType implements TagHandler {
}
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
synchronized (nbtLock) {
return tag.read(nbt);
}
}
@Override
public <T> void setTag(@NotNull Tag<T> tag, @Nullable T value) {
synchronized (nbtLock) {
tag.write(nbt, value);
}
public @NotNull TagHandler tagHandler() {
return tagHandler;
}
}

View File

@ -3,7 +3,6 @@ package net.minestom.server.instance;
import net.minestom.server.MinecraftServer;
import net.minestom.server.instance.block.Block;
import net.minestom.server.instance.block.BlockHandler;
import net.minestom.server.tag.Tag;
import net.minestom.server.utils.NamespaceID;
import net.minestom.server.utils.async.AsyncUtils;
import net.minestom.server.world.biomes.Biome;
@ -52,7 +51,7 @@ public class AnvilLoader implements IChunkLoader {
try (var reader = new NBTReader(Files.newInputStream(levelPath))) {
final NBTCompound tag = (NBTCompound) reader.read();
Files.copy(levelPath, path.resolve("level.dat_old"), StandardCopyOption.REPLACE_EXISTING);
instance.setTag(Tag.NBT, tag);
instance.tagHandler().updateContent(tag);
} catch (IOException | NBTException e) {
MinecraftServer.getExceptionManager().handleException(e);
}
@ -213,8 +212,8 @@ public class AnvilLoader implements IChunkLoader {
@Override
public @NotNull CompletableFuture<Void> saveInstance(@NotNull Instance instance) {
final var nbt = instance.getTag(Tag.NBT);
if (nbt == null) {
final var nbt = instance.tagHandler().asCompound();
if (nbt.isEmpty()) {
// Instance has no data
return AsyncUtils.VOID_FUTURE;
}

View File

@ -9,15 +9,12 @@ import net.minestom.server.entity.pathfinding.PFColumnarSpace;
import net.minestom.server.instance.block.Block;
import net.minestom.server.network.packet.server.play.ChunkDataPacket;
import net.minestom.server.snapshot.Snapshotable;
import net.minestom.server.tag.Tag;
import net.minestom.server.tag.TagHandler;
import net.minestom.server.tag.Taggable;
import net.minestom.server.utils.chunk.ChunkSupplier;
import net.minestom.server.utils.chunk.ChunkUtils;
import net.minestom.server.world.biomes.Biome;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound;
import java.util.List;
import java.util.Set;
@ -36,7 +33,7 @@ import java.util.UUID;
* You generally want to avoid storing references of this object as this could lead to a huge memory leak,
* you should store the chunk coordinates instead.
*/
public abstract class Chunk implements Block.Getter, Block.Setter, Biome.Getter, Biome.Setter, Viewable, Tickable, TagHandler, Snapshotable {
public abstract class Chunk implements Block.Getter, Block.Setter, Biome.Getter, Biome.Setter, Viewable, Tickable, Taggable, Snapshotable {
public static final int CHUNK_SIZE_X = 16;
public static final int CHUNK_SIZE_Z = 16;
public static final int CHUNK_SECTION_SIZE = 16;
@ -58,7 +55,7 @@ public abstract class Chunk implements Block.Getter, Block.Setter, Biome.Getter,
protected PFColumnarSpace columnarSpace;
// Data
private final MutableNBTCompound nbt = new MutableNBTCompound();
private final TagHandler tagHandler = TagHandler.newHandler();
public Chunk(@NotNull Instance instance, int chunkX, int chunkZ, boolean shouldGenerate) {
this.identifier = UUID.randomUUID();
@ -284,13 +281,8 @@ public abstract class Chunk implements Block.Getter, Block.Setter, Biome.Getter,
}
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return tag.read(nbt);
}
@Override
public <T> void setTag(@NotNull Tag<T> tag, @Nullable T value) {
tag.write(nbt, value);
public @NotNull TagHandler tagHandler() {
return tagHandler;
}
/**

View File

@ -16,8 +16,6 @@ import net.minestom.server.network.packet.server.play.data.ChunkData;
import net.minestom.server.network.packet.server.play.data.LightData;
import net.minestom.server.snapshot.ChunkSnapshot;
import net.minestom.server.snapshot.SnapshotUpdater;
import net.minestom.server.tag.Tag;
import net.minestom.server.tag.TagReadable;
import net.minestom.server.utils.ArrayUtils;
import net.minestom.server.utils.MathUtils;
import net.minestom.server.utils.Utils;
@ -257,7 +255,7 @@ public class DynamicChunk extends Chunk {
final int[] entityIds = ArrayUtils.mapToIntArray(entities, Entity::getEntityId);
return new InstanceSnapshotImpl.Chunk(minSection, chunkX, chunkZ,
clonedSections, entries.clone(), entityIds, updater.reference(instance),
TagReadable.fromCompound(Objects.requireNonNull(getTag(Tag.NBT))));
tagHandler().readableCopy());
}
private void assertLock() {

View File

@ -22,9 +22,8 @@ import net.minestom.server.snapshot.ChunkSnapshot;
import net.minestom.server.snapshot.InstanceSnapshot;
import net.minestom.server.snapshot.SnapshotUpdater;
import net.minestom.server.snapshot.Snapshotable;
import net.minestom.server.tag.Tag;
import net.minestom.server.tag.TagHandler;
import net.minestom.server.tag.TagReadable;
import net.minestom.server.tag.Taggable;
import net.minestom.server.thread.ThreadDispatcher;
import net.minestom.server.timer.Schedulable;
import net.minestom.server.timer.Scheduler;
@ -38,9 +37,7 @@ import net.minestom.server.world.DimensionType;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound;
import java.time.Duration;
import java.util.*;
@ -59,7 +56,7 @@ import java.util.stream.Collectors;
* with {@link InstanceManager#registerInstance(Instance)}, and
* you need to be sure to signal the {@link ThreadDispatcher} of every partition/element changes.
*/
public abstract class Instance implements Block.Getter, Block.Setter, Tickable, Schedulable, Snapshotable, TagHandler, PacketGroupingAudience {
public abstract class Instance implements Block.Getter, Block.Setter, Tickable, Schedulable, Snapshotable, Taggable, PacketGroupingAudience {
private boolean registered;
@ -85,8 +82,7 @@ public abstract class Instance implements Block.Getter, Block.Setter, Tickable,
protected UUID uniqueId;
// instance custom data
private final Object nbtLock = new Object();
private final MutableNBTCompound nbt = new MutableNBTCompound();
private final TagHandler tagHandler = TagHandler.newHandler();
private final Scheduler scheduler = Scheduler.newScheduler();
@ -599,17 +595,8 @@ public abstract class Instance implements Block.Getter, Block.Setter, Tickable,
}
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
synchronized (nbtLock) {
return tag.read(nbt);
}
}
@Override
public <T> void setTag(@NotNull Tag<T> tag, @Nullable T value) {
synchronized (nbtLock) {
tag.write(nbt, value);
}
public @NotNull TagHandler tagHandler() {
return tagHandler;
}
@Override
@ -623,7 +610,7 @@ public abstract class Instance implements Block.Getter, Block.Setter, Tickable,
final int[] entities = ArrayUtils.mapToIntArray(entityTracker.entities(), Entity::getEntityId);
return new InstanceSnapshotImpl.Instance(updater.reference(MinecraftServer.process()),
getDimensionType(), getWorldAge(), getTime(), chunksMap, entities,
TagReadable.fromCompound(Objects.requireNonNull(getTag(Tag.NBT))));
tagHandler.readableCopy());
}
/**

View File

@ -67,9 +67,7 @@ public sealed interface Block extends ProtocolObject, TagReadable, Blocks permit
* @return a new block with different nbt
*/
@Contract(pure = true)
default @NotNull Block withNbt(@Nullable NBTCompound compound) {
return withTag(Tag.NBT, compound);
}
@NotNull Block withNbt(@Nullable NBTCompound compound);
/**
* Creates a new block with the specified {@link BlockHandler handler}.
@ -88,9 +86,7 @@ public sealed interface Block extends ProtocolObject, TagReadable, Blocks permit
* @return the block nbt, null if not present
*/
@Contract(pure = true)
default @Nullable NBTCompound nbt() {
return getTag(Tag.NBT);
}
@Nullable NBTCompound nbt();
@Contract(pure = true)
default boolean hasNbt() {

View File

@ -150,6 +150,11 @@ record BlockImpl(@NotNull Registry.BlockEntry registry,
return new BlockImpl(registry, propertiesArray, finalNbt, handler);
}
@Override
public @NotNull Block withNbt(@Nullable NBTCompound compound) {
return new BlockImpl(registry, propertiesArray, compound, handler);
}
@Override
public @NotNull Block withHandler(@Nullable BlockHandler handler) {
return new BlockImpl(registry, propertiesArray, nbt, handler);

View File

@ -6,14 +6,11 @@ import net.minestom.server.event.inventory.PlayerInventoryItemChangeEvent;
import net.minestom.server.inventory.click.InventoryClickProcessor;
import net.minestom.server.inventory.condition.InventoryCondition;
import net.minestom.server.item.ItemStack;
import net.minestom.server.tag.Tag;
import net.minestom.server.tag.TagHandler;
import net.minestom.server.tag.Taggable;
import net.minestom.server.utils.MathUtils;
import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
@ -26,7 +23,7 @@ import java.util.function.UnaryOperator;
/**
* Represents an inventory where items can be modified/retrieved.
*/
public sealed abstract class AbstractInventory implements InventoryClickHandler, TagHandler
public sealed abstract class AbstractInventory implements InventoryClickHandler, Taggable
permits Inventory, PlayerInventory {
private static final VarHandle ITEM_UPDATER = MethodHandles.arrayElementVarHandle(ItemStack[].class);
@ -39,8 +36,7 @@ public sealed abstract class AbstractInventory implements InventoryClickHandler,
// the click processor which process all the clicks in the inventory
protected final InventoryClickProcessor clickProcessor = new InventoryClickProcessor();
private final Object nbtLock = new Object();
private final MutableNBTCompound nbt = new MutableNBTCompound();
private final TagHandler tagHandler = TagHandler.newHandler();
protected AbstractInventory(int size) {
this.size = size;
@ -251,16 +247,7 @@ public sealed abstract class AbstractInventory implements InventoryClickHandler,
}
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
synchronized (nbtLock) {
return tag.read(nbt);
}
}
@Override
public <T> void setTag(@NotNull Tag<T> tag, @Nullable T value) {
synchronized (nbtLock) {
tag.write(nbt, value);
}
public @NotNull TagHandler tagHandler() {
return tagHandler;
}
}

View File

@ -1,19 +1,16 @@
package net.minestom.server.tag;
import net.minestom.server.MinecraftServer;
import net.minestom.server.item.ItemStack;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jglrxavpok.hephaistos.nbt.NBT;
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import org.jglrxavpok.hephaistos.nbt.NBTCompoundLike;
import org.jglrxavpok.hephaistos.nbt.NBTException;
import org.jglrxavpok.hephaistos.nbt.*;
import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound;
import org.jglrxavpok.hephaistos.parser.SNBTParser;
import java.io.StringReader;
import java.util.function.BiConsumer;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.function.Supplier;
@ -26,74 +23,47 @@ import java.util.function.Supplier;
*/
@ApiStatus.NonExtendable
public class Tag<T> {
/**
* Handles the snbt of the tag holder.
* <p>
* Writing will override all tags. Proceed with caution.
*/
@ApiStatus.Experimental
public static final Tag<String> SNBT = new Tag<>(null, NBTCompoundLike::toSNBT, (original, snbt) -> {
try {
final var updated = new SNBTParser(new StringReader(snbt)).parse();
if (!(updated instanceof NBTCompound updatedCompound))
throw new IllegalArgumentException("'" + snbt + "' is not a compound!");
original.copyFrom(updatedCompound);
} catch (NBTException e) {
MinecraftServer.getExceptionManager().handleException(e);
}
});
/**
* Handles the complete tag holder compound.
* <p>
* Writing will override all tags. Proceed with caution.
*/
@ApiStatus.Experimental
public static final Tag<NBTCompound> NBT = new Tag<>(null, NBTCompoundLike::toCompound,
(original, updated) -> {
if (updated == null) {
original.clear();
return;
}
original.copyFrom(updated);
});
private static final Map<String, Integer> INDEX_MAP = new ConcurrentHashMap<>();
private static final AtomicInteger INDEX = new AtomicInteger();
private final String key;
private final Function<NBTCompoundLike, T> readFunction;
private final BiConsumer<MutableNBTCompound, T> writeConsumer;
private final Function<NBT, T> readFunction;
private final Function<T, NBT> writeFunction;
private final Supplier<T> defaultValue;
protected Tag(@Nullable String key,
@NotNull Function<NBTCompoundLike, T> readFunction,
@NotNull BiConsumer<MutableNBTCompound, T> writeConsumer,
final int index;
protected Tag(@NotNull String key,
@NotNull Function<NBT, T> readFunction,
@NotNull Function<T, NBT> writeFunction,
@Nullable Supplier<T> defaultValue) {
this.key = key;
this.readFunction = readFunction;
this.writeConsumer = writeConsumer;
this.writeFunction = writeFunction;
this.defaultValue = defaultValue;
this.index = INDEX_MAP.computeIfAbsent(key, k -> INDEX.getAndIncrement());
}
protected Tag(@Nullable String key,
@NotNull Function<NBTCompoundLike, T> readFunction,
@NotNull BiConsumer<MutableNBTCompound, T> writeConsumer) {
this(key, readFunction, writeConsumer, null);
static <T, N extends NBT> Tag<T> tag(@NotNull String key,
@NotNull Class<N> nbtClass,
@NotNull Function<N, T> readFunction,
@NotNull Function<T, N> writeFunction) {
return new Tag<T>(key, (Function<NBT, T>) readFunction, (Function<T, NBT>) writeFunction, null);
}
/**
* Returns the key used to navigate inside the holder nbt.
* <p>
* Can be null if unused (e.g. {@link #View(TagSerializer)}, {@link #SNBT} and {@link #NBT}).
*
* @return the tag key
*/
public @Nullable String getKey() {
public @NotNull String getKey() {
return key;
}
@Contract(value = "_ -> new", pure = true)
public Tag<T> defaultValue(@NotNull Supplier<T> defaultValue) {
return new Tag<>(key, readFunction, writeConsumer, defaultValue);
return new Tag<>(key, readFunction, writeFunction, defaultValue);
}
@Contract(value = "_ -> new", pure = true)
@ -106,42 +76,40 @@ public class Tag<T> {
@NotNull Function<R, T> writeMap) {
return new Tag<>(key,
// Read
nbtCompound -> {
final var old = readFunction.apply(nbtCompound);
if (old == null) {
return null;
}
return readMap.apply(old);
},
readFunction.andThen(t -> {
if (t == null) return null;
return readMap.apply(t);
}),
// Write
(nbtCompound, r) -> {
var n = writeMap.apply(r);
writeConsumer.accept(nbtCompound, n);
},
writeMap.andThen(writeFunction),
// Default value
() -> {
if (defaultValue == null) {
return null;
}
var old = defaultValue.get();
return readMap.apply(old);
});
() -> readMap.apply(createDefault()));
}
public @Nullable T read(@NotNull NBTCompoundLike nbtCompound) {
T result = readFunction.apply(nbtCompound);
if (result == null) {
final var supplier = defaultValue;
result = supplier != null ? supplier.get() : null;
public @Nullable T read(@NotNull NBTCompoundLike nbt) {
final String key = this.key;
if (key.isEmpty()) {
// Special handling for view tag
return convertToValue(nbt.toCompound());
}
return result;
final NBT subTag = nbt.get(key);
return convertToValue(subTag);
}
T createDefault() {
final var supplier = defaultValue;
return supplier != null ? supplier.get() : null;
}
public void write(@NotNull MutableNBTCompound nbtCompound, @Nullable T value) {
if (key == null || value != null) {
this.writeConsumer.accept(nbtCompound, value);
final String key = this.key;
if (value != null) {
final NBT nbt = writeFunction.apply(value);
if (key.isEmpty()) nbtCompound.copyFrom((NBTCompoundLike) nbt);
else nbtCompound.set(key, nbt);
} else {
nbtCompound.remove(key);
if (key.isEmpty()) nbtCompound.clear();
else nbtCompound.remove(key);
}
}
@ -150,53 +118,52 @@ public class Tag<T> {
write(nbtCompound, (T) value);
}
T convertToValue(NBT nbt) {
final T result;
try {
if (nbt == null || (result = readFunction.apply(nbt)) == null)
return createDefault();
return result;
} catch (ClassCastException e) {
return createDefault();
}
}
NBT convertToNbt(T value) {
return writeFunction.apply(value);
}
public static @NotNull Tag<Byte> Byte(@NotNull String key) {
return new Tag<>(key,
nbtCompound -> nbtCompound.getByte(key),
(nbtCompound, value) -> nbtCompound.setByte(key, value));
return tag(key, NBTByte.class, NBTByte::getValue, NBT::Byte);
}
public static @NotNull Tag<Short> Short(@NotNull String key) {
return new Tag<>(key,
nbtCompound -> nbtCompound.getShort(key),
(nbtCompound, value) -> nbtCompound.setShort(key, value));
return tag(key, NBTShort.class, NBTShort::getValue, NBT::Short);
}
public static @NotNull Tag<Integer> Integer(@NotNull String key) {
return new Tag<>(key,
nbtCompound -> nbtCompound.getInt(key),
(nbtCompound, integer) -> nbtCompound.setInt(key, integer));
return tag(key, NBTInt.class, NBTInt::getValue, NBT::Int);
}
public static @NotNull Tag<Long> Long(@NotNull String key) {
return new Tag<>(key,
nbtCompound -> nbtCompound.getLong(key),
(nbtCompound, value) -> nbtCompound.setLong(key, value));
return tag(key, NBTLong.class, NBTLong::getValue, NBT::Long);
}
public static @NotNull Tag<Float> Float(@NotNull String key) {
return new Tag<>(key,
nbtCompound -> nbtCompound.getFloat(key),
(nbtCompound, value) -> nbtCompound.setFloat(key, value));
return tag(key, NBTFloat.class, NBTFloat::getValue, NBT::Float);
}
public static @NotNull Tag<Double> Double(@NotNull String key) {
return new Tag<>(key,
nbtCompound -> nbtCompound.getDouble(key),
(nbtCompound, value) -> nbtCompound.setDouble(key, value));
return tag(key, NBTDouble.class, NBTDouble::getValue, NBT::Double);
}
public static @NotNull Tag<String> String(@NotNull String key) {
return new Tag<>(key,
nbtCompound -> nbtCompound.getString(key),
(nbtCompound, value) -> nbtCompound.setString(key, value));
return tag(key, NBTString.class, NBTString::getValue, NBT::String);
}
public static <T extends NBT> @NotNull Tag<T> NBT(@NotNull String key) {
//noinspection unchecked
return new Tag<>(key,
nbt -> (T) nbt.get(key),
((nbt, value) -> nbt.set(key, value)));
return tag(key, NBT.class, nbt -> (T) nbt, t -> t);
}
/**
@ -208,61 +175,26 @@ public class Tag<T> {
* @return the created tag
*/
public static <T> @NotNull Tag<T> Structure(@NotNull String key, @NotNull TagSerializer<T> serializer) {
return new Tag<>(key,
nbtCompound -> {
final NBTCompound compound = nbtCompound.getCompound(key);
if (compound == null) return null;
return serializer.read(TagReadable.fromCompound(compound));
},
(nbtCompound, value) -> {
MutableNBTCompound mutableCopy = nbtCompound.get(key) instanceof NBTCompound c ?
c.toMutableCompound() : new MutableNBTCompound();
serializer.write(TagWritable.fromCompound(mutableCopy), value);
nbtCompound.set(key, mutableCopy.toCompound());
return tag(key, NBTCompound.class,
nbt -> serializer.read(TagHandler.fromCompound(nbt)),
(value) -> {
TagHandler handler = TagHandler.newHandler();
serializer.write(handler, value);
return handler.asCompound();
});
}
public static <T> @NotNull Tag<T> View(@NotNull TagSerializer<T> serializer) {
return new Tag<>(null,
nbtCompound -> serializer.read(TagReadable.fromCompound(nbtCompound)),
(nbtCompound, value) -> serializer.write(TagWritable.fromCompound(nbtCompound), value));
return tag("", NBTCompound.class,
nbt -> serializer.read(TagHandler.fromCompound(nbt)),
(value) -> {
TagHandler handler = TagHandler.newHandler();
serializer.write(handler, value);
return handler.asCompound();
});
}
/**
* @deprecated use {@link Tag#NBT(String)} with {@link NBT#ByteArray(byte...)}
*/
@Deprecated
public static @NotNull Tag<byte[]> ByteArray(@NotNull String key) {
return new Tag<>(key,
nbtCompound -> nbtCompound.getByteArray(key).copyArray(),
(nbtCompound, value) -> nbtCompound.setByteArray(key, value));
}
/**
* @deprecated use {@link Tag#NBT(String)} with {@link NBT#IntArray(int...)}
*/
@Deprecated
public static @NotNull Tag<int[]> IntArray(@NotNull String key) {
return new Tag<>(key,
nbtCompound -> nbtCompound.getIntArray(key).copyArray(),
(nbtCompound, value) -> nbtCompound.setIntArray(key, value));
}
/**
* @deprecated use {@link Tag#NBT(String)} with {@link NBT#LongArray(long...)}
*/
@Deprecated
public static @NotNull Tag<long[]> LongArray(@NotNull String key) {
return new Tag<>(key,
nbtCompound -> nbtCompound.getLongArray(key).copyArray(),
(nbtCompound, value) -> nbtCompound.setLongArray(key, value));
}
/**
* @deprecated use {@link #Structure(String, TagSerializer)} instead
*/
@Deprecated
public static <T> @NotNull Tag<T> Custom(@NotNull String key, @NotNull TagSerializer<T> serializer) {
return Structure(key, serializer);
public static @NotNull Tag<ItemStack> ItemStack(@NotNull String key) {
return tag(key, NBTCompound.class, ItemStack::fromItemNBT, ItemStack::toItemNBT);
}
}

View File

@ -2,35 +2,34 @@ package net.minestom.server.tag;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound;
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import org.jglrxavpok.hephaistos.nbt.NBTCompoundLike;
/**
* Represents an element which can read and write {@link Tag tags}.
*/
@ApiStatus.Experimental
public interface TagHandler extends TagReadable, TagWritable {
/**
* Converts a nbt compound to a tag handler.
* <p>
* The returned tag handler is not thread-safe.
*
* @param compound the compound to convert
* @return a {@link TagHandler} capable of writing and reading {@code compound}
*/
static @NotNull TagHandler fromCompound(@NotNull MutableNBTCompound compound) {
return new TagHandler() {
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return tag.read(compound);
}
@NotNull TagReadable readableCopy();
@Override
public <T> void setTag(@NotNull Tag<T> tag, @Nullable T value) {
tag.write(compound, value);
}
};
void updateContent(@NotNull NBTCompoundLike compound);
@NotNull NBTCompound asCompound();
@ApiStatus.Experimental
static @NotNull TagHandler newHandler() {
return new TagHandlerImpl();
}
/**
* Copy the content of the given {@link NBTCompoundLike} into a new {@link TagHandler}.
*
* @param compound the compound to read tags from
* @return a new tag handler with the content of the given compound
*/
static @NotNull TagHandler fromCompound(@NotNull NBTCompoundLike compound) {
TagHandler handler = newHandler();
handler.updateContent(compound);
return handler;
}
}

View File

@ -0,0 +1,128 @@
package net.minestom.server.tag;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
import org.jglrxavpok.hephaistos.nbt.NBT;
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import org.jglrxavpok.hephaistos.nbt.NBTCompoundLike;
import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound;
import java.lang.invoke.VarHandle;
import java.util.Arrays;
final class TagHandlerImpl implements TagHandler {
private Entry<?>[] entries = new Entry[0];
private Cache cache;
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
VarHandle.acquireFence();
return read(entries, tag);
}
@Override
public synchronized <T> void setTag(@NotNull Tag<T> tag, @Nullable T value) {
VarHandle.acquireFence();
final int index = tag.index;
Entry<?>[] entries = this.entries;
final Entry<T> entry = value != null ? new Entry<>(tag, value) : null;
if (index >= entries.length) {
if (value == null)
return; // no need to create/remove an entry
this.entries = entries = Arrays.copyOf(entries, index + 1);
}
entries[index] = entry;
this.cache = null;
VarHandle.releaseFence();
}
@Override
public @NotNull TagReadable readableCopy() {
return updatedCache();
}
@Override
public void updateContent(@NotNull NBTCompoundLike compound) {
Entry<?>[] entries = new Entry[0];
for (var entry : compound) {
final String key = entry.getKey();
final NBT nbt = entry.getValue();
final Tag<NBT> tag = Tag.NBT(key);
final int index = tag.index;
if (index >= entries.length) {
entries = Arrays.copyOf(entries, index + 1);
}
entries[index] = new Entry<>(tag, nbt);
}
this.entries = entries;
this.cache = null;
VarHandle.releaseFence();
}
@Override
public @NotNull NBTCompound asCompound() {
return updatedCache().compound;
}
private Cache updatedCache() {
VarHandle.acquireFence();
Cache cache = this.cache;
if (cache == null) {
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;
tag.writeUnsafe(tmp, entry.value);
}
cache = !tmp.isEmpty() ? new Cache(entries, tmp.toCompound()) : Cache.EMPTY;
} else {
cache = Cache.EMPTY;
}
this.cache = cache;
VarHandle.releaseFence();
}
return cache;
}
private static final class Entry<T> {
final Tag<T> tag;
final T value;
volatile NBT nbt;
Entry(Tag<T> tag, T value) {
this.tag = tag;
this.value = value;
}
}
private record Cache(Entry<?>[] entries, NBTCompound compound) implements TagReadable {
static final Cache EMPTY = new Cache(new Entry[0], NBTCompound.EMPTY);
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return read(entries, tag);
}
}
private static <T> T read(Entry<?>[] entries, Tag<T> tag) {
final int index = tag.index;
final Entry<?> entry;
if (index >= entries.length || (entry = entries[index]) == null) {
return tag.createDefault();
}
final Tag entryTag = entry.tag;
if (entryTag == tag) {
// Tag is the same, return the value
//noinspection unchecked
return (T) entry.value;
}
// Value must be parsed from nbt if the tag is different
NBT nbt = entry.nbt;
if (nbt == null) entry.nbt = nbt = entryTag.convertToNbt(entry.value);
return tag.convertToValue(nbt);
}
}

View File

@ -2,7 +2,6 @@ package net.minestom.server.tag;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.UnknownNullability;
import org.jglrxavpok.hephaistos.nbt.NBTCompoundLike;
/**
* Represents an element which can read {@link Tag tags}.
@ -27,21 +26,4 @@ public interface TagReadable {
default boolean hasTag(@NotNull Tag<?> tag) {
return getTag(tag) != null;
}
/**
* Converts a nbt compound to a tag reader.
* <p>
* The returned tag reader is not thread-safe.
*
* @param compound the compound to convert
* @return a {@link TagReadable} capable of reading {@code compound}
*/
static @NotNull TagReadable fromCompound(@NotNull NBTCompoundLike compound) {
return new TagReadable() {
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return tag.read(compound);
}
};
}
}

View File

@ -2,7 +2,6 @@ package net.minestom.server.tag;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound;
/**
* Represents an element which can write {@link Tag tags}.
@ -21,21 +20,4 @@ public interface TagWritable {
default void removeTag(@NotNull Tag<?> tag) {
setTag(tag, null);
}
/**
* Converts a nbt compound to a tag writer.
* <p>
* The returned tag writer is not thread-safe.
*
* @param compound the compound to convert
* @return a {@link TagWritable} capable of writing {@code compound}
*/
static @NotNull TagWritable fromCompound(@NotNull MutableNBTCompound compound) {
return new TagWritable() {
@Override
public <T> void setTag(@NotNull Tag<T> tag, @Nullable T value) {
tag.write(compound, value);
}
};
}
}

View File

@ -0,0 +1,30 @@
package net.minestom.server.tag;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
public interface Taggable extends TagReadable, TagWritable {
@NotNull TagHandler tagHandler();
@Override
default <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return tagHandler().getTag(tag);
}
@Override
default boolean hasTag(@NotNull Tag<?> tag) {
return tagHandler().hasTag(tag);
}
@Override
default <T> void setTag(@NotNull Tag<T> tag, @Nullable T value) {
tagHandler().setTag(tag, value);
}
@Override
default void removeTag(@NotNull Tag<?> tag) {
tagHandler().removeTag(tag);
}
}

View File

@ -2,6 +2,9 @@ package net.minestom.server.api;
import java.lang.ref.WeakReference;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class TestUtils {
public static void waitUntilCleared(WeakReference<?> ref) {
while (ref.get() != null) {
@ -12,4 +15,27 @@ public class TestUtils {
}
}
}
public static void assertEqualsIgnoreSpace(String s1, String s2, boolean matchCase) {
final String val1 = stripExtraSpaces(s1);
final String val2 = stripExtraSpaces(s2);
if (matchCase) {
assertEquals(val1, val2);
} else {
assertTrue(val1.equalsIgnoreCase(val2));
}
}
public static void assertEqualsIgnoreSpace(String s1, String s2) {
assertEqualsIgnoreSpace(s1, s2, true);
}
private static String stripExtraSpaces(String s) {
StringBuilder formattedString = new StringBuilder();
java.util.StringTokenizer st = new java.util.StringTokenizer(s);
while (st.hasMoreTokens()) {
formattedString.append(st.nextToken());
}
return formattedString.toString().trim();
}
}

View File

@ -3,10 +3,8 @@ package net.minestom.server.command;
import net.minestom.server.command.builder.Command;
import net.minestom.server.command.builder.CommandDispatcher;
import net.minestom.server.permission.Permission;
import net.minestom.server.tag.Tag;
import net.minestom.server.tag.TagHandler;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
import org.junit.jupiter.api.Test;
import java.util.Set;
@ -133,12 +131,8 @@ public class CommandConditionTest {
}
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
public @NotNull TagHandler tagHandler() {
return null;
}
@Override
public <T> void setTag(@NotNull Tag<T> tag, @Nullable T value) {
}
}
}

View File

@ -4,10 +4,8 @@ import net.minestom.server.command.builder.Command;
import net.minestom.server.command.builder.CommandDispatcher;
import net.minestom.server.command.builder.arguments.ArgumentType;
import net.minestom.server.permission.Permission;
import net.minestom.server.tag.Tag;
import net.minestom.server.tag.TagHandler;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
import org.junit.jupiter.api.Test;
import java.util.Set;
@ -73,12 +71,8 @@ public class CommandParsingTest {
}
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
public @NotNull TagHandler tagHandler() {
return null;
}
@Override
public <T> void setTag(@NotNull Tag<T> tag, @Nullable T value) {
}
}
}

View File

@ -0,0 +1,94 @@
package net.minestom.server.tag;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import org.junit.jupiter.api.Test;
import java.lang.ref.WeakReference;
import static net.minestom.server.api.TestUtils.waitUntilCleared;
import static org.junit.jupiter.api.Assertions.*;
public class TagItemTest {
@Test
public void get() {
var item = ItemStack.of(Material.DIAMOND);
var tag = Tag.ItemStack("item");
var handler = TagHandler.newHandler();
handler.setTag(tag, item);
assertSame(item, handler.getTag(tag));
}
@Test
public void getDifferentObject() {
var item = ItemStack.of(Material.DIAMOND);
var handler = TagHandler.newHandler();
handler.setTag(Tag.ItemStack("item"), item);
assertEquals(item, handler.getTag(Tag.ItemStack("item")));
}
@Test
public void remove() {
var item = ItemStack.of(Material.DIAMOND);
var tag = Tag.ItemStack("item");
var handler = TagHandler.newHandler();
handler.setTag(tag, item);
assertSame(item, handler.getTag(tag));
handler.setTag(tag, null);
assertNull(handler.getTag(tag));
}
@Test
public void gc() {
var item = ItemStack.of(Material.DIAMOND);
var tag = Tag.ItemStack("item");
var handler = TagHandler.newHandler();
handler.setTag(tag, item);
assertSame(item, handler.getTag(tag));
handler.setTag(tag, null);
var ref = new WeakReference<>(item);
//noinspection UnusedAssignment
item = null;
waitUntilCleared(ref);
}
@Test
public void invalidation() {
var item = ItemStack.of(Material.DIAMOND);
var item2 = ItemStack.of(Material.DIAMOND, 2);
var handler = TagHandler.newHandler();
var tag = Tag.ItemStack("item");
handler.setTag(tag, item);
assertSame(item, handler.getTag(tag));
handler.setTag(tag, item2);
assertSame(item2, handler.getTag(tag));
}
@Test
public void differentTagInvalidation() {
var item = ItemStack.of(Material.DIAMOND);
var item2 = ItemStack.of(Material.DIAMOND, 2);
var handler = TagHandler.newHandler();
var itemTag = Tag.ItemStack("item");
var nbtTag = Tag.NBT("item");
// Write the item using the ItemStack tag
{
handler.setTag(itemTag, item);
assertSame(item, handler.getTag(itemTag));
assertEquals(item.toItemNBT(), handler.getTag(nbtTag));
}
// Override it with an NBT tag
{
handler.setTag(nbtTag, item2.toItemNBT());
assertEquals(item2, handler.getTag(itemTag));
assertEquals(item2.toItemNBT(), handler.getTag(nbtTag));
}
}
}

View File

@ -0,0 +1,35 @@
package net.minestom.server.tag;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class TagMapTest {
private record Entry(int value) {
}
@Test
public void map() {
var handler = TagHandler.newHandler();
var intTag = Tag.Integer("key");
var tag = intTag.map(Entry::new, Entry::value);
handler.setTag(tag, new Entry(1));
assertEquals(1, handler.getTag(intTag));
assertEquals(new Entry(1), handler.getTag(tag));
}
@Test
public void mapDefault() {
var handler = TagHandler.newHandler();
var intTag = Tag.Integer("key");
var tag = intTag.map(Entry::new, Entry::value);
assertEquals(new Entry(1), handler.getTag(tag.defaultValue(new Entry(1))));
handler.setTag(tag, new Entry(2));
assertEquals(2, handler.getTag(intTag));
assertEquals(new Entry(2), handler.getTag(tag));
}
}

View File

@ -0,0 +1,140 @@
package net.minestom.server.tag;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.junit.jupiter.api.Test;
import static net.minestom.server.api.TestUtils.assertEqualsIgnoreSpace;
import static org.junit.jupiter.api.Assertions.*;
public class TagStructureTest {
private static final Tag<Entry> STRUCTURE_TAG = Tag.Structure("entry", new TagSerializer<>() {
private static final Tag<String> VALUE_TAG = Tag.String("value");
@Override
public @Nullable Entry read(@NotNull TagReadable reader) {
final String value = reader.getTag(VALUE_TAG);
return value != null ? new Entry(value) : null;
}
@Override
public void write(@NotNull TagWritable writer, @Nullable Entry value) {
if (value != null) {
writer.setTag(VALUE_TAG, value.value);
} else {
writer.removeTag(VALUE_TAG);
}
}
});
private static final Tag<Entry> STRUCTURE_TAG2 = Tag.Structure("entry", new TagSerializer<>() {
private static final Tag<String> VALUE_TAG = Tag.String("value2");
@Override
public @Nullable Entry read(@NotNull TagReadable reader) {
final String value = reader.getTag(VALUE_TAG);
return value != null ? new Entry(value) : null;
}
@Override
public void write(@NotNull TagWritable writer, @Nullable Entry value) {
if (value != null) {
writer.setTag(VALUE_TAG, value.value);
} else {
writer.removeTag(VALUE_TAG);
}
}
});
private record Entry(String value) {
}
@Test
public void basic() {
var handler = TagHandler.newHandler();
assertNull(handler.getTag(STRUCTURE_TAG));
assertFalse(handler.hasTag(STRUCTURE_TAG));
var entry = new Entry("hello");
handler.setTag(STRUCTURE_TAG, entry);
assertTrue(handler.hasTag(STRUCTURE_TAG));
assertEquals(entry, handler.getTag(STRUCTURE_TAG));
handler.removeTag(STRUCTURE_TAG);
assertFalse(handler.hasTag(STRUCTURE_TAG));
assertNull(handler.getTag(STRUCTURE_TAG));
}
@Test
public void snbt() {
var handler = TagHandler.newHandler();
var entry = new Entry("hello");
handler.setTag(STRUCTURE_TAG, entry);
assertEqualsIgnoreSpace("""
{
"entry": {
"value": "hello"
}
}
""", handler.asCompound().toSNBT());
handler.removeTag(STRUCTURE_TAG);
assertEqualsIgnoreSpace("{}", handler.asCompound().toSNBT());
}
@Test
public void overrideBasic() {
var handler = TagHandler.newHandler();
assertNull(handler.getTag(STRUCTURE_TAG));
assertFalse(handler.hasTag(STRUCTURE_TAG));
var entry1 = new Entry("hello");
var entry2 = new Entry("hello2");
// Add first entry
{
handler.setTag(STRUCTURE_TAG, entry1);
assertTrue(handler.hasTag(STRUCTURE_TAG));
assertEquals(entry1, handler.getTag(STRUCTURE_TAG));
}
// Add second entry
{
handler.setTag(STRUCTURE_TAG2, entry2);
assertTrue(handler.hasTag(STRUCTURE_TAG2));
assertEquals(entry2, handler.getTag(STRUCTURE_TAG2));
// Assert first
assertFalse(handler.hasTag(STRUCTURE_TAG));
assertNull(handler.getTag(STRUCTURE_TAG));
}
}
@Test
public void overrideNbt() {
var handler = TagHandler.newHandler();
var entry1 = new Entry("hello");
var entry2 = new Entry("hello2");
// Add first entry
{
handler.setTag(STRUCTURE_TAG, entry1);
assertEqualsIgnoreSpace("""
{
"entry": {
"value": "hello"
}
}
""", handler.asCompound().toSNBT());
}
// Add second entry
{
handler.setTag(STRUCTURE_TAG2, entry2);
assertEqualsIgnoreSpace("""
{
"entry": {
"value2": "hello2"
}
}
""", handler.asCompound().toSNBT());
}
}
}

View File

@ -1,44 +1,74 @@
package net.minestom.server.tag;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import org.jglrxavpok.hephaistos.nbt.NBT;
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound;
import org.junit.jupiter.api.Test;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
public class TagTest {
@Test
public void testTag() {
var mutable = new MutableNBTCompound();
mutable.setInt("key", 5);
public void intGet() {
var mutable = new MutableNBTCompound().setInt("key", 5);
var tag = Tag.Integer("key");
var handler = TagHandler.fromCompound(new MutableNBTCompound());
handler.setTag(tag, 5);
assertEquals(mutable.toCompound(), handler.getTag(Tag.NBT), "NBT is not the same");
assertEquals(5, handler.getTag(tag));
assertEquals(mutable.toCompound(), handler.asCompound(), "NBT is not the same");
// Removal
handler.setTag(tag, null);
assertEquals(new NBTCompound(), handler.getTag(Tag.NBT), "Tag must be removed when set to null");
assertEquals(new NBTCompound(), handler.asCompound(), "Tag must be removed when set to null");
}
@Test
public void testSnbt() {
var mutable = new MutableNBTCompound();
mutable.setInt("key", 5);
var reader = TagReadable.fromCompound(mutable);
final String snbt = reader.getTag(Tag.SNBT);
assertEquals(snbt, mutable.toCompound().toSNBT(), "SNBT is not the same");
public void intNull() {
var handler = TagHandler.fromCompound(new MutableNBTCompound().set("key", NBT.Int(5)));
// Removal
var tag = Tag.Integer("key");
handler.setTag(tag, null);
assertFalse(handler.hasTag(tag));
assertEquals(NBTCompound.EMPTY, handler.asCompound(), "Tag must be removed when set to null");
}
@Test
public void testDefault() {
public void intRemove() {
var handler = TagHandler.fromCompound(new MutableNBTCompound().set("key", NBT.Int(5)));
// Removal
var tag = Tag.Integer("key");
handler.removeTag(tag);
assertFalse(handler.hasTag(tag));
assertEquals(NBTCompound.EMPTY, handler.asCompound(), "Tag must be removed when set to null");
}
@Test
public void snbt() {
var mutable = new MutableNBTCompound().setInt("key", 5);
var reader = TagHandler.fromCompound(mutable);
assertEquals(reader.asCompound().toSNBT(), mutable.toCompound().toSNBT(), "SNBT is not the same");
}
@Test
public void fromNbt() {
var mutable = new MutableNBTCompound().setInt("key", 5);
var handler = TagHandler.fromCompound(mutable);
assertEquals(5, handler.getTag(Tag.Integer("key")));
assertEquals(mutable.toCompound(), handler.asCompound(), "NBT is not the same");
}
@Test
public void defaultValue() {
var nullable = Tag.String("key");
var notNull = nullable.defaultValue("Hey");
assertNotSame(nullable, notNull);
var handler = TagHandler.fromCompound(new MutableNBTCompound());
var handler = TagHandler.newHandler();
assertFalse(handler.hasTag(nullable));
assertTrue(handler.hasTag(notNull)); // default value is set
assertFalse(handler.hasTag(nullable));
@ -46,4 +76,49 @@ public class TagTest {
assertNull(handler.getTag(nullable));
assertEquals("Hey", handler.getTag(notNull));
}
@Test
public void invalidType() {
var tag1 = Tag.Integer("key");
var tag2 = Tag.String("key");
var handler = TagHandler.newHandler();
handler.setTag(tag1, 5);
assertEquals(5, handler.getTag(tag1));
assertNull(handler.getTag(tag2));
assertEquals("hey", handler.getTag(tag2.defaultValue("hey")));
}
@Test
public void item() {
var item = ItemStack.of(Material.DIAMOND);
var tag = Tag.ItemStack("item");
var handler = TagHandler.newHandler();
handler.setTag(tag, item);
assertEquals(item, handler.getTag(tag));
}
@Test
public void tagResizing() {
var tag1 = Tag.Integer("tag1");
var tag2 = Tag.Integer("tag2");
var handler = TagHandler.newHandler();
handler.setTag(tag1, 5);
handler.setTag(tag2, 1);
assertEquals(5, handler.getTag(tag1));
assertEquals(1, handler.getTag(tag2));
}
@Test
public void nbtResizing() {
var handler = TagHandler.fromCompound(NBT.Compound(Map.of(
"tag1", NBT.Int(5),
"tag2", NBT.Int(1))));
assertEquals(5, handler.getTag(Tag.Integer("tag1")));
assertEquals(1, handler.getTag(Tag.Integer("tag2")));
}
}

View File

@ -0,0 +1,66 @@
package net.minestom.server.tag;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.junit.jupiter.api.Test;
import static net.minestom.server.api.TestUtils.assertEqualsIgnoreSpace;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertNull;
public class TagViewTest {
private static final Tag<Entry> VIEW_TAG = Tag.View(new TagSerializer<>() {
private static final Tag<String> VALUE_TAG = Tag.String("value");
@Override
public @Nullable Entry read(@NotNull TagReadable reader) {
final String value = reader.getTag(VALUE_TAG);
return value != null ? new Entry(value) : null;
}
@Override
public void write(@NotNull TagWritable writer, @Nullable Entry value) {
if (value != null) {
writer.setTag(VALUE_TAG, value.value);
} else {
writer.removeTag(VALUE_TAG);
}
}
});
private record Entry(String value) {
}
@Test
public void basic() {
var handler = TagHandler.newHandler();
assertNull(handler.getTag(VIEW_TAG));
assertFalse(handler.hasTag(VIEW_TAG));
var entry = new Entry("hello");
handler.setTag(VIEW_TAG, entry);
assertTrue(handler.hasTag(VIEW_TAG));
assertEquals(entry, handler.getTag(VIEW_TAG));
handler.removeTag(VIEW_TAG);
assertFalse(handler.hasTag(VIEW_TAG));
assertNull(handler.getTag(VIEW_TAG));
}
@Test
public void snbt() {
var handler = TagHandler.newHandler();
var entry = new Entry("hello");
handler.setTag(VIEW_TAG, entry);
assertEqualsIgnoreSpace("""
{
"value": "hello"
}
""", handler.asCompound().toSNBT());
handler.removeTag(VIEW_TAG);
assertEqualsIgnoreSpace("{}", handler.asCompound().toSNBT());
}
}