feat: add optimized concurrent table for waypoint management

This commit is contained in:
Helvetica Volubi
2025-12-10 15:22:58 +08:00
parent 5e96851513
commit c06d875df7
5 changed files with 317 additions and 70 deletions
@@ -0,0 +1,17 @@
package fun.bm.lophine.config.modules.optimizations;
import me.earthme.luminol.config.IConfigModule;
import me.earthme.luminol.config.flags.ConfigClassInfo;
import me.earthme.luminol.config.flags.ConfigInfo;
import me.earthme.luminol.config.flags.HotReloadUnsupported;
import me.earthme.luminol.enums.EnumConfigCategory;
@ConfigClassInfo(category = EnumConfigCategory.OPTIMIZATIONS, name = "waypoint")
public class WayPointOptimizedTableConfig implements IConfigModule {
@HotReloadUnsupported
@ConfigInfo(name = "optimizedTable", comments = """
Should use optimized table instead of normal concurrent table for waypoints.
May improve performance when there are many waypoints and players.
When enabled, more memory is needed to store data.""")
public static boolean optimizedTable = false;
}
@@ -0,0 +1,22 @@
package fun.bm.lophine.utils.concurrent;
import java.util.List;
import java.util.Map;
public abstract class AbstractConcurrentTable<X, Y, Z> {
public abstract void put(X x, Y y, Z z);
public abstract void remove(X x, Y y, Z z);
public abstract List<Z> getZ(X x, Y y);
public abstract List<Y> getY(X x, Z z);
public abstract List<X> getX(Y y, Z z);
public abstract Map<X, Y> getXY(Z z);
public abstract Map<Y, Z> getYZ(X x);
public abstract Map<X, Z> getXZ(Y y);
public abstract List<X> getAllX();
public abstract List<Y> getAllY();
public abstract List<Z> getAllZ();
public abstract void clearXY(Z z);
public abstract void clearYZ(X x);
public abstract void clearXZ(Y y);
public abstract void clearAll();
}
@@ -5,112 +5,135 @@ 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<X, Y, Z> {
ConcurrentLinkedDeque<TableEntry<X, Y, Z>> data = new ConcurrentLinkedDeque<>();
public class ConcurrentTable<X, Y, Z> extends AbstractConcurrentTable<X, Y, Z> {
protected final ConcurrentLinkedDeque<TableEntry<X, Y, Z>> data = new ConcurrentLinkedDeque<>();
@Override
public void put(X x, Y y, Z z) {
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));
}
public Z getZ(X x, Y y) {
for (TableEntry<X, Y, Z> entry : data) {
if (entry.getX().equals(x) && entry.getY().equals(y)) {
return entry.getZ();
}
}
return null;
@Override
public List<Z> getZ(X x, Y y) {
return filterAndCollect(
entry -> entry.getX().equals(x) && entry.getY().equals(y),
TableEntry::getZ
);
}
public Y getY(X x, Z z) {
for (TableEntry<X, Y, Z> entry : data) {
if (entry.getX().equals(x) && entry.getZ().equals(z)) {
return entry.getY();
}
}
return null;
@Override
public List<Y> getY(X x, Z z) {
return filterAndCollect(
entry -> entry.getX().equals(x) && entry.getZ().equals(z),
TableEntry::getY
);
}
public X getX(Y y, Z z) {
for (TableEntry<X, Y, Z> entry : data) {
if (entry.getY().equals(y) && entry.getZ().equals(z)) {
return entry.getX();
}
}
return null;
@Override
public List<X> getX(Y y, Z z) {
return filterAndCollect(
entry -> entry.getY().equals(y) && entry.getZ().equals(z),
TableEntry::getX
);
}
@Override
public Map<X, Y> getXY(Z z) {
HashMap<X, Y> map = new HashMap<>();
for (TableEntry<X, Y, Z> entry : data) {
if (entry.getZ().equals(z)) {
map.put(entry.getX(), entry.getY());
}
}
return map;
return filterAndMap(
entry -> entry.getZ().equals(z),
TableEntry::getX,
TableEntry::getY
);
}
@Override
public Map<Y, Z> getYZ(X x) {
HashMap<Y, Z> map = new HashMap<>();
for (TableEntry<X, Y, Z> entry : data) {
if (entry.getX().equals(x)) {
map.put(entry.getY(), entry.getZ());
}
}
return map;
return filterAndMap(
entry -> entry.getX().equals(x),
TableEntry::getY,
TableEntry::getZ
);
}
@Override
public Map<X, Z> getXZ(Y y) {
HashMap<X, Z> map = new HashMap<>();
for (TableEntry<X, Y, Z> entry : data) {
if (entry.getY().equals(y)) {
map.put(entry.getX(), entry.getZ());
}
}
return map;
return filterAndMap(
entry -> entry.getY().equals(y),
TableEntry::getX,
TableEntry::getZ
);
}
@Override
public List<X> getAllX() {
List<X> xList = new ArrayList<>();
for (TableEntry<X, Y, Z> entry : data) {
xList.add(entry.getX());
}
return xList;
return collectAll(TableEntry::getX);
}
@Override
public List<Y> getAllY() {
List<Y> yList = new ArrayList<>();
for (TableEntry<X, Y, Z> entry : data) {
yList.add(entry.getY());
}
return yList;
return collectAll(TableEntry::getY);
}
@Override
public List<Z> getAllZ() {
List<Z> zList = new ArrayList<>();
for (TableEntry<X, Y, Z> entry : data) {
zList.add(entry.getZ());
}
return zList;
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 <T> List<T> filterAndCollect(Predicate<TableEntry<X, Y, Z>> filter,
java.util.function.Function<TableEntry<X, Y, Z>, T> mapper) {
List<T> result = new ArrayList<>();
for (TableEntry<X, Y, Z> entry : data) {
if (filter.test(entry)) {
result.add(mapper.apply(entry));
}
}
return result;
}
private <K, V> Map<K, V> filterAndMap(Predicate<TableEntry<X, Y, Z>> filter,
java.util.function.Function<TableEntry<X, Y, Z>, K> keyMapper,
java.util.function.Function<TableEntry<X, Y, Z>, V> valueMapper) {
Map<K, V> map = new HashMap<>();
for (TableEntry<X, Y, Z> entry : data) {
if (filter.test(entry)) {
map.put(keyMapper.apply(entry), valueMapper.apply(entry));
}
}
return map;
}
private <T> List<T> collectAll(java.util.function.Function<TableEntry<X, Y, Z>, T> mapper) {
List<T> result = new ArrayList<>();
for (TableEntry<X, Y, Z> entry : data) {
result.add(mapper.apply(entry));
}
return result;
}
}
@@ -0,0 +1,175 @@
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<X, Y, Z> extends ConcurrentTable<X, Y, Z> {
private final ConcurrentHashMap<X, ConcurrentHashMap<Y, Set<Z>>> xyIndex = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Y, ConcurrentHashMap<Z, Set<X>>> yzIndex = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Z, ConcurrentHashMap<X, Set<Y>>> zxIndex = new ConcurrentHashMap<>();
@Override
public void put(X x, Y y, Z z) {
super.put(x, y, z);
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 <K, V, T> void removeFromIndex(ConcurrentHashMap<K, ConcurrentHashMap<V, Set<T>>> 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<TableEntry<X, Y, Z>> 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<Z> getZ(X x, Y y) {
Set<Z> result = xyIndex.getOrDefault(x, new ConcurrentHashMap<>()).get(y);
return result != null ? new ArrayList<>(result) : new ArrayList<>();
}
@Override
public List<Y> getY(X x, Z z) {
Set<Y> result = zxIndex.getOrDefault(z, new ConcurrentHashMap<>()).get(x);
return result != null ? new ArrayList<>(result) : new ArrayList<>();
}
public List<X> getX(Y y, Z z) {
Set<X> result = yzIndex.getOrDefault(y, new ConcurrentHashMap<>()).get(z);
return result != null ? new ArrayList<>(result) : new ArrayList<>();
}
@Override
public Map<X, Y> getXY(Z z) {
return buildMapFromIndex(zxIndex.get(z), Function.identity(), Function.identity());
}
@Override
public Map<Y, Z> getYZ(X x) {
return buildMapFromIndex(xyIndex.get(x), Function.identity(), Function.identity());
}
@Override
public Map<X, Z> getXZ(Y y) {
return reverseMapFromIndex(yzIndex.get(y));
}
@Override
public List<X> getAllX() {
Set<X> resultSet = new HashSet<>(xyIndex.keySet());
return new ArrayList<>(resultSet);
}
@Override
public List<Y> getAllY() {
Set<Y> resultSet = new HashSet<>(yzIndex.keySet());
return new ArrayList<>(resultSet);
}
@Override
public List<Z> getAllZ() {
Set<Z> 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 <K, V, R, S> Map<R, S> buildMapFromIndex(ConcurrentHashMap<K, Set<V>> indexMap, java.util.function.Function<K, R> keyMapper, java.util.function.Function<V, S> valueMapper) {
Map<R, S> result = new HashMap<>();
if (indexMap != null) {
for (Map.Entry<K, Set<V>> entry : indexMap.entrySet()) {
K key = entry.getKey();
Set<V> valueSet = entry.getValue();
if (valueSet != null && !valueSet.isEmpty()) {
for (V value : valueSet) {
result.put(keyMapper.apply(key), valueMapper.apply(value));
}
}
}
}
return result;
}
private <K, V> Map<V, K> reverseMapFromIndex(ConcurrentHashMap<K, Set<V>> indexMap) {
Map<V, K> result = new HashMap<>();
if (indexMap != null) {
for (Map.Entry<K, Set<V>> entry : indexMap.entrySet()) {
K key = entry.getKey();
Set<V> valueSet = entry.getValue();
if (valueSet != null && !valueSet.isEmpty()) {
for (V value : valueSet) {
result.put(value, key);
}
}
}
}
return result;
}
}