3
0

Implement thread-safe, async-like and not buggy BlockUpdatesTracker

close #320
This commit is contained in:
Sergey Shatunov
2016-02-03 10:54:09 +07:00
parent 3359c026df
commit 7114a28fc6
7 changed files with 658 additions and 65 deletions

View File

@ -0,0 +1,39 @@
package kcauldron;
public class BlockCoords {
public final int x, y, z;
public final long key;
private final int hash;
public BlockCoords(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
key = ((long) y << 56) | (((long) z & 0xFFFFFFF) << 28) | (x & 0xFFFFFFF);
hash = (int) (key ^ (key >>> 32));
}
public BlockCoords(BlockCoords coords) {
this.x = coords.x;
this.y = coords.y;
this.z = coords.z;
this.key = coords.key;
this.hash = coords.hash;
}
@Override
public boolean equals(Object obj) {
if (obj == this)
return true;
if (!(obj instanceof BlockCoords))
return false;
BlockCoords coords = (BlockCoords) obj;
return x == coords.x && y == coords.y && z == coords.z;
}
@Override
public int hashCode() {
return hash;
}
}

View File

@ -0,0 +1,69 @@
package kcauldron;
import net.minecraft.block.Block;
import net.minecraft.world.NextTickListEntry;
public class BlockUpdateEntry extends BlockCoords implements Comparable<BlockUpdateEntry> {
public final int priority;
public final long time;
public final Block block;
public final long id;
private NextTickListEntry mcEntry;
public BlockUpdateEntry(int x, int y, int z, int priority, long time, Block block, long id) {
super(x, y, z);
this.priority = priority;
this.time = time;
this.block = block;
this.id = id;
}
public NextTickListEntry asMCEntry() {
if (mcEntry == null) {
mcEntry = new NextTickListEntry(x, y, z, block);
mcEntry.setPriority(priority);
mcEntry.setScheduledTime(time);
}
return mcEntry;
}
@Override
public int hashCode() {
int hash = super.hashCode();
hash = hash * 31 + priority;
hash = hash * 31 + (int) (time | (time >>> 32));
hash = hash * 31 + Block.getIdFromBlock(block);
return hash;
}
@Override
public boolean equals(Object obj) {
if (!super.equals(obj))
return false;
if (!(obj instanceof BlockUpdateEntry))
return false;
BlockUpdateEntry entry = (BlockUpdateEntry) obj;
if (priority != entry.priority)
return false;
if (time != entry.time)
return false;
if (block != entry.block)
return false;
return true;
}
@Override
public int compareTo(BlockUpdateEntry o) {
if (o == this) return 0;
if (time < o.time)
return -1;
if (time > o.time)
return 1;
int diff = priority - o.priority;
if (diff != 0)
return diff;
if (id < o.id) return -1;
if (id > o.id) return 1;
return 0; // Normally never should happens
}
}

View File

