diff --git a/lophine-server/src/main/java/fun/bm/lophine/utils/concurrent/AbstractConcurrentTable.java b/lophine-server/src/main/java/fun/bm/lophine/utils/concurrent/AbstractConcurrentTable.java new file mode 100644 index 0000000..ee3c40f --- /dev/null +++ b/lophine-server/src/main/java/fun/bm/lophine/utils/concurrent/AbstractConcurrentTable.java @@ -0,0 +1,36 @@ +package fun.bm.lophine.utils.concurrent; + +import java.util.List; +import java.util.Map; + +public abstract class AbstractConcurrentTable { + public abstract void put(X x, Y y, Z z); + + public abstract void remove(X x, Y y, Z z); + + public abstract List getZ(X x, Y y); + + public abstract List getY(X x, Z z); + + public abstract List getX(Y y, Z z); + + public abstract Map getXY(Z z); + + public abstract Map getYZ(X x); + + public abstract Map getXZ(Y y); + + public abstract List getAllX(); + + public abstract List getAllY(); + + public abstract List getAllZ(); + + public abstract void clearXY(Z z); + + public abstract void clearYZ(X x); + + public abstract void clearXZ(Y y); + + public abstract void clearAll(); +} diff --git a/lophine-server/src/main/java/fun/bm/lophine/utils/concurrent/ConcurrentTable.java b/lophine-server/src/main/java/fun/bm/lophine/utils/concurrent/ConcurrentTable.java new file mode 100644 index 0000000..0b350e1 --- /dev/null +++ b/lophine-server/src/main/java/fun/bm/lophine/utils/concurrent/ConcurrentTable.java @@ -0,0 +1,182 @@ +package fun.bm.lophine.utils.concurrent; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.function.Predicate; + +public class ConcurrentTable extends AbstractConcurrentTable { + protected final ConcurrentLinkedDeque> data = new ConcurrentLinkedDeque<>(); + protected final boolean flagX; + protected final boolean flagY; + protected final boolean flagZ; + + public ConcurrentTable() { + this(false, false, false); + } + + public ConcurrentTable(boolean flagX, boolean flagY, boolean flagZ) { + this.flagX = flagX; + this.flagY = flagY; + this.flagZ = flagZ; + } + + @Override + public void put(X x, Y y, Z z) { + put(x, y, z, false); + } + + public void put(X x, Y y, Z z, boolean flag) { + if (!flag) { + if (flagX) { + List datas = getX(y, z); + for (X x1 : datas) { + if (!x1.equals(x)) { + remove(x1, y, z); + } + } + } + if (flagY) { + List datas = getY(x, z); + for (Y y1 : datas) { + if (!y1.equals(y)) { + remove(x, y1, z); + } + } + } + if (flagZ) { + List datas = getZ(x, y); + for (Z z1 : datas) { + if (!z1.equals(z)) { + remove(x, y, z1); + } + } + } + } + data.add(new TableEntry<>(x, y, z)); + } + + @Override + public void remove(X x, Y y, Z z) { + data.removeIf(entry -> entry.getX().equals(x) && entry.getY().equals(y) && entry.getZ().equals(z)); + } + + @Override + public List getZ(X x, Y y) { + return filterAndCollect( + entry -> entry.getX().equals(x) && entry.getY().equals(y), + TableEntry::getZ + ); + } + + @Override + public List getY(X x, Z z) { + return filterAndCollect( + entry -> entry.getX().equals(x) && entry.getZ().equals(z), + TableEntry::getY + ); + } + + @Override + public List getX(Y y, Z z) { + return filterAndCollect( + entry -> entry.getY().equals(y) && entry.getZ().equals(z), + TableEntry::getX + ); + } + + @Override + public Map getXY(Z z) { + return filterAndMap( + entry -> entry.getZ().equals(z), + TableEntry::getX, + TableEntry::getY + ); + } + + @Override + public Map getYZ(X x) { + return filterAndMap( + entry -> entry.getX().equals(x), + TableEntry::getY, + TableEntry::getZ + ); + } + + @Override + public Map getXZ(Y y) { + return filterAndMap( + entry -> entry.getY().equals(y), + TableEntry::getX, + TableEntry::getZ + ); + } + + @Override + public List getAllX() { + return collectAll(TableEntry::getX); + } + + @Override + public List getAllY() { + return collectAll(TableEntry::getY); + } + + @Override + public List getAllZ() { + return collectAll(TableEntry::getZ); + } + + @Override + public void clearXY(Z z) { + data.removeIf(entry -> entry.getZ().equals(z)); + } + + @Override + public void clearYZ(X x) { + data.removeIf(entry -> entry.getX().equals(x)); + } + + @Override + public void clearXZ(Y y) { + data.removeIf(entry -> entry.getY().equals(y)); + } + + @Override + public void clearAll() { + data.clear(); + } + + private List filterAndCollect(Predicate> filter, + java.util.function.Function, T> mapper) { + List result = new ArrayList<>(); + for (TableEntry entry : data) { + if (filter.test(entry)) { + result.add(mapper.apply(entry)); + } + } + return result; + } + + private Map filterAndMap(Predicate> filter, + java.util.function.Function, K> keyMapper, + java.util.function.Function, V> valueMapper) { + Map map = new HashMap<>(); + for (TableEntry entry : data) { + if (filter.test(entry)) { + map.put(keyMapper.apply(entry), valueMapper.apply(entry)); + } + } + return map; + } + + private List collectAll(java.util.function.Function, T> mapper) { + List result = new ArrayList<>(); + for (TableEntry entry : data) { + result.add(mapper.apply(entry)); + } + return result; + } +} diff --git a/lophine-server/src/main/java/fun/bm/lophine/utils/concurrent/OptimizedConcurrentTable.java b/lophine-server/src/main/java/fun/bm/lophine/utils/concurrent/OptimizedConcurrentTable.java new file mode 100644 index 0000000..2b701fa --- /dev/null +++ b/lophine-server/src/main/java/fun/bm/lophine/utils/concurrent/OptimizedConcurrentTable.java @@ -0,0 +1,207 @@ +package fun.bm.lophine.utils.concurrent; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.function.Predicate; + +public class OptimizedConcurrentTable extends ConcurrentTable { + private final ConcurrentHashMap>> xyIndex = new ConcurrentHashMap<>(); + private final ConcurrentHashMap>> yzIndex = new ConcurrentHashMap<>(); + private final ConcurrentHashMap>> zxIndex = new ConcurrentHashMap<>(); + + public OptimizedConcurrentTable() { + super(); + } + + public OptimizedConcurrentTable(boolean flagX, boolean flagY, boolean flagZ) { + super(flagX, flagY, flagZ); + } + + @Override + public void put(X x, Y y, Z z) { + if (flagX) { + List datas = getX(y, z); + for (X x1 : datas) { + if (!x1.equals(x)) { + remove(x1, y, z); + } + } + } + if (flagY) { + List datas = getY(x, z); + for (Y y1 : datas) { + if (!y1.equals(y)) { + remove(x, y1, z); + } + } + } + if (flagZ) { + List datas = getZ(x, y); + for (Z z1 : datas) { + if (!z1.equals(z)) { + remove(x, y, z1); + } + } + } + super.put(x, y, z, true); + xyIndex.computeIfAbsent(x, k -> new ConcurrentHashMap<>()) + .computeIfAbsent(y, k -> ConcurrentHashMap.newKeySet()).add(z); + yzIndex.computeIfAbsent(y, k -> new ConcurrentHashMap<>()) + .computeIfAbsent(z, k -> ConcurrentHashMap.newKeySet()).add(x); + zxIndex.computeIfAbsent(z, k -> new ConcurrentHashMap<>()) + .computeIfAbsent(x, k -> ConcurrentHashMap.newKeySet()).add(y); + } + + @Override + public void remove(X x, Y y, Z z) { + super.remove(x, y, z); + removeFromIndex(xyIndex, x, y, z); + removeFromIndex(yzIndex, y, z, x); + removeFromIndex(zxIndex, z, x, y); + } + + private void removeFromIndex(ConcurrentHashMap>> index, + K key1, V key2, T value) { + index.computeIfPresent(key1, (k, map) -> { + map.computeIfPresent(key2, (k2, set) -> { + set.remove(value); + return set.isEmpty() ? null : set; + }); + return map.isEmpty() ? null : map; + }); + } + + public void removeAll(Predicate> predicate) { + data.removeIf(entry -> { + boolean shouldRemove = predicate.test(entry); + if (shouldRemove) { + removeFromIndex(xyIndex, entry.getX(), entry.getY(), entry.getZ()); + removeFromIndex(yzIndex, entry.getY(), entry.getZ(), entry.getX()); + removeFromIndex(zxIndex, entry.getZ(), entry.getX(), entry.getY()); + } + return shouldRemove; + }); + } + + public boolean putIfAbsent(X x, Y y, Z z) { + if (data.stream().anyMatch(entry -> + Objects.equals(entry.getX(), x) && + Objects.equals(entry.getY(), y) && + Objects.equals(entry.getZ(), z))) { + return false; + } + put(x, y, z); + return true; + } + + @Override + public List getZ(X x, Y y) { + Set result = xyIndex.getOrDefault(x, new ConcurrentHashMap<>()).get(y); + return result != null ? new ArrayList<>(result) : new ArrayList<>(); + } + + @Override + public List getY(X x, Z z) { + Set result = zxIndex.getOrDefault(z, new ConcurrentHashMap<>()).get(x); + return result != null ? new ArrayList<>(result) : new ArrayList<>(); + } + + public List getX(Y y, Z z) { + Set result = yzIndex.getOrDefault(y, new ConcurrentHashMap<>()).get(z); + return result != null ? new ArrayList<>(result) : new ArrayList<>(); + } + + @Override + public Map getXY(Z z) { + return buildMapFromIndex(zxIndex.get(z), Function.identity(), Function.identity()); + } + + @Override + public Map getYZ(X x) { + return buildMapFromIndex(xyIndex.get(x), Function.identity(), Function.identity()); + } + + @Override + public Map getXZ(Y y) { + return reverseMapFromIndex(yzIndex.get(y)); + } + + @Override + public List getAllX() { + Set resultSet = new HashSet<>(xyIndex.keySet()); + return new ArrayList<>(resultSet); + } + + @Override + public List getAllY() { + Set resultSet = new HashSet<>(yzIndex.keySet()); + return new ArrayList<>(resultSet); + } + + @Override + public List getAllZ() { + Set resultSet = new HashSet<>(zxIndex.keySet()); + return new ArrayList<>(resultSet); + } + + + @Override + public void clearXY(Z z) { + super.clearXY(z); + zxIndex.remove(z); + } + + @Override + public void clearYZ(X x) { + super.clearYZ(x); + xyIndex.remove(x); + } + + @Override + public void clearXZ(Y y) { + super.clearXZ(y); + yzIndex.remove(y); + } + + @Override + public void clearAll() { + super.clearAll(); + xyIndex.clear(); + yzIndex.clear(); + zxIndex.clear(); + } + + + private Map buildMapFromIndex(ConcurrentHashMap> indexMap, java.util.function.Function keyMapper, java.util.function.Function valueMapper) { + Map result = new HashMap<>(); + if (indexMap != null) { + for (Map.Entry> entry : indexMap.entrySet()) { + K key = entry.getKey(); + Set valueSet = entry.getValue(); + if (valueSet != null && !valueSet.isEmpty()) { + for (V value : valueSet) { + result.put(keyMapper.apply(key), valueMapper.apply(value)); + } + } + } + } + return result; + } + + private Map reverseMapFromIndex(ConcurrentHashMap> indexMap) { + Map result = new HashMap<>(); + if (indexMap != null) { + for (Map.Entry> entry : indexMap.entrySet()) { + K key = entry.getKey(); + Set valueSet = entry.getValue(); + if (valueSet != null && !valueSet.isEmpty()) { + for (V value : valueSet) { + result.put(value, key); + } + } + } + } + return result; + } +} diff --git a/lophine-server/src/main/java/fun/bm/lophine/utils/concurrent/TableEntry.java b/lophine-server/src/main/java/fun/bm/lophine/utils/concurrent/TableEntry.java new file mode 100644 index 0000000..391ee37 --- /dev/null +++ b/lophine-server/src/main/java/fun/bm/lophine/utils/concurrent/TableEntry.java @@ -0,0 +1,25 @@ +package fun.bm.lophine.utils.concurrent; + +public class TableEntry { + private final X x; + private final Y y; + private final Z z; + + public TableEntry(X x, Y y, Z z) { + this.x = x; + this.y = y; + this.z = z; + } + + public X getX() { + return x; + } + + public Y getY() { + return y; + } + + public Z getZ() { + return z; + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/protocol/servux/ServuxHudDataProtocol.java b/lophine-server/src/main/java/org/leavesmc/leaves/protocol/servux/ServuxHudDataProtocol.java index 68aa759..bd25228 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/protocol/servux/ServuxHudDataProtocol.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/protocol/servux/ServuxHudDataProtocol.java @@ -17,11 +17,10 @@ package org.leavesmc.leaves.protocol.servux; -import com.google.common.collect.HashBasedTable; -import com.google.common.collect.Table; -import com.google.common.collect.Tables; import com.mojang.serialization.DataResult; import fun.bm.lophine.config.modules.function.protocol.ServuxProtocolConfig; +import fun.bm.lophine.utils.concurrent.AbstractConcurrentTable; +import fun.bm.lophine.utils.concurrent.OptimizedConcurrentTable; import io.netty.buffer.Unpooled; import net.minecraft.core.BlockPos; import net.minecraft.nbt.CompoundTag; @@ -59,7 +58,7 @@ public class ServuxHudDataProtocol implements LeavesProtocol { private static final Map> loggerPlayers = new ConcurrentHashMap<>(); private static final Map> LOGGERS = new ConcurrentHashMap<>(); - private static final Table DATA = Tables.synchronizedTable(HashBasedTable.create()); + private static final AbstractConcurrentTable DATA = new OptimizedConcurrentTable<>(false, false, true); public static boolean refreshSpawnMetadata = false; @@ -85,7 +84,7 @@ public class ServuxHudDataProtocol implements LeavesProtocol { private static void onPlayerLeave(ServerPlayer player) { players.remove(player); loggerPlayers.remove(player); - DATA.rowMap().values().forEach(row -> row.remove(player)); + DATA.clearXZ(player); } @ProtocolHandler.PayloadReceiver(payload = HudDataPayload.class) @@ -260,10 +259,12 @@ public class ServuxHudDataProtocol implements LeavesProtocol { loggerPlayers.forEach((player, list) -> { CompoundTag nbt = new CompoundTag(); for (DataLogger.Type type : list) { - Tag data = DATA.get(type, player); + List data0 = DATA.getZ(type, player); + if (data0.isEmpty()) continue; + Tag data = data0.getFirst(); if (data != null) { nbt.put(type.getSerializedName(), data); - DATA.remove(type, player); + DATA.remove(type, player, data); } } if (!nbt.isEmpty()) {