2022-03-20 01:47:57 +01:00
|
|
|
package net.minestom.server.tag;
|
|
|
|
|
2022-03-26 13:03:28 +01:00
|
|
|
import net.minestom.server.utils.ArrayUtils;
|
2022-03-20 01:47:57 +01:00
|
|
|
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.util.Arrays;
|
2022-03-24 09:03:30 +01:00
|
|
|
import java.util.function.UnaryOperator;
|
2022-03-20 01:47:57 +01:00
|
|
|
|
|
|
|
final class TagHandlerImpl implements TagHandler {
|
2022-04-10 15:49:33 +02:00
|
|
|
private volatile Entry<?>[] entries;
|
2022-03-20 01:47:57 +01:00
|
|
|
private Cache cache;
|
|
|
|
|
2022-04-10 15:49:33 +02:00
|
|
|
TagHandlerImpl(Entry<?>[] entries, Cache cache) {
|
|
|
|
this.entries = entries;
|
|
|
|
this.cache = cache;
|
|
|
|
}
|
|
|
|
|
|
|
|
TagHandlerImpl() {
|
|
|
|
this(new Entry[0], null);
|
|
|
|
}
|
|
|
|
|
2022-04-10 10:01:39 +02:00
|
|
|
static TagHandlerImpl fromCompound(NBTCompoundLike compound) {
|
|
|
|
TagHandlerImpl handler = new TagHandlerImpl();
|
|
|
|
for (var entry : compound) {
|
|
|
|
final Tag<NBT> tag = Tag.NBT(entry.getKey());
|
|
|
|
final NBT nbt = entry.getValue();
|
|
|
|
handler.setTag(tag, nbt);
|
|
|
|
}
|
|
|
|
return handler;
|
|
|
|
}
|
|
|
|
|
2022-03-20 01:47:57 +01:00
|
|
|
@Override
|
|
|
|
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
|
2022-04-09 15:20:11 +02:00
|
|
|
if (tag.isView()) return tag.read(asCompound());
|
2022-03-20 01:47:57 +01:00
|
|
|
return read(entries, tag);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2022-04-09 16:17:24 +02:00
|
|
|
public <T> void setTag(@NotNull Tag<T> tag, @Nullable T value) {
|
2022-04-09 15:20:11 +02:00
|
|
|
if (tag.isView()) {
|
2022-04-09 16:17:24 +02:00
|
|
|
MutableNBTCompound viewCompound = new MutableNBTCompound();
|
|
|
|
tag.writeUnsafe(viewCompound, value);
|
|
|
|
updateContent(viewCompound);
|
|
|
|
} else {
|
2022-04-10 10:01:39 +02:00
|
|
|
if (value instanceof NBT nbt) {
|
|
|
|
synchronized (this) {
|
|
|
|
write(tag, null);
|
|
|
|
TagNbtSeparator.separate(tag.getKey(), nbt,
|
|
|
|
entry -> write(entry.tag(), entry.value()));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (value != null) {
|
|
|
|
final UnaryOperator<T> copy = tag.copy;
|
|
|
|
if (copy != null) value = copy.apply(value);
|
|
|
|
}
|
|
|
|
write(tag, value);
|
2022-04-09 16:17:24 +02:00
|
|
|
}
|
2022-04-07 11:34:18 +02:00
|
|
|
}
|
2022-04-09 16:17:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public @NotNull TagReadable readableCopy() {
|
|
|
|
return updatedCache();
|
|
|
|
}
|
|
|
|
|
2022-04-10 15:49:33 +02:00
|
|
|
@Override
|
|
|
|
public synchronized @NotNull TagHandler copy() {
|
|
|
|
return new TagHandlerImpl(entries.clone(), cache);
|
|
|
|
}
|
|
|
|
|
2022-04-09 16:17:24 +02:00
|
|
|
@Override
|
|
|
|
public void updateContent(@NotNull NBTCompoundLike compound) {
|
|
|
|
final TagHandlerImpl converted = fromCompound(compound);
|
|
|
|
synchronized (this) {
|
|
|
|
this.cache = converted.cache;
|
|
|
|
this.entries = converted.entries;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public @NotNull NBTCompound asCompound() {
|
|
|
|
return updatedCache().compound;
|
|
|
|
}
|
|
|
|
|
|
|
|
public synchronized <T> void write(@NotNull Tag<T> tag, @Nullable T value) {
|
2022-03-24 05:42:01 +01:00
|
|
|
int tagIndex = tag.index;
|
|
|
|
TagHandlerImpl local = this;
|
2022-03-20 01:47:57 +01:00
|
|
|
Entry<?>[] entries = this.entries;
|
2022-03-26 13:03:28 +01:00
|
|
|
final Entry<?>[] localEntries = entries;
|
2022-03-24 05:42:01 +01:00
|
|
|
|
2022-04-12 14:14:36 +02:00
|
|
|
final Tag.PathEntry[] paths = tag.path;
|
2022-03-24 05:42:01 +01:00
|
|
|
TagHandlerImpl[] pathHandlers = null;
|
|
|
|
if (paths != null) {
|
2022-03-28 15:59:36 +02:00
|
|
|
final int length = paths.length;
|
|
|
|
pathHandlers = new TagHandlerImpl[length];
|
|
|
|
for (int i = 0; i < length; i++) {
|
|
|
|
final Tag.PathEntry path = paths[i];
|
2022-03-24 05:42:01 +01:00
|
|
|
final int pathIndex = path.index();
|
|
|
|
if (pathIndex >= entries.length) {
|
|
|
|
if (value == null) return;
|
|
|
|
local.entries = entries = Arrays.copyOf(entries, pathIndex + 1);
|
|
|
|
}
|
2022-03-26 13:03:28 +01:00
|
|
|
final Entry<?> entry = entries[pathIndex];
|
2022-04-12 14:14:36 +02:00
|
|
|
if (entry instanceof PathEntry pathEntry) {
|
2022-03-26 13:03:28 +01:00
|
|
|
// Existing path, continue navigating
|
2022-04-10 10:01:39 +02:00
|
|
|
local = pathEntry.value;
|
2022-04-09 16:17:24 +02:00
|
|
|
} else {
|
2022-04-10 10:01:39 +02:00
|
|
|
if (value == null) return;
|
2022-04-12 14:14:36 +02:00
|
|
|
// Empty path, create a new handler.
|
|
|
|
// Slow path is taken if the entry comes from a Structure tag, requiring conversion from NBT
|
|
|
|
local = entry != null && entry.updatedNbt() instanceof NBTCompound compound ? fromCompound(compound) : new TagHandlerImpl();
|
2022-04-10 10:01:39 +02:00
|
|
|
entries[pathIndex] = new PathEntry(path.name(), local);
|
2022-04-09 16:17:24 +02:00
|
|
|
}
|
2022-03-24 05:42:01 +01:00
|
|
|
entries = local.entries;
|
2022-03-28 15:59:36 +02:00
|
|
|
pathHandlers[i] = local;
|
2022-03-24 05:42:01 +01:00
|
|
|
}
|
|
|
|
// Handle removal if the tag was present (recursively)
|
|
|
|
if (value == null) {
|
2022-04-13 22:22:39 +02:00
|
|
|
// Remove entry
|
|
|
|
{
|
|
|
|
Entry<?>[] finalEntries = pathHandlers[length - 1].entries;
|
2022-04-13 22:26:56 +02:00
|
|
|
if (finalEntries.length > tagIndex) finalEntries[tagIndex] = null;
|
2022-04-13 22:37:54 +02:00
|
|
|
else return;
|
2022-04-13 22:22:39 +02:00
|
|
|
}
|
|
|
|
// Clear empty parents
|
2022-03-24 05:42:01 +01:00
|
|
|
boolean empty = false;
|
2022-03-28 15:59:36 +02:00
|
|
|
for (int i = length - 1; i >= 0; i--) {
|
2022-03-24 05:42:01 +01:00
|
|
|
TagHandlerImpl handler = pathHandlers[i];
|
|
|
|
Entry<?>[] entr = handler.entries;
|
|
|
|
// Verify if the handler is empty
|
2022-03-26 13:03:28 +01:00
|
|
|
empty = tagIndex >= entr.length || ArrayUtils.isEmpty(entr);
|
|
|
|
if (empty && i > 0) {
|
2022-03-24 05:42:01 +01:00
|
|
|
TagHandlerImpl parent = pathHandlers[i - 1];
|
2022-03-26 15:26:46 +01:00
|
|
|
parent.entries[paths[i].index()] = null;
|
2022-03-24 05:42:01 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (empty) {
|
|
|
|
// Remove the root handler
|
|
|
|
local = this;
|
2022-03-26 13:03:28 +01:00
|
|
|
entries = localEntries;
|
2022-03-26 15:26:46 +01:00
|
|
|
tagIndex = paths[0].index();
|
2022-03-24 05:42:01 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Normal tag
|
|
|
|
if (tagIndex >= entries.length) {
|
|
|
|
if (value == null) return;
|
|
|
|
local.entries = entries = Arrays.copyOf(entries, tagIndex + 1);
|
2022-03-20 01:47:57 +01:00
|
|
|
}
|
2022-04-10 10:01:39 +02:00
|
|
|
entries[tagIndex] = value != null ? new TagEntry<>(tag, value) : null;
|
2022-03-20 01:47:57 +01:00
|
|
|
this.cache = null;
|
2022-03-24 05:42:01 +01:00
|
|
|
if (pathHandlers != null) {
|
|
|
|
for (var handler : pathHandlers) handler.cache = null;
|
|
|
|
}
|
2022-03-20 01:47:57 +01:00
|
|
|
}
|
|
|
|
|
2022-03-26 13:05:21 +01:00
|
|
|
private synchronized Cache updatedCache() {
|
2022-03-20 01:47:57 +01:00
|
|
|
Cache cache = this.cache;
|
|
|
|
if (cache == null) {
|
2022-04-10 10:01:39 +02:00
|
|
|
final Entry<?>[] entries = this.entries;
|
2022-03-26 13:05:21 +01:00
|
|
|
if (entries.length > 0) {
|
|
|
|
MutableNBTCompound tmp = new MutableNBTCompound();
|
|
|
|
for (Entry<?> entry : entries) {
|
2022-04-10 10:01:39 +02:00
|
|
|
if (entry != null) tmp.put(entry.tag().getKey(), entry.updatedNbt());
|
2022-03-20 01:47:57 +01:00
|
|
|
}
|
2022-04-10 10:01:39 +02:00
|
|
|
cache = !tmp.isEmpty() ? new Cache(entries.clone(), tmp.toCompound()) : Cache.EMPTY;
|
|
|
|
} else cache = Cache.EMPTY;
|
2022-03-26 13:05:21 +01:00
|
|
|
this.cache = cache;
|
2022-03-20 01:47:57 +01:00
|
|
|
}
|
|
|
|
return cache;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static <T> T read(Entry<?>[] entries, Tag<T> tag) {
|
2022-04-12 12:16:40 +02:00
|
|
|
final Tag.PathEntry[] paths = tag.path;
|
2022-03-26 15:26:46 +01:00
|
|
|
if (paths != null) {
|
2022-03-24 05:42:01 +01:00
|
|
|
// Must be a path-able entry
|
2022-04-12 12:16:40 +02:00
|
|
|
if ((entries = traversePath(paths, entries)) == null)
|
|
|
|
return tag.createDefault();
|
2022-03-24 05:42:01 +01:00
|
|
|
}
|
2022-04-10 10:01:39 +02:00
|
|
|
final int index = tag.index;
|
2022-04-12 12:18:00 +02:00
|
|
|
final Entry<?> entry;
|
2022-03-20 01:47:57 +01:00
|
|
|
if (index >= entries.length || (entry = entries[index]) == null) {
|
|
|
|
return tag.createDefault();
|
|
|
|
}
|
2022-04-10 10:01:39 +02:00
|
|
|
if (entry.tag().shareValue(tag)) {
|
|
|
|
// The tag used to write the entry is compatible with the one used to get
|
|
|
|
// return the value directly
|
2022-03-20 01:47:57 +01:00
|
|
|
//noinspection unchecked
|
2022-04-10 10:01:39 +02:00
|
|
|
return (T) entry.value();
|
2022-03-20 01:47:57 +01:00
|
|
|
}
|
|
|
|
// Value must be parsed from nbt if the tag is different
|
2022-03-24 11:23:16 +01:00
|
|
|
final NBT nbt = entry.updatedNbt();
|
2022-03-23 06:51:36 +01:00
|
|
|
try {
|
2022-04-07 11:05:11 +02:00
|
|
|
return tag.entry.read().apply(nbt);
|
2022-03-23 06:51:36 +01:00
|
|
|
} catch (ClassCastException e) {
|
|
|
|
return tag.createDefault();
|
|
|
|
}
|
2022-03-20 01:47:57 +01:00
|
|
|
}
|
2022-04-09 16:17:24 +02:00
|
|
|
|
2022-04-12 14:14:36 +02:00
|
|
|
private static Entry<?>[] traversePath(Tag.PathEntry[] paths, Entry<?>[] entries) {
|
2022-04-12 12:16:40 +02:00
|
|
|
for (var path : paths) {
|
|
|
|
final int pathIndex = path.index();
|
|
|
|
final Entry<?> entry;
|
|
|
|
if (pathIndex >= entries.length || (entry = entries[pathIndex]) == null)
|
|
|
|
return null;
|
|
|
|
if (entry instanceof PathEntry pathEntry) {
|
|
|
|
entries = pathEntry.value.entries;
|
|
|
|
} else if (entry.updatedNbt() instanceof NBTCompound compound) {
|
|
|
|
// Slow path forcing a conversion of the structure to NBTCompound
|
|
|
|
// TODO should the handler be cached inside the entry?
|
|
|
|
TagHandlerImpl tmp = fromCompound(compound);
|
|
|
|
entries = tmp.entries;
|
|
|
|
} else {
|
|
|
|
// Entry is not path-able
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return entries;
|
|
|
|
}
|
|
|
|
|
2022-04-10 10:01:39 +02:00
|
|
|
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) {
|
|
|
|
if (tag.isView()) return tag.read(compound);
|
|
|
|
return read(entries, tag);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-10 12:26:33 +02:00
|
|
|
private sealed interface Entry<T>
|
|
|
|
permits TagEntry, PathEntry {
|
2022-04-10 10:01:39 +02:00
|
|
|
Tag<T> tag();
|
|
|
|
|
|
|
|
T value();
|
|
|
|
|
|
|
|
NBT updatedNbt();
|
|
|
|
}
|
|
|
|
|
|
|
|
private static final class TagEntry<T> implements Entry<T> {
|
2022-04-10 12:26:33 +02:00
|
|
|
private final Tag<T> tag;
|
|
|
|
private final T value;
|
2022-04-10 10:01:39 +02:00
|
|
|
volatile NBT nbt;
|
|
|
|
|
|
|
|
TagEntry(Tag<T> tag, T value) {
|
|
|
|
this.tag = tag;
|
|
|
|
this.value = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Tag<T> tag() {
|
|
|
|
return tag;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public T value() {
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public NBT updatedNbt() {
|
|
|
|
NBT nbt = this.nbt;
|
|
|
|
if (nbt == null) this.nbt = nbt = tag.entry.write().apply(value);
|
|
|
|
return nbt;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private record PathEntry(Tag<TagHandlerImpl> tag,
|
|
|
|
TagHandlerImpl value) implements Entry<TagHandlerImpl> {
|
|
|
|
PathEntry(String key, TagHandlerImpl value) {
|
|
|
|
this(Tag.tag(key, Serializers.PATH), value);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public NBT updatedNbt() {
|
|
|
|
return value.asCompound();
|
2022-04-09 16:17:24 +02:00
|
|
|
}
|
|
|
|
}
|
2022-03-20 01:47:57 +01:00
|
|
|
}
|