2022-03-20 01:47:57 +01:00
|
|
|
package net.minestom.server.tag;
|
|
|
|
|
2022-04-14 16:26:57 +02:00
|
|
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
|
|
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
2022-04-25 05:45:22 +02:00
|
|
|
import net.minestom.server.utils.PropertyUtils;
|
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;
|
2022-04-29 18:24:55 +02:00
|
|
|
import org.jglrxavpok.hephaistos.nbt.NBTType;
|
2022-03-20 01:47:57 +01:00
|
|
|
import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound;
|
|
|
|
|
2022-04-25 05:45:22 +02:00
|
|
|
import java.lang.invoke.VarHandle;
|
2022-05-02 21:30:22 +02:00
|
|
|
import java.util.function.Supplier;
|
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-25 05:45:22 +02:00
|
|
|
private static final boolean CACHE_ENABLE = PropertyUtils.getBoolean("minestom.tag-handler-cache", true);
|
|
|
|
|
|
|
|
private final TagHandlerImpl parent;
|
2022-04-14 16:26:57 +02:00
|
|
|
private volatile SPMCMap entries;
|
2022-03-20 01:47:57 +01:00
|
|
|
private Cache cache;
|
|
|
|
|
2022-04-25 05:45:22 +02:00
|
|
|
TagHandlerImpl(TagHandlerImpl parent) {
|
|
|
|
this.parent = parent;
|
|
|
|
this.entries = new SPMCMap(this);
|
2022-04-14 16:26:57 +02:00
|
|
|
this.cache = null;
|
2022-04-10 15:49:33 +02:00
|
|
|
}
|
|
|
|
|
2022-04-25 05:45:22 +02:00
|
|
|
TagHandlerImpl() {
|
|
|
|
this(null);
|
|
|
|
}
|
|
|
|
|
2022-04-26 06:37:26 +02:00
|
|
|
static TagHandlerImpl fromCompound(NBTCompoundLike compoundLike) {
|
|
|
|
final NBTCompound compound = compoundLike.toCompound();
|
2022-04-25 05:45:22 +02:00
|
|
|
TagHandlerImpl handler = new TagHandlerImpl(null);
|
2022-04-26 06:37:26 +02:00
|
|
|
TagNbtSeparator.separate(compound, entry -> handler.setTag(entry.tag(), entry.value()));
|
2022-04-10 10:01:39 +02:00
|
|
|
return handler;
|
|
|
|
}
|
|
|
|
|
2022-03-20 01:47:57 +01:00
|
|
|
@Override
|
|
|
|
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
|
2022-05-02 21:30:22 +02:00
|
|
|
return read(entries, tag, this::asCompound);
|
2022-03-20 01:47:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2022-04-09 16:17:24 +02:00
|
|
|
public <T> void setTag(@NotNull Tag<T> tag, @Nullable T value) {
|
2022-05-02 21:30:22 +02:00
|
|
|
TagHandlerImpl local = this;
|
|
|
|
final Tag.PathEntry[] paths = tag.path;
|
|
|
|
final boolean present = value != null;
|
|
|
|
final int tagIndex = tag.index;
|
|
|
|
final boolean isView = tag.isView();
|
|
|
|
synchronized (this) {
|
|
|
|
if (paths != null) {
|
|
|
|
if ((local = traversePathWrite(this, paths, present)) == null)
|
|
|
|
return; // Tried to remove an absent tag. Do nothing
|
|
|
|
}
|
|
|
|
SPMCMap entries = local.entries;
|
|
|
|
if (present) {
|
|
|
|
if (!isView) {
|
2022-04-27 04:45:56 +02:00
|
|
|
entries.put(tagIndex, valueToEntry(local, tag, value));
|
|
|
|
} else {
|
2022-05-02 21:30:22 +02:00
|
|
|
local.updateContent((NBTCompound) tag.entry.write(value));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Remove recursively
|
|
|
|
if (!isView) {
|
2022-04-27 04:45:56 +02:00
|
|
|
if (entries.remove(tagIndex) == null) return;
|
2022-05-02 21:30:22 +02:00
|
|
|
} else {
|
|
|
|
entries.clear();
|
|
|
|
}
|
|
|
|
if (paths != null) {
|
|
|
|
TagHandlerImpl tmp = local;
|
|
|
|
int i = paths.length;
|
|
|
|
do {
|
|
|
|
if (!tmp.entries.isEmpty()) break;
|
|
|
|
tmp = tmp.parent;
|
|
|
|
tmp.entries.remove(paths[--i].index());
|
|
|
|
} while (i > 0);
|
2022-04-27 04:45:56 +02:00
|
|
|
}
|
2022-04-25 05:45:22 +02:00
|
|
|
}
|
2022-05-02 21:30:22 +02:00
|
|
|
entries.invalidate();
|
|
|
|
assert !local.entries.rehashed;
|
2022-04-16 19:15:48 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-26 06:37:26 +02:00
|
|
|
private <T> Entry<?> valueToEntry(TagHandlerImpl parent, Tag<T> tag, @NotNull T value) {
|
2022-04-16 19:15:48 +02:00
|
|
|
if (value instanceof NBT nbt) {
|
|
|
|
if (nbt instanceof NBTCompound compound) {
|
2022-04-26 06:37:26 +02:00
|
|
|
var handler = new TagHandlerImpl(parent);
|
|
|
|
handler.updateContent(compound);
|
2022-04-16 19:15:48 +02:00
|
|
|
return new PathEntry(tag.getKey(), handler);
|
2022-04-10 10:01:39 +02:00
|
|
|
} else {
|
2022-04-16 19:15:48 +02:00
|
|
|
final var nbtEntry = TagNbtSeparator.separateSingle(tag.getKey(), nbt);
|
|
|
|
return new TagEntry<>(nbtEntry.tag(), nbtEntry.value());
|
2022-04-09 16:17:24 +02:00
|
|
|
}
|
2022-04-16 19:15:48 +02:00
|
|
|
} else {
|
|
|
|
final UnaryOperator<T> copy = tag.copy;
|
|
|
|
if (copy != null) value = copy.apply(value);
|
|
|
|
return new TagEntry<>(tag, value);
|
2022-04-07 11:34:18 +02:00
|
|
|
}
|
2022-04-09 16:17:24 +02:00
|
|
|
}
|
|
|
|
|
2022-05-07 16:26:39 +02:00
|
|
|
@Override
|
|
|
|
public synchronized <T> void updateTag(@NotNull Tag<T> tag, @NotNull UnaryOperator<@UnknownNullability T> value) {
|
|
|
|
setTag(tag, value.apply(getTag(tag)));
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public synchronized <T> @UnknownNullability T updateAndGetTag(@NotNull Tag<T> tag, @NotNull UnaryOperator<@UnknownNullability T> value) {
|
|
|
|
final T next = value.apply(getTag(tag));
|
|
|
|
setTag(tag, next);
|
|
|
|
return next;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public synchronized <T> @UnknownNullability T getAndUpdateTag(@NotNull Tag<T> tag, @NotNull UnaryOperator<@UnknownNullability T> value) {
|
|
|
|
final T prev = getTag(tag);
|
|
|
|
setTag(tag, value.apply(prev));
|
|
|
|
return prev;
|
|
|
|
}
|
|
|
|
|
2022-04-09 16:17:24 +02:00
|
|
|
@Override
|
|
|
|
public @NotNull TagReadable readableCopy() {
|
|
|
|
return updatedCache();
|
|
|
|
}
|
|
|
|
|
2022-04-10 15:49:33 +02:00
|
|
|
@Override
|
2022-04-25 07:26:26 +02:00
|
|
|
public @NotNull TagHandler copy() {
|
|
|
|
return fromCompound(asCompound());
|
2022-04-10 15:49:33 +02:00
|
|
|
}
|
|
|
|
|
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;
|
2022-04-25 05:45:22 +02:00
|
|
|
this.entries = new SPMCMap(this, converted.entries);
|
2022-04-09 16:17:24 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public @NotNull NBTCompound asCompound() {
|
|
|
|
return updatedCache().compound;
|
|
|
|
}
|
|
|
|
|
2022-04-27 04:45:56 +02:00
|
|
|
private static TagHandlerImpl traversePathWrite(TagHandlerImpl root, Tag.PathEntry[] paths,
|
|
|
|
boolean present) {
|
2022-04-25 05:45:22 +02:00
|
|
|
TagHandlerImpl local = root;
|
2022-04-27 04:45:56 +02:00
|
|
|
for (Tag.PathEntry path : paths) {
|
2022-04-25 05:45:22 +02:00
|
|
|
final int pathIndex = path.index();
|
|
|
|
final Entry<?> entry = local.entries.get(pathIndex);
|
|
|
|
if (entry instanceof PathEntry pathEntry) {
|
|
|
|
// Existing path, continue navigating
|
2022-04-26 06:37:26 +02:00
|
|
|
assert pathEntry.value.parent == local : "Path parent is invalid: " + pathEntry.value.parent + " != " + local;
|
2022-04-25 05:45:22 +02:00
|
|
|
local = pathEntry.value;
|
|
|
|
} else {
|
|
|
|
if (!present) return null;
|
|
|
|
// Empty path, create a new handler.
|
|
|
|
// Slow path is taken if the entry comes from a Structure tag, requiring conversion from NBT
|
|
|
|
TagHandlerImpl tmp = local;
|
|
|
|
local = new TagHandlerImpl(tmp);
|
|
|
|
if (entry != null && entry.updatedNbt() instanceof NBTCompound compound) {
|
|
|
|
local.updateContent(compound);
|
2022-04-09 16:17:24 +02:00
|
|
|
}
|
2022-04-25 05:45:22 +02:00
|
|
|
tmp.entries.put(pathIndex, new PathEntry(path.name(), local));
|
2022-03-24 05:42:01 +01:00
|
|
|
}
|
|
|
|
}
|
2022-04-25 05:45:22 +02:00
|
|
|
return local;
|
2022-03-20 01:47:57 +01:00
|
|
|
}
|
|
|
|
|
2022-04-25 07:26:26 +02:00
|
|
|
private synchronized Cache updatedCache() {
|
2022-04-25 05:45:22 +02:00
|
|
|
Cache cache;
|
|
|
|
if (!CACHE_ENABLE || (cache = this.cache) == null) {
|
2022-04-25 07:26:26 +02:00
|
|
|
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;
|
2022-03-20 01:47:57 +01:00
|
|
|
}
|
|
|
|
return cache;
|
|
|
|
}
|
|
|
|
|
2022-05-02 21:30:22 +02:00
|
|
|
private static <T> T read(Int2ObjectOpenHashMap<Entry<?>> entries, Tag<T> tag,
|
|
|
|
Supplier<NBTCompound> rootCompoundSupplier) {
|
2022-04-12 12:16:40 +02:00
|
|
|
final Tag.PathEntry[] paths = tag.path;
|
2022-05-02 21:30:22 +02:00
|
|
|
TagHandlerImpl pathHandler = null;
|
2022-03-26 15:26:46 +01:00
|
|
|
if (paths != null) {
|
2022-05-02 21:30:22 +02:00
|
|
|
if ((pathHandler = traversePathRead(paths, entries)) == null)
|
|
|
|
return tag.createDefault(); // Must be a path-able entry, but not present
|
|
|
|
entries = pathHandler.entries;
|
2022-03-24 05:42:01 +01:00
|
|
|
}
|
2022-05-02 21:30:22 +02:00
|
|
|
|
|
|
|
if (tag.isView()) {
|
|
|
|
return tag.read(pathHandler != null ?
|
|
|
|
pathHandler.asCompound() : rootCompoundSupplier.get());
|
|
|
|
}
|
|
|
|
|
2022-04-12 12:18:00 +02:00
|
|
|
final Entry<?> entry;
|
2022-04-14 16:26:57 +02:00
|
|
|
if ((entry = entries.get(tag.index)) == null) {
|
2022-03-20 01:47:57 +01:00
|
|
|
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-04-20 17:19:45 +02:00
|
|
|
final Serializers.Entry<T, NBT> serializerEntry = tag.entry;
|
2022-04-29 18:24:55 +02:00
|
|
|
final NBTType<NBT> type = serializerEntry.nbtType();
|
|
|
|
return type == null || type == nbt.getID() ? serializerEntry.read(nbt) : tag.createDefault();
|
2022-03-20 01:47:57 +01:00
|
|
|
}
|
2022-04-09 16:17:24 +02:00
|
|
|
|
2022-05-02 21:30:22 +02:00
|
|
|
private static TagHandlerImpl traversePathRead(Tag.PathEntry[] paths,
|
|
|
|
Int2ObjectOpenHashMap<Entry<?>> entries) {
|
|
|
|
assert paths != null && paths.length > 0;
|
|
|
|
TagHandlerImpl result = null;
|
2022-04-12 12:16:40 +02:00
|
|
|
for (var path : paths) {
|
|
|
|
final Entry<?> entry;
|
2022-04-14 16:26:57 +02:00
|
|
|
if ((entry = entries.get(path.index())) == null)
|
2022-04-12 12:16:40 +02:00
|
|
|
return null;
|
|
|
|
if (entry instanceof PathEntry pathEntry) {
|
2022-05-02 21:30:22 +02:00
|
|
|
result = pathEntry.value;
|
2022-04-12 12:16:40 +02:00
|
|
|
} 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?
|
2022-05-02 21:30:22 +02:00
|
|
|
result = fromCompound(compound);
|
2022-04-12 12:16:40 +02:00
|
|
|
} else {
|
|
|
|
// Entry is not path-able
|
|
|
|
return null;
|
|
|
|
}
|
2022-05-02 21:30:22 +02:00
|
|
|
assert result != null;
|
|
|
|
entries = result.entries;
|
2022-04-12 12:16:40 +02:00
|
|
|
}
|
2022-05-02 21:30:22 +02:00
|
|
|
assert result != null;
|
|
|
|
return result;
|
2022-04-12 12:16:40 +02:00
|
|
|
}
|
|
|
|
|
2022-04-14 16:26:57 +02:00
|
|
|
private record Cache(Int2ObjectOpenHashMap<Entry<?>> entries, NBTCompound compound) implements TagReadable {
|
|
|
|
static final Cache EMPTY = new Cache(new Int2ObjectOpenHashMap<>(), NBTCompound.EMPTY);
|
2022-04-10 10:01:39 +02:00
|
|
|
|
|
|
|
@Override
|
|
|
|
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
|
2022-05-02 21:30:22 +02:00
|
|
|
return read(entries, tag, () -> compound);
|
2022-04-10 10:01:39 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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;
|
2022-04-20 17:19:45 +02:00
|
|
|
if (nbt == null) this.nbt = nbt = tag.entry.write(value);
|
2022-04-10 10:01:39 +02:00
|
|
|
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
|
2022-04-16 19:15:48 +02:00
|
|
|
public NBTCompound updatedNbt() {
|
2022-04-10 10:01:39 +02:00
|
|
|
return value.asCompound();
|
2022-04-09 16:17:24 +02:00
|
|
|
}
|
|
|
|
}
|
2022-04-14 16:26:57 +02:00
|
|
|
|
2022-04-25 05:45:22 +02:00
|
|
|
static final class SPMCMap extends Int2ObjectOpenHashMap<Entry<?>> {
|
|
|
|
final TagHandlerImpl handler;
|
|
|
|
volatile boolean rehashed;
|
2022-04-14 16:26:57 +02:00
|
|
|
|
2022-04-25 05:45:22 +02:00
|
|
|
SPMCMap(TagHandlerImpl handler) {
|
|
|
|
super();
|
|
|
|
this.handler = handler;
|
2022-04-14 16:26:57 +02:00
|
|
|
assertState();
|
|
|
|
}
|
|
|
|
|
2022-04-25 05:45:22 +02:00
|
|
|
SPMCMap(TagHandlerImpl handler, Int2ObjectMap<TagHandlerImpl.Entry<?>> m) {
|
|
|
|
super(m.size(), DEFAULT_LOAD_FACTOR);
|
|
|
|
this.handler = handler;
|
2022-04-14 16:26:57 +02:00
|
|
|
assertState();
|
2022-04-25 05:45:22 +02:00
|
|
|
putAll(m);
|
2022-04-14 16:26:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void rehash(int newSize) {
|
|
|
|
assertState();
|
2022-04-25 05:45:22 +02:00
|
|
|
this.handler.entries = new SPMCMap(handler, this);
|
2022-04-14 16:26:57 +02:00
|
|
|
this.rehashed = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public SPMCMap clone() {
|
|
|
|
return (SPMCMap) super.clone();
|
|
|
|
}
|
|
|
|
|
2022-04-25 05:45:22 +02:00
|
|
|
void invalidate() {
|
|
|
|
if (!CACHE_ENABLE) return;
|
|
|
|
TagHandlerImpl tmp = handler;
|
|
|
|
do {
|
|
|
|
tmp.cache = null;
|
|
|
|
} while ((tmp = tmp.parent) != null);
|
|
|
|
VarHandle.fullFence();
|
|
|
|
}
|
|
|
|
|
2022-04-14 16:26:57 +02:00
|
|
|
private void assertState() {
|
|
|
|
assert !rehashed;
|
2022-04-25 05:45:22 +02:00
|
|
|
assert handler != null;
|
2022-04-14 16:26:57 +02:00
|
|
|
}
|
|
|
|
}
|
2022-03-20 01:47:57 +01:00
|
|
|
}
|