diff --git a/jmh-benchmarks/src/jmh/java/net/minestom/jmh/tag/TagReadBenchmark.java b/jmh-benchmarks/src/jmh/java/net/minestom/jmh/tag/TagReadBenchmark.java new file mode 100644 index 000000000..747f34b41 --- /dev/null +++ b/jmh-benchmarks/src/jmh/java/net/minestom/jmh/tag/TagReadBenchmark.java @@ -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 TAG = Tag.String("key"); + + @Param({"false", "true"}) + public boolean present; + + TagHandler tagHandler; + Tag 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"))); + } +} diff --git a/src/main/java/net/minestom/server/command/CommandSender.java b/src/main/java/net/minestom/server/command/CommandSender.java index a1136daea..63f8f5b95 100644 --- a/src/main/java/net/minestom/server/command/CommandSender.java +++ b/src/main/java/net/minestom/server/command/CommandSender.java @@ -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; *

* 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. diff --git a/src/main/java/net/minestom/server/command/ConsoleSender.java b/src/main/java/net/minestom/server/command/ConsoleSender.java index 225a3db46..4c115003b 100644 --- a/src/main/java/net/minestom/server/command/ConsoleSender.java +++ b/src/main/java/net/minestom/server/command/ConsoleSender.java @@ -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 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 @UnknownNullability T getTag(@NotNull Tag tag) { - return tag.read(nbtCompound); - } - - @Override - public void setTag(@NotNull Tag tag, @Nullable T value) { - tag.write(nbtCompound, value); + public @NotNull TagHandler tagHandler() { + return tagHandler; } } diff --git a/src/main/java/net/minestom/server/command/ServerSender.java b/src/main/java/net/minestom/server/command/ServerSender.java index 8db93d949..e69d01519 100644 --- a/src/main/java/net/minestom/server/command/ServerSender.java +++ b/src/main/java/net/minestom/server/command/ServerSender.java @@ -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 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 @UnknownNullability T getTag(@NotNull Tag tag) { - return tag.read(nbtCompound); - } - - @Override - public void setTag(@NotNull Tag tag, @Nullable T value) { - tag.write(nbtCompound, value); + public @NotNull TagHandler tagHandler() { + return tagHandler; } } diff --git a/src/main/java/net/minestom/server/entity/Entity.java b/src/main/java/net/minestom/server/entity/Entity.java index b51605fe4..f126746ba 100644 --- a/src/main/java/net/minestom/server/entity/Entity.java +++ b/src/main/java/net/minestom/server/entity/Entity.java @@ -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; *