@ -0,0 +1,293 @@
package kcauldron;
import java.util.HashSet;
import java.util.Iterator;
import java.util.NavigableSet;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import gnu.trove.iterator.TLongObjectIterator;
import gnu.trove.map.TLongObjectMap;
import gnu.trove.map.hash.TLongObjectHashMap;
import net.minecraft.block.Block;
import net.minecraft.world.NextTickListEntry;
import net.minecraft.world.WorldServer;
import net.minecraft.world.chunk.Chunk;
public final class BlockUpdatesTracker implements Iterable<BlockUpdateEntry> {
private long lastEntryId = Long.MIN_VALUE;
private final WorldServer world;
private final NavigableSet<BlockUpdateEntry> sortedTree = new TreeSet<BlockUpdateEntry>();
private final TLongObjectMap<BlockUpdateEntry> trackerMap = new TLongObjectHashMap<BlockUpdateEntry>();
private final ReadWriteLock lock = new ReentrantReadWriteLock();
public BlockUpdatesTracker(WorldServer world) {
this.world = world;
}
public BlockUpdateEntry allocateEntry(int x, int y, int z, int priority, long time, Block block) {
return allocateEntry(x, y, z, priority, time, block, true);
}
public BlockUpdateEntry allocateEntry(int x, int y, int z, int priority, long time, Block block,
boolean allowChunkTracking) {
lock.writeLock().lock();
try {
Chunk chunk = allowChunkTracking ? world.getChunkIfLoaded(x >> 4, z >> 4) : null;
BlockUpdateEntry entry = new BlockUpdateEntry(x, y, z, priority, time, block, lastEntryId++);
BlockUpdateEntry oldEntry = trackerMap.remove(entry.key);
if (oldEntry != null) {
sortedTree.remove(oldEntry);
if (chunk != null)
chunk.blockUpdates.remove(oldEntry);
}
trackerMap.put(entry.key, entry);
sortedTree.add(entry);
if (chunk != null)
chunk.blockUpdates.add(entry);
return entry;
} finally {
lock.writeLock().unlock();
}
}
public BlockUpdateEntry removeEntry(int x, int y, int z) {
return removeEntry(x, y, z, true);
}
public BlockUpdateEntry removeEntry(int x, int y, int z, boolean allowChunkTracking) {
lock.writeLock().lock();
try {
Chunk chunk = allowChunkTracking ? world.getChunkIfLoaded(x >> 4, z >> 4) : null;
BlockUpdateEntry entry = trackerMap.remove(new BlockCoords(x, y, z).key);
if (entry != null) {
sortedTree.remove(entry);
if (chunk != null)
chunk.blockUpdates.remove(entry);
}
return entry;
} finally {
lock.writeLock().unlock();
}
}
public BlockUpdateEntry removeEntry(BlockUpdateEntry entry, boolean allowChunkTracking) {
lock.writeLock().lock();
try {
Chunk chunk = allowChunkTracking ? world.getChunkIfLoaded(entry.x >> 4, entry.z >> 4) : null;
trackerMap.remove(entry.key);
if (entry != null) {
sortedTree.remove(entry);
if (chunk != null)
chunk.blockUpdates.remove(entry);
}
return entry;
} finally {
lock.writeLock().unlock();
}
}
public BlockUpdateEntry getEntry(int x, int y, int z) {
lock.readLock().lock();
try {
return trackerMap.get(new BlockCoords(x, y, z).key);
} finally {
lock.readLock().unlock();
}
}
public BlockUpdateEntry peek() {
lock.readLock().lock();
try {
return sortedTree.first();
} finally {
lock.readLock().unlock();
}
}
public boolean isEmpty() {
lock.readLock().lock();
try {
return trackerMap.isEmpty();
} finally {
lock.readLock().unlock();
}
}
public int size() {
lock.readLock().lock();
try {
return trackerMap.size();
} finally {
lock.readLock().unlock();
}
}
@Override
public Iterator<BlockUpdateEntry> iterator() {
return new Iterator<BlockUpdateEntry>() {
private final Iterator<BlockUpdateEntry> iterator = sortedTree.iterator();;
private BlockUpdateEntry next;
@Override
public boolean hasNext() {
next = null;
return iterator.hasNext();
}
@Override
public BlockUpdateEntry next() {
return next = iterator.next();
}
@Override
public void remove() {
if (next == null)
next();
if (next != null)
removeEntry(next, true);
}
};
}
private final VanillaWrapper wrapper = new VanillaWrapper();
public final HashSet<NextTickListEntry> hashSet = new VanillaHashSetWrapper();
public final TreeSet<NextTickListEntry> treeSet = new VanillaTreeSetWrapper();
private final class VanillaWrapper {
private Set<NextTickListEntry> add = new HashSet<NextTickListEntry>();
private Set<NextTickListEntry> remove = new HashSet<NextTickListEntry>();
public boolean add(NextTickListEntry e) {
if (add.remove(e)) {
allocateEntry(e.xCoord, e.yCoord, e.zCoord, e.priority, e.scheduledTime, e.func_151351_a());
return true;
}
return add.add(e);
}
public boolean remove(NextTickListEntry e) {
if (remove.remove(e)) {
removeEntry(e.xCoord, e.yCoord, e.zCoord);
return true;
}
return remove.add(e);
}
public boolean contains(NextTickListEntry e, boolean allowInterscan) {
if (allowInterscan) {
if (add.contains(e))
return true;
if (remove.contains(e))
return false;
}
return getEntry(e.xCoord, e.yCoord, e.zCoord) != null;
}
}
private final class VanillaHashSetWrapper extends HashSet<NextTickListEntry> {
@Override
public Iterator<NextTickListEntry> iterator() {
return new Iterator<NextTickListEntry>() {
private final TLongObjectIterator<BlockUpdateEntry> iterator = trackerMap.iterator();;
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public NextTickListEntry next() {
iterator.advance();
return iterator.value().asMCEntry();
}
@Override
public void remove() {
removeEntry(iterator.value(), true);
}
};
}
@Override
public boolean add(NextTickListEntry e) {
return wrapper.add(e);
}
@Override
public boolean remove(Object o) {
return wrapper.remove((NextTickListEntry) o);
}
@Override
public boolean contains(Object o) {
return wrapper.contains((NextTickListEntry) o, true);
}
@Override
public boolean isEmpty() {
return BlockUpdatesTracker.this.sortedTree.isEmpty();
}
@Override
public int size() {
return BlockUpdatesTracker.this.sortedTree.size();
}
}
private final class VanillaTreeSetWrapper extends TreeSet<NextTickListEntry> {
@Override
public Iterator<NextTickListEntry> iterator() {
return new Iterator<NextTickListEntry>() {
private final Iterator<BlockUpdateEntry> iterator = sortedTree.iterator();;
private BlockUpdateEntry next;
@Override
public boolean hasNext() {
next = null;
return iterator.hasNext();
}
@Override
public NextTickListEntry next() {
return (next = iterator.next()).asMCEntry();
}
@Override
public void remove() {
if (next == null)
next();
if (next != null)
removeEntry(next, true);
}
};
}
@Override
public boolean add(NextTickListEntry e) {
return wrapper.add(e);
}
@Override
public boolean remove(Object o) {
return wrapper.remove((NextTickListEntry) o);
}
@Override
public boolean contains(Object o) {
return wrapper.contains((NextTickListEntry) o, false);
}
@Override
public boolean isEmpty() {
return BlockUpdatesTracker.this.sortedTree.isEmpty();
}
@Override
public int size() {
return BlockUpdatesTracker.this.sortedTree.size();
}
}
}