mirror of https://github.com/YatopiaMC/Yatopia.git
2641 lines
114 KiB
Diff
2641 lines
114 KiB
Diff
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..5ea5b3933725d80dd193e815ac507ee51ee17630
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/tuinity/tuinity/chunk/SingleThreadChunkRegionManager.java
|
|
@@ -0,0 +1,477 @@
|
|
+package com.tuinity.tuinity.chunk;
|
|
+
|
|
+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.MCUtil;
|
|
+import net.minecraft.server.level.WorldServer;
|
|
+import net.minecraft.world.level.ChunkCoordIntPair;
|
|
+import java.util.ArrayList;
|
|
+import java.util.Arrays;
|
|
+import java.util.Iterator;
|
|
+import java.util.List;
|
|
+import java.util.function.Supplier;
|
|
+
|
|
+public final class SingleThreadChunkRegionManager {
|
|
+
|
|
+ protected final int regionSectionMergeRadius;
|
|
+ protected final int regionSectionChunkSize;
|
|
+ public final int regionChunkShift; // log2(REGION_CHUNK_SIZE)
|
|
+
|
|
+ public final WorldServer world;
|
|
+ public final String name;
|
|
+
|
|
+ protected final Long2ObjectOpenHashMap<RegionSection> regionsBySection = new Long2ObjectOpenHashMap<>();
|
|
+ protected final ReferenceLinkedOpenHashSet<Region> needsRecalculation = new ReferenceLinkedOpenHashSet<>();
|
|
+ protected final int minSectionRecalcCount;
|
|
+ protected final double maxDeadRegionPercent;
|
|
+ protected final Supplier<RegionData> regionDataSupplier;
|
|
+ protected final Supplier<RegionSectionData> regionSectionDataSupplier;
|
|
+
|
|
+ public SingleThreadChunkRegionManager(final WorldServer world, final int minSectionRecalcCount,
|
|
+ final double maxDeadRegionPercent, final int sectionMergeRadius,
|
|
+ final int regionSectionChunkShift,
|
|
+ final String name, final Supplier<RegionData> regionDataSupplier,
|
|
+ final Supplier<RegionSectionData> regionSectionDataSupplier) {
|
|
+ this.regionSectionMergeRadius = sectionMergeRadius;
|
|
+ this.regionSectionChunkSize = 1 << regionSectionChunkShift;
|
|
+ this.regionChunkShift = regionSectionChunkShift;
|
|
+ this.world = world;
|
|
+ this.name = name;
|
|
+ this.minSectionRecalcCount = Math.max(2, minSectionRecalcCount);
|
|
+ this.maxDeadRegionPercent = maxDeadRegionPercent;
|
|
+ this.regionDataSupplier = regionDataSupplier;
|
|
+ this.regionSectionDataSupplier = regionSectionDataSupplier;
|
|
+ }
|
|
+
|
|
+ // 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 region) {
|
|
+ this.needsRecalculation.add(region);
|
|
+ }
|
|
+
|
|
+ protected void removeFromRecalcQueue(final Region region) {
|
|
+ this.needsRecalculation.remove(region);
|
|
+ }
|
|
+
|
|
+ public RegionSection getRegionSection(final int chunkX, final int chunkZ) {
|
|
+ return this.regionsBySection.get(MCUtil.getCoordinateKey(chunkX >> this.regionChunkShift, chunkZ >> this.regionChunkShift));
|
|
+ }
|
|
+
|
|
+ public Region getRegion(final int chunkX, final int chunkZ) {
|
|
+ final RegionSection section = this.regionsBySection.get(MCUtil.getCoordinateKey(chunkX >> regionChunkShift, chunkZ >> regionChunkShift));
|
|
+ return section != null ? section.region : null;
|
|
+ }
|
|
+
|
|
+ private final List<Region> toMerge = new ArrayList<>();
|
|
+
|
|
+ protected RegionSection getOrCreateAndMergeSection(final int sectionX, final int sectionZ, final RegionSection force) {
|
|
+ final long sectionKey = MCUtil.getCoordinateKey(sectionX, sectionZ);
|
|
+
|
|
+ if (force == null) {
|
|
+ RegionSection region = this.regionsBySection.get(sectionKey);
|
|
+ if (region != null) {
|
|
+ return region;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ int mergeCandidateSectionSize = -1;
|
|
+ Region mergeIntoCandidate = null;
|
|
+
|
|
+ // find optimal candidate to merge into
|
|
+
|
|
+ final int minX = sectionX - this.regionSectionMergeRadius;
|
|
+ final int maxX = sectionX + this.regionSectionMergeRadius;
|
|
+ final int minZ = sectionZ - this.regionSectionMergeRadius;
|
|
+ final int maxZ = sectionZ + this.regionSectionMergeRadius;
|
|
+ for (int currX = minX; currX <= maxX; ++currX) {
|
|
+ for (int currZ = minZ; currZ <= maxZ; ++currZ) {
|
|
+ final RegionSection section = this.regionsBySection.get(MCUtil.getCoordinateKey(currX, currZ));
|
|
+ if (section == null) {
|
|
+ continue;
|
|
+ }
|
|
+ final Region 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 region = this.toMerge.get(i);
|
|
+ if (region.dead || mergeIntoCandidate == region) {
|
|
+ continue;
|
|
+ }
|
|
+ region.mergeInto(mergeIntoCandidate);
|
|
+ }
|
|
+ this.toMerge.clear();
|
|
+ } else {
|
|
+ mergeIntoCandidate = new Region(this);
|
|
+ }
|
|
+
|
|
+ final RegionSection section;
|
|
+ if (force == null) {
|
|
+ this.regionsBySection.put(sectionKey, section = new RegionSection(sectionKey, this));
|
|
+ } else {
|
|
+ final RegionSection existing = this.regionsBySection.putIfAbsent(sectionKey, force);
|
|
+ if (existing != null) {
|
|
+ throw new IllegalStateException("Attempting to override section '" + existing.toStringWithRegion() +
|
|
+ ", with " + force.toStringWithRegion());
|
|
+ }
|
|
+
|
|
+ section = force;
|
|
+ }
|
|
+
|
|
+ mergeIntoCandidate.addRegionSection(section);
|
|
+ //mergeIntoCandidate.check();
|
|
+ //this.check();
|
|
+
|
|
+ return section;
|
|
+ }
|
|
+
|
|
+ public void addChunk(final int chunkX, final int chunkZ) {
|
|
+ this.getOrCreateAndMergeSection(chunkX >> this.regionChunkShift, chunkZ >> this.regionChunkShift, null).addChunk(chunkX, chunkZ);
|
|
+ }
|
|
+
|
|
+ public void removeChunk(final int chunkX, final int chunkZ) {
|
|
+ final RegionSection section = this.regionsBySection.get(
|
|
+ MCUtil.getCoordinateKey(chunkX >> this.regionChunkShift, chunkZ >> this.regionChunkShift)
|
|
+ );
|
|
+ if (section != null) {
|
|
+ section.removeChunk(chunkX, chunkZ);
|
|
+ } else {
|
|
+ throw new IllegalStateException("Cannot remove chunk at (" + chunkX + "," + chunkZ + ") from region state, section does not exist");
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void recalculateRegions() {
|
|
+ for (int i = 0, len = this.needsRecalculation.size(); i < len; ++i) {
|
|
+ final Region region = this.needsRecalculation.removeFirst();
|
|
+
|
|
+ this.recalculateRegion(region);
|
|
+ //this.check();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ protected void recalculateRegion(final Region region) {
|
|
+ region.markedForRecalc = false;
|
|
+ //region.check();
|
|
+ // clear unused regions
|
|
+ for (final Iterator<RegionSection> iterator = region.deadSections.iterator(); iterator.hasNext();) {
|
|
+ final RegionSection deadSection = iterator.next();
|
|
+
|
|
+ if (deadSection.hasChunks()) {
|
|
+ throw new IllegalStateException("Dead section '" + deadSection.toStringWithRegion() + "' is marked dead but has chunks!");
|
|
+ }
|
|
+ if (!region.removeRegionSection(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> iterator = region.sections.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) {
|
|
+ final RegionSection 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> iterator = region.sections.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) {
|
|
+ final RegionSection aliveSection = iterator.next();
|
|
+ this.getOrCreateAndMergeSection(aliveSection.getSectionX(), aliveSection.getSectionZ(), aliveSection);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static final class Region {
|
|
+ protected final IteratorSafeOrderedReferenceSet<RegionSection> sections = new IteratorSafeOrderedReferenceSet<>();
|
|
+ protected final ReferenceOpenHashSet<RegionSection> deadSections = new ReferenceOpenHashSet<>(16, 0.7f);
|
|
+ protected boolean dead;
|
|
+ protected boolean markedForRecalc;
|
|
+
|
|
+ public final SingleThreadChunkRegionManager regionManager;
|
|
+ public final RegionData regionData;
|
|
+
|
|
+ protected Region(final SingleThreadChunkRegionManager regionManager) {
|
|
+ this.regionManager = regionManager;
|
|
+ this.regionData = regionManager.regionDataSupplier.get();
|
|
+ }
|
|
+
|
|
+ public IteratorSafeOrderedReferenceSet.Iterator<RegionSection> 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!");
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ */
|
|
+
|
|
+ // note: it is not true that the region at this point is not in any region. use the region field on the section
|
|
+ // to see if it is currently in another region.
|
|
+ protected final boolean addRegionSection(final RegionSection section) {
|
|
+ if (!this.sections.add(section)) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ section.sectionData.addToRegion(section, section.region, this);
|
|
+
|
|
+ section.region = this;
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ protected final boolean removeRegionSection(final RegionSection section) {
|
|
+ if (!this.sections.remove(section)) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ section.sectionData.removeFromRegion(section, this);
|
|
+
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ protected void mergeInto(final Region 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> iterator = this.sections.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) {
|
|
+ final RegionSection section = iterator.next();
|
|
+
|
|
+ if (!mergeTarget.addRegionSection(section)) {
|
|
+ throw new IllegalStateException("Target cannot contain source's sections! Source " + this + ", target " + mergeTarget);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ for (final RegionSection 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 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 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> iterator = this.sections.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) {
|
|
+ final RegionSection section = iterator.next();
|
|
+ ret.append(section);
|
|
+ if (iterator.hasNext()) {
|
|
+ ret.append(',');
|
|
+ }
|
|
+ }
|
|
+ ret.append(']');
|
|
+
|
|
+ ret.append('}');
|
|
+ return ret.toString();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static final class RegionSection {
|
|
+ protected final long regionCoordinate;
|
|
+ protected final long[] chunksBitset;
|
|
+ protected int chunkCount;
|
|
+ protected Region region;
|
|
+
|
|
+ public final SingleThreadChunkRegionManager regionManager;
|
|
+ public final RegionSectionData sectionData;
|
|
+
|
|
+ protected RegionSection(final long regionCoordinate, final SingleThreadChunkRegionManager regionManager) {
|
|
+ this.regionCoordinate = regionCoordinate;
|
|
+ this.regionManager = regionManager;
|
|
+ this.chunksBitset = new long[Math.max(1, regionManager.regionSectionChunkSize * regionManager.regionSectionChunkSize / Long.SIZE)];
|
|
+ this.sectionData = regionManager.regionSectionDataSupplier.get();
|
|
+ }
|
|
+
|
|
+ public int getSectionX() {
|
|
+ return MCUtil.getCoordinateX(this.regionCoordinate);
|
|
+ }
|
|
+
|
|
+ public int getSectionZ() {
|
|
+ return MCUtil.getCoordinateZ(this.regionCoordinate);
|
|
+ }
|
|
+
|
|
+ public Region getRegion() {
|
|
+ return this.region;
|
|
+ }
|
|
+
|
|
+ private int getChunkIndex(final int chunkX, final int chunkZ) {
|
|
+ return (chunkX & (this.regionManager.regionSectionChunkSize - 1)) | ((chunkZ & (this.regionManager.regionSectionChunkSize - 1)) << this.regionManager.regionChunkShift);
|
|
+ }
|
|
+
|
|
+ protected boolean hasChunks() {
|
|
+ return this.chunkCount != 0;
|
|
+ }
|
|
+
|
|
+ protected void addChunk(final int chunkX, final int chunkZ) {
|
|
+ final int index = this.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 = this.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) {
|
|
+ final StringBuilder ret = new StringBuilder();
|
|
+ for (final long value : array) {
|
|
+ // zero pad the hex string
|
|
+ final char[] zeros = new char[Long.SIZE / 4];
|
|
+ Arrays.fill(zeros, '0');
|
|
+ final 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 RegionData {
|
|
+
|
|
+ }
|
|
+
|
|
+ public static interface RegionSectionData {
|
|
+
|
|
+ public void removeFromRegion(final RegionSection section, final Region from);
|
|
+
|
|
+ // removal from the old region is handled via removeFromRegion
|
|
+ public void addToRegion(final RegionSection section, final Region oldRegion, final Region newRegion);
|
|
+
|
|
+ }
|
|
+}
|
|
\ 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..9cc49e8e4ad841df2b38dc37ec761bf360f5a357
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/tuinity/tuinity/util/misc/Delayed26WayDistancePropagator3D.java
|
|
@@ -0,0 +1,300 @@
|
|
+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 boolean propagateUpdates() {
|
|
+ if (this.updatedSources.isEmpty()) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ boolean ret = false;
|
|
+
|
|
+ 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;
|
|
+ }
|
|
+ ret = true;
|
|
+
|
|
+ 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();
|
|
+
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ 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..cdd3c4032c1d6b34a10ba415bd4d0e377aa9af3c
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/tuinity/tuinity/util/misc/Delayed8WayDistancePropagator2D.java
|
|
@@ -0,0 +1,718 @@
|
|
+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 boolean propagateUpdates() {
|
|
+ if (this.updatedSources.isEmpty()) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ boolean ret = false;
|
|
+
|
|
+ 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;
|
|
+ }
|
|
+ ret = true;
|
|
+
|
|
+ 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();
|
|
+
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ 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/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java
|
|
index 1d72af9cace7aa8f1d20c7c1c5be621f533e2dad..006e7076932f6be576a64da09c4d84ca4a15f5dd 100644
|
|
--- a/src/main/java/net/minecraft/server/MCUtil.java
|
|
+++ b/src/main/java/net/minecraft/server/MCUtil.java
|
|
@@ -238,6 +238,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 net.minecraft.core.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 bfd0d8e3e7b6409b8ef7620ef147120480878c51..dafd5c368036afa4516da7eac04ddb7c649e8b97 100644
|
|
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
@@ -267,6 +267,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
|
|
@@ -1098,6 +1099,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;
|
|
@@ -1484,6 +1486,11 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
|
midTickLoadChunks(); // Paper
|
|
worldserver.timings.doTick.startTiming(); // Spigot
|
|
worldserver.doTick(booleansupplier);
|
|
+ // Tuinity start
|
|
+ for (final com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager regionManager : worldserver.getChunkProvider().playerChunkMap.regionManagers) {
|
|
+ regionManager.recalculateRegions();
|
|
+ }
|
|
+ // Tuinity end
|
|
worldserver.timings.doTick.stopTiming(); // Spigot
|
|
midTickLoadChunks(); // Paper
|
|
} catch (Throwable throwable) {
|
|
diff --git a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java
|
|
index c5e54c519e1f686761faa53b5e9579c514a65332..e9b236bb95985ba0806a3d27d705ac61bce55ea5 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java
|
|
@@ -137,7 +137,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) {
|
|
@@ -226,6 +226,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);
|
|
@@ -624,8 +783,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/level/PlayerChunk.java b/src/main/java/net/minecraft/server/level/PlayerChunk.java
|
|
index a323b76f68c273a73cb3f20167a668b2100f4944..f7deb4fe98ef9900acb276c7680d5a2da9661e8f 100644
|
|
--- a/src/main/java/net/minecraft/server/level/PlayerChunk.java
|
|
+++ b/src/main/java/net/minecraft/server/level/PlayerChunk.java
|
|
@@ -534,6 +534,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);
|
|
@@ -704,6 +705,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
|
|
|
|
}
|
|
});
|
|
@@ -714,6 +718,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);
|
|
@@ -731,7 +741,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
|
|
|
|
|
|
}
|
|
@@ -743,6 +755,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/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java
|
|
index 300884804bf9ac3fba7c30a04d8adf52e3dd2e3e..f3d6811156e68040106f1d027a10ea33b5646b05 100644
|
|
--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java
|
|
+++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java
|
|
@@ -332,6 +332,36 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
}
|
|
// Paper end
|
|
|
|
+ // Tuinity start
|
|
+ public final List<com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager> regionManagers = new java.util.ArrayList<>();
|
|
+ public final com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager dataRegionManager;
|
|
+
|
|
+ public static final class DataRegionData implements com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionData {
|
|
+
|
|
+
|
|
+
|
|
+ }
|
|
+
|
|
+ public static final class DataRegionSectionData implements com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSectionData {
|
|
+
|
|
+ @Override
|
|
+ public void removeFromRegion(final com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection section,
|
|
+ final com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.Region from) {
|
|
+ final DataRegionSectionData sectionData = (DataRegionSectionData)section.sectionData;
|
|
+ final DataRegionData fromData = (DataRegionData)from.regionData;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void addToRegion(final com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection section,
|
|
+ final com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.Region oldRegion,
|
|
+ final com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.Region newRegion) {
|
|
+ final DataRegionSectionData sectionData = (DataRegionSectionData)section.sectionData;
|
|
+ final DataRegionData oldRegionData = oldRegion == null ? null : (DataRegionData)oldRegion.regionData;
|
|
+ final DataRegionData newRegionData = (DataRegionData)newRegion.regionData;
|
|
+ }
|
|
+ }
|
|
+ // 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);
|
|
@@ -499,6 +529,10 @@ 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, 2, (1.0 / 3.0), 1, 6, "Data", DataRegionData::new, DataRegionSectionData::new);
|
|
+ this.regionManagers.add(this.dataRegionManager);
|
|
+ // Tuinity end
|
|
}
|
|
// Paper start - Chunk Prioritization
|
|
public void queueHolderUpdate(PlayerChunk playerchunk) {
|
|
@@ -833,6 +867,11 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
playerchunk.a(j);
|
|
} else {
|
|
playerchunk = new PlayerChunk(new ChunkCoordIntPair(i), j, this.lightEngine, this.p, this);
|
|
+ // Tuinity start
|
|
+ for (int index = 0, len = this.regionManagers.size(); index < len; ++index) {
|
|
+ this.regionManagers.get(index).addChunk(playerchunk.location.x, playerchunk.location.z);
|
|
+ }
|
|
+ // Tuinity end
|
|
}
|
|
|
|
this.updatingChunks.put(i, playerchunk);
|
|
@@ -1075,7 +1114,14 @@ 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
|
|
+ // Tuinity start
|
|
+ for (int index = 0, len = this.regionManagers.size(); index < len; ++index) {
|
|
+ this.regionManagers.get(index).removeChunk(playerchunk.location.x, playerchunk.location.z);
|
|
+ }
|
|
+ // Tuinity end
|
|
if (ichunkaccess instanceof Chunk) {
|
|
((Chunk) ichunkaccess).setLoaded(false);
|
|
}
|
|
@@ -1098,7 +1144,13 @@ 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
|
|
+ // Tuinity start
|
|
+ for (int index = 0, len = this.regionManagers.size(); index < len; ++index) {
|
|
+ this.regionManagers.get(index).removeChunk(playerchunk.location.x, playerchunk.location.z);
|
|
+ }
|
|
+ // Tuinity end
|
|
+ } // Tuinity end
|
|
|
|
}
|
|
};
|
|
@@ -1765,6 +1817,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/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java
|
|
index ef9b08df58d9d28df6b8ade076d95bf7e5cb1b18..f88b80e4772ade4199564cf96ef94ce45e493311 100644
|
|
--- a/src/main/java/net/minecraft/server/level/WorldServer.java
|
|
+++ b/src/main/java/net/minecraft/server/level/WorldServer.java
|
|
@@ -168,6 +168,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 {
|
|
|
|
@@ -319,6 +320,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
|
|
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
|
|
index 429f0591c6a55f6c5d08a0755f7d39da676468bc..6a3e859eef2878d9e203bba7930e52d7513d96e8 100644
|
|
--- a/src/main/java/net/minecraft/world/entity/Entity.java
|
|
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
|
|
@@ -299,6 +299,14 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne
|
|
}
|
|
// 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/world/level/block/state/BlockBase.java b/src/main/java/net/minecraft/world/level/block/state/BlockBase.java
|
|
index 3fdafc0ff0c4148ec844dbdc1455d17cdcb4a75a..1499dfb2f21294f91fb078d12e8e2e9e8b8b0382 100644
|
|
--- a/src/main/java/net/minecraft/world/level/block/state/BlockBase.java
|
|
+++ b/src/main/java/net/minecraft/world/level/block/state/BlockBase.java
|
|
@@ -348,14 +348,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;
|
|
@@ -396,12 +396,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
|
|
|
|
}
|
|
|
|
@@ -425,10 +433,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);
|
|
}
|
|
@@ -438,7 +448,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/world/level/levelgen/HeightMap.java b/src/main/java/net/minecraft/world/level/levelgen/HeightMap.java
|
|
index aed66330b0f42eb8c8fbbebdb36f190db8e3b6b9..79214a93c533839d0c560a1e4f5904c62265590e 100644
|
|
--- a/src/main/java/net/minecraft/world/level/levelgen/HeightMap.java
|
|
+++ b/src/main/java/net/minecraft/world/level/levelgen/HeightMap.java
|
|
@@ -109,6 +109,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/world/phys/shapes/VoxelShapes.java b/src/main/java/net/minecraft/world/phys/shapes/VoxelShapes.java
|
|
index 858d4689e618c72250447adb61e0bcc3c156f8f3..38fde2e10adf06bf5c5141d17d82ba125b372cd7 100644
|
|
--- a/src/main/java/net/minecraft/world/phys/shapes/VoxelShapes.java
|
|
+++ b/src/main/java/net/minecraft/world/phys/shapes/VoxelShapes.java
|
|
@@ -324,6 +324,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;
|
|
}
|