From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Sat, 18 May 2019 12:25:19 -0700
Subject: [PATCH] Util patch


diff --git a/src/main/java/com/tuinity/tuinity/chunk/SingleThreadChunkRegionManager.java b/src/main/java/com/tuinity/tuinity/chunk/SingleThreadChunkRegionManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..20150ad0750a648d349701a09b31881be124e46c
--- /dev/null
+++ b/src/main/java/com/tuinity/tuinity/chunk/SingleThreadChunkRegionManager.java
@@ -0,0 +1,488 @@
+package com.tuinity.tuinity.chunk;
+
+import co.aikar.timings.MinecraftTimings;
+import co.aikar.timings.Timing;
+import com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet;
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet;
+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
+import net.minecraft.server.ChunkCoordIntPair;
+import net.minecraft.server.MCUtil;
+import net.minecraft.server.WorldServer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.EnumMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.Function;
+
+public final class SingleThreadChunkRegionManager<T extends Enum<T> & SingleThreadChunkRegionManager.RegionDataCreator<T>> {
+
+    static final int REGION_SECTION_MERGE_RADIUS = 1;
+    // if this becomes > 8, then the RegionSection needs to be properly modified (see bitset)
+    public static final int REGION_CHUNK_SIZE = 32;
+    public static final int REGION_CHUNK_SIZE_SHIFT = 5; // log2(REGION_CHUNK_SIZE)
+
+    public final WorldServer world;
+    public final Class<T> dataClass;
+    public final String name;
+
+    public final Timing addChunkTimings;
+    public final Timing removeChunkTimings;
+    public final Timing regionRecalculateTimings;
+
+    protected final Long2ObjectOpenHashMap<RegionSection<T>> regionsBySection = new Long2ObjectOpenHashMap<>();
+    protected final ReferenceLinkedOpenHashSet<Region<T>> needsRecalculation = new ReferenceLinkedOpenHashSet<>();
+    protected final int minSectionRecalcCount;
+    protected final double maxDeadRegionPercent;
+
+    public SingleThreadChunkRegionManager(final WorldServer world, final Class<T> enumClass,
+                                          final int minSectionRecalcCount, final double maxDeadRegionPercent,
+                                          final String name) {
+        this.world = world;
+        this.dataClass = enumClass;
+        this.name = name;
+        this.minSectionRecalcCount = Math.max(2, minSectionRecalcCount);
+        this.maxDeadRegionPercent = maxDeadRegionPercent;
+
+        String prefix = world.getWorld().getName() + " - Region Manager - " + name + " - ";
+        this.addChunkTimings = MinecraftTimings.getInternalTaskName(prefix.concat("add"));
+        this.removeChunkTimings = MinecraftTimings.getInternalTaskName(prefix.concat("remove"));
+        this.regionRecalculateTimings = MinecraftTimings.getInternalTaskName(prefix.concat("recalculate"));
+    }
+
+    // tested via https://gist.github.com/Spottedleaf/aa7ade3451c37b4cac061fc77074db2f
+
+    /*
+    protected void check() {
+        ReferenceOpenHashSet<Region<T>> checked = new ReferenceOpenHashSet<>();
+
+        for (RegionSection<T> section : this.regionsBySection.values()) {
+            if (!checked.add(section.region)) {
+                section.region.check();
+            }
+        }
+        for (Region<T> region : this.needsRecalculation) {
+            region.check();
+        }
+    }
+    */
+
+    protected void addToRecalcQueue(final Region<T> region) {
+        this.needsRecalculation.add(region);
+    }
+
+    protected void removeFromRecalcQueue(final Region<T> region) {
+        this.needsRecalculation.remove(region);
+    }
+
+    public RegionSection<T> getRegionSection(final int chunkX, final int chunkZ) {
+        return this.regionsBySection.get(MCUtil.getCoordinateKey(chunkX >> REGION_CHUNK_SIZE_SHIFT, chunkZ >> REGION_CHUNK_SIZE_SHIFT));
+    }
+
+    public Region<T> getRegion(final int chunkX, final int chunkZ) {
+        final RegionSection<T> section = this.regionsBySection.get(MCUtil.getCoordinateKey(chunkX >> REGION_CHUNK_SIZE_SHIFT, chunkZ >> REGION_CHUNK_SIZE_SHIFT));
+        return section != null ? section.region : null;
+    }
+
+    private final List<Region<T>> toMerge = new ArrayList<>((2 * REGION_SECTION_MERGE_RADIUS + 1) * (2 * REGION_SECTION_MERGE_RADIUS + 1));
+
+    protected RegionSection<T> getOrCreateAndMergeSection(final int sectionX, final int sectionZ, final RegionSection<T> force) {
+        final long sectionKey = MCUtil.getCoordinateKey(sectionX, sectionZ);
+
+        if (force == null) {
+            RegionSection<T> region = this.regionsBySection.get(sectionKey);
+            if (region != null) {
+                return region;
+            }
+        }
+
+        int mergeCandidateSectionSize = -1;
+        Region<T> mergeIntoCandidate = null;
+
+        // find optimal candidate to merge into
+
+        final int minX = sectionX - REGION_SECTION_MERGE_RADIUS;
+        final int maxX = sectionX + REGION_SECTION_MERGE_RADIUS;
+        final int minZ = sectionZ - REGION_SECTION_MERGE_RADIUS;
+        final int maxZ = sectionZ + REGION_SECTION_MERGE_RADIUS;
+        for (int currX = minX; currX <= maxX; ++currX) {
+            for (int currZ = minZ; currZ <= maxZ; ++currZ) {
+                final RegionSection<T> section = this.regionsBySection.get(MCUtil.getCoordinateKey(currX, currZ));
+                if (section == null) {
+                    continue;
+                }
+                final Region<T> region = section.region;
+                if (region.dead) {
+                    throw new IllegalStateException("Dead region should not be in live region manager state: " + region);
+                }
+                final int sections = region.sections.size();
+
+                if (sections > mergeCandidateSectionSize) {
+                    mergeCandidateSectionSize = sections;
+                    mergeIntoCandidate = region;
+                }
+                this.toMerge.add(region);
+            }
+        }
+
+        // merge
+        if (mergeIntoCandidate != null) {
+            for (int i = 0; i < this.toMerge.size(); ++i) {
+                final Region<T> region = this.toMerge.get(i);
+                if (region.dead || mergeIntoCandidate == region) {
+                    continue;
+                }
+                region.mergeInto(mergeIntoCandidate);
+            }
+            this.toMerge.clear();
+        } else {
+            mergeIntoCandidate = new Region<>(this);
+        }
+
+        final RegionSection<T> section;
+        if (force == null) {
+            this.regionsBySection.put(sectionKey, section = new RegionSection<>(sectionKey, this));
+        } else {
+            final RegionSection<T> existing = this.regionsBySection.putIfAbsent(sectionKey, force);
+            if (existing != null) {
+                throw new IllegalStateException("Attempting to override section '" + existing.toStringWithRegion() +
+                        ", with " + force.toStringWithRegion());
+            }
+
+            section = force;
+        }
+
+        section.region = mergeIntoCandidate;
+        mergeIntoCandidate.sections.add(section);
+        //mergeIntoCandidate.check();
+        //this.check();
+
+        return section;
+    }
+
+    public void addChunk(final int chunkX, final int chunkZ) {
+        this.addChunkTimings.startTiming();
+        try {
+            this.getOrCreateAndMergeSection(chunkX >> REGION_CHUNK_SIZE_SHIFT, chunkZ >> REGION_CHUNK_SIZE_SHIFT, null).addChunk(chunkX, chunkZ);
+        } finally {
+            this.addChunkTimings.stopTiming();
+        }
+    }
+
+    public void removeChunk(final int chunkX, final int chunkZ) {
+        this.removeChunkTimings.startTiming();
+        try {
+            final RegionSection<T> section = this.regionsBySection.get(
+                    MCUtil.getCoordinateKey(chunkX >> REGION_CHUNK_SIZE_SHIFT, chunkZ >> REGION_CHUNK_SIZE_SHIFT)
+            );
+            if (section != null) {
+                section.removeChunk(chunkX, chunkZ);
+            } else {
+                throw new IllegalStateException("Cannot remove chunk at (" + chunkX + "," + chunkZ + ") from region state, section does not exist");
+            }
+        } finally {
+            this.removeChunkTimings.stopTiming();
+        }
+    }
+
+    public void recalculateRegions() {
+        for (int i = 0, len = this.needsRecalculation.size(); i < len; ++i) {
+            final Region<T> region = this.needsRecalculation.removeFirst();
+
+            this.recalculateRegion(region);
+            //this.check();
+        }
+    }
+
+    protected void recalculateRegion(final Region<T> region) {
+        this.regionRecalculateTimings.startTiming();
+        try {
+            region.markedForRecalc = false;
+            //region.check();
+            // clear unused regions
+            for (final Iterator<RegionSection<T>> iterator = region.deadSections.iterator(); iterator.hasNext();) {
+                final RegionSection<T> deadSection = iterator.next();
+
+                if (deadSection.hasChunks()) {
+                    throw new IllegalStateException("Dead section '" + deadSection.toStringWithRegion() + "' is marked dead but has chunks!");
+                }
+                if (!region.sections.remove(deadSection)) {
+                    throw new IllegalStateException("Region " + region + " has inconsistent state, it should contain section " + deadSection);
+                }
+                if (!this.regionsBySection.remove(deadSection.regionCoordinate, deadSection)) {
+                    throw new IllegalStateException("Cannot remove dead section '" +
+                            deadSection.toStringWithRegion() + "' from section state! State at section coordinate: " +
+                            this.regionsBySection.get(deadSection.regionCoordinate));
+                }
+            }
+            region.deadSections.clear();
+
+            // implicitly cover cases where size == 0
+            if (region.sections.size() < this.minSectionRecalcCount) {
+                //region.check();
+                return;
+            }
+
+            // run a test to see if we actually need to recalculate
+            // TODO
+
+            // destroy and rebuild the region
+            region.dead = true;
+
+            // destroy region state
+            for (final Iterator<RegionSection<T>> iterator = region.sections.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) {
+                final RegionSection<T> aliveSection = iterator.next();
+                if (!aliveSection.hasChunks()) {
+                    throw new IllegalStateException("Alive section '" + aliveSection.toStringWithRegion() + "' has no chunks!");
+                }
+                if (!this.regionsBySection.remove(aliveSection.regionCoordinate, aliveSection)) {
+                    throw new IllegalStateException("Cannot remove alive section '" +
+                            aliveSection.toStringWithRegion() + "' from section state! State at section coordinate: " +
+                            this.regionsBySection.get(aliveSection.regionCoordinate));
+                }
+            }
+
+            // rebuild regions
+            for (final Iterator<RegionSection<T>> iterator = region.sections.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) {
+                final RegionSection<T> aliveSection = iterator.next();
+                this.getOrCreateAndMergeSection(aliveSection.getSectionX(), aliveSection.getSectionZ(), aliveSection);
+            }
+        } finally {
+            this.regionRecalculateTimings.stopTiming();
+        }
+    }
+
+    public static final class Region<T extends Enum<T> & SingleThreadChunkRegionManager.RegionDataCreator<T>> {
+        protected final IteratorSafeOrderedReferenceSet<RegionSection<T>> sections = new IteratorSafeOrderedReferenceSet<>(true);
+        protected final ReferenceOpenHashSet<RegionSection<T>> deadSections = new ReferenceOpenHashSet<>(16, 0.7f);
+        protected boolean dead;
+        protected boolean markedForRecalc;
+
+        public final SingleThreadChunkRegionManager<T> regionManager;
+
+        protected Region(final SingleThreadChunkRegionManager<T> regionManager) {
+            this.regionManager = regionManager;
+        }
+
+        public IteratorSafeOrderedReferenceSet.Iterator<RegionSection<T>> getSections() {
+            return this.sections.iterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS);
+        }
+
+        protected final double getDeadSectionPercent() {
+            return (double)this.deadSections.size() / (double)this.sections.size();
+        }
+
+        /*
+        protected void check() {
+            if (this.dead) {
+                throw new IllegalStateException("Dead region!");
+            }
+            for (final Iterator<RegionSection<T>> iterator = this.sections.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) {
+                final RegionSection<T> section = iterator.next();
+                if (section.region != this) {
+                    throw new IllegalStateException("Region section must point to us!");
+                }
+                if (this.regionManager.regionsBySection.get(section.regionCoordinate) != section) {
+                    throw new IllegalStateException("Region section must match the regionmanager state!");
+                }
+            }
+        }
+        */
+
+        protected void mergeInto(final Region<T> mergeTarget) {
+            if (this == mergeTarget) {
+                throw new IllegalStateException("Cannot merge a region onto itself");
+            }
+            if (this.dead) {
+                throw new IllegalStateException("Source region is dead! Source " + this + ", target " + mergeTarget);
+            } else if (mergeTarget.dead) {
+                throw new IllegalStateException("Target region is dead! Source " + this + ", target " + mergeTarget);
+            }
+            this.dead = true;
+            if (this.markedForRecalc) {
+                this.regionManager.removeFromRecalcQueue(this);
+            }
+
+            for (final Iterator<RegionSection<T>> iterator = this.sections.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) {
+                final RegionSection<T> section = iterator.next();
+
+                if (!mergeTarget.sections.add(section)) {
+                    throw new IllegalStateException("Target cannot contain source's sections! Source " + this + ", target " + mergeTarget);
+                }
+
+                section.region = mergeTarget;
+            }
+
+            for (final RegionSection<T> deadSection : this.deadSections) {
+                if (!this.sections.contains(deadSection)) {
+                    throw new IllegalStateException("Source region does not even contain its own dead sections! Missing " + deadSection + " from region " + this);
+                }
+                mergeTarget.deadSections.add(deadSection);
+            }
+            //mergeTarget.check();
+        }
+
+        protected void markSectionAlive(final RegionSection<T> section) {
+            this.deadSections.remove(section);
+            if (this.markedForRecalc && (this.sections.size() < this.regionManager.minSectionRecalcCount || this.getDeadSectionPercent() < this.regionManager.maxDeadRegionPercent)) {
+                this.regionManager.removeFromRecalcQueue(this);
+                this.markedForRecalc = false;
+            }
+        }
+
+        protected void markSectionDead(final RegionSection<T> section) {
+            this.deadSections.add(section);
+            if (!this.markedForRecalc && (this.sections.size() >= this.regionManager.minSectionRecalcCount || this.sections.size() == this.deadSections.size()) && this.getDeadSectionPercent() >= this.regionManager.maxDeadRegionPercent) {
+                this.regionManager.addToRecalcQueue(this);
+                this.markedForRecalc = true;
+            }
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder ret = new StringBuilder(128);
+
+            ret.append("Region{");
+            ret.append("dead=").append(this.dead).append(',');
+            ret.append("markedForRecalc=").append(this.markedForRecalc).append(',');
+
+            ret.append("sectionCount=").append(this.sections.size()).append(',');
+            ret.append("sections=[");
+            for (final Iterator<RegionSection<T>> iterator = this.sections.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) {
+                final RegionSection<T> section = iterator.next();
+                ret.append(section);
+                if (iterator.hasNext()) {
+                    ret.append(',');
+                }
+            }
+            ret.append(']');
+
+            ret.append('}');
+            return ret.toString();
+        }
+    }
+
+    public static final class RegionSection<T extends Enum<T> & SingleThreadChunkRegionManager.RegionDataCreator<T>> {
+        protected final long regionCoordinate;
+        protected final long[] chunksBitset = new long[Math.max(1, REGION_CHUNK_SIZE * REGION_CHUNK_SIZE / Long.SIZE)];
+        protected int chunkCount;
+        protected Region<T> region;
+        protected final EnumMap<T, Object> data;
+        protected final Function<? super T, Object> createIfAbsentFunction;
+
+        public final SingleThreadChunkRegionManager<T> regionManager;
+
+        protected RegionSection(final long regionCoordinate, final SingleThreadChunkRegionManager<T> regionManager) {
+            this.regionCoordinate = regionCoordinate;
+            this.data = new EnumMap<>(regionManager.dataClass);
+            this.regionManager = regionManager;
+            this.createIfAbsentFunction = (final T keyInMap) -> {
+                return keyInMap.createData(RegionSection.this, regionManager);
+            };
+        }
+
+        public int getSectionX() {
+            return MCUtil.getCoordinateX(this.regionCoordinate);
+        }
+
+        public int getSectionZ() {
+            return MCUtil.getCoordinateZ(this.regionCoordinate);
+        }
+
+        public Region<T> getRegion() {
+            return this.region;
+        }
+
+        public Object getData(final T key) {
+            return this.data.get(key);
+        }
+
+        public Object getOrCreateData(final T key) {
+            return this.data.computeIfAbsent(key, this.createIfAbsentFunction);
+        }
+
+        public Object removeData(final T key) {
+            return this.data.remove(key);
+        }
+
+        public void setData(final T key, final Object data) {
+            this.data.put(key, data);
+        }
+
+        private static int getChunkIndex(final int chunkX, final int chunkZ) {
+            return (chunkX & (REGION_CHUNK_SIZE - 1)) | ((chunkZ & (REGION_CHUNK_SIZE - 1)) << REGION_CHUNK_SIZE_SHIFT);
+        }
+
+        protected boolean hasChunks() {
+            return this.chunkCount != 0;
+        }
+
+        protected void addChunk(final int chunkX, final int chunkZ) {
+            final int index = getChunkIndex(chunkX, chunkZ);
+            final long bitset = this.chunksBitset[index >>> 6]; // index / Long.SIZE
+            final long after = this.chunksBitset[index >>> 6] = bitset | (1L << (index & (Long.SIZE - 1)));
+            if (after == bitset) {
+                throw new IllegalStateException("Cannot add a chunk to a section which already has the chunk! RegionSection: " + this + ", global chunk: " + new ChunkCoordIntPair(chunkX, chunkZ).toString());
+            }
+            if (++this.chunkCount != 1) {
+                return;
+            }
+            this.region.markSectionAlive(this);
+        }
+
+        protected void removeChunk(final int chunkX, final int chunkZ) {
+            final int index = getChunkIndex(chunkX, chunkZ);
+            final long before = this.chunksBitset[index >>> 6]; // index / Long.SIZE
+            final long bitset = this.chunksBitset[index >>> 6] = before & ~(1L << (index & (Long.SIZE - 1)));
+            if (before == bitset) {
+                throw new IllegalStateException("Cannot remove a chunk from a section which does not have that chunk! RegionSection: " + this + ", global chunk: " + new ChunkCoordIntPair(chunkX, chunkZ).toString());
+            }
+            if (--this.chunkCount != 0) {
+                return;
+            }
+            this.region.markSectionDead(this);
+        }
+
+        @Override
+        public String toString() {
+            return "RegionSection{" +
+                    "regionCoordinate=" + new ChunkCoordIntPair(this.regionCoordinate).toString() + "," +
+                    "chunkCount=" + this.chunkCount + "," +
+                    "chunksBitset=" + toString(this.chunksBitset) + "," +
+                    "hash=" + this.hashCode() +
+                    "}";
+        }
+
+        public String toStringWithRegion() {
+            return "RegionSection{" +
+                    "regionCoordinate=" + new ChunkCoordIntPair(this.regionCoordinate).toString() + "," +
+                    "chunkCount=" + this.chunkCount + "," +
+                    "chunksBitset=" + toString(this.chunksBitset) + "," +
+                    "hash=" + this.hashCode() + "," +
+                    "region=" + this.region +
+                    "}";
+        }
+
+        private static String toString(final long[] array) {
+            StringBuilder ret = new StringBuilder();
+            for (long value : array) {
+                // zero pad the hex string
+                char[] zeros = new char[Long.SIZE / 4];
+                Arrays.fill(zeros, '0');
+                String string = Long.toHexString(value);
+                System.arraycopy(string.toCharArray(), 0, zeros, zeros.length - string.length(), string.length());
+
+                ret.append(zeros);
+            }
+
+            return ret.toString();
+        }
+    }
+
+    public static interface RegionDataCreator<E extends Enum<E> & RegionDataCreator<E>> {
+
+        Object createData(final RegionSection<E> section,
+                          final SingleThreadChunkRegionManager<E> regionManager);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/tuinity/tuinity/util/IntervalledCounter.java b/src/main/java/com/tuinity/tuinity/util/IntervalledCounter.java
new file mode 100644
index 0000000000000000000000000000000000000000..d2c7d2c7920324d7207225ed19484e804368489d
--- /dev/null
+++ b/src/main/java/com/tuinity/tuinity/util/IntervalledCounter.java
@@ -0,0 +1,100 @@
+package com.tuinity.tuinity.util;
+
+public final class IntervalledCounter {
+
+    protected long[] times;
+    protected final long interval;
+    protected long minTime;
+    protected int sum;
+    protected int head; // inclusive
+    protected int tail; // exclusive
+
+    public IntervalledCounter(final long interval) {
+        this.times = new long[8];
+        this.interval = interval;
+    }
+
+    public void updateCurrentTime() {
+        this.updateCurrentTime(System.nanoTime());
+    }
+
+    public void updateCurrentTime(final long currentTime) {
+        int sum = this.sum;
+        int head = this.head;
+        final int tail = this.tail;
+        final long minTime = currentTime - this.interval;
+
+        final int arrayLen = this.times.length;
+
+        // guard against overflow by using subtraction
+        while (head != tail && this.times[head] - minTime < 0) {
+            head = (head + 1) % arrayLen;
+            --sum;
+        }
+
+        this.sum = sum;
+        this.head = head;
+        this.minTime = minTime;
+    }
+
+    public void addTime(final long currTime) {
+        // guard against overflow by using subtraction
+        if (currTime - this.minTime < 0) {
+            return;
+        }
+        int nextTail = (this.tail + 1) % this.times.length;
+        if (nextTail == this.head) {
+            this.resize();
+            nextTail = (this.tail + 1) % this.times.length;
+        }
+
+        this.times[this.tail] = currTime;
+        this.tail = nextTail;
+    }
+
+    public void updateAndAdd(final int count) {
+        final long currTime = System.nanoTime();
+        this.updateCurrentTime(currTime);
+        for (int i = 0; i < count; ++i) {
+            this.addTime(currTime);
+        }
+    }
+
+    public void updateAndAdd(final int count, final long currTime) {
+        this.updateCurrentTime(currTime);
+        for (int i = 0; i < count; ++i) {
+            this.addTime(currTime);
+        }
+    }
+
+    private void resize() {
+        final long[] oldElements = this.times;
+        final long[] newElements = new long[this.times.length * 2];
+        this.times = newElements;
+
+        final int head = this.head;
+        final int tail = this.tail;
+        final int size = tail >= head ? (tail - head) : (tail + (oldElements.length - head));
+        this.head = 0;
+        this.tail = size;
+
+        if (tail >= head) {
+            System.arraycopy(oldElements, head, newElements, 0, size);
+        } else {
+            System.arraycopy(oldElements, head, newElements, 0, oldElements.length - head);
+            System.arraycopy(oldElements, 0, newElements, oldElements.length - head, tail);
+        }
+    }
+
+    // returns in units per second
+    public double getRate() {
+        return this.size() / (this.interval * 1.0e-9);
+    }
+
+    public int size() {
+        final int head = this.head;
+        final int tail = this.tail;
+
+        return tail >= head ? (tail - head) : (tail + (this.times.length - head));
+    }
+}
diff --git a/src/main/java/com/tuinity/tuinity/util/maplist/IteratorSafeOrderedReferenceSet.java b/src/main/java/com/tuinity/tuinity/util/maplist/IteratorSafeOrderedReferenceSet.java
new file mode 100644
index 0000000000000000000000000000000000000000..be408aebbccbda46e8aa82ef337574137cfa0096
--- /dev/null
+++ b/src/main/java/com/tuinity/tuinity/util/maplist/IteratorSafeOrderedReferenceSet.java
@@ -0,0 +1,335 @@
+package com.tuinity.tuinity.util.maplist;
+
+import it.unimi.dsi.fastutil.objects.Reference2IntLinkedOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
+import org.bukkit.Bukkit;
+import java.util.Arrays;
+import java.util.NoSuchElementException;
+
+public final class IteratorSafeOrderedReferenceSet<E> {
+
+    public static final int ITERATOR_FLAG_SEE_ADDITIONS = 1 << 0;
+
+    protected final Reference2IntLinkedOpenHashMap<E> indexMap;
+    protected int firstInvalidIndex = -1;
+
+    /* list impl */
+    protected E[] listElements;
+    protected int listSize;
+
+    protected final double maxFragFactor;
+
+    protected int iteratorCount;
+
+    private final boolean threadRestricted;
+
+    public IteratorSafeOrderedReferenceSet() {
+        this(16, 0.75f, 16, 0.2);
+    }
+
+    public IteratorSafeOrderedReferenceSet(final boolean threadRestricted) {
+        this(16, 0.75f, 16, 0.2, threadRestricted);
+    }
+
+    public IteratorSafeOrderedReferenceSet(final int setCapacity, final float setLoadFactor, final int arrayCapacity,
+                                           final double maxFragFactor) {
+        this(setCapacity, setLoadFactor, arrayCapacity, maxFragFactor, false);
+    }
+    public IteratorSafeOrderedReferenceSet(final int setCapacity, final float setLoadFactor, final int arrayCapacity,
+                                           final double maxFragFactor, final boolean threadRestricted) {
+        this.indexMap = new Reference2IntLinkedOpenHashMap<>(setCapacity, setLoadFactor);
+        this.indexMap.defaultReturnValue(-1);
+        this.maxFragFactor = maxFragFactor;
+        this.listElements = (E[])new Object[arrayCapacity];
+        this.threadRestricted = threadRestricted;
+    }
+
+    /*
+    public void check() {
+        int iterated = 0;
+        ReferenceOpenHashSet<E> check = new ReferenceOpenHashSet<>();
+        if (this.listElements != null) {
+            for (int i = 0; i < this.listSize; ++i) {
+                Object obj = this.listElements[i];
+                if (obj != null) {
+                    iterated++;
+                    if (!check.add((E)obj)) {
+                        throw new IllegalStateException("contains duplicate");
+                    }
+                    if (!this.contains((E)obj)) {
+                        throw new IllegalStateException("desync");
+                    }
+                }
+            }
+        }
+
+        if (iterated != this.size()) {
+            throw new IllegalStateException("Size is mismatched! Got " + iterated + ", expected " + this.size());
+        }
+
+        check.clear();
+        iterated = 0;
+        for (final java.util.Iterator<E> iterator = this.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) {
+            final E element = iterator.next();
+            iterated++;
+            if (!check.add(element)) {
+                throw new IllegalStateException("contains duplicate (iterator is wrong)");
+            }
+            if (!this.contains(element)) {
+                throw new IllegalStateException("desync (iterator is wrong)");
+            }
+        }
+
+        if (iterated != this.size()) {
+            throw new IllegalStateException("Size is mismatched! (iterator is wrong) Got " + iterated + ", expected " + this.size());
+        }
+    }
+    */
+
+    protected final boolean allowSafeIteration() {
+        return !this.threadRestricted || Bukkit.isPrimaryThread();
+    }
+
+    protected final double getFragFactor() {
+        return 1.0 - ((double)this.indexMap.size() / (double)this.listSize);
+    }
+
+    public int createRawIterator() {
+        if (this.allowSafeIteration()) {
+            ++this.iteratorCount;
+        }
+        if (this.indexMap.isEmpty()) {
+            return -1;
+        } else {
+            return this.firstInvalidIndex == 0 ? this.indexMap.getInt(this.indexMap.firstKey()) : 0;
+        }
+    }
+
+    public int advanceRawIterator(final int index) {
+        final E[] elements = this.listElements;
+        int ret = index + 1;
+        for (int len = this.listSize; ret < len; ++ret) {
+            if (elements[ret] != null) {
+                return ret;
+            }
+        }
+
+        return -1;
+    }
+
+    public void finishRawIterator() {
+        if (this.allowSafeIteration() && --this.iteratorCount == 0) {
+            if (this.getFragFactor() >= this.maxFragFactor) {
+                this.defrag();
+            }
+        }
+    }
+
+    public boolean remove(final E element) {
+        final int index = this.indexMap.removeInt(element);
+        if (index >= 0) {
+            if (this.firstInvalidIndex < 0 || index < this.firstInvalidIndex) {
+                this.firstInvalidIndex = index;
+            }
+            if (this.listElements[index] != element) {
+                throw new IllegalStateException();
+            }
+            this.listElements[index] = null;
+            if (this.allowSafeIteration() && this.iteratorCount == 0 && this.getFragFactor() >= this.maxFragFactor) {
+                this.defrag();
+            }
+            //this.check();
+            return true;
+        }
+        return false;
+    }
+
+    public boolean contains(final E element) {
+        return this.indexMap.containsKey(element);
+    }
+
+    public boolean add(final E element) {
+        final int listSize = this.listSize;
+
+        final int previous = this.indexMap.putIfAbsent(element, listSize);
+        if (previous != -1) {
+            return false;
+        }
+
+        if (listSize >= this.listElements.length) {
+            this.listElements = Arrays.copyOf(this.listElements, listSize * 2);
+        }
+        this.listElements[listSize] = element;
+        this.listSize = listSize + 1;
+
+        //this.check();
+        return true;
+    }
+
+    protected void defrag() {
+        if (this.firstInvalidIndex < 0) {
+            return; // nothing to do
+        }
+
+        if (this.indexMap.isEmpty()) {
+            Arrays.fill(this.listElements, 0, this.listSize, null);
+            this.listSize = 0;
+            this.firstInvalidIndex = -1;
+            //this.check();
+            return;
+        }
+
+        final E[] backingArray = this.listElements;
+
+        int lastValidIndex;
+        java.util.Iterator<Reference2IntMap.Entry<E>> iterator;
+
+        if (this.firstInvalidIndex == 0) {
+            iterator = this.indexMap.reference2IntEntrySet().fastIterator();
+            lastValidIndex = 0;
+        } else {
+            lastValidIndex = this.firstInvalidIndex;
+            final E key = backingArray[lastValidIndex - 1];
+            iterator = this.indexMap.reference2IntEntrySet().fastIterator(new Reference2IntMap.Entry<E>() {
+                @Override
+                public int getIntValue() {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public int setValue(int i) {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public E getKey() {
+                    return key;
+                }
+            });
+        }
+
+        while (iterator.hasNext()) {
+            final Reference2IntMap.Entry<E> entry = iterator.next();
+
+            final int newIndex = lastValidIndex++;
+            backingArray[newIndex] = entry.getKey();
+            entry.setValue(newIndex);
+        }
+
+        // cleanup end
+        Arrays.fill(backingArray, lastValidIndex, this.listSize, null);
+        this.listSize = lastValidIndex;
+        this.firstInvalidIndex = -1;
+        //this.check();
+    }
+
+    public E rawGet(final int index) {
+        return this.listElements[index];
+    }
+
+    public int size() {
+        // always returns the correct amount - listSize can be different
+        return this.indexMap.size();
+    }
+
+    public IteratorSafeOrderedReferenceSet.Iterator<E> iterator() {
+        return this.iterator(0);
+    }
+
+    public IteratorSafeOrderedReferenceSet.Iterator<E> iterator(final int flags) {
+        if (this.allowSafeIteration()) {
+            ++this.iteratorCount;
+        }
+        return new BaseIterator<>(this, true, (flags & ITERATOR_FLAG_SEE_ADDITIONS) != 0 ? Integer.MAX_VALUE : this.listSize);
+    }
+
+    public java.util.Iterator<E> unsafeIterator() {
+        return this.unsafeIterator(0);
+    }
+    public java.util.Iterator<E> unsafeIterator(final int flags) {
+        return new BaseIterator<>(this, false, (flags & ITERATOR_FLAG_SEE_ADDITIONS) != 0 ? Integer.MAX_VALUE : this.listSize);
+    }
+
+    public static interface Iterator<E> extends java.util.Iterator<E> {
+
+        public void finishedIterating();
+
+    }
+
+    protected static final class BaseIterator<E> implements IteratorSafeOrderedReferenceSet.Iterator<E> {
+
+        protected final IteratorSafeOrderedReferenceSet<E> set;
+        protected final boolean canFinish;
+        protected final int maxIndex;
+        protected int nextIndex;
+        protected E pendingValue;
+        protected boolean finished;
+        protected E lastReturned;
+
+        protected BaseIterator(final IteratorSafeOrderedReferenceSet<E> set, final boolean canFinish, final int maxIndex) {
+            this.set = set;
+            this.canFinish = canFinish;
+            this.maxIndex = maxIndex;
+        }
+
+        @Override
+        public boolean hasNext() {
+            if (this.finished) {
+                return false;
+            }
+            if (this.pendingValue != null) {
+                return true;
+            }
+
+            final E[] elements = this.set.listElements;
+            int index, len;
+            for (index = this.nextIndex, len = Math.min(this.maxIndex, this.set.listSize); index < len; ++index) {
+                final E element = elements[index];
+                if (element != null) {
+                    this.pendingValue = element;
+                    this.nextIndex = index + 1;
+                    return true;
+                }
+            }
+
+            this.nextIndex = index;
+            return false;
+        }
+
+        @Override
+        public E next() {
+            if (!this.hasNext()) {
+                throw new NoSuchElementException();
+            }
+            final E ret = this.pendingValue;
+
+            this.pendingValue = null;
+            this.lastReturned = ret;
+
+            return ret;
+        }
+
+        @Override
+        public void remove() {
+            final E lastReturned = this.lastReturned;
+            if (lastReturned == null) {
+                throw new IllegalStateException();
+            }
+            this.lastReturned = null;
+            this.set.remove(lastReturned);
+        }
+
+        @Override
+        public void finishedIterating() {
+            if (this.finished || !this.canFinish) {
+                throw new IllegalStateException();
+            }
+            this.lastReturned = null;
+            this.finished = true;
+            if (this.set.allowSafeIteration()) {
+                this.set.finishRawIterator();
+            }
+        }
+    }
+}
diff --git a/src/main/java/com/tuinity/tuinity/util/misc/Delayed26WayDistancePropagator3D.java b/src/main/java/com/tuinity/tuinity/util/misc/Delayed26WayDistancePropagator3D.java
new file mode 100644
index 0000000000000000000000000000000000000000..155d10994f2d7df9ac927d955d99016fe304360f
--- /dev/null
+++ b/src/main/java/com/tuinity/tuinity/util/misc/Delayed26WayDistancePropagator3D.java
@@ -0,0 +1,295 @@
+package com.tuinity.tuinity.util.misc;
+
+import it.unimi.dsi.fastutil.HashCommon;
+import it.unimi.dsi.fastutil.bytes.ByteArrayFIFOQueue;
+import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap;
+import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue;
+import it.unimi.dsi.fastutil.longs.LongIterator;
+import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet;
+import net.minecraft.server.MCUtil;
+
+public final class Delayed26WayDistancePropagator3D {
+
+    // this map is considered "stale" unless updates are propagated.
+    protected final Delayed8WayDistancePropagator2D.LevelMap levels = new Delayed8WayDistancePropagator2D.LevelMap(8192*2, 0.6f);
+
+    // this map is never stale
+    protected final Long2ByteOpenHashMap sources = new Long2ByteOpenHashMap(4096, 0.6f);
+
+    // Generally updates to positions are made close to other updates, so we link to decrease cache misses when
+    // propagating updates
+    protected final LongLinkedOpenHashSet updatedSources = new LongLinkedOpenHashSet();
+
+    @FunctionalInterface
+    public static interface LevelChangeCallback {
+
+        /**
+         * This can be called for intermediate updates. So do not rely on newLevel being close to or
+         * the exact level that is expected after a full propagation has occured.
+         */
+        public void onLevelUpdate(final long coordinate, final byte oldLevel, final byte newLevel);
+
+    }
+
+    protected final LevelChangeCallback changeCallback;
+
+    public Delayed26WayDistancePropagator3D() {
+        this(null);
+    }
+
+    public Delayed26WayDistancePropagator3D(final LevelChangeCallback changeCallback) {
+        this.changeCallback = changeCallback;
+    }
+
+    public int getLevel(final long pos) {
+        return this.levels.get(pos);
+    }
+
+    public int getLevel(final int x, final int y, final int z) {
+        return this.levels.get(MCUtil.getSectionKey(x, y, z));
+    }
+
+    public void setSource(final int x, final int y, final int z, final int level) {
+        this.setSource(MCUtil.getSectionKey(x, y, z), level);
+    }
+
+    public void setSource(final long coordinate, final int level) {
+        if ((level & 63) != level || level == 0) {
+            throw new IllegalArgumentException("Level must be in (0, 63], not " + level);
+        }
+
+        final byte byteLevel = (byte)level;
+        final byte oldLevel = this.sources.put(coordinate, byteLevel);
+
+        if (oldLevel == byteLevel) {
+            return; // nothing to do
+        }
+
+        // queue to update later
+        this.updatedSources.add(coordinate);
+    }
+
+    public void removeSource(final int x, final int y, final int z) {
+        this.removeSource(MCUtil.getSectionKey(x, y, z));
+    }
+
+    public void removeSource(final long coordinate) {
+        if (this.sources.remove(coordinate) != 0) {
+            this.updatedSources.add(coordinate);
+        }
+    }
+
+    // queues used for BFS propagating levels
+    protected final Delayed8WayDistancePropagator2D.WorkQueue[] levelIncreaseWorkQueues = new Delayed8WayDistancePropagator2D.WorkQueue[64];
+    {
+        for (int i = 0; i < this.levelIncreaseWorkQueues.length; ++i) {
+            this.levelIncreaseWorkQueues[i] = new Delayed8WayDistancePropagator2D.WorkQueue();
+        }
+    }
+    protected final Delayed8WayDistancePropagator2D.WorkQueue[] levelRemoveWorkQueues = new Delayed8WayDistancePropagator2D.WorkQueue[64];
+    {
+        for (int i = 0; i < this.levelRemoveWorkQueues.length; ++i) {
+            this.levelRemoveWorkQueues[i] = new Delayed8WayDistancePropagator2D.WorkQueue();
+        }
+    }
+    protected long levelIncreaseWorkQueueBitset;
+    protected long levelRemoveWorkQueueBitset;
+
+    protected final void addToIncreaseWorkQueue(final long coordinate, final byte level) {
+        final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelIncreaseWorkQueues[level];
+        queue.queuedCoordinates.enqueue(coordinate);
+        queue.queuedLevels.enqueue(level);
+
+        this.levelIncreaseWorkQueueBitset |= (1L << level);
+    }
+
+    protected final void addToIncreaseWorkQueue(final long coordinate, final byte index, final byte level) {
+        final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelIncreaseWorkQueues[index];
+        queue.queuedCoordinates.enqueue(coordinate);
+        queue.queuedLevels.enqueue(level);
+
+        this.levelIncreaseWorkQueueBitset |= (1L << index);
+    }
+
+    protected final void addToRemoveWorkQueue(final long coordinate, final byte level) {
+        final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelRemoveWorkQueues[level];
+        queue.queuedCoordinates.enqueue(coordinate);
+        queue.queuedLevels.enqueue(level);
+
+        this.levelRemoveWorkQueueBitset |= (1L << level);
+    }
+
+    public void propagateUpdates() {
+        if (this.updatedSources.isEmpty()) {
+            return;
+        }
+
+        for (final LongIterator iterator = this.updatedSources.iterator(); iterator.hasNext();) {
+            final long coordinate = iterator.nextLong();
+
+            final byte currentLevel = this.levels.get(coordinate);
+            final byte updatedSource = this.sources.get(coordinate);
+
+            if (currentLevel == updatedSource) {
+                continue;
+            }
+
+            if (updatedSource > currentLevel) {
+                // level increase
+                this.addToIncreaseWorkQueue(coordinate, updatedSource);
+            } else {
+                // level decrease
+                this.addToRemoveWorkQueue(coordinate, currentLevel);
+                // if the current coordinate is a source, then the decrease propagation will detect that and queue
+                // the source propagation
+            }
+        }
+
+        this.updatedSources.clear();
+
+        // propagate source level increases first for performance reasons (in crowded areas hopefully the additions
+        // make the removes remove less)
+        this.propagateIncreases();
+
+        // now we propagate the decreases (which will then re-propagate clobbered sources)
+        this.propagateDecreases();
+    }
+
+    protected void propagateIncreases() {
+        for (int queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelIncreaseWorkQueueBitset);
+             this.levelIncreaseWorkQueueBitset != 0L;
+             this.levelIncreaseWorkQueueBitset ^= (1L << queueIndex), queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelIncreaseWorkQueueBitset)) {
+
+            final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelIncreaseWorkQueues[queueIndex];
+            while (!queue.queuedLevels.isEmpty()) {
+                final long coordinate = queue.queuedCoordinates.removeFirstLong();
+                byte level = queue.queuedLevels.removeFirstByte();
+
+                final boolean neighbourCheck = level < 0;
+
+                final byte currentLevel;
+                if (neighbourCheck) {
+                    level = (byte)-level;
+                    currentLevel = this.levels.get(coordinate);
+                } else {
+                    currentLevel = this.levels.putIfGreater(coordinate, level);
+                }
+
+                if (neighbourCheck) {
+                    // used when propagating from decrease to indicate that this level needs to check its neighbours
+                    // this means the level at coordinate could be equal, but would still need neighbours checked
+
+                    if (currentLevel != level) {
+                        // something caused the level to change, which means something propagated to it (which means
+                        // us propagating here is redundant), or something removed the level (which means we
+                        // cannot propagate further)
+                        continue;
+                    }
+                } else if (currentLevel >= level) {
+                    // something higher/equal propagated
+                    continue;
+                }
+                if (this.changeCallback != null) {
+                    this.changeCallback.onLevelUpdate(coordinate, currentLevel, level);
+                }
+
+                if (level == 1) {
+                    // can't propagate 0 to neighbours
+                    continue;
+                }
+
+                // propagate to neighbours
+                final byte neighbourLevel = (byte)(level - 1);
+                final int x = MCUtil.getSectionX(coordinate);
+                final int y = MCUtil.getSectionY(coordinate);
+                final int z = MCUtil.getSectionZ(coordinate);
+
+                for (int dy = -1; dy <= 1; ++dy) {
+                    for (int dz = -1; dz <= 1; ++dz) {
+                        for (int dx = -1; dx <= 1; ++dx) {
+                            if ((dy | dz | dx) == 0) {
+                                // already propagated to coordinate
+                                continue;
+                            }
+
+                            // sure we can check the neighbour level in the map right now and avoid a propagation,
+                            // but then we would still have to recheck it when popping the value off of the queue!
+                            // so just avoid the double lookup
+                            final long neighbourCoordinate = MCUtil.getSectionKey(dx + x, dy + y, dz + z);
+                            this.addToIncreaseWorkQueue(neighbourCoordinate, neighbourLevel);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    protected void propagateDecreases() {
+        for (int queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelRemoveWorkQueueBitset);
+             this.levelRemoveWorkQueueBitset != 0L;
+             this.levelRemoveWorkQueueBitset ^= (1L << queueIndex), queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelRemoveWorkQueueBitset)) {
+
+            final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelRemoveWorkQueues[queueIndex];
+            while (!queue.queuedLevels.isEmpty()) {
+                final long coordinate = queue.queuedCoordinates.removeFirstLong();
+                final byte level = queue.queuedLevels.removeFirstByte();
+
+                final byte currentLevel = this.levels.removeIfGreaterOrEqual(coordinate, level);
+                if (currentLevel == 0) {
+                    // something else removed
+                    continue;
+                }
+
+                if (currentLevel > level) {
+                    // something higher propagated here or we hit the propagation of another source
+                    // in the second case we need to re-propagate because we could have just clobbered another source's
+                    // propagation
+                    this.addToIncreaseWorkQueue(coordinate, currentLevel, (byte)-currentLevel); // indicate to the increase code that the level's neighbours need checking
+                    continue;
+                }
+
+                if (this.changeCallback != null) {
+                    this.changeCallback.onLevelUpdate(coordinate, currentLevel, (byte)0);
+                }
+
+                final byte source = this.sources.get(coordinate);
+                if (source != 0) {
+                    // must re-propagate source later
+                    this.addToIncreaseWorkQueue(coordinate, source);
+                }
+
+                if (level == 0) {
+                    // can't propagate -1 to neighbours
+                    // we have to check neighbours for removing 1 just in case the neighbour is 2
+                    continue;
+                }
+
+                // propagate to neighbours
+                final byte neighbourLevel = (byte)(level - 1);
+                final int x = MCUtil.getSectionX(coordinate);
+                final int y = MCUtil.getSectionY(coordinate);
+                final int z = MCUtil.getSectionZ(coordinate);
+
+                for (int dy = -1; dy <= 1; ++dy) {
+                    for (int dz = -1; dz <= 1; ++dz) {
+                        for (int dx = -1; dx <= 1; ++dx) {
+                            if ((dy | dz | dx) == 0) {
+                                // already propagated to coordinate
+                                continue;
+                            }
+
+                            // sure we can check the neighbour level in the map right now and avoid a propagation,
+                            // but then we would still have to recheck it when popping the value off of the queue!
+                            // so just avoid the double lookup
+                            final long neighbourCoordinate = MCUtil.getSectionKey(dx + x, dy + y, dz + z);
+                            this.addToRemoveWorkQueue(neighbourCoordinate, neighbourLevel);
+                        }
+                    }
+                }
+            }
+        }
+
+        // propagate sources we clobbered in the process
+        this.propagateIncreases();
+    }
+}
diff --git a/src/main/java/com/tuinity/tuinity/util/misc/Delayed8WayDistancePropagator2D.java b/src/main/java/com/tuinity/tuinity/util/misc/Delayed8WayDistancePropagator2D.java
new file mode 100644
index 0000000000000000000000000000000000000000..606417a8aeaca2682595f417bba8e9d411999da9
--- /dev/null
+++ b/src/main/java/com/tuinity/tuinity/util/misc/Delayed8WayDistancePropagator2D.java
@@ -0,0 +1,713 @@
+package com.tuinity.tuinity.util.misc;
+
+import it.unimi.dsi.fastutil.HashCommon;
+import it.unimi.dsi.fastutil.bytes.ByteArrayFIFOQueue;
+import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap;
+import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue;
+import it.unimi.dsi.fastutil.longs.LongIterator;
+import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet;
+import net.minecraft.server.MCUtil;
+
+public final class Delayed8WayDistancePropagator2D {
+
+    // Test
+    /*
+    protected static void test(int x, int z, com.destroystokyo.paper.util.misc.DistanceTrackingAreaMap<Ticket> reference, Delayed8WayDistancePropagator2D test) {
+        int got = test.getLevel(x, z);
+
+        int expect = 0;
+        Object[] nearest = reference.getObjectsInRange(x, z) == null ? null : reference.getObjectsInRange(x, z).getBackingSet();
+        if (nearest != null) {
+            for (Object _obj : nearest) {
+                if (_obj instanceof Ticket) {
+                    Ticket ticket = (Ticket)_obj;
+                    long ticketCoord = reference.getLastCoordinate(ticket);
+                    int viewDistance = reference.getLastViewDistance(ticket);
+                    int distance = Math.max(com.destroystokyo.paper.util.math.IntegerUtil.branchlessAbs(MCUtil.getCoordinateX(ticketCoord) - x),
+                            com.destroystokyo.paper.util.math.IntegerUtil.branchlessAbs(MCUtil.getCoordinateZ(ticketCoord) - z));
+                    int level = viewDistance - distance;
+                    if (level > expect) {
+                        expect = level;
+                    }
+                }
+            }
+        }
+
+        if (expect != got) {
+            throw new IllegalStateException("Expected " + expect + " at pos (" + x + "," + z + ") but got " + got);
+        }
+    }
+
+    static class Ticket {
+
+        int x;
+        int z;
+
+        final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<Ticket> empty
+                = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<>(this);
+
+    }
+
+    public static void main(final String[] args) {
+        com.destroystokyo.paper.util.misc.DistanceTrackingAreaMap<Ticket> reference = new com.destroystokyo.paper.util.misc.DistanceTrackingAreaMap<Ticket>() {
+            @Override
+            protected com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<Ticket> getEmptySetFor(Ticket object) {
+                return object.empty;
+            }
+        };
+        Delayed8WayDistancePropagator2D test = new Delayed8WayDistancePropagator2D();
+
+        final int maxDistance = 64;
+        // test origin
+        {
+            Ticket originTicket = new Ticket();
+            int originDistance = 31;
+            // test single source
+            reference.add(originTicket, 0, 0, originDistance);
+            test.setSource(0, 0, originDistance); test.propagateUpdates(); // set and propagate
+            for (int dx = -originDistance; dx <= originDistance; ++dx) {
+                for (int dz = -originDistance; dz <= originDistance; ++dz) {
+                    test(dx, dz, reference, test);
+                }
+            }
+            // test single source decrease
+            reference.update(originTicket, 0, 0, originDistance/2);
+            test.setSource(0, 0, originDistance/2); test.propagateUpdates(); // set and propagate
+            for (int dx = -originDistance; dx <= originDistance; ++dx) {
+                for (int dz = -originDistance; dz <= originDistance; ++dz) {
+                    test(dx, dz, reference, test);
+                }
+            }
+            // test source increase
+            originDistance = 2*originDistance;
+            reference.update(originTicket, 0, 0, originDistance);
+            test.setSource(0, 0, originDistance); test.propagateUpdates(); // set and propagate
+            for (int dx = -4*originDistance; dx <= 4*originDistance; ++dx) {
+                for (int dz = -4*originDistance; dz <= 4*originDistance; ++dz) {
+                    test(dx, dz, reference, test);
+                }
+            }
+
+            reference.remove(originTicket);
+            test.removeSource(0, 0); test.propagateUpdates();
+        }
+
+        // test multiple sources at origin
+        {
+            int originDistance = 31;
+            java.util.List<Ticket> list = new java.util.ArrayList<>();
+            for (int i = 0; i < 10; ++i) {
+                Ticket a = new Ticket();
+                list.add(a);
+                a.x = (i & 1) == 1 ? -i : i;
+                a.z = (i & 1) == 1 ? -i : i;
+            }
+            for (Ticket ticket : list) {
+                reference.add(ticket, ticket.x, ticket.z, originDistance);
+                test.setSource(ticket.x, ticket.z, originDistance);
+            }
+            test.propagateUpdates();
+
+            for (int dx = -8*originDistance; dx <= 8*originDistance; ++dx) {
+                for (int dz = -8*originDistance; dz <= 8*originDistance; ++dz) {
+                    test(dx, dz, reference, test);
+                }
+            }
+
+            // test ticket level decrease
+
+            for (Ticket ticket : list) {
+                reference.update(ticket, ticket.x, ticket.z, originDistance/2);
+                test.setSource(ticket.x, ticket.z, originDistance/2);
+            }
+            test.propagateUpdates();
+
+            for (int dx = -8*originDistance; dx <= 8*originDistance; ++dx) {
+                for (int dz = -8*originDistance; dz <= 8*originDistance; ++dz) {
+                    test(dx, dz, reference, test);
+                }
+            }
+
+            // test ticket level increase
+
+            for (Ticket ticket : list) {
+                reference.update(ticket, ticket.x, ticket.z, originDistance*2);
+                test.setSource(ticket.x, ticket.z, originDistance*2);
+            }
+            test.propagateUpdates();
+
+            for (int dx = -16*originDistance; dx <= 16*originDistance; ++dx) {
+                for (int dz = -16*originDistance; dz <= 16*originDistance; ++dz) {
+                    test(dx, dz, reference, test);
+                }
+            }
+
+            // test ticket remove
+            for (int i = 0, len = list.size(); i < len; ++i) {
+                if ((i & 3) != 0) {
+                    continue;
+                }
+                Ticket ticket = list.get(i);
+                reference.remove(ticket);
+                test.removeSource(ticket.x, ticket.z);
+            }
+            test.propagateUpdates();
+
+            for (int dx = -16*originDistance; dx <= 16*originDistance; ++dx) {
+                for (int dz = -16*originDistance; dz <= 16*originDistance; ++dz) {
+                    test(dx, dz, reference, test);
+                }
+            }
+        }
+
+        // now test at coordinate offsets
+        // test offset
+        {
+            Ticket originTicket = new Ticket();
+            int originDistance = 31;
+            int offX = 54432;
+            int offZ = -134567;
+            // test single source
+            reference.add(originTicket, offX, offZ, originDistance);
+            test.setSource(offX, offZ, originDistance); test.propagateUpdates(); // set and propagate
+            for (int dx = -originDistance; dx <= originDistance; ++dx) {
+                for (int dz = -originDistance; dz <= originDistance; ++dz) {
+                    test(dx + offX, dz + offZ, reference, test);
+                }
+            }
+            // test single source decrease
+            reference.update(originTicket, offX, offZ, originDistance/2);
+            test.setSource(offX, offZ, originDistance/2); test.propagateUpdates(); // set and propagate
+            for (int dx = -originDistance; dx <= originDistance; ++dx) {
+                for (int dz = -originDistance; dz <= originDistance; ++dz) {
+                    test(dx + offX, dz + offZ, reference, test);
+                }
+            }
+            // test source increase
+            originDistance = 2*originDistance;
+            reference.update(originTicket, offX, offZ, originDistance);
+            test.setSource(offX, offZ, originDistance); test.propagateUpdates(); // set and propagate
+            for (int dx = -4*originDistance; dx <= 4*originDistance; ++dx) {
+                for (int dz = -4*originDistance; dz <= 4*originDistance; ++dz) {
+                    test(dx + offX, dz + offZ, reference, test);
+                }
+            }
+
+            reference.remove(originTicket);
+            test.removeSource(offX, offZ); test.propagateUpdates();
+        }
+
+        // test multiple sources at origin
+        {
+            int originDistance = 31;
+            int offX = 54432;
+            int offZ = -134567;
+            java.util.List<Ticket> list = new java.util.ArrayList<>();
+            for (int i = 0; i < 10; ++i) {
+                Ticket a = new Ticket();
+                list.add(a);
+                a.x = offX + ((i & 1) == 1 ? -i : i);
+                a.z = offZ + ((i & 1) == 1 ? -i : i);
+            }
+            for (Ticket ticket : list) {
+                reference.add(ticket, ticket.x, ticket.z, originDistance);
+                test.setSource(ticket.x, ticket.z, originDistance);
+            }
+            test.propagateUpdates();
+
+            for (int dx = -8*originDistance; dx <= 8*originDistance; ++dx) {
+                for (int dz = -8*originDistance; dz <= 8*originDistance; ++dz) {
+                    test(dx, dz, reference, test);
+                }
+            }
+
+            // test ticket level decrease
+
+            for (Ticket ticket : list) {
+                reference.update(ticket, ticket.x, ticket.z, originDistance/2);
+                test.setSource(ticket.x, ticket.z, originDistance/2);
+            }
+            test.propagateUpdates();
+
+            for (int dx = -8*originDistance; dx <= 8*originDistance; ++dx) {
+                for (int dz = -8*originDistance; dz <= 8*originDistance; ++dz) {
+                    test(dx, dz, reference, test);
+                }
+            }
+
+            // test ticket level increase
+
+            for (Ticket ticket : list) {
+                reference.update(ticket, ticket.x, ticket.z, originDistance*2);
+                test.setSource(ticket.x, ticket.z, originDistance*2);
+            }
+            test.propagateUpdates();
+
+            for (int dx = -16*originDistance; dx <= 16*originDistance; ++dx) {
+                for (int dz = -16*originDistance; dz <= 16*originDistance; ++dz) {
+                    test(dx, dz, reference, test);
+                }
+            }
+
+            // test ticket remove
+            for (int i = 0, len = list.size(); i < len; ++i) {
+                if ((i & 3) != 0) {
+                    continue;
+                }
+                Ticket ticket = list.get(i);
+                reference.remove(ticket);
+                test.removeSource(ticket.x, ticket.z);
+            }
+            test.propagateUpdates();
+
+            for (int dx = -16*originDistance; dx <= 16*originDistance; ++dx) {
+                for (int dz = -16*originDistance; dz <= 16*originDistance; ++dz) {
+                    test(dx, dz, reference, test);
+                }
+            }
+        }
+    }
+     */
+
+    // this map is considered "stale" unless updates are propagated.
+    protected final LevelMap levels = new LevelMap(8192*2, 0.6f);
+
+    // this map is never stale
+    protected final Long2ByteOpenHashMap sources = new Long2ByteOpenHashMap(4096, 0.6f);
+
+    // Generally updates to positions are made close to other updates, so we link to decrease cache misses when
+    // propagating updates
+    protected final LongLinkedOpenHashSet updatedSources = new LongLinkedOpenHashSet();
+
+    @FunctionalInterface
+    public static interface LevelChangeCallback {
+
+        /**
+         * This can be called for intermediate updates. So do not rely on newLevel being close to or
+         * the exact level that is expected after a full propagation has occured.
+         */
+        public void onLevelUpdate(final long coordinate, final byte oldLevel, final byte newLevel);
+
+    }
+
+    protected final LevelChangeCallback changeCallback;
+
+    public Delayed8WayDistancePropagator2D() {
+        this(null);
+    }
+
+    public Delayed8WayDistancePropagator2D(final LevelChangeCallback changeCallback) {
+        this.changeCallback = changeCallback;
+    }
+
+    public int getLevel(final long pos) {
+        return this.levels.get(pos);
+    }
+
+    public int getLevel(final int x, final int z) {
+        return this.levels.get(MCUtil.getCoordinateKey(x, z));
+    }
+
+    public void setSource(final int x, final int z, final int level) {
+        this.setSource(MCUtil.getCoordinateKey(x, z), level);
+    }
+
+    public void setSource(final long coordinate, final int level) {
+        if ((level & 63) != level || level == 0) {
+            throw new IllegalArgumentException("Level must be in (0, 63], not " + level);
+        }
+
+        final byte byteLevel = (byte)level;
+        final byte oldLevel = this.sources.put(coordinate, byteLevel);
+
+        if (oldLevel == byteLevel) {
+            return; // nothing to do
+        }
+
+        // queue to update later
+        this.updatedSources.add(coordinate);
+    }
+
+    public void removeSource(final int x, final int z) {
+        this.removeSource(MCUtil.getCoordinateKey(x, z));
+    }
+
+    public void removeSource(final long coordinate) {
+        if (this.sources.remove(coordinate) != 0) {
+            this.updatedSources.add(coordinate);
+        }
+    }
+
+    // queues used for BFS propagating levels
+    protected final WorkQueue[] levelIncreaseWorkQueues = new WorkQueue[64];
+    {
+        for (int i = 0; i < this.levelIncreaseWorkQueues.length; ++i) {
+            this.levelIncreaseWorkQueues[i] = new WorkQueue();
+        }
+    }
+    protected final WorkQueue[] levelRemoveWorkQueues = new WorkQueue[64];
+    {
+        for (int i = 0; i < this.levelRemoveWorkQueues.length; ++i) {
+            this.levelRemoveWorkQueues[i] = new WorkQueue();
+        }
+    }
+    protected long levelIncreaseWorkQueueBitset;
+    protected long levelRemoveWorkQueueBitset;
+
+    protected final void addToIncreaseWorkQueue(final long coordinate, final byte level) {
+        final WorkQueue queue = this.levelIncreaseWorkQueues[level];
+        queue.queuedCoordinates.enqueue(coordinate);
+        queue.queuedLevels.enqueue(level);
+
+        this.levelIncreaseWorkQueueBitset |= (1L << level);
+    }
+
+    protected final void addToIncreaseWorkQueue(final long coordinate, final byte index, final byte level) {
+        final WorkQueue queue = this.levelIncreaseWorkQueues[index];
+        queue.queuedCoordinates.enqueue(coordinate);
+        queue.queuedLevels.enqueue(level);
+
+        this.levelIncreaseWorkQueueBitset |= (1L << index);
+    }
+
+    protected final void addToRemoveWorkQueue(final long coordinate, final byte level) {
+        final WorkQueue queue = this.levelRemoveWorkQueues[level];
+        queue.queuedCoordinates.enqueue(coordinate);
+        queue.queuedLevels.enqueue(level);
+
+        this.levelRemoveWorkQueueBitset |= (1L << level);
+    }
+
+    public void propagateUpdates() {
+        if (this.updatedSources.isEmpty()) {
+            return;
+        }
+
+        for (final LongIterator iterator = this.updatedSources.iterator(); iterator.hasNext();) {
+            final long coordinate = iterator.nextLong();
+
+            final byte currentLevel = this.levels.get(coordinate);
+            final byte updatedSource = this.sources.get(coordinate);
+
+            if (currentLevel == updatedSource) {
+                continue;
+            }
+
+            if (updatedSource > currentLevel) {
+                // level increase
+                this.addToIncreaseWorkQueue(coordinate, updatedSource);
+            } else {
+                // level decrease
+                this.addToRemoveWorkQueue(coordinate, currentLevel);
+                // if the current coordinate is a source, then the decrease propagation will detect that and queue
+                // the source propagation
+            }
+        }
+
+        this.updatedSources.clear();
+
+        // propagate source level increases first for performance reasons (in crowded areas hopefully the additions
+        // make the removes remove less)
+        this.propagateIncreases();
+
+        // now we propagate the decreases (which will then re-propagate clobbered sources)
+        this.propagateDecreases();
+    }
+
+    protected void propagateIncreases() {
+        for (int queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelIncreaseWorkQueueBitset);
+             this.levelIncreaseWorkQueueBitset != 0L;
+             this.levelIncreaseWorkQueueBitset ^= (1L << queueIndex), queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelIncreaseWorkQueueBitset)) {
+
+            final WorkQueue queue = this.levelIncreaseWorkQueues[queueIndex];
+            while (!queue.queuedLevels.isEmpty()) {
+                final long coordinate = queue.queuedCoordinates.removeFirstLong();
+                byte level = queue.queuedLevels.removeFirstByte();
+
+                final boolean neighbourCheck = level < 0;
+
+                final byte currentLevel;
+                if (neighbourCheck) {
+                    level = (byte)-level;
+                    currentLevel = this.levels.get(coordinate);
+                } else {
+                    currentLevel = this.levels.putIfGreater(coordinate, level);
+                }
+
+                if (neighbourCheck) {
+                    // used when propagating from decrease to indicate that this level needs to check its neighbours
+                    // this means the level at coordinate could be equal, but would still need neighbours checked
+
+                    if (currentLevel != level) {
+                        // something caused the level to change, which means something propagated to it (which means
+                        // us propagating here is redundant), or something removed the level (which means we
+                        // cannot propagate further)
+                        continue;
+                    }
+                } else if (currentLevel >= level) {
+                    // something higher/equal propagated
+                    continue;
+                }
+                if (this.changeCallback != null) {
+                    this.changeCallback.onLevelUpdate(coordinate, currentLevel, level);
+                }
+
+                if (level == 1) {
+                    // can't propagate 0 to neighbours
+                    continue;
+                }
+
+                // propagate to neighbours
+                final byte neighbourLevel = (byte)(level - 1);
+                final int x = (int)coordinate;
+                final int z = (int)(coordinate >>> 32);
+
+                for (int dx = -1; dx <= 1; ++dx) {
+                    for (int dz = -1; dz <= 1; ++dz) {
+                        if ((dx | dz) == 0) {
+                            // already propagated to coordinate
+                            continue;
+                        }
+
+                        // sure we can check the neighbour level in the map right now and avoid a propagation,
+                        // but then we would still have to recheck it when popping the value off of the queue!
+                        // so just avoid the double lookup
+                        final long neighbourCoordinate = MCUtil.getCoordinateKey(x + dx, z + dz);
+                        this.addToIncreaseWorkQueue(neighbourCoordinate, neighbourLevel);
+                    }
+                }
+            }
+        }
+    }
+
+    protected void propagateDecreases() {
+        for (int queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelRemoveWorkQueueBitset);
+             this.levelRemoveWorkQueueBitset != 0L;
+             this.levelRemoveWorkQueueBitset ^= (1L << queueIndex), queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelRemoveWorkQueueBitset)) {
+
+            final WorkQueue queue = this.levelRemoveWorkQueues[queueIndex];
+            while (!queue.queuedLevels.isEmpty()) {
+                final long coordinate = queue.queuedCoordinates.removeFirstLong();
+                final byte level = queue.queuedLevels.removeFirstByte();
+
+                final byte currentLevel = this.levels.removeIfGreaterOrEqual(coordinate, level);
+                if (currentLevel == 0) {
+                    // something else removed
+                    continue;
+                }
+
+                if (currentLevel > level) {
+                    // something higher propagated here or we hit the propagation of another source
+                    // in the second case we need to re-propagate because we could have just clobbered another source's
+                    // propagation
+                    this.addToIncreaseWorkQueue(coordinate, currentLevel, (byte)-currentLevel); // indicate to the increase code that the level's neighbours need checking
+                    continue;
+                }
+
+                if (this.changeCallback != null) {
+                    this.changeCallback.onLevelUpdate(coordinate, currentLevel, (byte)0);
+                }
+
+                final byte source = this.sources.get(coordinate);
+                if (source != 0) {
+                    // must re-propagate source later
+                    this.addToIncreaseWorkQueue(coordinate, source);
+                }
+
+                if (level == 0) {
+                    // can't propagate -1 to neighbours
+                    // we have to check neighbours for removing 1 just in case the neighbour is 2
+                    continue;
+                }
+
+                // propagate to neighbours
+                final byte neighbourLevel = (byte)(level - 1);
+                final int x = (int)coordinate;
+                final int z = (int)(coordinate >>> 32);
+
+                for (int dx = -1; dx <= 1; ++dx) {
+                    for (int dz = -1; dz <= 1; ++dz) {
+                        if ((dx | dz) == 0) {
+                            // already propagated to coordinate
+                            continue;
+                        }
+
+                        // sure we can check the neighbour level in the map right now and avoid a propagation,
+                        // but then we would still have to recheck it when popping the value off of the queue!
+                        // so just avoid the double lookup
+                        final long neighbourCoordinate = MCUtil.getCoordinateKey(x + dx, z + dz);
+                        this.addToRemoveWorkQueue(neighbourCoordinate, neighbourLevel);
+                    }
+                }
+            }
+        }
+
+        // propagate sources we clobbered in the process
+        this.propagateIncreases();
+    }
+
+    protected static final class LevelMap extends Long2ByteOpenHashMap {
+        public LevelMap() {
+            super();
+        }
+
+        public LevelMap(final int expected, final float loadFactor) {
+            super(expected, loadFactor);
+        }
+
+        // copied from superclass
+        private int find(final long k) {
+            if (k == 0L) {
+                return this.containsNullKey ? this.n : -(this.n + 1);
+            } else {
+                final long[] key = this.key;
+                long curr;
+                int pos;
+                if ((curr = key[pos = (int)HashCommon.mix(k) & this.mask]) == 0L) {
+                    return -(pos + 1);
+                } else if (k == curr) {
+                    return pos;
+                } else {
+                    while((curr = key[pos = pos + 1 & this.mask]) != 0L) {
+                        if (k == curr) {
+                            return pos;
+                        }
+                    }
+
+                    return -(pos + 1);
+                }
+            }
+        }
+
+        // copied from superclass
+        private void insert(final int pos, final long k, final byte v) {
+            if (pos == this.n) {
+                this.containsNullKey = true;
+            }
+
+            this.key[pos] = k;
+            this.value[pos] = v;
+            if (this.size++ >= this.maxFill) {
+                this.rehash(HashCommon.arraySize(this.size + 1, this.f));
+            }
+        }
+
+        // copied from superclass
+        public byte putIfGreater(final long key, final byte value) {
+            final int pos = this.find(key);
+            if (pos < 0) {
+                if (this.defRetValue < value) {
+                    this.insert(-pos - 1, key, value);
+                }
+                return this.defRetValue;
+            } else {
+                final byte curr = this.value[pos];
+                if (value > curr) {
+                    this.value[pos] = value;
+                    return curr;
+                }
+                return curr;
+            }
+        }
+
+        // copied from superclass
+        private void removeEntry(final int pos) {
+            --this.size;
+            this.shiftKeys(pos);
+            if (this.n > this.minN && this.size < this.maxFill / 4 && this.n > 16) {
+                this.rehash(this.n / 2);
+            }
+        }
+
+        // copied from superclass
+        private void removeNullEntry() {
+            this.containsNullKey = false;
+            --this.size;
+            if (this.n > this.minN && this.size < this.maxFill / 4 && this.n > 16) {
+                this.rehash(this.n / 2);
+            }
+        }
+
+        // copied from superclass
+        public byte removeIfGreaterOrEqual(final long key, final byte value) {
+            if (key == 0L) {
+                if (!this.containsNullKey) {
+                    return this.defRetValue;
+                }
+                final byte current = this.value[this.n];
+                if (value >= current) {
+                    this.removeNullEntry();
+                    return current;
+                }
+                return current;
+            } else {
+                long[] keys = this.key;
+                byte[] values = this.value;
+                long curr;
+                int pos;
+                if ((curr = keys[pos = (int)HashCommon.mix(key) & this.mask]) == 0L) {
+                    return this.defRetValue;
+                } else if (key == curr) {
+                    final byte current = values[pos];
+                    if (value >= current) {
+                        this.removeEntry(pos);
+                        return current;
+                    }
+                    return current;
+                } else {
+                    while((curr = keys[pos = pos + 1 & this.mask]) != 0L) {
+                        if (key == curr) {
+                            final byte current = values[pos];
+                            if (value >= current) {
+                                this.removeEntry(pos);
+                                return current;
+                            }
+                            return current;
+                        }
+                    }
+
+                    return this.defRetValue;
+                }
+            }
+        }
+    }
+
+    protected static final class WorkQueue {
+
+        public final NoResizeLongArrayFIFODeque queuedCoordinates = new NoResizeLongArrayFIFODeque();
+        public final NoResizeByteArrayFIFODeque queuedLevels = new NoResizeByteArrayFIFODeque();
+
+    }
+
+    protected static final class NoResizeLongArrayFIFODeque extends LongArrayFIFOQueue {
+
+        /**
+         * Assumes non-empty. If empty, undefined behaviour.
+         */
+        public long removeFirstLong() {
+            // copied from superclass
+            long t = this.array[this.start];
+            if (++this.start == this.length) {
+                this.start = 0;
+            }
+
+            return t;
+        }
+    }
+
+    protected static final class NoResizeByteArrayFIFODeque extends ByteArrayFIFOQueue {
+
+        /**
+         * Assumes non-empty. If empty, undefined behaviour.
+         */
+        public byte removeFirstByte() {
+            // copied from superclass
+            byte t = this.array[this.start];
+            if (++this.start == this.length) {
+                this.start = 0;
+            }
+
+            return t;
+        }
+    }
+}
diff --git a/src/main/java/net/minecraft/server/BlockBase.java b/src/main/java/net/minecraft/server/BlockBase.java
index 1f334d63282bd5c23dc3b275a220f09e60c34537..85f60b56b5689b77ba3d9e99e29b4f734df5d91e 100644
--- a/src/main/java/net/minecraft/server/BlockBase.java
+++ b/src/main/java/net/minecraft/server/BlockBase.java
@@ -295,14 +295,14 @@ public abstract class BlockBase {
 
     public abstract static class BlockData extends IBlockDataHolder<Block, IBlockData> {
 
-        private final int b;
-        private final boolean e;
+        private final int b; public final int getEmittedLight() { return this.b; } // Tuinity - OBFHELPER
+        private final boolean e; public final boolean isTransparentOnSomeFaces() { return this.e; } // Tuinity - OBFHELPER
         private final boolean f;
         private final Material g;
         private final MaterialMapColor h;
         public final float strength;
         private final boolean j;
-        private final boolean k;
+        private final boolean k; public final boolean isOpaque() { return this.k; } // Tuinity - OBFHELPER
         private final BlockBase.e l;
         private final BlockBase.e m;
         private final BlockBase.e n;
@@ -343,12 +343,20 @@ public abstract class BlockBase {
         protected Fluid fluid;
         // Paper end
 
+        // Tuinity start - micro the hell out of this call
+        protected boolean shapeExceedsCube = true;
+        public final boolean shapeExceedsCube() {
+            return this.shapeExceedsCube;
+        }
+        // Tuinity end
+
         public void a() {
             this.fluid = this.getBlock().d(this.p()); // Paper - moved from getFluid()
             this.isTicking = this.getBlock().isTicking(this.p()); // Paper - moved from isTicking()
             if (!this.getBlock().o()) {
                 this.a = new BlockBase.BlockData.Cache(this.p());
             }
+            this.shapeExceedsCube = this.a == null || this.a.c; // Tuinity - moved from actual method to here
 
         }
 
@@ -372,10 +380,12 @@ public abstract class BlockBase {
             return this.a != null ? this.a.g : this.getBlock().b(this.p(), iblockaccess, blockposition);
         }
 
+        public final int getOpacity(IBlockAccess iblockaccess, BlockPosition blockposition) { return this.b(iblockaccess, blockposition); } // Tuinity - OBFHELPER
         public int b(IBlockAccess iblockaccess, BlockPosition blockposition) {
             return this.a != null ? this.a.h : this.getBlock().f(this.p(), iblockaccess, blockposition);
         }
 
+        public final VoxelShape getCullingFace(IBlockAccess iblockaccess, BlockPosition blockposition, EnumDirection enumdirection) { return this.a(iblockaccess, blockposition, enumdirection); } // Tuinity - OBFHELPER
         public VoxelShape a(IBlockAccess iblockaccess, BlockPosition blockposition, EnumDirection enumdirection) {
             return this.a != null && this.a.i != null ? this.a.i[enumdirection.ordinal()] : VoxelShapes.a(this.c(iblockaccess, blockposition), enumdirection);
         }
@@ -385,7 +395,7 @@ public abstract class BlockBase {
         }
 
         public final boolean d() { // Paper
-            return this.a == null || this.a.c;
+            return this.shapeExceedsCube; // Tuinity - moved into shape cache init
         }
 
         public final boolean e() { // Paper
diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
index 0ea1b25bf98d71c251351807fa92d4d08eb6b06e..c8c0748628e83b6396c13afe896acc16c85109b0 100644
--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
@@ -112,7 +112,7 @@ public class ChunkProviderServer extends IChunkProvider {
         return (Chunk)this.getChunkAt(x, z, ChunkStatus.FULL, true);
     }
 
-    private long chunkFutureAwaitCounter;
+    long chunkFutureAwaitCounter; // Tuinity - private -> package private
 
     public void getEntityTickingChunkAsync(int x, int z, java.util.function.Consumer<Chunk> onLoad) {
         if (Thread.currentThread() != this.serverThread) {
@@ -201,6 +201,165 @@ public class ChunkProviderServer extends IChunkProvider {
     }
     // Paper end - rewrite ticklistserver
 
+    // Tuinity start
+    // this will try to avoid chunk neighbours for lighting
+    public final IChunkAccess getFullStatusChunkAt(int chunkX, int chunkZ) {
+        Chunk ifLoaded = this.getChunkAtIfLoadedImmediately(chunkX, chunkZ);
+        if (ifLoaded != null) {
+            return ifLoaded;
+        }
+
+        IChunkAccess empty = this.getChunkAt(chunkX, chunkZ, ChunkStatus.EMPTY, true);
+        if (empty != null && empty.getChunkStatus() == ChunkStatus.FULL) {
+            return empty;
+        }
+        return this.getChunkAt(chunkX, chunkZ, ChunkStatus.FULL, true);
+    }
+
+    public final IChunkAccess getFullStatusChunkAtIfLoaded(int chunkX, int chunkZ) {
+        Chunk ifLoaded = this.getChunkAtIfLoadedImmediately(chunkX, chunkZ);
+        if (ifLoaded != null) {
+            return ifLoaded;
+        }
+
+        IChunkAccess ret = this.getChunkAtImmediately(chunkX, chunkZ);
+        if (ret != null && ret.getChunkStatus() == ChunkStatus.FULL) {
+            return ret;
+        } else {
+            return null;
+        }
+    }
+
+    void getChunkAtAsynchronously(int chunkX, int chunkZ, int ticketLevel,
+                                  java.util.function.Consumer<IChunkAccess> consumer) {
+        this.getChunkAtAsynchronously(chunkX, chunkZ, ticketLevel, (PlayerChunk playerChunk) -> {
+            if (ticketLevel <= 33) {
+                return (CompletableFuture)playerChunk.getFullChunkFuture();
+            } else {
+                return playerChunk.getOrCreateFuture(PlayerChunk.getChunkStatus(ticketLevel), ChunkProviderServer.this.playerChunkMap);
+            }
+        }, consumer);
+    }
+
+    void getChunkAtAsynchronously(int chunkX, int chunkZ, int ticketLevel,
+                                  java.util.function.Function<PlayerChunk, CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>>> function,
+                                  java.util.function.Consumer<IChunkAccess> consumer) {
+        if (Thread.currentThread() != this.serverThread) {
+            throw new IllegalStateException();
+        }
+        ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(chunkX, chunkZ);
+        Long identifier = Long.valueOf(this.chunkFutureAwaitCounter++);
+        this.addTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, identifier);
+        this.tickDistanceManager();
+
+        PlayerChunk chunk = this.playerChunkMap.getUpdatingChunk(chunkPos.pair());
+
+        if (chunk == null) {
+            throw new IllegalStateException("Expected playerchunk " + chunkPos + " in world '" + this.world.getWorld().getName() + "'");
+        }
+
+        CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> future = function.apply(chunk);
+
+        future.whenCompleteAsync((either, throwable) -> {
+            try {
+                if (throwable != null) {
+                    if (throwable instanceof ThreadDeath) {
+                        throw (ThreadDeath)throwable;
+                    }
+                    MinecraftServer.LOGGER.fatal("Failed to complete future await for chunk " + chunkPos.toString() + " in world '" + ChunkProviderServer.this.world.getWorld().getName() + "'", throwable);
+                } else if (either.right().isPresent()) {
+                    MinecraftServer.LOGGER.fatal("Failed to complete future await for chunk " + chunkPos.toString() + " in world '" + ChunkProviderServer.this.world.getWorld().getName() + "': " + either.right().get().toString());
+                }
+
+                try {
+                    if (consumer != null) {
+                        consumer.accept(either == null ? null : either.left().orElse(null)); // indicate failure to the callback.
+                    }
+                } catch (Throwable thr) {
+                    if (thr instanceof ThreadDeath) {
+                        throw (ThreadDeath)thr;
+                    }
+                    MinecraftServer.LOGGER.fatal("Load callback for future await failed " + chunkPos.toString() + " in world '" + ChunkProviderServer.this.world.getWorld().getName() + "'", thr);
+                    return;
+                }
+            } finally {
+                // due to odd behaviour with CB unload implementation we need to have these AFTER the load callback.
+                ChunkProviderServer.this.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, ticketLevel, chunkPos);
+                ChunkProviderServer.this.removeTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, identifier);
+            }
+        }, this.serverThreadQueue);
+    }
+
+    void chunkLoadAccept(int chunkX, int chunkZ, IChunkAccess chunk, java.util.function.Consumer<IChunkAccess> consumer) {
+        try {
+            consumer.accept(chunk);
+        } catch (Throwable throwable) {
+            if (throwable instanceof ThreadDeath) {
+                throw (ThreadDeath)throwable;
+            }
+            MinecraftServer.LOGGER.error("Load callback for chunk " + chunkX + "," + chunkZ + " in world '" + this.world.getWorld().getName() + "' threw an exception", throwable);
+        }
+    }
+
+    public final void getChunkAtAsynchronously(int chunkX, int chunkZ, ChunkStatus status, boolean gen, boolean allowSubTicketLevel, java.util.function.Consumer<IChunkAccess> onLoad) {
+        // try to fire sync
+        int chunkStatusTicketLevel = 33 + ChunkStatus.getTicketLevelOffset(status);
+        PlayerChunk playerChunk = this.playerChunkMap.getUpdatingChunk(MCUtil.getCoordinateKey(chunkX, chunkZ));
+        if (playerChunk != null) {
+            ChunkStatus holderStatus = playerChunk.getChunkHolderStatus();
+            IChunkAccess immediate = playerChunk.getAvailableChunkNow();
+            if (immediate != null) {
+                if (allowSubTicketLevel ? immediate.getChunkStatus().isAtLeastStatus(status) : (playerChunk.getTicketLevel() <= chunkStatusTicketLevel && holderStatus != null && holderStatus.isAtLeastStatus(status))) {
+                    this.chunkLoadAccept(chunkX, chunkZ, immediate, onLoad);
+                    return;
+                } else {
+                    if (gen || (!allowSubTicketLevel && immediate.getChunkStatus().isAtLeastStatus(status))) {
+                        this.getChunkAtAsynchronously(chunkX, chunkZ, chunkStatusTicketLevel, onLoad);
+                        return;
+                    } else {
+                        this.chunkLoadAccept(chunkX, chunkZ, null, onLoad);
+                        return;
+                    }
+                }
+            }
+        }
+
+        // need to fire async
+
+        if (gen && !allowSubTicketLevel) {
+            this.getChunkAtAsynchronously(chunkX, chunkZ, chunkStatusTicketLevel, onLoad);
+            return;
+        }
+
+        this.getChunkAtAsynchronously(chunkX, chunkZ, MCUtil.getTicketLevelFor(ChunkStatus.EMPTY), (IChunkAccess chunk) -> {
+            if (chunk == null) {
+                throw new IllegalStateException("Chunk cannot be null");
+            }
+
+            if (!chunk.getChunkStatus().isAtLeastStatus(status)) {
+                if (gen) {
+                    this.getChunkAtAsynchronously(chunkX, chunkZ, chunkStatusTicketLevel, onLoad);
+                    return;
+                } else {
+                    ChunkProviderServer.this.chunkLoadAccept(chunkX, chunkZ, null, onLoad);
+                    return;
+                }
+            } else {
+                if (allowSubTicketLevel) {
+                    ChunkProviderServer.this.chunkLoadAccept(chunkX, chunkZ, chunk, onLoad);
+                    return;
+                } else {
+                    this.getChunkAtAsynchronously(chunkX, chunkZ, chunkStatusTicketLevel, onLoad);
+                    return;
+                }
+            }
+        });
+    }
+
+    final com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<Chunk> tickingChunks = new com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<>(4096, 0.75f, 4096, 0.15, true);
+    final com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<Chunk> entityTickingChunks = new com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<>(4096, 0.75f, 4096, 0.15, true);
+    // Tuinity end
+
     public ChunkProviderServer(WorldServer worldserver, Convertable.ConversionSession convertable_conversionsession, DataFixer datafixer, DefinedStructureManager definedstructuremanager, Executor executor, ChunkGenerator chunkgenerator, int i, boolean flag, WorldLoadListener worldloadlistener, Supplier<WorldPersistentData> supplier) {
         this.world = worldserver;
         this.serverThreadQueue = new ChunkProviderServer.a(worldserver);
@@ -599,8 +758,8 @@ public class ChunkProviderServer extends IChunkProvider {
         return !this.a(playerchunk, k);
     }
 
-    @Override
-    public IBlockAccess c(int i, int j) {
+    public final IBlockAccess getFeaturesReadyChunk(int x, int z) { return this.c(x, z); } // Tuinity - OBFHELPER
+    @Override public IBlockAccess c(int i, int j) { // Tuinity - OBFHELPER
         long k = ChunkCoordIntPair.pair(i, j);
         PlayerChunk playerchunk = this.getChunk(k);
 
diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java
index 534238688be75c53058dcfeabcdf86d9c0504089..6eee9c858dffad0bb0ce06d850deec0725789c9f 100644
--- a/src/main/java/net/minecraft/server/Entity.java
+++ b/src/main/java/net/minecraft/server/Entity.java
@@ -207,6 +207,14 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
     }
     // CraftBukkit end
 
+    // Tuinity start
+    public final AxisAlignedBB getBoundingBoxAt(double x, double y, double z) {
+        double widthHalf = (double)this.size.width / 2.0;
+        double height = (double)this.size.height;
+        return new AxisAlignedBB(x - widthHalf, y, z - widthHalf, x + widthHalf, y + height, z + widthHalf);
+    }
+    // Tuinity end
+
     // Paper start - optimise entity tracking
     final org.spigotmc.TrackingRange.TrackingRangeType trackingRangeType = org.spigotmc.TrackingRange.getTrackingRangeType(this);
 
diff --git a/src/main/java/net/minecraft/server/HeightMap.java b/src/main/java/net/minecraft/server/HeightMap.java
index 14ddb2a8949ce18a0c42e17a82d0d7a13ac325fe..476da43b9f0ef35b4985f88e4784b1f8c5222af3 100644
--- a/src/main/java/net/minecraft/server/HeightMap.java
+++ b/src/main/java/net/minecraft/server/HeightMap.java
@@ -101,6 +101,7 @@ public class HeightMap {
         }
     }
 
+    public final int get(int x, int z) { return this.a(x, z); } // Tuinity - OBFHELPER
     public int a(int i, int j) {
         return this.a(c(i, j));
     }
diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java
index ff74be14512a947e81b62d53e616131ca7d7f609..2371557d083446b17ffebdae576b1cc39e939eb1 100644
--- a/src/main/java/net/minecraft/server/MCUtil.java
+++ b/src/main/java/net/minecraft/server/MCUtil.java
@@ -221,6 +221,63 @@ public final class MCUtil {
         return getBlockKey(getBlockCoordinate(entity.locX()), getBlockCoordinate(entity.locY()), getBlockCoordinate(entity.locZ()));
     }
 
+    // Tuinity start
+
+    static final int SECTION_X_BITS = 22;
+    static final long SECTION_X_MASK = (1L << SECTION_X_BITS) - 1;
+    static final int SECTION_Y_BITS = 20;
+    static final long SECTION_Y_MASK = (1L << SECTION_Y_BITS) - 1;
+    static final int SECTION_Z_BITS = 22;
+    static final long SECTION_Z_MASK = (1L << SECTION_Z_BITS) - 1;
+    // format is y,z,x (in order of LSB to MSB)
+    static final int SECTION_Y_SHIFT = 0;
+    static final int SECTION_Z_SHIFT = SECTION_Y_SHIFT + SECTION_Y_BITS;
+    static final int SECTION_X_SHIFT = SECTION_Z_SHIFT + SECTION_X_BITS;
+    static final int SECTION_TO_BLOCK_SHIFT = 4;
+
+    public static long getSectionKey(final int x, final int y, final int z) {
+        return ((x & SECTION_X_MASK) << SECTION_X_SHIFT)
+                | ((y & SECTION_Y_MASK) << SECTION_Y_SHIFT)
+                | ((z & SECTION_Z_MASK) << SECTION_Z_SHIFT);
+    }
+
+    public static long getSectionKey(final SectionPosition pos) {
+        return ((pos.getX() & SECTION_X_MASK) << SECTION_X_SHIFT)
+                | ((pos.getY() & SECTION_Y_MASK) << SECTION_Y_SHIFT)
+                | ((pos.getZ() & SECTION_Z_MASK) << SECTION_Z_SHIFT);
+    }
+
+    public static long getSectionKey(final ChunkCoordIntPair pos, final int y) {
+        return ((pos.x & SECTION_X_MASK) << SECTION_X_SHIFT)
+                | ((y & SECTION_Y_MASK) << SECTION_Y_SHIFT)
+                | ((pos.z & SECTION_Z_MASK) << SECTION_Z_SHIFT);
+    }
+
+    public static long getSectionKey(final BlockPosition pos) {
+        return (((long)pos.getX() << (SECTION_X_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_X_MASK << SECTION_X_SHIFT)) |
+                ((pos.getY() >> SECTION_TO_BLOCK_SHIFT) & (SECTION_Y_MASK << SECTION_Y_SHIFT)) |
+                (((long)pos.getZ() << (SECTION_Z_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_Z_MASK << SECTION_Z_SHIFT));
+    }
+
+    public static long getSectionKey(final Entity entity) {
+        return ((MCUtil.fastFloor(entity.locX()) & SECTION_X_MASK) << SECTION_X_SHIFT)
+                | ((MCUtil.fastFloor(entity.locY()) & SECTION_Y_MASK) << SECTION_Y_SHIFT)
+                | ((MCUtil.fastFloor(entity.locZ()) & SECTION_Z_MASK) << SECTION_Z_SHIFT);
+    }
+
+    public static int getSectionX(final long key) {
+        return (int)(key << (Long.SIZE - (SECTION_X_SHIFT + SECTION_X_BITS)) >> (Long.SIZE - SECTION_X_BITS));
+    }
+
+    public static int getSectionY(final long key) {
+        return (int)(key << (Long.SIZE - (SECTION_Y_SHIFT + SECTION_Y_BITS)) >> (Long.SIZE - SECTION_Y_BITS));
+    }
+
+    public static int getSectionZ(final long key) {
+        return (int)(key << (Long.SIZE - (SECTION_Z_SHIFT + SECTION_Z_BITS)) >> (Long.SIZE - SECTION_Z_BITS));
+    }
+    // Tuinity end
+
     // assumes the sets have the same comparator, and if this comparator is null then assume T is Comparable
     public static <T> void mergeSortedSets(final java.util.function.Consumer<T> consumer, final java.util.Comparator<? super T> comparator, final java.util.SortedSet<T>...sets) {
         final ObjectRBTreeSet<T> all = new ObjectRBTreeSet<>(comparator);
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 2131f7322640879496692b73fd2a8c52b059ae24..74f89ea456e23937d0fec14831508365f3014ab0 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -155,6 +155,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
     public org.bukkit.command.RemoteConsoleCommandSender remoteConsole;
     //public ConsoleReader reader; // Paper
     public static int currentTick = 0; // Paper - Further improve tick loop
+    public static long currentTickLong = 0L; // Tuinity
     public java.util.Queue<Runnable> processQueue = new java.util.concurrent.ConcurrentLinkedQueue<Runnable>();
     public int autosavePeriod;
     public boolean serverAutoSave = false; // Paper
@@ -976,6 +977,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
                         this.lastOverloadTime = this.nextTick;
                     }
 
+                    ++MinecraftServer.currentTickLong; // Tuinity
                     if ( ++MinecraftServer.currentTick % SAMPLE_INTERVAL == 0 )
                     {
                         final long diff = curTime - tickSection;
@@ -1362,6 +1364,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
                 midTickLoadChunks(); // Paper
                 worldserver.timings.doTick.startTiming(); // Spigot
                 worldserver.doTick(booleansupplier);
+                worldserver.getChunkProvider().playerChunkMap.dataRegionManager.recalculateRegions(); // Tuinity
                 worldserver.timings.doTick.stopTiming(); // Spigot
                 midTickLoadChunks(); // Paper
             } catch (Throwable throwable) {
diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java
index 904c6a7d0a36b57bb4f693fc4fd0dd5b17adcbac..b03865a932d341ae2fdad6c9447979fa9e95fc14 100644
--- a/src/main/java/net/minecraft/server/PlayerChunk.java
+++ b/src/main/java/net/minecraft/server/PlayerChunk.java
@@ -508,6 +508,7 @@ public class PlayerChunk {
         // Paper end - per player view distance
     }
 
+    public final CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> getOrCreateFuture(ChunkStatus chunkstatus, PlayerChunkMap playerchunkmap) { return this.a(chunkstatus, playerchunkmap); } // Tuinity - OBFHELPER
     public CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> a(ChunkStatus chunkstatus, PlayerChunkMap playerchunkmap) {
         int i = chunkstatus.c();
         CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> completablefuture = (CompletableFuture) this.statusFutures.get(i);
@@ -678,6 +679,9 @@ public class PlayerChunk {
                     // Paper start - rewrite ticklistserver
                     PlayerChunk.this.chunkMap.world.onChunkSetTicking(PlayerChunk.this.location.x, PlayerChunk.this.location.z);
                     // Paper end - rewrite ticklistserver
+                    // Tuinity start - ticking chunk set
+                    PlayerChunk.this.chunkMap.world.getChunkProvider().tickingChunks.add(tickingChunk);
+                    // Tuinity end - ticking chunk set
 
                 }
             });
@@ -688,6 +692,12 @@ public class PlayerChunk {
         if (flag4 && !flag5) {
             this.tickingFuture.complete(PlayerChunk.UNLOADED_CHUNK); this.isTickingReady = false; // Paper - cache chunk ticking stage
             this.tickingFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE;
+            // Tuinity start - ticking chunk set
+            Chunk chunkIfCached = this.getFullChunkIfCached();
+            if (chunkIfCached != null) {
+                this.chunkMap.world.getChunkProvider().tickingChunks.remove(chunkIfCached);
+            }
+            // Tuinity end - ticking chunk set
         }
 
         boolean flag6 = playerchunk_state.isAtLeast(PlayerChunk.State.ENTITY_TICKING);
@@ -705,7 +715,9 @@ public class PlayerChunk {
                     Chunk entityTickingChunk = either.left().get();
                     PlayerChunk.this.isEntityTickingReady = true;
 
-
+                    // Tuinity start - entity ticking chunk set
+                    PlayerChunk.this.chunkMap.world.getChunkProvider().entityTickingChunks.add(entityTickingChunk);
+                    // Tuinity end - entity ticking chunk set
 
 
                 }
@@ -717,6 +729,12 @@ public class PlayerChunk {
         if (flag6 && !flag7) {
             this.entityTickingFuture.complete(PlayerChunk.UNLOADED_CHUNK); this.isEntityTickingReady = false; // Paper - cache chunk ticking stage
             this.entityTickingFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE;
+            // Tuinity start - entity ticking chunk set
+            Chunk chunkIfCached = this.getFullChunkIfCached();
+            if (chunkIfCached != null) {
+                this.chunkMap.world.getChunkProvider().entityTickingChunks.remove(chunkIfCached);
+            }
+            // Tuinity end - entity ticking chunk set
         }
 
         // Paper start - raise IO/load priority if priority changes, use our preferred priority
diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java
index 00d0a5fd7c5d2db19756f3c6cfb2381868af51fd..2b2920a5602e23dd766ddbbf10b4fed90a1a21bc 100644
--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java
+++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java
@@ -278,6 +278,21 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
     }
     // Paper end
 
+    // Tuinity start
+    public static enum RegionData implements com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionDataCreator<RegionData> {
+
+        ;
+
+        @Override
+        public Object createData(com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection<RegionData> section,
+                                 com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager<RegionData> regionManager) {
+            throw new AbstractMethodError();
+        }
+    }
+
+    public final com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager<RegionData> dataRegionManager;
+    // Tuiniy end
+
     private final java.util.concurrent.ExecutorService lightThread;
     public PlayerChunkMap(WorldServer worldserver, Convertable.ConversionSession convertable_conversionsession, DataFixer datafixer, DefinedStructureManager definedstructuremanager, Executor executor, IAsyncTaskHandler<Runnable> iasynctaskhandler, ILightAccess ilightaccess, ChunkGenerator chunkgenerator, WorldLoadListener worldloadlistener, Supplier<WorldPersistentData> supplier, int i, boolean flag) {
         super(new File(convertable_conversionsession.a(worldserver.getDimensionKey()), "region"), datafixer, flag);
@@ -445,6 +460,9 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
                 PlayerChunkMap.this.sendChunk(player, new ChunkCoordIntPair(rangeX, rangeZ), null, true, false); // unloaded, loaded
             });
         // Paper end - no-tick view distance
+        // Tuinity start
+        this.dataRegionManager = new com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager<>(this.world, RegionData.class, 2, (1.0 / 3.0), "Data");
+        // Tuinity end
     }
     // Paper start - Chunk Prioritization
     public void queueHolderUpdate(PlayerChunk playerchunk) {
@@ -779,6 +797,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
                     playerchunk.a(j);
                 } else {
                     playerchunk = new PlayerChunk(new ChunkCoordIntPair(i), j, this.lightEngine, this.p, this);
+                    this.dataRegionManager.addChunk(playerchunk.location.x, playerchunk.location.z); // Tuinity
                 }
 
                 this.updatingChunks.put(i, playerchunk);
@@ -1021,7 +1040,10 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
             if (completablefuture1 != completablefuture) {
                 this.a(i, playerchunk);
             } else {
-                if (this.pendingUnload.remove(i, playerchunk) && ichunkaccess != null) {
+                // Tuinity start
+                boolean removed;
+                if ((removed = this.pendingUnload.remove(i, playerchunk)) && ichunkaccess != null) { // Tuinity end
+                    this.dataRegionManager.removeChunk(playerchunk.location.x, playerchunk.location.z); // Tuinity
                     if (ichunkaccess instanceof Chunk) {
                         ((Chunk) ichunkaccess).setLoaded(false);
                     }
@@ -1044,7 +1066,9 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
                     this.lightEngine.a(ichunkaccess.getPos());
                     this.lightEngine.queueUpdate();
                     this.worldLoadListener.a(ichunkaccess.getPos(), (ChunkStatus) null);
-                }
+                } else if (removed) { // Tuinity start
+                    this.dataRegionManager.removeChunk(playerchunk.location.x, playerchunk.location.z);
+                } // Tuinity end
 
             }
         };
@@ -1711,6 +1735,11 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
         return chunkHolder == null ? null : chunkHolder.getAvailableChunkNow();
     }
     // Paper end
+    // Tuinity start
+    public PlayerChunk getUnloadingPlayerChunk(int chunkX, int chunkZ) {
+        return this.pendingUnload.get(ChunkCoordIntPair.pair(chunkX, chunkZ));
+    }
+    // Tuinity end
 
 
     // Paper start - async io
diff --git a/src/main/java/net/minecraft/server/VoxelShapes.java b/src/main/java/net/minecraft/server/VoxelShapes.java
index e21c747b6c39155c44bf30860681d67b0b29fb12..9f4f9df09968dc45878ad59f5ee45672a3f08fbd 100644
--- a/src/main/java/net/minecraft/server/VoxelShapes.java
+++ b/src/main/java/net/minecraft/server/VoxelShapes.java
@@ -314,6 +314,7 @@ public final class VoxelShapes {
         }
     }
 
+    public static boolean combinationOccludes(VoxelShape voxelshape, VoxelShape voxelshape1) { return b(voxelshape, voxelshape1); } // Tuinity - OBFHELPER
     public static boolean b(VoxelShape voxelshape, VoxelShape voxelshape1) {
         return voxelshape != b() && voxelshape1 != b() ? (voxelshape.isEmpty() && voxelshape1.isEmpty() ? false : !c(b(), b(voxelshape, voxelshape1, OperatorBoolean.OR), OperatorBoolean.ONLY_FIRST)) : true;
     }
diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java
index cf7d94aabab600822eb5e27f38690b06456d5fcc..4183117fe3977e4f0c927b4262a4804bcb8738f6 100644
--- a/src/main/java/net/minecraft/server/WorldServer.java
+++ b/src/main/java/net/minecraft/server/WorldServer.java
@@ -55,6 +55,7 @@ import org.bukkit.event.server.MapInitializeEvent;
 import org.bukkit.event.weather.LightningStrikeEvent;
 import org.bukkit.event.world.TimeSkipEvent;
 // CraftBukkit end
+import it.unimi.dsi.fastutil.ints.IntArrayList; // Tuinity
 
 public class WorldServer extends World implements GeneratorAccessSeed {
 
@@ -206,6 +207,96 @@ public class WorldServer extends World implements GeneratorAccessSeed {
     }
     // Paper end - rewrite ticklistserver
 
+    // Tuinity start
+    public final boolean areChunksLoadedForMove(AxisAlignedBB axisalignedbb) {
+        // copied code from collision methods, so that we can guarantee that they wont load chunks (we don't override
+        // ICollisionAccess methods for VoxelShapes)
+        // be more strict too, add a block (dumb plugins in move events?)
+        int minBlockX = MathHelper.floor(axisalignedbb.minX - 1.0E-7D) - 3;
+        int maxBlockX = MathHelper.floor(axisalignedbb.maxX + 1.0E-7D) + 3;
+
+        int minBlockZ = MathHelper.floor(axisalignedbb.minZ - 1.0E-7D) - 3;
+        int maxBlockZ = MathHelper.floor(axisalignedbb.maxZ + 1.0E-7D) + 3;
+
+        int minChunkX = minBlockX >> 4;
+        int maxChunkX = maxBlockX >> 4;
+
+        int minChunkZ = minBlockZ >> 4;
+        int maxChunkZ = maxBlockZ >> 4;
+
+        ChunkProviderServer chunkProvider = this.getChunkProvider();
+
+        for (int cx = minChunkX; cx <= maxChunkX; ++cx) {
+            for (int cz = minChunkZ; cz <= maxChunkZ; ++cz) {
+                if (chunkProvider.getChunkAtIfLoadedImmediately(cx, cz) == null) {
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    public final void loadChunksForMoveAsync(AxisAlignedBB axisalignedbb, double toX, double toZ,
+                                             java.util.function.Consumer<List<IChunkAccess>> onLoad) {
+        if (Thread.currentThread() != this.serverThread) {
+            this.getChunkProvider().serverThreadQueue.execute(() -> {
+                this.loadChunksForMoveAsync(axisalignedbb, toX, toZ, onLoad);
+            });
+            return;
+        }
+        List<IChunkAccess> ret = new java.util.ArrayList<>();
+        IntArrayList ticketLevels = new IntArrayList();
+
+        int minBlockX = MathHelper.floor(axisalignedbb.minX - 1.0E-7D) - 3;
+        int maxBlockX = MathHelper.floor(axisalignedbb.maxX + 1.0E-7D) + 3;
+
+        int minBlockZ = MathHelper.floor(axisalignedbb.minZ - 1.0E-7D) - 3;
+        int maxBlockZ = MathHelper.floor(axisalignedbb.maxZ + 1.0E-7D) + 3;
+
+        int minChunkX = minBlockX >> 4;
+        int maxChunkX = maxBlockX >> 4;
+
+        int minChunkZ = minBlockZ >> 4;
+        int maxChunkZ = maxBlockZ >> 4;
+
+        ChunkProviderServer chunkProvider = this.getChunkProvider();
+
+        int requiredChunks = (maxChunkX - minChunkX + 1) * (maxChunkZ - minChunkZ + 1);
+        int[] loadedChunks = new int[1];
+
+        Long holderIdentifier = Long.valueOf(chunkProvider.chunkFutureAwaitCounter++);
+
+        java.util.function.Consumer<IChunkAccess> consumer = (IChunkAccess chunk) -> {
+            if (chunk != null) {
+                int ticketLevel = Math.max(33, chunkProvider.playerChunkMap.getUpdatingChunk(chunk.getPos().pair()).getTicketLevel());
+                ret.add(chunk);
+                ticketLevels.add(ticketLevel);
+                chunkProvider.addTicketAtLevel(TicketType.FUTURE_AWAIT, chunk.getPos(), ticketLevel, holderIdentifier);
+            }
+            if (++loadedChunks[0] == requiredChunks) {
+                try {
+                    onLoad.accept(java.util.Collections.unmodifiableList(ret));
+                } finally {
+                    for (int i = 0, len = ret.size(); i < len; ++i) {
+                        ChunkCoordIntPair chunkPos = ret.get(i).getPos();
+                        int ticketLevel = ticketLevels.getInt(i);
+
+                        chunkProvider.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, ticketLevel, chunkPos);
+                        chunkProvider.removeTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, holderIdentifier);
+                    }
+                }
+            }
+        };
+
+        for (int cx = minChunkX; cx <= maxChunkX; ++cx) {
+            for (int cz = minChunkZ; cz <= maxChunkZ; ++cz) {
+                chunkProvider.getChunkAtAsynchronously(cx, cz, ChunkStatus.FULL, true, false, consumer);
+            }
+        }
+    }
+    // Tuinity end
+
     // Add env and gen to constructor, WorldData -> WorldDataServer
     public WorldServer(MinecraftServer minecraftserver, Executor executor, Convertable.ConversionSession convertable_conversionsession, IWorldDataServer iworlddataserver, ResourceKey<World> resourcekey, DimensionManager dimensionmanager, WorldLoadListener worldloadlistener, ChunkGenerator chunkgenerator, boolean flag, long i, List<MobSpawner> list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen) {
         super(iworlddataserver, resourcekey, dimensionmanager, minecraftserver::getMethodProfiler, false, flag, i, gen, env, executor); // Paper pass executor
