diff --git a/lophine-server/minecraft-patches/features/0007-Add-config-to-enable-waypoint-command-bar.patch b/lophine-server/minecraft-patches/features/0007-Add-config-to-enable-waypoint-command-bar.patch index 68ab30e..eb952a3 100644 --- a/lophine-server/minecraft-patches/features/0007-Add-config-to-enable-waypoint-command-bar.patch +++ b/lophine-server/minecraft-patches/features/0007-Add-config-to-enable-waypoint-command-bar.patch @@ -23,7 +23,7 @@ index 1fb359073342a657e9e493403263b56be5b3393f..451a685782da46f5683ea6707289bbaf WorldBorderCommand.register(this.dispatcher); if (JvmProfiler.INSTANCE.isAvailable()) { diff --git a/net/minecraft/server/waypoints/ServerWaypointManager.java b/net/minecraft/server/waypoints/ServerWaypointManager.java -index 0f8cacbb8fe55a60e2f0c98bf36c005b29f41a4b..73b69b0d93c17ab8bd3a7c0226d210760b607d7f 100644 +index 0f8cacbb8fe55a60e2f0c98bf36c005b29f41a4b..40675f209518d58267fb6ce3bfe697a2e681ea29 100644 --- a/net/minecraft/server/waypoints/ServerWaypointManager.java +++ b/net/minecraft/server/waypoints/ServerWaypointManager.java @@ -16,19 +16,26 @@ import net.minecraft.world.waypoints.WaypointManager; @@ -35,7 +35,7 @@ index 0f8cacbb8fe55a60e2f0c98bf36c005b29f41a4b..73b69b0d93c17ab8bd3a7c0226d21076 - private final Table connections = HashBasedTable.create(); + private final Set waypoints = new java.util.concurrent.CopyOnWriteArraySet<>(); // Lophine - concurrent + private final Set players = new java.util.concurrent.CopyOnWriteArraySet<>(); // Lophine - concurrent -+ private final fun.bm.lophine.utils.concurrent.ConcurrentTable connections = new fun.bm.lophine.utils.concurrent.ConcurrentTable<>(); // Lophine - concurrent ++ private final fun.bm.lophine.utils.concurrent.AbstractConcurrentTable connections = fun.bm.lophine.config.modules.optimizations.WayPointOptimizedTableConfig.optimizedTable ? new fun.bm.lophine.utils.concurrent.OptimizedConcurrentTable<>() : new fun.bm.lophine.utils.concurrent.ConcurrentTable<>(); // Lophine - concurrent @Override public void trackWaypoint(WaypointTransmitter waypoint) { @@ -121,23 +121,33 @@ index 0f8cacbb8fe55a60e2f0c98bf36c005b29f41a4b..73b69b0d93c17ab8bd3a7c0226d21076 } public void remakeConnections(WaypointTransmitter waypoint) { -@@ -86,7 +125,8 @@ public class ServerWaypointManager implements WaypointManager { - WaypointTransmitter.Connection connection = this.connections.remove(player, waypoint); -+ WaypointTransmitter.Connection connection = this.connections.getZ(player, waypoint); // Lophine - concurrent -+ this.connections.remove(player, waypoint, connection); // Lophine - concurrent - if (connection != null) { - connection.disconnect(); - } -@@ -106,7 +146,8 @@ public class ServerWaypointManager implements WaypointManager c = this.connections.getZ(player, waypoint); ++ c.forEach((connection) -> { ++ this.connections.remove(player, waypoint, connection); ++ if (connection != null) { ++ connection.disconnect(); ++ } ++ }); ++ // Lophine end - concurrent + }); + } + } +@@ -106,7 +150,8 @@ public class ServerWaypointManager implements WaypointManager { connection.disconnect(); - this.connections.remove(player, waypoint); -+ WaypointTransmitter.Connection c = this.connections.getZ(player, waypoint); // Lophine - concurrent -+ this.connections.remove(player, waypoint, c); // Lophine - concurrent ++ java.util.List c = this.connections.getZ(player, waypoint); // Lophine - concurrent ++ c.forEach((connection1) -> this.connections.remove(player, waypoint, connection1)); // Lophine - concurrent }); } } diff --git a/lophine-server/src/main/java/fun/bm/lophine/config/modules/optimizations/WayPointOptimizedTableConfig.java b/lophine-server/src/main/java/fun/bm/lophine/config/modules/optimizations/WayPointOptimizedTableConfig.java new file mode 100644 index 0000000..0d622f5 --- /dev/null +++ b/lophine-server/src/main/java/fun/bm/lophine/config/modules/optimizations/WayPointOptimizedTableConfig.java @@ -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; +} 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..086fbaf --- /dev/null +++ b/lophine-server/src/main/java/fun/bm/lophine/utils/concurrent/AbstractConcurrentTable.java @@ -0,0 +1,22 @@ +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 index a6fc8ca..11db91e 100644 --- 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 @@ -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 { - ConcurrentLinkedDeque> data = new ConcurrentLinkedDeque<>(); +public class ConcurrentTable extends AbstractConcurrentTable { + protected final ConcurrentLinkedDeque> 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 entry : data) { - if (entry.getX().equals(x) && entry.getY().equals(y)) { - return entry.getZ(); - } - } - return null; + @Override + public List 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 entry : data) { - if (entry.getX().equals(x) && entry.getZ().equals(z)) { - return entry.getY(); - } - } - return null; + @Override + public List 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 entry : data) { - if (entry.getY().equals(y) && entry.getZ().equals(z)) { - return entry.getX(); - } - } - return null; + @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) { - HashMap map = new HashMap<>(); - for (TableEntry 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 getYZ(X x) { - HashMap map = new HashMap<>(); - for (TableEntry 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 getXZ(Y y) { - HashMap map = new HashMap<>(); - for (TableEntry 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 getAllX() { - List xList = new ArrayList<>(); - for (TableEntry entry : data) { - xList.add(entry.getX()); - } - return xList; + return collectAll(TableEntry::getX); } + @Override public List getAllY() { - List yList = new ArrayList<>(); - for (TableEntry entry : data) { - yList.add(entry.getY()); - } - return yList; + return collectAll(TableEntry::getY); } + @Override public List getAllZ() { - List zList = new ArrayList<>(); - for (TableEntry 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 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..01359c4 --- /dev/null +++ b/lophine-server/src/main/java/fun/bm/lophine/utils/concurrent/OptimizedConcurrentTable.java @@ -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 extends ConcurrentTable { + private final ConcurrentHashMap>> xyIndex = new ConcurrentHashMap<>(); + private final ConcurrentHashMap>> yzIndex = new ConcurrentHashMap<>(); + private final ConcurrentHashMap>> 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 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; + } +}