diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java index 3242f4401a..6a5fe5b3ec 100644 --- a/src/main/java/net/minecraft/server/ChunkProviderServer.java +++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java @@ -11,20 +11,20 @@ import java.util.Set; import java.util.Random; import org.bukkit.Server; -import org.bukkit.craftbukkit.util.LongHashset; -import org.bukkit.craftbukkit.util.LongHashtable; +import org.bukkit.craftbukkit.util.LongHashSet; +import org.bukkit.craftbukkit.util.LongObjectHashMap; import org.bukkit.event.world.ChunkUnloadEvent; // CraftBukkit end public class ChunkProviderServer implements IChunkProvider { // CraftBukkit start - public LongHashset unloadQueue = new LongHashset(); + public LongHashSet unloadQueue = new LongHashSet(); public Chunk emptyChunk; public IChunkProvider chunkProvider; // CraftBukkit private IChunkLoader e; public boolean forceChunkLoad = false; // true -> false - public LongHashtable chunks = new LongHashtable(); + public LongObjectHashMap chunks = new LongObjectHashMap(); public List chunkList = new ArrayList(); public WorldServer world; // CraftBukkit end diff --git a/src/main/java/net/minecraft/server/SpawnerCreature.java b/src/main/java/net/minecraft/server/SpawnerCreature.java index a484622525..c9870fd1a6 100644 --- a/src/main/java/net/minecraft/server/SpawnerCreature.java +++ b/src/main/java/net/minecraft/server/SpawnerCreature.java @@ -7,33 +7,14 @@ import java.util.List; import java.util.Random; // CraftBukkit start -import org.bukkit.craftbukkit.util.LongBaseHashtable; -import org.bukkit.craftbukkit.util.EntryBase; +import org.bukkit.craftbukkit.util.LongObjectHashMap; import org.bukkit.craftbukkit.util.LongHash; import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; // CraftBukkit end public final class SpawnerCreature { - // CraftBukkit start - // private static HashMap b = new HashMap(); // Moved local to spawnEntities - static private class ChunkEntry extends EntryBase { - public boolean spawn; - - public ChunkEntry(int x, int z, boolean spawn) { - super(LongHash.toLong(x, z)); - this.spawn = spawn; - } - - int getX() { - return LongHash.msw(key); - } - - int getZ() { - return LongHash.lsw(key); - } - } - // CraftBukkit end + private static LongObjectHashMap b = new LongObjectHashMap(); // CraftBukkit - HashMap -> LongObjectHashMap protected static final Class[] a = new Class[] { EntitySpider.class, EntityZombie.class, EntitySkeleton.class}; protected static ChunkPosition getRandomPosition(World world, int i, int j) { @@ -49,10 +30,7 @@ public final class SpawnerCreature { if (!flag && !flag1) { return 0; } else { - // CraftBukkit start - // b.clear(); - LongBaseHashtable chunkCoords = new LongBaseHashtable(); - // CraftBukkit end + b.clear(); int i; int j; @@ -67,13 +45,14 @@ public final class SpawnerCreature { for (int l = -b0; l <= b0; ++l) { for (int i1 = -b0; i1 <= b0; ++i1) { boolean flag2 = l == -b0 || l == b0 || i1 == -b0 || i1 == b0; + // CraftBukkit start - long chunkCoord = LongHash.toLong(l + k, i1 + j); + long chunkCoords = LongHash.toLong(l + k, i1 + j); if (!flag2) { - chunkCoords.put(new ChunkEntry(l + k, i1 + j, false)); - } else if (!chunkCoords.containsKey(chunkCoord)) { - chunkCoords.put(new ChunkEntry(l + k, i1 + j, true)); + b.put(chunkCoords, false); + } else if (!b.containsKey(chunkCoords)) { + b.put(chunkCoords, true); } // CraftBukkit end } @@ -82,7 +61,6 @@ public final class SpawnerCreature { i = 0; ChunkCoordinates chunkcoordinates = worldserver.getSpawn(); - java.util.ArrayList b = chunkCoords.entries(); // CraftBukkit EnumCreatureType[] aenumcreaturetype = EnumCreatureType.values(); j = aenumcreaturetype.length; @@ -110,13 +88,15 @@ public final class SpawnerCreature { // CraftBukkit end if ((!enumcreaturetype.d() || flag1) && (enumcreaturetype.d() || flag) && worldserver.a(enumcreaturetype.a()) <= limit * b.size() / 256) { // CraftBukkit - use per-world limits + Iterator iterator = b.keySet().iterator(); - // CraftBukkit start label108: - for (EntryBase base : b) { - ChunkEntry entry = (SpawnerCreature.ChunkEntry) base; - if (!entry.spawn) { - ChunkPosition chunkposition = getRandomPosition(worldserver, entry.getX(), entry.getZ()); + while (iterator.hasNext()) { + // CraftBukkit start + long key = ((Long) iterator.next()).longValue(); + + if (!((Boolean) b.get(key)).booleanValue()) { + ChunkPosition chunkposition = getRandomPosition(worldserver, LongHash.msw(key), LongHash.lsw(key)); // CraftBukkit end int k1 = chunkposition.x; int l1 = chunkposition.y; diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java index ae3dda7367..3a1bdc1f47 100644 --- a/src/main/java/net/minecraft/server/World.java +++ b/src/main/java/net/minecraft/server/World.java @@ -9,7 +9,7 @@ import java.util.concurrent.Callable; // CraftBukkit start import org.bukkit.Bukkit; -import org.bukkit.craftbukkit.util.LongHashset; +import org.bukkit.craftbukkit.util.LongHashSet; import org.bukkit.craftbukkit.util.UnsafeList; import org.bukkit.generator.ChunkGenerator; import org.bukkit.craftbukkit.CraftServer; @@ -60,7 +60,7 @@ public abstract class World implements IBlockAccess { // CraftBukkit start - public, longhashset public boolean allowMonsters = true; public boolean allowAnimals = true; - protected LongHashset chunkTickList = new LongHashset(); + protected LongHashSet chunkTickList = new LongHashSet(); public long ticksPerAnimalSpawns; public long ticksPerMonsterSpawns; // CraftBukkit end @@ -1057,12 +1057,12 @@ public abstract class World implements IBlockAccess { for (i = 0; i < this.j.size(); ++i) { entity = (Entity) this.j.get(i); // CraftBukkit start - fixed an NPE, don't process entities in chunks queued for unload - ChunkProviderServer chunkProviderServer = ((WorldServer) entity.world).chunkProviderServer; - if (chunkProviderServer.unloadQueue.containsKey(MathHelper.floor(entity.locX) >> 4, MathHelper.floor(entity.locZ) >> 4)) { + if (entity == null) { continue; } - if (entity == null) { + ChunkProviderServer chunkProviderServer = ((WorldServer) entity.world).chunkProviderServer; + if (chunkProviderServer.unloadQueue.contains(MathHelper.floor(entity.locX) >> 4, MathHelper.floor(entity.locZ) >> 4)) { continue; } // CraftBukkit end @@ -1103,7 +1103,7 @@ public abstract class World implements IBlockAccess { // CraftBukkit start - don't tick entities in chunks queued for unload ChunkProviderServer chunkProviderServer = ((WorldServer) entity.world).chunkProviderServer; - if (chunkProviderServer.unloadQueue.containsKey(MathHelper.floor(entity.locX) >> 4, MathHelper.floor(entity.locZ) >> 4)) { + if (chunkProviderServer.unloadQueue.contains(MathHelper.floor(entity.locX) >> 4, MathHelper.floor(entity.locZ) >> 4)) { continue; } // CraftBukkit end @@ -1147,7 +1147,7 @@ public abstract class World implements IBlockAccess { // CraftBukkit start - don't tick entities in chunks queued for unload ChunkProviderServer chunkProviderServer = ((WorldServer) tileentity.world).chunkProviderServer; - if (chunkProviderServer.unloadQueue.containsKey(tileentity.x >> 4, tileentity.z >> 4)) { + if (chunkProviderServer.unloadQueue.contains(tileentity.x >> 4, tileentity.z >> 4)) { continue; } // CraftBukkit end @@ -1819,7 +1819,7 @@ public abstract class World implements IBlockAccess { for (int i1 = -b0; i1 <= b0; ++i1) { // CraftBukkit start - don't tick chunks queued for unload ChunkProviderServer chunkProviderServer = ((WorldServer) entityhuman.world).chunkProviderServer; - if (chunkProviderServer.unloadQueue.containsKey(l + j, i1 + k)) { + if (chunkProviderServer.unloadQueue.contains(l + j, i1 + k)) { continue; } // CraftBukkit end diff --git a/src/main/java/org/bukkit/craftbukkit/util/EntryBase.java b/src/main/java/org/bukkit/craftbukkit/util/EntryBase.java deleted file mode 100644 index 4f71657b00..0000000000 --- a/src/main/java/org/bukkit/craftbukkit/util/EntryBase.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.bukkit.craftbukkit.util; - -public class EntryBase { - protected long key; - - public EntryBase(long key) { - this.key = key; - } -} diff --git a/src/main/java/org/bukkit/craftbukkit/util/LongBaseHashtable.java b/src/main/java/org/bukkit/craftbukkit/util/LongBaseHashtable.java deleted file mode 100644 index 8732597c22..0000000000 --- a/src/main/java/org/bukkit/craftbukkit/util/LongBaseHashtable.java +++ /dev/null @@ -1,112 +0,0 @@ -package org.bukkit.craftbukkit.util; - -import static org.bukkit.craftbukkit.util.Java15Compat.Arrays_copyOf; - -import java.util.ArrayList; - -public class LongBaseHashtable extends LongHash { - - EntryBase[][][] values = new EntryBase[256][][]; - EntryBase cache = null; - - public void put(int msw, int lsw, EntryBase entry) { - put(entry); - } - - public EntryBase getEntry(int msw, int lsw) { - return getEntry(toLong(msw, lsw)); - } - - public synchronized void put(EntryBase entry) { - int mainIdx = (int) (entry.key & 255); - EntryBase[][] outer = this.values[mainIdx]; - if (outer == null) this.values[mainIdx] = outer = new EntryBase[256][]; - - int outerIdx = (int) ((entry.key >> 32) & 255); - EntryBase[] inner = outer[outerIdx]; - - if (inner == null) { - outer[outerIdx] = inner = new EntryBase[5]; - inner[0] = this.cache = entry; - } else { - int i; - for (i = 0; i < inner.length; i++) { - if (inner[i] == null || inner[i].key == entry.key) { - inner[i] = this.cache = entry; - return; - } - } - - outer[outerIdx] = inner = Arrays_copyOf(inner, i + i); - inner[i] = entry; - } - } - - public synchronized EntryBase getEntry(long key) { - return containsKey(key) ? cache : null; - } - - public synchronized boolean containsKey(long key) { - if (this.cache != null && cache.key == key) return true; - - int outerIdx = (int) ((key >> 32) & 255); - EntryBase[][] outer = this.values[(int) (key & 255)]; - if (outer == null) return false; - - EntryBase[] inner = outer[outerIdx]; - if (inner == null) return false; - - for (int i = 0; i < inner.length; i++) { - EntryBase e = inner[i]; - if (e == null) { - return false; - } else if (e.key == key) { - this.cache = e; - return true; - } - } - return false; - } - - public synchronized void remove(long key) { - EntryBase[][] outer = this.values[(int) (key & 255)]; - if (outer == null) return; - - EntryBase[] inner = outer[(int) ((key >> 32) & 255)]; - if (inner == null) return; - - for (int i = 0; i < inner.length; i++) { - if (inner[i] == null) continue; - - if (inner[i].key == key) { - for (i++; i < inner.length; i++) { - if (inner[i] == null) break; - inner[i - 1] = inner[i]; - } - - inner[i-1] = null; - this.cache = null; - return; - } - } - } - - public synchronized ArrayList entries() { - ArrayList ret = new ArrayList(); - - for (EntryBase[][] outer : this.values) { - if (outer == null) continue; - - for (EntryBase[] inner : outer) { - if (inner == null) continue; - - for (EntryBase entry : inner) { - if (entry == null) break; - - ret.add(entry); - } - } - } - return ret; - } -} diff --git a/src/main/java/org/bukkit/craftbukkit/util/LongHash.java b/src/main/java/org/bukkit/craftbukkit/util/LongHash.java index 5cb09ed796..691cafd03d 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/LongHash.java +++ b/src/main/java/org/bukkit/craftbukkit/util/LongHash.java @@ -1,6 +1,6 @@ package org.bukkit.craftbukkit.util; -public abstract class LongHash { +public class LongHash { public static long toLong(int msw, int lsw) { return ((long) msw << 32) + lsw - Integer.MIN_VALUE; } @@ -12,16 +12,4 @@ public abstract class LongHash { public static int lsw(long l) { return (int) (l & 0xFFFFFFFF) + Integer.MIN_VALUE; } - - public boolean containsKey(int msw, int lsw) { - return containsKey(toLong(msw, lsw)); - } - - public void remove(int msw, int lsw) { - remove(toLong(msw, lsw)); - } - - public abstract boolean containsKey(long key); - - public abstract void remove(long key); } diff --git a/src/main/java/org/bukkit/craftbukkit/util/LongHashSet.java b/src/main/java/org/bukkit/craftbukkit/util/LongHashSet.java new file mode 100644 index 0000000000..22c96c5ab5 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/LongHashSet.java @@ -0,0 +1,301 @@ +/* + Based on CompactHashSet Copyright 2011 Ontopia Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package org.bukkit.craftbukkit.util; + +import java.util.Iterator; +import java.util.ConcurrentModificationException; +import java.util.NoSuchElementException; + +public class LongHashSet { + private final static int INITIAL_SIZE = 3; + private final static double LOAD_FACTOR = 0.75; + + private final static long FREE = 0; + private final static long REMOVED = Long.MIN_VALUE; + + private int freeEntries; + private int elements; + private long[] values; + private int modCount; + + public LongHashSet() { + this(INITIAL_SIZE); + } + + public LongHashSet(int size) { + values = new long[(size==0 ? 1 : size)]; + elements = 0; + freeEntries = values.length; + modCount = 0; + } + + public Iterator iterator() { + return new Itr(); + } + + public int size() { + return elements; + } + + public boolean isEmpty() { + return elements == 0; + } + + public boolean contains(int msw, int lsw) { + return contains(LongHash.toLong(msw, lsw)); + } + + public boolean contains(long value) { + int hash = hash(value); + int index = (hash & 0x7FFFFFFF) % values.length; + int offset = 1; + + // search for the object (continue while !null and !this object) + while(values[index] != FREE && !(hash(values[index]) == hash && values[index] == value)) { + index = ((index + offset) & 0x7FFFFFFF) % values.length; + offset = offset * 2 + 1; + + if (offset == -1) { + offset = 2; + } + } + + return values[index] != FREE; + } + + public boolean add(int msw, int lsw) { + return add(LongHash.toLong(msw, lsw)); + } + + public boolean add(long value) { + int hash = hash(value); + int index = (hash & 0x7FFFFFFF) % values.length; + int offset = 1; + int deletedix = -1; + + // search for the object (continue while !null and !this object) + while(values[index] != FREE && !(hash(values[index]) == hash && values[index] == value)) { + // if there's a deleted object here we can put this object here, + // provided it's not in here somewhere else already + if (values[index] == REMOVED) { + deletedix = index; + } + + index = ((index + offset) & 0x7FFFFFFF) % values.length; + offset = offset * 2 + 1; + + if (offset == -1) { + offset = 2; + } + } + + if (values[index] == FREE) { + if (deletedix != -1) { // reusing a deleted cell + index = deletedix; + } else { + freeEntries--; + } + + modCount++; + elements++; + values[index] = value; + + if (1 - (freeEntries / (double) values.length) > LOAD_FACTOR) { + rehash(); + } + + return true; + } else { + return false; + } + } + + public void remove(int msw, int lsw) { + remove(LongHash.toLong(msw, lsw)); + } + + public boolean remove(long value) { + int hash = hash(value); + int index = (hash & 0x7FFFFFFF) % values.length; + int offset = 1; + + // search for the object (continue while !null and !this object) + while(values[index] != FREE && !(hash(values[index]) == hash && values[index] == value)) { + index = ((index + offset) & 0x7FFFFFFF) % values.length; + offset = offset * 2 + 1; + + if (offset == -1) { + offset = 2; + } + } + + if (values[index] != FREE) { + values[index] = REMOVED; + modCount++; + elements--; + return true; + } else { + return false; + } + } + + public void clear() { + elements = 0; + for (int ix = 0; ix < values.length; ix++) { + values[ix] = FREE; + } + + freeEntries = values.length; + modCount++; + } + + public long[] toArray() { + long[] result = new long[elements]; + long[] values = Java15Compat.Arrays_copyOf(this.values, this.values.length); + int pos = 0; + + for (long value : values) { + if (value != FREE && value != REMOVED) { + result[pos++] = value; + } + } + + return result; + } + + public long popFirst() { + for (long value : values) { + if (value != FREE && value != REMOVED) { + remove(value); + return value; + } + } + + return 0; + } + + public long[] popAll() { + long[] ret = toArray(); + clear(); + return ret; + } + + // This method copied from Murmur3, written by Austin Appleby released under Public Domain + private int hash(long value) { + value ^= value >>> 33; + value *= 0xff51afd7ed558ccdL; + value ^= value >>> 33; + value *= 0xc4ceb9fe1a85ec53L; + value ^= value >>> 33; + return (int) value; + } + + private void rehash() { + int gargagecells = values.length - (elements + freeEntries); + if (gargagecells / (double) values.length > 0.05) { + rehash(values.length); + } else { + rehash(values.length * 2 + 1); + } + } + + private void rehash(int newCapacity) { + long[] newValues = new long[newCapacity]; + + for (long value : values) { + if (value == FREE || value == REMOVED) { + continue; + } + + int hash = hash(value); + int index = (hash & 0x7FFFFFFF) % newCapacity; + int offset = 1; + + // search for the object + while (newValues[index] != FREE) { + index = ((index + offset) & 0x7FFFFFFF) % newCapacity; + offset = offset * 2 + 1; + + if (offset == -1) { + offset = 2; + } + } + + newValues[index] = value; + } + + values = newValues; + freeEntries = values.length - elements; + } + + private class Itr implements Iterator { + private int index; + private int lastReturned = -1; + private int expectedModCount; + + public Itr() { + for (index = 0; index < values.length && (values[index] == FREE || values[index] == REMOVED); index++) { + // This is just to drive the index forward to the first valid entry + } + expectedModCount = modCount; + } + + public boolean hasNext() { + return index != values.length; + } + + public Long next() { + if (modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + + int length = values.length; + if (index >= length) { + lastReturned = -2; + throw new NoSuchElementException(); + } + + lastReturned = index; + for (index += 1; index < length && (values[index] == FREE || values[index] == REMOVED); index++) { + // This is just to drive the index forward to the next valid entry + } + + if (values[lastReturned] == FREE) { + return FREE; + } else { + return values[lastReturned]; + } + } + + public void remove() { + if (modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + + if (lastReturned == -1 || lastReturned == -2) { + throw new IllegalStateException(); + } + + if (values[lastReturned] != FREE && values[lastReturned] != REMOVED) { + values[lastReturned] = REMOVED; + elements--; + modCount++; + expectedModCount = modCount; + } + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/LongHashset.java b/src/main/java/org/bukkit/craftbukkit/util/LongHashset.java deleted file mode 100644 index e5847c3594..0000000000 --- a/src/main/java/org/bukkit/craftbukkit/util/LongHashset.java +++ /dev/null @@ -1,178 +0,0 @@ -package org.bukkit.craftbukkit.util; - -import static org.bukkit.craftbukkit.util.Java15Compat.Arrays_copyOf; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; -import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; - -public class LongHashset extends LongHash { - long[][][] values = new long[256][][]; - int count = 0; - ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); - ReadLock rl = rwl.readLock(); - WriteLock wl = rwl.writeLock(); - - public boolean isEmpty() { - rl.lock(); - try { - return this.count == 0; - } finally { - rl.unlock(); - } - } - - public int size() { - return count; - } - - public void add(int msw, int lsw) { - add(toLong(msw, lsw)); - } - - public void add(long key) { - wl.lock(); - try { - int mainIdx = (int) (key & 255); - long outer[][] = this.values[mainIdx]; - if (outer == null) this.values[mainIdx] = outer = new long[256][]; - - int outerIdx = (int) ((key >> 32) & 255); - long inner[] = outer[outerIdx]; - - if (inner == null) { - synchronized (this) { - outer[outerIdx] = inner = new long[1]; - inner[0] = key; - this.count++; - } - } else { - int i; - for (i = 0; i < inner.length; i++) { - if (inner[i] == key) { - return; - } - } - inner = Arrays_copyOf(inner, i + 1); - outer[outerIdx] = inner; - inner[i] = key; - this.count++; - } - } finally { - wl.unlock(); - } - } - - public boolean containsKey(long key) { - rl.lock(); - try { - long[][] outer = this.values[(int) (key & 255)]; - if (outer == null) return false; - - long[] inner = outer[(int) ((key >> 32) & 255)]; - if (inner == null) return false; - - for (long entry : inner) { - if (entry == key) return true; - } - return false; - } finally { - rl.unlock(); - } - } - - public void remove(long key) { - wl.lock(); - try { - long[][] outer = this.values[(int) (key & 255)]; - if (outer == null) return; - - long[] inner = outer[(int) ((key >> 32) & 255)]; - if (inner == null) return; - - int max = inner.length - 1; - for (int i = 0; i <= max; i++) { - if (inner[i] == key) { - this.count--; - if (i != max) { - inner[i] = inner[max]; - } - - outer[(int) ((key >> 32) & 255)] = (max == 0 ? null : Arrays_copyOf(inner, max)); - return; - } - } - } finally { - wl.unlock(); - } - } - - public long popFirst() { - wl.lock(); - try { - for (long[][] outer: this.values) { - if (outer == null) continue; - - for (int i = 0; i < outer.length; i++) { - long[] inner = outer[i]; - if (inner == null || inner.length == 0) continue; - - this.count--; - long ret = inner[inner.length - 1]; - outer[i] = Arrays_copyOf(inner, inner.length - 1); - - return ret; - } - } - } finally { - wl.unlock(); - } - return 0; - } - - public long[] popAll() { - int index = 0; - wl.lock(); - try { - long[] ret = new long[this.count]; - for (long[][] outer : this.values) { - if (outer == null) continue; - - for (int oIdx = outer.length - 1; oIdx >= 0; oIdx--) { - long[] inner = outer[oIdx]; - if (inner == null) continue; - - for (long entry: inner) { - ret[index++] = entry; - } - outer[oIdx] = null; - } - } - count = 0; - return ret; - } finally { - wl.unlock(); - } - } - - public long[] keys() { - int index = 0; - rl.lock(); - try { - long[] ret = new long[this.count]; - for (long[][] outer : this.values) { - if (outer == null) continue; - - for (long[] inner : outer) { - if (inner == null) continue; - - for (long entry : inner) { - ret[index++] = entry; - } - } - } - return ret; - } finally { - rl.unlock(); - } - } -} diff --git a/src/main/java/org/bukkit/craftbukkit/util/LongHashtable.java b/src/main/java/org/bukkit/craftbukkit/util/LongHashtable.java deleted file mode 100644 index 2b89cf9418..0000000000 --- a/src/main/java/org/bukkit/craftbukkit/util/LongHashtable.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.bukkit.craftbukkit.util; - -import java.util.ArrayList; - -public class LongHashtable extends LongBaseHashtable { - - public void put(int msw, int lsw, V value) { - put(toLong(msw, lsw), value); - } - - public V get(int msw, int lsw) { - return get(toLong(msw, lsw)); - } - - public synchronized void put(long key, V value) { - put(new Entry(key, value)); - } - - @SuppressWarnings("unchecked") - public synchronized V get(long key) { - Entry entry = ((Entry) getEntry(key)); - return entry != null ? entry.value : null; - } - - @SuppressWarnings("unchecked") - public synchronized ArrayList values() { - ArrayList ret = new ArrayList(); - - ArrayList entries = entries(); - - for (EntryBase entry : entries) { - ret.add(((Entry) entry).value); - } - return ret; - } - - private class Entry extends EntryBase { - V value; - - Entry(long k, V v) { - super(k); - this.value = v; - } - } -} diff --git a/src/main/java/org/bukkit/craftbukkit/util/LongObjectHashMap.java b/src/main/java/org/bukkit/craftbukkit/util/LongObjectHashMap.java new file mode 100644 index 0000000000..34cc0a6ed8 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/LongObjectHashMap.java @@ -0,0 +1,537 @@ +/* +Based on OpenLongObjectHashMap from colt. Original copyright follows: + +Copyright © 1999 CERN - European Organization for Nuclear Research. +Permission to use, copy, modify, distribute and sell this software and its documentation for any purpose +is hereby granted without fee, provided that the above copyright notice appear in all copies and +that both that copyright notice and this permission notice appear in supporting documentation. +CERN makes no representations about the suitability of this software for any purpose. +It is provided "as is" without expressed or implied warranty. +*/ + +package org.bukkit.craftbukkit.util; + +import java.util.AbstractCollection; +import java.util.AbstractSet; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map.Entry; +import java.util.NoSuchElementException; +import java.util.Set; + +public class LongObjectHashMap implements Cloneable { + private long keys[]; + private V values[]; + + private int freeEntries; + private int elements; + + private int highWaterMark; + private int lowWaterMark; + private final double minLoadFactor; + private final double maxLoadFactor; + + private final long FREE = 0; + private final long REMOVED = Long.MIN_VALUE; + + private static final int defaultCapacity = 277; + private static final double defaultMinLoadFactor = 0.2; + private static final double defaultMaxLoadFactor = 0.5; + + private static final int largestPrime = Integer.MAX_VALUE; + private static final int[] primeCapacities = { + largestPrime, + + 5, 11, 23, 47, 97, 197, 397, 797, 1597, 3203, 6421, 12853, 25717, 51437, 102877, 205759, + 411527, 823117, 1646237, 3292489, 6584983, 13169977, 26339969, 52679969, 105359939, + 210719881, 421439783, 842879579, 1685759167, + + 433, 877, 1759, 3527, 7057, 14143, 28289, 56591, 113189, 226379, 452759, 905551, 1811107, + 3622219, 7244441, 14488931, 28977863, 57955739, 115911563, 231823147, 463646329, 927292699, + 1854585413, + + 953, 1907, 3821, 7643, 15287, 30577, 61169, 122347, 244703, 489407, 978821, 1957651, 3915341, + 7830701, 15661423, 31322867, 62645741, 125291483, 250582987, 501165979, 1002331963, + 2004663929, + + 1039, 2081, 4177, 8363, 16729, 33461, 66923, 133853, 267713, 535481, 1070981, 2141977, 4283963, + 8567929, 17135863, 34271747, 68543509, 137087021, 274174111, 548348231, 1096696463, + + 31, 67, 137, 277, 557, 1117, 2237, 4481, 8963, 17929, 35863, 71741, 143483, 286973, 573953, + 1147921, 2295859, 4591721, 9183457, 18366923, 36733847, 73467739, 146935499, 293871013, + 587742049, 1175484103, + + 599, 1201, 2411, 4831, 9677, 19373, 38747, 77509, 155027, 310081, 620171, 1240361, 2480729, + 4961459, 9922933, 19845871, 39691759, 79383533, 158767069, 317534141, 635068283, 1270136683, + + 311, 631, 1277, 2557, 5119, 10243, 20507, 41017, 82037, 164089, 328213, 656429, 1312867, + 2625761, 5251529, 10503061, 21006137, 42012281, 84024581, 168049163, 336098327, 672196673, + 1344393353, + + 3, 7, 17, 37, 79, 163, 331, 673, 1361, 2729, 5471, 10949, 21911, 43853, 87719, 175447, 350899, + 701819, 1403641, 2807303, 5614657, 11229331, 22458671, 44917381, 89834777, 179669557, + 359339171, 718678369, 1437356741, + + 43, 89, 179, 359, 719, 1439, 2879, 5779, 11579, 23159, 46327, 92657, 185323, 370661, 741337, + 1482707, 2965421, 5930887, 11861791, 23723597, 47447201, 94894427, 189788857, 379577741, + 759155483, 1518310967, + + 379, 761, 1523, 3049, 6101, 12203, 24407, 48817, 97649, 195311, 390647, 781301, 1562611, + 3125257, 6250537, 12501169, 25002389, 50004791, 100009607, 200019221, 400038451, 800076929, + 1600153859 + }; + + static { + java.util.Arrays.sort(primeCapacities); + } + + public LongObjectHashMap() { + this(defaultCapacity); + } + + public LongObjectHashMap(int initialCapacity) { + this(initialCapacity, defaultMinLoadFactor, defaultMaxLoadFactor); + } + + @SuppressWarnings("unchecked") + public LongObjectHashMap(int initialCapacity, double minLoadFactor, double maxLoadFactor) { + int capacity = initialCapacity; + capacity = nextPrime(capacity); + if (capacity == 0) capacity = 1; + + keys = new long[capacity]; + values = (V[]) new Object[capacity]; + + this.minLoadFactor = minLoadFactor; + if (maxLoadFactor == largestPrime) { + this.maxLoadFactor = 1.0; + } else { + this.maxLoadFactor = maxLoadFactor; + } + + elements = 0; + freeEntries = capacity; + + lowWaterMark = 0; + highWaterMark = chooseHighWaterMark(capacity, this.maxLoadFactor); + } + + public void clear() { + Arrays.fill(keys, FREE); + Arrays.fill(values, null); + + elements = 0; + freeEntries = keys.length; + trimToSize(); + } + + @SuppressWarnings("unchecked") + public Set keySet() { + return new KeySet(); + } + + @SuppressWarnings("unchecked") + public Collection values() { + return new ValueCollection(); + } + + /** + * Returns a Set of Entry objects for the HashMap. This is not how the internal + * implementation is laid out so this constructs the entire Set when called. For + * this reason it should be avoided if at all possible. + * @deprecated + * @return Set of Entry objects + */ + @Deprecated + public Set> entrySet() { + HashSet> set = new HashSet>(); + + for (int i = 0; i < keys.length; i++) { + if (keys[i] != FREE && keys[i] != REMOVED) { + set.add(new LongObjectEntry(keys[i], values[i])); + } + } + + return set; + } + + public boolean containsKey(int msw, int lsw) { + return containsKey(LongHash.toLong(msw, lsw)); + } + + public boolean containsKey(long key) { + return indexOfKey(key) >= 0; + } + + public boolean containsValue(V value) { + return indexOfValue(value) >= 0; + } + + public int size() { + return elements; + } + + public boolean isEmpty() { + return elements == 0; + } + + public V get(int msw, int lsw) { + return get(LongHash.toLong(msw, lsw)); + } + + public V get(long key) { + int i = indexOfKey(key); + if (i < 0) { + return null; + } + return values[i]; + } + + public V put(int msw, int lsw, V value) { + return put(LongHash.toLong(msw, lsw), value); + } + + public V put(long key, V value) { + int i = indexOfInsertion(key); + if (i < 0) { + i = -i - 1; + V oldValue = values[i]; + values[i] = value; + return oldValue; + } + + if (elements > highWaterMark) { + int newCapacity = chooseGrowCapacity(elements + 1, minLoadFactor, maxLoadFactor); + rehash(newCapacity); + return put(key, value); + } + + if (keys[i] == FREE) { + freeEntries--; + } + + keys[i] = key; + values[i] = value; + elements++; + + if (freeEntries < 1) { + int newCapacity = chooseGrowCapacity(elements + 1, minLoadFactor, maxLoadFactor); + rehash(newCapacity); + } + + return null; + } + + public V remove(int msw, int lsw) { + return remove(LongHash.toLong(msw, lsw)); + } + + public V remove(long key) { + int i = indexOfKey(key); + if (i < 0) { + return null; + } + + V oldValue = values[i]; + keys[i] = REMOVED; + values[i] = null; + elements--; + + if (elements < lowWaterMark) { + int newCapacity = chooseShrinkCapacity(elements, minLoadFactor, maxLoadFactor); + rehash(newCapacity); + } + + return oldValue; + } + + public void ensureCapacity(int minCapacity) { + if (keys.length < minCapacity) { + int newCapacity = nextPrime(minCapacity); + rehash(newCapacity); + } + } + + public void trimToSize() { + int newCapacity = nextPrime((int) (1 + 1.2 * size())); + if (keys.length > newCapacity) { + rehash(newCapacity); + } + } + + public Object clone() throws CloneNotSupportedException { + LongObjectHashMap copy = (LongObjectHashMap) super.clone(); + copy.keys = keys.clone(); + copy.values = values.clone(); + return copy; + } + + /** + * @param key the key to be added to the receiver. + * @return the index where the key would need to be inserted, if it is not already contained. + * Returns -index-1 if the key is already contained at slot index. + * Therefore, if the returned index < 0, then it is already contained at slot -index-1. + * If the returned index >= 0, then it is NOT already contained and should be inserted at slot index. + */ + private int indexOfInsertion(long key) { + final int length = keys.length; + + final int hash = hash(key) & 0x7FFFFFFF; + int i = hash % length; + int decrement = hash % (length - 2); + if (decrement == 0) { + decrement = 1; + } + + while ((keys[i] != FREE && keys[i] != REMOVED) && keys[i] != key) { + i -= decrement; + if (i < 0) { + i += length; + } + } + + if (keys[i] == REMOVED) { + int j = i; + while (keys[i] != FREE && (keys[i] == REMOVED || keys[i] != key)) { + i -= decrement; + if (i < 0) { + i += length; + } + } + if (keys[i] == FREE) { + i = j; + } + } + + + if (keys[i] != FREE && keys[i] != REMOVED) { + // key already contained at slot i. + // return a negative number identifying the slot. + return -i - 1; + } + // not already contained, should be inserted at slot i. + // return a number >= 0 identifying the slot. + return i; + } + + private int indexOfKey(long key) { + final int length = keys.length; + + final int hash = hash(key) & 0x7FFFFFFF; + int i = hash % length; + int decrement = hash % (length - 2); + if (decrement == 0) decrement = 1; + + while (keys[i] != FREE && (keys[i] == REMOVED || keys[i] != key)) { + i -= decrement; + if (i < 0) i += length; + } + + if (keys[i] == FREE) { + return -1; + } + + return i; + } + + private int indexOfValue(Object value) { + for (int i = keys.length; --i >= 0; ) { + if ((keys[i] != FREE && keys[i] != REMOVED) && values[i] == value) { + return i; + } + } + + return -1; + } + + @SuppressWarnings("unchecked") + private void rehash(int newCapacity) { + int oldCapacity = keys.length; + + long oldKeys[] = keys; + V oldValues[] = values; + + long newKeys[] = new long[newCapacity]; + V newValues[] = (V[]) new Object[newCapacity]; + + lowWaterMark = chooseLowWaterMark(newCapacity, minLoadFactor); + highWaterMark = chooseHighWaterMark(newCapacity, maxLoadFactor); + + keys = newKeys; + values = newValues; + freeEntries = newCapacity - elements; + + for (int i = oldCapacity; i-- > 0; ) { + if (oldKeys[i] != FREE && oldKeys[i] != REMOVED) { + long element = oldKeys[i]; + int index = indexOfInsertion(element); + newKeys[index] = element; + newValues[index] = oldValues[i]; + } + } + } + + private static int nextPrime(int desiredCapacity) { + int i = Arrays.binarySearch(primeCapacities, desiredCapacity); + if (i < 0) { + i = -i - 1; + } + + return primeCapacities[i]; + } + + private int chooseGrowCapacity(int size, double minLoad, double maxLoad) { + return nextPrime(Math.max(size + 1, (int) ((4 * size / (3 * minLoad + maxLoad))))); + } + + private int chooseShrinkCapacity(int size, double minLoad, double maxLoad) { + return nextPrime(Math.max(size + 1, (int) ((4 * size / (minLoad + 3 * maxLoad))))); + } + + private int chooseHighWaterMark(int capacity, double maxLoad) { + return Math.min(capacity - 2, (int) (capacity * maxLoad)); + } + + private int chooseLowWaterMark(int capacity, double minLoad) { + return (int) (capacity * minLoad); + } + + // This method copied from Murmur3, written by Austin Appleby released under Public Domain + private int hash(long value) { + value ^= value >>> 33; + value *= 0xff51afd7ed558ccdL; + value ^= value >>> 33; + value *= 0xc4ceb9fe1a85ec53L; + value ^= value >>> 33; + return (int) value; + } + + private class LongObjectEntry implements Entry { + private final long key; + private final V value; + + LongObjectEntry(long k, V v) { + key = k; + value = v; + } + + public Long getKey() { + return key; + } + + public V getValue() { + return value; + } + + public V setValue(V v) { + // Changes are not reflected in the main HashMap + return value; + } + } + + private class KeySet extends AbstractSet { + public int size() { + return elements; + } + + public boolean contains(long key) { + return containsKey(key); + } + + @SuppressWarnings("unchecked") + public Iterator iterator() { + return new KeyIterator(); + } + } + + private class KeyIterator implements Iterator { + private int ix; + + private KeyIterator() { + for (ix = 0; ix < keys.length; ix++) { + if (values[ix] != null && keys[ix] != REMOVED) { + break; + } + } + } + + public boolean hasNext() { + return ix < keys.length; + } + + public void remove() { + throw new UnsupportedOperationException("Collection is read-only"); + } + + public Long next() { + if (ix >= keys.length) { + throw new NoSuchElementException(); + } + + long key = keys[ix++]; + + for (; ix < keys.length; ix++) { + if (keys[ix] != FREE && keys[ix] != REMOVED) { + break; + } + } + + return key; + } + } + + private class ValueCollection extends AbstractCollection { + public int size() { + return elements; + } + + @SuppressWarnings("unchecked") + public Iterator iterator() { + return new ValueIterator(); + } + + @SuppressWarnings("unchecked") + public boolean contains(Object value) { + return containsValue((V) value); + } + } + + private class ValueIterator implements Iterator { + private int ix; + + private ValueIterator() { + for (ix = 0; ix < values.length; ix++) { + if (values[ix] != null) { + break; + } + } + } + + public boolean hasNext() { + return ix < values.length; + } + + public void remove() { + throw new UnsupportedOperationException("Collection is read-only"); + } + + @SuppressWarnings("unchecked") + public V next() { + if (ix >= values.length) { + throw new NoSuchElementException(); + } + + V value = (V) values[ix++]; + + for (; ix < values.length; ix++) { + if (values[ix] != null) { + break; + } + } + + return value; + } + } +}