* 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, TagHandler, PermissionHandler, HoverEventSource, Sound.Emitter { +public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, EventHandler, Taggable, PermissionHandler, HoverEventSource, Sound.Emitter { private static final Int2ObjectSyncMap ENTITY_BY_ID = Int2ObjectSyncMap.hashmap(); private static final Map 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 viewers = viewEngine.set; - private final MutableNBTCompound nbtCompound = new MutableNBTCompound(); + private final TagHandler tagHandler = TagHandler.newHandler(); private final Scheduler scheduler = Scheduler.newScheduler(); private final EventNode eventNode; private final Set permissions = new CopyOnWriteArraySet<>(); @@ -1538,13 +1535,8 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev } @Override - public @UnknownNullability T getTag(@NotNull Tag tag) { - return tag.read(nbtCompound); - } - - @Override - public void setTag(@NotNull Tag 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 diff --git a/src/main/java/net/minestom/server/entity/damage/DamageType.java b/src/main/java/net/minestom/server/entity/damage/DamageType.java index f84d2fb51..785ff9bbd 100644 --- a/src/main/java/net/minestom/server/entity/damage/DamageType.java +++ b/src/main/java/net/minestom/server/entity/damage/DamageType.java @@ -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; *

* 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 @UnknownNullability T getTag(@NotNull Tag tag) { - synchronized (nbtLock) { - return tag.read(nbt); - } - } - - @Override - public void setTag(@NotNull Tag tag, @Nullable T value) { - synchronized (nbtLock) { - tag.write(nbt, value); - } + public @NotNull TagHandler tagHandler() { + return tagHandler; } } diff --git a/src/main/java/net/minestom/server/instance/AnvilLoader.java b/src/main/java/net/minestom/server/instance/AnvilLoader.java index 3b7f9ce07..87be945f3 100644 --- a/src/main/java/net/minestom/server/instance/AnvilLoader.java +++ b/src/main/java/net/minestom/server/instance/AnvilLoader.java @@ -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 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; } diff --git a/src/main/java/net/minestom/server/instance/Chunk.java b/src/main/java/net/minestom/server/instance/Chunk.java index be37c4904..71086a059 100644 --- a/src/main/java/net/minestom/server/instance/Chunk.java +++ b/src/main/java/net/minestom/server/instance/Chunk.java @@ -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 @UnknownNullability T getTag(@NotNull Tag tag) { - return tag.read(nbt); - } - - @Override - public void setTag(@NotNull Tag tag, @Nullable T value) { - tag.write(nbt, value); + public @NotNull TagHandler tagHandler() { + return tagHandler; } /** diff --git a/src/main/java/net/minestom/server/instance/DynamicChunk.java b/src/main/java/net/minestom/server/instance/DynamicChunk.java index 2fc2dd735..8b17d1119 100644 --- a/src/main/java/net/minestom/server/instance/DynamicChunk.java +++ b/src/main/java/net/minestom/server/instance/DynamicChunk.java @@ -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() { diff --git a/src/main/java/net/minestom/server/instance/Instance.java b/src/main/java/net/minestom/server/instance/Instance.java index e8902d28b..540b2b58d 100644 --- a/src/main/java/net/minestom/server/instance/Instance.java +++ b/src/main/java/net/minestom/server/instance/Instance.java @@ -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 @UnknownNullability T getTag(@NotNull Tag tag) { - synchronized (nbtLock) { - return tag.read(nbt); - } - } - - @Override - public void setTag(@NotNull Tag 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()); } /** diff --git a/src/main/java/net/minestom/server/instance/block/Block.java b/src/main/java/net/minestom/server/instance/block/Block.java index 05a156c51..967a64357 100644 --- a/src/main/java/net/minestom/server/instance/block/Block.java +++ b/src/main/java/net/minestom/server/instance/block/Block.java @@ -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() { diff --git a/src/main/java/net/minestom/server/instance/block/BlockImpl.java b/src/main/java/net/minestom/server/instance/block/BlockImpl.java index 23302f098..55124a0f7 100644 --- a/src/main/java/net/minestom/server/instance/block/BlockImpl.java +++ b/src/main/java/net/minestom/server/instance/block/BlockImpl.java @@ -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); diff --git a/src/main/java/net/minestom/server/inventory/AbstractInventory.java b/src/main/java/net/minestom/server/inventory/AbstractInventory.java index d6840e20a..0719cc7f6 100644 --- a/src/main/java/net/minestom/server/inventory/AbstractInventory.java +++ b/src/main/java/net/minestom/server/inventory/AbstractInventory.java @@ -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 @UnknownNullability T getTag(@NotNull Tag tag) { - synchronized (nbtLock) { - return tag.read(nbt); - } - } - - @Override - public void setTag(@NotNull Tag tag, @Nullable T value) { - synchronized (nbtLock) { - tag.write(nbt, value); - } + public @NotNull TagHandler tagHandler() { + return tagHandler; } } diff --git a/src/main/java/net/minestom/server/tag/Tag.java b/src/main/java/net/minestom/server/tag/Tag.java index b16716771..73d70ae85 100644 --- a/src/main/java/net/minestom/server/tag/Tag.java +++ b/src/main/java/net/minestom/server/tag/Tag.java @@ -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 { - - /** - * Handles the snbt of the tag holder. - *

- * Writing will override all tags. Proceed with caution. - */ - @ApiStatus.Experimental - public static final Tag 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. - *

- * Writing will override all tags. Proceed with caution. - */ - @ApiStatus.Experimental - public static final Tag NBT = new Tag<>(null, NBTCompoundLike::toCompound, - (original, updated) -> { - if (updated == null) { - original.clear(); - return; - } - original.copyFrom(updated); - }); + private static final Map INDEX_MAP = new ConcurrentHashMap<>(); + private static final AtomicInteger INDEX = new AtomicInteger(); private final String key; - private final Function readFunction; - private final BiConsumer writeConsumer; + private final Function readFunction; + private final Function writeFunction; private final Supplier defaultValue; - protected Tag(@Nullable String key, - @NotNull Function readFunction, - @NotNull BiConsumer writeConsumer, + final int index; + + protected Tag(@NotNull String key, + @NotNull Function readFunction, + @NotNull Function writeFunction, @Nullable Supplier 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 readFunction, - @NotNull BiConsumer writeConsumer) { - this(key, readFunction, writeConsumer, null); + static Tag tag(@NotNull String key, + @NotNull Class nbtClass, + @NotNull Function readFunction, + @NotNull Function writeFunction) { + return new Tag(key, (Function) readFunction, (Function) writeFunction, null); } /** * Returns the key used to navigate inside the holder nbt. - *

- * 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 defaultValue(@NotNull Supplier 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 { @NotNull Function 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 { 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(@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(@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(@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(@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(@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(@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(@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 @NotNull Tag 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 { * @return the created tag */ public static @NotNull Tag Structure(@NotNull String key, @NotNull TagSerializer 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 @NotNull Tag View(@NotNull TagSerializer 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 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 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 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 @NotNull Tag Custom(@NotNull String key, @NotNull TagSerializer serializer) { - return Structure(key, serializer); + public static @NotNull Tag ItemStack(@NotNull String key) { + return tag(key, NBTCompound.class, ItemStack::fromItemNBT, ItemStack::toItemNBT); } } diff --git a/src/main/java/net/minestom/server/tag/TagHandler.java b/src/main/java/net/minestom/server/tag/TagHandler.java index 807b4aa9d..d7c420d93 100644 --- a/src/main/java/net/minestom/server/tag/TagHandler.java +++ b/src/main/java/net/minestom/server/tag/TagHandler.java @@ -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. - *

- * 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 @UnknownNullability T getTag(@NotNull Tag tag) { - return tag.read(compound); - } + @NotNull TagReadable readableCopy(); - @Override - public void setTag(@NotNull Tag 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; } } diff --git a/src/main/java/net/minestom/server/tag/TagHandlerImpl.java b/src/main/java/net/minestom/server/tag/TagHandlerImpl.java new file mode 100644 index 000000000..cb4cc7454 --- /dev/null +++ b/src/main/java/net/minestom/server/tag/TagHandlerImpl.java @@ -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 @UnknownNullability T getTag(@NotNull Tag tag) { + VarHandle.acquireFence(); + return read(entries, tag); + } + + @Override + public synchronized void setTag(@NotNull Tag tag, @Nullable T value) { + VarHandle.acquireFence(); + final int index = tag.index; + Entry[] entries = this.entries; + final Entry 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 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 { + final Tag tag; + final T value; + volatile NBT nbt; + + Entry(Tag 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 @UnknownNullability T getTag(@NotNull Tag tag) { + return read(entries, tag); + } + } + + private static T read(Entry[] entries, Tag 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); + } +} diff --git a/src/main/java/net/minestom/server/tag/TagReadable.java b/src/main/java/net/minestom/server/tag/TagReadable.java index afac8f0ac..95305999e 100644 --- a/src/main/java/net/minestom/server/tag/TagReadable.java +++ b/src/main/java/net/minestom/server/tag/TagReadable.java @@ -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. - *

- * 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 @UnknownNullability T getTag(@NotNull Tag tag) { - return tag.read(compound); - } - }; - } } diff --git a/src/main/java/net/minestom/server/tag/TagWritable.java b/src/main/java/net/minestom/server/tag/TagWritable.java index c343c3cba..cd1135d59 100644 --- a/src/main/java/net/minestom/server/tag/TagWritable.java +++ b/src/main/java/net/minestom/server/tag/TagWritable.java @@ -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. - *

- * 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 void setTag(@NotNull Tag tag, @Nullable T value) { - tag.write(compound, value); - } - }; - } } diff --git a/src/main/java/net/minestom/server/tag/Taggable.java b/src/main/java/net/minestom/server/tag/Taggable.java new file mode 100644 index 000000000..39d49ca3b --- /dev/null +++ b/src/main/java/net/minestom/server/tag/Taggable.java @@ -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 @UnknownNullability T getTag(@NotNull Tag tag) { + return tagHandler().getTag(tag); + } + + @Override + default boolean hasTag(@NotNull Tag tag) { + return tagHandler().hasTag(tag); + } + + @Override + default void setTag(@NotNull Tag tag, @Nullable T value) { + tagHandler().setTag(tag, value); + } + + @Override + default void removeTag(@NotNull Tag tag) { + tagHandler().removeTag(tag); + } +} diff --git a/src/test/java/net/minestom/server/api/TestUtils.java b/src/test/java/net/minestom/server/api/TestUtils.java index a834d7afd..06b095bbe 100644 --- a/src/test/java/net/minestom/server/api/TestUtils.java +++ b/src/test/java/net/minestom/server/api/TestUtils.java @@ -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(); + } } diff --git a/src/test/java/net/minestom/server/command/CommandConditionTest.java b/src/test/java/net/minestom/server/command/CommandConditionTest.java index 79dc1f92a..0a913a8a2 100644 --- a/src/test/java/net/minestom/server/command/CommandConditionTest.java +++ b/src/test/java/net/minestom/server/command/CommandConditionTest.java @@ -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 @UnknownNullability T getTag(@NotNull Tag tag) { + public @NotNull TagHandler tagHandler() { return null; } - - @Override - public void setTag(@NotNull Tag tag, @Nullable T value) { - } } } diff --git a/src/test/java/net/minestom/server/command/CommandParsingTest.java b/src/test/java/net/minestom/server/command/CommandParsingTest.java index 92f53581b..dbcf8a225 100644 --- a/src/test/java/net/minestom/server/command/CommandParsingTest.java +++ b/src/test/java/net/minestom/server/command/CommandParsingTest.java @@ -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 @UnknownNullability T getTag(@NotNull Tag tag) { + public @NotNull TagHandler tagHandler() { return null; } - - @Override - public void setTag(@NotNull Tag tag, @Nullable T value) { - } } } diff --git a/src/test/java/net/minestom/server/tag/TagItemTest.java b/src/test/java/net/minestom/server/tag/TagItemTest.java new file mode 100644 index 000000000..34434d54d --- /dev/null +++ b/src/test/java/net/minestom/server/tag/TagItemTest.java @@ -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)); + } + } +} diff --git a/src/test/java/net/minestom/server/tag/TagMapTest.java b/src/test/java/net/minestom/server/tag/TagMapTest.java new file mode 100644 index 000000000..b06a10974 --- /dev/null +++ b/src/test/java/net/minestom/server/tag/TagMapTest.java @@ -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)); + } +} diff --git a/src/test/java/net/minestom/server/tag/TagStructureTest.java b/src/test/java/net/minestom/server/tag/TagStructureTest.java new file mode 100644 index 000000000..984350d5c --- /dev/null +++ b/src/test/java/net/minestom/server/tag/TagStructureTest.java @@ -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 STRUCTURE_TAG = Tag.Structure("entry", new TagSerializer<>() { + private static final Tag 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 STRUCTURE_TAG2 = Tag.Structure("entry", new TagSerializer<>() { + private static final Tag 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()); + } + } +} diff --git a/src/test/java/net/minestom/server/tag/TagTest.java b/src/test/java/net/minestom/server/tag/TagTest.java index 2119a5f93..79557d4bb 100644 --- a/src/test/java/net/minestom/server/tag/TagTest.java +++ b/src/test/java/net/minestom/server/tag/TagTest.java @@ -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"))); + } } diff --git a/src/test/java/net/minestom/server/tag/TagViewTest.java b/src/test/java/net/minestom/server/tag/TagViewTest.java new file mode 100644 index 000000000..247477bfb --- /dev/null +++ b/src/test/java/net/minestom/server/tag/TagViewTest.java @@ -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 VIEW_TAG = Tag.View(new TagSerializer<>() { + private static final Tag 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()); + } + +}