diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftChunk.java index c9469f5d21..24459f064c 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftChunk.java @@ -10,10 +10,11 @@ import org.bukkit.Chunk; import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.craftbukkit.block.CraftBlock; +import org.bukkit.craftbukkit.util.SoftMap; public class CraftChunk implements Chunk { private WeakReference weakChunk; - private final HashMap cache = new HashMap(); + private final SoftMap cache = new SoftMap(); private WorldServer worldServer; private int x; private int z; diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/util/SoftMap.java b/paper-server/src/main/java/org/bukkit/craftbukkit/util/SoftMap.java new file mode 100644 index 0000000000..1b8f6c47bc --- /dev/null +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/util/SoftMap.java @@ -0,0 +1,200 @@ +package org.bukkit.craftbukkit.util; + +import java.util.Map; +import java.util.AbstractMap; +import java.util.LinkedList; +import java.util.HashMap; +import java.util.Set; +import java.util.Collection; +import java.util.Iterator; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.SoftReference; + +/** + * Creates a map that uses soft reference. This indicates to the garbage collector + * that they can be removed if necessary + * + * A minimum number of strong references can be set. These most recent N objects added + * to the map will not be removed by the garbage collector. + * + * Objects will never be removed if they are referenced strongly from somewhere else + + * Note: While data corruption won't happen, the garbage collector is potentially async + * This could lead to the return values from containsKey() and similar methods being + * out of date by the time they are used. The class could return null when the object + * is retrieved by a .get() call directly after a .containsKey() call returned true + * + * @author raphfrk + */ + +public class SoftMap { + + private final HashMap> map = new HashMap>(); + private final ReferenceQueue queue = new ReferenceQueue(); + private final LinkedList strongReferenceQueue = new LinkedList(); + private final int strongReferenceSize; + + public SoftMap() { + this(20); + } + + public SoftMap(int size) { + strongReferenceSize = size; + } + + // When a soft reference is deleted by the garbage collector, it is set to reference null + // and added to the queue + // + // However, these null references still exist in the HashMap as keys. This method removes these keys. + // + // It is called whenever there is a method call of the map. + + private void emptyQueue() { + SoftMapReference ref; + while((ref=(SoftMapReference)queue.poll()) != null) { + map.remove(ref.key); + } + } + + public void clear() { + strongReferenceQueue.clear(); + map.clear(); + emptyQueue(); + } + + // Shouldn't support this, since the garbage collection is async + + public boolean containsKey(K key) { + emptyQueue(); + return map.containsKey(key); + } + + // Shouldn't support this, since the garbage collection is async + + public boolean containsValue(V value) { + emptyQueue(); + return map.containsValue(value); + } + + // Shouldn't support this since it would create strong references to all the entries + + public Set entrySet() { + emptyQueue(); + throw new UnsupportedOperationException("SoftMap does not support this operation, since it creates potentially stong references"); + } + + // Doesn't support these either + + public boolean equals(Object o) { + emptyQueue(); + throw new UnsupportedOperationException("SoftMap doesn't support equals checks"); + } + + // This operation returns null if the entry is not in the map + + public V get(K key) { + emptyQueue(); + return fastGet(key); + } + + private V fastGet(K key) { + SoftMapReference ref = map.get(key); + if(ref==null) { + return null; + } + V value = ref.get(); + if(value!=null) { + strongReferenceQueue.addFirst(value); + if(strongReferenceQueue.size() > strongReferenceSize) { + strongReferenceQueue.removeLast(); + } + } + return value; + } + + // Doesn't support this either + + public int hashCode() { + emptyQueue(); + throw new UnsupportedOperationException("SoftMap doesn't support hashCode"); + } + + // This is another risky method, since again, garbage collection is async + + public boolean isEmpty() { + emptyQueue(); + return map.isEmpty(); + } + + // Return all the keys, again could go out of date + + public Set keySet() { + emptyQueue(); + return map.keySet(); + } + + // Adds the mapping to the map + + public V put(K key, V value) { + emptyQueue(); + V old = fastGet(key); + fastPut(key, value); + return old; + } + + private void fastPut(K key, V value) { + map.put(key, new SoftMapReference(key, value, queue)); + strongReferenceQueue.addFirst(value); + if(strongReferenceQueue.size() > strongReferenceSize) { + strongReferenceQueue.removeLast(); + } + } + + // Adds the mappings to the map + + public void putAll(Map other) { + emptyQueue(); + Iterator itr = other.keySet().iterator(); + while(itr.hasNext()) { + K key = itr.next(); + fastPut(key, (V)other.get(key)); + } + } + + // Remove object + + public V remove(K key) { + emptyQueue(); + SoftMapReference ref = map.remove(key); + if(ref != null) { + return ref.get(); + } + return null; + } + + // Returns size, could go out of date + + public int size() { + emptyQueue(); + return map.size(); + } + + // Shouldn't support this since it would create strong references to all the entries + + public Collection values() { + emptyQueue(); + throw new UnsupportedOperationException("SoftMap does not support this operation, since it creates potentially stong references"); + } + + + private static class SoftMapReference extends SoftReference { + K key; + + SoftMapReference(K key, V value, ReferenceQueue queue) { + super(value, queue); + this.key = key; + } + } + +}