diff --git a/Spigot-Server-Patches/0375-Optimize-Server-World-Map.patch b/Spigot-Server-Patches/0375-Optimize-Server-World-Map.patch new file mode 100644 index 0000000000..92953d4f25 --- /dev/null +++ b/Spigot-Server-Patches/0375-Optimize-Server-World-Map.patch @@ -0,0 +1,234 @@ +From a986a6ac3b035444524be7214677b83c7094fc5d Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 17 Sep 2018 23:37:31 -0400 +Subject: [PATCH] Optimize Server World Map + +Minecraft moved worlds to a hashmap in 1.13.1. +This creates inconsistent order for iteration of the map. + +This patch restores World management to be back as an Array. + +.values() will allow us to iterate as it was pre 1.13.1 by +ArrayList, giving consistent ordering and effecient iteration performance. + +KeySet and EntrySet iteration is proxied to the List iterator, +and should retain manipulation behavior but nothing should be doing that. + +Getting a World by dimension ID is now back a constant time operation. + +Hopefully no other plugins try to mess with this map, as we are only handling +known NMS used methods, but we can add more if naughty plugins are found later. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldMap.java b/src/main/java/com/destroystokyo/paper/PaperWorldMap.java +new file mode 100644 +index 0000000000..af9e4455c6 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldMap.java +@@ -0,0 +1,191 @@ ++package com.destroystokyo.paper; ++ ++import net.minecraft.server.DimensionManager; ++import net.minecraft.server.WorldServer; ++ ++import javax.annotation.Nonnull; ++import java.util.AbstractSet; ++import java.util.ArrayList; ++import java.util.Collection; ++import java.util.HashMap; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Map; ++import java.util.Set; ++ ++public class PaperWorldMap extends HashMap { ++ private final List worlds = new ArrayList<>(); ++ private final List worldsIterable = new ArrayList() { ++ @Override ++ public Iterator iterator() { ++ Iterator iterator = super.iterator(); ++ return new Iterator() { ++ private WorldServer last; ++ ++ @Override ++ public boolean hasNext() { ++ return iterator.hasNext(); ++ } ++ ++ @Override ++ public WorldServer next() { ++ this.last = iterator.next(); ++ return last; ++ } ++ ++ @Override ++ public void remove() { ++ worlds.set(last.dimension.getDimensionID()+1, null); ++ } ++ }; ++ } ++ }; ++ @Override ++ public int size() { ++ return worldsIterable.size(); ++ } ++ ++ @Override ++ public boolean isEmpty() { ++ return worldsIterable.isEmpty(); ++ } ++ ++ @Override ++ public WorldServer get(Object key) { ++ // Will hit the below method ++ return key instanceof DimensionManager ? get((DimensionManager) key) : null; ++ } ++ ++ public WorldServer get(DimensionManager key) { ++ int id = key.getDimensionID()+1; ++ return worlds.size() > id ? worlds.get(id) : null; ++ } ++ ++ @Override ++ public boolean containsKey(Object key) { ++ // will hit below method ++ return key instanceof DimensionManager && containsKey((DimensionManager) key); ++ } ++ public boolean containsKey(DimensionManager key) { ++ return get(key) != null; ++ } ++ ++ @Override ++ public WorldServer put(DimensionManager key, WorldServer value) { ++ while (worlds.size() <= key.getDimensionID()+1) { ++ worlds.add(null); ++ } ++ WorldServer old = worlds.set(key.getDimensionID()+1, value); ++ if (old != null) { ++ worldsIterable.remove(old); ++ } ++ worldsIterable.add(value); ++ return old; ++ } ++ ++ @Override ++ public void putAll(Map m) { ++ for (Entry e : m.entrySet()) { ++ put(e.getKey(), e.getValue()); ++ } ++ } ++ ++ @Override ++ public WorldServer remove(Object key) { ++ return key instanceof DimensionManager ? remove((DimensionManager) key) : null; ++ } ++ ++ public WorldServer remove(DimensionManager key) { ++ WorldServer old; ++ if (key.getDimensionID()+1 == worlds.size() - 1) { ++ old = worlds.remove(key.getDimensionID()+1); ++ } else { ++ old = worlds.set(key.getDimensionID() + 1, null); ++ } ++ if (old != null) { ++ worldsIterable.remove(old); ++ } ++ return old; ++ } ++ ++ @Override ++ public void clear() { ++ throw new RuntimeException("What the hell are you doing?"); ++ } ++ ++ @Override ++ public boolean containsValue(Object value) { ++ return value instanceof WorldServer && get(((WorldServer) value).dimension) != null; ++ } ++ ++ @Nonnull ++ @Override ++ public Set keySet() { ++ return new AbstractSet() { ++ @Override ++ public Iterator iterator() { ++ Iterator iterator = worldsIterable.iterator(); ++ return new Iterator() { ++ ++ @Override ++ public boolean hasNext() { ++ return iterator.hasNext(); ++ } ++ ++ @Override ++ public DimensionManager next() { ++ return iterator.next().dimension; ++ } ++ ++ @Override ++ public void remove() { ++ iterator.remove(); ++ } ++ }; ++ } ++ ++ @Override ++ public int size() { ++ return worlds.size(); ++ } ++ }; ++ } ++ ++ @Override ++ public Collection values() { ++ return worldsIterable; ++ } ++ ++ @Override ++ public Set> entrySet() { ++ return new AbstractSet>() { ++ @Override ++ public Iterator> iterator() { ++ Iterator iterator = worldsIterable.iterator(); ++ return new Iterator>() { ++ ++ @Override ++ public boolean hasNext() { ++ return iterator.hasNext(); ++ } ++ ++ @Override ++ public Entry next() { ++ WorldServer entry = iterator.next(); ++ return new SimpleEntry<>(entry.dimension, entry); ++ } ++ ++ @Override ++ public void remove() { ++ iterator.remove(); ++ } ++ }; ++ } ++ ++ @Override ++ public int size() { ++ return worldsIterable.size(); ++ } ++ }; ++ } ++} +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index fb62320310..2912e9ec68 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -87,7 +87,7 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati + public final DataFixer dataConverterManager; + private String serverIp; + private int q = -1; +- public final Map worldServer = Maps.newIdentityHashMap(); ++ public final Map worldServer = new com.destroystokyo.paper.PaperWorldMap(); // Paper + private PlayerList s; + private boolean isRunning = true; + private boolean isRestarting = false; // Paper - flag to signify we're attempting to restart +-- +2.19.0 +