diff --git a/src/main/java/io/akarin/server/misc/CopyOnWriteHashMap.java b/src/main/java/io/akarin/server/misc/CopyOnWriteHashMap.java new file mode 100644 index 000000000..ca9c60ea7 --- /dev/null +++ b/src/main/java/io/akarin/server/misc/CopyOnWriteHashMap.java @@ -0,0 +1,184 @@ +package io.akarin.server.misc; + +import java.io.Serializable; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; + +/** + * A thread-safe version of {@link Map} in which all operations that change the + * Map are implemented by making a new copy of the underlying Map. + *

+ * While the creation of a new Map can be expensive, this class is designed for + * cases in which the primary function is to read data from the Map, not to + * modify the Map. Therefore the operations that do not cause a change to this + * class happen quickly and concurrently. + * + * @author Kuzma Deretuke + */ +public class CopyOnWriteHashMap implements Map, Serializable, Cloneable { + private static final long serialVersionUID = 5481095911554321115L; + private AtomicReference> internalMap = new AtomicReference>(); + + /** + * Creates a new instance of CopyOnWriteHashMap. + */ + public CopyOnWriteHashMap() { + internalMap.set(new HashMap()); + } + + /** + * Creates a new instance of CopyOnWriteHashMap with the specified initial size. + * + * @param initialCapacity The initial size of the Map. + */ + public CopyOnWriteHashMap(int initialCapacity) { + internalMap.set(new HashMap(initialCapacity)); + } + + /** + * Creates a new instance of CopyOnWriteHashMap in which the initial data, + * being held by this map, is contained in the supplied map. + * + * @param data A Map containing the initial contents to be placed into this class. + */ + public CopyOnWriteHashMap(Map data) { + internalMap.set(new HashMap(data)); + } + + @Override + public V put(K key, V value) { + Map oldMap; + Map newMap; + V val; + do { + oldMap = internalMap.get(); + newMap = new HashMap(oldMap); + val = newMap.put(key, value); + } + while (!internalMap.compareAndSet(oldMap, newMap)); + return val; + } + + @Override + public V remove(Object key) { + Map oldMap; + Map newMap; + V val; + do { + oldMap = internalMap.get(); + newMap = new HashMap(oldMap); + val = newMap.remove(key); + } + while (!internalMap.compareAndSet(oldMap, newMap)); + return val; + } + + @Override + public void putAll(Map newData) { + Map oldMap; + Map newMap; + do { + oldMap = internalMap.get(); + newMap = new HashMap(oldMap); + newMap.putAll(newData); + } + while (!internalMap.compareAndSet(oldMap, newMap)); + } + + @Override + public void clear() { + internalMap.set(new HashMap()); + } + + // + // Below are methods that do not modify the internal map + // + + @Override + public int size() { + return internalMap.get().size(); + } + + @Override + public boolean isEmpty() { + return internalMap.get().isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return internalMap.get().containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return internalMap.get().containsValue(value); + } + + @Override + public V get(Object key) { + return internalMap.get().get(key); + } + + @Override + public Set keySet() { + return internalMap.get().keySet(); + } + + @Override + public Collection values() { + return internalMap.get().values(); + } + + @Override + public Set> entrySet() { + return internalMap.get().entrySet(); + } + + @Override + public int hashCode() { + return internalMap.get().hashCode(); + } + + @Override + public boolean equals(Object o) { + return internalMap.get().equals(o); + } + + @Override + public String toString() { + Map map = internalMap.get(); + Iterator> i = map.entrySet().iterator(); + if (!i.hasNext()) + return "{}"; + + StringBuilder sb = new StringBuilder(); + sb.append('{'); + for (; ; ) { + Entry e = i.next(); + K key = e.getKey(); + V value = e.getValue(); + sb.append(key == this ? "(this Map)" : (key == map ? "(internal Map)" : key)); + sb.append('='); + sb.append(value == this ? "(this Map)" : (value == map ? "(internal Map)" : value)); + if (!i.hasNext()) + return sb.append('}').toString(); + sb.append(',').append(' '); + } + } + + @Override + public Object clone() { + try { + CopyOnWriteHashMap clone = (CopyOnWriteHashMap) super.clone(); + clone.internalMap = new AtomicReference>(new HashMap(internalMap.get())); + return clone; + } + catch (CloneNotSupportedException e) { + throw new InternalError(); + } + } +} \ No newline at end of file diff --git a/src/main/java/net/minecraft/server/JsonList.java b/src/main/java/net/minecraft/server/JsonList.java index 2c69fffd9..5fc59cb41 100644 --- a/src/main/java/net/minecraft/server/JsonList.java +++ b/src/main/java/net/minecraft/server/JsonList.java @@ -14,6 +14,8 @@ import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; import com.mojang.authlib.GameProfile; +import io.akarin.server.misc.CopyOnWriteHashMap; + import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; @@ -28,6 +30,8 @@ import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; + import javax.annotation.Nullable; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.builder.ToStringBuilder; @@ -41,7 +45,7 @@ public class JsonList> { protected final Gson b; private final File c; // Paper - replace HashMap is ConcurrentHashMap - protected final Map d = Maps.newConcurrentMap(); private final Map getBackingMap() { return this.d; } // Paper - OBFHELPER + protected final Map d = new CopyOnWriteHashMap(); private final Map getBackingMap() { return this.d; } // Paper - OBFHELPER // Akarin private boolean e = true; private static final ParameterizedType f = new ParameterizedType() { public Type[] getActualTypeArguments() { diff --git a/work/Paper b/work/Paper index 0181fb14b..ab8bbdfd8 160000 --- a/work/Paper +++ b/work/Paper @@ -1 +1 @@ -Subproject commit 0181fb14b6b52052194e2ee30dea5744f14ffc73 +Subproject commit ab8bbdfd8008deb0241d6ed1c62345c24ed34bd8