Implement thread-safe, async-like and not buggy BlockUpdatesTracker
close #320
This commit is contained in:
39
src/main/java/kcauldron/BlockCoords.java
Normal file
39
src/main/java/kcauldron/BlockCoords.java
Normal 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;
|
||||
}
|
||||
}
|
69
src/main/java/kcauldron/BlockUpdateEntry.java
Normal file
69
src/main/java/kcauldron/BlockUpdateEntry.java
Normal 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
|
||||
}
|
||||
}
|
293
src/main/java/kcauldron/BlockUpdatesTracker.java
Normal file
293
src/main/java/kcauldron/BlockUpdatesTracker.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user