diff --git a/src/main/java/net/minestom/server/tag/TagHandlerImpl.java b/src/main/java/net/minestom/server/tag/TagHandlerImpl.java index 6bc8a1ed5..aa157cdb2 100644 --- a/src/main/java/net/minestom/server/tag/TagHandlerImpl.java +++ b/src/main/java/net/minestom/server/tag/TagHandlerImpl.java @@ -21,12 +21,6 @@ final class TagHandlerImpl implements TagHandler { private volatile SPMCMap entries; private Cache cache; - TagHandlerImpl(TagHandlerImpl parent, SPMCMap entries, Cache cache) { - this.parent = parent; - this.entries = entries; - this.cache = cache; - } - TagHandlerImpl(TagHandlerImpl parent) { this.parent = parent; this.entries = new SPMCMap(this); @@ -60,7 +54,7 @@ final class TagHandlerImpl implements TagHandler { tag.write(viewCompound, value); updateContent(viewCompound); } else { - final Entry entry = valueToEntry(tag, value); + Entry entry = valueToEntry(tag, value); final int tagIndex = tag.index; final Tag.PathEntry[] paths = tag.path; final boolean present = entry != null; @@ -71,6 +65,11 @@ final class TagHandlerImpl implements TagHandler { return; } SPMCMap entries = local.entries; + if (entry instanceof PathEntry pathEntry) { + var childHandler = new TagHandlerImpl(local); + childHandler.updateContent(pathEntry.updatedNbt()); + entry = new PathEntry(tag.getKey(), childHandler); + } if (present) entries.put(tagIndex, entry); else entries.remove(tagIndex); entries.invalidate(); @@ -102,9 +101,8 @@ final class TagHandlerImpl implements TagHandler { } @Override - public synchronized @NotNull TagHandler copy() { - assert parent == null; - return new TagHandlerImpl(null, entries.clone(), cache); + public @NotNull TagHandler copy() { + return fromCompound(asCompound()); } @Override @@ -132,6 +130,12 @@ final class TagHandlerImpl implements TagHandler { final Entry entry = local.entries.get(pathIndex); if (entry instanceof PathEntry pathEntry) { // Existing path, continue navigating + { + // FIXME + //assert pathEntry.value.parent == local : "Path parent is invalid: " + pathEntry.value.parent + " != " + local; + pathEntry.value.cache = null; + pathEntry.value.parent.cache = null; + } local = pathEntry.value; } else { if (!present) return null; @@ -170,21 +174,18 @@ final class TagHandlerImpl implements TagHandler { return local; } - private Cache updatedCache() { - VarHandle.fullFence(); + private synchronized Cache updatedCache() { Cache cache; if (!CACHE_ENABLE || (cache = this.cache) == null) { - synchronized (this) { - final SPMCMap entries = this.entries; - if (!entries.isEmpty()) { - MutableNBTCompound tmp = new MutableNBTCompound(); - for (Entry entry : entries.values()) { - if (entry != null) tmp.put(entry.tag().getKey(), entry.updatedNbt()); - } - cache = new Cache(entries.clone(), tmp.toCompound()); - } else cache = Cache.EMPTY; - this.cache = cache; - } + final SPMCMap entries = this.entries; + if (!entries.isEmpty()) { + MutableNBTCompound tmp = new MutableNBTCompound(); + for (Entry entry : entries.values()) { + if (entry != null) tmp.put(entry.tag().getKey(), entry.updatedNbt()); + } + cache = new Cache(entries.clone(), tmp.toCompound()); + } else cache = Cache.EMPTY; + this.cache = cache; } return cache; } diff --git a/src/test/java/net/minestom/server/tag/TagHandlerCopyTest.java b/src/test/java/net/minestom/server/tag/TagHandlerCopyTest.java index 28923ced1..898281871 100644 --- a/src/test/java/net/minestom/server/tag/TagHandlerCopyTest.java +++ b/src/test/java/net/minestom/server/tag/TagHandlerCopyTest.java @@ -2,6 +2,7 @@ package net.minestom.server.tag; import org.junit.jupiter.api.Test; +import static net.minestom.server.api.TestUtils.assertEqualsSNBT; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; @@ -9,11 +10,64 @@ public class TagHandlerCopyTest { @Test public void copy() { - var handler1 = TagHandler.newHandler(); - handler1.setTag(Tag.String("key"), "test"); + var handler = TagHandler.newHandler(); + handler.setTag(Tag.String("key"), "test"); - var handler2 = handler1.copy(); - assertEquals(handler1.getTag(Tag.String("key")), handler2.getTag(Tag.String("key"))); + var copy = handler.copy(); + assertEquals(handler.getTag(Tag.String("key")), copy.getTag(Tag.String("key"))); + } + + @Test + public void copyCachePath() { + var tag = Tag.String("key").path("path"); + var handler = TagHandler.newHandler(); + handler.setTag(tag, "test"); + assertEqualsSNBT(""" + {"path":{"key":"test"}} + """, handler.asCompound()); + + var copy = handler.copy(); + handler.setTag(tag, "test2"); + assertEqualsSNBT(""" + {"path":{"key":"test2"}} + """, handler.asCompound()); + assertEqualsSNBT(""" + {"path":{"key":"test"}} + """, copy.asCompound()); + + copy.setTag(tag, "test3"); + assertEquals("test3", copy.getTag(tag)); + assertEqualsSNBT(""" + {"path":{"key":"test3"}} + """, copy.asCompound()); + } + + @Test + public void copyCache() { + var tag = Tag.String("key"); + var handler = TagHandler.newHandler(); + handler.setTag(tag, "test"); + assertEqualsSNBT(""" + {"key":"test"} + """, handler.asCompound()); + + var copy = handler.copy(); + handler.setTag(tag, "test2"); + assertEqualsSNBT(""" + {"key":"test2"} + """, handler.asCompound()); + assertEqualsSNBT(""" + {"key":"test"} + """, copy.asCompound()); + + copy.setTag(tag, "test3"); + assertEquals("test3", copy.getTag(tag)); + assertEqualsSNBT(""" + {"key":"test2"} + """, handler.asCompound()); + assertEqualsSNBT(""" + {"key":"test3"} + """, copy.asCompound()); } @Test diff --git a/src/test/java/net/minestom/server/tag/TagNbtTest.java b/src/test/java/net/minestom/server/tag/TagNbtTest.java index 1f25e4aaa..406f0dd8b 100644 --- a/src/test/java/net/minestom/server/tag/TagNbtTest.java +++ b/src/test/java/net/minestom/server/tag/TagNbtTest.java @@ -50,6 +50,75 @@ public class TagNbtTest { assertEqualsSNBT("{}", handler.asCompound()); } + @Test + public void fromCompoundModify() { + var compound = NBT.Compound(Map.of("key", NBT.Int(5))); + var handler = TagHandler.fromCompound(compound); + assertEquals(compound, handler.asCompound()); + assertEqualsSNBT(""" + {"key":5} + """, handler.asCompound()); + + handler.setTag(Tag.Integer("key"), 10); + assertEquals(10, handler.getTag(Tag.Integer("key"))); + assertEqualsSNBT(""" + {"key":10} + """, handler.asCompound()); + handler.setTag(Tag.Integer("key"), 15); + assertEqualsSNBT(""" + {"key":15} + """, handler.asCompound()); + } + + @Test + public void fromCompoundModifyPath() { + var compound = NBT.Compound(Map.of("path", NBT.Compound(Map.of("key", NBT.Int(5))))); + var handler = TagHandler.fromCompound(compound); + var tag = Tag.Integer("key").path("path"); + + handler.setTag(tag, 10); + assertEquals(10, handler.getTag(tag)); + assertEqualsSNBT(""" + {"path":{"key":10}} + """, handler.asCompound()); + handler.setTag(tag, 15); + assertEqualsSNBT(""" + {"path":{"key":15}} + """, handler.asCompound()); + } + + @Test + public void fromCompoundModifyDoublePath() { + var compound = NBT.Compound(Map.of("path", NBT.Compound(Map.of("path2", + NBT.Compound(Map.of("key", NBT.Int(5))))))); + var handler = TagHandler.fromCompound(compound); + var tag = Tag.Integer("key").path("path", "path2"); + + handler.setTag(tag, 10); + assertEquals(10, handler.getTag(tag)); + assertEqualsSNBT(""" + {"path":{"path2":{"key":10}}} + """, handler.asCompound()); + handler.setTag(tag, 15); + assertEqualsSNBT(""" + {"path":{"path2":{"key":15}}} + """, handler.asCompound()); + } + + @Test + public void compoundOverride() { + var handler = TagHandler.newHandler(); + var nbtTag = Tag.NBT("path1"); + + var nbt1 = NBT.Compound(Map.of("key", NBT.Int(5))); + var nbt2 = NBT.Compound(Map.of("other-key", NBT.Int(5))); + handler.setTag(nbtTag, nbt1); + assertEquals(nbt1, handler.getTag(nbtTag)); + + handler.setTag(nbtTag, nbt2); + assertEquals(nbt2, handler.getTag(nbtTag)); + } + @Test public void compoundRead() { var handler = TagHandler.newHandler();