From 8e569ba62a3b4bf39d1669b6423b99ccffa74182 Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Thu, 24 Oct 2024 08:20:45 -0700 Subject: [PATCH] Merge patches --- patches/server/0822-fixup-MC-Utils.patch | 1263 -- ...ch => 0822-fixup-Paper-config-files.patch} | 4 +- patches/server/0823-fixup-MC-Utils.patch | 1334 +- ...> 0824-Rewrite-dataconverter-system.patch} | 0 ...timize-BlockPosition-helper-methods.patch} | 0 .../0826-Moonrise-optimisation-patches.patch | 8423 ++++++---- ...-fixup-Moonrise-optimisation-patches.patch | 13349 ---------------- 7 files changed, 6876 insertions(+), 17497 deletions(-) delete mode 100644 patches/server/0822-fixup-MC-Utils.patch rename patches/server/{0824-fixup-Paper-config-files.patch => 0822-fixup-Paper-config-files.patch} (95%) rename patches/server/{0825-Rewrite-dataconverter-system.patch => 0824-Rewrite-dataconverter-system.patch} (100%) rename patches/server/{0827-fixup-Optimize-BlockPosition-helper-methods.patch => 0825-fixup-Optimize-BlockPosition-helper-methods.patch} (100%) delete mode 100644 patches/server/0828-fixup-Moonrise-optimisation-patches.patch diff --git a/patches/server/0822-fixup-MC-Utils.patch b/patches/server/0822-fixup-MC-Utils.patch deleted file mode 100644 index cc014ce7e8..0000000000 --- a/patches/server/0822-fixup-MC-Utils.patch +++ /dev/null @@ -1,1263 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Mon, 21 Oct 2024 12:21:54 -0700 -Subject: [PATCH] fixup! MC Utils - - -diff --git a/src/main/java/ca/spottedleaf/moonrise/common/PlatformHooks.java b/src/main/java/ca/spottedleaf/moonrise/common/PlatformHooks.java -new file mode 100644 -index 0000000000000000000000000000000000000000..3c5ed66328ccf94c4744a191a7c63562dd08158d ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/moonrise/common/PlatformHooks.java -@@ -0,0 +1,115 @@ -+package ca.spottedleaf.moonrise.common; -+ -+import com.mojang.datafixers.DataFixer; -+import net.minecraft.core.BlockPos; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.server.level.ChunkHolder; -+import net.minecraft.server.level.GenerationChunkHolder; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.util.datafix.DataFixTypes; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.level.BlockGetter; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.LevelChunk; -+import net.minecraft.world.level.chunk.ProtoChunk; -+import net.minecraft.world.level.chunk.storage.SerializableChunkData; -+import net.minecraft.world.level.entity.EntityTypeTest; -+import net.minecraft.world.phys.AABB; -+import java.util.List; -+import java.util.ServiceLoader; -+import java.util.function.Predicate; -+ -+public interface PlatformHooks { -+ public static PlatformHooks get() { -+ return Holder.INSTANCE; -+ } -+ -+ public String getBrand(); -+ -+ public int getLightEmission(final BlockState blockState, final BlockGetter world, final BlockPos pos); -+ -+ public Predicate maybeHasLightEmission(); -+ -+ public boolean hasCurrentlyLoadingChunk(); -+ -+ public LevelChunk getCurrentlyLoadingChunk(final GenerationChunkHolder holder); -+ -+ public void setCurrentlyLoading(final GenerationChunkHolder holder, final LevelChunk levelChunk); -+ -+ public void chunkFullStatusComplete(final LevelChunk newChunk, final ProtoChunk original); -+ -+ public boolean allowAsyncTicketUpdates(); -+ -+ public void onChunkHolderTicketChange(final ServerLevel world, final ChunkHolder holder, final int oldLevel, final int newLevel); -+ -+ public void chunkUnloadFromWorld(final LevelChunk chunk); -+ -+ public void chunkSyncSave(final ServerLevel world, final ChunkAccess chunk, final SerializableChunkData data); -+ -+ public void onChunkWatch(final ServerLevel world, final LevelChunk chunk, final ServerPlayer player); -+ -+ public void onChunkUnWatch(final ServerLevel world, final ChunkPos chunk, final ServerPlayer player); -+ -+ public void addToGetEntities(final Level world, final Entity entity, final AABB boundingBox, final Predicate predicate, -+ final List into); -+ -+ public void addToGetEntities(final Level world, final EntityTypeTest entityTypeTest, -+ final AABB boundingBox, final Predicate predicate, -+ final List into, final int maxCount); -+ -+ public void entityMove(final Entity entity, final long oldSection, final long newSection); -+ -+ public boolean screenEntity(final ServerLevel world, final Entity entity, final boolean fromDisk, final boolean event); -+ -+ public boolean configFixMC224294(); -+ -+ public boolean configAutoConfigSendDistance(); -+ -+ public double configPlayerMaxLoadRate(); -+ -+ public double configPlayerMaxGenRate(); -+ -+ public double configPlayerMaxSendRate(); -+ -+ public int configPlayerMaxConcurrentLoads(); -+ -+ public int configPlayerMaxConcurrentGens(); -+ -+ public long configAutoSaveInterval(); -+ -+ public int configMaxAutoSavePerTick(); -+ -+ public boolean configFixMC159283(); -+ -+ // support for CB chunk mustNotSave -+ public boolean forceNoSave(final ChunkAccess chunk); -+ -+ public CompoundTag convertNBT(final DataFixTypes type, final DataFixer dataFixer, final CompoundTag nbt, -+ final int fromVersion, final int toVersion); -+ -+ public boolean hasMainChunkLoadHook(); -+ -+ public void mainChunkLoad(final ChunkAccess chunk, final SerializableChunkData chunkData); -+ -+ public List modifySavedEntities(final ServerLevel world, final int chunkX, final int chunkZ, final List entities); -+ -+ public void unloadEntity(final Entity entity); -+ -+ public int modifyEntityTrackingRange(final Entity entity, final int currentRange); -+ -+ public static final class Holder { -+ private Holder() { -+ } -+ -+ private static final PlatformHooks INSTANCE; -+ -+ static { -+ INSTANCE = ServiceLoader.load(PlatformHooks.class, PlatformHooks.class.getClassLoader()).findFirst() -+ .orElseThrow(() -> new RuntimeException("Failed to locate PlatformHooks")); -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java -index ba68998f6ef57b24c72fd833bd7de440de9501cc..7fed43a1e7bcf35c4d7fd3224837a47fedd59860 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java -+++ b/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java -@@ -13,15 +13,15 @@ import java.util.NoSuchElementException; - */ - public final class EntityList implements Iterable { - -- protected final Int2IntOpenHashMap entityToIndex = new Int2IntOpenHashMap(2, 0.8f); -+ private final Int2IntOpenHashMap entityToIndex = new Int2IntOpenHashMap(2, 0.8f); - { - this.entityToIndex.defaultReturnValue(Integer.MIN_VALUE); - } - -- protected static final Entity[] EMPTY_LIST = new Entity[0]; -+ private static final Entity[] EMPTY_LIST = new Entity[0]; - -- protected Entity[] entities = EMPTY_LIST; -- protected int count; -+ private Entity[] entities = EMPTY_LIST; -+ private int count; - - public int size() { - return this.count; -@@ -94,10 +94,9 @@ public final class EntityList implements Iterable { - - @Override - public Iterator iterator() { -- return new Iterator() { -- -- Entity lastRet; -- int current; -+ return new Iterator<>() { -+ private Entity lastRet; -+ private int current; - - @Override - public boolean hasNext() { -diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/IBlockDataList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/IBlockDataList.java -deleted file mode 100644 -index fcfbca333234c09f7c056bbfcd9ac8860b20a8db..0000000000000000000000000000000000000000 ---- a/src/main/java/ca/spottedleaf/moonrise/common/list/IBlockDataList.java -+++ /dev/null -@@ -1,125 +0,0 @@ --package ca.spottedleaf.moonrise.common.list; -- --import it.unimi.dsi.fastutil.longs.LongIterator; --import it.unimi.dsi.fastutil.shorts.Short2LongOpenHashMap; --import java.util.Arrays; --import net.minecraft.world.level.block.Block; --import net.minecraft.world.level.block.state.BlockState; --import net.minecraft.world.level.chunk.GlobalPalette; -- --public final class IBlockDataList { -- -- private static final GlobalPalette GLOBAL_PALETTE = new GlobalPalette<>(Block.BLOCK_STATE_REGISTRY); -- -- // map of location -> (index | (location << 16) | (palette id << 32)) -- private final Short2LongOpenHashMap map = new Short2LongOpenHashMap(2, 0.8f); -- { -- this.map.defaultReturnValue(Long.MAX_VALUE); -- } -- -- private static final long[] EMPTY_LIST = new long[0]; -- -- private long[] byIndex = EMPTY_LIST; -- private int size; -- -- public static int getLocationKey(final int x, final int y, final int z) { -- return (x & 15) | (((z & 15) << 4)) | ((y & 255) << (4 + 4)); -- } -- -- public static BlockState getBlockDataFromRaw(final long raw) { -- return GLOBAL_PALETTE.valueFor((int)(raw >>> 32)); -- } -- -- public static int getIndexFromRaw(final long raw) { -- return (int)(raw & 0xFFFF); -- } -- -- public static int getLocationFromRaw(final long raw) { -- return (int)((raw >>> 16) & 0xFFFF); -- } -- -- public static long getRawFromValues(final int index, final int location, final BlockState data) { -- return (long)index | ((long)location << 16) | (((long)GLOBAL_PALETTE.idFor(data)) << 32); -- } -- -- public static long setIndexRawValues(final long value, final int index) { -- return value & ~(0xFFFF) | (index); -- } -- -- public long add(final int x, final int y, final int z, final BlockState data) { -- return this.add(getLocationKey(x, y, z), data); -- } -- -- public long add(final int location, final BlockState data) { -- final long curr = this.map.get((short)location); -- -- if (curr == Long.MAX_VALUE) { -- final int index = this.size++; -- final long raw = getRawFromValues(index, location, data); -- this.map.put((short)location, raw); -- -- if (index >= this.byIndex.length) { -- this.byIndex = Arrays.copyOf(this.byIndex, (int)Math.max(4L, this.byIndex.length * 2L)); -- } -- -- this.byIndex[index] = raw; -- return raw; -- } else { -- final int index = getIndexFromRaw(curr); -- final long raw = this.byIndex[index] = getRawFromValues(index, location, data); -- -- this.map.put((short)location, raw); -- -- return raw; -- } -- } -- -- public long remove(final int x, final int y, final int z) { -- return this.remove(getLocationKey(x, y, z)); -- } -- -- public long remove(final int location) { -- final long ret = this.map.remove((short)location); -- final int index = getIndexFromRaw(ret); -- if (ret == Long.MAX_VALUE) { -- return ret; -- } -- -- // move the entry at the end to this index -- final int endIndex = --this.size; -- final long end = this.byIndex[endIndex]; -- if (index != endIndex) { -- // not empty after this call -- this.map.put((short)getLocationFromRaw(end), setIndexRawValues(end, index)); -- } -- this.byIndex[index] = end; -- this.byIndex[endIndex] = 0L; -- -- return ret; -- } -- -- public int size() { -- return this.size; -- } -- -- public long getRaw(final int index) { -- return this.byIndex[index]; -- } -- -- public int getLocation(final int index) { -- return getLocationFromRaw(this.getRaw(index)); -- } -- -- public BlockState getData(final int index) { -- return getBlockDataFromRaw(this.getRaw(index)); -- } -- -- public void clear() { -- this.size = 0; -- this.map.clear(); -- } -- -- public LongIterator getRawIterator() { -- return this.map.values().iterator(); -- } --} -\ No newline at end of file -diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/IntList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/IntList.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9f3b25bb2439f283f878db93973a02fcdcd14eed ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/moonrise/common/list/IntList.java -@@ -0,0 +1,77 @@ -+package ca.spottedleaf.moonrise.common.list; -+ -+import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; -+import java.util.Arrays; -+ -+public final class IntList { -+ -+ private final Int2IntOpenHashMap map = new Int2IntOpenHashMap(); -+ { -+ this.map.defaultReturnValue(Integer.MIN_VALUE); -+ } -+ -+ private static final int[] EMPTY_LIST = new int[0]; -+ -+ private int[] byIndex = EMPTY_LIST; -+ private int count; -+ -+ public int size() { -+ return this.count; -+ } -+ -+ public void setMinCapacity(final int len) { -+ final int[] byIndex = this.byIndex; -+ if (byIndex.length < len) { -+ this.byIndex = Arrays.copyOf(byIndex, len); -+ } -+ } -+ -+ public int getRaw(final int index) { -+ return this.byIndex[index]; -+ } -+ -+ public boolean add(final int value) { -+ final int count = this.count; -+ final int currIndex = this.map.putIfAbsent(value, count); -+ -+ if (currIndex != Integer.MIN_VALUE) { -+ return false; // already in this list -+ } -+ -+ int[] list = this.byIndex; -+ -+ if (list.length == count) { -+ // resize required -+ list = this.byIndex = Arrays.copyOf(list, (int)Math.max(4L, count * 2L)); // overflow results in negative -+ } -+ -+ list[count] = value; -+ this.count = count + 1; -+ -+ return true; -+ } -+ -+ public boolean remove(final int value) { -+ final int index = this.map.remove(value); -+ if (index == Integer.MIN_VALUE) { -+ return false; -+ } -+ -+ // move the entry at the end to this index -+ final int endIndex = --this.count; -+ final int end = this.byIndex[endIndex]; -+ if (index != endIndex) { -+ // not empty after this call -+ this.map.put(end, index); -+ } -+ this.byIndex[index] = end; -+ this.byIndex[endIndex] = 0; -+ -+ return true; -+ } -+ -+ public void clear() { -+ this.count = 0; -+ this.map.clear(); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/ShortList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/ShortList.java -new file mode 100644 -index 0000000000000000000000000000000000000000..2bae9949ef325d0001aa638150fbbdf968367e75 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/moonrise/common/list/ShortList.java -@@ -0,0 +1,77 @@ -+package ca.spottedleaf.moonrise.common.list; -+ -+import it.unimi.dsi.fastutil.shorts.Short2ShortOpenHashMap; -+import java.util.Arrays; -+ -+public final class ShortList { -+ -+ private final Short2ShortOpenHashMap map = new Short2ShortOpenHashMap(); -+ { -+ this.map.defaultReturnValue(Short.MIN_VALUE); -+ } -+ -+ private static final short[] EMPTY_LIST = new short[0]; -+ -+ private short[] byIndex = EMPTY_LIST; -+ private short count; -+ -+ public int size() { -+ return (int)this.count; -+ } -+ -+ public short getRaw(final int index) { -+ return this.byIndex[index]; -+ } -+ -+ public void setMinCapacity(final int len) { -+ final short[] byIndex = this.byIndex; -+ if (byIndex.length < len) { -+ this.byIndex = Arrays.copyOf(byIndex, len); -+ } -+ } -+ -+ public boolean add(final short value) { -+ final int count = (int)this.count; -+ final short currIndex = this.map.putIfAbsent(value, (short)count); -+ -+ if (currIndex != Short.MIN_VALUE) { -+ return false; // already in this list -+ } -+ -+ short[] list = this.byIndex; -+ -+ if (list.length == count) { -+ // resize required -+ list = this.byIndex = Arrays.copyOf(list, (int)Math.max(4L, count * 2L)); // overflow results in negative -+ } -+ -+ list[count] = value; -+ this.count = (short)(count + 1); -+ -+ return true; -+ } -+ -+ public boolean remove(final short value) { -+ final short index = this.map.remove(value); -+ if (index == Short.MIN_VALUE) { -+ return false; -+ } -+ -+ // move the entry at the end to this index -+ final short endIndex = --this.count; -+ final short end = this.byIndex[endIndex]; -+ if (index != endIndex) { -+ // not empty after this call -+ this.map.put(end, index); -+ } -+ this.byIndex[(int)index] = end; -+ this.byIndex[(int)endIndex] = (short)0; -+ -+ return true; -+ } -+ -+ public void clear() { -+ this.count = (short)0; -+ this.map.clear(); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/moonrise/common/misc/LazyRunnable.java b/src/main/java/ca/spottedleaf/moonrise/common/misc/LazyRunnable.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c2d917c2eac55b8a4411a6e159f177f9428b1150 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/moonrise/common/misc/LazyRunnable.java -@@ -0,0 +1,22 @@ -+package ca.spottedleaf.moonrise.common.misc; -+ -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import java.lang.invoke.VarHandle; -+ -+public final class LazyRunnable implements Runnable { -+ -+ private volatile Runnable toRun; -+ private static final VarHandle TO_RUN_HANDLE = ConcurrentUtil.getVarHandle(LazyRunnable.class, "toRun", Runnable.class); -+ -+ public void setRunnable(final Runnable run) { -+ final Runnable prev = (Runnable)TO_RUN_HANDLE.compareAndExchange(this, (Runnable)null, run); -+ if (prev != null) { -+ throw new IllegalStateException("Runnable already set"); -+ } -+ } -+ -+ @Override -+ public void run() { -+ ((Runnable)TO_RUN_HANDLE.getVolatile(this)).run(); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java b/src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java -index ab093b0e8ac6f762921eb1d15f5217345c4eba05..bb44de17a37082e57f2292a4f470740be1d09b11 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java -+++ b/src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java -@@ -4,13 +4,17 @@ import ca.spottedleaf.moonrise.common.list.ReferenceList; - import ca.spottedleaf.moonrise.common.util.CoordinateUtils; - import ca.spottedleaf.moonrise.common.util.MoonriseConstants; - import ca.spottedleaf.moonrise.common.util.ChunkSystem; -+import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel; -+import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData; - import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickConstants; -+import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickServerLevel; - import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; - import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; - import net.minecraft.core.BlockPos; - import net.minecraft.server.level.ServerLevel; - import net.minecraft.server.level.ServerPlayer; - import net.minecraft.world.level.ChunkPos; -+import java.util.ArrayList; - - public final class NearbyPlayers { - -@@ -20,7 +24,27 @@ public final class NearbyPlayers { - GENERAL_REALLY_SMALL, - TICK_VIEW_DISTANCE, - VIEW_DISTANCE, -- SPAWN_RANGE, // Moonrise - chunk tick iteration -+ // Moonrise start - chunk tick iteration -+ SPAWN_RANGE { -+ @Override -+ void addTo(final ServerPlayer player, final ServerLevel world, final int chunkX, final int chunkZ) { -+ ((ChunkTickServerLevel)world).moonrise$addPlayerTickingRequest(chunkX, chunkZ); -+ } -+ -+ @Override -+ void removeFrom(final ServerPlayer player, final ServerLevel world, final int chunkX, final int chunkZ) { -+ ((ChunkTickServerLevel)world).moonrise$removePlayerTickingRequest(chunkX, chunkZ); -+ } -+ }; -+ // Moonrise end - chunk tick iteration -+ -+ void addTo(final ServerPlayer player, final ServerLevel world, final int chunkX, final int chunkZ) { -+ -+ } -+ -+ void removeFrom(final ServerPlayer player, final ServerLevel world, final int chunkX, final int chunkZ) { -+ -+ } - } - - private static final NearbyMapType[] MAP_TYPES = NearbyMapType.values(); -@@ -37,6 +61,12 @@ public final class NearbyPlayers { - private final ServerLevel world; - private final Reference2ReferenceOpenHashMap players = new Reference2ReferenceOpenHashMap<>(); - private final Long2ReferenceOpenHashMap byChunk = new Long2ReferenceOpenHashMap<>(); -+ private final Long2ReferenceOpenHashMap>[] directByChunk = new Long2ReferenceOpenHashMap[TOTAL_MAP_TYPES]; -+ { -+ for (int i = 0; i < this.directByChunk.length; ++i) { -+ this.directByChunk[i] = new Long2ReferenceOpenHashMap<>(); -+ } -+ } - - public NearbyPlayers(final ServerLevel world) { - this.world = world; -@@ -70,6 +100,16 @@ public final class NearbyPlayers { - } - } - -+ public void clear() { -+ if (this.players.isEmpty()) { -+ return; -+ } -+ -+ for (final ServerPlayer player : new ArrayList<>(this.players.keySet())) { -+ this.removePlayer(player); -+ } -+ } -+ - public void tickPlayer(final ServerPlayer player) { - final TrackedPlayer[] players = this.players.get(player); - if (players == null) { -@@ -94,38 +134,41 @@ public final class NearbyPlayers { - return this.byChunk.get(CoordinateUtils.getChunkKey(pos)); - } - -- public ReferenceList getPlayers(final BlockPos pos, final NearbyMapType type) { -- final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(pos)); -+ public TrackedChunk getChunk(final int chunkX, final int chunkZ) { -+ return this.byChunk.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ } - -- return chunk == null ? null : chunk.players[type.ordinal()]; -+ public ReferenceList getPlayers(final BlockPos pos, final NearbyMapType type) { -+ return this.directByChunk[type.ordinal()].get(CoordinateUtils.getChunkKey(pos)); - } - - public ReferenceList getPlayers(final ChunkPos pos, final NearbyMapType type) { -- final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(pos)); -- -- return chunk == null ? null : chunk.players[type.ordinal()]; -+ return this.directByChunk[type.ordinal()].get(CoordinateUtils.getChunkKey(pos)); - } - - public ReferenceList getPlayersByChunk(final int chunkX, final int chunkZ, final NearbyMapType type) { -- final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -- -- return chunk == null ? null : chunk.players[type.ordinal()]; -+ return this.directByChunk[type.ordinal()].get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); - } - - public ReferenceList getPlayersByBlock(final int blockX, final int blockZ, final NearbyMapType type) { -- final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(blockX >> 4, blockZ >> 4)); -- -- return chunk == null ? null : chunk.players[type.ordinal()]; -+ return this.directByChunk[type.ordinal()].get(CoordinateUtils.getChunkKey(blockX >> 4, blockZ >> 4)); - } - - public static final class TrackedChunk { - - private static final ServerPlayer[] EMPTY_PLAYERS_ARRAY = new ServerPlayer[0]; - -+ private final long chunkKey; -+ private final NearbyPlayers nearbyPlayers; - private final ReferenceList[] players = new ReferenceList[TOTAL_MAP_TYPES]; - private int nonEmptyLists; - private long updateCount; - -+ public TrackedChunk(final long chunkKey, final NearbyPlayers nearbyPlayers) { -+ this.chunkKey = chunkKey; -+ this.nearbyPlayers = nearbyPlayers; -+ } -+ - public boolean isEmpty() { - return this.nonEmptyLists == 0; - } -@@ -145,7 +188,9 @@ public final class NearbyPlayers { - final ReferenceList list = this.players[idx]; - if (list == null) { - ++this.nonEmptyLists; -- (this.players[idx] = new ReferenceList<>(EMPTY_PLAYERS_ARRAY)).add(player); -+ final ReferenceList players = (this.players[idx] = new ReferenceList<>(EMPTY_PLAYERS_ARRAY)); -+ this.nearbyPlayers.directByChunk[idx].put(this.chunkKey, players); -+ players.add(player); - return; - } - -@@ -169,6 +214,7 @@ public final class NearbyPlayers { - - if (list.size() == 0) { - this.players[idx] = null; -+ this.nearbyPlayers.directByChunk[idx].remove(this.chunkKey); - --this.nonEmptyLists; - } - } -@@ -187,9 +233,19 @@ public final class NearbyPlayers { - protected void addCallback(final ServerPlayer parameter, final int chunkX, final int chunkZ) { - final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ); - -- NearbyPlayers.this.byChunk.computeIfAbsent(chunkKey, (final long keyInMap) -> { -- return new TrackedChunk(); -- }).addPlayer(parameter, this.type); -+ final TrackedChunk chunk = NearbyPlayers.this.byChunk.get(chunkKey); -+ final NearbyMapType type = this.type; -+ if (chunk != null) { -+ chunk.addPlayer(parameter, type); -+ type.addTo(parameter, NearbyPlayers.this.world, chunkX, chunkZ); -+ } else { -+ final TrackedChunk created = new TrackedChunk(chunkKey, NearbyPlayers.this); -+ NearbyPlayers.this.byChunk.put(chunkKey, created); -+ created.addPlayer(parameter, type); -+ type.addTo(parameter, NearbyPlayers.this.world, chunkX, chunkZ); -+ -+ ((ChunkSystemLevel)NearbyPlayers.this.world).moonrise$requestChunkData(chunkKey).nearbyPlayers = created; -+ } - } - - @Override -@@ -201,10 +257,16 @@ public final class NearbyPlayers { - throw new IllegalStateException("Chunk should exist at " + new ChunkPos(chunkKey)); - } - -- chunk.removePlayer(parameter, this.type); -+ final NearbyMapType type = this.type; -+ chunk.removePlayer(parameter, type); -+ type.removeFrom(parameter, NearbyPlayers.this.world, chunkX, chunkZ); - - if (chunk.isEmpty()) { - NearbyPlayers.this.byChunk.remove(chunkKey); -+ final ChunkData chunkData = ((ChunkSystemLevel)NearbyPlayers.this.world).moonrise$releaseChunkData(chunkKey); -+ if (chunkData != null) { -+ chunkData.nearbyPlayers = null; -+ } - } - } - } -diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java b/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java -index da323a1105347d5cf4b946df10ded78a953236f2..94bba2b71918d79f54b3e28c35e76098ba0afd8c 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java -+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java -@@ -1,6 +1,7 @@ - package ca.spottedleaf.moonrise.common.util; - --import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.util.Priority; -+import ca.spottedleaf.moonrise.common.PlatformHooks; - import com.mojang.logging.LogUtils; - import net.minecraft.server.level.ChunkHolder; - import net.minecraft.server.level.FullChunkStatus; -@@ -24,15 +25,15 @@ public final class ChunkSystem { - } - - public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run) { -- scheduleChunkTask(level, chunkX, chunkZ, run, PrioritisedExecutor.Priority.NORMAL); -+ scheduleChunkTask(level, chunkX, chunkZ, run, Priority.NORMAL); - } - -- public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run, final PrioritisedExecutor.Priority priority) { -+ public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run, final Priority priority) { - level.chunkSource.mainThreadProcessor.execute(run); - } - - public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final boolean gen, -- final ChunkStatus toStatus, final boolean addTicket, final PrioritisedExecutor.Priority priority, -+ final ChunkStatus toStatus, final boolean addTicket, final Priority priority, - final Consumer onComplete) { - if (gen) { - scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete); -@@ -59,7 +60,7 @@ public final class ChunkSystem { - - private static long chunkLoadCounter = 0L; - public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final ChunkStatus toStatus, -- final boolean addTicket, final PrioritisedExecutor.Priority priority, final Consumer onComplete) { -+ final boolean addTicket, final Priority priority, final Consumer onComplete) { - if (!org.bukkit.Bukkit.isPrimaryThread()) { - scheduleChunkTask(level, chunkX, chunkZ, () -> { - scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete); -@@ -113,13 +114,13 @@ public final class ChunkSystem { - } - loadCallback.accept(result.orElse(null)); - }, (final Runnable r) -> { -- scheduleChunkTask(level, chunkX, chunkZ, r, PrioritisedExecutor.Priority.HIGHEST); -+ scheduleChunkTask(level, chunkX, chunkZ, r, Priority.HIGHEST); - }); - } - - public static void scheduleTickingState(final ServerLevel level, final int chunkX, final int chunkZ, - final FullChunkStatus toStatus, final boolean addTicket, -- final PrioritisedExecutor.Priority priority, final Consumer onComplete) { -+ final Priority priority, final Consumer onComplete) { - // This method goes unused until the chunk system rewrite - if (toStatus == FullChunkStatus.INACCESSIBLE) { - throw new IllegalArgumentException("Cannot wait for INACCESSIBLE status"); -@@ -196,7 +197,7 @@ public final class ChunkSystem { - } - loadCallback.accept(result.orElse(null)); - }, (final Runnable r) -> { -- scheduleChunkTask(level, chunkX, chunkZ, r, PrioritisedExecutor.Priority.HIGHEST); -+ scheduleChunkTask(level, chunkX, chunkZ, r, Priority.HIGHEST); - }); - } - -@@ -220,7 +221,10 @@ public final class ChunkSystem { - return getUpdatingChunkHolderCount(level) != 0; - } - -- public static boolean screenEntity(final ServerLevel level, final Entity entity) { -+ public static boolean screenEntity(final ServerLevel level, final Entity entity, final boolean fromDisk, final boolean event) { -+ if (!PlatformHooks.get().screenEntity(level, entity, fromDisk, event)) { -+ return false; -+ } - return true; - } - -diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/MixinWorkarounds.java b/src/main/java/ca/spottedleaf/moonrise/common/util/MixinWorkarounds.java -index ac6f284ee4469d16c5655328b2488d7612832353..97848869df61648fc415e4d39f409f433202c274 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/common/util/MixinWorkarounds.java -+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/MixinWorkarounds.java -@@ -3,8 +3,12 @@ package ca.spottedleaf.moonrise.common.util; - public final class MixinWorkarounds { - - // mixins tries to find the owner of the clone() method, which doesn't exist and NPEs -+ // https://github.com/FabricMC/Mixin/pull/147 - public static long[] clone(final long[] values) { - return values.clone(); - } - -+ public static byte[] clone(final byte[] values) { -+ return values.clone(); -+ } - } -diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java -index 3abe0bd2a820352b85306d554bf14a4cf6123091..c125c70a68130be373acc989053a6c0e487be924 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java -+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java -@@ -1,45 +1,100 @@ - package ca.spottedleaf.moonrise.common.util; - --import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadPool; -+import ca.spottedleaf.concurrentutil.executor.thread.PrioritisedThreadPool; -+import ca.spottedleaf.moonrise.common.PlatformHooks; - import org.slf4j.Logger; - import org.slf4j.LoggerFactory; --import java.io.File; -+import java.util.concurrent.TimeUnit; -+import java.util.concurrent.atomic.AtomicInteger; -+import java.util.function.Consumer; - - public final class MoonriseCommon { - - private static final Logger LOGGER = LoggerFactory.getLogger(MoonriseCommon.class); - -- // Paper start -- public static PrioritisedThreadPool WORKER_POOL; -- public static int WORKER_THREADS; -- public static void init(io.papermc.paper.configuration.GlobalConfiguration.ChunkSystem chunkSystem) { -- // Paper end -+ public static final PrioritisedThreadPool WORKER_POOL = new PrioritisedThreadPool( -+ new Consumer<>() { -+ private final AtomicInteger idGenerator = new AtomicInteger(); -+ -+ @Override -+ public void accept(Thread thread) { -+ thread.setDaemon(true); -+ thread.setName(PlatformHooks.get().getBrand() + " Common Worker #" + this.idGenerator.getAndIncrement()); -+ thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { -+ @Override -+ public void uncaughtException(final Thread thread, final Throwable throwable) { -+ LOGGER.error("Uncaught exception in thread " + thread.getName(), throwable); -+ } -+ }); -+ } -+ } -+ ); -+ public static final long WORKER_QUEUE_HOLD_TIME = (long)(20.0e6); // 20ms -+ public static final int CLIENT_DIVISION = 0; -+ public static final PrioritisedThreadPool.ExecutorGroup RENDER_EXECUTOR_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(CLIENT_DIVISION, 0); -+ public static final int SERVER_DIVISION = 1; -+ public static final PrioritisedThreadPool.ExecutorGroup PARALLEL_GEN_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(SERVER_DIVISION, 0); -+ public static final PrioritisedThreadPool.ExecutorGroup RADIUS_AWARE_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(SERVER_DIVISION, 0); -+ public static final PrioritisedThreadPool.ExecutorGroup LOAD_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(SERVER_DIVISION, 0); -+ -+ public static void adjustWorkerThreads(final int configWorkerThreads, final int configIoThreads) { - int defaultWorkerThreads = Runtime.getRuntime().availableProcessors() / 2; - if (defaultWorkerThreads <= 4) { - defaultWorkerThreads = defaultWorkerThreads <= 3 ? 1 : 2; - } else { - defaultWorkerThreads = defaultWorkerThreads / 2; - } -- defaultWorkerThreads = Integer.getInteger("Paper.WorkerThreadCount", Integer.valueOf(defaultWorkerThreads)); // Paper -+ defaultWorkerThreads = Integer.getInteger(PlatformHooks.get().getBrand() + ".WorkerThreadCount", Integer.valueOf(defaultWorkerThreads)); - -- int workerThreads = chunkSystem.workerThreads; // Paper -+ int workerThreads = configWorkerThreads; - - if (workerThreads <= 0) { - workerThreads = defaultWorkerThreads; - } - -- WORKER_POOL = new PrioritisedThreadPool( -- "Paper Worker Pool", workerThreads, // Paper -- (final Thread thread, final Integer id) -> { -- thread.setName("Paper Common Worker #" + id.intValue()); // Paper -+ final int ioThreads = Math.max(1, configIoThreads); -+ -+ WORKER_POOL.adjustThreadCount(workerThreads); -+ IO_POOL.adjustThreadCount(ioThreads); -+ -+ LOGGER.info(PlatformHooks.get().getBrand() + " is using " + workerThreads + " worker threads, " + ioThreads + " I/O threads"); -+ } -+ -+ public static final PrioritisedThreadPool IO_POOL = new PrioritisedThreadPool( -+ new Consumer<>() { -+ private final AtomicInteger idGenerator = new AtomicInteger(); -+ -+ @Override -+ public void accept(final Thread thread) { -+ thread.setDaemon(true); -+ thread.setName(PlatformHooks.get().getBrand() + " I/O Worker #" + this.idGenerator.getAndIncrement()); - thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { - @Override - public void uncaughtException(final Thread thread, final Throwable throwable) { - LOGGER.error("Uncaught exception in thread " + thread.getName(), throwable); - } - }); -- }, (long)(20.0e6)); // 20ms -- WORKER_THREADS = workerThreads; -+ } -+ } -+ ); -+ public static final long IO_QUEUE_HOLD_TIME = (long)(100.0e6); // 100ms -+ public static final PrioritisedThreadPool.ExecutorGroup CLIENT_PROFILER_IO_GROUP = IO_POOL.createExecutorGroup(CLIENT_DIVISION, 0); -+ public static final PrioritisedThreadPool.ExecutorGroup SERVER_REGION_IO_GROUP = IO_POOL.createExecutorGroup(SERVER_DIVISION, 0); -+ -+ public static void haltExecutors() { -+ MoonriseCommon.WORKER_POOL.shutdown(false); -+ LOGGER.info("Awaiting termination of worker pool for up to 60s..."); -+ if (!MoonriseCommon.WORKER_POOL.join(TimeUnit.SECONDS.toMillis(60L))) { -+ LOGGER.error("Worker pool did not shut down in time!"); -+ MoonriseCommon.WORKER_POOL.halt(false); -+ } -+ -+ MoonriseCommon.IO_POOL.shutdown(false); -+ LOGGER.info("Awaiting termination of I/O pool for up to 60s..."); -+ if (!MoonriseCommon.IO_POOL.join(TimeUnit.SECONDS.toMillis(60L))) { -+ LOGGER.error("I/O pool did not shut down in time!"); -+ MoonriseCommon.IO_POOL.halt(false); -+ } - } - - private MoonriseCommon() {} -diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java -index 1cf32d7d1bbc8a0a3f7cb9024c793f6744199f64..559c959aff3c9deef867b9e425fba3e2e669cac6 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java -+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java -@@ -1,8 +1,10 @@ - package ca.spottedleaf.moonrise.common.util; - -+import ca.spottedleaf.moonrise.common.PlatformHooks; -+ - public final class MoonriseConstants { - -- public static final int MAX_VIEW_DISTANCE = 32; -+ public static final int MAX_VIEW_DISTANCE = Integer.getInteger(PlatformHooks.get().getBrand() + ".MaxViewDistance", 32); - - private MoonriseConstants() {} - -diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/SimpleRandom.java b/src/main/java/ca/spottedleaf/moonrise/common/util/SimpleRandom.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a9ff1c1a70faf4b7a64b265932f07a8b8f00c1ff ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/SimpleRandom.java -@@ -0,0 +1,52 @@ -+package ca.spottedleaf.moonrise.common.util; -+ -+import net.minecraft.world.level.levelgen.LegacyRandomSource; -+ -+/** -+ * Avoid costly CAS of superclass -+ */ -+public final class SimpleRandom extends LegacyRandomSource { -+ -+ private static final long MULTIPLIER = 25214903917L; -+ private static final long ADDEND = 11L; -+ private static final int BITS = 48; -+ private static final long MASK = (1L << BITS) - 1; -+ -+ private long value; -+ -+ public SimpleRandom(final long seed) { -+ super(0L); -+ this.value = seed; -+ } -+ -+ @Override -+ public void setSeed(final long seed) { -+ this.value = (seed ^ MULTIPLIER) & MASK; -+ } -+ -+ private long advanceSeed() { -+ return this.value = ((this.value * MULTIPLIER) + ADDEND) & MASK; -+ } -+ -+ @Override -+ public int next(final int bits) { -+ return (int)(this.advanceSeed() >>> (BITS - bits)); -+ } -+ -+ @Override -+ public int nextInt() { -+ final long seed = this.advanceSeed(); -+ return (int)(seed >>> (BITS - Integer.SIZE)); -+ } -+ -+ @Override -+ public int nextInt(final int bound) { -+ if (bound <= 0) { -+ throw new IllegalArgumentException(); -+ } -+ -+ // https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ -+ final long value = this.advanceSeed() >>> (BITS - Integer.SIZE); -+ return (int)((value * (long)bound) >>> Integer.SIZE); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java b/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java -index 11b7f15755dde766140c29bedca456c80d53293f..217d1f908a36a5177ba3cbb80a33f73d4dab0fa0 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java -+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java -@@ -77,11 +77,15 @@ public class TickThread extends Thread { - } - - public TickThread(final Runnable run, final String name) { -- this(run, name, ID_GENERATOR.incrementAndGet()); -+ this(null, run, name); - } - -- private TickThread(final Runnable run, final String name, final int id) { -- super(run, name); -+ public TickThread(final ThreadGroup group, final Runnable run, final String name) { -+ this(group, run, name, ID_GENERATOR.incrementAndGet()); -+ } -+ -+ private TickThread(final ThreadGroup group, final Runnable run, final String name, final int id) { -+ super(group, run, name); - this.id = id; - } - -diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/WorldUtil.java b/src/main/java/ca/spottedleaf/moonrise/common/util/WorldUtil.java -index af9623240ff2d389aa7090623f507720e7dbab7d..efda2688ae1254a82ba7f6bf8bf597ef224cbb86 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/common/util/WorldUtil.java -+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/WorldUtil.java -@@ -8,11 +8,19 @@ public final class WorldUtil { - // min, max are inclusive - - public static int getMaxSection(final LevelHeightAccessor world) { -- return world.getMaxSection() - 1; // getMaxSection() is exclusive -+ return world.getMaxSectionY(); -+ } -+ -+ public static int getMaxSection(final Level world) { -+ return world.getMaxSectionY(); - } - - public static int getMinSection(final LevelHeightAccessor world) { -- return world.getMinSection(); -+ return world.getMinSectionY(); -+ } -+ -+ public static int getMinSection(final Level world) { -+ return world.getMinSectionY(); - } - - public static int getMaxLightSection(final LevelHeightAccessor world) { -diff --git a/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java b/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6f2dc0900dbf13a02410682eecda56cea4481346 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java -@@ -0,0 +1,199 @@ -+package ca.spottedleaf.moonrise.paper; -+ -+import ca.spottedleaf.moonrise.common.PlatformHooks; -+import com.mojang.datafixers.DataFixer; -+import net.minecraft.core.BlockPos; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.server.level.ChunkHolder; -+import net.minecraft.server.level.GenerationChunkHolder; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.util.datafix.DataFixTypes; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.level.BlockGetter; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.LevelChunk; -+import net.minecraft.world.level.chunk.ProtoChunk; -+import net.minecraft.world.level.chunk.storage.SerializableChunkData; -+import net.minecraft.world.level.entity.EntityTypeTest; -+import net.minecraft.world.phys.AABB; -+import java.util.List; -+import java.util.function.Predicate; -+ -+public final class PaperHooks implements PlatformHooks { -+ -+ @Override -+ public String getBrand() { -+ return "Paper"; -+ } -+ -+ @Override -+ public int getLightEmission(final BlockState blockState, final BlockGetter world, final BlockPos pos) { -+ return blockState.getLightEmission(); -+ } -+ -+ @Override -+ public Predicate maybeHasLightEmission() { -+ return (final BlockState state) -> { -+ return state.getLightEmission() != 0; -+ }; -+ } -+ -+ @Override -+ public boolean hasCurrentlyLoadingChunk() { -+ return false; -+ } -+ -+ @Override -+ public LevelChunk getCurrentlyLoadingChunk(final GenerationChunkHolder holder) { -+ return null; -+ } -+ -+ @Override -+ public void setCurrentlyLoading(final GenerationChunkHolder holder, final LevelChunk levelChunk) { -+ -+ } -+ -+ @Override -+ public void chunkFullStatusComplete(final LevelChunk newChunk, final ProtoChunk original) { -+ -+ } -+ -+ @Override -+ public boolean allowAsyncTicketUpdates() { -+ return true; -+ } -+ -+ @Override -+ public void onChunkHolderTicketChange(final ServerLevel world, final ChunkHolder holder, final int oldLevel, final int newLevel) { -+ -+ } -+ -+ @Override -+ public void chunkUnloadFromWorld(final LevelChunk chunk) { -+ -+ } -+ -+ @Override -+ public void chunkSyncSave(final ServerLevel world, final ChunkAccess chunk, final SerializableChunkData data) { -+ -+ } -+ -+ @Override -+ public void onChunkWatch(final ServerLevel world, final LevelChunk chunk, final ServerPlayer player) { -+ -+ } -+ -+ @Override -+ public void onChunkUnWatch(final ServerLevel world, final ChunkPos chunk, final ServerPlayer player) { -+ -+ } -+ -+ @Override -+ public void addToGetEntities(final Level world, final Entity entity, final AABB boundingBox, final Predicate predicate, final List into) { -+ -+ } -+ -+ @Override -+ public void addToGetEntities(final Level world, final EntityTypeTest entityTypeTest, final AABB boundingBox, final Predicate predicate, final List into, final int maxCount) { -+ -+ } -+ -+ @Override -+ public void entityMove(final Entity entity, final long oldSection, final long newSection) { -+ -+ } -+ -+ @Override -+ public boolean screenEntity(final ServerLevel world, final Entity entity, final boolean fromDisk, final boolean event) { -+ return true; -+ } -+ -+ @Override -+ public boolean configFixMC224294() { -+ return true; -+ } -+ -+ @Override -+ public boolean configAutoConfigSendDistance() { -+ -+ } -+ -+ @Override -+ public double configPlayerMaxLoadRate() { -+ -+ } -+ -+ @Override -+ public double configPlayerMaxGenRate() { -+ -+ } -+ -+ @Override -+ public double configPlayerMaxSendRate() { -+ -+ } -+ -+ @Override -+ public int configPlayerMaxConcurrentLoads() { -+ -+ } -+ -+ @Override -+ public int configPlayerMaxConcurrentGens() { -+ -+ } -+ -+ @Override -+ public long configAutoSaveInterval() { -+ -+ } -+ -+ @Override -+ public int configMaxAutoSavePerTick() { -+ -+ } -+ -+ @Override -+ public boolean configFixMC159283() { -+ return true; -+ } -+ -+ @Override -+ public boolean forceNoSave(final ChunkAccess chunk) { -+ return chunk instanceof LevelChunk levelChunk && levelChunk.mustNotSave; -+ } -+ -+ @Override -+ public CompoundTag convertNBT(final DataFixTypes type, final DataFixer dataFixer, final CompoundTag nbt, final int fromVersion, final int toVersion) { -+ return type.update(dataFixer, nbt, fromVersion, toVersion); -+ } -+ -+ @Override -+ public boolean hasMainChunkLoadHook() { -+ return false; -+ } -+ -+ @Override -+ public void mainChunkLoad(final ChunkAccess chunk, final SerializableChunkData chunkData) { -+ -+ } -+ -+ @Override -+ public List modifySavedEntities(final ServerLevel world, final int chunkX, final int chunkZ, final List entities) { -+ return entities; -+ } -+ -+ @Override -+ public void unloadEntity(final Entity entity) { -+ entity.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK, org.bukkit.event.entity.EntityRemoveEvent.Cause.UNLOAD); -+ } -+ -+ @Override -+ public int modifyEntityTrackingRange(final Entity entity, final int currentRange) { -+ return org.spigotmc.TrackingRange.getEntityTrackingRange(entity, currentRange); -+ } -+} -diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java -index 6baa313b8201ed23193d7885c85606b0899ade3c..3eb38271b6ca26099b2da04c2d969e32fd72b2af 100644 ---- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java -+++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java -@@ -94,15 +94,13 @@ public class PersistentEntitySectionManager implements A - private boolean addEntity(T entity, boolean existing) { - org.spigotmc.AsyncCatcher.catchOp("Entity add"); // Paper - // Paper start - chunk system hooks -- if (existing) { -- // I don't want to know why this is a generic type. -- Entity entityCasted = (Entity)entity; -- boolean wasRemoved = entityCasted.isRemoved(); -- boolean screened = ca.spottedleaf.moonrise.common.util.ChunkSystem.screenEntity((net.minecraft.server.level.ServerLevel)entityCasted.level(), entityCasted); -- if ((!wasRemoved && entityCasted.isRemoved()) || !screened) { -- // removed by callback -- return false; -- } -+ // I don't want to know why this is a generic type. -+ Entity entityCasted = (Entity)entity; -+ boolean wasRemoved = entityCasted.isRemoved(); -+ boolean screened = ca.spottedleaf.moonrise.common.util.ChunkSystem.screenEntity((net.minecraft.server.level.ServerLevel)entityCasted.level(), entityCasted, existing, false); -+ if ((!wasRemoved && entityCasted.isRemoved()) || !screened) { -+ // removed by callback -+ return false; - } - // Paper end - chunk system hooks - if (!this.addEntityUuid(entity)) { -diff --git a/src/main/resources/META-INF/services/ca.spottedleaf.moonrise.common.PlatformHooks b/src/main/resources/META-INF/services/ca.spottedleaf.moonrise.common.PlatformHooks -new file mode 100644 -index 0000000000000000000000000000000000000000..e57c3ca79677b1dfe7cf3db36f0406de7ea5bd0a ---- /dev/null -+++ b/src/main/resources/META-INF/services/ca.spottedleaf.moonrise.common.PlatformHooks -@@ -0,0 +1 @@ -+ca.spottedleaf.moonrise.paper.PaperHooks diff --git a/patches/server/0824-fixup-Paper-config-files.patch b/patches/server/0822-fixup-Paper-config-files.patch similarity index 95% rename from patches/server/0824-fixup-Paper-config-files.patch rename to patches/server/0822-fixup-Paper-config-files.patch index a5c6d251af..cdf7e80f80 100644 --- a/patches/server/0824-fixup-Paper-config-files.patch +++ b/patches/server/0822-fixup-Paper-config-files.patch @@ -5,7 +5,7 @@ Subject: [PATCH] fixup! Paper config files diff --git a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -index 73e8a524925ed6f2580d3bd01616646fabafda78..4bfe7e987450afa433fcad1847f6130654769416 100644 +index 73e8a524925ed6f2580d3bd01616646fabafda78..61893f8216ddaedd899b573322f3ad0088074ac5 100644 --- a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java +++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java @@ -30,6 +30,45 @@ public class GlobalConfiguration extends ConfigurationPart { @@ -81,7 +81,7 @@ index 73e8a524925ed6f2580d3bd01616646fabafda78..4bfe7e987450afa433fcad1847f61306 @PostProcess private void postProcess() { - //io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.init(this); -+ ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler.init(this); ++ } } diff --git a/patches/server/0823-fixup-MC-Utils.patch b/patches/server/0823-fixup-MC-Utils.patch index ea91946273..54f6874194 100644 --- a/patches/server/0823-fixup-MC-Utils.patch +++ b/patches/server/0823-fixup-MC-Utils.patch @@ -1,94 +1,1257 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Spottedleaf -Date: Wed, 23 Oct 2024 22:13:41 -0700 +Date: Mon, 21 Oct 2024 12:21:54 -0700 Subject: [PATCH] fixup! MC Utils diff --git a/src/main/java/ca/spottedleaf/moonrise/common/PlatformHooks.java b/src/main/java/ca/spottedleaf/moonrise/common/PlatformHooks.java -index 3c5ed66328ccf94c4744a191a7c63562dd08158d..deb64f7ebebcf6de91ffe0542d6b449a4db64da0 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/common/PlatformHooks.java +new file mode 100644 +index 0000000000000000000000000000000000000000..deb64f7ebebcf6de91ffe0542d6b449a4db64da0 +--- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/common/PlatformHooks.java -@@ -1,5 +1,6 @@ - package ca.spottedleaf.moonrise.common; - +@@ -0,0 +1,117 @@ ++package ca.spottedleaf.moonrise.common; ++ +import com.mojang.datafixers.DSL; - import com.mojang.datafixers.DataFixer; - import net.minecraft.core.BlockPos; - import net.minecraft.nbt.CompoundTag; -@@ -7,7 +8,6 @@ import net.minecraft.server.level.ChunkHolder; - import net.minecraft.server.level.GenerationChunkHolder; - import net.minecraft.server.level.ServerLevel; - import net.minecraft.server.level.ServerPlayer; --import net.minecraft.util.datafix.DataFixTypes; - import net.minecraft.world.entity.Entity; - import net.minecraft.world.level.BlockGetter; - import net.minecraft.world.level.ChunkPos; -@@ -88,7 +88,7 @@ public interface PlatformHooks { - // support for CB chunk mustNotSave - public boolean forceNoSave(final ChunkAccess chunk); - -- public CompoundTag convertNBT(final DataFixTypes type, final DataFixer dataFixer, final CompoundTag nbt, ++import com.mojang.datafixers.DataFixer; ++import net.minecraft.core.BlockPos; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.server.level.ChunkHolder; ++import net.minecraft.server.level.GenerationChunkHolder; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.level.BlockGetter; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.LevelChunk; ++import net.minecraft.world.level.chunk.ProtoChunk; ++import net.minecraft.world.level.chunk.storage.SerializableChunkData; ++import net.minecraft.world.level.entity.EntityTypeTest; ++import net.minecraft.world.phys.AABB; ++import java.util.List; ++import java.util.ServiceLoader; ++import java.util.function.Predicate; ++ ++public interface PlatformHooks { ++ public static PlatformHooks get() { ++ return Holder.INSTANCE; ++ } ++ ++ public String getBrand(); ++ ++ public int getLightEmission(final BlockState blockState, final BlockGetter world, final BlockPos pos); ++ ++ public Predicate maybeHasLightEmission(); ++ ++ public boolean hasCurrentlyLoadingChunk(); ++ ++ public LevelChunk getCurrentlyLoadingChunk(final GenerationChunkHolder holder); ++ ++ public void setCurrentlyLoading(final GenerationChunkHolder holder, final LevelChunk levelChunk); ++ ++ public void chunkFullStatusComplete(final LevelChunk newChunk, final ProtoChunk original); ++ ++ public boolean allowAsyncTicketUpdates(); ++ ++ public void onChunkHolderTicketChange(final ServerLevel world, final ChunkHolder holder, final int oldLevel, final int newLevel); ++ ++ public void chunkUnloadFromWorld(final LevelChunk chunk); ++ ++ public void chunkSyncSave(final ServerLevel world, final ChunkAccess chunk, final SerializableChunkData data); ++ ++ public void onChunkWatch(final ServerLevel world, final LevelChunk chunk, final ServerPlayer player); ++ ++ public void onChunkUnWatch(final ServerLevel world, final ChunkPos chunk, final ServerPlayer player); ++ ++ public void addToGetEntities(final Level world, final Entity entity, final AABB boundingBox, final Predicate predicate, ++ final List into); ++ ++ public void addToGetEntities(final Level world, final EntityTypeTest entityTypeTest, ++ final AABB boundingBox, final Predicate predicate, ++ final List into, final int maxCount); ++ ++ public void entityMove(final Entity entity, final long oldSection, final long newSection); ++ ++ public boolean screenEntity(final ServerLevel world, final Entity entity, final boolean fromDisk, final boolean event); ++ ++ public boolean configFixMC224294(); ++ ++ public boolean configAutoConfigSendDistance(); ++ ++ public double configPlayerMaxLoadRate(); ++ ++ public double configPlayerMaxGenRate(); ++ ++ public double configPlayerMaxSendRate(); ++ ++ public int configPlayerMaxConcurrentLoads(); ++ ++ public int configPlayerMaxConcurrentGens(); ++ ++ public long configAutoSaveInterval(); ++ ++ public int configMaxAutoSavePerTick(); ++ ++ public boolean configFixMC159283(); ++ ++ // support for CB chunk mustNotSave ++ public boolean forceNoSave(final ChunkAccess chunk); ++ + public CompoundTag convertNBT(final DSL.TypeReference type, final DataFixer dataFixer, final CompoundTag nbt, - final int fromVersion, final int toVersion); - - public boolean hasMainChunkLoadHook(); -@@ -99,6 +99,8 @@ public interface PlatformHooks { - - public void unloadEntity(final Entity entity); - ++ final int fromVersion, final int toVersion); ++ ++ public boolean hasMainChunkLoadHook(); ++ ++ public void mainChunkLoad(final ChunkAccess chunk, final SerializableChunkData chunkData); ++ ++ public List modifySavedEntities(final ServerLevel world, final int chunkX, final int chunkZ, final List entities); ++ ++ public void unloadEntity(final Entity entity); ++ + public void postLoadProtoChunk(final ServerLevel world, final ProtoChunk chunk); + - public int modifyEntityTrackingRange(final Entity entity, final int currentRange); ++ public int modifyEntityTrackingRange(final Entity entity, final int currentRange); ++ ++ public static final class Holder { ++ private Holder() { ++ } ++ ++ private static final PlatformHooks INSTANCE; ++ ++ static { ++ INSTANCE = ServiceLoader.load(PlatformHooks.class, PlatformHooks.class.getClassLoader()).findFirst() ++ .orElseThrow(() -> new RuntimeException("Failed to locate PlatformHooks")); ++ } ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java +index ba68998f6ef57b24c72fd833bd7de440de9501cc..7fed43a1e7bcf35c4d7fd3224837a47fedd59860 100644 +--- a/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java ++++ b/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java +@@ -13,15 +13,15 @@ import java.util.NoSuchElementException; + */ + public final class EntityList implements Iterable { - public static final class Holder { -diff --git a/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java b/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java -index 6f2dc0900dbf13a02410682eecda56cea4481346..ee514a767f69de69d86e1e88d70fe37c4ab84277 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java -+++ b/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java -@@ -1,14 +1,16 @@ - package ca.spottedleaf.moonrise.paper; - - import ca.spottedleaf.moonrise.common.PlatformHooks; -+import com.mojang.datafixers.DSL; - import com.mojang.datafixers.DataFixer; -+import com.mojang.serialization.Dynamic; - import net.minecraft.core.BlockPos; - import net.minecraft.nbt.CompoundTag; -+import net.minecraft.nbt.NbtOps; - import net.minecraft.server.level.ChunkHolder; - import net.minecraft.server.level.GenerationChunkHolder; - import net.minecraft.server.level.ServerLevel; - import net.minecraft.server.level.ServerPlayer; --import net.minecraft.util.datafix.DataFixTypes; - import net.minecraft.world.entity.Entity; - import net.minecraft.world.level.BlockGetter; - import net.minecraft.world.level.ChunkPos; -@@ -168,8 +170,11 @@ public final class PaperHooks implements PlatformHooks { +- protected final Int2IntOpenHashMap entityToIndex = new Int2IntOpenHashMap(2, 0.8f); ++ private final Int2IntOpenHashMap entityToIndex = new Int2IntOpenHashMap(2, 0.8f); + { + this.entityToIndex.defaultReturnValue(Integer.MIN_VALUE); } +- protected static final Entity[] EMPTY_LIST = new Entity[0]; ++ private static final Entity[] EMPTY_LIST = new Entity[0]; + +- protected Entity[] entities = EMPTY_LIST; +- protected int count; ++ private Entity[] entities = EMPTY_LIST; ++ private int count; + + public int size() { + return this.count; +@@ -94,10 +94,9 @@ public final class EntityList implements Iterable { + @Override -- public CompoundTag convertNBT(final DataFixTypes type, final DataFixer dataFixer, final CompoundTag nbt, final int fromVersion, final int toVersion) { -- return type.update(dataFixer, nbt, fromVersion, toVersion); + public Iterator iterator() { +- return new Iterator() { +- +- Entity lastRet; +- int current; ++ return new Iterator<>() { ++ private Entity lastRet; ++ private int current; + + @Override + public boolean hasNext() { +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/IBlockDataList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/IBlockDataList.java +deleted file mode 100644 +index fcfbca333234c09f7c056bbfcd9ac8860b20a8db..0000000000000000000000000000000000000000 +--- a/src/main/java/ca/spottedleaf/moonrise/common/list/IBlockDataList.java ++++ /dev/null +@@ -1,125 +0,0 @@ +-package ca.spottedleaf.moonrise.common.list; +- +-import it.unimi.dsi.fastutil.longs.LongIterator; +-import it.unimi.dsi.fastutil.shorts.Short2LongOpenHashMap; +-import java.util.Arrays; +-import net.minecraft.world.level.block.Block; +-import net.minecraft.world.level.block.state.BlockState; +-import net.minecraft.world.level.chunk.GlobalPalette; +- +-public final class IBlockDataList { +- +- private static final GlobalPalette GLOBAL_PALETTE = new GlobalPalette<>(Block.BLOCK_STATE_REGISTRY); +- +- // map of location -> (index | (location << 16) | (palette id << 32)) +- private final Short2LongOpenHashMap map = new Short2LongOpenHashMap(2, 0.8f); +- { +- this.map.defaultReturnValue(Long.MAX_VALUE); +- } +- +- private static final long[] EMPTY_LIST = new long[0]; +- +- private long[] byIndex = EMPTY_LIST; +- private int size; +- +- public static int getLocationKey(final int x, final int y, final int z) { +- return (x & 15) | (((z & 15) << 4)) | ((y & 255) << (4 + 4)); +- } +- +- public static BlockState getBlockDataFromRaw(final long raw) { +- return GLOBAL_PALETTE.valueFor((int)(raw >>> 32)); +- } +- +- public static int getIndexFromRaw(final long raw) { +- return (int)(raw & 0xFFFF); +- } +- +- public static int getLocationFromRaw(final long raw) { +- return (int)((raw >>> 16) & 0xFFFF); +- } +- +- public static long getRawFromValues(final int index, final int location, final BlockState data) { +- return (long)index | ((long)location << 16) | (((long)GLOBAL_PALETTE.idFor(data)) << 32); +- } +- +- public static long setIndexRawValues(final long value, final int index) { +- return value & ~(0xFFFF) | (index); +- } +- +- public long add(final int x, final int y, final int z, final BlockState data) { +- return this.add(getLocationKey(x, y, z), data); +- } +- +- public long add(final int location, final BlockState data) { +- final long curr = this.map.get((short)location); +- +- if (curr == Long.MAX_VALUE) { +- final int index = this.size++; +- final long raw = getRawFromValues(index, location, data); +- this.map.put((short)location, raw); +- +- if (index >= this.byIndex.length) { +- this.byIndex = Arrays.copyOf(this.byIndex, (int)Math.max(4L, this.byIndex.length * 2L)); +- } +- +- this.byIndex[index] = raw; +- return raw; +- } else { +- final int index = getIndexFromRaw(curr); +- final long raw = this.byIndex[index] = getRawFromValues(index, location, data); +- +- this.map.put((short)location, raw); +- +- return raw; +- } +- } +- +- public long remove(final int x, final int y, final int z) { +- return this.remove(getLocationKey(x, y, z)); +- } +- +- public long remove(final int location) { +- final long ret = this.map.remove((short)location); +- final int index = getIndexFromRaw(ret); +- if (ret == Long.MAX_VALUE) { +- return ret; +- } +- +- // move the entry at the end to this index +- final int endIndex = --this.size; +- final long end = this.byIndex[endIndex]; +- if (index != endIndex) { +- // not empty after this call +- this.map.put((short)getLocationFromRaw(end), setIndexRawValues(end, index)); +- } +- this.byIndex[index] = end; +- this.byIndex[endIndex] = 0L; +- +- return ret; +- } +- +- public int size() { +- return this.size; +- } +- +- public long getRaw(final int index) { +- return this.byIndex[index]; +- } +- +- public int getLocation(final int index) { +- return getLocationFromRaw(this.getRaw(index)); +- } +- +- public BlockState getData(final int index) { +- return getBlockDataFromRaw(this.getRaw(index)); +- } +- +- public void clear() { +- this.size = 0; +- this.map.clear(); +- } +- +- public LongIterator getRawIterator() { +- return this.map.values().iterator(); +- } +-} +\ No newline at end of file +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/IntList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/IntList.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9f3b25bb2439f283f878db93973a02fcdcd14eed +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/common/list/IntList.java +@@ -0,0 +1,77 @@ ++package ca.spottedleaf.moonrise.common.list; ++ ++import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; ++import java.util.Arrays; ++ ++public final class IntList { ++ ++ private final Int2IntOpenHashMap map = new Int2IntOpenHashMap(); ++ { ++ this.map.defaultReturnValue(Integer.MIN_VALUE); ++ } ++ ++ private static final int[] EMPTY_LIST = new int[0]; ++ ++ private int[] byIndex = EMPTY_LIST; ++ private int count; ++ ++ public int size() { ++ return this.count; ++ } ++ ++ public void setMinCapacity(final int len) { ++ final int[] byIndex = this.byIndex; ++ if (byIndex.length < len) { ++ this.byIndex = Arrays.copyOf(byIndex, len); ++ } ++ } ++ ++ public int getRaw(final int index) { ++ return this.byIndex[index]; ++ } ++ ++ public boolean add(final int value) { ++ final int count = this.count; ++ final int currIndex = this.map.putIfAbsent(value, count); ++ ++ if (currIndex != Integer.MIN_VALUE) { ++ return false; // already in this list ++ } ++ ++ int[] list = this.byIndex; ++ ++ if (list.length == count) { ++ // resize required ++ list = this.byIndex = Arrays.copyOf(list, (int)Math.max(4L, count * 2L)); // overflow results in negative ++ } ++ ++ list[count] = value; ++ this.count = count + 1; ++ ++ return true; ++ } ++ ++ public boolean remove(final int value) { ++ final int index = this.map.remove(value); ++ if (index == Integer.MIN_VALUE) { ++ return false; ++ } ++ ++ // move the entry at the end to this index ++ final int endIndex = --this.count; ++ final int end = this.byIndex[endIndex]; ++ if (index != endIndex) { ++ // not empty after this call ++ this.map.put(end, index); ++ } ++ this.byIndex[index] = end; ++ this.byIndex[endIndex] = 0; ++ ++ return true; ++ } ++ ++ public void clear() { ++ this.count = 0; ++ this.map.clear(); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/ShortList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/ShortList.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2bae9949ef325d0001aa638150fbbdf968367e75 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/common/list/ShortList.java +@@ -0,0 +1,77 @@ ++package ca.spottedleaf.moonrise.common.list; ++ ++import it.unimi.dsi.fastutil.shorts.Short2ShortOpenHashMap; ++import java.util.Arrays; ++ ++public final class ShortList { ++ ++ private final Short2ShortOpenHashMap map = new Short2ShortOpenHashMap(); ++ { ++ this.map.defaultReturnValue(Short.MIN_VALUE); ++ } ++ ++ private static final short[] EMPTY_LIST = new short[0]; ++ ++ private short[] byIndex = EMPTY_LIST; ++ private short count; ++ ++ public int size() { ++ return (int)this.count; ++ } ++ ++ public short getRaw(final int index) { ++ return this.byIndex[index]; ++ } ++ ++ public void setMinCapacity(final int len) { ++ final short[] byIndex = this.byIndex; ++ if (byIndex.length < len) { ++ this.byIndex = Arrays.copyOf(byIndex, len); ++ } ++ } ++ ++ public boolean add(final short value) { ++ final int count = (int)this.count; ++ final short currIndex = this.map.putIfAbsent(value, (short)count); ++ ++ if (currIndex != Short.MIN_VALUE) { ++ return false; // already in this list ++ } ++ ++ short[] list = this.byIndex; ++ ++ if (list.length == count) { ++ // resize required ++ list = this.byIndex = Arrays.copyOf(list, (int)Math.max(4L, count * 2L)); // overflow results in negative ++ } ++ ++ list[count] = value; ++ this.count = (short)(count + 1); ++ ++ return true; ++ } ++ ++ public boolean remove(final short value) { ++ final short index = this.map.remove(value); ++ if (index == Short.MIN_VALUE) { ++ return false; ++ } ++ ++ // move the entry at the end to this index ++ final short endIndex = --this.count; ++ final short end = this.byIndex[endIndex]; ++ if (index != endIndex) { ++ // not empty after this call ++ this.map.put(end, index); ++ } ++ this.byIndex[(int)index] = end; ++ this.byIndex[(int)endIndex] = (short)0; ++ ++ return true; ++ } ++ ++ public void clear() { ++ this.count = (short)0; ++ this.map.clear(); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/misc/LazyRunnable.java b/src/main/java/ca/spottedleaf/moonrise/common/misc/LazyRunnable.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c2d917c2eac55b8a4411a6e159f177f9428b1150 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/common/misc/LazyRunnable.java +@@ -0,0 +1,22 @@ ++package ca.spottedleaf.moonrise.common.misc; ++ ++import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; ++import java.lang.invoke.VarHandle; ++ ++public final class LazyRunnable implements Runnable { ++ ++ private volatile Runnable toRun; ++ private static final VarHandle TO_RUN_HANDLE = ConcurrentUtil.getVarHandle(LazyRunnable.class, "toRun", Runnable.class); ++ ++ public void setRunnable(final Runnable run) { ++ final Runnable prev = (Runnable)TO_RUN_HANDLE.compareAndExchange(this, (Runnable)null, run); ++ if (prev != null) { ++ throw new IllegalStateException("Runnable already set"); ++ } ++ } ++ ++ @Override ++ public void run() { ++ ((Runnable)TO_RUN_HANDLE.getVolatile(this)).run(); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java b/src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java +index ab093b0e8ac6f762921eb1d15f5217345c4eba05..bb44de17a37082e57f2292a4f470740be1d09b11 100644 +--- a/src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java ++++ b/src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java +@@ -4,13 +4,17 @@ import ca.spottedleaf.moonrise.common.list.ReferenceList; + import ca.spottedleaf.moonrise.common.util.CoordinateUtils; + import ca.spottedleaf.moonrise.common.util.MoonriseConstants; + import ca.spottedleaf.moonrise.common.util.ChunkSystem; ++import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel; ++import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData; + import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickConstants; ++import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickServerLevel; + import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; + import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; + import net.minecraft.core.BlockPos; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.server.level.ServerPlayer; + import net.minecraft.world.level.ChunkPos; ++import java.util.ArrayList; + + public final class NearbyPlayers { + +@@ -20,7 +24,27 @@ public final class NearbyPlayers { + GENERAL_REALLY_SMALL, + TICK_VIEW_DISTANCE, + VIEW_DISTANCE, +- SPAWN_RANGE, // Moonrise - chunk tick iteration ++ // Moonrise start - chunk tick iteration ++ SPAWN_RANGE { ++ @Override ++ void addTo(final ServerPlayer player, final ServerLevel world, final int chunkX, final int chunkZ) { ++ ((ChunkTickServerLevel)world).moonrise$addPlayerTickingRequest(chunkX, chunkZ); ++ } ++ ++ @Override ++ void removeFrom(final ServerPlayer player, final ServerLevel world, final int chunkX, final int chunkZ) { ++ ((ChunkTickServerLevel)world).moonrise$removePlayerTickingRequest(chunkX, chunkZ); ++ } ++ }; ++ // Moonrise end - chunk tick iteration ++ ++ void addTo(final ServerPlayer player, final ServerLevel world, final int chunkX, final int chunkZ) { ++ ++ } ++ ++ void removeFrom(final ServerPlayer player, final ServerLevel world, final int chunkX, final int chunkZ) { ++ ++ } + } + + private static final NearbyMapType[] MAP_TYPES = NearbyMapType.values(); +@@ -37,6 +61,12 @@ public final class NearbyPlayers { + private final ServerLevel world; + private final Reference2ReferenceOpenHashMap players = new Reference2ReferenceOpenHashMap<>(); + private final Long2ReferenceOpenHashMap byChunk = new Long2ReferenceOpenHashMap<>(); ++ private final Long2ReferenceOpenHashMap>[] directByChunk = new Long2ReferenceOpenHashMap[TOTAL_MAP_TYPES]; ++ { ++ for (int i = 0; i < this.directByChunk.length; ++i) { ++ this.directByChunk[i] = new Long2ReferenceOpenHashMap<>(); ++ } ++ } + + public NearbyPlayers(final ServerLevel world) { + this.world = world; +@@ -70,6 +100,16 @@ public final class NearbyPlayers { + } + } + ++ public void clear() { ++ if (this.players.isEmpty()) { ++ return; ++ } ++ ++ for (final ServerPlayer player : new ArrayList<>(this.players.keySet())) { ++ this.removePlayer(player); ++ } ++ } ++ + public void tickPlayer(final ServerPlayer player) { + final TrackedPlayer[] players = this.players.get(player); + if (players == null) { +@@ -94,38 +134,41 @@ public final class NearbyPlayers { + return this.byChunk.get(CoordinateUtils.getChunkKey(pos)); + } + +- public ReferenceList getPlayers(final BlockPos pos, final NearbyMapType type) { +- final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(pos)); ++ public TrackedChunk getChunk(final int chunkX, final int chunkZ) { ++ return this.byChunk.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); ++ } + +- return chunk == null ? null : chunk.players[type.ordinal()]; ++ public ReferenceList getPlayers(final BlockPos pos, final NearbyMapType type) { ++ return this.directByChunk[type.ordinal()].get(CoordinateUtils.getChunkKey(pos)); + } + + public ReferenceList getPlayers(final ChunkPos pos, final NearbyMapType type) { +- final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(pos)); +- +- return chunk == null ? null : chunk.players[type.ordinal()]; ++ return this.directByChunk[type.ordinal()].get(CoordinateUtils.getChunkKey(pos)); + } + + public ReferenceList getPlayersByChunk(final int chunkX, final int chunkZ, final NearbyMapType type) { +- final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); +- +- return chunk == null ? null : chunk.players[type.ordinal()]; ++ return this.directByChunk[type.ordinal()].get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); + } + + public ReferenceList getPlayersByBlock(final int blockX, final int blockZ, final NearbyMapType type) { +- final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(blockX >> 4, blockZ >> 4)); +- +- return chunk == null ? null : chunk.players[type.ordinal()]; ++ return this.directByChunk[type.ordinal()].get(CoordinateUtils.getChunkKey(blockX >> 4, blockZ >> 4)); + } + + public static final class TrackedChunk { + + private static final ServerPlayer[] EMPTY_PLAYERS_ARRAY = new ServerPlayer[0]; + ++ private final long chunkKey; ++ private final NearbyPlayers nearbyPlayers; + private final ReferenceList[] players = new ReferenceList[TOTAL_MAP_TYPES]; + private int nonEmptyLists; + private long updateCount; + ++ public TrackedChunk(final long chunkKey, final NearbyPlayers nearbyPlayers) { ++ this.chunkKey = chunkKey; ++ this.nearbyPlayers = nearbyPlayers; ++ } ++ + public boolean isEmpty() { + return this.nonEmptyLists == 0; + } +@@ -145,7 +188,9 @@ public final class NearbyPlayers { + final ReferenceList list = this.players[idx]; + if (list == null) { + ++this.nonEmptyLists; +- (this.players[idx] = new ReferenceList<>(EMPTY_PLAYERS_ARRAY)).add(player); ++ final ReferenceList players = (this.players[idx] = new ReferenceList<>(EMPTY_PLAYERS_ARRAY)); ++ this.nearbyPlayers.directByChunk[idx].put(this.chunkKey, players); ++ players.add(player); + return; + } + +@@ -169,6 +214,7 @@ public final class NearbyPlayers { + + if (list.size() == 0) { + this.players[idx] = null; ++ this.nearbyPlayers.directByChunk[idx].remove(this.chunkKey); + --this.nonEmptyLists; + } + } +@@ -187,9 +233,19 @@ public final class NearbyPlayers { + protected void addCallback(final ServerPlayer parameter, final int chunkX, final int chunkZ) { + final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ); + +- NearbyPlayers.this.byChunk.computeIfAbsent(chunkKey, (final long keyInMap) -> { +- return new TrackedChunk(); +- }).addPlayer(parameter, this.type); ++ final TrackedChunk chunk = NearbyPlayers.this.byChunk.get(chunkKey); ++ final NearbyMapType type = this.type; ++ if (chunk != null) { ++ chunk.addPlayer(parameter, type); ++ type.addTo(parameter, NearbyPlayers.this.world, chunkX, chunkZ); ++ } else { ++ final TrackedChunk created = new TrackedChunk(chunkKey, NearbyPlayers.this); ++ NearbyPlayers.this.byChunk.put(chunkKey, created); ++ created.addPlayer(parameter, type); ++ type.addTo(parameter, NearbyPlayers.this.world, chunkX, chunkZ); ++ ++ ((ChunkSystemLevel)NearbyPlayers.this.world).moonrise$requestChunkData(chunkKey).nearbyPlayers = created; ++ } + } + + @Override +@@ -201,10 +257,16 @@ public final class NearbyPlayers { + throw new IllegalStateException("Chunk should exist at " + new ChunkPos(chunkKey)); + } + +- chunk.removePlayer(parameter, this.type); ++ final NearbyMapType type = this.type; ++ chunk.removePlayer(parameter, type); ++ type.removeFrom(parameter, NearbyPlayers.this.world, chunkX, chunkZ); + + if (chunk.isEmpty()) { + NearbyPlayers.this.byChunk.remove(chunkKey); ++ final ChunkData chunkData = ((ChunkSystemLevel)NearbyPlayers.this.world).moonrise$releaseChunkData(chunkKey); ++ if (chunkData != null) { ++ chunkData.nearbyPlayers = null; ++ } + } + } + } +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java b/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java +index da323a1105347d5cf4b946df10ded78a953236f2..94bba2b71918d79f54b3e28c35e76098ba0afd8c 100644 +--- a/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java ++++ b/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java +@@ -1,6 +1,7 @@ + package ca.spottedleaf.moonrise.common.util; + +-import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; ++import ca.spottedleaf.concurrentutil.util.Priority; ++import ca.spottedleaf.moonrise.common.PlatformHooks; + import com.mojang.logging.LogUtils; + import net.minecraft.server.level.ChunkHolder; + import net.minecraft.server.level.FullChunkStatus; +@@ -24,15 +25,15 @@ public final class ChunkSystem { + } + + public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run) { +- scheduleChunkTask(level, chunkX, chunkZ, run, PrioritisedExecutor.Priority.NORMAL); ++ scheduleChunkTask(level, chunkX, chunkZ, run, Priority.NORMAL); + } + +- public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run, final PrioritisedExecutor.Priority priority) { ++ public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run, final Priority priority) { + level.chunkSource.mainThreadProcessor.execute(run); + } + + public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final boolean gen, +- final ChunkStatus toStatus, final boolean addTicket, final PrioritisedExecutor.Priority priority, ++ final ChunkStatus toStatus, final boolean addTicket, final Priority priority, + final Consumer onComplete) { + if (gen) { + scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete); +@@ -59,7 +60,7 @@ public final class ChunkSystem { + + private static long chunkLoadCounter = 0L; + public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final ChunkStatus toStatus, +- final boolean addTicket, final PrioritisedExecutor.Priority priority, final Consumer onComplete) { ++ final boolean addTicket, final Priority priority, final Consumer onComplete) { + if (!org.bukkit.Bukkit.isPrimaryThread()) { + scheduleChunkTask(level, chunkX, chunkZ, () -> { + scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete); +@@ -113,13 +114,13 @@ public final class ChunkSystem { + } + loadCallback.accept(result.orElse(null)); + }, (final Runnable r) -> { +- scheduleChunkTask(level, chunkX, chunkZ, r, PrioritisedExecutor.Priority.HIGHEST); ++ scheduleChunkTask(level, chunkX, chunkZ, r, Priority.HIGHEST); + }); + } + + public static void scheduleTickingState(final ServerLevel level, final int chunkX, final int chunkZ, + final FullChunkStatus toStatus, final boolean addTicket, +- final PrioritisedExecutor.Priority priority, final Consumer onComplete) { ++ final Priority priority, final Consumer onComplete) { + // This method goes unused until the chunk system rewrite + if (toStatus == FullChunkStatus.INACCESSIBLE) { + throw new IllegalArgumentException("Cannot wait for INACCESSIBLE status"); +@@ -196,7 +197,7 @@ public final class ChunkSystem { + } + loadCallback.accept(result.orElse(null)); + }, (final Runnable r) -> { +- scheduleChunkTask(level, chunkX, chunkZ, r, PrioritisedExecutor.Priority.HIGHEST); ++ scheduleChunkTask(level, chunkX, chunkZ, r, Priority.HIGHEST); + }); + } + +@@ -220,7 +221,10 @@ public final class ChunkSystem { + return getUpdatingChunkHolderCount(level) != 0; + } + +- public static boolean screenEntity(final ServerLevel level, final Entity entity) { ++ public static boolean screenEntity(final ServerLevel level, final Entity entity, final boolean fromDisk, final boolean event) { ++ if (!PlatformHooks.get().screenEntity(level, entity, fromDisk, event)) { ++ return false; ++ } + return true; + } + +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/MixinWorkarounds.java b/src/main/java/ca/spottedleaf/moonrise/common/util/MixinWorkarounds.java +index ac6f284ee4469d16c5655328b2488d7612832353..97848869df61648fc415e4d39f409f433202c274 100644 +--- a/src/main/java/ca/spottedleaf/moonrise/common/util/MixinWorkarounds.java ++++ b/src/main/java/ca/spottedleaf/moonrise/common/util/MixinWorkarounds.java +@@ -3,8 +3,12 @@ package ca.spottedleaf.moonrise.common.util; + public final class MixinWorkarounds { + + // mixins tries to find the owner of the clone() method, which doesn't exist and NPEs ++ // https://github.com/FabricMC/Mixin/pull/147 + public static long[] clone(final long[] values) { + return values.clone(); + } + ++ public static byte[] clone(final byte[] values) { ++ return values.clone(); ++ } + } +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java +index 3abe0bd2a820352b85306d554bf14a4cf6123091..c125c70a68130be373acc989053a6c0e487be924 100644 +--- a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java ++++ b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java +@@ -1,45 +1,100 @@ + package ca.spottedleaf.moonrise.common.util; + +-import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadPool; ++import ca.spottedleaf.concurrentutil.executor.thread.PrioritisedThreadPool; ++import ca.spottedleaf.moonrise.common.PlatformHooks; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; +-import java.io.File; ++import java.util.concurrent.TimeUnit; ++import java.util.concurrent.atomic.AtomicInteger; ++import java.util.function.Consumer; + + public final class MoonriseCommon { + + private static final Logger LOGGER = LoggerFactory.getLogger(MoonriseCommon.class); + +- // Paper start +- public static PrioritisedThreadPool WORKER_POOL; +- public static int WORKER_THREADS; +- public static void init(io.papermc.paper.configuration.GlobalConfiguration.ChunkSystem chunkSystem) { +- // Paper end ++ public static final PrioritisedThreadPool WORKER_POOL = new PrioritisedThreadPool( ++ new Consumer<>() { ++ private final AtomicInteger idGenerator = new AtomicInteger(); ++ ++ @Override ++ public void accept(Thread thread) { ++ thread.setDaemon(true); ++ thread.setName(PlatformHooks.get().getBrand() + " Common Worker #" + this.idGenerator.getAndIncrement()); ++ thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { ++ @Override ++ public void uncaughtException(final Thread thread, final Throwable throwable) { ++ LOGGER.error("Uncaught exception in thread " + thread.getName(), throwable); ++ } ++ }); ++ } ++ } ++ ); ++ public static final long WORKER_QUEUE_HOLD_TIME = (long)(20.0e6); // 20ms ++ public static final int CLIENT_DIVISION = 0; ++ public static final PrioritisedThreadPool.ExecutorGroup RENDER_EXECUTOR_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(CLIENT_DIVISION, 0); ++ public static final int SERVER_DIVISION = 1; ++ public static final PrioritisedThreadPool.ExecutorGroup PARALLEL_GEN_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(SERVER_DIVISION, 0); ++ public static final PrioritisedThreadPool.ExecutorGroup RADIUS_AWARE_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(SERVER_DIVISION, 0); ++ public static final PrioritisedThreadPool.ExecutorGroup LOAD_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(SERVER_DIVISION, 0); ++ ++ public static void adjustWorkerThreads(final int configWorkerThreads, final int configIoThreads) { + int defaultWorkerThreads = Runtime.getRuntime().availableProcessors() / 2; + if (defaultWorkerThreads <= 4) { + defaultWorkerThreads = defaultWorkerThreads <= 3 ? 1 : 2; + } else { + defaultWorkerThreads = defaultWorkerThreads / 2; + } +- defaultWorkerThreads = Integer.getInteger("Paper.WorkerThreadCount", Integer.valueOf(defaultWorkerThreads)); // Paper ++ defaultWorkerThreads = Integer.getInteger(PlatformHooks.get().getBrand() + ".WorkerThreadCount", Integer.valueOf(defaultWorkerThreads)); + +- int workerThreads = chunkSystem.workerThreads; // Paper ++ int workerThreads = configWorkerThreads; + + if (workerThreads <= 0) { + workerThreads = defaultWorkerThreads; + } + +- WORKER_POOL = new PrioritisedThreadPool( +- "Paper Worker Pool", workerThreads, // Paper +- (final Thread thread, final Integer id) -> { +- thread.setName("Paper Common Worker #" + id.intValue()); // Paper ++ final int ioThreads = Math.max(1, configIoThreads); ++ ++ WORKER_POOL.adjustThreadCount(workerThreads); ++ IO_POOL.adjustThreadCount(ioThreads); ++ ++ LOGGER.info(PlatformHooks.get().getBrand() + " is using " + workerThreads + " worker threads, " + ioThreads + " I/O threads"); ++ } ++ ++ public static final PrioritisedThreadPool IO_POOL = new PrioritisedThreadPool( ++ new Consumer<>() { ++ private final AtomicInteger idGenerator = new AtomicInteger(); ++ ++ @Override ++ public void accept(final Thread thread) { ++ thread.setDaemon(true); ++ thread.setName(PlatformHooks.get().getBrand() + " I/O Worker #" + this.idGenerator.getAndIncrement()); + thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException(final Thread thread, final Throwable throwable) { + LOGGER.error("Uncaught exception in thread " + thread.getName(), throwable); + } + }); +- }, (long)(20.0e6)); // 20ms +- WORKER_THREADS = workerThreads; ++ } ++ } ++ ); ++ public static final long IO_QUEUE_HOLD_TIME = (long)(100.0e6); // 100ms ++ public static final PrioritisedThreadPool.ExecutorGroup CLIENT_PROFILER_IO_GROUP = IO_POOL.createExecutorGroup(CLIENT_DIVISION, 0); ++ public static final PrioritisedThreadPool.ExecutorGroup SERVER_REGION_IO_GROUP = IO_POOL.createExecutorGroup(SERVER_DIVISION, 0); ++ ++ public static void haltExecutors() { ++ MoonriseCommon.WORKER_POOL.shutdown(false); ++ LOGGER.info("Awaiting termination of worker pool for up to 60s..."); ++ if (!MoonriseCommon.WORKER_POOL.join(TimeUnit.SECONDS.toMillis(60L))) { ++ LOGGER.error("Worker pool did not shut down in time!"); ++ MoonriseCommon.WORKER_POOL.halt(false); ++ } ++ ++ MoonriseCommon.IO_POOL.shutdown(false); ++ LOGGER.info("Awaiting termination of I/O pool for up to 60s..."); ++ if (!MoonriseCommon.IO_POOL.join(TimeUnit.SECONDS.toMillis(60L))) { ++ LOGGER.error("I/O pool did not shut down in time!"); ++ MoonriseCommon.IO_POOL.halt(false); ++ } + } + + private MoonriseCommon() {} +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java +index 1cf32d7d1bbc8a0a3f7cb9024c793f6744199f64..559c959aff3c9deef867b9e425fba3e2e669cac6 100644 +--- a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java ++++ b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java +@@ -1,8 +1,10 @@ + package ca.spottedleaf.moonrise.common.util; + ++import ca.spottedleaf.moonrise.common.PlatformHooks; ++ + public final class MoonriseConstants { + +- public static final int MAX_VIEW_DISTANCE = 32; ++ public static final int MAX_VIEW_DISTANCE = Integer.getInteger(PlatformHooks.get().getBrand() + ".MaxViewDistance", 32); + + private MoonriseConstants() {} + +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/SimpleRandom.java b/src/main/java/ca/spottedleaf/moonrise/common/util/SimpleRandom.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a9ff1c1a70faf4b7a64b265932f07a8b8f00c1ff +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/common/util/SimpleRandom.java +@@ -0,0 +1,52 @@ ++package ca.spottedleaf.moonrise.common.util; ++ ++import net.minecraft.world.level.levelgen.LegacyRandomSource; ++ ++/** ++ * Avoid costly CAS of superclass ++ */ ++public final class SimpleRandom extends LegacyRandomSource { ++ ++ private static final long MULTIPLIER = 25214903917L; ++ private static final long ADDEND = 11L; ++ private static final int BITS = 48; ++ private static final long MASK = (1L << BITS) - 1; ++ ++ private long value; ++ ++ public SimpleRandom(final long seed) { ++ super(0L); ++ this.value = seed; ++ } ++ ++ @Override ++ public void setSeed(final long seed) { ++ this.value = (seed ^ MULTIPLIER) & MASK; ++ } ++ ++ private long advanceSeed() { ++ return this.value = ((this.value * MULTIPLIER) + ADDEND) & MASK; ++ } ++ ++ @Override ++ public int next(final int bits) { ++ return (int)(this.advanceSeed() >>> (BITS - bits)); ++ } ++ ++ @Override ++ public int nextInt() { ++ final long seed = this.advanceSeed(); ++ return (int)(seed >>> (BITS - Integer.SIZE)); ++ } ++ ++ @Override ++ public int nextInt(final int bound) { ++ if (bound <= 0) { ++ throw new IllegalArgumentException(); ++ } ++ ++ // https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ ++ final long value = this.advanceSeed() >>> (BITS - Integer.SIZE); ++ return (int)((value * (long)bound) >>> Integer.SIZE); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java b/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java +index 11b7f15755dde766140c29bedca456c80d53293f..217d1f908a36a5177ba3cbb80a33f73d4dab0fa0 100644 +--- a/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java ++++ b/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java +@@ -77,11 +77,15 @@ public class TickThread extends Thread { + } + + public TickThread(final Runnable run, final String name) { +- this(run, name, ID_GENERATOR.incrementAndGet()); ++ this(null, run, name); + } + +- private TickThread(final Runnable run, final String name, final int id) { +- super(run, name); ++ public TickThread(final ThreadGroup group, final Runnable run, final String name) { ++ this(group, run, name, ID_GENERATOR.incrementAndGet()); ++ } ++ ++ private TickThread(final ThreadGroup group, final Runnable run, final String name, final int id) { ++ super(group, run, name); + this.id = id; + } + +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/WorldUtil.java b/src/main/java/ca/spottedleaf/moonrise/common/util/WorldUtil.java +index af9623240ff2d389aa7090623f507720e7dbab7d..efda2688ae1254a82ba7f6bf8bf597ef224cbb86 100644 +--- a/src/main/java/ca/spottedleaf/moonrise/common/util/WorldUtil.java ++++ b/src/main/java/ca/spottedleaf/moonrise/common/util/WorldUtil.java +@@ -8,11 +8,19 @@ public final class WorldUtil { + // min, max are inclusive + + public static int getMaxSection(final LevelHeightAccessor world) { +- return world.getMaxSection() - 1; // getMaxSection() is exclusive ++ return world.getMaxSectionY(); ++ } ++ ++ public static int getMaxSection(final Level world) { ++ return world.getMaxSectionY(); + } + + public static int getMinSection(final LevelHeightAccessor world) { +- return world.getMinSection(); ++ return world.getMinSectionY(); ++ } ++ ++ public static int getMinSection(final Level world) { ++ return world.getMinSectionY(); + } + + public static int getMaxLightSection(final LevelHeightAccessor world) { +diff --git a/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java b/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ee514a767f69de69d86e1e88d70fe37c4ab84277 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java +@@ -0,0 +1,209 @@ ++package ca.spottedleaf.moonrise.paper; ++ ++import ca.spottedleaf.moonrise.common.PlatformHooks; ++import com.mojang.datafixers.DSL; ++import com.mojang.datafixers.DataFixer; ++import com.mojang.serialization.Dynamic; ++import net.minecraft.core.BlockPos; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.nbt.NbtOps; ++import net.minecraft.server.level.ChunkHolder; ++import net.minecraft.server.level.GenerationChunkHolder; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.level.BlockGetter; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.LevelChunk; ++import net.minecraft.world.level.chunk.ProtoChunk; ++import net.minecraft.world.level.chunk.storage.SerializableChunkData; ++import net.minecraft.world.level.entity.EntityTypeTest; ++import net.minecraft.world.phys.AABB; ++import java.util.List; ++import java.util.function.Predicate; ++ ++public final class PaperHooks implements PlatformHooks { ++ ++ @Override ++ public String getBrand() { ++ return "Paper"; ++ } ++ ++ @Override ++ public int getLightEmission(final BlockState blockState, final BlockGetter world, final BlockPos pos) { ++ return blockState.getLightEmission(); ++ } ++ ++ @Override ++ public Predicate maybeHasLightEmission() { ++ return (final BlockState state) -> { ++ return state.getLightEmission() != 0; ++ }; ++ } ++ ++ @Override ++ public boolean hasCurrentlyLoadingChunk() { ++ return false; ++ } ++ ++ @Override ++ public LevelChunk getCurrentlyLoadingChunk(final GenerationChunkHolder holder) { ++ return null; ++ } ++ ++ @Override ++ public void setCurrentlyLoading(final GenerationChunkHolder holder, final LevelChunk levelChunk) { ++ ++ } ++ ++ @Override ++ public void chunkFullStatusComplete(final LevelChunk newChunk, final ProtoChunk original) { ++ ++ } ++ ++ @Override ++ public boolean allowAsyncTicketUpdates() { ++ return true; ++ } ++ ++ @Override ++ public void onChunkHolderTicketChange(final ServerLevel world, final ChunkHolder holder, final int oldLevel, final int newLevel) { ++ ++ } ++ ++ @Override ++ public void chunkUnloadFromWorld(final LevelChunk chunk) { ++ ++ } ++ ++ @Override ++ public void chunkSyncSave(final ServerLevel world, final ChunkAccess chunk, final SerializableChunkData data) { ++ ++ } ++ ++ @Override ++ public void onChunkWatch(final ServerLevel world, final LevelChunk chunk, final ServerPlayer player) { ++ ++ } ++ ++ @Override ++ public void onChunkUnWatch(final ServerLevel world, final ChunkPos chunk, final ServerPlayer player) { ++ ++ } ++ ++ @Override ++ public void addToGetEntities(final Level world, final Entity entity, final AABB boundingBox, final Predicate predicate, final List into) { ++ ++ } ++ ++ @Override ++ public void addToGetEntities(final Level world, final EntityTypeTest entityTypeTest, final AABB boundingBox, final Predicate predicate, final List into, final int maxCount) { ++ ++ } ++ ++ @Override ++ public void entityMove(final Entity entity, final long oldSection, final long newSection) { ++ ++ } ++ ++ @Override ++ public boolean screenEntity(final ServerLevel world, final Entity entity, final boolean fromDisk, final boolean event) { ++ return true; ++ } ++ ++ @Override ++ public boolean configFixMC224294() { ++ return true; ++ } ++ ++ @Override ++ public boolean configAutoConfigSendDistance() { ++ ++ } ++ ++ @Override ++ public double configPlayerMaxLoadRate() { ++ ++ } ++ ++ @Override ++ public double configPlayerMaxGenRate() { ++ ++ } ++ ++ @Override ++ public double configPlayerMaxSendRate() { ++ ++ } ++ ++ @Override ++ public int configPlayerMaxConcurrentLoads() { ++ ++ } ++ ++ @Override ++ public int configPlayerMaxConcurrentGens() { ++ ++ } ++ ++ @Override ++ public long configAutoSaveInterval() { ++ ++ } ++ ++ @Override ++ public int configMaxAutoSavePerTick() { ++ ++ } ++ ++ @Override ++ public boolean configFixMC159283() { ++ return true; ++ } ++ ++ @Override ++ public boolean forceNoSave(final ChunkAccess chunk) { ++ return chunk instanceof LevelChunk levelChunk && levelChunk.mustNotSave; ++ } ++ ++ @Override + public CompoundTag convertNBT(final DSL.TypeReference type, final DataFixer dataFixer, final CompoundTag nbt, + final int fromVersion, final int toVersion) { + return (CompoundTag)dataFixer.update( + type, new Dynamic<>(NbtOps.INSTANCE, nbt), fromVersion, toVersion + ).getValue(); - } - - @Override -@@ -192,6 +197,11 @@ public final class PaperHooks implements PlatformHooks { - entity.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK, org.bukkit.event.entity.EntityRemoveEvent.Cause.UNLOAD); - } - ++ } ++ ++ @Override ++ public boolean hasMainChunkLoadHook() { ++ return false; ++ } ++ ++ @Override ++ public void mainChunkLoad(final ChunkAccess chunk, final SerializableChunkData chunkData) { ++ ++ } ++ ++ @Override ++ public List modifySavedEntities(final ServerLevel world, final int chunkX, final int chunkZ, final List entities) { ++ return entities; ++ } ++ ++ @Override ++ public void unloadEntity(final Entity entity) { ++ entity.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK, org.bukkit.event.entity.EntityRemoveEvent.Cause.UNLOAD); ++ } ++ + @Override + public void postLoadProtoChunk(final ServerLevel world, final ProtoChunk chunk) { + net.minecraft.world.level.chunk.status.ChunkStatusTasks.postLoadProtoChunk(world, chunk.getEntities()); + } + - @Override - public int modifyEntityTrackingRange(final Entity entity, final int currentRange) { - return org.spigotmc.TrackingRange.getEntityTrackingRange(entity, currentRange); ++ @Override ++ public int modifyEntityTrackingRange(final Entity entity, final int currentRange) { ++ return org.spigotmc.TrackingRange.getEntityTrackingRange(entity, currentRange); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java +index 61893f8216ddaedd899b573322f3ad0088074ac5..36b96e0ed5c0d25068ec4678eddd8a19a020d345 100644 +--- a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java ++++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java +@@ -242,7 +242,7 @@ public class GlobalConfiguration extends ConfigurationPart { + + @PostProcess + private void postProcess() { +- ++ ca.spottedleaf.moonrise.common.util.MoonriseCommon.adjustWorkerThreads(this.workerThreads, this.ioThreads); + } + } + diff --git a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java index f18c2b85ed9541f646f157184221e333d0ae58bd..aff4c3d63a97d5bbde004a616f7e14fca59b5ab9 100644 --- a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java @@ -103,15 +1266,36 @@ index f18c2b85ed9541f646f157184221e333d0ae58bd..aff4c3d63a97d5bbde004a616f7e14fc // CraftBukkit start - these are spawned serialized (DefinedStructure) and we don't call an add event below at the moment due to ordering complexities world.addWorldGenChunkEntities(EntityType.loadEntitiesRecursive(entities, world, EntitySpawnReason.LOAD).filter((entity) -> { diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java -index 3eb38271b6ca26099b2da04c2d969e32fd72b2af..5aa74c00a61282830d82359eae2b114e2a48b6d9 100644 +index 6baa313b8201ed23193d7885c85606b0899ade3c..5aa74c00a61282830d82359eae2b114e2a48b6d9 100644 --- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java +++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java -@@ -97,7 +97,7 @@ public class PersistentEntitySectionManager implements A - // I don't want to know why this is a generic type. - Entity entityCasted = (Entity)entity; - boolean wasRemoved = entityCasted.isRemoved(); -- boolean screened = ca.spottedleaf.moonrise.common.util.ChunkSystem.screenEntity((net.minecraft.server.level.ServerLevel)entityCasted.level(), entityCasted, existing, false); +@@ -94,15 +94,13 @@ public class PersistentEntitySectionManager implements A + private boolean addEntity(T entity, boolean existing) { + org.spigotmc.AsyncCatcher.catchOp("Entity add"); // Paper + // Paper start - chunk system hooks +- if (existing) { +- // I don't want to know why this is a generic type. +- Entity entityCasted = (Entity)entity; +- boolean wasRemoved = entityCasted.isRemoved(); +- boolean screened = ca.spottedleaf.moonrise.common.util.ChunkSystem.screenEntity((net.minecraft.server.level.ServerLevel)entityCasted.level(), entityCasted); +- if ((!wasRemoved && entityCasted.isRemoved()) || !screened) { +- // removed by callback +- return false; +- } ++ // I don't want to know why this is a generic type. ++ Entity entityCasted = (Entity)entity; ++ boolean wasRemoved = entityCasted.isRemoved(); + boolean screened = ca.spottedleaf.moonrise.common.util.ChunkSystem.screenEntity((net.minecraft.server.level.ServerLevel)entityCasted.level(), entityCasted, existing, true); - if ((!wasRemoved && entityCasted.isRemoved()) || !screened) { - // removed by callback - return false; ++ if ((!wasRemoved && entityCasted.isRemoved()) || !screened) { ++ // removed by callback ++ return false; + } + // Paper end - chunk system hooks + if (!this.addEntityUuid(entity)) { +diff --git a/src/main/resources/META-INF/services/ca.spottedleaf.moonrise.common.PlatformHooks b/src/main/resources/META-INF/services/ca.spottedleaf.moonrise.common.PlatformHooks +new file mode 100644 +index 0000000000000000000000000000000000000000..e57c3ca79677b1dfe7cf3db36f0406de7ea5bd0a +--- /dev/null ++++ b/src/main/resources/META-INF/services/ca.spottedleaf.moonrise.common.PlatformHooks +@@ -0,0 +1 @@ ++ca.spottedleaf.moonrise.paper.PaperHooks diff --git a/patches/server/0825-Rewrite-dataconverter-system.patch b/patches/server/0824-Rewrite-dataconverter-system.patch similarity index 100% rename from patches/server/0825-Rewrite-dataconverter-system.patch rename to patches/server/0824-Rewrite-dataconverter-system.patch diff --git a/patches/server/0827-fixup-Optimize-BlockPosition-helper-methods.patch b/patches/server/0825-fixup-Optimize-BlockPosition-helper-methods.patch similarity index 100% rename from patches/server/0827-fixup-Optimize-BlockPosition-helper-methods.patch rename to patches/server/0825-fixup-Optimize-BlockPosition-helper-methods.patch diff --git a/patches/server/0826-Moonrise-optimisation-patches.patch b/patches/server/0826-Moonrise-optimisation-patches.patch index 452ab610f1..59a89d7268 100644 --- a/patches/server/0826-Moonrise-optimisation-patches.patch +++ b/patches/server/0826-Moonrise-optimisation-patches.patch @@ -17,10 +17,10 @@ Currently includes: See https://github.com/Tuinity/Moonrise diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java b/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java -index 94bba2b71918d79f54b3e28c35e76098ba0afd8c..b61611351bf23efc1e90bab8a850ebbe6ffdd516 100644 +index 94bba2b71918d79f54b3e28c35e76098ba0afd8c..fc029c8fb22a7c8eeb23bfc171812f6da91c60fa 100644 --- a/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java +++ b/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java -@@ -2,6 +2,10 @@ package ca.spottedleaf.moonrise.common.util; +@@ -2,11 +2,17 @@ package ca.spottedleaf.moonrise.common.util; import ca.spottedleaf.concurrentutil.util.Priority; import ca.spottedleaf.moonrise.common.PlatformHooks; @@ -28,10 +28,17 @@ index 94bba2b71918d79f54b3e28c35e76098ba0afd8c..b61611351bf23efc1e90bab8a850ebbe +import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk; +import ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader; +import ca.spottedleaf.moonrise.patches.chunk_system.world.ChunkSystemServerChunkCache; ++import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickServerLevel; import com.mojang.logging.LogUtils; import net.minecraft.server.level.ChunkHolder; import net.minecraft.server.level.FullChunkStatus; -@@ -18,203 +22,46 @@ import java.util.function.Consumer; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.server.level.progress.ChunkProgressListener; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.level.chunk.ChunkAccess; + import net.minecraft.world.level.chunk.LevelChunk; +@@ -18,203 +24,46 @@ import java.util.function.Consumer; public final class ChunkSystem { private static final Logger LOGGER = LogUtils.getLogger(); @@ -243,17 +250,26 @@ index 94bba2b71918d79f54b3e28c35e76098ba0afd8c..b61611351bf23efc1e90bab8a850ebbe } public static boolean hasAnyChunkHolders(final ServerLevel level) { -@@ -236,52 +83,85 @@ public final class ChunkSystem { - +@@ -233,55 +82,96 @@ public final class ChunkSystem { } -- public static void onChunkBorder(final LevelChunk chunk, final ChunkHolder holder) { + public static void onChunkHolderDelete(final ServerLevel level, final ChunkHolder holder) { ++ // Update progress listener for LevelLoadingScreen ++ final ChunkProgressListener progressListener = level.getChunkSource().chunkMap.progressListener; ++ if (progressListener != null) { ++ ChunkSystem.scheduleChunkTask(level, holder.getPos().x, holder.getPos().z, () -> { ++ progressListener.onStatusChange(holder.getPos(), null); ++ }); ++ } ++ } + + public static void onChunkPreBorder(final LevelChunk chunk, final ChunkHolder holder) { + ((ChunkSystemServerChunkCache)((ServerLevel)chunk.getLevel()).getChunkSource()) + .moonrise$setFullChunk(chunk.getPos().x, chunk.getPos().z, chunk); -+ } + } -+ public static void onChunkBorder(final LevelChunk chunk, final ChunkHolder holder) { + public static void onChunkBorder(final LevelChunk chunk, final ChunkHolder holder) { +- + ((ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getLoadedChunks().add( + ((ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder() + ); @@ -278,10 +294,11 @@ index 94bba2b71918d79f54b3e28c35e76098ba0afd8c..b61611351bf23efc1e90bab8a850ebbe + ((ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder() + ); + if (!((ChunkSystemLevelChunk)chunk).moonrise$isPostProcessingDone()) { -+ chunk.postProcessGeneration(); ++ chunk.postProcessGeneration((ServerLevel)chunk.getLevel()); + } + ((ServerLevel)chunk.getLevel()).startTickingChunk(chunk); + ((ServerLevel)chunk.getLevel()).getChunkSource().chunkMap.tickingGenerated.incrementAndGet(); ++ ((ChunkTickServerLevel)(ServerLevel)chunk.getLevel()).moonrise$markChunkForPlayerTicking(chunk); // Moonrise - chunk tick iteration } public static void onChunkNotTicking(final LevelChunk chunk, final ChunkHolder holder) { @@ -289,6 +306,7 @@ index 94bba2b71918d79f54b3e28c35e76098ba0afd8c..b61611351bf23efc1e90bab8a850ebbe + ((ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getTickingChunks().remove( + ((ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder() + ); ++ ((ChunkTickServerLevel)(ServerLevel)chunk.getLevel()).moonrise$removeChunkForPlayerTicking(chunk); // Moonrise - chunk tick iteration } public static void onChunkEntityTicking(final LevelChunk chunk, final ChunkHolder holder) { @@ -361,65 +379,318 @@ index be60439c43b887f0143e7713689fd2773066ba73..dc17aa5a0937c13d431e41779f241f2e @Override diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/block_counting/BlockCountingBitStorage.java b/src/main/java/ca/spottedleaf/moonrise/patches/block_counting/BlockCountingBitStorage.java new file mode 100644 -index 0000000000000000000000000000000000000000..aef4fc0d3c272febe675d1ac846b88e58b4e7533 +index 0000000000000000000000000000000000000000..93bc56daec4526f373c84763b8c7ccb4a30e800b --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/block_counting/BlockCountingBitStorage.java @@ -0,0 +1,10 @@ +package ca.spottedleaf.moonrise.patches.block_counting; + +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -+import it.unimi.dsi.fastutil.ints.IntArrayList; ++import it.unimi.dsi.fastutil.shorts.ShortArrayList; + +public interface BlockCountingBitStorage { + -+ public Int2ObjectOpenHashMap moonrise$countEntries(); ++ public Int2ObjectOpenHashMap moonrise$countEntries(); + +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/block_counting/BlockCountingChunkSection.java b/src/main/java/ca/spottedleaf/moonrise/patches/block_counting/BlockCountingChunkSection.java new file mode 100644 -index 0000000000000000000000000000000000000000..a08ddb0598d44368af5b6bace971ee31edf9919e +index 0000000000000000000000000000000000000000..0d1443a113c07d7655e7b927a899447f70db8fa9 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/block_counting/BlockCountingChunkSection.java @@ -0,0 +1,11 @@ +package ca.spottedleaf.moonrise.patches.block_counting; + -+import ca.spottedleaf.moonrise.common.list.IBlockDataList; ++import ca.spottedleaf.moonrise.common.list.ShortList; + +public interface BlockCountingChunkSection { + -+ public int moonrise$getSpecialCollidingBlocks(); ++ public boolean moonrise$hasSpecialCollidingBlocks(); + -+ public IBlockDataList moonrise$getTickingBlockList(); ++ public ShortList moonrise$getTickingBlockList(); + +} -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_getblock/GetBlockChunk.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_getblock/GetBlockChunk.java +diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccess.java b/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccess.java new file mode 100644 -index 0000000000000000000000000000000000000000..08338917dc61c856eaba0b76e05c1497c458399d +index 0000000000000000000000000000000000000000..89e75b454695e174c5619104eeb15eb923a2d9a7 --- /dev/null -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_getblock/GetBlockChunk.java -@@ -0,0 +1,9 @@ -+package ca.spottedleaf.moonrise.patches.chunk_getblock; ++++ b/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccess.java +@@ -0,0 +1,12 @@ ++package ca.spottedleaf.moonrise.patches.blockstate_propertyaccess; + -+import net.minecraft.world.level.block.state.BlockState; ++public interface PropertyAccess { + -+public interface GetBlockChunk { ++ public int moonrise$getId(); + -+ public BlockState moonrise$getBlock(final int x, final int y, final int z); ++ public int moonrise$getIdFor(final T value); + ++ public T moonrise$getById(final int id); ++ ++ public void moonrise$setById(final T[] values); ++} +diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccessStateHolder.java b/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccessStateHolder.java +new file mode 100644 +index 0000000000000000000000000000000000000000..01da52b9e8a786824f199a057b62ce0431ecbc43 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccessStateHolder.java +@@ -0,0 +1,7 @@ ++package ca.spottedleaf.moonrise.patches.blockstate_propertyaccess; ++ ++public interface PropertyAccessStateHolder { ++ ++ public long moonrise$getTableIndex(); ++ ++} +diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/util/ZeroCollidingReferenceStateTable.java b/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/util/ZeroCollidingReferenceStateTable.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b5335a2a8cb5dc7637c7112c8f7193389d726489 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/util/ZeroCollidingReferenceStateTable.java +@@ -0,0 +1,230 @@ ++package ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.util; ++ ++import ca.spottedleaf.concurrentutil.util.IntegerUtil; ++import ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.PropertyAccess; ++import ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.PropertyAccessStateHolder; ++import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; ++import it.unimi.dsi.fastutil.objects.AbstractObjectSet; ++import it.unimi.dsi.fastutil.objects.AbstractReference2ObjectMap; ++import it.unimi.dsi.fastutil.objects.ObjectIterator; ++import it.unimi.dsi.fastutil.objects.ObjectSet; ++import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; ++import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; ++import java.util.ArrayList; ++import java.util.Collection; ++import java.util.Collections; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Map; ++import net.minecraft.world.level.block.state.StateHolder; ++import net.minecraft.world.level.block.state.properties.Property; ++ ++public final class ZeroCollidingReferenceStateTable { ++ ++ private final Int2ObjectOpenHashMap propertyToIndexer; ++ private S[] lookup; ++ private final Collection> properties; ++ ++ public ZeroCollidingReferenceStateTable(final Collection> properties) { ++ this.propertyToIndexer = new Int2ObjectOpenHashMap<>(properties.size()); ++ this.properties = new ReferenceOpenHashSet<>(properties); ++ ++ final List> sortedProperties = new ArrayList<>(properties); ++ ++ // important that each table sees the same property order given the same _set_ of properties, ++ // as each table will calculate the index for the block state ++ sortedProperties.sort((final Property p1, final Property p2) -> { ++ return Integer.compare( ++ ((PropertyAccess)p1).moonrise$getId(), ++ ((PropertyAccess)p2).moonrise$getId() ++ ); ++ }); ++ ++ int currentMultiple = 1; ++ for (final Property property : sortedProperties) { ++ final int totalValues = property.getPossibleValues().size(); ++ ++ this.propertyToIndexer.put( ++ ((PropertyAccess)property).moonrise$getId(), ++ new Indexer( ++ totalValues, ++ currentMultiple, ++ IntegerUtil.getUnsignedDivisorMagic((long)currentMultiple, 32), ++ IntegerUtil.getUnsignedDivisorMagic((long)totalValues, 32) ++ ) ++ ); ++ ++ currentMultiple *= totalValues; ++ } ++ } ++ ++ public > boolean hasProperty(final Property property) { ++ return this.propertyToIndexer.containsKey(((PropertyAccess)property).moonrise$getId()); ++ } ++ ++ public long getIndex(final StateHolder stateHolder) { ++ long ret = 0L; ++ ++ for (final Map.Entry, Comparable> entry : stateHolder.getValues().entrySet()) { ++ final Property property = entry.getKey(); ++ final Comparable value = entry.getValue(); ++ ++ final Indexer indexer = this.propertyToIndexer.get(((PropertyAccess)property).moonrise$getId()); ++ ++ ret += (((PropertyAccess)property).moonrise$getIdFor(value)) * indexer.multiple; ++ } ++ ++ return ret; ++ } ++ ++ public boolean isLoaded() { ++ return this.lookup != null; ++ } ++ ++ public void loadInTable(final Map, Comparable>, S> universe) { ++ if (this.lookup != null) { ++ throw new IllegalStateException(); ++ } ++ ++ this.lookup = (S[])new StateHolder[universe.size()]; ++ ++ for (final Map.Entry, Comparable>, S> entry : universe.entrySet()) { ++ final S value = entry.getValue(); ++ if (value == null) { ++ continue; ++ } ++ this.lookup[(int)((PropertyAccessStateHolder)(StateHolder)value).moonrise$getTableIndex()] = value; ++ } ++ ++ for (final S value : this.lookup) { ++ if (value == null) { ++ throw new IllegalStateException(); ++ } ++ } ++ } ++ ++ public > T get(final long index, final Property property) { ++ final Indexer indexer = this.propertyToIndexer.get(((PropertyAccess)property).moonrise$getId()); ++ if (indexer == null) { ++ return null; ++ } ++ ++ final long divided = (index * indexer.multipleDivMagic) >>> 32; ++ final long modded = (((divided * indexer.modMagic) & 0xFFFFFFFFL) * indexer.totalValues) >>> 32; ++ // equiv to: divided = index / multiple ++ // modded = divided % totalValues ++ ++ return ((PropertyAccess)property).moonrise$getById((int)modded); ++ } ++ ++ public > S set(final long index, final Property property, final T with) { ++ final int newValueId = ((PropertyAccess)property).moonrise$getIdFor(with); ++ if (newValueId < 0) { ++ return null; ++ } ++ ++ final Indexer indexer = this.propertyToIndexer.get(((PropertyAccess)property).moonrise$getId()); ++ if (indexer == null) { ++ return null; ++ } ++ ++ final long divided = (index * indexer.multipleDivMagic) >>> 32; ++ final long modded = (((divided * indexer.modMagic) & 0xFFFFFFFFL) * indexer.totalValues) >>> 32; ++ // equiv to: divided = index / multiple ++ // modded = divided % totalValues ++ ++ // subtract out the old value, add in the new ++ final long newIndex = (((long)newValueId - modded) * indexer.multiple) + index; ++ ++ return this.lookup[(int)newIndex]; ++ } ++ ++ public > S trySet(final long index, final Property property, final T with, final S dfl) { ++ final Indexer indexer = this.propertyToIndexer.get(((PropertyAccess)property).moonrise$getId()); ++ if (indexer == null) { ++ return dfl; ++ } ++ ++ final int newValueId = ((PropertyAccess)property).moonrise$getIdFor(with); ++ if (newValueId < 0) { ++ return null; ++ } ++ ++ final long divided = (index * indexer.multipleDivMagic) >>> 32; ++ final long modded = (((divided * indexer.modMagic) & 0xFFFFFFFFL) * indexer.totalValues) >>> 32; ++ // equiv to: divided = index / multiple ++ // modded = divided % totalValues ++ ++ // subtract out the old value, add in the new ++ final long newIndex = (((long)newValueId - modded) * indexer.multiple) + index; ++ ++ return this.lookup[(int)newIndex]; ++ } ++ ++ public Collection> getProperties() { ++ return Collections.unmodifiableCollection(this.properties); ++ } ++ ++ public Map, Comparable> getMapView(final long stateIndex) { ++ return new MapView(stateIndex); ++ } ++ ++ private static final record Indexer( ++ int totalValues, int multiple, long multipleDivMagic, long modMagic ++ ) {} ++ ++ private class MapView extends AbstractReference2ObjectMap, Comparable> { ++ private final long stateIndex; ++ private EntrySet entrySet; ++ ++ MapView(final long stateIndex) { ++ this.stateIndex = stateIndex; ++ } ++ ++ @Override ++ public boolean containsKey(final Object key) { ++ return key instanceof Property prop && ZeroCollidingReferenceStateTable.this.hasProperty(prop); ++ } ++ ++ @Override ++ public int size() { ++ return ZeroCollidingReferenceStateTable.this.properties.size(); ++ } ++ ++ @Override ++ public ObjectSet, Comparable>> reference2ObjectEntrySet() { ++ if (this.entrySet == null) ++ this.entrySet = new EntrySet(); ++ return this.entrySet; ++ } ++ ++ @Override ++ public Comparable get(final Object key) { ++ return key instanceof Property prop ? ZeroCollidingReferenceStateTable.this.get(this.stateIndex, prop) : null; ++ } ++ ++ class EntrySet extends AbstractObjectSet, Comparable>> { ++ @Override ++ public ObjectIterator, Comparable>> iterator() { ++ final Iterator> propIterator = ZeroCollidingReferenceStateTable.this.properties.iterator(); ++ return new ObjectIterator<>() { ++ @Override ++ public boolean hasNext() { ++ return propIterator.hasNext(); ++ } ++ ++ @Override ++ public Entry, Comparable> next() { ++ Property prop = propIterator.next(); ++ return new AbstractReference2ObjectMap.BasicEntry<>(prop, ZeroCollidingReferenceStateTable.this.get(MapView.this.stateIndex, prop)); ++ } ++ }; ++ } ++ ++ @Override ++ public int size() { ++ return ZeroCollidingReferenceStateTable.this.properties.size(); ++ } ++ } ++ } +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ChunkSystemConverters.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ChunkSystemConverters.java new file mode 100644 -index 0000000000000000000000000000000000000000..49160a30b8e19e5c5ada811fbcae2a05959524f3 +index 0000000000000000000000000000000000000000..44bb25554634af2ec0b2e9b3d9231304d5dff034 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ChunkSystemConverters.java -@@ -0,0 +1,38 @@ +@@ -0,0 +1,39 @@ +package ca.spottedleaf.moonrise.patches.chunk_system; + ++import ca.spottedleaf.moonrise.common.PlatformHooks; +import net.minecraft.SharedConstants; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.Tag; +import net.minecraft.server.level.ServerLevel; -+import net.minecraft.util.datafix.DataFixTypes; ++import net.minecraft.util.datafix.fixes.References; + +public final class ChunkSystemConverters { + @@ -440,84 +711,26 @@ index 0000000000000000000000000000000000000000..49160a30b8e19e5c5ada811fbcae2a05 + public static CompoundTag convertPoiCompoundTag(final CompoundTag data, final ServerLevel world) { + final int dataVersion = getDataVersion(data, DEFAULT_POI_DATA_VERSION); + -+ return DataFixTypes.POI_CHUNK.update(world.getServer().getFixerUpper(), data, dataVersion, getCurrentVersion()); ++ return PlatformHooks.get().convertNBT(References.POI_CHUNK, world.getServer().getFixerUpper(), data, dataVersion, getCurrentVersion()); + } + + public static CompoundTag convertEntityChunkCompoundTag(final CompoundTag data, final ServerLevel world) { + final int dataVersion = getDataVersion(data, DEFAULT_ENTITY_CHUNK_DATA_VERSION); + -+ return DataFixTypes.ENTITY_CHUNK.update(world.getServer().getFixerUpper(), data, dataVersion, getCurrentVersion()); ++ return PlatformHooks.get().convertNBT(References.ENTITY_CHUNK, world.getServer().getFixerUpper(), data, dataVersion, getCurrentVersion()); + } + + private ChunkSystemConverters() {} +} -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ChunkSystemFeatures.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ChunkSystemFeatures.java -new file mode 100644 -index 0000000000000000000000000000000000000000..67f6dd9a4855611cfe242c2e37e90f6d27d4c823 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ChunkSystemFeatures.java -@@ -0,0 +1,36 @@ -+package ca.spottedleaf.moonrise.patches.chunk_system; -+ -+import ca.spottedleaf.moonrise.patches.chunk_system.async_save.AsyncChunkSaveData; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.level.chunk.ChunkAccess; -+ -+public final class ChunkSystemFeatures { -+ -+ public static boolean supportsAsyncChunkSave() { -+ // uncertain how to properly pass AsyncSaveData to ChunkSerializer#write -+ // additionally, there may be mods hooking into the write() call which may not be thread-safe to call -+ return true; -+ } -+ -+ public static AsyncChunkSaveData getAsyncSaveData(final ServerLevel world, final ChunkAccess chunk) { -+ return net.minecraft.world.level.chunk.storage.ChunkSerializer.getAsyncSaveData(world, chunk); -+ } -+ -+ public static CompoundTag saveChunkAsync(final ServerLevel world, final ChunkAccess chunk, final AsyncChunkSaveData asyncSaveData) { -+ return net.minecraft.world.level.chunk.storage.ChunkSerializer.saveChunk(world, chunk, asyncSaveData); -+ } -+ -+ public static boolean forceNoSave(final ChunkAccess chunk) { -+ // support for CB chunk mustNotSave -+ return chunk instanceof net.minecraft.world.level.chunk.LevelChunk levelChunk && levelChunk.mustNotSave; -+ } -+ -+ public static boolean supportsAsyncChunkDeserialization() { -+ // as it stands, the current problem with supporting this in Moonrise is that we are unsure that any mods -+ // hooking into ChunkSerializer#read() are thread-safe to call -+ return true; -+ } -+ -+ private ChunkSystemFeatures() {} -+} -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/async_save/AsyncChunkSaveData.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/async_save/AsyncChunkSaveData.java -new file mode 100644 -index 0000000000000000000000000000000000000000..becd1c6d54ed6c912aee3a9178a970e2751d3694 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/async_save/AsyncChunkSaveData.java -@@ -0,0 +1,11 @@ -+package ca.spottedleaf.moonrise.patches.chunk_system.async_save; -+ -+import net.minecraft.nbt.ListTag; -+import net.minecraft.nbt.Tag; -+ -+public record AsyncChunkSaveData( -+ Tag blockTickList, // non-null if we had to go to the server's tick list -+ Tag fluidTickList, // non-null if we had to go to the server's tick list -+ ListTag blockEntities, -+ long worldTime -+) {} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/entity/ChunkSystemEntity.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/entity/ChunkSystemEntity.java new file mode 100644 -index 0000000000000000000000000000000000000000..2c279854bdf214538380fa354e4298ec4bd9ac4e +index 0000000000000000000000000000000000000000..c7da23900228aab3a5673eb5adfada5091140319 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/entity/ChunkSystemEntity.java -@@ -0,0 +1,39 @@ +@@ -0,0 +1,44 @@ +package ca.spottedleaf.moonrise.patches.chunk_system.entity; + ++import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData; +import net.minecraft.server.level.FullChunkStatus; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.monster.Shulker; @@ -537,6 +750,10 @@ index 0000000000000000000000000000000000000000..2c279854bdf214538380fa354e4298ec + + public void moonrise$setChunkStatus(final FullChunkStatus status); + ++ public ChunkData moonrise$getChunkData(); ++ ++ public void moonrise$setChunkData(final ChunkData chunkData); ++ + public int moonrise$getSectionX(); + + public void moonrise$setSectionX(final int x); @@ -557,12 +774,13 @@ index 0000000000000000000000000000000000000000..2c279854bdf214538380fa354e4298ec +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java new file mode 100644 -index 0000000000000000000000000000000000000000..73df26b27146bbad2106d57b22dd3c792ed3dd1d +index 0000000000000000000000000000000000000000..a814512fcfb85312474ae2c2c21443843bf57831 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java -@@ -0,0 +1,14 @@ +@@ -0,0 +1,31 @@ +package ca.spottedleaf.moonrise.patches.chunk_system.io; + ++import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.level.chunk.storage.RegionFile; +import java.io.IOException; + @@ -574,63 +792,71 @@ index 0000000000000000000000000000000000000000..73df26b27146bbad2106d57b22dd3c79 + + public RegionFile moonrise$getRegionFileIfExists(final int chunkX, final int chunkZ) throws IOException; + ++ public MoonriseRegionFileIO.RegionDataController.WriteData moonrise$startWrite( ++ final int chunkX, final int chunkZ, final CompoundTag compound ++ ) throws IOException; ++ ++ public void moonrise$finishWrite( ++ final int chunkX, final int chunkZ, final MoonriseRegionFileIO.RegionDataController.WriteData writeData ++ ) throws IOException; ++ ++ public MoonriseRegionFileIO.RegionDataController.ReadData moonrise$readData( ++ final int chunkX, final int chunkZ ++ ) throws IOException; ++ ++ // if the return value is null, then the caller needs to re-try with a new call to readData() ++ public CompoundTag moonrise$finishRead( ++ final int chunkX, final int chunkZ, final MoonriseRegionFileIO.RegionDataController.ReadData readData ++ ) throws IOException; +} -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/RegionFileIOThread.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/RegionFileIOThread.java +diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java new file mode 100644 -index 0000000000000000000000000000000000000000..3218cbf84f54daf06e84442d5eb1a36d8da6b215 +index 0000000000000000000000000000000000000000..99f6f3e58b11b8967e6f1c3391c190d9a860ab7f --- /dev/null -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/RegionFileIOThread.java -@@ -0,0 +1,1240 @@ ++++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java +@@ -0,0 +1,1707 @@ +package ca.spottedleaf.moonrise.patches.chunk_system.io; + +import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; ++import ca.spottedleaf.concurrentutil.completable.CallbackCompletable; ++import ca.spottedleaf.concurrentutil.completable.Completable; +import ca.spottedleaf.concurrentutil.executor.Cancellable; -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedQueueExecutorThread; -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadedTaskQueue; ++import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor; ++import ca.spottedleaf.concurrentutil.executor.queue.PrioritisedTaskQueue; +import ca.spottedleaf.concurrentutil.function.BiLong1Function; +import ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable; +import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; ++import ca.spottedleaf.concurrentutil.util.Priority; +import ca.spottedleaf.moonrise.common.util.CoordinateUtils; +import ca.spottedleaf.moonrise.common.util.TickThread; +import ca.spottedleaf.moonrise.common.util.WorldUtil; +import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel; ++import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; +import net.minecraft.nbt.CompoundTag; ++import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.storage.RegionFile; +import net.minecraft.world.level.chunk.storage.RegionFileStorage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; ++import java.io.DataInputStream; ++import java.io.DataOutputStream; +import java.io.IOException; +import java.lang.invoke.VarHandle; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.atomic.AtomicInteger; ++import java.util.concurrent.atomic.AtomicLong; +import java.util.function.BiConsumer; +import java.util.function.Consumer; -+import java.util.function.Function; + -+/** -+ * Prioritised RegionFile I/O executor, responsible for all RegionFile access. -+ *

-+ * All functions provided are MT-Safe, however certain ordering constraints are recommended: -+ *

  • -+ * Chunk saves may not occur for unloaded chunks. -+ *
  • -+ *
  • -+ * Tasks must be scheduled on the chunk scheduler thread. -+ *
  • -+ * By following these constraints, no chunk data loss should occur with the exception of underlying I/O problems. -+ *

    -+ */ -+public final class RegionFileIOThread extends PrioritisedQueueExecutorThread { ++public final class MoonriseRegionFileIO { + -+ private static final Logger LOGGER = LoggerFactory.getLogger(RegionFileIOThread.class); ++ private static final int REGION_FILE_SHIFT = 5; ++ private static final Logger LOGGER = LoggerFactory.getLogger(MoonriseRegionFileIO.class); + + /** -+ * The kinds of region files controlled by the region file thread. Add more when needed, and ensure -+ * getControllerFor is updated. ++ * The types of RegionFiles controlled by the I/O thread(s). + */ + public static enum RegionFileType { + CHUNK_DATA, @@ -638,9 +864,7 @@ index 0000000000000000000000000000000000000000..3218cbf84f54daf06e84442d5eb1a36d + ENTITY_DATA; + } + -+ private static final RegionFileType[] CACHED_REGIONFILE_TYPES = RegionFileType.values(); -+ -+ public static ChunkDataController getControllerFor(final ServerLevel world, final RegionFileType type) { ++ public static RegionDataController getControllerFor(final ServerLevel world, final RegionFileType type) { + switch (type) { + case CHUNK_DATA: + return ((ChunkSystemServerLevel)world).moonrise$getChunkDataController(); @@ -653,8 +877,10 @@ index 0000000000000000000000000000000000000000..3218cbf84f54daf06e84442d5eb1a36d + } + } + ++ private static final RegionFileType[] CACHED_REGIONFILE_TYPES = RegionFileType.values(); ++ + /** -+ * Collects regionfile data for a certain chunk. ++ * Collects RegionFile data for a certain chunk. + */ + public static final class RegionFileData { + @@ -663,13 +889,13 @@ index 0000000000000000000000000000000000000000..3218cbf84f54daf06e84442d5eb1a36d + private final Throwable[] throwables = new Throwable[CACHED_REGIONFILE_TYPES.length]; + + /** -+ * Sets the result associated with the specified regionfile type. Note that -+ * results can only be set once per regionfile type. ++ * Sets the result associated with the specified RegionFile type. Note that ++ * results can only be set once per RegionFile type. + * -+ * @param type The regionfile type. ++ * @param type The RegionFile type. + * @param data The result to set. + */ -+ public void setData(final RegionFileType type, final CompoundTag data) { ++ public void setData(final MoonriseRegionFileIO.RegionFileType type, final CompoundTag data) { + final int index = type.ordinal(); + + if (this.hasResult[index]) { @@ -680,13 +906,13 @@ index 0000000000000000000000000000000000000000..3218cbf84f54daf06e84442d5eb1a36d + } + + /** -+ * Sets the result associated with the specified regionfile type. Note that -+ * results can only be set once per regionfile type. ++ * Sets the result associated with the specified RegionFile type. Note that ++ * results can only be set once per RegionFile type. + * -+ * @param type The regionfile type. ++ * @param type The RegionFile type. + * @param throwable The result to set. + */ -+ public void setThrowable(final RegionFileType type, final Throwable throwable) { ++ public void setThrowable(final MoonriseRegionFileIO.RegionFileType type, final Throwable throwable) { + final int index = type.ordinal(); + + if (this.hasResult[index]) { @@ -697,26 +923,26 @@ index 0000000000000000000000000000000000000000..3218cbf84f54daf06e84442d5eb1a36d + } + + /** -+ * Returns whether there is a result for the specified regionfile type. ++ * Returns whether there is a result for the specified RegionFile type. + * -+ * @param type Specified regionfile type. ++ * @param type Specified RegionFile type. + * + * @return Whether a result exists for {@code type}. + */ -+ public boolean hasResult(final RegionFileType type) { ++ public boolean hasResult(final MoonriseRegionFileIO.RegionFileType type) { + return this.hasResult[type.ordinal()]; + } + + /** -+ * Returns the data result for the regionfile type. ++ * Returns the data result for the RegionFile type. + * -+ * @param type Specified regionfile type. ++ * @param type Specified RegionFile type. + * + * @throws IllegalArgumentException If the result has not been set for {@code type}. + * @return The data result for the specified type. If the result is a {@code Throwable}, + * then returns {@code null}. + */ -+ public CompoundTag getData(final RegionFileType type) { ++ public CompoundTag getData(final MoonriseRegionFileIO.RegionFileType type) { + final int index = type.ordinal(); + + if (!this.hasResult[index]) { @@ -727,15 +953,15 @@ index 0000000000000000000000000000000000000000..3218cbf84f54daf06e84442d5eb1a36d + } + + /** -+ * Returns the throwable result for the regionfile type. ++ * Returns the throwable result for the RegionFile type. + * -+ * @param type Specified regionfile type. ++ * @param type Specified RegionFile type. + * + * @throws IllegalArgumentException If the result has not been set for {@code type}. + * @return The throwable result for the specified type. If the result is an {@code CompoundTag}, + * then returns {@code null}. + */ -+ public Throwable getThrowable(final RegionFileType type) { ++ public Throwable getThrowable(final MoonriseRegionFileIO.RegionFileType type) { + final int index = type.ordinal(); + + if (!this.hasResult[index]) { @@ -746,132 +972,53 @@ index 0000000000000000000000000000000000000000..3218cbf84f54daf06e84442d5eb1a36d + } + } + -+ private static final Object INIT_LOCK = new Object(); -+ -+ static RegionFileIOThread[] threads; -+ -+ /* needs to be consistent given a set of parameters */ -+ static RegionFileIOThread selectThread(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type) { -+ if (threads == null) { -+ throw new IllegalStateException("Threads not initialised"); -+ } -+ -+ final int regionX = chunkX >> 5; -+ final int regionZ = chunkZ >> 5; -+ final int typeOffset = type.ordinal(); -+ -+ return threads[(System.identityHashCode(world) + regionX + regionZ + typeOffset) % threads.length]; -+ } -+ -+ /** -+ * Shuts down the I/O executor(s). Watis for all tasks to complete if specified. -+ * Tasks queued during this call might not be accepted, and tasks queued after will not be accepted. -+ * -+ * @param wait Whether to wait until all tasks have completed. -+ */ -+ public static void close(final boolean wait) { -+ for (int i = 0, len = threads.length; i < len; ++i) { -+ threads[i].close(false, true); -+ } -+ if (wait) { -+ RegionFileIOThread.flush(); -+ } -+ } -+ -+ public static long[] getExecutedTasks() { -+ final long[] ret = new long[threads.length]; -+ for (int i = 0, len = threads.length; i < len; ++i) { -+ ret[i] = threads[i].getTotalTasksExecuted(); -+ } -+ -+ return ret; -+ } -+ -+ public static long[] getTasksScheduled() { -+ final long[] ret = new long[threads.length]; -+ for (int i = 0, len = threads.length; i < len; ++i) { -+ ret[i] = threads[i].getTotalTasksScheduled(); -+ } -+ return ret; -+ } -+ -+ public static void flush() { -+ for (int i = 0, len = threads.length; i < len; ++i) { -+ threads[i].waitUntilAllExecuted(); -+ } -+ } -+ + public static void flushRegionStorages(final ServerLevel world) throws IOException { + for (final RegionFileType type : CACHED_REGIONFILE_TYPES) { -+ getControllerFor(world, type).getCache().flush(); ++ flushRegionStorages(world, type); + } + } + -+ public static void partialFlush(final int totalTasksRemaining) { -+ long failures = 1L; // start out at 0.25ms ++ public static void flushRegionStorages(final ServerLevel world, final RegionFileType type) throws IOException { ++ getControllerFor(world, type).getCache().flush(); ++ } + -+ for (;;) { -+ final long[] executed = getExecutedTasks(); -+ final long[] scheduled = getTasksScheduled(); -+ -+ long sum = 0; -+ for (int i = 0; i < executed.length; ++i) { -+ sum += scheduled[i] - executed[i]; -+ } -+ -+ if (sum <= totalTasksRemaining) { -+ break; -+ } -+ -+ failures = ConcurrentUtil.linearLongBackoff(failures, 250_000L, 5_000_000L); // 500us, 5ms ++ public static void flush(final MinecraftServer server) { ++ for (final ServerLevel world : server.getAllLevels()) { ++ flush(world); + } + } + -+ /** -+ * Inits the executor with the specified number of threads. -+ * -+ * @param threads Specified number of threads. -+ */ -+ public static void init(final int threads) { -+ synchronized (INIT_LOCK) { -+ if (RegionFileIOThread.threads != null) { -+ throw new IllegalStateException("Already initialised threads"); -+ } -+ -+ RegionFileIOThread.threads = new RegionFileIOThread[threads]; -+ -+ for (int i = 0; i < threads; ++i) { -+ RegionFileIOThread.threads[i] = new RegionFileIOThread(i); -+ RegionFileIOThread.threads[i].start(); -+ } ++ public static void flush(final ServerLevel world) { ++ for (final RegionFileType regionFileType : CACHED_REGIONFILE_TYPES) { ++ flush(world, regionFileType); + } + } + -+ public static void deinit() { -+ if (true) { // Paper -+ // TODO does this cause issues with mods? how to implement -+ close(true); -+ synchronized (INIT_LOCK) { -+ RegionFileIOThread.threads = null; ++ public static void flush(final ServerLevel world, final RegionFileType type) { ++ final RegionDataController taskController = getControllerFor(world, type); ++ ++ long failures = 1L; // start at 0.13ms ++ ++ while (taskController.hasTasks()) { ++ Thread.yield(); ++ failures = ConcurrentUtil.linearLongBackoff(failures, 125_000L, 5_000_000L); // 125us, 5ms ++ } ++ } ++ ++ public static void partialFlush(final ServerLevel world, final int tasksRemaining) { ++ for (long failures = 1L;;) { // start at 0.13ms ++ long totalTasks = 0L; ++ for (final RegionFileType regionFileType : CACHED_REGIONFILE_TYPES) { ++ totalTasks += getControllerFor(world, regionFileType).getTotalWorkingTasks(); + } -+ } else { RegionFileIOThread.flush(); } -+ } + -+ private RegionFileIOThread(final int threadNumber) { -+ super(new PrioritisedThreadedTaskQueue(), (int)(1.0e6)); // 1.0ms spinwait time -+ this.setName("RegionFile I/O Thread #" + threadNumber); -+ this.setPriority(Thread.NORM_PRIORITY - 2); // we keep priority close to normal because threads can wait on us -+ this.setUncaughtExceptionHandler((final Thread thread, final Throwable thr) -> { -+ LOGGER.error("Uncaught exception thrown from I/O thread, report this! Thread: " + thread.getName(), thr); -+ }); -+ } -+ -+ /** -+ * Returns whether the current thread is a regionfile I/O executor. -+ * @return Whether the current thread is a regionfile I/O executor. -+ */ -+ public static boolean isRegionFileThread() { -+ return Thread.currentThread() instanceof RegionFileIOThread; ++ if (totalTasks > (long)tasksRemaining) { ++ Thread.yield(); ++ failures = ConcurrentUtil.linearLongBackoff(failures, 125_000L, 5_000_000L); // 125us, 5ms ++ } else { ++ return; ++ } ++ } + } + + /** @@ -887,35 +1034,6 @@ index 0000000000000000000000000000000000000000..3218cbf84f54daf06e84442d5eb1a36d + } + + /** -+ * Returns the current {@code CompoundTag} pending for write for the specified chunk & regionfile type. -+ * Note that this does not copy the result, so do not modify the result returned. -+ * -+ * @param world Specified world. -+ * @param chunkX Specified chunk x. -+ * @param chunkZ Specified chunk z. -+ * @param type Specified regionfile type. -+ * -+ * @return The compound tag associated for the specified chunk. {@code null} if no write was pending, or if {@code null} is the write pending. -+ */ -+ public static CompoundTag getPendingWrite(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type) { -+ final RegionFileIOThread thread = RegionFileIOThread.selectThread(world, chunkX, chunkZ, type); -+ return thread.getPendingWriteInternal(world, chunkX, chunkZ, type); -+ } -+ -+ CompoundTag getPendingWriteInternal(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type) { -+ final ChunkDataController taskController = getControllerFor(world, type); -+ final ChunkDataTask task = taskController.tasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ -+ if (task == null) { -+ return null; -+ } -+ -+ final CompoundTag ret = task.inProgressWrite; -+ -+ return ret == ChunkDataTask.NOTHING_TO_WRITE ? null : ret; -+ } -+ -+ /** + * Returns the priority for the specified regionfile type for the specified chunk. + * @param world Specified world. + * @param chunkX Specified chunk x. @@ -924,19 +1042,14 @@ index 0000000000000000000000000000000000000000..3218cbf84f54daf06e84442d5eb1a36d + * @return The priority for the chunk + */ + public static Priority getPriority(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type) { -+ final RegionFileIOThread thread = RegionFileIOThread.selectThread(world, chunkX, chunkZ, type); -+ return thread.getPriorityInternal(world, chunkX, chunkZ, type); -+ } -+ -+ Priority getPriorityInternal(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type) { -+ final ChunkDataController taskController = getControllerFor(world, type); -+ final ChunkDataTask task = taskController.tasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); ++ final RegionDataController taskController = getControllerFor(world, type); ++ final ChunkIOTask task = taskController.chunkTasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); + + if (task == null) { + return Priority.COMPLETING; + } + -+ return task.prioritisedTask.getPriority(); ++ return task.getPriority(); + } + + /** @@ -957,7 +1070,7 @@ index 0000000000000000000000000000000000000000..3218cbf84f54daf06e84442d5eb1a36d + public static void setPriority(final ServerLevel world, final int chunkX, final int chunkZ, + final Priority priority) { + for (final RegionFileType type : CACHED_REGIONFILE_TYPES) { -+ RegionFileIOThread.setPriority(world, chunkX, chunkZ, type, priority); ++ MoonriseRegionFileIO.setPriority(world, chunkX, chunkZ, type, priority); + } + } + @@ -979,17 +1092,11 @@ index 0000000000000000000000000000000000000000..3218cbf84f54daf06e84442d5eb1a36d + */ + public static void setPriority(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type, + final Priority priority) { -+ final RegionFileIOThread thread = RegionFileIOThread.selectThread(world, chunkX, chunkZ, type); -+ thread.setPriorityInternal(world, chunkX, chunkZ, type, priority); -+ } -+ -+ void setPriorityInternal(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type, -+ final Priority priority) { -+ final ChunkDataController taskController = getControllerFor(world, type); -+ final ChunkDataTask task = taskController.tasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); ++ final RegionDataController taskController = getControllerFor(world, type); ++ final ChunkIOTask task = taskController.chunkTasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); + + if (task != null) { -+ task.prioritisedTask.setPriority(priority); ++ task.setPriority(priority); + } + } + @@ -1009,7 +1116,7 @@ index 0000000000000000000000000000000000000000..3218cbf84f54daf06e84442d5eb1a36d + public static void raisePriority(final ServerLevel world, final int chunkX, final int chunkZ, + final Priority priority) { + for (final RegionFileType type : CACHED_REGIONFILE_TYPES) { -+ RegionFileIOThread.raisePriority(world, chunkX, chunkZ, type, priority); ++ MoonriseRegionFileIO.raisePriority(world, chunkX, chunkZ, type, priority); + } + } + @@ -1029,17 +1136,11 @@ index 0000000000000000000000000000000000000000..3218cbf84f54daf06e84442d5eb1a36d + */ + public static void raisePriority(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type, + final Priority priority) { -+ final RegionFileIOThread thread = RegionFileIOThread.selectThread(world, chunkX, chunkZ, type); -+ thread.raisePriorityInternal(world, chunkX, chunkZ, type, priority); -+ } -+ -+ void raisePriorityInternal(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type, -+ final Priority priority) { -+ final ChunkDataController taskController = getControllerFor(world, type); -+ final ChunkDataTask task = taskController.tasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); ++ final RegionDataController taskController = getControllerFor(world, type); ++ final ChunkIOTask task = taskController.chunkTasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); + + if (task != null) { -+ task.prioritisedTask.raisePriority(priority); ++ task.raisePriority(priority); + } + } + @@ -1059,7 +1160,7 @@ index 0000000000000000000000000000000000000000..3218cbf84f54daf06e84442d5eb1a36d + public static void lowerPriority(final ServerLevel world, final int chunkX, final int chunkZ, + final Priority priority) { + for (final RegionFileType type : CACHED_REGIONFILE_TYPES) { -+ RegionFileIOThread.lowerPriority(world, chunkX, chunkZ, type, priority); ++ MoonriseRegionFileIO.lowerPriority(world, chunkX, chunkZ, type, priority); + } + } + @@ -1079,17 +1180,11 @@ index 0000000000000000000000000000000000000000..3218cbf84f54daf06e84442d5eb1a36d + */ + public static void lowerPriority(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type, + final Priority priority) { -+ final RegionFileIOThread thread = RegionFileIOThread.selectThread(world, chunkX, chunkZ, type); -+ thread.lowerPriorityInternal(world, chunkX, chunkZ, type, priority); -+ } -+ -+ void lowerPriorityInternal(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type, -+ final Priority priority) { -+ final ChunkDataController taskController = getControllerFor(world, type); -+ final ChunkDataTask task = taskController.tasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); ++ final RegionDataController taskController = getControllerFor(world, type); ++ final ChunkIOTask task = taskController.chunkTasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); + + if (task != null) { -+ task.prioritisedTask.lowerPriority(priority); ++ task.lowerPriority(priority); + } + } + @@ -1116,7 +1211,7 @@ index 0000000000000000000000000000000000000000..3218cbf84f54daf06e84442d5eb1a36d + */ + public static void scheduleSave(final ServerLevel world, final int chunkX, final int chunkZ, final CompoundTag data, + final RegionFileType type) { -+ RegionFileIOThread.scheduleSave(world, chunkX, chunkZ, data, type, Priority.NORMAL); ++ MoonriseRegionFileIO.scheduleSave(world, chunkX, chunkZ, data, type, Priority.NORMAL); + } + + /** @@ -1143,37 +1238,112 @@ index 0000000000000000000000000000000000000000..3218cbf84f54daf06e84442d5eb1a36d + */ + public static void scheduleSave(final ServerLevel world, final int chunkX, final int chunkZ, final CompoundTag data, + final RegionFileType type, final Priority priority) { -+ final RegionFileIOThread thread = RegionFileIOThread.selectThread(world, chunkX, chunkZ, type); -+ thread.scheduleSaveInternal(world, chunkX, chunkZ, data, type, priority); ++ scheduleSave( ++ world, chunkX, chunkZ, ++ (final BiConsumer consumer) -> { ++ consumer.accept(data, null); ++ }, null, type, priority ++ ); + } + -+ void scheduleSaveInternal(final ServerLevel world, final int chunkX, final int chunkZ, final CompoundTag data, -+ final RegionFileType type, final Priority priority) { -+ final ChunkDataController taskController = getControllerFor(world, type); ++ /** ++ * Schedules the chunk data to be written asynchronously. ++ *

    ++ * Impl notes: ++ *

    ++ *
  • ++ * This function presumes a chunk load for the coordinates is not called during this function (anytime after is OK). This means ++ * saves must be scheduled before a chunk is unloaded. ++ *
  • ++ *
  • ++ * Writes may be called concurrently, although only the "later" write will go through. ++ *
  • ++ *
  • ++ * The specified write task, if not null, will have its priority controlled by the scheduler. ++ *
  • ++ * ++ * @param world Chunk's world ++ * @param chunkX Chunk's x coordinate ++ * @param chunkZ Chunk's z coordinate ++ * @param completable Chunk's pending data ++ * @param writeTask The task responsible for completing the pending chunk data ++ * @param type The regionfile type to write to. ++ * @param priority The minimum priority to schedule at. ++ * ++ * @throws IllegalStateException If the file io thread has shutdown. ++ */ ++ public static void scheduleSave(final ServerLevel world, final int chunkX, final int chunkZ, final CallbackCompletable completable, ++ final PrioritisedExecutor.PrioritisedTask writeTask, final RegionFileType type, final Priority priority) { ++ scheduleSave(world, chunkX, chunkZ, completable::addWaiter, writeTask, type, priority); ++ } ++ ++ /** ++ * Schedules the chunk data to be written asynchronously. ++ *

    ++ * Impl notes: ++ *

    ++ *
  • ++ * This function presumes a chunk load for the coordinates is not called during this function (anytime after is OK). This means ++ * saves must be scheduled before a chunk is unloaded. ++ *
  • ++ *
  • ++ * Writes may be called concurrently, although only the "later" write will go through. ++ *
  • ++ *
  • ++ * The specified write task, if not null, will have its priority controlled by the scheduler. ++ *
  • ++ * ++ * @param world Chunk's world ++ * @param chunkX Chunk's x coordinate ++ * @param chunkZ Chunk's z coordinate ++ * @param completable Chunk's pending data ++ * @param writeTask The task responsible for completing the pending chunk data ++ * @param type The regionfile type to write to. ++ * @param priority The minimum priority to schedule at. ++ * ++ * @throws IllegalStateException If the file io thread has shutdown. ++ */ ++ public static void scheduleSave(final ServerLevel world, final int chunkX, final int chunkZ, final Completable completable, ++ final PrioritisedExecutor.PrioritisedTask writeTask, final RegionFileType type, final Priority priority) { ++ scheduleSave(world, chunkX, chunkZ, completable::whenComplete, writeTask, type, priority); ++ } ++ ++ private static void scheduleSave(final ServerLevel world, final int chunkX, final int chunkZ, final Consumer> scheduler, ++ final PrioritisedExecutor.PrioritisedTask writeTask, final RegionFileType type, final Priority priority) { ++ final RegionDataController taskController = getControllerFor(world, type); + + final boolean[] created = new boolean[1]; -+ final long key = CoordinateUtils.getChunkKey(chunkX, chunkZ); -+ final ChunkDataTask task = taskController.tasks.compute(key, (final long keyInMap, final ChunkDataTask taskRunning) -> { -+ if (taskRunning == null || taskRunning.failedWrite) { -+ // no task is scheduled or the previous write failed - meaning we need to overwrite it ++ final ChunkIOTask.InProgressWrite write = new ChunkIOTask.InProgressWrite(writeTask); ++ final ChunkIOTask task = taskController.chunkTasks.compute(CoordinateUtils.getChunkKey(chunkX, chunkZ), ++ (final long keyInMap, final ChunkIOTask taskRunning) -> { ++ if (taskRunning == null || taskRunning.failedWrite) { ++ // no task is scheduled or the previous write failed - meaning we need to overwrite it + -+ // create task -+ final ChunkDataTask newTask = new ChunkDataTask(world, chunkX, chunkZ, taskController, RegionFileIOThread.this, priority); -+ newTask.inProgressWrite = data; -+ created[0] = true; ++ // create task ++ final ChunkIOTask newTask = new ChunkIOTask( ++ world, taskController, chunkX, chunkZ, priority, new ChunkIOTask.InProgressRead() ++ ); + -+ return newTask; ++ newTask.pushPendingWrite(write); ++ ++ created[0] = true; ++ ++ return newTask; ++ } ++ ++ taskRunning.pushPendingWrite(write); ++ ++ return taskRunning; + } ++ ); + -+ taskRunning.inProgressWrite = data; -+ -+ return taskRunning; -+ }); ++ write.schedule(task, scheduler); + + if (created[0]) { -+ task.prioritisedTask.queue(); ++ taskController.startTask(task); ++ task.scheduleWriteCompress(); + } else { -+ task.prioritisedTask.raisePriority(priority); ++ task.raisePriority(priority); + } + } + @@ -1206,7 +1376,7 @@ index 0000000000000000000000000000000000000000..3218cbf84f54daf06e84442d5eb1a36d + */ + public static Cancellable loadAllChunkData(final ServerLevel world, final int chunkX, final int chunkZ, + final Consumer onComplete, final boolean intendingToBlock) { -+ return RegionFileIOThread.loadAllChunkData(world, chunkX, chunkZ, onComplete, intendingToBlock, Priority.NORMAL); ++ return MoonriseRegionFileIO.loadAllChunkData(world, chunkX, chunkZ, onComplete, intendingToBlock, Priority.NORMAL); + } + + /** @@ -1240,7 +1410,7 @@ index 0000000000000000000000000000000000000000..3218cbf84f54daf06e84442d5eb1a36d + public static Cancellable loadAllChunkData(final ServerLevel world, final int chunkX, final int chunkZ, + final Consumer onComplete, final boolean intendingToBlock, + final Priority priority) { -+ return RegionFileIOThread.loadChunkData(world, chunkX, chunkZ, onComplete, intendingToBlock, priority, CACHED_REGIONFILE_TYPES); ++ return MoonriseRegionFileIO.loadChunkData(world, chunkX, chunkZ, onComplete, intendingToBlock, priority, CACHED_REGIONFILE_TYPES); + } + + /** @@ -1274,7 +1444,7 @@ index 0000000000000000000000000000000000000000..3218cbf84f54daf06e84442d5eb1a36d + public static Cancellable loadChunkData(final ServerLevel world, final int chunkX, final int chunkZ, + final Consumer onComplete, final boolean intendingToBlock, + final RegionFileType... types) { -+ return RegionFileIOThread.loadChunkData(world, chunkX, chunkZ, onComplete, intendingToBlock, Priority.NORMAL, types); ++ return MoonriseRegionFileIO.loadChunkData(world, chunkX, chunkZ, onComplete, intendingToBlock, Priority.NORMAL, types); + } + + /** @@ -1324,7 +1494,7 @@ index 0000000000000000000000000000000000000000..3218cbf84f54daf06e84442d5eb1a36d + + for (int i = 0; i < expectedCompletions; ++i) { + final RegionFileType type = types[i]; -+ reads[i] = RegionFileIOThread.loadDataAsync(world, chunkX, chunkZ, type, ++ reads[i] = MoonriseRegionFileIO.loadDataAsync(world, chunkX, chunkZ, type, + (final CompoundTag data, final Throwable throwable) -> { + if (throwable != null) { + ret.setThrowable(type, throwable); @@ -1370,7 +1540,7 @@ index 0000000000000000000000000000000000000000..3218cbf84f54daf06e84442d5eb1a36d + public static Cancellable loadDataAsync(final ServerLevel world, final int chunkX, final int chunkZ, + final RegionFileType type, final BiConsumer onComplete, + final boolean intendingToBlock) { -+ return RegionFileIOThread.loadDataAsync(world, chunkX, chunkZ, type, onComplete, intendingToBlock, Priority.NORMAL); ++ return MoonriseRegionFileIO.loadDataAsync(world, chunkX, chunkZ, type, onComplete, intendingToBlock, Priority.NORMAL); + } + + /** @@ -1403,57 +1573,62 @@ index 0000000000000000000000000000000000000000..3218cbf84f54daf06e84442d5eb1a36d + public static Cancellable loadDataAsync(final ServerLevel world, final int chunkX, final int chunkZ, + final RegionFileType type, final BiConsumer onComplete, + final boolean intendingToBlock, final Priority priority) { -+ final RegionFileIOThread thread = RegionFileIOThread.selectThread(world, chunkX, chunkZ, type); -+ return thread.loadDataAsyncInternal(world, chunkX, chunkZ, type, onComplete, intendingToBlock, priority); -+ } -+ -+ Cancellable loadDataAsyncInternal(final ServerLevel world, final int chunkX, final int chunkZ, -+ final RegionFileType type, final BiConsumer onComplete, -+ final boolean intendingToBlock, final Priority priority) { -+ final ChunkDataController taskController = getControllerFor(world, type); ++ final RegionDataController taskController = getControllerFor(world, type); + + final ImmediateCallbackCompletion callbackInfo = new ImmediateCallbackCompletion(); + + final long key = CoordinateUtils.getChunkKey(chunkX, chunkZ); -+ final BiLong1Function compute = (final long keyInMap, final ChunkDataTask running) -> { ++ final BiLong1Function compute = (final long keyInMap, final ChunkIOTask running) -> { + if (running == null) { + // not scheduled + + // set up task -+ final ChunkDataTask newTask = new ChunkDataTask( -+ world, chunkX, chunkZ, taskController, RegionFileIOThread.this, priority ++ final ChunkIOTask newTask = new ChunkIOTask( ++ world, taskController, chunkX, chunkZ, priority, new ChunkIOTask.InProgressRead() + ); -+ newTask.inProgressRead = new InProgressRead(); + newTask.inProgressRead.addToAsyncWaiters(onComplete); + -+ callbackInfo.tasksNeedsScheduling = true; ++ callbackInfo.tasksNeedReadScheduling = true; + return newTask; + } + -+ final CompoundTag pendingWrite = running.inProgressWrite; ++ final ChunkIOTask.InProgressWrite pendingWrite = running.inProgressWrite; + -+ if (pendingWrite == ChunkDataTask.NOTHING_TO_WRITE) { ++ if (pendingWrite == null) { + // need to add to waiters here, because the regionfile thread will use compute() to lock and check for cancellations + if (!running.inProgressRead.addToAsyncWaiters(onComplete)) { + callbackInfo.data = running.inProgressRead.value; + callbackInfo.throwable = running.inProgressRead.throwable; + callbackInfo.completeNow = true; ++ return running; + } ++ ++ callbackInfo.read = running.inProgressRead; ++ + return running; + } + + // at this stage we have to use the in progress write's data to avoid an order issue -+ callbackInfo.data = pendingWrite; -+ callbackInfo.throwable = null; -+ callbackInfo.completeNow = true; ++ ++ if (!pendingWrite.addToAsyncWaiters(onComplete)) { ++ // data is ready now ++ callbackInfo.data = pendingWrite.value; ++ callbackInfo.throwable = pendingWrite.throwable; ++ callbackInfo.completeNow = true; ++ return running; ++ } ++ ++ callbackInfo.write = pendingWrite; ++ + return running; + }; + -+ final ChunkDataTask ret = taskController.tasks.compute(key, compute); ++ final ChunkIOTask ret = taskController.chunkTasks.compute(key, compute); + + // needs to be scheduled -+ if (callbackInfo.tasksNeedsScheduling) { -+ ret.prioritisedTask.queue(); ++ if (callbackInfo.tasksNeedReadScheduling) { ++ taskController.startTask(ret); ++ ret.scheduleReadIO(); + } else if (callbackInfo.completeNow) { + try { + onComplete.accept(callbackInfo.data == null ? null : callbackInfo.data.copy(), callbackInfo.throwable); @@ -1462,10 +1637,21 @@ index 0000000000000000000000000000000000000000..3218cbf84f54daf06e84442d5eb1a36d + } + } else { + // we're waiting on a task we didn't schedule, so raise its priority to what we want -+ ret.prioritisedTask.raisePriority(priority); ++ ret.raisePriority(priority); + } + -+ return new CancellableRead(onComplete, ret); ++ return new CancellableRead(onComplete, callbackInfo.read, callbackInfo.write); ++ } ++ ++ private static final class ImmediateCallbackCompletion { ++ ++ private CompoundTag data; ++ private Throwable throwable; ++ private boolean completeNow; ++ private boolean tasksNeedReadScheduling; ++ private ChunkIOTask.InProgressRead read; ++ private ChunkIOTask.InProgressWrite write; ++ + } + + /** @@ -1485,7 +1671,7 @@ index 0000000000000000000000000000000000000000..3218cbf84f54daf06e84442d5eb1a36d + final Priority priority) throws IOException { + final CompletableFuture ret = new CompletableFuture<>(); + -+ RegionFileIOThread.loadDataAsync(world, chunkX, chunkZ, type, (final CompoundTag compound, final Throwable thr) -> { ++ MoonriseRegionFileIO.loadDataAsync(world, chunkX, chunkZ, type, (final CompoundTag compound, final Throwable thr) -> { + if (thr != null) { + ret.completeExceptionally(thr); + } else { @@ -1499,52 +1685,53 @@ index 0000000000000000000000000000000000000000..3218cbf84f54daf06e84442d5eb1a36d + throw new IOException(ex); + } + } -+ -+ private static final class ImmediateCallbackCompletion { -+ -+ public CompoundTag data; -+ public Throwable throwable; -+ public boolean completeNow; -+ public boolean tasksNeedsScheduling; -+ -+ } -+ ++ + private static final class CancellableRead implements Cancellable { + + private BiConsumer callback; -+ private ChunkDataTask task; ++ private ChunkIOTask.InProgressRead read; ++ private ChunkIOTask.InProgressWrite write; + -+ CancellableRead(final BiConsumer callback, final ChunkDataTask task) { ++ private CancellableRead(final BiConsumer callback, ++ final ChunkIOTask.InProgressRead read, ++ final ChunkIOTask.InProgressWrite write) { + this.callback = callback; -+ this.task = task; ++ this.read = read; ++ this.write = write; + } + + @Override + public boolean cancel() { + final BiConsumer callback = this.callback; -+ final ChunkDataTask task = this.task; ++ final ChunkIOTask.InProgressRead read = this.read; ++ final ChunkIOTask.InProgressWrite write = this.write; + -+ if (callback == null || task == null) { ++ if (callback == null || (read == null && write == null)) { + return false; + } + + this.callback = null; -+ this.task = null; ++ this.read = null; ++ this.write = null; + -+ final InProgressRead read = task.inProgressRead; ++ if (read != null) { ++ return read.cancel(callback); ++ } ++ if (write != null) { ++ return write.cancel(callback); ++ } + -+ // read can be null if no read was scheduled (i.e no regionfile existed or chunk in regionfile didn't) -+ return read != null && read.cancel(callback); ++ // unreachable ++ throw new InternalError(); + } + } + + private static final class CancellableReads implements Cancellable { + + private Cancellable[] reads; -+ + private static final VarHandle READS_HANDLE = ConcurrentUtil.getVarHandle(CancellableReads.class, "reads", Cancellable[].class); + -+ CancellableReads(final Cancellable[] reads) { ++ private CancellableReads(final Cancellable[] reads) { + this.reads = reads; + } + @@ -1566,286 +1753,802 @@ index 0000000000000000000000000000000000000000..3218cbf84f54daf06e84442d5eb1a36d + } + } + -+ private static final class InProgressRead { -+ -+ private static final Logger LOGGER = LoggerFactory.getLogger(InProgressRead.class); -+ -+ private CompoundTag value; -+ private Throwable throwable; -+ private final MultiThreadedQueue> callbacks = new MultiThreadedQueue<>(); -+ -+ public boolean hasNoWaiters() { -+ return this.callbacks.isEmpty(); -+ } -+ -+ public boolean addToAsyncWaiters(final BiConsumer callback) { -+ return this.callbacks.add(callback); -+ } -+ -+ public boolean cancel(final BiConsumer callback) { -+ return this.callbacks.remove(callback); -+ } -+ -+ public void complete(final ChunkDataTask task, final CompoundTag value, final Throwable throwable) { -+ this.value = value; -+ this.throwable = throwable; -+ -+ BiConsumer consumer; -+ while ((consumer = this.callbacks.pollOrBlockAdds()) != null) { -+ try { -+ consumer.accept(value == null ? null : value.copy(), throwable); -+ } catch (final Throwable thr) { -+ LOGGER.error("Callback " + ConcurrentUtil.genericToString(consumer) + " failed to handle chunk data for task " + task.toString(), thr); -+ } -+ } -+ } -+ } -+ -+ public static abstract class ChunkDataController { -+ -+ // ConcurrentHashMap synchronizes per chain, so reduce the chance of task's hashes colliding. -+ private final ConcurrentLong2ReferenceChainedHashTable tasks = ConcurrentLong2ReferenceChainedHashTable.createWithCapacity(8192, 0.5f); -+ -+ public final RegionFileType type; -+ -+ public ChunkDataController(final RegionFileType type) { -+ this.type = type; -+ } -+ -+ public abstract RegionFileStorage getCache(); -+ -+ public abstract void writeData(final int chunkX, final int chunkZ, final CompoundTag compound) throws IOException; -+ -+ public abstract CompoundTag readData(final int chunkX, final int chunkZ) throws IOException; -+ -+ public boolean hasTasks() { -+ return !this.tasks.isEmpty(); -+ } -+ -+ public boolean doesRegionFileNotExist(final int chunkX, final int chunkZ) { -+ return ((ChunkSystemRegionFileStorage)(Object)this.getCache()).moonrise$doesRegionFileNotExistNoIO(chunkX, chunkZ); -+ } -+ -+ public T computeForRegionFile(final int chunkX, final int chunkZ, final boolean existingOnly, final Function function) { -+ final RegionFileStorage cache = this.getCache(); -+ final RegionFile regionFile; -+ synchronized (cache) { -+ try { -+ if (existingOnly) { -+ regionFile = ((ChunkSystemRegionFileStorage)(Object)cache).moonrise$getRegionFileIfExists(chunkX, chunkZ); -+ } else { -+ regionFile = cache.getRegionFile(new ChunkPos(chunkX, chunkZ), existingOnly); -+ } -+ } catch (final IOException ex) { -+ throw new RuntimeException(ex); -+ } -+ -+ return function.apply(regionFile); -+ } -+ } -+ -+ public T computeForRegionFileIfLoaded(final int chunkX, final int chunkZ, final Function function) { -+ final RegionFileStorage cache = this.getCache(); -+ final RegionFile regionFile; -+ -+ synchronized (cache) { -+ regionFile = ((ChunkSystemRegionFileStorage)(Object)cache).moonrise$getRegionFileIfLoaded(chunkX, chunkZ); -+ -+ return function.apply(regionFile); -+ } -+ } -+ } -+ -+ private static final class ChunkDataTask implements Runnable { -+ -+ private static final CompoundTag NOTHING_TO_WRITE = new CompoundTag(); -+ -+ private static final Logger LOGGER = LoggerFactory.getLogger(ChunkDataTask.class); -+ -+ private InProgressRead inProgressRead; -+ private volatile CompoundTag inProgressWrite = NOTHING_TO_WRITE; // only needs to be acquire/release -+ -+ private boolean failedWrite; ++ private static final class ChunkIOTask { + + private final ServerLevel world; ++ private final RegionDataController regionDataController; + private final int chunkX; + private final int chunkZ; -+ private final ChunkDataController taskController; ++ private Priority priority; ++ private PrioritisedExecutor.PrioritisedTask currentTask; + -+ private final PrioritisedTask prioritisedTask; ++ private final InProgressRead inProgressRead; ++ private volatile InProgressWrite inProgressWrite; ++ private final ReferenceOpenHashSet allPendingWrites = new ReferenceOpenHashSet<>(); + -+ /* -+ * IO thread will perform reads before writes for a given chunk x and z -+ * -+ * How reads/writes are scheduled: -+ * -+ * If read is scheduled while scheduling write, take no special action and just schedule write -+ * If read is scheduled while scheduling read and no write is scheduled, chain the read task -+ * -+ * -+ * If write is scheduled while scheduling read, use the pending write data and ret immediately (so no read is scheduled) -+ * If write is scheduled while scheduling write (ignore read in progress), overwrite the write in progress data -+ * -+ * This allows the reads and writes to act as if they occur synchronously to the thread scheduling them, however -+ * it fails to properly propagate write failures thanks to writes overwriting each other -+ */ ++ private RegionDataController.ReadData readData; ++ private RegionDataController.WriteData writeData; ++ private boolean failedWrite; + -+ public ChunkDataTask(final ServerLevel world, final int chunkX, final int chunkZ, final ChunkDataController taskController, -+ final PrioritisedExecutor executor, final Priority priority) { ++ public ChunkIOTask(final ServerLevel world, final RegionDataController regionDataController, ++ final int chunkX, final int chunkZ, final Priority priority, final InProgressRead inProgressRead) { + this.world = world; ++ this.regionDataController = regionDataController; + this.chunkX = chunkX; + this.chunkZ = chunkZ; -+ this.taskController = taskController; -+ this.prioritisedTask = executor.createTask(this, priority); ++ this.priority = priority; ++ this.inProgressRead = inProgressRead; ++ } ++ ++ public Priority getPriority() { ++ synchronized (this) { ++ return this.priority; ++ } ++ } ++ ++ // must hold lock on this object ++ private void updatePriority(final Priority priority) { ++ this.priority = priority; ++ if (this.currentTask != null) { ++ this.currentTask.setPriority(priority); ++ } ++ for (final InProgressWrite write : this.allPendingWrites) { ++ if (write.writeTask != null) { ++ write.writeTask.setPriority(priority); ++ } ++ } ++ } ++ ++ public boolean setPriority(final Priority priority) { ++ synchronized (this) { ++ if (this.priority == priority) { ++ return false; ++ } ++ ++ this.updatePriority(priority); ++ ++ return true; ++ } ++ } ++ ++ public boolean raisePriority(final Priority priority) { ++ synchronized (this) { ++ if (this.priority.isHigherOrEqualPriority(priority)) { ++ return false; ++ } ++ ++ this.updatePriority(priority); ++ ++ return true; ++ } ++ } ++ ++ public boolean lowerPriority(final Priority priority) { ++ synchronized (this) { ++ if (this.priority.isLowerOrEqualPriority(priority)) { ++ return false; ++ } ++ ++ this.updatePriority(priority); ++ ++ return true; ++ } ++ } ++ ++ private void pushPendingWrite(final InProgressWrite write) { ++ this.inProgressWrite = write; ++ synchronized (this) { ++ this.allPendingWrites.add(write); ++ if (write.writeTask != null) { ++ write.writeTask.setPriority(this.priority); ++ } ++ } ++ } ++ ++ private void pendingWriteComplete(final InProgressWrite write) { ++ synchronized (this) { ++ this.allPendingWrites.remove(write); ++ } ++ } ++ ++ public void scheduleReadIO() { ++ final PrioritisedExecutor.PrioritisedTask task; ++ synchronized (this) { ++ task = this.regionDataController.ioScheduler.createTask(this.chunkX, this.chunkZ, this::performReadIO, this.priority); ++ this.currentTask = task; ++ } ++ task.queue(); ++ } ++ ++ private void performReadIO() { ++ final InProgressRead read = this.inProgressRead; ++ final long chunkKey = CoordinateUtils.getChunkKey(this.chunkX, this.chunkZ); ++ ++ final boolean[] canRead = new boolean[] { true }; ++ ++ if (read.hasNoWaiters()) { ++ // cancelled read? go to task controller to confirm ++ final ChunkIOTask inMap = this.regionDataController.chunkTasks.compute(chunkKey, (final long keyInMap, final ChunkIOTask valueInMap) -> { ++ if (valueInMap == null) { ++ throw new IllegalStateException("Write completed concurrently, expected this task: " + ChunkIOTask.this.toString() + ", report this!"); ++ } ++ if (valueInMap != ChunkIOTask.this) { ++ throw new IllegalStateException("Chunk task mismatch, expected this task: " + ChunkIOTask.this.toString() + ", got: " + valueInMap.toString() + ", report this!"); ++ } ++ ++ if (!read.hasNoWaiters()) { ++ return valueInMap; ++ } else { ++ canRead[0] = false; ++ } ++ ++ if (valueInMap.inProgressWrite != null) { ++ return valueInMap; ++ } ++ ++ return null; ++ }); ++ ++ if (inMap == null) { ++ this.regionDataController.endTask(this); ++ // read is cancelled - and no write pending, so we're done ++ return; ++ } ++ // if there is a write in progress, we don't actually have to worry about waiters gaining new entries - ++ // the readers will just use the in progress write, so the value in canRead is good to use without ++ // further synchronisation. ++ } ++ ++ if (canRead[0]) { ++ RegionDataController.ReadData readData = null; ++ Throwable throwable = null; ++ ++ try { ++ readData = this.regionDataController.readData(this.chunkX, this.chunkZ); ++ } catch (final Throwable thr) { ++ throwable = thr; ++ LOGGER.error("Failed to read chunk data for task: " + this.toString(), thr); ++ } ++ ++ if (throwable != null) { ++ this.finishRead(null, throwable); ++ } else { ++ switch (readData.result()) { ++ case NO_DATA: ++ case SYNC_READ: { ++ this.finishRead(readData.syncRead(), null); ++ break; ++ } ++ case HAS_DATA: { ++ this.readData = readData; ++ this.scheduleReadDecompress(); ++ // read will handle write scheduling ++ return; ++ } ++ default: { ++ throw new IllegalStateException("Unknown state: " + readData.result()); ++ } ++ } ++ } ++ } ++ ++ if (!this.tryAbortWrite()) { ++ this.scheduleWriteCompress(); ++ } ++ } ++ ++ private void scheduleReadDecompress() { ++ final PrioritisedExecutor.PrioritisedTask task; ++ synchronized (this) { ++ task = this.regionDataController.compressionExecutor.createTask(this::performReadDecompress, this.priority); ++ this.currentTask = task; ++ } ++ task.queue(); ++ } ++ ++ private void performReadDecompress() { ++ final RegionDataController.ReadData readData = this.readData; ++ this.readData = null; ++ ++ CompoundTag compoundTag = null; ++ Throwable throwable = null; ++ ++ try { ++ compoundTag = this.regionDataController.finishRead(this.chunkX, this.chunkZ, readData); ++ } catch (final Throwable thr) { ++ throwable = thr; ++ LOGGER.error("Failed to decompress chunk data for task: " + this.toString(), thr); ++ } ++ ++ if (compoundTag == null) { ++ // need to re-try from the start ++ this.scheduleReadIO(); ++ return; ++ } ++ ++ this.finishRead(compoundTag, throwable); ++ if (!this.tryAbortWrite()) { ++ this.scheduleWriteCompress(); ++ } ++ } ++ ++ private void finishRead(final CompoundTag compoundTag, final Throwable throwable) { ++ this.inProgressRead.complete(this, compoundTag, throwable); ++ } ++ ++ public void scheduleWriteCompress() { ++ final InProgressWrite inProgressWrite = this.inProgressWrite; ++ ++ final PrioritisedExecutor.PrioritisedTask task; ++ synchronized (this) { ++ task = this.regionDataController.compressionExecutor.createTask(() -> { ++ ChunkIOTask.this.performWriteCompress(inProgressWrite); ++ }, this.priority); ++ this.currentTask = task; ++ } ++ ++ inProgressWrite.addToWaiters(this, (final CompoundTag data, final Throwable throwable) -> { ++ task.queue(); ++ }); ++ } ++ ++ private boolean tryAbortWrite() { ++ final long chunkKey = CoordinateUtils.getChunkKey(this.chunkX, this.chunkZ); ++ if (this.inProgressWrite == null) { ++ final ChunkIOTask inMap = this.regionDataController.chunkTasks.compute(chunkKey, (final long keyInMap, final ChunkIOTask valueInMap) -> { ++ if (valueInMap == null) { ++ throw new IllegalStateException("Write completed concurrently, expected this task: " + ChunkIOTask.this.toString() + ", report this!"); ++ } ++ if (valueInMap != ChunkIOTask.this) { ++ throw new IllegalStateException("Chunk task mismatch, expected this task: " + ChunkIOTask.this.toString() + ", got: " + valueInMap.toString() + ", report this!"); ++ } ++ ++ if (valueInMap.inProgressWrite != null) { ++ return valueInMap; ++ } ++ ++ return null; ++ }); ++ ++ if (inMap == null) { ++ this.regionDataController.endTask(this); ++ return true; // set the task value to null, indicating we're done ++ } // else: inProgressWrite changed, so now we have something to write ++ } ++ ++ return false; ++ } ++ ++ private void performWriteCompress(final InProgressWrite inProgressWrite) { ++ final CompoundTag write = inProgressWrite.value; ++ if (!inProgressWrite.isComplete()) { ++ throw new IllegalStateException("Should be writable"); ++ } ++ ++ RegionDataController.WriteData writeData = null; ++ boolean failedWrite = false; ++ ++ try { ++ writeData = this.regionDataController.startWrite(this.chunkX, this.chunkZ, write); ++ } catch (final Throwable thr) { ++ // TODO implement this? ++ /*if (thr instanceof RegionFileStorage.RegionFileSizeException) { ++ final int maxSize = RegionFile.MAX_CHUNK_SIZE / (1024 * 1024); ++ LOGGER.error("Chunk at (" + this.chunkX + "," + this.chunkZ + ") in '" + WorldUtil.getWorldName(this.world) + "' exceeds max size of " + maxSize + "MiB, it has been deleted from disk."); ++ } else */ ++ { ++ failedWrite = thr instanceof IOException; ++ LOGGER.error("Failed to write chunk data for task: " + this.toString(), thr); ++ } ++ } ++ ++ if (writeData == null) { ++ // null if a throwable was encountered ++ ++ // we cannot continue to the I/O stage here, so try to complete ++ ++ if (this.tryCompleteWrite(inProgressWrite, failedWrite)) { ++ return; ++ } else { ++ // fetch new data and try again ++ this.scheduleWriteCompress(); ++ return; ++ } ++ } else { ++ // writeData != null && !failedWrite ++ // we can continue to I/O stage ++ this.writeData = writeData; ++ this.scheduleWriteIO(inProgressWrite); ++ return; ++ } ++ } ++ ++ private void scheduleWriteIO(final InProgressWrite inProgressWrite) { ++ final PrioritisedExecutor.PrioritisedTask task; ++ synchronized (this) { ++ task = this.regionDataController.ioScheduler.createTask(this.chunkX, this.chunkZ, () -> { ++ ChunkIOTask.this.runWriteIO(inProgressWrite); ++ }, this.priority); ++ this.currentTask = task; ++ } ++ task.queue(); ++ } ++ ++ private void runWriteIO(final InProgressWrite inProgressWrite) { ++ RegionDataController.WriteData writeData = this.writeData; ++ this.writeData = null; ++ ++ boolean failedWrite = false; ++ ++ try { ++ this.regionDataController.finishWrite(this.chunkX, this.chunkZ, writeData); ++ } catch (final Throwable thr) { ++ failedWrite = thr instanceof IOException; ++ LOGGER.error("Failed to write chunk data for task: " + this.toString(), thr); ++ } ++ ++ if (!this.tryCompleteWrite(inProgressWrite, failedWrite)) { ++ // fetch new data and try again ++ this.scheduleWriteCompress(); ++ } ++ return; ++ } ++ ++ private boolean tryCompleteWrite(final InProgressWrite written, final boolean failedWrite) { ++ final long chunkKey = CoordinateUtils.getChunkKey(this.chunkX, this.chunkZ); ++ ++ final boolean[] done = new boolean[] { false }; ++ ++ this.regionDataController.chunkTasks.compute(chunkKey, (final long keyInMap, final ChunkIOTask valueInMap) -> { ++ if (valueInMap == null) { ++ throw new IllegalStateException("Write completed concurrently, expected this task: " + ChunkIOTask.this.toString() + ", report this!"); ++ } ++ if (valueInMap != ChunkIOTask.this) { ++ throw new IllegalStateException("Chunk task mismatch, expected this task: " + ChunkIOTask.this.toString() + ", got: " + valueInMap.toString() + ", report this!"); ++ } ++ if (valueInMap.inProgressWrite == written) { ++ valueInMap.failedWrite = failedWrite; ++ done[0] = true; ++ // keep the data in map if we failed the write so we can try to prevent data loss ++ return failedWrite ? valueInMap : null; ++ } ++ // different data than expected, means we need to retry write ++ return valueInMap; ++ }); ++ ++ if (done[0]) { ++ this.regionDataController.endTask(this); ++ return true; ++ } ++ return false; + } + + @Override + public String toString() { -+ return "Task for world: '" + WorldUtil.getWorldName(this.world) + "' at (" + this.chunkX + "," + this.chunkZ + -+ ") type: " + this.taskController.type.name() + ", hash: " + this.hashCode(); ++ return "Task for world: '" + WorldUtil.getWorldName(this.world) + "' at (" + this.chunkX + "," ++ + this.chunkZ + ") type: " + this.regionDataController.type.name() + ", hash: " + this.hashCode(); ++ } ++ ++ private static final class InProgressRead { ++ ++ private static final Logger LOGGER = LoggerFactory.getLogger(InProgressRead.class); ++ ++ private CompoundTag value; ++ private Throwable throwable; ++ private final MultiThreadedQueue> callbacks = new MultiThreadedQueue<>(); ++ ++ public boolean hasNoWaiters() { ++ return this.callbacks.isEmpty(); ++ } ++ ++ public boolean addToAsyncWaiters(final BiConsumer callback) { ++ return this.callbacks.add(callback); ++ } ++ ++ public boolean cancel(final BiConsumer callback) { ++ return this.callbacks.remove(callback); ++ } ++ ++ public void complete(final ChunkIOTask task, final CompoundTag value, final Throwable throwable) { ++ this.value = value; ++ this.throwable = throwable; ++ ++ BiConsumer consumer; ++ while ((consumer = this.callbacks.pollOrBlockAdds()) != null) { ++ try { ++ consumer.accept(value == null ? null : value.copy(), throwable); ++ } catch (final Throwable thr) { ++ LOGGER.error("Callback " + ConcurrentUtil.genericToString(consumer) + " failed to handle chunk data (read) for task " + task.toString(), thr); ++ } ++ } ++ } ++ } ++ ++ private static final class InProgressWrite { ++ ++ private static final Logger LOGGER = LoggerFactory.getLogger(InProgressWrite.class); ++ ++ private CompoundTag value; ++ private Throwable throwable; ++ private volatile boolean complete; ++ private final MultiThreadedQueue> callbacks = new MultiThreadedQueue<>(); ++ ++ private final PrioritisedExecutor.PrioritisedTask writeTask; ++ ++ public InProgressWrite(final PrioritisedExecutor.PrioritisedTask writeTask) { ++ this.writeTask = writeTask; ++ } ++ ++ public boolean isComplete() { ++ return this.complete; ++ } ++ ++ public void schedule(final ChunkIOTask task, final Consumer> scheduler) { ++ scheduler.accept((final CompoundTag data, final Throwable throwable) -> { ++ InProgressWrite.this.complete(task, data, throwable); ++ }); ++ } ++ ++ public boolean addToAsyncWaiters(final BiConsumer callback) { ++ return this.callbacks.add(callback); ++ } ++ ++ public void addToWaiters(final ChunkIOTask task, final BiConsumer consumer) { ++ if (!this.callbacks.add(consumer)) { ++ this.syncAccept(task, consumer, this.value, this.throwable); ++ } ++ } ++ ++ private void syncAccept(final ChunkIOTask task, final BiConsumer consumer, final CompoundTag value, final Throwable throwable) { ++ try { ++ consumer.accept(value == null ? null : value.copy(), throwable); ++ } catch (final Throwable thr) { ++ LOGGER.error("Callback " + ConcurrentUtil.genericToString(consumer) + " failed to handle chunk data (write) for task " + task.toString(), thr); ++ } ++ } ++ ++ public void complete(final ChunkIOTask task, final CompoundTag value, final Throwable throwable) { ++ this.value = value; ++ this.throwable = throwable; ++ this.complete = true; ++ ++ task.pendingWriteComplete(this); ++ ++ BiConsumer consumer; ++ while ((consumer = this.callbacks.pollOrBlockAdds()) != null) { ++ this.syncAccept(task, consumer, value, throwable); ++ } ++ } ++ ++ public boolean cancel(final BiConsumer callback) { ++ return this.callbacks.remove(callback); ++ } ++ } ++ } ++ ++ public static abstract class RegionDataController { ++ ++ public final RegionFileType type; ++ private final PrioritisedExecutor compressionExecutor; ++ private final IOScheduler ioScheduler; ++ private final ConcurrentLong2ReferenceChainedHashTable chunkTasks = new ConcurrentLong2ReferenceChainedHashTable<>(); ++ ++ private final AtomicLong inProgressTasks = new AtomicLong(); ++ ++ public RegionDataController(final RegionFileType type, final PrioritisedExecutor ioExecutor, ++ final PrioritisedExecutor compressionExecutor) { ++ this.type = type; ++ this.compressionExecutor = compressionExecutor; ++ this.ioScheduler = new IOScheduler(ioExecutor); ++ } ++ ++ final void startTask(final ChunkIOTask task) { ++ this.inProgressTasks.getAndIncrement(); ++ } ++ ++ final void endTask(final ChunkIOTask task) { ++ this.inProgressTasks.getAndDecrement(); ++ } ++ ++ public boolean hasTasks() { ++ return this.inProgressTasks.get() != 0L; ++ } ++ ++ public long getTotalWorkingTasks() { ++ return this.inProgressTasks.get(); ++ } ++ ++ public abstract RegionFileStorage getCache(); ++ ++ public static record WriteData(CompoundTag input, WriteResult result, DataOutputStream output, IORunnable write) { ++ public static enum WriteResult { ++ WRITE, ++ DELETE; ++ } ++ } ++ ++ public abstract WriteData startWrite(final int chunkX, final int chunkZ, final CompoundTag compound) throws IOException; ++ ++ public abstract void finishWrite(final int chunkX, final int chunkZ, final WriteData writeData) throws IOException; ++ ++ public static record ReadData(ReadResult result, DataInputStream input, CompoundTag syncRead) { ++ public static enum ReadResult { ++ NO_DATA, ++ HAS_DATA, ++ SYNC_READ; ++ } ++ } ++ ++ public abstract ReadData readData(final int chunkX, final int chunkZ) throws IOException; ++ ++ // if the return value is null, then the caller needs to re-try with a new call to readData() ++ public abstract CompoundTag finishRead(final int chunkX, final int chunkZ, final ReadData readData) throws IOException; ++ ++ public static interface IORunnable { ++ ++ public void run(final RegionFile regionFile) throws IOException; ++ ++ } ++ } ++ ++ private static final class IOScheduler { ++ ++ private final ConcurrentLong2ReferenceChainedHashTable regionTasks = new ConcurrentLong2ReferenceChainedHashTable<>(); ++ private final PrioritisedExecutor executor; ++ ++ public IOScheduler(final PrioritisedExecutor executor) { ++ this.executor = executor; ++ } ++ ++ public PrioritisedExecutor.PrioritisedTask createTask(final int chunkX, final int chunkZ, ++ final Runnable run, final Priority priority) { ++ final PrioritisedExecutor.PrioritisedTask[] ret = new PrioritisedExecutor.PrioritisedTask[1]; ++ final long subOrder = this.executor.generateNextSubOrder(); ++ this.regionTasks.compute(CoordinateUtils.getChunkKey(chunkX >> REGION_FILE_SHIFT, chunkZ >> REGION_FILE_SHIFT), ++ (final long regionKey, final RegionIOTasks existing) -> { ++ final RegionIOTasks res; ++ if (existing != null) { ++ res = existing; ++ } else { ++ res = new RegionIOTasks(regionKey, IOScheduler.this); ++ } ++ ++ ret[0] = res.createTask(run, priority, subOrder); ++ ++ return res; ++ }); ++ ++ return ret[0]; ++ } ++ } ++ ++ private static final class RegionIOTasks implements Runnable { ++ ++ private static final Logger LOGGER = LoggerFactory.getLogger(RegionIOTasks.class); ++ ++ private final PrioritisedTaskQueue queue = new PrioritisedTaskQueue(); ++ private final long regionKey; ++ private final IOScheduler ioScheduler; ++ private long createdTasks; ++ private long executedTasks; ++ ++ private PrioritisedExecutor.PrioritisedTask task; ++ ++ public RegionIOTasks(final long regionKey, final IOScheduler ioScheduler) { ++ this.regionKey = regionKey; ++ this.ioScheduler = ioScheduler; ++ } ++ ++ public PrioritisedExecutor.PrioritisedTask createTask(final Runnable run, final Priority priority, ++ final long subOrder) { ++ ++this.createdTasks; ++ return new WrappedTask(this.queue.createTask(run, priority, subOrder)); ++ } ++ ++ private void adjustTaskPriority() { ++ final PrioritisedTaskQueue.PrioritySubOrderPair priority = this.queue.getHighestPrioritySubOrder(); ++ if (this.task == null) { ++ if (priority == null) { ++ return; ++ } ++ this.task = this.ioScheduler.executor.createTask(this, priority.priority(), priority.subOrder()); ++ this.task.queue(); ++ } else { ++ if (priority == null) { ++ throw new IllegalStateException(); ++ } else { ++ this.task.setPriorityAndSubOrder(priority.priority(), priority.subOrder()); ++ } ++ } + } + + @Override + public void run() { -+ final InProgressRead read = this.inProgressRead; -+ final long chunkKey = CoordinateUtils.getChunkKey(this.chunkX, this.chunkZ); ++ final Runnable run; ++ synchronized (this) { ++ run = this.queue.pollTask(); ++ } + -+ if (read != null) { -+ final boolean[] canRead = new boolean[] { true }; -+ -+ if (read.hasNoWaiters()) { -+ // cancelled read? go to task controller to confirm -+ final ChunkDataTask inMap = this.taskController.tasks.compute(chunkKey, (final long keyInMap, final ChunkDataTask valueInMap) -> { -+ if (valueInMap == null) { -+ throw new IllegalStateException("Write completed concurrently, expected this task: " + ChunkDataTask.this.toString() + ", report this!"); -+ } -+ if (valueInMap != ChunkDataTask.this) { -+ throw new IllegalStateException("Chunk task mismatch, expected this task: " + ChunkDataTask.this.toString() + ", got: " + valueInMap.toString() + ", report this!"); -+ } -+ -+ if (!read.hasNoWaiters()) { -+ return valueInMap; -+ } else { -+ canRead[0] = false; -+ } -+ -+ return valueInMap.inProgressWrite == NOTHING_TO_WRITE ? null : valueInMap; -+ }); -+ -+ if (inMap == null) { -+ // read is cancelled - and no write pending, so we're done -+ return; -+ } -+ // if there is a write in progress, we don't actually have to worry about waiters gaining new entries - -+ // the readers will just use the in progress write, so the value in canRead is good to use without -+ // further synchronisation. ++ try { ++ run.run(); ++ } finally { ++ synchronized (this) { ++ this.task = null; ++ this.adjustTaskPriority(); + } -+ -+ if (canRead[0]) { -+ CompoundTag compound = null; -+ Throwable throwable = null; -+ -+ try { -+ compound = this.taskController.readData(this.chunkX, this.chunkZ); -+ } catch (final Throwable thr) { -+ throwable = thr; -+ LOGGER.error("Failed to read chunk data for task: " + this.toString(), thr); ++ this.ioScheduler.regionTasks.compute(this.regionKey, (final long keyInMap, final RegionIOTasks tasks) -> { ++ if (tasks != RegionIOTasks.this) { ++ throw new IllegalStateException("Region task mismatch"); + } -+ read.complete(this, compound, throwable); ++ ++tasks.executedTasks; ++ if (tasks.createdTasks != tasks.executedTasks) { ++ return tasks; ++ } ++ ++ if (tasks.task != null) { ++ throw new IllegalStateException("Task may not be null when created==executed"); ++ } ++ ++ return null; ++ }); ++ } ++ } ++ ++ private final class WrappedTask implements PrioritisedExecutor.PrioritisedTask { ++ ++ private final PrioritisedExecutor.PrioritisedTask wrapped; ++ ++ public WrappedTask(final PrioritisedExecutor.PrioritisedTask wrap) { ++ this.wrapped = wrap; ++ } ++ ++ @Override ++ public PrioritisedExecutor getExecutor() { ++ return RegionIOTasks.this.ioScheduler.executor; ++ } ++ ++ @Override ++ public boolean queue() { ++ synchronized (RegionIOTasks.this) { ++ if (this.wrapped.queue()) { ++ RegionIOTasks.this.adjustTaskPriority(); ++ return true; ++ } ++ return false; + } + } + -+ CompoundTag write = this.inProgressWrite; -+ -+ if (write == NOTHING_TO_WRITE) { -+ final ChunkDataTask inMap = this.taskController.tasks.compute(chunkKey, (final long keyInMap, final ChunkDataTask valueInMap) -> { -+ if (valueInMap == null) { -+ throw new IllegalStateException("Write completed concurrently, expected this task: " + ChunkDataTask.this.toString() + ", report this!"); -+ } -+ if (valueInMap != ChunkDataTask.this) { -+ throw new IllegalStateException("Chunk task mismatch, expected this task: " + ChunkDataTask.this.toString() + ", got: " + valueInMap.toString() + ", report this!"); -+ } -+ return valueInMap.inProgressWrite == NOTHING_TO_WRITE ? null : valueInMap; -+ }); -+ -+ if (inMap == null) { -+ return; // set the task value to null, indicating we're done -+ } // else: inProgressWrite changed, so now we have something to write ++ @Override ++ public boolean isQueued() { ++ return this.wrapped.isQueued(); + } + -+ for (;;) { -+ write = this.inProgressWrite; -+ final CompoundTag dataWritten = write; ++ @Override ++ public boolean cancel() { ++ throw new UnsupportedOperationException(); ++ } + -+ boolean failedWrite = false; ++ @Override ++ public boolean execute() { ++ throw new UnsupportedOperationException(); ++ } + -+ try { -+ this.taskController.writeData(this.chunkX, this.chunkZ, write); -+ } catch (final Throwable thr) { -+ if (thr instanceof RegionFileStorage.RegionFileSizeException) { -+ final int maxSize = RegionFile.MAX_CHUNK_SIZE / (1024 * 1024); -+ LOGGER.error("Chunk at (" + this.chunkX + "," + this.chunkZ + ") in '" + WorldUtil.getWorldName(this.world) + "' exceeds max size of " + maxSize + "MiB, it has been deleted from disk."); -+ } else { -+ failedWrite = thr instanceof IOException; -+ LOGGER.error("Failed to write chunk data for task: " + this.toString(), thr); ++ @Override ++ public Priority getPriority() { ++ return this.wrapped.getPriority(); ++ } ++ ++ @Override ++ public boolean setPriority(final Priority priority) { ++ synchronized (RegionIOTasks.this) { ++ if (this.wrapped.setPriority(priority) && this.wrapped.isQueued()) { ++ RegionIOTasks.this.adjustTaskPriority(); ++ return true; + } ++ return false; + } ++ } + -+ final boolean finalFailWrite = failedWrite; -+ final boolean[] done = new boolean[] { false }; -+ -+ this.taskController.tasks.compute(chunkKey, (final long keyInMap, final ChunkDataTask valueInMap) -> { -+ if (valueInMap == null) { -+ throw new IllegalStateException("Write completed concurrently, expected this task: " + ChunkDataTask.this.toString() + ", report this!"); ++ @Override ++ public boolean raisePriority(final Priority priority) { ++ synchronized (RegionIOTasks.this) { ++ if (this.wrapped.raisePriority(priority) && this.wrapped.isQueued()) { ++ RegionIOTasks.this.adjustTaskPriority(); ++ return true; + } -+ if (valueInMap != ChunkDataTask.this) { -+ throw new IllegalStateException("Chunk task mismatch, expected this task: " + ChunkDataTask.this.toString() + ", got: " + valueInMap.toString() + ", report this!"); -+ } -+ if (valueInMap.inProgressWrite == dataWritten) { -+ valueInMap.failedWrite = finalFailWrite; -+ done[0] = true; -+ // keep the data in map if we failed the write so we can try to prevent data loss -+ return finalFailWrite ? valueInMap : null; -+ } -+ // different data than expected, means we need to retry write -+ return valueInMap; -+ }); -+ -+ if (done[0]) { -+ return; ++ return false; + } ++ } + -+ // fetch & write new data -+ continue; ++ @Override ++ public boolean lowerPriority(final Priority priority) { ++ synchronized (RegionIOTasks.this) { ++ if (this.wrapped.lowerPriority(priority) && this.wrapped.isQueued()) { ++ RegionIOTasks.this.adjustTaskPriority(); ++ return true; ++ } ++ return false; ++ } ++ } ++ ++ @Override ++ public long getSubOrder() { ++ return this.wrapped.getSubOrder(); ++ } ++ ++ @Override ++ public boolean setSubOrder(final long subOrder) { ++ synchronized (RegionIOTasks.this) { ++ if (this.wrapped.setSubOrder(subOrder) && this.wrapped.isQueued()) { ++ RegionIOTasks.this.adjustTaskPriority(); ++ return true; ++ } ++ return false; ++ } ++ } ++ ++ @Override ++ public boolean raiseSubOrder(final long subOrder) { ++ synchronized (RegionIOTasks.this) { ++ if (this.wrapped.raiseSubOrder(subOrder) && this.wrapped.isQueued()) { ++ RegionIOTasks.this.adjustTaskPriority(); ++ return true; ++ } ++ return false; ++ } ++ } ++ ++ @Override ++ public boolean lowerSubOrder(final long subOrder) { ++ synchronized (RegionIOTasks.this) { ++ if (this.wrapped.lowerSubOrder(subOrder) && this.wrapped.isQueued()) { ++ RegionIOTasks.this.adjustTaskPriority(); ++ return true; ++ } ++ return false; ++ } ++ } ++ ++ @Override ++ public boolean setPriorityAndSubOrder(final Priority priority, final long subOrder) { ++ synchronized (RegionIOTasks.this) { ++ if (this.wrapped.setPriorityAndSubOrder(priority, subOrder) && this.wrapped.isQueued()) { ++ RegionIOTasks.this.adjustTaskPriority(); ++ return true; ++ } ++ return false; ++ } + } + } + } +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/ChunkDataController.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/ChunkDataController.java new file mode 100644 -index 0000000000000000000000000000000000000000..c35e0c29700be48dda3e53e7d2db224766ef17b7 +index 0000000000000000000000000000000000000000..a36ab89f5c37f5f9ab0152f087bb4cf3560f8581 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/ChunkDataController.java -@@ -0,0 +1,56 @@ +@@ -0,0 +1,50 @@ +package ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller; + -+import ca.spottedleaf.moonrise.patches.chunk_system.io.RegionFileIOThread; ++import ca.spottedleaf.moonrise.patches.chunk_system.io.ChunkSystemRegionFileStorage; ++import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO; ++import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemChunkMap; ++import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler; +import ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemChunkStorage; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.storage.RegionFileStorage; +import java.io.IOException; -+import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + -+public final class ChunkDataController extends RegionFileIOThread.ChunkDataController { ++public final class ChunkDataController extends MoonriseRegionFileIO.RegionDataController { + + private final ServerLevel world; + -+ public ChunkDataController(final ServerLevel world) { -+ super(RegionFileIOThread.RegionFileType.CHUNK_DATA); ++ public ChunkDataController(final ServerLevel world, final ChunkTaskScheduler taskScheduler) { ++ super(MoonriseRegionFileIO.RegionFileType.CHUNK_DATA, taskScheduler.ioExecutor, taskScheduler.compressionExecutor); + this.world = world; + } + @@ -1855,43 +2558,37 @@ index 0000000000000000000000000000000000000000..c35e0c29700be48dda3e53e7d2db2247 + } + + @Override -+ public void writeData(final int chunkX, final int chunkZ, final CompoundTag compound) throws IOException { -+ final CompletableFuture future = this.world.getChunkSource().chunkMap.write(new ChunkPos(chunkX, chunkZ), compound); -+ -+ try { -+ if (future != null) { -+ // rets non-null when sync writing (i.e. future should be completed here) -+ future.join(); -+ } -+ } catch (final CompletionException ex) { -+ if (ex.getCause() instanceof IOException ioException) { -+ throw ioException; -+ } -+ throw ex; -+ } ++ public WriteData startWrite(final int chunkX, final int chunkZ, final CompoundTag compound) throws IOException { ++ return ((ChunkSystemRegionFileStorage)this.getCache()).moonrise$startWrite(chunkX, chunkZ, compound); + } + + @Override -+ public CompoundTag readData(final int chunkX, final int chunkZ) throws IOException { -+ try { -+ return this.world.getChunkSource().chunkMap.read(new ChunkPos(chunkX, chunkZ)).join().orElse(null); -+ } catch (final CompletionException ex) { -+ if (ex.getCause() instanceof IOException ioException) { -+ throw ioException; -+ } -+ throw ex; -+ } ++ public void finishWrite(final int chunkX, final int chunkZ, final WriteData writeData) throws IOException { ++ ((ChunkSystemChunkMap)this.world.getChunkSource().chunkMap).moonrise$writeFinishCallback(new ChunkPos(chunkX, chunkZ)); ++ ((ChunkSystemRegionFileStorage)this.getCache()).moonrise$finishWrite(chunkX, chunkZ, writeData); ++ } ++ ++ @Override ++ public ReadData readData(final int chunkX, final int chunkZ) throws IOException { ++ return ((ChunkSystemRegionFileStorage)this.getCache()).moonrise$readData(chunkX, chunkZ); ++ } ++ ++ @Override ++ public CompoundTag finishRead(final int chunkX, final int chunkZ, final ReadData readData) throws IOException { ++ return ((ChunkSystemRegionFileStorage)this.getCache()).moonrise$finishRead(chunkX, chunkZ, readData); + } +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/EntityDataController.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/EntityDataController.java new file mode 100644 -index 0000000000000000000000000000000000000000..fdd189ef056187941d43809c5d61cab717aecf60 +index 0000000000000000000000000000000000000000..828c868f68c2a20bf90d0f7ec253fdeb591f15f6 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/EntityDataController.java -@@ -0,0 +1,55 @@ +@@ -0,0 +1,73 @@ +package ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller; + -+import ca.spottedleaf.moonrise.patches.chunk_system.io.RegionFileIOThread; ++import ca.spottedleaf.moonrise.patches.chunk_system.io.ChunkSystemRegionFileStorage; ++import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO; ++import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.storage.EntityStorage; @@ -1900,12 +2597,12 @@ index 0000000000000000000000000000000000000000..fdd189ef056187941d43809c5d61cab7 +import java.io.IOException; +import java.nio.file.Path; + -+public final class EntityDataController extends RegionFileIOThread.ChunkDataController { ++public final class EntityDataController extends MoonriseRegionFileIO.RegionDataController { + + private final EntityRegionFileStorage storage; + -+ public EntityDataController(final EntityRegionFileStorage storage) { -+ super(RegionFileIOThread.RegionFileType.ENTITY_DATA); ++ public EntityDataController(final EntityRegionFileStorage storage, final ChunkTaskScheduler taskScheduler) { ++ super(MoonriseRegionFileIO.RegionFileType.ENTITY_DATA, taskScheduler.ioExecutor, taskScheduler.compressionExecutor); + this.storage = storage; + } + @@ -1915,13 +2612,35 @@ index 0000000000000000000000000000000000000000..fdd189ef056187941d43809c5d61cab7 + } + + @Override -+ public void writeData(final int chunkX, final int chunkZ, final CompoundTag compound) throws IOException { -+ this.storage.write(new ChunkPos(chunkX, chunkZ), compound); ++ public WriteData startWrite(final int chunkX, final int chunkZ, final CompoundTag compound) throws IOException { ++ checkPosition(new ChunkPos(chunkX, chunkZ), compound); ++ ++ return ((ChunkSystemRegionFileStorage)this.getCache()).moonrise$startWrite(chunkX, chunkZ, compound); + } + + @Override -+ public CompoundTag readData(final int chunkX, final int chunkZ) throws IOException { -+ return this.storage.read(new ChunkPos(chunkX, chunkZ)); ++ public void finishWrite(final int chunkX, final int chunkZ, final WriteData writeData) throws IOException { ++ ((ChunkSystemRegionFileStorage)this.getCache()).moonrise$finishWrite(chunkX, chunkZ, writeData); ++ } ++ ++ @Override ++ public ReadData readData(final int chunkX, final int chunkZ) throws IOException { ++ return ((ChunkSystemRegionFileStorage)this.getCache()).moonrise$readData(chunkX, chunkZ); ++ } ++ ++ @Override ++ public CompoundTag finishRead(final int chunkX, final int chunkZ, final ReadData readData) throws IOException { ++ return ((ChunkSystemRegionFileStorage)this.getCache()).moonrise$finishRead(chunkX, chunkZ, readData); ++ } ++ ++ private static void checkPosition(final ChunkPos pos, final CompoundTag nbt) { ++ final ChunkPos nbtPos = nbt == null ? null : EntityStorage.readChunkPos(nbt); ++ if (nbtPos != null && !pos.equals(nbtPos)) { ++ throw new IllegalArgumentException( ++ "Entity chunk coordinate and serialized data do not have matching coordinates, trying to serialize coordinate " + pos.toString() ++ + " but compound says coordinate is " + nbtPos ++ ); ++ } + } + + public static final class EntityRegionFileStorage extends RegionFileStorage { @@ -1933,38 +2652,34 @@ index 0000000000000000000000000000000000000000..fdd189ef056187941d43809c5d61cab7 + + @Override + public void write(final ChunkPos pos, final CompoundTag nbt) throws IOException { -+ final ChunkPos nbtPos = nbt == null ? null : EntityStorage.readChunkPos(nbt); -+ if (nbtPos != null && !pos.equals(nbtPos)) { -+ throw new IllegalArgumentException( -+ "Entity chunk coordinate and serialized data do not have matching coordinates, trying to serialize coordinate " + pos.toString() -+ + " but compound says coordinate is " + nbtPos + " for world: " + this -+ ); -+ } ++ checkPosition(pos, nbt); + super.write(pos, nbt); + } + } +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/PoiDataController.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/PoiDataController.java new file mode 100644 -index 0000000000000000000000000000000000000000..af867f8fedd0bb8f675e94243aa1a3f17363483b +index 0000000000000000000000000000000000000000..bd0d782852f9cfe5bc0b5339ecf4d82c10332ec9 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/PoiDataController.java -@@ -0,0 +1,33 @@ +@@ -0,0 +1,45 @@ +package ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller; + -+import ca.spottedleaf.moonrise.patches.chunk_system.io.RegionFileIOThread; ++import ca.spottedleaf.moonrise.patches.chunk_system.io.ChunkSystemRegionFileStorage; ++import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO; +import ca.spottedleaf.moonrise.patches.chunk_system.level.storage.ChunkSystemSectionStorage; ++import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.chunk.storage.RegionFileStorage; +import java.io.IOException; + -+public final class PoiDataController extends RegionFileIOThread.ChunkDataController { ++public final class PoiDataController extends MoonriseRegionFileIO.RegionDataController { + + private final ServerLevel world; + -+ public PoiDataController(final ServerLevel world) { -+ super(RegionFileIOThread.RegionFileType.POI_DATA); ++ public PoiDataController(final ServerLevel world, final ChunkTaskScheduler taskScheduler) { ++ super(MoonriseRegionFileIO.RegionFileType.POI_DATA, taskScheduler.ioExecutor, taskScheduler.compressionExecutor); + this.world = world; + } + @@ -1974,23 +2689,50 @@ index 0000000000000000000000000000000000000000..af867f8fedd0bb8f675e94243aa1a3f1 + } + + @Override -+ public void writeData(final int chunkX, final int chunkZ, final CompoundTag compound) throws IOException { -+ ((ChunkSystemSectionStorage)this.world.getPoiManager()).moonrise$write(chunkX, chunkZ, compound); ++ public WriteData startWrite(final int chunkX, final int chunkZ, final CompoundTag compound) throws IOException { ++ return ((ChunkSystemRegionFileStorage)this.getCache()).moonrise$startWrite(chunkX, chunkZ, compound); + } + + @Override -+ public CompoundTag readData(final int chunkX, final int chunkZ) throws IOException { -+ return ((ChunkSystemSectionStorage)this.world.getPoiManager()).moonrise$read(chunkX, chunkZ); ++ public void finishWrite(final int chunkX, final int chunkZ, final WriteData writeData) throws IOException { ++ ((ChunkSystemRegionFileStorage)this.getCache()).moonrise$finishWrite(chunkX, chunkZ, writeData); + } ++ ++ @Override ++ public ReadData readData(final int chunkX, final int chunkZ) throws IOException { ++ return ((ChunkSystemRegionFileStorage)this.getCache()).moonrise$readData(chunkX, chunkZ); ++ } ++ ++ @Override ++ public CompoundTag finishRead(final int chunkX, final int chunkZ, final ReadData readData) throws IOException { ++ return ((ChunkSystemRegionFileStorage)this.getCache()).moonrise$finishRead(chunkX, chunkZ, readData); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemChunkMap.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemChunkMap.java +new file mode 100644 +index 0000000000000000000000000000000000000000..47a4d3376d08dde94a39254bec21473ff27f53e6 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemChunkMap.java +@@ -0,0 +1,10 @@ ++package ca.spottedleaf.moonrise.patches.chunk_system.level; ++ ++import net.minecraft.world.level.ChunkPos; ++import java.io.IOException; ++ ++public interface ChunkSystemChunkMap { ++ ++ public void moonrise$writeFinishCallback(final ChunkPos pos) throws IOException; ++ +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemLevel.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemLevel.java new file mode 100644 -index 0000000000000000000000000000000000000000..efcd9057f008f0b9cf0d22b2b21d1851205841e5 +index 0000000000000000000000000000000000000000..5d4d650186b18eb00782429d53d861564d8e4ba9 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemLevel.java -@@ -0,0 +1,22 @@ +@@ -0,0 +1,33 @@ +package ca.spottedleaf.moonrise.patches.chunk_system.level; + ++import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData; +import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.EntityLookup; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.LevelChunk; @@ -2010,6 +2752,16 @@ index 0000000000000000000000000000000000000000..efcd9057f008f0b9cf0d22b2b21d1851 + + public void moonrise$midTickTasks(); + ++ public ChunkData moonrise$getChunkData(final long chunkKey); ++ ++ public ChunkData moonrise$getChunkData(final int chunkX, final int chunkZ); ++ ++ public ChunkData moonrise$requestChunkData(final long chunkKey); ++ ++ public ChunkData moonrise$releaseChunkData(final long chunkKey); ++ ++ public boolean moonrise$areChunksLoaded(final int fromX, final int fromZ, final int toX, final int toZ); ++ +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemLevelReader.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemLevelReader.java new file mode 100644 @@ -2029,19 +2781,20 @@ index 0000000000000000000000000000000000000000..0b58701342d573fa43cdd06681534854 +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemServerLevel.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemServerLevel.java new file mode 100644 -index 0000000000000000000000000000000000000000..b8a87b7e6505feb76ce1bd58c84615256cf6faa6 +index 0000000000000000000000000000000000000000..9d46482476f9ed9032a2b0f89afc20e03ed42dbb --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemServerLevel.java -@@ -0,0 +1,61 @@ +@@ -0,0 +1,64 @@ +package ca.spottedleaf.moonrise.patches.chunk_system.level; + -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; ++import ca.spottedleaf.concurrentutil.util.Priority; +import ca.spottedleaf.moonrise.common.list.ReferenceList; +import ca.spottedleaf.moonrise.common.misc.NearbyPlayers; -+import ca.spottedleaf.moonrise.patches.chunk_system.io.RegionFileIOThread; ++import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO; +import ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader; +import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler; +import net.minecraft.core.BlockPos; ++import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.status.ChunkStatus; @@ -2052,32 +2805,34 @@ index 0000000000000000000000000000000000000000..b8a87b7e6505feb76ce1bd58c8461525 + + public ChunkTaskScheduler moonrise$getChunkTaskScheduler(); + -+ public RegionFileIOThread.ChunkDataController moonrise$getChunkDataController(); ++ public MoonriseRegionFileIO.RegionDataController moonrise$getChunkDataController(); + -+ public RegionFileIOThread.ChunkDataController moonrise$getPoiChunkDataController(); ++ public MoonriseRegionFileIO.RegionDataController moonrise$getPoiChunkDataController(); + -+ public RegionFileIOThread.ChunkDataController moonrise$getEntityChunkDataController(); ++ public MoonriseRegionFileIO.RegionDataController moonrise$getEntityChunkDataController(); + + public int moonrise$getRegionChunkShift(); + -+ // Paper - marked closing not needed on CB ++ public boolean moonrise$isMarkedClosing(); ++ ++ public void moonrise$setMarkedClosing(final boolean value); + + public RegionizedPlayerChunkLoader moonrise$getPlayerChunkLoader(); + + public void moonrise$loadChunksAsync(final BlockPos pos, final int radiusBlocks, -+ final PrioritisedExecutor.Priority priority, ++ final Priority priority, + final Consumer> onLoad); + + public void moonrise$loadChunksAsync(final BlockPos pos, final int radiusBlocks, -+ final ChunkStatus chunkStatus, final PrioritisedExecutor.Priority priority, ++ final ChunkStatus chunkStatus, final Priority priority, + final Consumer> onLoad); + + public void moonrise$loadChunksAsync(final int minChunkX, final int maxChunkX, final int minChunkZ, final int maxChunkZ, -+ final PrioritisedExecutor.Priority priority, ++ final Priority priority, + final Consumer> onLoad); + + public void moonrise$loadChunksAsync(final int minChunkX, final int maxChunkX, final int minChunkZ, final int maxChunkZ, -+ final ChunkStatus chunkStatus, final PrioritisedExecutor.Priority priority, ++ final ChunkStatus chunkStatus, final Priority priority, + final Consumer> onLoad); + + public RegionizedPlayerChunkLoader.ViewDistanceHolder moonrise$getViewDistanceHolder(); @@ -2094,6 +2849,33 @@ index 0000000000000000000000000000000000000000..b8a87b7e6505feb76ce1bd58c8461525 + + public ReferenceList moonrise$getEntityTickingChunks(); +} +diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkData.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkData.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8b9dc582627b46843f4b5ea6f8c3df2d8cac46fa +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkData.java +@@ -0,0 +1,21 @@ ++package ca.spottedleaf.moonrise.patches.chunk_system.level.chunk; ++ ++import ca.spottedleaf.moonrise.common.misc.NearbyPlayers; ++ ++public final class ChunkData { ++ ++ private int referenceCount = 0; ++ public NearbyPlayers.TrackedChunk nearbyPlayers; // Moonrise - nearby players ++ ++ public ChunkData() { ++ ++ } ++ ++ public int increaseRef() { ++ return ++this.referenceCount; ++ } ++ ++ public int decreaseRef() { ++ return --this.referenceCount; ++ } ++} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemChunkHolder.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemChunkHolder.java new file mode 100644 index 0000000000000000000000000000000000000000..7d049d750df88762566f13a9c4fc7574a2df4825 @@ -2160,18 +2942,21 @@ index 0000000000000000000000000000000000000000..f4bc44bb266763345c4e6f859c89352c +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemDistanceManager.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemDistanceManager.java new file mode 100644 -index 0000000000000000000000000000000000000000..883fe6401f1b9711fa544d18a815b4d638f580df +index 0000000000000000000000000000000000000000..aacd543f03b35908011d0c2891e978cc093ebcf5 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemDistanceManager.java -@@ -0,0 +1,9 @@ +@@ -0,0 +1,12 @@ +package ca.spottedleaf.moonrise.patches.chunk_system.level.chunk; + ++import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager; +import net.minecraft.server.level.ChunkMap; + +public interface ChunkSystemDistanceManager { + + public ChunkMap moonrise$getChunkMap(); + ++ public ChunkHolderManager moonrise$getChunkHolderManager(); ++ +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemLevelChunk.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemLevelChunk.java new file mode 100644 @@ -2194,13 +2979,15 @@ index 0000000000000000000000000000000000000000..5b092bca7027e37aeee8f4b852ad896d +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java new file mode 100644 -index 0000000000000000000000000000000000000000..997b05167c19472acb98edac32d4548cc65efa8e +index 0000000000000000000000000000000000000000..5ed6599d1f9a2edf8c904f3602b06d26d857600c --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java -@@ -0,0 +1,819 @@ +@@ -0,0 +1,798 @@ +package ca.spottedleaf.moonrise.patches.chunk_system.level.entity; + ++import ca.spottedleaf.moonrise.common.PlatformHooks; +import ca.spottedleaf.moonrise.common.list.EntityList; ++import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData; +import ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity; +import com.google.common.collect.ImmutableList; +import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; @@ -2213,6 +3000,7 @@ index 0000000000000000000000000000000000000000..997b05167c19472acb98edac32d4548c +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.EntitySpawnReason; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.boss.EnderDragonPart; +import net.minecraft.world.entity.boss.enderdragon.EnderDragon; @@ -2226,7 +3014,6 @@ index 0000000000000000000000000000000000000000..997b05167c19472acb98edac32d4548c +import java.util.Iterator; +import java.util.List; +import java.util.function.Predicate; -+import org.bukkit.event.entity.EntityRemoveEvent; + +public final class ChunkEntitySlices { + @@ -2243,6 +3030,7 @@ index 0000000000000000000000000000000000000000..997b05167c19472acb98edac32d4548c + private final EntityList entities = new EntityList(); + + public FullChunkStatus status; ++ public final ChunkData chunkData; + + private boolean isTransient; + @@ -2255,7 +3043,7 @@ index 0000000000000000000000000000000000000000..997b05167c19472acb98edac32d4548c + } + + public ChunkEntitySlices(final Level world, final int chunkX, final int chunkZ, final FullChunkStatus status, -+ final int minSection, final int maxSection) { // inclusive, inclusive ++ final ChunkData chunkData, final int minSection, final int maxSection) { // inclusive, inclusive + this.minSection = minSection; + this.maxSection = maxSection; + this.chunkX = chunkX; @@ -2268,11 +3056,12 @@ index 0000000000000000000000000000000000000000..997b05167c19472acb98edac32d4548c + this.entitiesByType = new Reference2ObjectOpenHashMap<>(); + + this.status = status; ++ this.chunkData = chunkData; + } + + public static List readEntities(final ServerLevel world, final CompoundTag compoundTag) { + // TODO check this and below on update for format changes -+ return EntityType.loadEntitiesRecursive(compoundTag.getList("Entities", 10), world).collect(ImmutableList.toImmutableList()); ++ return EntityType.loadEntitiesRecursive(compoundTag.getList("Entities", 10), world, EntitySpawnReason.LOAD).collect(ImmutableList.toImmutableList()); + } + + // Paper start - rewrite chunk system @@ -2300,7 +3089,7 @@ index 0000000000000000000000000000000000000000..997b05167c19472acb98edac32d4548c + } + + final ListTag entitiesTag = new ListTag(); -+ for (final Entity entity : entities) { ++ for (final Entity entity : PlatformHooks.get().modifySavedEntities(world, chunkPos.x, chunkPos.z, entities)) { + CompoundTag compoundTag = new CompoundTag(); + if (entity.save(compoundTag)) { + entitiesTag.add(compoundTag); @@ -2347,12 +3136,12 @@ index 0000000000000000000000000000000000000000..997b05167c19472acb98edac32d4548c + continue; + } + if (entity.shouldBeSaved()) { -+ entity.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK, EntityRemoveEvent.Cause.UNLOAD); ++ PlatformHooks.get().unloadEntity(entity); + if (entity.isVehicle()) { + // we cannot assume that these entities are contained within this chunk, because entities can + // desync - so we need to remove them all + for (final Entity passenger : entity.getIndirectPassengers()) { -+ passenger.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK, EntityRemoveEvent.Cause.UNLOAD); ++ PlatformHooks.get().unloadEntity(passenger); + } + } + } @@ -2361,34 +3150,7 @@ index 0000000000000000000000000000000000000000..997b05167c19472acb98edac32d4548c + return this.entities.size() != 0; + } + -+ // Paper start -+ public org.bukkit.entity.Entity[] getChunkEntities() { -+ List ret = new java.util.ArrayList<>(); -+ final Entity[] entities = this.entities.getRawData(); -+ for (int i = 0, size = Math.min(entities.length, this.entities.size()); i < size; ++i) { -+ final Entity entity = entities[i]; -+ if (entity == null) { -+ continue; -+ } -+ final org.bukkit.entity.Entity bukkit = entity.getBukkitEntity(); -+ if (bukkit != null && bukkit.isValid()) { -+ ret.add(bukkit); -+ } -+ } -+ -+ return ret.toArray(new org.bukkit.entity.Entity[0]); -+ } -+ -+ public void callEntitiesLoadEvent() { -+ org.bukkit.craftbukkit.event.CraftEventFactory.callEntitiesLoadEvent(this.world, new ChunkPos(this.chunkX, this.chunkZ), this.getAllEntities()); -+ } -+ -+ public void callEntitiesUnloadEvent() { -+ org.bukkit.craftbukkit.event.CraftEventFactory.callEntitiesUnloadEvent(this.world, new ChunkPos(this.chunkX, this.chunkZ), this.getAllEntities()); -+ } -+ // Paper end -+ -+ private List getAllEntities() { ++ public List getAllEntities() { + final int len = this.entities.size(); + if (len == 0) { + return new ArrayList<>(); @@ -2451,6 +3213,7 @@ index 0000000000000000000000000000000000000000..997b05167c19472acb98edac32d4548c + return false; + } + ((ChunkSystemEntity)entity).moonrise$setChunkStatus(this.status); ++ ((ChunkSystemEntity)entity).moonrise$setChunkData(this.chunkData); + final int sectionIndex = chunkSection - this.minSection; + + this.allEntities.addEntity(entity, sectionIndex); @@ -2484,6 +3247,7 @@ index 0000000000000000000000000000000000000000..997b05167c19472acb98edac32d4548c + return false; + } + ((ChunkSystemEntity)entity).moonrise$setChunkStatus(null); ++ ((ChunkSystemEntity)entity).moonrise$setChunkData(null); + final int sectionIndex = chunkSection - this.minSection; + + this.allEntities.removeEntity(entity, sectionIndex); @@ -3019,7 +3783,7 @@ index 0000000000000000000000000000000000000000..997b05167c19472acb98edac32d4548c +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java new file mode 100644 -index 0000000000000000000000000000000000000000..efc0c1acc8239dd7b00211a1d3bfd3fc3b2c810c +index 0000000000000000000000000000000000000000..93335de8cf514dc8417e4b9b2d495663deda2904 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java @@ -0,0 +1,1083 @@ @@ -3071,8 +3835,6 @@ index 0000000000000000000000000000000000000000..efc0c1acc8239dd7b00211a1d3bfd3fc + + protected final SWMRLong2ObjectHashTable regions = new SWMRLong2ObjectHashTable<>(128, 0.5f); + -+ protected final int minSection; // inclusive -+ protected final int maxSection; // inclusive + protected final LevelCallback worldCallback; + + protected final ConcurrentLong2ReferenceChainedHashTable entityById = new ConcurrentLong2ReferenceChainedHashTable<>(); @@ -3081,8 +3843,6 @@ index 0000000000000000000000000000000000000000..efc0c1acc8239dd7b00211a1d3bfd3fc + + public EntityLookup(final Level world, final LevelCallback worldCallback) { + this.world = world; -+ this.minSection = WorldUtil.getMinSection(world); -+ this.maxSection = WorldUtil.getMaxSection(world); + this.worldCallback = worldCallback; + } + @@ -3116,7 +3876,7 @@ index 0000000000000000000000000000000000000000..efc0c1acc8239dd7b00211a1d3bfd3fc + + protected abstract void entityEndTicking(final Entity entity); + -+ protected abstract boolean screenEntity(final Entity entity); ++ protected abstract boolean screenEntity(final Entity entity, final boolean fromDisk, final boolean event); + + private static Entity maskNonAccessible(final Entity entity) { + if (entity == null) { @@ -3372,7 +4132,7 @@ index 0000000000000000000000000000000000000000..efc0c1acc8239dd7b00211a1d3bfd3fc + } + + protected void addRecursivelySafe(final Entity root, final boolean fromDisk) { -+ if (!this.addEntity(root, fromDisk)) { ++ if (!this.addEntity(root, fromDisk, true)) { + // possible we are a passenger, and so should dismount from any valid entity in the world + root.stopRiding(); + return; @@ -3411,7 +4171,11 @@ index 0000000000000000000000000000000000000000..efc0c1acc8239dd7b00211a1d3bfd3fc + } + + public boolean addNewEntity(final Entity entity) { -+ return this.addEntity(entity, false); ++ return this.addNewEntity(entity, true); ++ } ++ ++ public boolean addNewEntity(final Entity entity, final boolean event) { ++ return this.addEntity(entity, false, event); + } + + public static Visibility getEntityStatus(final Entity entity) { @@ -3422,10 +4186,10 @@ index 0000000000000000000000000000000000000000..efc0c1acc8239dd7b00211a1d3bfd3fc + return Visibility.fromFullChunkStatus(entityStatus == null ? FullChunkStatus.INACCESSIBLE : entityStatus); + } + -+ protected boolean addEntity(final Entity entity, final boolean fromDisk) { ++ protected boolean addEntity(final Entity entity, final boolean fromDisk, final boolean event) { + final BlockPos pos = entity.blockPosition(); + final int sectionX = pos.getX() >> 4; -+ final int sectionY = Mth.clamp(pos.getY() >> 4, this.minSection, this.maxSection); ++ final int sectionY = Mth.clamp(pos.getY() >> 4, WorldUtil.getMinSection(this.world), WorldUtil.getMaxSection(this.world)); + final int sectionZ = pos.getZ() >> 4; + this.checkThread(sectionX, sectionZ, "Cannot add entity off-main thread"); + @@ -3439,7 +4203,7 @@ index 0000000000000000000000000000000000000000..efc0c1acc8239dd7b00211a1d3bfd3fc + return false; + } + -+ if (!this.screenEntity(entity)) { ++ if (!this.screenEntity(entity, fromDisk, event)) { + return false; + } + @@ -3544,7 +4308,7 @@ index 0000000000000000000000000000000000000000..efc0c1acc8239dd7b00211a1d3bfd3fc + final int sectionZ = ((ChunkSystemEntity)entity).moonrise$getSectionZ(); + final BlockPos newPos = entity.blockPosition(); + final int newSectionX = newPos.getX() >> 4; -+ final int newSectionY = Mth.clamp(newPos.getY() >> 4, this.minSection, this.maxSection); ++ final int newSectionY = Mth.clamp(newPos.getY() >> 4, WorldUtil.getMinSection(this.world), WorldUtil.getMaxSection(this.world)); + final int newSectionZ = newPos.getZ() >> 4; + + if (newSectionX == sectionX && newSectionY == sectionY && newSectionZ == sectionZ) { @@ -3984,7 +4748,7 @@ index 0000000000000000000000000000000000000000..efc0c1acc8239dd7b00211a1d3bfd3fc + + public ChunkEntitySlices getOrCreateChunk(final int chunkX, final int chunkZ) { + final ChunkSlicesRegion region = this.getRegion(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT); -+ ChunkEntitySlices ret; ++ final ChunkEntitySlices ret; + if (region == null || (ret = region.get((chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT))) == null) { + return this.createEntityChunk(chunkX, chunkZ, true); + } @@ -4082,7 +4846,7 @@ index 0000000000000000000000000000000000000000..efc0c1acc8239dd7b00211a1d3bfd3fc + @Override + public void onRemove(final Entity.RemovalReason reason) { + final Entity entity = this.entity; -+ EntityLookup.this.checkThread(entity, "Cannot remove entity off-main"); // Paper - rewrite chunk system ++ EntityLookup.this.checkThread(entity, "Cannot remove entity off-main"); + final Visibility tickingState = EntityLookup.getEntityStatus(entity); + + EntityLookup.this.removeEntity(entity); @@ -4106,15 +4870,15 @@ index 0000000000000000000000000000000000000000..efc0c1acc8239dd7b00211a1d3bfd3fc + public void onRemove(final Entity.RemovalReason reason) {} + } +} -\ No newline at end of file diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/client/ClientEntityLookup.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/client/ClientEntityLookup.java new file mode 100644 -index 0000000000000000000000000000000000000000..edcde00206d068bd79175fea33efa05b0e8c1562 +index 0000000000000000000000000000000000000000..a038215156a163b0b1cbc870ada5b4ac85ed1335 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/client/ClientEntityLookup.java -@@ -0,0 +1,123 @@ +@@ -0,0 +1,129 @@ +package ca.spottedleaf.moonrise.patches.chunk_system.level.entity.client; + ++import ca.spottedleaf.moonrise.common.PlatformHooks; +import ca.spottedleaf.moonrise.common.util.CoordinateUtils; +import ca.spottedleaf.moonrise.common.util.WorldUtil; +import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.ChunkEntitySlices; @@ -4160,7 +4924,8 @@ index 0000000000000000000000000000000000000000..edcde00206d068bd79175fea33efa05b + + final ChunkEntitySlices ret = new ChunkEntitySlices( + this.world, chunkX, chunkZ, -+ ticking ? FullChunkStatus.ENTITY_TICKING : FullChunkStatus.FULL, WorldUtil.getMinSection(this.world), WorldUtil.getMaxSection(this.world) ++ ticking ? FullChunkStatus.ENTITY_TICKING : FullChunkStatus.FULL, null, ++ WorldUtil.getMinSection(this.world), WorldUtil.getMaxSection(this.world) + ); + + // note: not handled by superclass @@ -4178,7 +4943,11 @@ index 0000000000000000000000000000000000000000..edcde00206d068bd79175fea33efa05b + protected void entitySectionChangeCallback(final Entity entity, + final int oldSectionX, final int oldSectionY, final int oldSectionZ, + final int newSectionX, final int newSectionY, final int newSectionZ) { -+ ++ PlatformHooks.get().entityMove( ++ entity, ++ CoordinateUtils.getChunkSectionKey(oldSectionX, oldSectionY, oldSectionZ), ++ CoordinateUtils.getChunkSectionKey(newSectionX, newSectionY, newSectionZ) ++ ); + } + + @Override @@ -4212,7 +4981,7 @@ index 0000000000000000000000000000000000000000..edcde00206d068bd79175fea33efa05b + } + + @Override -+ protected boolean screenEntity(final Entity entity) { ++ protected boolean screenEntity(final Entity entity, final boolean fromDisk, final boolean event) { + return true; + } + @@ -4238,7 +5007,7 @@ index 0000000000000000000000000000000000000000..edcde00206d068bd79175fea33efa05b +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/dfl/DefaultEntityLookup.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/dfl/DefaultEntityLookup.java new file mode 100644 -index 0000000000000000000000000000000000000000..465469e44346c50f30f3abd6b44f4173ccfcf248 +index 0000000000000000000000000000000000000000..2ff58cf753c60913ee73aae015182e9c5560d529 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/dfl/DefaultEntityLookup.java @@ -0,0 +1,114 @@ @@ -4276,7 +5045,7 @@ index 0000000000000000000000000000000000000000..465469e44346c50f30f3abd6b44f4173 + protected ChunkEntitySlices createEntityChunk(final int chunkX, final int chunkZ, final boolean transientChunk) { + final ChunkEntitySlices ret = new ChunkEntitySlices( + this.world, chunkX, chunkZ, FullChunkStatus.FULL, -+ WorldUtil.getMinSection(this.world), WorldUtil.getMaxSection(this.world) ++ null, WorldUtil.getMinSection(this.world), WorldUtil.getMaxSection(this.world) + ); + + // note: not handled by superclass @@ -4328,7 +5097,7 @@ index 0000000000000000000000000000000000000000..465469e44346c50f30f3abd6b44f4173 + } + + @Override -+ protected boolean screenEntity(final Entity entity) { ++ protected boolean screenEntity(final Entity entity, final boolean fromDisk, final boolean event) { + return true; + } + @@ -4358,13 +5127,15 @@ index 0000000000000000000000000000000000000000..465469e44346c50f30f3abd6b44f4173 +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java new file mode 100644 -index 0000000000000000000000000000000000000000..dacf2b2988ce603879fe525a3418ac77f8a663f7 +index 0000000000000000000000000000000000000000..58d9187adc188b693b6becc400f766e069bf1bf5 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java -@@ -0,0 +1,113 @@ +@@ -0,0 +1,116 @@ +package ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server; + ++import ca.spottedleaf.moonrise.common.PlatformHooks; +import ca.spottedleaf.moonrise.common.list.ReferenceList; ++import ca.spottedleaf.moonrise.common.util.CoordinateUtils; +import ca.spottedleaf.moonrise.common.util.TickThread; +import ca.spottedleaf.moonrise.common.util.ChunkSystem; +import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel; @@ -4381,7 +5152,6 @@ index 0000000000000000000000000000000000000000..dacf2b2988ce603879fe525a3418ac77 + + private final ServerLevel serverWorld; + public final ReferenceList trackerEntities = new ReferenceList<>(EMPTY_ENTITY_ARRAY); // Moonrise - entity tracker -+ public final ReferenceList trackerUnloadedEntities = new ReferenceList<>(EMPTY_ENTITY_ARRAY); // Moonrise - entity tracker + + public ServerEntityLookup(final ServerLevel world, final LevelCallback worldCallback) { + super(world, worldCallback); @@ -4427,6 +5197,11 @@ index 0000000000000000000000000000000000000000..dacf2b2988ce603879fe525a3418ac77 + if (entity instanceof ServerPlayer player) { + ((ChunkSystemServerLevel)this.serverWorld).moonrise$getNearbyPlayers().tickPlayer(player); + } ++ PlatformHooks.get().entityMove( ++ entity, ++ CoordinateUtils.getChunkSectionKey(oldSectionX, oldSectionY, oldSectionZ), ++ CoordinateUtils.getChunkSectionKey(newSectionX, newSectionY, newSectionZ) ++ ); + } + + @Override @@ -4441,14 +5216,12 @@ index 0000000000000000000000000000000000000000..dacf2b2988ce603879fe525a3418ac77 + if (entity instanceof ServerPlayer player) { + ((ChunkSystemServerLevel)this.serverWorld).moonrise$getNearbyPlayers().removePlayer(player); + } -+ this.trackerUnloadedEntities.remove(entity); // Moonrise - entity tracker + } + + @Override + protected void entityStartLoaded(final Entity entity) { + // Moonrise start - entity tracker + this.trackerEntities.add(entity); -+ this.trackerUnloadedEntities.remove(entity); + // Moonrise end - entity tracker + } + @@ -4456,7 +5229,6 @@ index 0000000000000000000000000000000000000000..dacf2b2988ce603879fe525a3418ac77 + protected void entityEndLoaded(final Entity entity) { + // Moonrise start - entity tracker + this.trackerEntities.remove(entity); -+ this.trackerUnloadedEntities.add(entity); + // Moonrise end - entity tracker + } + @@ -4471,8 +5243,8 @@ index 0000000000000000000000000000000000000000..dacf2b2988ce603879fe525a3418ac77 + } + + @Override -+ protected boolean screenEntity(final Entity entity) { -+ return ChunkSystem.screenEntity(this.serverWorld, entity); ++ protected boolean screenEntity(final Entity entity, final boolean fromDisk, final boolean event) { ++ return ChunkSystem.screenEntity(this.serverWorld, entity, fromDisk, event); + } +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/ChunkSystemPoiManager.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/ChunkSystemPoiManager.java @@ -4518,16 +5290,15 @@ index 0000000000000000000000000000000000000000..89b956b8fdf1a0d862a843104511005e +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/PoiChunk.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/PoiChunk.java new file mode 100644 -index 0000000000000000000000000000000000000000..fd35e4db0c8fec8f86b8743bcc2b15ed2e7433f1 +index 0000000000000000000000000000000000000000..bbf9d6c1c9525d97160806819a57be03eca290f1 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/PoiChunk.java -@@ -0,0 +1,212 @@ +@@ -0,0 +1,204 @@ +package ca.spottedleaf.moonrise.patches.chunk_system.level.poi; + +import ca.spottedleaf.moonrise.common.util.CoordinateUtils; +import ca.spottedleaf.moonrise.common.util.TickThread; +import ca.spottedleaf.moonrise.common.util.WorldUtil; -+import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import net.minecraft.SharedConstants; +import net.minecraft.nbt.CompoundTag; @@ -4647,7 +5418,6 @@ index 0000000000000000000000000000000000000000..fd35e4db0c8fec8f86b8743bcc2b15ed + ret.putInt("DataVersion", SharedConstants.getCurrentVersion().getDataVersion().getVersion()); + + final ServerLevel world = this.world; -+ final PoiManager poiManager = world.getPoiManager(); + final int chunkX = this.chunkX; + final int chunkZ = this.chunkZ; + @@ -4657,13 +5427,8 @@ index 0000000000000000000000000000000000000000..fd35e4db0c8fec8f86b8743bcc2b15ed + continue; + } + -+ final long key = CoordinateUtils.getChunkSectionKey(chunkX, sectionY, chunkZ); -+ // codecs are honestly such a fucking disaster. What the fuck is this trash? -+ final Codec codec = PoiSection.codec(() -> { -+ poiManager.setDirty(key); -+ }); -+ -+ final DataResult serializedResult = codec.encodeStart(registryOps, section); ++ // I do not believe asynchronously converting to CompoundTag is worth the scheduling. ++ final DataResult serializedResult = PoiSection.Packed.CODEC.encodeStart(registryOps, section.pack()); + final int finalSectionY = sectionY; + final Tag serialized = serializedResult.resultOrPartial((final String description) -> { + LOGGER.error("Failed to serialize poi chunk for world: " + WorldUtil.getWorldName(world) + ", chunk: (" + chunkX + "," + finalSectionY + "," + chunkZ + "); description: " + description); @@ -4707,19 +5472,18 @@ index 0000000000000000000000000000000000000000..fd35e4db0c8fec8f86b8743bcc2b15ed + continue; + } + -+ final long coordinateKey = CoordinateUtils.getChunkSectionKey(chunkX, sectionY, chunkZ); -+ // codecs are honestly such a fucking disaster. What the fuck is this trash? -+ final Codec codec = PoiSection.codec(() -> { -+ poiManager.setDirty(coordinateKey); -+ }); -+ + final CompoundTag section = sections.getCompound(key); -+ final DataResult deserializeResult = codec.parse(registryOps, section); ++ final DataResult deserializeResult = PoiSection.Packed.CODEC.parse(registryOps, section); + final int finalSectionY = sectionY; -+ final PoiSection deserialized = deserializeResult.resultOrPartial((final String description) -> { ++ final PoiSection.Packed packed = deserializeResult.resultOrPartial((final String description) -> { + LOGGER.error("Failed to deserialize poi chunk for world: " + WorldUtil.getWorldName(world) + ", chunk: (" + chunkX + "," + finalSectionY + "," + chunkZ + "); description: " + description); + }).orElse(null); + ++ final long coordinateKey = CoordinateUtils.getChunkSectionKey(chunkX, sectionY, chunkZ); ++ final PoiSection deserialized = packed == null ? null : packed.unpack(() -> { ++ poiManager.setDirty(coordinateKey); ++ }); ++ + if (deserialized == null || ((ChunkSystemPoiSection)deserialized).moonrise$isEmpty()) { + // completely empty, no point in storing this + continue; @@ -4736,19 +5500,15 @@ index 0000000000000000000000000000000000000000..fd35e4db0c8fec8f86b8743bcc2b15ed +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/storage/ChunkSystemSectionStorage.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/storage/ChunkSystemSectionStorage.java new file mode 100644 -index 0000000000000000000000000000000000000000..fb87d7ece6ebccfd0ffd2f1a609b45a0d2461d9e +index 0000000000000000000000000000000000000000..524752744e37a2db0e3ea089468bdf497129bfef --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/storage/ChunkSystemSectionStorage.java -@@ -0,0 +1,17 @@ +@@ -0,0 +1,13 @@ +package ca.spottedleaf.moonrise.patches.chunk_system.level.storage; + -+import com.mojang.serialization.Dynamic; +import net.minecraft.nbt.CompoundTag; -+import net.minecraft.nbt.Tag; +import net.minecraft.world.level.chunk.storage.RegionFileStorage; +import java.io.IOException; -+import java.util.Optional; -+import java.util.concurrent.CompletableFuture; + +public interface ChunkSystemSectionStorage { + @@ -4780,14 +5540,15 @@ index 0000000000000000000000000000000000000000..003a857e70ead858e8437e3c1bfaf22f +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java new file mode 100644 -index 0000000000000000000000000000000000000000..852d75a73dae7448cbe1e2f5e164b235efa8a969 +index 0000000000000000000000000000000000000000..b2fa9883aefb07f64bb5db7e0052218d2ad09aba --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java -@@ -0,0 +1,1082 @@ +@@ -0,0 +1,1085 @@ +package ca.spottedleaf.moonrise.patches.chunk_system.player; + -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; +import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; ++import ca.spottedleaf.concurrentutil.util.Priority; ++import ca.spottedleaf.moonrise.common.PlatformHooks; +import ca.spottedleaf.moonrise.common.misc.AllocatingRateLimiter; +import ca.spottedleaf.moonrise.common.misc.SingleUserAreaMap; +import ca.spottedleaf.moonrise.common.util.CoordinateUtils; @@ -5198,7 +5959,11 @@ index 0000000000000000000000000000000000000000..852d75a73dae7448cbe1e2f5e164b235 + if (this.sentChunks.add(CoordinateUtils.getChunkKey(chunkX, chunkZ))) { + ((ChunkSystemChunkHolder)((ChunkSystemServerLevel)this.world).moonrise$getChunkTaskScheduler().chunkHolderManager + .getChunkHolder(chunkX, chunkZ).vanillaChunkHolder).moonrise$addReceivedChunk(this.player); -+ PlayerChunkSender.sendChunk(this.player.connection, this.world, ((ChunkSystemLevel)this.world).moonrise$getFullChunkIfLoaded(chunkX, chunkZ)); ++ ++ final LevelChunk chunk = ((ChunkSystemLevel)this.world).moonrise$getFullChunkIfLoaded(chunkX, chunkZ); ++ ++ PlatformHooks.get().onChunkWatch(this.world, chunk, this.player); ++ PlayerChunkSender.sendChunk(this.player.connection, this.world, chunk); + return; + } + throw new IllegalStateException(); @@ -5212,17 +5977,12 @@ index 0000000000000000000000000000000000000000..852d75a73dae7448cbe1e2f5e164b235 + } + + private void sendUnloadChunkRaw(final int chunkX, final int chunkZ) { ++ PlatformHooks.get().onChunkUnWatch(this.world, new ChunkPos(chunkX, chunkZ), this.player); + // Note: Check PlayerChunkSender#dropChunk for other logic + // Note: drop isAlive() check so that chunks properly unload client-side when the player dies + ((ChunkSystemChunkHolder)((ChunkSystemServerLevel)this.world).moonrise$getChunkTaskScheduler().chunkHolderManager + .getChunkHolder(chunkX, chunkZ).vanillaChunkHolder).moonrise$removeReceivedChunk(this.player); -+ final ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ); -+ this.player.connection.send(new ClientboundForgetLevelChunkPacket(chunkPos)); -+ // Paper start - PlayerChunkUnloadEvent -+ if (io.papermc.paper.event.packet.PlayerChunkUnloadEvent.getHandlerList().getRegisteredListeners().length > 0) { -+ new io.papermc.paper.event.packet.PlayerChunkUnloadEvent(this.world.getWorld().getChunkAt(chunkPos.longKey), this.player.getBukkitEntity()).callEvent(); -+ } -+ // Paper end - PlayerChunkUnloadEvent ++ this.player.connection.send(new ClientboundForgetLevelChunkPacket(new ChunkPos(chunkX, chunkZ))); + } + + private final SingleUserAreaMap broadcastMap = new SingleUserAreaMap<>(this) { @@ -5315,7 +6075,7 @@ index 0000000000000000000000000000000000000000..852d75a73dae7448cbe1e2f5e164b235 + final int playerSendViewDistance, final int worldSendViewDistance) { + return Math.min( + loadViewDistance - 1, -+ playerSendViewDistance < 0 ? (!io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.autoConfigSendDistance || clientViewDistance < 0 ? (worldSendViewDistance < 0 ? (loadViewDistance - 1) : worldSendViewDistance) : clientViewDistance + 1) : playerSendViewDistance ++ playerSendViewDistance < 0 ? (!PlatformHooks.get().configAutoConfigSendDistance() || clientViewDistance < 0 ? (worldSendViewDistance < 0 ? (loadViewDistance - 1) : worldSendViewDistance) : clientViewDistance + 1) : playerSendViewDistance + ); + } + @@ -5340,26 +6100,26 @@ index 0000000000000000000000000000000000000000..852d75a73dae7448cbe1e2f5e164b235 + } + + private double getMaxChunkLoadRate() { -+ final double configRate = io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkLoadRate; ++ final double configRate = PlatformHooks.get().configPlayerMaxLoadRate(); + + return configRate <= 0.0 || configRate > (double)MAX_RATE ? (double)MAX_RATE : Math.max(1.0, configRate); + } + + private double getMaxChunkGenRate() { -+ final double configRate = io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkGenerateRate; ++ final double configRate = PlatformHooks.get().configPlayerMaxGenRate(); + + return configRate <= 0.0 || configRate > (double)MAX_RATE ? (double)MAX_RATE : Math.max(1.0, configRate); + } + + private double getMaxChunkSendRate() { -+ final double configRate = io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkSendRate; ++ final double configRate = PlatformHooks.get().configPlayerMaxSendRate(); + + return configRate <= 0.0 || configRate > (double)MAX_RATE ? (double)MAX_RATE : Math.max(1.0, configRate); + } + + private long getMaxChunkLoads() { + final long radiusChunks = (2L * this.lastLoadDistance + 1L) * (2L * this.lastLoadDistance + 1L); -+ long configLimit = io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkLoads; ++ long configLimit = (long)PlatformHooks.get().configPlayerMaxConcurrentLoads(); + if (configLimit == 0L) { + // by default, only allow 1/5th of the chunks in the view distance to be concurrently active + configLimit = Math.max(5L, radiusChunks / 5L); @@ -5373,7 +6133,7 @@ index 0000000000000000000000000000000000000000..852d75a73dae7448cbe1e2f5e164b235 + + private long getMaxChunkGenerates() { + final long radiusChunks = (2L * this.lastLoadDistance + 1L) * (2L * this.lastLoadDistance + 1L); -+ long configLimit = io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkGenerates; ++ long configLimit = (long)PlatformHooks.get().configPlayerMaxConcurrentGens(); + if (configLimit == 0L) { + // by default, only allow 1/5th of the chunks in the view distance to be concurrently active + configLimit = Math.max(5L, radiusChunks / 5L); @@ -5495,7 +6255,7 @@ index 0000000000000000000000000000000000000000..852d75a73dae7448cbe1e2f5e164b235 + final int queuedChunkX = CoordinateUtils.getChunkX(queuedLoadChunk); + final int queuedChunkZ = CoordinateUtils.getChunkZ(queuedLoadChunk); + ((ChunkSystemServerLevel)this.world).moonrise$getChunkTaskScheduler().scheduleChunkLoad( -+ queuedChunkX, queuedChunkZ, ChunkStatus.EMPTY, false, PrioritisedExecutor.Priority.NORMAL, null ++ queuedChunkX, queuedChunkZ, ChunkStatus.EMPTY, false, Priority.NORMAL, null + ); + if (this.removed) { + return; @@ -5611,7 +6371,7 @@ index 0000000000000000000000000000000000000000..852d75a73dae7448cbe1e2f5e164b235 + } + if (!((ChunkSystemLevelChunk)chunk).moonrise$isPostProcessingDone()) { + // not yet post-processed, need to do this so that tile entities can properly be sent to clients -+ chunk.postProcessGeneration(); ++ chunk.postProcessGeneration(this.world); + // check if there was any recursive action + if (this.removed || this.sendQueue.isEmpty() || this.sendQueue.firstLong() != pendingSend) { + return; @@ -5650,7 +6410,6 @@ index 0000000000000000000000000000000000000000..852d75a73dae7448cbe1e2f5e164b235 + final int clientViewDistance = getClientViewDistance(this.player); + final int sendViewDistance = getSendViewDistance(loadViewDistance, clientViewDistance, playerDistances.sendViewDistance, worldDistances.sendViewDistance); + -+ // TODO check PlayerList diff in paper chunk system patch + // send view distances + this.player.connection.send(this.updateClientChunkRadius(sendViewDistance)); + this.player.connection.send(this.updateClientSimulationDistance(tickViewDistance)); @@ -5864,6 +6623,10 @@ index 0000000000000000000000000000000000000000..852d75a73dae7448cbe1e2f5e164b235 + + // now all tickets should be removed, which is all of our external state + } ++ ++ public LongOpenHashSet getSentChunksRaw() { ++ return this.sentChunks; ++ } + } +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/queue/ChunkUnloadQueue.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/queue/ChunkUnloadQueue.java @@ -6019,21 +6782,21 @@ index 0000000000000000000000000000000000000000..7eafc5b7cba23d8dec92ecc1050afe3f \ No newline at end of file diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java new file mode 100644 -index 0000000000000000000000000000000000000000..58d3d1a47e9f2423c467bb329c2d5f4b58a8b5ef +index 0000000000000000000000000000000000000000..f98df65eaed2abedc66f3a49790e0cfb65354ed9 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java -@@ -0,0 +1,1428 @@ +@@ -0,0 +1,1455 @@ +package ca.spottedleaf.moonrise.patches.chunk_system.scheduling; + -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; +import ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock; +import ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable; ++import ca.spottedleaf.concurrentutil.util.Priority; ++import ca.spottedleaf.moonrise.common.PlatformHooks; +import ca.spottedleaf.moonrise.common.util.CoordinateUtils; -+import ca.spottedleaf.moonrise.common.util.MoonriseCommon; +import ca.spottedleaf.moonrise.common.util.TickThread; +import ca.spottedleaf.moonrise.common.util.WorldUtil; +import ca.spottedleaf.moonrise.common.util.ChunkSystem; -+import ca.spottedleaf.moonrise.patches.chunk_system.io.RegionFileIOThread; ++import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO; +import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel; +import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.ChunkEntitySlices; +import ca.spottedleaf.moonrise.patches.chunk_system.level.poi.PoiChunk; @@ -6045,7 +6808,6 @@ index 0000000000000000000000000000000000000000..58d3d1a47e9f2423c467bb329c2d5f4b +import ca.spottedleaf.moonrise.patches.chunk_system.util.ChunkSystemSortedArraySet; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; -+import com.mojang.logging.LogUtils; +import it.unimi.dsi.fastutil.longs.Long2ByteLinkedOpenHashMap; +import it.unimi.dsi.fastutil.longs.Long2ByteMap; +import it.unimi.dsi.fastutil.longs.Long2IntMap; @@ -6065,7 +6827,9 @@ index 0000000000000000000000000000000000000000..58d3d1a47e9f2423c467bb329c2d5f4b +import net.minecraft.util.SortedArraySet; +import net.minecraft.util.Unit; +import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.chunk.LevelChunk; +import org.slf4j.Logger; ++import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.text.DecimalFormat; +import java.util.ArrayDeque; @@ -6083,7 +6847,7 @@ index 0000000000000000000000000000000000000000..58d3d1a47e9f2423c467bb329c2d5f4b + +public final class ChunkHolderManager { + -+ private static final Logger LOGGER = LogUtils.getClassLogger(); ++ private static final Logger LOGGER = LoggerFactory.getLogger(ChunkHolderManager.class); + + public static final int FULL_LOADED_TICKET_LEVEL = ChunkLevel.FULL_CHUNK_LEVEL; + public static final int BLOCK_TICKING_TICKET_LEVEL = ChunkLevel.BLOCK_TICKING_LEVEL; @@ -6214,7 +6978,7 @@ index 0000000000000000000000000000000000000000..58d3d1a47e9f2423c467bb329c2d5f4b + if (halt) { + LOGGER.info("Waiting 60s for chunk system to halt for world '" + WorldUtil.getWorldName(this.world) + "'"); + if (!this.taskScheduler.halt(true, TimeUnit.SECONDS.toNanos(60L))) { -+ LOGGER.warn("Failed to halt world generation/loading tasks for world '" + WorldUtil.getWorldName(this.world) + "'"); ++ LOGGER.warn("Failed to halt generation/loading tasks for world '" + WorldUtil.getWorldName(this.world) + "'"); + } else { + LOGGER.info("Halted chunk system for world '" + WorldUtil.getWorldName(this.world) + "'"); + } @@ -6224,21 +6988,21 @@ index 0000000000000000000000000000000000000000..58d3d1a47e9f2423c467bb329c2d5f4b + this.saveAllChunks(true, true, true); + } + -+ boolean hasTasks = false; -+ for (final RegionFileIOThread.RegionFileType type : RegionFileIOThread.RegionFileType.values()) { -+ if (RegionFileIOThread.getControllerFor(this.world, type).hasTasks()) { -+ hasTasks = true; -+ break; ++ MoonriseRegionFileIO.flush(this.world); ++ ++ if (halt) { ++ LOGGER.info("Waiting 60s for chunk I/O to halt for world '" + WorldUtil.getWorldName(this.world) + "'"); ++ if (!this.taskScheduler.haltIO(true, TimeUnit.SECONDS.toNanos(60L))) { ++ LOGGER.warn("Failed to halt I/O tasks for world '" + WorldUtil.getWorldName(this.world) + "'"); ++ } else { ++ LOGGER.info("Halted I/O scheduler for world '" + WorldUtil.getWorldName(this.world) + "'"); + } + } -+ if (hasTasks) { -+ RegionFileIOThread.flush(); -+ } + + // kill regionfile cache -+ for (final RegionFileIOThread.RegionFileType type : RegionFileIOThread.RegionFileType.values()) { ++ for (final MoonriseRegionFileIO.RegionFileType type : MoonriseRegionFileIO.RegionFileType.values()) { + try { -+ RegionFileIOThread.getControllerFor(this.world, type).getCache().close(); ++ MoonriseRegionFileIO.getControllerFor(this.world, type).getCache().close(); + } catch (final IOException ex) { + LOGGER.error("Failed to close '" + type.name() + "' regionfile cache for world '" + WorldUtil.getWorldName(this.world) + "'", ex); + } @@ -6255,8 +7019,8 @@ index 0000000000000000000000000000000000000000..58d3d1a47e9f2423c467bb329c2d5f4b + public void autoSave() { + final List reschedule = new ArrayList<>(); + final long currentTick = this.currentTick; -+ final long maxSaveTime = currentTick - Math.max(1L, this.world.paperConfig().chunks.autoSaveInterval.value()); -+ final int maxToSave = this.world.paperConfig().chunks.maxAutoSaveChunksPerTick; ++ final long maxSaveTime = currentTick - Math.max(1L, PlatformHooks.get().configAutoSaveInterval()); ++ final int maxToSave = PlatformHooks.get().configMaxAutoSavePerTick(); + for (int autoSaved = 0; autoSaved < maxToSave && !this.autoSaveQueue.isEmpty();) { + final NewChunkHolder holder = this.autoSaveQueue.first(); + @@ -6296,55 +7060,74 @@ index 0000000000000000000000000000000000000000..58d3d1a47e9f2423c467bb329c2d5f4b + + long start = System.nanoTime(); + long lastLog = start; -+ boolean needsFlush = false; -+ final int flushInterval = 50; ++ final int flushInterval = 200; ++ int lastFlush = 0; + + int savedChunk = 0; + int savedEntity = 0; + int savedPoi = 0; + ++ if (shutdown) { ++ // Normal unload process does not occur during shutdown: fire event manually ++ // for mods that expect ChunkEvent.Unload to fire on shutdown (before LevelEvent.Unload) ++ for (int i = 0, len = holders.size(); i < len; ++i) { ++ final NewChunkHolder holder = holders.get(i); ++ if (holder.getCurrentChunk() instanceof LevelChunk levelChunk) { ++ PlatformHooks.get().chunkUnloadFromWorld(levelChunk); ++ } ++ } ++ } + for (int i = 0, len = holders.size(); i < len; ++i) { + final NewChunkHolder holder = holders.get(i); + try { + final NewChunkHolder.SaveStat saveStat = holder.save(shutdown); + if (saveStat != null) { -+ ++saved; -+ needsFlush = flush; + if (saveStat.savedChunk()) { + ++savedChunk; ++ ++saved; + } + if (saveStat.savedEntityChunk()) { + ++savedEntity; ++ ++saved; + } + if (saveStat.savedPoiChunk()) { + ++savedPoi; ++ ++saved; + } + } + } catch (final Throwable thr) { + LOGGER.error("Failed to save chunk (" + holder.chunkX + "," + holder.chunkZ + ") in world '" + WorldUtil.getWorldName(this.world) + "'", thr); + } -+ if (needsFlush && (saved % flushInterval) == 0) { -+ needsFlush = false; -+ RegionFileIOThread.partialFlush(flushInterval / 2); ++ if (flush && (saved - lastFlush) > (flushInterval / 2)) { ++ lastFlush = saved; ++ MoonriseRegionFileIO.partialFlush(this.world, flushInterval / 2); + } + if (logProgress) { + final long currTime = System.nanoTime(); + if ((currTime - lastLog) > TimeUnit.SECONDS.toNanos(10L)) { + lastLog = currTime; -+ LOGGER.info("Saved " + saved + " chunks (" + format.format((double)(i+1)/(double)len * 100.0) + "%) in world '" + WorldUtil.getWorldName(this.world) + "'"); ++ LOGGER.info( ++ "Saved " + savedChunk + " block chunks, " + savedEntity + " entity chunks, " + savedPoi ++ + " poi chunks in world '" + WorldUtil.getWorldName(this.world) + "', progress: " ++ + format.format((double)(i+1)/(double)len * 100.0) ++ ); + } + } + } + if (flush) { -+ RegionFileIOThread.flush(); ++ MoonriseRegionFileIO.flush(this.world); + try { -+ RegionFileIOThread.flushRegionStorages(this.world); ++ MoonriseRegionFileIO.flushRegionStorages(this.world); + } catch (final IOException ex) { + LOGGER.error("Exception when flushing regions in world '" + WorldUtil.getWorldName(this.world) + "'", ex); + } + } + if (logProgress) { -+ LOGGER.info("Saved " + savedChunk + " block chunks, " + savedEntity + " entity chunks, " + savedPoi + " poi chunks in world '" + WorldUtil.getWorldName(this.world) + "' in " + format.format(1.0E-9 * (System.nanoTime() - start)) + "s"); ++ LOGGER.info( ++ "Saved " + savedChunk + " block chunks, " + savedEntity + " entity chunks, " + savedPoi ++ + " poi chunks in world '" + WorldUtil.getWorldName(this.world) + "' in " ++ + format.format(1.0E-9 * (System.nanoTime() - start)) + "s" ++ ); + } + } + @@ -6823,21 +7606,21 @@ index 0000000000000000000000000000000000000000..58d3d1a47e9f2423c467bb329c2d5f4b + return this.chunkHolders.get(position); + } + -+ public void raisePriority(final int x, final int z, final PrioritisedExecutor.Priority priority) { ++ public void raisePriority(final int x, final int z, final Priority priority) { + final NewChunkHolder chunkHolder = this.getChunkHolder(x, z); + if (chunkHolder != null) { + chunkHolder.raisePriority(priority); + } + } + -+ public void setPriority(final int x, final int z, final PrioritisedExecutor.Priority priority) { ++ public void setPriority(final int x, final int z, final Priority priority) { + final NewChunkHolder chunkHolder = this.getChunkHolder(x, z); + if (chunkHolder != null) { + chunkHolder.setPriority(priority); + } + } + -+ public void lowerPriority(final int x, final int z, final PrioritisedExecutor.Priority priority) { ++ public void lowerPriority(final int x, final int z, final Priority priority) { + final NewChunkHolder chunkHolder = this.getChunkHolder(x, z); + if (chunkHolder != null) { + chunkHolder.lowerPriority(priority); @@ -6920,7 +7703,7 @@ index 0000000000000000000000000000000000000000..58d3d1a47e9f2423c467bb329c2d5f4b + final ChunkLoadTask.EntityDataLoadTask entityLoad = current.getEntityDataLoadTask(); + + if (entityLoad != null) { -+ entityLoad.raisePriority(PrioritisedExecutor.Priority.BLOCKING); ++ entityLoad.raisePriority(Priority.BLOCKING); + } + } + } @@ -6996,7 +7779,7 @@ index 0000000000000000000000000000000000000000..58d3d1a47e9f2423c467bb329c2d5f4b + final ChunkLoadTask.PoiDataLoadTask poiLoad = current.getPoiDataLoadTask(); + + if (poiLoad != null) { -+ poiLoad.raisePriority(PrioritisedExecutor.Priority.BLOCKING); ++ poiLoad.raisePriority(Priority.BLOCKING); + } + } + } finally { @@ -7043,7 +7826,7 @@ index 0000000000000000000000000000000000000000..58d3d1a47e9f2423c467bb329c2d5f4b + } + + ChunkHolderManager.this.processPendingFullUpdate(); -+ }, PrioritisedExecutor.Priority.HIGHEST); ++ }, Priority.HIGHEST); + } else { + final ArrayDeque pendingFullLoadUpdate = this.pendingFullLoadUpdate; + for (int i = 0, len = changedFullStatus.size(); i < len; ++i) { @@ -7053,11 +7836,10 @@ index 0000000000000000000000000000000000000000..58d3d1a47e9f2423c467bb329c2d5f4b + } + + private void removeChunkHolder(final NewChunkHolder holder) { -+ holder.markUnloaded(); ++ holder.onUnload(); + this.autoSaveQueue.remove(holder); + ChunkSystem.onChunkHolderDelete(this.world, holder.vanillaChunkHolder); + this.chunkHolders.remove(CoordinateUtils.getChunkKey(holder.chunkX, holder.chunkZ)); -+ + } + + // note: never call while inside the chunk system, this will absolutely break everything @@ -7338,6 +8120,9 @@ index 0000000000000000000000000000000000000000..58d3d1a47e9f2423c467bb329c2d5f4b + if (BLOCK_TICKET_UPDATES.get() == Boolean.TRUE) { + throw new IllegalStateException("Cannot update ticket level while unloading chunks or updating entity manager"); + } ++ if (!PlatformHooks.get().allowAsyncTicketUpdates() && !TickThread.isTickThread()) { ++ TickThread.ensureTickThread("Cannot asynchronously process ticket updates"); ++ } + + List changedFullStatus = null; + @@ -7353,10 +8138,15 @@ index 0000000000000000000000000000000000000000..58d3d1a47e9f2423c467bb329c2d5f4b + } + changedFullStatus = new ArrayList<>(); + -+ ret |= this.ticketLevelPropagator.performUpdates( -+ this.ticketLockArea, this.taskScheduler.schedulingLockArea, -+ scheduledTasks, changedFullStatus -+ ); ++ this.blockTicketUpdates(); ++ try { ++ ret |= this.ticketLevelPropagator.performUpdates( ++ this.ticketLockArea, this.taskScheduler.schedulingLockArea, ++ scheduledTasks, changedFullStatus ++ ); ++ } finally { ++ this.unblockTicketUpdates(Boolean.FALSE); ++ } + } + + if (changedFullStatus != null) { @@ -7453,23 +8243,24 @@ index 0000000000000000000000000000000000000000..58d3d1a47e9f2423c467bb329c2d5f4b +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java new file mode 100644 -index 0000000000000000000000000000000000000000..8671a90e969d16c7a57ddc38fedb7cf01815f64c +index 0000000000000000000000000000000000000000..120ce31729dc8d4bba0901ca06d3212f3158d089 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java -@@ -0,0 +1,1041 @@ +@@ -0,0 +1,1037 @@ +package ca.spottedleaf.moonrise.patches.chunk_system.scheduling; + -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadPool; -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadedTaskQueue; ++import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor; ++import ca.spottedleaf.concurrentutil.executor.queue.PrioritisedTaskQueue; ++import ca.spottedleaf.concurrentutil.executor.thread.PrioritisedThreadPool; +import ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock; +import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; ++import ca.spottedleaf.concurrentutil.util.Priority; ++import ca.spottedleaf.moonrise.common.config.moonrise.MoonriseConfig; +import ca.spottedleaf.moonrise.common.util.CoordinateUtils; +import ca.spottedleaf.moonrise.common.util.JsonUtil; +import ca.spottedleaf.moonrise.common.util.MoonriseCommon; +import ca.spottedleaf.moonrise.common.util.TickThread; +import ca.spottedleaf.moonrise.common.util.WorldUtil; -+import ca.spottedleaf.moonrise.patches.chunk_system.io.RegionFileIOThread; +import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel; +import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemChunkStatus; +import ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer; @@ -7482,7 +8273,6 @@ index 0000000000000000000000000000000000000000..8671a90e969d16c7a57ddc38fedb7cf0 +import ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer; +import ca.spottedleaf.moonrise.patches.chunk_system.status.ChunkSystemChunkStep; +import ca.spottedleaf.moonrise.patches.chunk_system.util.ParallelSearchRadiusIteration; -+import com.mojang.logging.LogUtils; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import net.minecraft.CrashReport; @@ -7507,6 +8297,7 @@ index 0000000000000000000000000000000000000000..8671a90e969d16c7a57ddc38fedb7cf0 +import net.minecraft.world.level.chunk.status.ChunkStep; +import net.minecraft.world.phys.Vec3; +import org.slf4j.Logger; ++import org.slf4j.LoggerFactory; +import java.io.File; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; @@ -7522,51 +8313,14 @@ index 0000000000000000000000000000000000000000..8671a90e969d16c7a57ddc38fedb7cf0 + +public final class ChunkTaskScheduler { + -+ private static final Logger LOGGER = LogUtils.getClassLogger(); ++ private static final Logger LOGGER = LoggerFactory.getLogger(ChunkTaskScheduler.class); + -+ static int newChunkSystemIOThreads; -+ static int newChunkSystemGenParallelism; -+ static int newChunkSystemGenPopulationParallelism; -+ static int newChunkSystemLoadParallelism; -+ -+ private static boolean initialised = false; -+ -+ public static void init(io.papermc.paper.configuration.GlobalConfiguration.ChunkSystem chunkSystem) { -+ if (initialised) { -+ return; -+ } -+ initialised = true; -+ MoonriseCommon.init(chunkSystem); // Paper -+ newChunkSystemIOThreads = chunkSystem.ioThreads; -+ if (newChunkSystemIOThreads <= 0) { -+ newChunkSystemIOThreads = 1; -+ } else { -+ newChunkSystemIOThreads = Math.max(1, newChunkSystemIOThreads); ++ public static void init(final boolean useParallelGen) { ++ for (final PrioritisedThreadPool.ExecutorGroup.ThreadPoolExecutor executor : MoonriseCommon.RADIUS_AWARE_GROUP.getAllExecutors()) { ++ executor.setMaxParallelism(useParallelGen ? -1 : 1); + } + -+ String newChunkSystemGenParallelism = chunkSystem.genParallelism; -+ if (newChunkSystemGenParallelism.equalsIgnoreCase("default")) { -+ newChunkSystemGenParallelism = "true"; -+ } -+ -+ boolean useParallelGen; -+ if (newChunkSystemGenParallelism.equalsIgnoreCase("on") || newChunkSystemGenParallelism.equalsIgnoreCase("enabled") -+ || newChunkSystemGenParallelism.equalsIgnoreCase("true")) { -+ useParallelGen = true; -+ } else if (newChunkSystemGenParallelism.equalsIgnoreCase("off") || newChunkSystemGenParallelism.equalsIgnoreCase("disabled") -+ || newChunkSystemGenParallelism.equalsIgnoreCase("false")) { -+ useParallelGen = false; -+ } else { -+ throw new IllegalStateException("Invalid option for gen-parallelism: must be one of [on, off, enabled, disabled, true, false, default]"); -+ } -+ -+ ChunkTaskScheduler.newChunkSystemGenParallelism = MoonriseCommon.WORKER_THREADS; -+ ChunkTaskScheduler.newChunkSystemGenPopulationParallelism = useParallelGen ? MoonriseCommon.WORKER_THREADS : 1; -+ ChunkTaskScheduler.newChunkSystemLoadParallelism = MoonriseCommon.WORKER_THREADS; -+ -+ RegionFileIOThread.init(newChunkSystemIOThreads); -+ -+ LOGGER.info("Chunk system is using " + newChunkSystemIOThreads + " I/O threads, " + MoonriseCommon.WORKER_THREADS + " worker threads, and population gen parallelism of " + ChunkTaskScheduler.newChunkSystemGenPopulationParallelism + " threads"); ++ LOGGER.info("Chunk system is using population gen parallelism: " + useParallelGen); + } + + public static final TicketType CHUNK_LOAD = TicketType.create("chunk_system:chunk_load", Long::compareTo); @@ -7610,13 +8364,15 @@ index 0000000000000000000000000000000000000000..8671a90e969d16c7a57ddc38fedb7cf0 + } + + public final ServerLevel world; -+ public final PrioritisedThreadPool workers; + public final RadiusAwarePrioritisedExecutor radiusAwareScheduler; -+ public final PrioritisedThreadPool.PrioritisedPoolExecutor parallelGenExecutor; -+ private final PrioritisedThreadPool.PrioritisedPoolExecutor radiusAwareGenExecutor; -+ public final PrioritisedThreadPool.PrioritisedPoolExecutor loadExecutor; ++ public final PrioritisedThreadPool.ExecutorGroup.ThreadPoolExecutor parallelGenExecutor; ++ private final PrioritisedThreadPool.ExecutorGroup.ThreadPoolExecutor radiusAwareGenExecutor; ++ public final PrioritisedThreadPool.ExecutorGroup.ThreadPoolExecutor loadExecutor; ++ public final PrioritisedThreadPool.ExecutorGroup.ThreadPoolExecutor ioExecutor; ++ public final PrioritisedThreadPool.ExecutorGroup.ThreadPoolExecutor compressionExecutor; ++ public final PrioritisedThreadPool.ExecutorGroup.ThreadPoolExecutor saveExecutor; + -+ private final PrioritisedThreadedTaskQueue mainThreadExecutor = new PrioritisedThreadedTaskQueue(); ++ private final PrioritisedTaskQueue mainThreadExecutor = new PrioritisedTaskQueue(); + + public final ChunkHolderManager chunkHolderManager; + @@ -7765,9 +8521,8 @@ index 0000000000000000000000000000000000000000..8671a90e969d16c7a57ddc38fedb7cf0 + return this.lockShift; + } + -+ public ChunkTaskScheduler(final ServerLevel world, final PrioritisedThreadPool workers) { ++ public ChunkTaskScheduler(final ServerLevel world) { + this.world = world; -+ this.workers = workers; + // must be >= region shift (in paper, doesn't exist) and must be >= ticket propagator section shift + // it must be >= region shift since the regioniser assumes ticket updates do not occur in parallel for the region sections + // it must be >= ticket propagator section shift so that the ticket propagator can assume that owning a position implies owning @@ -7776,11 +8531,14 @@ index 0000000000000000000000000000000000000000..8671a90e969d16c7a57ddc38fedb7cf0 + this.lockShift = Math.max(((ChunkSystemServerLevel)world).moonrise$getRegionChunkShift(), ThreadedTicketLevelPropagator.SECTION_SHIFT); + this.schedulingLockArea = new ReentrantAreaLock(this.getChunkSystemLockShift()); + -+ final String worldName = WorldUtil.getWorldName(world); -+ this.parallelGenExecutor = workers.createExecutor("Chunk parallel generation executor for world '" + worldName + "'", 1, Math.max(1, newChunkSystemGenParallelism)); -+ this.radiusAwareGenExecutor = workers.createExecutor("Chunk radius aware generator for world '" + worldName + "'", 1, Math.max(1, newChunkSystemGenPopulationParallelism)); -+ this.loadExecutor = workers.createExecutor("Chunk load executor for world '" + worldName + "'", 1, newChunkSystemLoadParallelism); -+ this.radiusAwareScheduler = new RadiusAwarePrioritisedExecutor(this.radiusAwareGenExecutor, Math.max(2, 1 + newChunkSystemGenPopulationParallelism)); ++ this.parallelGenExecutor = MoonriseCommon.PARALLEL_GEN_GROUP.createExecutor(-1, MoonriseCommon.WORKER_QUEUE_HOLD_TIME, 0); ++ this.radiusAwareGenExecutor = MoonriseCommon.RADIUS_AWARE_GROUP.createExecutor(1, MoonriseCommon.WORKER_QUEUE_HOLD_TIME, 0); ++ this.loadExecutor = MoonriseCommon.LOAD_GROUP.createExecutor(-1, MoonriseCommon.WORKER_QUEUE_HOLD_TIME, 0); ++ this.radiusAwareScheduler = new RadiusAwarePrioritisedExecutor(this.radiusAwareGenExecutor, 16); ++ this.ioExecutor = MoonriseCommon.SERVER_REGION_IO_GROUP.createExecutor(-1, MoonriseCommon.IO_QUEUE_HOLD_TIME, 0); ++ // we need a separate executor here so that on shutdown we can continue to process I/O tasks ++ this.compressionExecutor = MoonriseCommon.LOAD_GROUP.createExecutor(-1, MoonriseCommon.WORKER_QUEUE_HOLD_TIME, 0); ++ this.saveExecutor = MoonriseCommon.LOAD_GROUP.createExecutor(-1, MoonriseCommon.WORKER_QUEUE_HOLD_TIME, 0); + this.chunkHolderManager = new ChunkHolderManager(world, this); + } + @@ -7819,7 +8577,7 @@ index 0000000000000000000000000000000000000000..8671a90e969d16c7a57ddc38fedb7cf0 + }; + + // this may not be good enough, specifically thanks to stupid ass plugins swallowing exceptions -+ this.scheduleChunkTask(chunkX, chunkZ, crash, PrioritisedExecutor.Priority.BLOCKING); ++ this.scheduleChunkTask(chunkX, chunkZ, crash, Priority.BLOCKING); + // so, make the main thread pick it up + ((ChunkSystemMinecraftServer)this.world.getServer()).moonrise$setChunkSystemCrash(new RuntimeException("Chunk system crash propagated from unrecoverableChunkSystemFailure", reportedException)); + } @@ -7829,20 +8587,20 @@ index 0000000000000000000000000000000000000000..8671a90e969d16c7a57ddc38fedb7cf0 + return this.mainThreadExecutor.executeTask(); + } + -+ public void raisePriority(final int x, final int z, final PrioritisedExecutor.Priority priority) { ++ public void raisePriority(final int x, final int z, final Priority priority) { + this.chunkHolderManager.raisePriority(x, z, priority); + } + -+ public void setPriority(final int x, final int z, final PrioritisedExecutor.Priority priority) { ++ public void setPriority(final int x, final int z, final Priority priority) { + this.chunkHolderManager.setPriority(x, z, priority); + } + -+ public void lowerPriority(final int x, final int z, final PrioritisedExecutor.Priority priority) { ++ public void lowerPriority(final int x, final int z, final Priority priority) { + this.chunkHolderManager.lowerPriority(x, z, priority); + } + + public void scheduleTickingState(final int chunkX, final int chunkZ, final FullChunkStatus toStatus, -+ final boolean addTicket, final PrioritisedExecutor.Priority priority, ++ final boolean addTicket, final Priority priority, + final Consumer onComplete) { + final int radius = toStatus.ordinal() - 1; // 0 -> BORDER, 1 -> TICKING, 2 -> ENTITY_TICKING + @@ -7938,7 +8696,7 @@ index 0000000000000000000000000000000000000000..8671a90e969d16c7a57ddc38fedb7cf0 + } + + public void scheduleChunkLoad(final int chunkX, final int chunkZ, final boolean gen, final ChunkStatus toStatus, final boolean addTicket, -+ final PrioritisedExecutor.Priority priority, final Consumer onComplete) { ++ final Priority priority, final Consumer onComplete) { + if (gen) { + this.scheduleChunkLoad(chunkX, chunkZ, toStatus, addTicket, priority, onComplete); + return; @@ -7962,7 +8720,7 @@ index 0000000000000000000000000000000000000000..8671a90e969d16c7a57ddc38fedb7cf0 + + // only appropriate to use with syncLoadNonFull + public boolean beginChunkLoadForNonFullSync(final int chunkX, final int chunkZ, final ChunkStatus toStatus, -+ final PrioritisedExecutor.Priority priority) { ++ final Priority priority) { + final int accessRadius = getAccessRadius(toStatus); + final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ); + final int minLevel = ChunkTaskScheduler.getTicketLevel(toStatus); @@ -8007,6 +8765,11 @@ index 0000000000000000000000000000000000000000..8671a90e969d16c7a57ddc38fedb7cf0 + if (status == null || status.isOrAfter(ChunkStatus.FULL)) { + throw new IllegalArgumentException("Status: " + status); + } ++ ++ if (!TickThread.isTickThread()) { ++ return this.world.getChunkSource().getChunk(chunkX, chunkZ, status, true); ++ } ++ + ChunkAccess loaded = ((ChunkSystemServerLevel)this.world).moonrise$getSpecificChunkIfLoaded(chunkX, chunkZ, status); + if (loaded != null) { + return loaded; @@ -8017,7 +8780,7 @@ index 0000000000000000000000000000000000000000..8671a90e969d16c7a57ddc38fedb7cf0 + this.chunkHolderManager.addTicketAtLevel(NON_FULL_CHUNK_LOAD, chunkX, chunkZ, ticketLevel, ticketId); + this.chunkHolderManager.processTicketUpdates(); + -+ this.beginChunkLoadForNonFullSync(chunkX, chunkZ, status, PrioritisedExecutor.Priority.BLOCKING); ++ this.beginChunkLoadForNonFullSync(chunkX, chunkZ, status, Priority.BLOCKING); + + // we could do a simple spinwait here, since we do not need to process tasks while performing this load + // but we process tasks only because it's a better use of the time spent @@ -8037,7 +8800,7 @@ index 0000000000000000000000000000000000000000..8671a90e969d16c7a57ddc38fedb7cf0 + } + + public void scheduleChunkLoad(final int chunkX, final int chunkZ, final ChunkStatus toStatus, final boolean addTicket, -+ final PrioritisedExecutor.Priority priority, final Consumer onComplete) { ++ final Priority priority, final Consumer onComplete) { + if (!TickThread.isTickThreadFor(this.world, chunkX, chunkZ)) { + this.scheduleChunkTask(chunkX, chunkZ, () -> { + ChunkTaskScheduler.this.scheduleChunkLoad(chunkX, chunkZ, toStatus, addTicket, priority, onComplete); @@ -8129,7 +8892,7 @@ index 0000000000000000000000000000000000000000..8671a90e969d16c7a57ddc38fedb7cf0 + + private ChunkProgressionTask createTask(final int chunkX, final int chunkZ, final ChunkAccess chunk, + final NewChunkHolder chunkHolder, final StaticCache2D neighbours, -+ final ChunkStatus toStatus, final PrioritisedExecutor.Priority initialPriority) { ++ final ChunkStatus toStatus, final Priority initialPriority) { + if (toStatus == ChunkStatus.EMPTY) { + return new ChunkLoadTask(this, this.world, chunkX, chunkZ, chunkHolder, initialPriority); + } @@ -8145,7 +8908,7 @@ index 0000000000000000000000000000000000000000..8671a90e969d16c7a57ddc38fedb7cf0 + + ChunkProgressionTask schedule(final int chunkX, final int chunkZ, final ChunkStatus targetStatus, final NewChunkHolder chunkHolder, + final List allTasks) { -+ return this.schedule(chunkX, chunkZ, targetStatus, chunkHolder, allTasks, chunkHolder.getEffectivePriority(PrioritisedExecutor.Priority.NORMAL)); ++ return this.schedule(chunkX, chunkZ, targetStatus, chunkHolder, allTasks, chunkHolder.getEffectivePriority(Priority.NORMAL)); + } + + // rets new task scheduled for the _specified_ chunk @@ -8154,7 +8917,7 @@ index 0000000000000000000000000000000000000000..8671a90e969d16c7a57ddc38fedb7cf0 + // schedule will ignore the generation target, so it should be checked by the caller to ensure the target is not regressed! + private ChunkProgressionTask schedule(final int chunkX, final int chunkZ, final ChunkStatus targetStatus, + final NewChunkHolder chunkHolder, final List allTasks, -+ final PrioritisedExecutor.Priority minPriority) { ++ final Priority minPriority) { + if (!this.schedulingLockArea.isHeldByCurrentThread(chunkX, chunkZ, getAccessRadius(targetStatus))) { + throw new IllegalStateException("Not holding scheduling lock"); + } @@ -8164,8 +8927,8 @@ index 0000000000000000000000000000000000000000..8671a90e969d16c7a57ddc38fedb7cf0 + return null; + } + -+ final PrioritisedExecutor.Priority requestedPriority = PrioritisedExecutor.Priority.max( -+ minPriority, chunkHolder.getEffectivePriority(PrioritisedExecutor.Priority.NORMAL) ++ final Priority requestedPriority = Priority.max( ++ minPriority, chunkHolder.getEffectivePriority(Priority.NORMAL) + ); + final ChunkStatus currentGenStatus = chunkHolder.getCurrentGenStatus(); + final ChunkAccess chunk = chunkHolder.getCurrentChunk(); @@ -8202,7 +8965,7 @@ index 0000000000000000000000000000000000000000..8671a90e969d16c7a57ddc38fedb7cf0 + + final int neighbourReadRadius = Math.max( + 0, -+ chunkPyramid.getStepTo(toStatus).getAccumulatedRadiusOf(ChunkStatus.EMPTY) ++ chunkStep.getAccumulatedRadiusOf(ChunkStatus.EMPTY) + ); + + boolean unGeneratedNeighbours = false; @@ -8242,7 +9005,7 @@ index 0000000000000000000000000000000000000000..8671a90e969d16c7a57ddc38fedb7cf0 + + final ChunkProgressionTask task = this.createTask( + chunkX, chunkZ, chunk, chunkHolder, neighbours, toStatus, -+ chunkHolder.getEffectivePriority(PrioritisedExecutor.Priority.NORMAL) ++ chunkHolder.getEffectivePriority(Priority.NORMAL) + ); + allTasks.add(task); + @@ -8253,7 +9016,7 @@ index 0000000000000000000000000000000000000000..8671a90e969d16c7a57ddc38fedb7cf0 + + // rets true if the neighbour is not at the required status, false otherwise + private boolean checkNeighbour(final int chunkX, final int chunkZ, final ChunkStatus requiredStatus, final NewChunkHolder center, -+ final List tasks, final PrioritisedExecutor.Priority minPriority) { ++ final List tasks, final Priority minPriority) { + final NewChunkHolder chunkHolder = this.chunkHolderManager.getChunkHolder(chunkX, chunkZ); + + if (chunkHolder == null) { @@ -8289,41 +9052,41 @@ index 0000000000000000000000000000000000000000..8671a90e969d16c7a57ddc38fedb7cf0 + */ + @Deprecated + public PrioritisedExecutor.PrioritisedTask scheduleChunkTask(final Runnable run) { -+ return this.scheduleChunkTask(run, PrioritisedExecutor.Priority.NORMAL); ++ return this.scheduleChunkTask(run, Priority.NORMAL); + } + + /** + * @deprecated Chunk tasks must be tied to coordinates in the future + */ + @Deprecated -+ public PrioritisedExecutor.PrioritisedTask scheduleChunkTask(final Runnable run, final PrioritisedExecutor.Priority priority) { -+ return this.mainThreadExecutor.queueRunnable(run, priority); ++ public PrioritisedExecutor.PrioritisedTask scheduleChunkTask(final Runnable run, final Priority priority) { ++ return this.mainThreadExecutor.queueTask(run, priority); + } + + public PrioritisedExecutor.PrioritisedTask createChunkTask(final int chunkX, final int chunkZ, final Runnable run) { -+ return this.createChunkTask(chunkX, chunkZ, run, PrioritisedExecutor.Priority.NORMAL); ++ return this.createChunkTask(chunkX, chunkZ, run, Priority.NORMAL); + } + + public PrioritisedExecutor.PrioritisedTask createChunkTask(final int chunkX, final int chunkZ, final Runnable run, -+ final PrioritisedExecutor.Priority priority) { ++ final Priority priority) { + return this.mainThreadExecutor.createTask(run, priority); + } + + public PrioritisedExecutor.PrioritisedTask scheduleChunkTask(final int chunkX, final int chunkZ, final Runnable run) { -+ return this.scheduleChunkTask(chunkX, chunkZ, run, PrioritisedExecutor.Priority.NORMAL); ++ return this.scheduleChunkTask(chunkX, chunkZ, run, Priority.NORMAL); + } + + public PrioritisedExecutor.PrioritisedTask scheduleChunkTask(final int chunkX, final int chunkZ, final Runnable run, -+ final PrioritisedExecutor.Priority priority) { -+ return this.mainThreadExecutor.queueRunnable(run, priority); ++ final Priority priority) { ++ return this.mainThreadExecutor.queueTask(run, priority); + } + + public boolean halt(final boolean sync, final long maxWaitNS) { + this.radiusAwareGenExecutor.halt(); + this.parallelGenExecutor.halt(); + this.loadExecutor.halt(); -+ final long time = System.nanoTime(); + if (sync) { ++ final long time = System.nanoTime(); + for (long failures = 9L;; failures = ConcurrentUtil.linearLongBackoff(failures, 500_000L, 50_000_000L)) { + if ( + !this.radiusAwareGenExecutor.isActive() && @@ -8341,6 +9104,29 @@ index 0000000000000000000000000000000000000000..8671a90e969d16c7a57ddc38fedb7cf0 + return true; + } + ++ public boolean haltIO(final boolean sync, final long maxWaitNS) { ++ this.ioExecutor.halt(); ++ this.saveExecutor.halt(); ++ this.compressionExecutor.halt(); ++ if (sync) { ++ final long time = System.nanoTime(); ++ for (long failures = 9L;; failures = ConcurrentUtil.linearLongBackoff(failures, 500_000L, 50_000_000L)) { ++ if ( ++ !this.ioExecutor.isActive() && ++ !this.saveExecutor.isActive() && ++ !this.compressionExecutor.isActive() ++ ) { ++ return true; ++ } ++ if ((System.nanoTime() - time) >= maxWaitNS) { ++ return false; ++ } ++ } ++ } ++ ++ return true; ++ } ++ + public static final ArrayDeque WAITING_CHUNKS = new ArrayDeque<>(); // stack + + public static final class ChunkInfo { @@ -8500,25 +9286,27 @@ index 0000000000000000000000000000000000000000..8671a90e969d16c7a57ddc38fedb7cf0 +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java new file mode 100644 -index 0000000000000000000000000000000000000000..45eda96fd8a1acb87dbb69ce5495fec7e451416f +index 0000000000000000000000000000000000000000..381631e405895ba3eede1cd2e1011c64aadbd662 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java -@@ -0,0 +1,2035 @@ +@@ -0,0 +1,1998 @@ +package ca.spottedleaf.moonrise.patches.chunk_system.scheduling; + -+import ca.spottedleaf.concurrentutil.completable.Completable; ++import ca.spottedleaf.concurrentutil.completable.CallbackCompletable; +import ca.spottedleaf.concurrentutil.executor.Cancellable; -+import ca.spottedleaf.concurrentutil.executor.standard.DelayedPrioritisedTask; -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; ++import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor; +import ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock; +import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; ++import ca.spottedleaf.concurrentutil.util.Priority; ++import ca.spottedleaf.moonrise.common.PlatformHooks; ++import ca.spottedleaf.moonrise.common.misc.LazyRunnable; +import ca.spottedleaf.moonrise.common.util.CoordinateUtils; +import ca.spottedleaf.moonrise.common.util.TickThread; +import ca.spottedleaf.moonrise.common.util.WorldUtil; +import ca.spottedleaf.moonrise.common.util.ChunkSystem; -+import ca.spottedleaf.moonrise.patches.chunk_system.ChunkSystemFeatures; -+import ca.spottedleaf.moonrise.patches.chunk_system.async_save.AsyncChunkSaveData; -+import ca.spottedleaf.moonrise.patches.chunk_system.io.RegionFileIOThread; ++import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData; ++import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO; ++import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel; +import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel; +import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemChunkHolder; +import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemChunkStatus; @@ -8542,13 +9330,14 @@ index 0000000000000000000000000000000000000000..45eda96fd8a1acb87dbb69ce5495fec7 +import net.minecraft.server.level.ChunkLevel; +import net.minecraft.server.level.FullChunkStatus; +import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.progress.ChunkProgressListener; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ImposterProtoChunk; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.status.ChunkStatus; -+import net.minecraft.world.level.chunk.storage.ChunkSerializer; ++import net.minecraft.world.level.chunk.storage.SerializableChunkData; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.lang.invoke.VarHandle; @@ -8564,6 +9353,8 @@ index 0000000000000000000000000000000000000000..45eda96fd8a1acb87dbb69ce5495fec7 + + private static final Logger LOGGER = LoggerFactory.getLogger(NewChunkHolder.class); + ++ public final ChunkData holderData; ++ + public final ServerLevel world; + public final int chunkX; + public final int chunkZ; @@ -8595,7 +9386,7 @@ index 0000000000000000000000000000000000000000..45eda96fd8a1acb87dbb69ce5495fec7 + if (this.entityChunk == null) { + ret = this.entityChunk = new ChunkEntitySlices( + this.world, this.chunkX, this.chunkZ, this.getChunkStatus(), -+ WorldUtil.getMinSection(this.world), WorldUtil.getMaxSection(this.world) ++ this.holderData, WorldUtil.getMinSection(this.world), WorldUtil.getMaxSection(this.world) + ); + + ret.setTransient(transientChunk); @@ -8679,7 +9470,7 @@ index 0000000000000000000000000000000000000000..45eda96fd8a1acb87dbb69ce5495fec7 + // no tasks to schedule _for_ + } else { + entityDataLoadTask = this.entityDataLoadTask = new ChunkLoadTask.EntityDataLoadTask( -+ this.scheduler, this.world, this.chunkX, this.chunkZ, this.getEffectivePriority(PrioritisedExecutor.Priority.NORMAL) ++ this.scheduler, this.world, this.chunkX, this.chunkZ, this.getEffectivePriority(Priority.NORMAL) + ); + entityDataLoadTask.addCallback(this::completeEntityLoad); + // need one schedule() per waiter @@ -8726,7 +9517,7 @@ index 0000000000000000000000000000000000000000..45eda96fd8a1acb87dbb69ce5495fec7 + + if (this.entityDataLoadTask == null) { + this.entityDataLoadTask = new ChunkLoadTask.EntityDataLoadTask( -+ this.scheduler, this.world, this.chunkX, this.chunkZ, this.getEffectivePriority(PrioritisedExecutor.Priority.NORMAL) ++ this.scheduler, this.world, this.chunkX, this.chunkZ, this.getEffectivePriority(Priority.NORMAL) + ); + this.entityDataLoadTask.addCallback(this::completeEntityLoad); + this.entityDataLoadTaskWaiters = new ArrayList<>(); @@ -8800,7 +9591,7 @@ index 0000000000000000000000000000000000000000..45eda96fd8a1acb87dbb69ce5495fec7 + // no tasks to schedule _for_ + } else { + poiDataLoadTask = this.poiDataLoadTask = new ChunkLoadTask.PoiDataLoadTask( -+ this.scheduler, this.world, this.chunkX, this.chunkZ, this.getEffectivePriority(PrioritisedExecutor.Priority.NORMAL) ++ this.scheduler, this.world, this.chunkX, this.chunkZ, this.getEffectivePriority(Priority.NORMAL) + ); + poiDataLoadTask.addCallback(this::completePoiLoad); + // need one schedule() per waiter @@ -8846,7 +9637,7 @@ index 0000000000000000000000000000000000000000..45eda96fd8a1acb87dbb69ce5495fec7 + + if (this.poiDataLoadTask == null) { + this.poiDataLoadTask = new ChunkLoadTask.PoiDataLoadTask( -+ this.scheduler, this.world, this.chunkX, this.chunkZ, this.getEffectivePriority(PrioritisedExecutor.Priority.NORMAL) ++ this.scheduler, this.world, this.chunkX, this.chunkZ, this.getEffectivePriority(Priority.NORMAL) + ); + this.poiDataLoadTask.addCallback(this::completePoiLoad); + this.poiDataLoadTaskWaiters = new ArrayList<>(); @@ -9025,15 +9816,15 @@ index 0000000000000000000000000000000000000000..45eda96fd8a1acb87dbb69ce5495fec7 + // priority state + + // the target priority for this chunk to generate at -+ private PrioritisedExecutor.Priority priority = null; ++ private Priority priority = null; + private boolean priorityLocked; + + // the priority neighbouring chunks have requested this chunk generate at -+ private PrioritisedExecutor.Priority neighbourRequestedPriority = null; ++ private Priority neighbourRequestedPriority = null; + -+ public PrioritisedExecutor.Priority getEffectivePriority(final PrioritisedExecutor.Priority dfl) { -+ final PrioritisedExecutor.Priority neighbour = this.neighbourRequestedPriority; -+ final PrioritisedExecutor.Priority us = this.priority; ++ public Priority getEffectivePriority(final Priority dfl) { ++ final Priority neighbour = this.neighbourRequestedPriority; ++ final Priority us = this.priority; + + if (neighbour == null) { + return us == null ? dfl : us; @@ -9042,7 +9833,7 @@ index 0000000000000000000000000000000000000000..45eda96fd8a1acb87dbb69ce5495fec7 + return neighbour; + } + -+ return PrioritisedExecutor.Priority.max(us, neighbour); ++ return Priority.max(us, neighbour); + } + + private void recalculateNeighbourRequestedPriority() { @@ -9051,18 +9842,18 @@ index 0000000000000000000000000000000000000000..45eda96fd8a1acb87dbb69ce5495fec7 + return; + } + -+ PrioritisedExecutor.Priority max = null; ++ Priority max = null; + + for (final NewChunkHolder holder : this.neighboursWaitingForUs.keySet()) { -+ final PrioritisedExecutor.Priority neighbourPriority = holder.getEffectivePriority(null); ++ final Priority neighbourPriority = holder.getEffectivePriority(null); + if (neighbourPriority != null && (max == null || neighbourPriority.isHigherPriority(max))) { + max = neighbourPriority; + } + } + -+ final PrioritisedExecutor.Priority current = this.getEffectivePriority(PrioritisedExecutor.Priority.NORMAL); ++ final Priority current = this.getEffectivePriority(Priority.NORMAL); + this.neighbourRequestedPriority = max; -+ final PrioritisedExecutor.Priority next = this.getEffectivePriority(PrioritisedExecutor.Priority.NORMAL); ++ final Priority next = this.getEffectivePriority(Priority.NORMAL); + + if (current == next) { + return; @@ -9084,7 +9875,7 @@ index 0000000000000000000000000000000000000000..45eda96fd8a1acb87dbb69ce5495fec7 + } + + // must hold scheduling lock -+ public void raisePriority(final PrioritisedExecutor.Priority priority) { ++ public void raisePriority(final Priority priority) { + if (this.priority != null && this.priority.isHigherOrEqualPriority(priority)) { + return; + } @@ -9097,13 +9888,13 @@ index 0000000000000000000000000000000000000000..45eda96fd8a1acb87dbb69ce5495fec7 + } + + // must hold scheduling lock -+ public void setPriority(final PrioritisedExecutor.Priority priority) { ++ public void setPriority(final Priority priority) { + if (this.priorityLocked) { + return; + } -+ final PrioritisedExecutor.Priority old = this.getEffectivePriority(null); ++ final Priority old = this.getEffectivePriority(null); + this.priority = priority; -+ final PrioritisedExecutor.Priority newPriority = this.getEffectivePriority(PrioritisedExecutor.Priority.NORMAL); ++ final Priority newPriority = this.getEffectivePriority(Priority.NORMAL); + + if (old != newPriority) { + if (this.generationTask != null) { @@ -9115,7 +9906,7 @@ index 0000000000000000000000000000000000000000..45eda96fd8a1acb87dbb69ce5495fec7 + } + + // must hold scheduling lock -+ public void lowerPriority(final PrioritisedExecutor.Priority priority) { ++ public void lowerPriority(final Priority priority) { + if (this.priority != null && this.priority.isLowerOrEqualPriority(priority)) { + return; + } @@ -9138,7 +9929,7 @@ index 0000000000000000000000000000000000000000..45eda96fd8a1acb87dbb69ce5495fec7 + } + + // ticket level state -+ public int oldTicketLevel = ChunkHolderManager.MAX_TICKET_LEVEL + 1; ++ private int oldTicketLevel = ChunkHolderManager.MAX_TICKET_LEVEL + 1; + private int currentTicketLevel = ChunkHolderManager.MAX_TICKET_LEVEL + 1; + + public int getTicketLevel() { @@ -9157,6 +9948,7 @@ index 0000000000000000000000000000000000000000..45eda96fd8a1acb87dbb69ce5495fec7 + world.getLightEngine(), null, world.getChunkSource().chunkMap + ); + ((ChunkSystemChunkHolder)this.vanillaChunkHolder).moonrise$setRealChunkHolder(this); ++ this.holderData = ((ChunkSystemLevel)this.world).moonrise$requestChunkData(CoordinateUtils.getChunkKey(chunkX, chunkZ)); + } + + public ChunkAccess getCurrentChunk() { @@ -9256,8 +10048,9 @@ index 0000000000000000000000000000000000000000..45eda96fd8a1acb87dbb69ce5495fec7 + /** Unloaded from chunk map */ + private boolean unloaded; + -+ void markUnloaded() { ++ void onUnload() { + this.unloaded = true; ++ ((ChunkSystemLevel)this.world).moonrise$releaseChunkData(CoordinateUtils.getChunkKey(this.chunkX, this.chunkZ)); + } + + private boolean inUnloadQueue = false; @@ -9294,9 +10087,10 @@ index 0000000000000000000000000000000000000000..45eda96fd8a1acb87dbb69ce5495fec7 + private UnloadTask entityDataUnload; + private UnloadTask poiDataUnload; + -+ public static final record UnloadTask(Completable completable, DelayedPrioritisedTask task) {} ++ public static final record UnloadTask(CallbackCompletable completable, PrioritisedExecutor.PrioritisedTask task, ++ LazyRunnable toRun) {} + -+ public UnloadTask getUnloadTask(final RegionFileIOThread.RegionFileType type) { ++ public UnloadTask getUnloadTask(final MoonriseRegionFileIO.RegionFileType type) { + switch (type) { + case CHUNK_DATA: + return this.chunkDataUnload; @@ -9309,7 +10103,7 @@ index 0000000000000000000000000000000000000000..45eda96fd8a1acb87dbb69ce5495fec7 + } + } + -+ private void removeUnloadTask(final RegionFileIOThread.RegionFileType type) { ++ private void removeUnloadTask(final MoonriseRegionFileIO.RegionFileType type) { + switch (type) { + case CHUNK_DATA: { + this.chunkDataUnload = null; @@ -9342,10 +10136,10 @@ index 0000000000000000000000000000000000000000..45eda96fd8a1acb87dbb69ce5495fec7 + // chunk state + this.currentChunk = null; + this.currentGenStatus = null; -+ this.lastChunkCompletion = null; + for (int i = 0; i < this.chunkCompletions.length; ++i) { -+ CHUNK_COMPLETION_ARRAY_HANDLE.setVolatile(this.chunkCompletions, i, (ChunkCompletion)null); ++ CHUNK_COMPLETION_ARRAY_HANDLE.setRelease(this.chunkCompletions, i, (ChunkCompletion)null); + } ++ this.lastChunkCompletion = null; + // entity chunk state + this.entityChunk = null; + this.pendingEntityChunk = null; @@ -9357,22 +10151,23 @@ index 0000000000000000000000000000000000000000..45eda96fd8a1acb87dbb69ce5495fec7 + this.priorityLocked = false; + + if (chunk != null) { -+ this.chunkDataUnload = new UnloadTask(new Completable<>(), new DelayedPrioritisedTask(PrioritisedExecutor.Priority.NORMAL)); ++ final LazyRunnable toRun = new LazyRunnable(); ++ this.chunkDataUnload = new UnloadTask(new CallbackCompletable<>(), this.scheduler.saveExecutor.createTask(toRun), toRun); + } + if (poiChunk != null) { -+ this.poiDataUnload = new UnloadTask(new Completable<>(), null); ++ this.poiDataUnload = new UnloadTask(new CallbackCompletable<>(), null, null); + } + if (entityChunk != null) { -+ this.entityDataUnload = new UnloadTask(new Completable<>(), null); ++ this.entityDataUnload = new UnloadTask(new CallbackCompletable<>(), null, null); + } + + return this.unloadState = (chunk != null || entityChunk != null || poiChunk != null) ? new UnloadState(this, chunk, entityChunk, poiChunk) : null; + } + + // data is null if failed or does not need to be saved -+ void completeAsyncUnloadDataSave(final RegionFileIOThread.RegionFileType type, final CompoundTag data) { ++ void completeAsyncUnloadDataSave(final MoonriseRegionFileIO.RegionFileType type, final CompoundTag data) { + if (data != null) { -+ RegionFileIOThread.scheduleSave(this.world, this.chunkX, this.chunkZ, data, type); ++ MoonriseRegionFileIO.scheduleSave(this.world, this.chunkX, this.chunkZ, data, type); + } + + this.getUnloadTask(type).completable().complete(data); @@ -9392,18 +10187,19 @@ index 0000000000000000000000000000000000000000..45eda96fd8a1acb87dbb69ce5495fec7 + final ChunkEntitySlices entityChunk = state.entityChunk(); + final PoiChunk poiChunk = state.poiChunk(); + -+ final boolean shouldLevelChunkNotSave = ChunkSystemFeatures.forceNoSave(chunk); ++ final boolean shouldLevelChunkNotSave = PlatformHooks.get().forceNoSave(chunk); + + // unload chunk data + if (chunk != null) { + if (chunk instanceof LevelChunk levelChunk) { + levelChunk.setLoaded(false); ++ PlatformHooks.get().chunkUnloadFromWorld(levelChunk); + } + + if (!shouldLevelChunkNotSave) { + this.saveChunk(chunk, true); + } else { -+ this.completeAsyncUnloadDataSave(RegionFileIOThread.RegionFileType.CHUNK_DATA, null); ++ this.completeAsyncUnloadDataSave(MoonriseRegionFileIO.RegionFileType.CHUNK_DATA, null); + } + + if (chunk instanceof LevelChunk levelChunk) { @@ -9572,6 +10368,9 @@ index 0000000000000000000000000000000000000000..45eda96fd8a1acb87dbb69ce5495fec7 + if (oldUnloaded != newUnloaded) { + this.checkUnload(); + } ++ ++ // Don't really have a choice but to place this hook here ++ PlatformHooks.get().onChunkHolderTicketChange(this.world, this, oldLevel, newLevel); + } + + static final int NEIGHBOUR_RADIUS = 2; @@ -9617,24 +10416,6 @@ index 0000000000000000000000000000000000000000..45eda96fd8a1acb87dbb69ce5495fec7 + private static final long CHUNK_LOADED_MASK_RAD1 = getLoadedMask(1); + private static final long CHUNK_LOADED_MASK_RAD2 = getLoadedMask(2); + -+ public static boolean areNeighboursFullLoaded(final long bitset, final int radius) { -+ switch (radius) { -+ case 0: { -+ return (bitset & CHUNK_LOADED_MASK_RAD0) == CHUNK_LOADED_MASK_RAD0; -+ } -+ case 1: { -+ return (bitset & CHUNK_LOADED_MASK_RAD1) == CHUNK_LOADED_MASK_RAD1; -+ } -+ case 2: { -+ return (bitset & CHUNK_LOADED_MASK_RAD2) == CHUNK_LOADED_MASK_RAD2; -+ } -+ -+ default: { -+ throw new IllegalArgumentException("Radius not recognized: " + radius); -+ } -+ } -+ } -+ + // only updated while holding scheduling lock + private FullChunkStatus pendingFullChunkStatus = FullChunkStatus.INACCESSIBLE; + // updated while holding no locks, but adds a ticket before to prevent pending status from dropping @@ -9869,6 +10650,17 @@ index 0000000000000000000000000000000000000000..45eda96fd8a1acb87dbb69ce5495fec7 + } + + private void completeStatusConsumers(ChunkStatus status, final ChunkAccess chunk) { ++ // Update progress listener for LevelLoadingScreen ++ if (chunk != null) { ++ final ChunkProgressListener progressListener = this.world.getChunkSource().chunkMap.progressListener; ++ if (progressListener != null) { ++ final ChunkStatus finalStatus = status; ++ this.scheduler.scheduleChunkTask(this.chunkX, this.chunkZ, () -> { ++ progressListener.onStatusChange(this.vanillaChunkHolder.getPos(), finalStatus); ++ }); ++ } ++ } ++ + // need to tell future statuses to complete if cancelled + do { + this.completeStatusConsumers0(status, chunk); @@ -9892,7 +10684,7 @@ index 0000000000000000000000000000000000000000..45eda96fd8a1acb87dbb69ce5495fec7 + LOGGER.error("Failed to process chunk status callback", thr); + } + } -+ }, PrioritisedExecutor.Priority.HIGHEST); ++ }, Priority.HIGHEST); + } + + private final Reference2ObjectOpenHashMap>> fullStatusWaiters = new Reference2ObjectOpenHashMap<>(); @@ -9920,7 +10712,7 @@ index 0000000000000000000000000000000000000000..45eda96fd8a1acb87dbb69ce5495fec7 + LOGGER.error("Failed to process chunk status callback", thr); + } + } -+ }, PrioritisedExecutor.Priority.HIGHEST); ++ }, Priority.HIGHEST); + } + + // note: must hold scheduling lock @@ -10176,6 +10968,8 @@ index 0000000000000000000000000000000000000000..45eda96fd8a1acb87dbb69ce5495fec7 + + public static final record SaveStat(boolean savedChunk, boolean savedEntityChunk, boolean savedPoiChunk) {} + ++ private static final MoonriseRegionFileIO.RegionFileType[] REGION_FILE_TYPES = MoonriseRegionFileIO.RegionFileType.values(); ++ + public SaveStat save(final boolean shutdown) { + TickThread.ensureTickThread(this.world, this.chunkX, this.chunkZ, "Cannot save data off-main"); + @@ -10183,6 +10977,7 @@ index 0000000000000000000000000000000000000000..45eda96fd8a1acb87dbb69ce5495fec7 + PoiChunk poi = this.getPoiChunk(); + ChunkEntitySlices entities = this.getEntityChunk(); + boolean executedUnloadTask = false; ++ final boolean[] executedUnloadTasks = new boolean[REGION_FILE_TYPES.length]; + + if (shutdown) { + // make sure that the async unloads complete @@ -10192,17 +10987,22 @@ index 0000000000000000000000000000000000000000..45eda96fd8a1acb87dbb69ce5495fec7 + poi = this.unloadState.poiChunk(); + entities = this.unloadState.entityChunk(); + } -+ final UnloadTask chunkUnloadTask = this.chunkDataUnload; -+ final DelayedPrioritisedTask chunkDataUnloadTask = chunkUnloadTask == null ? null : chunkUnloadTask.task(); -+ if (chunkDataUnloadTask != null) { -+ final PrioritisedExecutor.PrioritisedTask unloadTask = chunkDataUnloadTask.getTask(); -+ if (unloadTask != null) { -+ executedUnloadTask = unloadTask.execute(); ++ for (final MoonriseRegionFileIO.RegionFileType regionFileType : REGION_FILE_TYPES) { ++ final UnloadTask unloadTask = this.getUnloadTask(regionFileType); ++ if (unloadTask == null) { ++ continue; ++ } ++ ++ final PrioritisedExecutor.PrioritisedTask task = unloadTask.task(); ++ if (task != null && task.isQueued()) { ++ final boolean executed = task.execute(); ++ executedUnloadTask |= executed; ++ executedUnloadTasks[regionFileType.ordinal()] = executed; + } + } + } + -+ final boolean forceNoSaveChunk = ChunkSystemFeatures.forceNoSave(chunk); ++ final boolean forceNoSaveChunk = PlatformHooks.get().forceNoSave(chunk); + + // can only synchronously save worldgen chunks during shutdown + boolean canSaveChunk = !forceNoSaveChunk && (chunk != null && ((shutdown || chunk instanceof LevelChunk) && chunk.isUnsaved())); @@ -10223,106 +11023,55 @@ index 0000000000000000000000000000000000000000..45eda96fd8a1acb87dbb69ce5495fec7 + } + } + -+ return executedUnloadTask | canSaveChunk | canSaveEntities | canSavePOI ? new SaveStat(executedUnloadTask || canSaveChunk, canSaveEntities, canSavePOI): null; -+ } -+ -+ static final class AsyncChunkSerializeTask implements Runnable { -+ -+ private final ServerLevel world; -+ private final ChunkAccess chunk; -+ private final AsyncChunkSaveData asyncSaveData; -+ private final NewChunkHolder toComplete; -+ -+ public AsyncChunkSerializeTask(final ServerLevel world, final ChunkAccess chunk, final AsyncChunkSaveData asyncSaveData, -+ final NewChunkHolder toComplete) { -+ this.world = world; -+ this.chunk = chunk; -+ this.asyncSaveData = asyncSaveData; -+ this.toComplete = toComplete; -+ } -+ -+ @Override -+ public void run() { -+ final CompoundTag toSerialize; -+ try { -+ toSerialize = ChunkSystemFeatures.saveChunkAsync(this.world, this.chunk, this.asyncSaveData); -+ } catch (final Throwable throwable) { -+ LOGGER.error("Failed to asynchronously save chunk " + this.chunk.getPos() + " for world '" + WorldUtil.getWorldName(this.world) + "', falling back to synchronous save", throwable); -+ final ChunkPos pos = this.chunk.getPos(); -+ ((ChunkSystemServerLevel)this.world).moonrise$getChunkTaskScheduler().scheduleChunkTask(pos.x, pos.z, () -> { -+ final CompoundTag synchronousSave; -+ try { -+ synchronousSave = ChunkSystemFeatures.saveChunkAsync(AsyncChunkSerializeTask.this.world, AsyncChunkSerializeTask.this.chunk, AsyncChunkSerializeTask.this.asyncSaveData); -+ } catch (final Throwable throwable2) { -+ LOGGER.error("Failed to synchronously save chunk " + AsyncChunkSerializeTask.this.chunk.getPos() + " for world '" + WorldUtil.getWorldName(AsyncChunkSerializeTask.this.world) + "', chunk data will be lost", throwable2); -+ AsyncChunkSerializeTask.this.toComplete.completeAsyncUnloadDataSave(RegionFileIOThread.RegionFileType.CHUNK_DATA, null); -+ return; -+ } -+ -+ AsyncChunkSerializeTask.this.toComplete.completeAsyncUnloadDataSave(RegionFileIOThread.RegionFileType.CHUNK_DATA, synchronousSave); -+ LOGGER.info("Successfully serialized chunk " + AsyncChunkSerializeTask.this.chunk.getPos() + " for world '" + WorldUtil.getWorldName(AsyncChunkSerializeTask.this.world) + "' synchronously"); -+ -+ }, PrioritisedExecutor.Priority.HIGHEST); -+ return; -+ } -+ this.toComplete.completeAsyncUnloadDataSave(RegionFileIOThread.RegionFileType.CHUNK_DATA, toSerialize); -+ } -+ -+ @Override -+ public String toString() { -+ return "AsyncChunkSerializeTask{" + -+ "chunk={pos=" + this.chunk.getPos() + ",world=\"" + WorldUtil.getWorldName(this.world) + "\"}" + -+ "}"; -+ } ++ return executedUnloadTask | canSaveChunk | canSaveEntities | canSavePOI ? ++ new SaveStat( ++ canSaveChunk | executedUnloadTasks[MoonriseRegionFileIO.RegionFileType.CHUNK_DATA.ordinal()], ++ canSaveEntities | executedUnloadTasks[MoonriseRegionFileIO.RegionFileType.ENTITY_DATA.ordinal()], ++ canSavePOI | executedUnloadTasks[MoonriseRegionFileIO.RegionFileType.POI_DATA.ordinal()] ++ ) ++ : null; + } + + private boolean saveChunk(final ChunkAccess chunk, final boolean unloading) { + if (!chunk.isUnsaved()) { + if (unloading) { -+ this.completeAsyncUnloadDataSave(RegionFileIOThread.RegionFileType.CHUNK_DATA, null); ++ this.completeAsyncUnloadDataSave(MoonriseRegionFileIO.RegionFileType.CHUNK_DATA, null); + } + return false; + } -+ boolean completing = false; -+ boolean failedAsyncPrepare = false; + try { -+ if (unloading && ChunkSystemFeatures.supportsAsyncChunkSave()) { -+ try { -+ final AsyncChunkSaveData asyncSaveData = ChunkSystemFeatures.getAsyncSaveData(this.world, chunk); ++ final SerializableChunkData chunkData = SerializableChunkData.copyOf(this.world, chunk); ++ PlatformHooks.get().chunkSyncSave(this.world, chunk, chunkData); + -+ final PrioritisedExecutor.PrioritisedTask task = this.scheduler.loadExecutor.createTask(new AsyncChunkSerializeTask(this.world, chunk, asyncSaveData, this)); ++ chunk.tryMarkSaved(); + -+ this.chunkDataUnload.task().setTask(task); ++ final CallbackCompletable completable = new CallbackCompletable<>(); + -+ chunk.setUnsaved(false); ++ final Runnable run = () -> { ++ final CompoundTag data = chunkData.write(); + -+ task.queue(); ++ completable.complete(data); + -+ return true; -+ } catch (final Throwable thr) { -+ LOGGER.error("Failed to prepare async chunk data (" + this.chunkX + "," + this.chunkZ + ") in world '" + WorldUtil.getWorldName(this.world) + "', falling back to synchronous save", thr); -+ failedAsyncPrepare = true; -+ // fall through to synchronous save ++ if (unloading) { ++ NewChunkHolder.this.completeAsyncUnloadDataSave(MoonriseRegionFileIO.RegionFileType.CHUNK_DATA, data); + } -+ } -+ -+ final CompoundTag save = ChunkSerializer.write(this.world, chunk); ++ }; + ++ final PrioritisedExecutor.PrioritisedTask task; + if (unloading) { -+ completing = true; -+ this.completeAsyncUnloadDataSave(RegionFileIOThread.RegionFileType.CHUNK_DATA, save); -+ if (failedAsyncPrepare) { -+ LOGGER.info("Successfully serialized chunk data (" + this.chunkX + "," + this.chunkZ + ") in world '" + WorldUtil.getWorldName(this.world) + "' synchronously"); -+ } ++ this.chunkDataUnload.toRun().setRunnable(run); ++ task = this.chunkDataUnload.task(); + } else { -+ RegionFileIOThread.scheduleSave(this.world, this.chunkX, this.chunkZ, save, RegionFileIOThread.RegionFileType.CHUNK_DATA); ++ task = this.scheduler.saveExecutor.createTask(run); + } -+ chunk.setUnsaved(false); ++ ++ task.queue(); ++ ++ MoonriseRegionFileIO.scheduleSave( ++ this.world, this.chunkX, this.chunkZ, completable, task, MoonriseRegionFileIO.RegionFileType.CHUNK_DATA, Priority.NORMAL ++ ); + } catch (final Throwable thr) { + LOGGER.error("Failed to save chunk data (" + this.chunkX + "," + this.chunkZ + ") in world '" + WorldUtil.getWorldName(this.world) + "'", thr); -+ if (unloading && !completing) { -+ this.completeAsyncUnloadDataSave(RegionFileIOThread.RegionFileType.CHUNK_DATA, null); -+ } + } + + return true; @@ -10340,7 +11089,7 @@ index 0000000000000000000000000000000000000000..45eda96fd8a1acb87dbb69ce5495fec7 + return false; + } + try { -+ mergeFrom = RegionFileIOThread.loadData(this.world, this.chunkX, this.chunkZ, RegionFileIOThread.RegionFileType.ENTITY_DATA, PrioritisedExecutor.Priority.BLOCKING); ++ mergeFrom = MoonriseRegionFileIO.loadData(this.world, this.chunkX, this.chunkZ, MoonriseRegionFileIO.RegionFileType.ENTITY_DATA, Priority.BLOCKING); + } catch (final Exception ex) { + LOGGER.error("Cannot merge transient entities for chunk (" + this.chunkX + "," + this.chunkZ + ") in world '" + WorldUtil.getWorldName(this.world) + "', data on disk will be replaced", ex); + } @@ -10359,7 +11108,7 @@ index 0000000000000000000000000000000000000000..45eda96fd8a1acb87dbb69ce5495fec7 + return false; + } + -+ RegionFileIOThread.scheduleSave(this.world, this.chunkX, this.chunkZ, save, RegionFileIOThread.RegionFileType.ENTITY_DATA); ++ MoonriseRegionFileIO.scheduleSave(this.world, this.chunkX, this.chunkZ, save, MoonriseRegionFileIO.RegionFileType.ENTITY_DATA); + this.lastEntitySaveNull = save == null; + if (unloading) { + this.lastEntityUnload = save; @@ -10383,7 +11132,7 @@ index 0000000000000000000000000000000000000000..45eda96fd8a1acb87dbb69ce5495fec7 + return false; + } + -+ RegionFileIOThread.scheduleSave(this.world, this.chunkX, this.chunkZ, save, RegionFileIOThread.RegionFileType.POI_DATA); ++ MoonriseRegionFileIO.scheduleSave(this.world, this.chunkX, this.chunkZ, save, MoonriseRegionFileIO.RegionFileType.POI_DATA); + this.lastPoiSaveNull = save == null; + if (unloading) { + this.poiDataUnload.completable().complete(save); @@ -10430,7 +11179,7 @@ index 0000000000000000000000000000000000000000..45eda96fd8a1acb87dbb69ce5495fec7 + return element == null ? JsonNull.INSTANCE : new JsonPrimitive(element.toString()); + } + -+ private static JsonObject serializeCompletable(final Completable completable) { ++ private static JsonObject serializeCompletable(final CallbackCompletable completable) { + final JsonObject ret = new JsonObject(); + + if (completable == null) { @@ -10525,13 +11274,13 @@ index 0000000000000000000000000000000000000000..45eda96fd8a1acb87dbb69ce5495fec7 + ret.add("poi_unload_completable", serializeCompletable(poiDataUnload == null ? null : poiDataUnload.completable())); + ret.add("chunk_unload_completable", serializeCompletable(chunkDataUnload == null ? null : chunkDataUnload.completable())); + -+ final DelayedPrioritisedTask unloadTask = chunkDataUnload == null ? null : chunkDataUnload.task(); ++ final PrioritisedExecutor.PrioritisedTask unloadTask = chunkDataUnload == null ? null : chunkDataUnload.task(); + if (unloadTask == null) { + ret.addProperty("unload_task_priority", "null"); -+ ret.addProperty("unload_task_priority_raw", "null"); ++ ret.addProperty("unload_task_suborder", Long.valueOf(0L)); + } else { + ret.addProperty("unload_task_priority", Objects.toString(unloadTask.getPriority())); -+ ret.addProperty("unload_task_priority_raw", Integer.valueOf(unloadTask.getPriorityInternal())); ++ ret.addProperty("unload_task_suborder", Long.valueOf(unloadTask.getSubOrder())); + } + + ret.addProperty("killed", Boolean.valueOf(this.unloaded)); @@ -10541,14 +11290,14 @@ index 0000000000000000000000000000000000000000..45eda96fd8a1acb87dbb69ce5495fec7 +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/PriorityHolder.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/PriorityHolder.java new file mode 100644 -index 0000000000000000000000000000000000000000..261e09454f49d04eb159c984ec695d7c7aa6a3a8 +index 0000000000000000000000000000000000000000..6b468c621b74449a6218391f6477cf63cfc98c7c --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/PriorityHolder.java @@ -0,0 +1,215 @@ +package ca.spottedleaf.moonrise.patches.chunk_system.scheduling; + -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; +import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; ++import ca.spottedleaf.concurrentutil.util.Priority; +import java.lang.invoke.VarHandle; + +public abstract class PriorityHolder { @@ -10575,8 +11324,8 @@ index 0000000000000000000000000000000000000000..261e09454f49d04eb159c984ec695d7c + PRIORITY_HANDLE.set((PriorityHolder)this, (int)val); + } + -+ protected PriorityHolder(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { ++ protected PriorityHolder(final Priority priority) { ++ if (!Priority.isValidPriority(priority)) { + throw new IllegalArgumentException("Invalid priority " + priority); + } + this.setPriorityPlain(priority.priority); @@ -10616,7 +11365,7 @@ index 0000000000000000000000000000000000000000..261e09454f49d04eb159c984ec695d7c + return; + } + -+ this.scheduleTask(PrioritisedExecutor.Priority.getPriority(priority)); ++ this.scheduleTask(Priority.getPriority(priority)); + + int failures = 0; + for (;;) { @@ -10633,7 +11382,7 @@ index 0000000000000000000000000000000000000000..261e09454f49d04eb159c984ec695d7c + return; + } + -+ this.setPriorityScheduled(PrioritisedExecutor.Priority.getPriority(priority)); ++ this.setPriorityScheduled(Priority.getPriority(priority)); + + ++failures; + for (int i = 0; i < failures; ++i) { @@ -10642,19 +11391,19 @@ index 0000000000000000000000000000000000000000..261e09454f49d04eb159c984ec695d7c + } + } + -+ public final PrioritisedExecutor.Priority getPriority() { ++ public final Priority getPriority() { + final int ret = this.getPriorityVolatile(); + if ((ret & PRIORITY_EXECUTED) != 0) { -+ return PrioritisedExecutor.Priority.COMPLETING; ++ return Priority.COMPLETING; + } + if ((ret & PRIORITY_SCHEDULED) != 0) { + return this.getScheduledPriority(); + } -+ return PrioritisedExecutor.Priority.getPriority(ret); ++ return Priority.getPriority(ret); + } + -+ public final void lowerPriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { ++ public final void lowerPriority(final Priority priority) { ++ if (!Priority.isValidPriority(priority)) { + throw new IllegalArgumentException("Invalid priority " + priority); + } + @@ -10686,8 +11435,8 @@ index 0000000000000000000000000000000000000000..261e09454f49d04eb159c984ec695d7c + } + } + -+ public final void setPriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { ++ public final void setPriority(final Priority priority) { ++ if (!Priority.isValidPriority(priority)) { + throw new IllegalArgumentException("Invalid priority " + priority); + } + @@ -10715,8 +11464,8 @@ index 0000000000000000000000000000000000000000..261e09454f49d04eb159c984ec695d7c + } + } + -+ public final void raisePriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { ++ public final void raisePriority(final Priority priority) { ++ if (!Priority.isValidPriority(priority)) { + throw new IllegalArgumentException("Invalid priority " + priority); + } + @@ -10750,15 +11499,15 @@ index 0000000000000000000000000000000000000000..261e09454f49d04eb159c984ec695d7c + + protected abstract void cancelScheduled(); + -+ protected abstract PrioritisedExecutor.Priority getScheduledPriority(); ++ protected abstract Priority getScheduledPriority(); + -+ protected abstract void scheduleTask(final PrioritisedExecutor.Priority priority); ++ protected abstract void scheduleTask(final Priority priority); + -+ protected abstract void lowerPriorityScheduled(final PrioritisedExecutor.Priority priority); ++ protected abstract void lowerPriorityScheduled(final Priority priority); + -+ protected abstract void setPriorityScheduled(final PrioritisedExecutor.Priority priority); ++ protected abstract void setPriorityScheduled(final Priority priority); + -+ protected abstract void raisePriorityScheduled(final PrioritisedExecutor.Priority priority); ++ protected abstract void raisePriorityScheduled(final Priority priority); +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java new file mode 100644 @@ -12225,17 +12974,17 @@ index 0000000000000000000000000000000000000000..310a8f80debadd64c2d962ebf83b7d05 +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/executor/RadiusAwarePrioritisedExecutor.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/executor/RadiusAwarePrioritisedExecutor.java new file mode 100644 -index 0000000000000000000000000000000000000000..e0b26ccb63596748b80fc6a5e47e373ba811ba8b +index 0000000000000000000000000000000000000000..5f4b99d8c5453f8ad2e600a57ea4e7dafa2d45f8 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/executor/RadiusAwarePrioritisedExecutor.java -@@ -0,0 +1,668 @@ +@@ -0,0 +1,729 @@ +package ca.spottedleaf.moonrise.patches.chunk_system.scheduling.executor; + -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; ++import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor; ++import ca.spottedleaf.concurrentutil.util.Priority; +import ca.spottedleaf.moonrise.common.util.CoordinateUtils; +import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; +import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; -+ +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; @@ -12247,17 +12996,38 @@ index 0000000000000000000000000000000000000000..e0b26ccb63596748b80fc6a5e47e373b + return Long.compare(t1.id, t2.id); + }; + -+ private final DependencyTree[] queues = new DependencyTree[PrioritisedExecutor.Priority.TOTAL_SCHEDULABLE_PRIORITIES]; ++ private final PrioritisedExecutor executor; ++ private final DependencyTree[] queues = new DependencyTree[Priority.TOTAL_SCHEDULABLE_PRIORITIES]; + private static final int NO_TASKS_QUEUED = -1; + private int selectedQueue = NO_TASKS_QUEUED; + private boolean canQueueTasks = true; + + public RadiusAwarePrioritisedExecutor(final PrioritisedExecutor executor, final int maxToSchedule) { ++ this.executor = executor; ++ + for (int i = 0; i < this.queues.length; ++i) { -+ this.queues[i] = new DependencyTree(this, executor, maxToSchedule, i); ++ this.queues[i] = new DependencyTree(this, executor, maxToSchedule); + } + } + ++ public void setMaxToSchedule(final int maxToSchedule) { ++ final List tasks; ++ ++ synchronized (this) { ++ for (final DependencyTree dependencyTree : this.queues) { ++ dependencyTree.maxToSchedule = maxToSchedule; ++ } ++ ++ if (this.selectedQueue == NO_TASKS_QUEUED || !this.canQueueTasks) { ++ return; ++ } ++ ++ tasks = this.queues[this.selectedQueue].tryPushTasks(); ++ } ++ ++ scheduleTasks(tasks); ++ } ++ + private boolean canQueueTasks() { + return this.canQueueTasks; + } @@ -12287,7 +13057,7 @@ index 0000000000000000000000000000000000000000..e0b26ccb63596748b80fc6a5e47e373b + return null; + } + -+ private List queue(final Task task, final PrioritisedExecutor.Priority priority) { ++ private List queue(final Task task, final Priority priority) { + final int priorityId = priority.priority; + final DependencyTree queue = this.queues[priorityId]; + @@ -12310,7 +13080,7 @@ index 0000000000000000000000000000000000000000..e0b26ccb63596748b80fc6a5e47e373b + return null; + } + -+ if (PrioritisedExecutor.Priority.isHigherPriority(priorityId, this.selectedQueue)) { ++ if (Priority.isHigherPriority(priorityId, this.selectedQueue)) { + // prevent the lower priority tree from queueing more tasks + this.canQueueTasks = false; + return null; @@ -12321,7 +13091,7 @@ index 0000000000000000000000000000000000000000..e0b26ccb63596748b80fc6a5e47e373b + } + + public PrioritisedExecutor.PrioritisedTask createTask(final int chunkX, final int chunkZ, final int radius, -+ final Runnable run, final PrioritisedExecutor.Priority priority) { ++ final Runnable run, final Priority priority) { + if (radius < 0) { + throw new IllegalArgumentException("Radius must be > 0: " + radius); + } @@ -12330,11 +13100,11 @@ index 0000000000000000000000000000000000000000..e0b26ccb63596748b80fc6a5e47e373b + + public PrioritisedExecutor.PrioritisedTask createTask(final int chunkX, final int chunkZ, final int radius, + final Runnable run) { -+ return this.createTask(chunkX, chunkZ, radius, run, PrioritisedExecutor.Priority.NORMAL); ++ return this.createTask(chunkX, chunkZ, radius, run, Priority.NORMAL); + } + + public PrioritisedExecutor.PrioritisedTask queueTask(final int chunkX, final int chunkZ, final int radius, -+ final Runnable run, final PrioritisedExecutor.Priority priority) { ++ final Runnable run, final Priority priority) { + final PrioritisedExecutor.PrioritisedTask ret = this.createTask(chunkX, chunkZ, radius, run, priority); + + ret.queue(); @@ -12351,15 +13121,15 @@ index 0000000000000000000000000000000000000000..e0b26ccb63596748b80fc6a5e47e373b + return ret; + } + -+ public PrioritisedExecutor.PrioritisedTask createInfiniteRadiusTask(final Runnable run, final PrioritisedExecutor.Priority priority) { ++ public PrioritisedExecutor.PrioritisedTask createInfiniteRadiusTask(final Runnable run, final Priority priority) { + return new Task(this, 0, 0, -1, run, priority); + } + + public PrioritisedExecutor.PrioritisedTask createInfiniteRadiusTask(final Runnable run) { -+ return this.createInfiniteRadiusTask(run, PrioritisedExecutor.Priority.NORMAL); ++ return this.createInfiniteRadiusTask(run, Priority.NORMAL); + } + -+ public PrioritisedExecutor.PrioritisedTask queueInfiniteRadiusTask(final Runnable run, final PrioritisedExecutor.Priority priority) { ++ public PrioritisedExecutor.PrioritisedTask queueInfiniteRadiusTask(final Runnable run, final Priority priority) { + final PrioritisedExecutor.PrioritisedTask ret = this.createInfiniteRadiusTask(run, priority); + + ret.queue(); @@ -12368,20 +13138,27 @@ index 0000000000000000000000000000000000000000..e0b26ccb63596748b80fc6a5e47e373b + } + + public PrioritisedExecutor.PrioritisedTask queueInfiniteRadiusTask(final Runnable run) { -+ final PrioritisedExecutor.PrioritisedTask ret = this.createInfiniteRadiusTask(run, PrioritisedExecutor.Priority.NORMAL); ++ final PrioritisedExecutor.PrioritisedTask ret = this.createInfiniteRadiusTask(run, Priority.NORMAL); + + ret.queue(); + + return ret; + } + ++ private static void scheduleTasks(final List toSchedule) { ++ if (toSchedule != null) { ++ for (int i = 0, len = toSchedule.size(); i < len; ++i) { ++ toSchedule.get(i).queue(); ++ } ++ } ++ } ++ + // all accesses must be synchronised by the radius aware object + private static final class DependencyTree { + + private final RadiusAwarePrioritisedExecutor scheduler; + private final PrioritisedExecutor executor; -+ private final int maxToSchedule; -+ private final int treeIndex; ++ private int maxToSchedule; + + private int currentlyExecuting; + private long idGenerator; @@ -12394,11 +13171,10 @@ index 0000000000000000000000000000000000000000..e0b26ccb63596748b80fc6a5e47e373b + private final Long2ReferenceOpenHashMap nodeByPosition = new Long2ReferenceOpenHashMap<>(); + + public DependencyTree(final RadiusAwarePrioritisedExecutor scheduler, final PrioritisedExecutor executor, -+ final int maxToSchedule, final int treeIndex) { ++ final int maxToSchedule) { + this.scheduler = scheduler; + this.executor = executor; + this.maxToSchedule = maxToSchedule; -+ this.treeIndex = treeIndex; + } + + public boolean hasWaitingTasks() { @@ -12643,13 +13419,13 @@ index 0000000000000000000000000000000000000000..e0b26ccb63596748b80fc6a5e47e373b + private final int chunkZ; + private final int radius; + private Runnable run; -+ private PrioritisedExecutor.Priority priority; ++ private Priority priority; + + private DependencyNode dependencyNode; + private PrioritisedExecutor.PrioritisedTask queuedTask; + + private Task(final RadiusAwarePrioritisedExecutor scheduler, final int chunkX, final int chunkZ, final int radius, -+ final Runnable run, final PrioritisedExecutor.Priority priority) { ++ final Runnable run, final Priority priority) { + this.scheduler = scheduler; + this.chunkX = chunkX; + this.chunkZ = chunkZ; @@ -12672,14 +13448,6 @@ index 0000000000000000000000000000000000000000..e0b26ccb63596748b80fc6a5e47e373b + run.run(); + } + -+ private static void scheduleTasks(final List toSchedule) { -+ if (toSchedule != null) { -+ for (int i = 0, len = toSchedule.size(); i < len; ++i) { -+ toSchedule.get(i).queue(); -+ } -+ } -+ } -+ + private void returnNode() { + final List toSchedule; + synchronized (this.scheduler) { @@ -12692,6 +13460,11 @@ index 0000000000000000000000000000000000000000..e0b26ccb63596748b80fc6a5e47e373b + } + + @Override ++ public PrioritisedExecutor getExecutor() { ++ return this.scheduler.executor; ++ } ++ ++ @Override + public void run() { + final Runnable run = this.run; + this.run = null; @@ -12706,7 +13479,7 @@ index 0000000000000000000000000000000000000000..e0b26ccb63596748b80fc6a5e47e373b + public boolean queue() { + final List toSchedule; + synchronized (this.scheduler) { -+ if (this.queuedTask != null || this.dependencyNode != null || this.priority == PrioritisedExecutor.Priority.COMPLETING) { ++ if (this.queuedTask != null || this.dependencyNode != null || this.priority == Priority.COMPLETING) { + return false; + } + @@ -12718,15 +13491,22 @@ index 0000000000000000000000000000000000000000..e0b26ccb63596748b80fc6a5e47e373b + } + + @Override ++ public boolean isQueued() { ++ synchronized (this.scheduler) { ++ return (this.queuedTask != null || this.dependencyNode != null) && this.priority != Priority.COMPLETING; ++ } ++ } ++ ++ @Override + public boolean cancel() { + final PrioritisedExecutor.PrioritisedTask task; + synchronized (this.scheduler) { + if ((task = this.queuedTask) == null) { -+ if (this.priority == PrioritisedExecutor.Priority.COMPLETING) { ++ if (this.priority == Priority.COMPLETING) { + return false; + } + -+ this.priority = PrioritisedExecutor.Priority.COMPLETING; ++ this.priority = Priority.COMPLETING; + if (this.dependencyNode != null) { + this.dependencyNode.purged = true; + this.dependencyNode = null; @@ -12750,11 +13530,11 @@ index 0000000000000000000000000000000000000000..e0b26ccb63596748b80fc6a5e47e373b + final PrioritisedExecutor.PrioritisedTask task; + synchronized (this.scheduler) { + if ((task = this.queuedTask) == null) { -+ if (this.priority == PrioritisedExecutor.Priority.COMPLETING) { ++ if (this.priority == Priority.COMPLETING) { + return false; + } + -+ this.priority = PrioritisedExecutor.Priority.COMPLETING; ++ this.priority = Priority.COMPLETING; + if (this.dependencyNode != null) { + this.dependencyNode.purged = true; + this.dependencyNode = null; @@ -12774,7 +13554,7 @@ index 0000000000000000000000000000000000000000..e0b26ccb63596748b80fc6a5e47e373b + } + + @Override -+ public PrioritisedExecutor.Priority getPriority() { ++ public Priority getPriority() { + final PrioritisedExecutor.PrioritisedTask task; + synchronized (this.scheduler) { + if ((task = this.queuedTask) == null) { @@ -12786,8 +13566,8 @@ index 0000000000000000000000000000000000000000..e0b26ccb63596748b80fc6a5e47e373b + } + + @Override -+ public boolean setPriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { ++ public boolean setPriority(final Priority priority) { ++ if (!Priority.isValidPriority(priority)) { + throw new IllegalArgumentException("Invalid priority " + priority); + } + @@ -12795,7 +13575,7 @@ index 0000000000000000000000000000000000000000..e0b26ccb63596748b80fc6a5e47e373b + List toSchedule = null; + synchronized (this.scheduler) { + if ((task = this.queuedTask) == null) { -+ if (this.priority == PrioritisedExecutor.Priority.COMPLETING) { ++ if (this.priority == Priority.COMPLETING) { + return false; + } + @@ -12823,8 +13603,8 @@ index 0000000000000000000000000000000000000000..e0b26ccb63596748b80fc6a5e47e373b + } + + @Override -+ public boolean raisePriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { ++ public boolean raisePriority(final Priority priority) { ++ if (!Priority.isValidPriority(priority)) { + throw new IllegalArgumentException("Invalid priority " + priority); + } + @@ -12832,7 +13612,7 @@ index 0000000000000000000000000000000000000000..e0b26ccb63596748b80fc6a5e47e373b + List toSchedule = null; + synchronized (this.scheduler) { + if ((task = this.queuedTask) == null) { -+ if (this.priority == PrioritisedExecutor.Priority.COMPLETING) { ++ if (this.priority == Priority.COMPLETING) { + return false; + } + @@ -12860,8 +13640,8 @@ index 0000000000000000000000000000000000000000..e0b26ccb63596748b80fc6a5e47e373b + } + + @Override -+ public boolean lowerPriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { ++ public boolean lowerPriority(final Priority priority) { ++ if (!Priority.isValidPriority(priority)) { + throw new IllegalArgumentException("Invalid priority " + priority); + } + @@ -12869,7 +13649,7 @@ index 0000000000000000000000000000000000000000..e0b26ccb63596748b80fc6a5e47e373b + List toSchedule = null; + synchronized (this.scheduler) { + if ((task = this.queuedTask) == null) { -+ if (this.priority == PrioritisedExecutor.Priority.COMPLETING) { ++ if (this.priority == Priority.COMPLETING) { + return false; + } + @@ -12895,19 +13675,50 @@ index 0000000000000000000000000000000000000000..e0b26ccb63596748b80fc6a5e47e373b + + return true; + } ++ ++ @Override ++ public long getSubOrder() { ++ // TODO implement ++ return 0; ++ } ++ ++ @Override ++ public boolean setSubOrder(final long subOrder) { ++ // TODO implement ++ return false; ++ } ++ ++ @Override ++ public boolean raiseSubOrder(final long subOrder) { ++ // TODO implement ++ return false; ++ } ++ ++ @Override ++ public boolean lowerSubOrder(final long subOrder) { ++ // TODO implement ++ return false; ++ } ++ ++ @Override ++ public boolean setPriorityAndSubOrder(final Priority priority, final long subOrder) { ++ // TODO implement ++ return this.setPriority(priority); ++ } + } +} -\ No newline at end of file diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkFullTask.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkFullTask.java new file mode 100644 -index 0000000000000000000000000000000000000000..fbdf721e8b4cfe6cef4ee60c53c680cbfc858d88 +index 0000000000000000000000000000000000000000..6ab353b0d2465c3680bb3c8d0852ba0f65c00fd2 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkFullTask.java -@@ -0,0 +1,142 @@ +@@ -0,0 +1,151 @@ +package ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task; + -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; ++import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor; +import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; ++import ca.spottedleaf.concurrentutil.util.Priority; ++import ca.spottedleaf.moonrise.common.PlatformHooks; +import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel; +import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk; +import ca.spottedleaf.moonrise.patches.chunk_system.level.poi.ChunkSystemPoiManager; @@ -12935,7 +13746,7 @@ index 0000000000000000000000000000000000000000..fbdf721e8b4cfe6cef4ee60c53c680cb + private final PrioritisedExecutor.PrioritisedTask convertToFullTask; + + public ChunkFullTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, final int chunkZ, -+ final NewChunkHolder chunkHolder, final ChunkAccess fromChunk, final PrioritisedExecutor.Priority priority) { ++ final NewChunkHolder chunkHolder, final ChunkAccess fromChunk, final Priority priority) { + super(scheduler, world, chunkX, chunkZ); + this.chunkHolder = chunkHolder; + this.fromChunk = fromChunk; @@ -12949,6 +13760,8 @@ index 0000000000000000000000000000000000000000..fbdf721e8b4cfe6cef4ee60c53c680cb + + @Override + public void run() { ++ final PlatformHooks platformHooks = PlatformHooks.get(); ++ + // See Vanilla ChunkPyramid#LOADING_PYRAMID.FULL for what this function should be doing + final LevelChunk chunk; + try { @@ -12967,7 +13780,7 @@ index 0000000000000000000000000000000000000000..fbdf721e8b4cfe6cef4ee60c53c680cb + final ServerLevel world = this.world; + final ProtoChunk protoChunk = (ProtoChunk)this.fromChunk; + chunk = new LevelChunk(this.world, protoChunk, (final LevelChunk unused) -> { -+ ChunkStatusTasks.postLoadProtoChunk(world, protoChunk.getEntities(), protoChunk.getPos()); // Paper - pass chunk pos ++ PlatformHooks.get().postLoadProtoChunk(world, protoChunk); + }); + this.chunkHolder.replaceProtoChunk(new ImposterProtoChunk(chunk, false)); + } @@ -12977,16 +13790,21 @@ index 0000000000000000000000000000000000000000..fbdf721e8b4cfe6cef4ee60c53c680cb + final NewChunkHolder chunkHolder = this.chunkHolder; + + chunk.setFullStatus(chunkHolder::getChunkStatus); -+ chunk.runPostLoad(); -+ // Unlike Vanilla, we load the entity chunk here, as we load the NBT in empty status (unlike Vanilla) -+ // This brings entity addition back in line with older versions of the game -+ // Since we load the NBT in the empty status, this will never block for I/O -+ ((ChunkSystemServerLevel)this.world).moonrise$getChunkTaskScheduler().chunkHolderManager.getOrCreateEntityChunk(this.chunkX, this.chunkZ, false); -+ -+ // we don't need the entitiesInLevel, not sure why it's there -+ chunk.setLoaded(true); -+ chunk.registerAllBlockEntitiesAfterLevelLoad(); -+ chunk.registerTickContainerInLevel(this.world); ++ try { ++ platformHooks.setCurrentlyLoading(this.chunkHolder.vanillaChunkHolder, chunk); ++ chunk.runPostLoad(); ++ // Unlike Vanilla, we load the entity chunk here, as we load the NBT in empty status (unlike Vanilla) ++ // This brings entity addition back in line with older versions of the game ++ // Since we load the NBT in the empty status, this will never block for I/O ++ ((ChunkSystemServerLevel)this.world).moonrise$getChunkTaskScheduler().chunkHolderManager.getOrCreateEntityChunk(this.chunkX, this.chunkZ, false); ++ chunk.setLoaded(true); ++ chunk.registerAllBlockEntitiesAfterLevelLoad(); ++ chunk.registerTickContainerInLevel(this.world); ++ chunk.setUnsavedListener(this.world.getChunkSource().chunkMap.worldGenContext.unsavedListener()); ++ platformHooks.chunkFullStatusComplete(chunk, (ProtoChunk)this.fromChunk); ++ } finally { ++ platformHooks.setCurrentlyLoading(this.chunkHolder.vanillaChunkHolder, null); ++ } + } catch (final Throwable throwable) { + this.complete(null, throwable); + return; @@ -13018,29 +13836,29 @@ index 0000000000000000000000000000000000000000..fbdf721e8b4cfe6cef4ee60c53c680cb + } + + @Override -+ public PrioritisedExecutor.Priority getPriority() { ++ public Priority getPriority() { + return this.convertToFullTask.getPriority(); + } + + @Override -+ public void lowerPriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { ++ public void lowerPriority(final Priority priority) { ++ if (!Priority.isValidPriority(priority)) { + throw new IllegalArgumentException("Invalid priority " + priority); + } + this.convertToFullTask.lowerPriority(priority); + } + + @Override -+ public void setPriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { ++ public void setPriority(final Priority priority) { ++ if (!Priority.isValidPriority(priority)) { + throw new IllegalArgumentException("Invalid priority " + priority); + } + this.convertToFullTask.setPriority(priority); + } + + @Override -+ public void raisePriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { ++ public void raisePriority(final Priority priority) { ++ if (!Priority.isValidPriority(priority)) { + throw new IllegalArgumentException("Invalid priority " + priority); + } + this.convertToFullTask.raisePriority(priority); @@ -13048,13 +13866,13 @@ index 0000000000000000000000000000000000000000..fbdf721e8b4cfe6cef4ee60c53c680cb +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkLightTask.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkLightTask.java new file mode 100644 -index 0000000000000000000000000000000000000000..7c2e6752228fac175c4aa97fa3d817b8a938922f +index 0000000000000000000000000000000000000000..4538ccfaea83d217ed85eaf16e82393c7f286489 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkLightTask.java @@ -0,0 +1,181 @@ +package ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task; + -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; ++import ca.spottedleaf.concurrentutil.util.Priority; +import ca.spottedleaf.moonrise.common.util.WorldUtil; +import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler; +import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.PriorityHolder; @@ -13079,9 +13897,9 @@ index 0000000000000000000000000000000000000000..7c2e6752228fac175c4aa97fa3d817b8 + private final LightTaskPriorityHolder priorityHolder; + + public ChunkLightTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, final int chunkZ, -+ final ChunkAccess chunk, final PrioritisedExecutor.Priority priority) { ++ final ChunkAccess chunk, final Priority priority) { + super(scheduler, world, chunkX, chunkZ); -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { ++ if (!Priority.isValidPriority(priority)) { + throw new IllegalArgumentException("Invalid priority " + priority); + } + this.priorityHolder = new LightTaskPriorityHolder(priority, this); @@ -13109,22 +13927,22 @@ index 0000000000000000000000000000000000000000..7c2e6752228fac175c4aa97fa3d817b8 + } + + @Override -+ public PrioritisedExecutor.Priority getPriority() { ++ public Priority getPriority() { + return this.priorityHolder.getPriority(); + } + + @Override -+ public void lowerPriority(final PrioritisedExecutor.Priority priority) { ++ public void lowerPriority(final Priority priority) { + this.priorityHolder.raisePriority(priority); + } + + @Override -+ public void setPriority(final PrioritisedExecutor.Priority priority) { ++ public void setPriority(final Priority priority) { + this.priorityHolder.setPriority(priority); + } + + @Override -+ public void raisePriority(final PrioritisedExecutor.Priority priority) { ++ public void raisePriority(final Priority priority) { + this.priorityHolder.raisePriority(priority); + } + @@ -13132,7 +13950,7 @@ index 0000000000000000000000000000000000000000..7c2e6752228fac175c4aa97fa3d817b8 + + private final ChunkLightTask task; + -+ private LightTaskPriorityHolder(final PrioritisedExecutor.Priority priority, final ChunkLightTask task) { ++ private LightTaskPriorityHolder(final Priority priority, final ChunkLightTask task) { + super(priority); + this.task = task; + } @@ -13144,13 +13962,13 @@ index 0000000000000000000000000000000000000000..7c2e6752228fac175c4aa97fa3d817b8 + } + + @Override -+ protected PrioritisedExecutor.Priority getScheduledPriority() { ++ protected Priority getScheduledPriority() { + final ChunkLightTask task = this.task; + return ((StarLightLightingProvider)task.world.getChunkSource().getLightEngine()).starlight$getLightEngine().getServerLightQueue().getPriority(task.chunkX, task.chunkZ); + } + + @Override -+ protected void scheduleTask(final PrioritisedExecutor.Priority priority) { ++ protected void scheduleTask(final Priority priority) { + final ChunkLightTask task = this.task; + final StarLightInterface starLightInterface = ((StarLightLightingProvider)task.world.getChunkSource().getLightEngine()).starlight$getLightEngine(); + final StarLightInterface.ServerLightQueue lightQueue = starLightInterface.getServerLightQueue(); @@ -13159,7 +13977,7 @@ index 0000000000000000000000000000000000000000..7c2e6752228fac175c4aa97fa3d817b8 + } + + @Override -+ protected void lowerPriorityScheduled(final PrioritisedExecutor.Priority priority) { ++ protected void lowerPriorityScheduled(final Priority priority) { + final ChunkLightTask task = this.task; + final StarLightInterface starLightInterface = ((StarLightLightingProvider)task.world.getChunkSource().getLightEngine()).starlight$getLightEngine(); + final StarLightInterface.ServerLightQueue lightQueue = starLightInterface.getServerLightQueue(); @@ -13167,7 +13985,7 @@ index 0000000000000000000000000000000000000000..7c2e6752228fac175c4aa97fa3d817b8 + } + + @Override -+ protected void setPriorityScheduled(final PrioritisedExecutor.Priority priority) { ++ protected void setPriorityScheduled(final Priority priority) { + final ChunkLightTask task = this.task; + final StarLightInterface starLightInterface = ((StarLightLightingProvider)task.world.getChunkSource().getLightEngine()).starlight$getLightEngine(); + final StarLightInterface.ServerLightQueue lightQueue = starLightInterface.getServerLightQueue(); @@ -13175,7 +13993,7 @@ index 0000000000000000000000000000000000000000..7c2e6752228fac175c4aa97fa3d817b8 + } + + @Override -+ protected void raisePriorityScheduled(final PrioritisedExecutor.Priority priority) { ++ protected void raisePriorityScheduled(final Priority priority) { + final ChunkLightTask task = this.task; + final StarLightInterface starLightInterface = ((StarLightLightingProvider)task.world.getChunkSource().getLightEngine()).starlight$getLightEngine(); + final StarLightInterface.ServerLightQueue lightQueue = starLightInterface.getServerLightQueue(); @@ -13235,19 +14053,20 @@ index 0000000000000000000000000000000000000000..7c2e6752228fac175c4aa97fa3d817b8 +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkLoadTask.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkLoadTask.java new file mode 100644 -index 0000000000000000000000000000000000000000..1ab93f219246d0b4dcdfd0f685f47c13091425f8 +index 0000000000000000000000000000000000000000..e0a88615a8b6d58191f29b1ff1a26427f0a4c1a6 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkLoadTask.java -@@ -0,0 +1,487 @@ +@@ -0,0 +1,494 @@ +package ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task; + +import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; ++import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor; +import ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock; +import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; ++import ca.spottedleaf.concurrentutil.util.Priority; ++import ca.spottedleaf.moonrise.common.PlatformHooks; +import ca.spottedleaf.moonrise.patches.chunk_system.ChunkSystemConverters; -+import ca.spottedleaf.moonrise.patches.chunk_system.ChunkSystemFeatures; -+import ca.spottedleaf.moonrise.patches.chunk_system.io.RegionFileIOThread; ++import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO; +import ca.spottedleaf.moonrise.patches.chunk_system.level.poi.PoiChunk; +import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler; +import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder; @@ -13259,7 +14078,7 @@ index 0000000000000000000000000000000000000000..1ab93f219246d0b4dcdfd0f685f47c13 +import net.minecraft.world.level.chunk.ProtoChunk; +import net.minecraft.world.level.chunk.UpgradeData; +import net.minecraft.world.level.chunk.status.ChunkStatus; -+import net.minecraft.world.level.chunk.storage.ChunkSerializer; ++import net.minecraft.world.level.chunk.storage.SerializableChunkData; +import net.minecraft.world.level.levelgen.blending.BlendingData; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; @@ -13282,7 +14101,7 @@ index 0000000000000000000000000000000000000000..1ab93f219246d0b4dcdfd0f685f47c13 + private final AtomicInteger taskCountToComplete = new AtomicInteger(3); // one for poi, one for entity, and one for chunk data + + public ChunkLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, final int chunkZ, -+ final NewChunkHolder chunkHolder, final PrioritisedExecutor.Priority priority) { ++ final NewChunkHolder chunkHolder, final Priority priority) { + super(scheduler, world, chunkX, chunkZ); + this.chunkHolder = chunkHolder; + this.loadTask = new ChunkDataLoadTask(scheduler, world, chunkX, chunkZ, priority); @@ -13411,12 +14230,12 @@ index 0000000000000000000000000000000000000000..1ab93f219246d0b4dcdfd0f685f47c13 + } + + @Override -+ public PrioritisedExecutor.Priority getPriority() { ++ public Priority getPriority() { + return this.loadTask.getPriority(); + } + + @Override -+ public void lowerPriority(final PrioritisedExecutor.Priority priority) { ++ public void lowerPriority(final Priority priority) { + final EntityDataLoadTask entityLoad = this.chunkHolder.getEntityDataLoadTask(); + if (entityLoad != null) { + entityLoad.lowerPriority(priority); @@ -13432,7 +14251,7 @@ index 0000000000000000000000000000000000000000..1ab93f219246d0b4dcdfd0f685f47c13 + } + + @Override -+ public void setPriority(final PrioritisedExecutor.Priority priority) { ++ public void setPriority(final Priority priority) { + final EntityDataLoadTask entityLoad = this.chunkHolder.getEntityDataLoadTask(); + if (entityLoad != null) { + entityLoad.setPriority(priority); @@ -13448,7 +14267,7 @@ index 0000000000000000000000000000000000000000..1ab93f219246d0b4dcdfd0f685f47c13 + } + + @Override -+ public void raisePriority(final PrioritisedExecutor.Priority priority) { ++ public void raisePriority(final Priority priority) { + final EntityDataLoadTask entityLoad = this.chunkHolder.getEntityDataLoadTask(); + if (entityLoad != null) { + entityLoad.raisePriority(priority); @@ -13472,8 +14291,8 @@ index 0000000000000000000000000000000000000000..1ab93f219246d0b4dcdfd0f685f47c13 + protected static final VarHandle COMPLETED_HANDLE = ConcurrentUtil.getVarHandle(CallbackDataLoadTask.class, "completed", boolean.class); + + protected CallbackDataLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, -+ final int chunkZ, final RegionFileIOThread.RegionFileType type, -+ final PrioritisedExecutor.Priority priority) { ++ final int chunkZ, final MoonriseRegionFileIO.RegionFileType type, ++ final Priority priority) { + super(scheduler, world, chunkX, chunkZ, type, priority); + } + @@ -13513,10 +14332,13 @@ index 0000000000000000000000000000000000000000..1ab93f219246d0b4dcdfd0f685f47c13 + } + } + -+ private static final class ChunkDataLoadTask extends CallbackDataLoadTask { ++ ++ private static record ReadChunk(ProtoChunk protoChunk, SerializableChunkData chunkData) {} ++ ++ private static final class ChunkDataLoadTask extends CallbackDataLoadTask { + private ChunkDataLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, -+ final int chunkZ, final PrioritisedExecutor.Priority priority) { -+ super(scheduler, world, chunkX, chunkZ, RegionFileIOThread.RegionFileType.CHUNK_DATA, priority); ++ final int chunkZ, final Priority priority) { ++ super(scheduler, world, chunkX, chunkZ, MoonriseRegionFileIO.RegionFileType.CHUNK_DATA, priority); + } + + @Override @@ -13530,40 +14352,42 @@ index 0000000000000000000000000000000000000000..1ab93f219246d0b4dcdfd0f685f47c13 + } + + @Override -+ protected PrioritisedExecutor.PrioritisedTask createOffMain(final Runnable run, final PrioritisedExecutor.Priority priority) { ++ protected PrioritisedExecutor.PrioritisedTask createOffMain(final Runnable run, final Priority priority) { + return this.scheduler.loadExecutor.createTask(run, priority); + } + + @Override -+ protected PrioritisedExecutor.PrioritisedTask createOnMain(final Runnable run, final PrioritisedExecutor.Priority priority) { ++ protected PrioritisedExecutor.PrioritisedTask createOnMain(final Runnable run, final Priority priority) { + return this.scheduler.createChunkTask(this.chunkX, this.chunkZ, run, priority); + } + + @Override -+ protected TaskResult completeOnMainOffMain(final CompoundTag data, final Throwable throwable) { ++ protected TaskResult completeOnMainOffMain(final ReadChunk data, final Throwable throwable) { + if (throwable != null) { + return new TaskResult<>(null, throwable); + } -+ if (data == null) { ++ ++ if (data == null || data.protoChunk() == null) { + return new TaskResult<>(this.getEmptyChunk(), null); + } + -+ if (ChunkSystemFeatures.supportsAsyncChunkDeserialization()) { -+ return this.deserialize(data); ++ if (!PlatformHooks.get().hasMainChunkLoadHook()) { ++ return new TaskResult<>(data.protoChunk(), null); + } -+ // need to deserialize on main thread ++ ++ // need to invoke the callback for loading on the main thread + return null; + } + + private ProtoChunk getEmptyChunk() { + return new ProtoChunk( + new ChunkPos(this.chunkX, this.chunkZ), UpgradeData.EMPTY, this.world, -+ this.world.registryAccess().registryOrThrow(Registries.BIOME), (BlendingData)null ++ this.world.registryAccess().lookupOrThrow(Registries.BIOME), (BlendingData)null + ); + } + + @Override -+ protected TaskResult runOffMain(final CompoundTag data, final Throwable throwable) { ++ protected TaskResult runOffMain(final CompoundTag data, final Throwable throwable) { + if (throwable != null) { + LOGGER.error("Failed to load chunk data for task: " + this.toString() + ", chunk data will be lost", throwable); + return new TaskResult<>(null, null); @@ -13575,42 +14399,43 @@ index 0000000000000000000000000000000000000000..1ab93f219246d0b4dcdfd0f685f47c13 + + try { + // run converters -+ final CompoundTag converted = this.world.getChunkSource().chunkMap.upgradeChunkTag(data, new net.minecraft.world.level.ChunkPos(this.chunkX, this.chunkZ)); ++ final CompoundTag converted = this.world.getChunkSource().chunkMap.upgradeChunkTag(data); + -+ return new TaskResult<>(converted, null); ++ // unpack the data ++ final SerializableChunkData chunkData = SerializableChunkData.parse( ++ this.world, this.world.registryAccess(), converted ++ ); ++ ++ if (chunkData == null) { ++ LOGGER.error("Deserialized chunk for task: " + this.toString() + " produced null, chunk data will be lost?"); ++ } ++ ++ // read into ProtoChunk ++ final ProtoChunk chunk = chunkData == null ? null : chunkData.read( ++ this.world, this.world.getPoiManager(), this.world.getChunkSource().chunkMap.storageInfo(), ++ new ChunkPos(this.chunkX, this.chunkZ) ++ ); ++ ++ return new TaskResult<>(new ReadChunk(chunk, chunkData), null); + } catch (final Throwable thr2) { + LOGGER.error("Failed to parse chunk data for task: " + this.toString() + ", chunk data will be lost", thr2); + return new TaskResult<>(null, null); + } + } + -+ private TaskResult deserialize(final CompoundTag data) { -+ try { -+ final ChunkAccess deserialized = ChunkSerializer.read( -+ this.world, this.world.getPoiManager(), this.world.getChunkSource().chunkMap.storageInfo(), new ChunkPos(this.chunkX, this.chunkZ), data -+ ); -+ return new TaskResult<>(deserialized, null); -+ } catch (final Throwable thr2) { -+ LOGGER.error("Failed to parse chunk data for task: " + this.toString() + ", chunk data will be lost", thr2); -+ return new TaskResult<>(this.getEmptyChunk(), null); -+ } -+ } -+ + @Override -+ protected TaskResult runOnMain(final CompoundTag data, final Throwable throwable) { -+ // data != null && throwable == null -+ if (ChunkSystemFeatures.supportsAsyncChunkDeserialization()) { -+ throw new UnsupportedOperationException(); -+ } -+ return this.deserialize(data); ++ protected TaskResult runOnMain(final ReadChunk data, final Throwable throwable) { ++ PlatformHooks.get().mainChunkLoad(data.protoChunk(), data.chunkData()); ++ ++ return new TaskResult<>(data.protoChunk(), null); + } + } + + public static final class PoiDataLoadTask extends CallbackDataLoadTask { + + public PoiDataLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, -+ final int chunkZ, final PrioritisedExecutor.Priority priority) { -+ super(scheduler, world, chunkX, chunkZ, RegionFileIOThread.RegionFileType.POI_DATA, priority); ++ final int chunkZ, final Priority priority) { ++ super(scheduler, world, chunkX, chunkZ, MoonriseRegionFileIO.RegionFileType.POI_DATA, priority); + } + + @Override @@ -13624,12 +14449,12 @@ index 0000000000000000000000000000000000000000..1ab93f219246d0b4dcdfd0f685f47c13 + } + + @Override -+ protected PrioritisedExecutor.PrioritisedTask createOffMain(final Runnable run, final PrioritisedExecutor.Priority priority) { ++ protected PrioritisedExecutor.PrioritisedTask createOffMain(final Runnable run, final Priority priority) { + return this.scheduler.loadExecutor.createTask(run, priority); + } + + @Override -+ protected PrioritisedExecutor.PrioritisedTask createOnMain(final Runnable run, final PrioritisedExecutor.Priority priority) { ++ protected PrioritisedExecutor.PrioritisedTask createOnMain(final Runnable run, final Priority priority) { + throw new UnsupportedOperationException(); + } + @@ -13671,8 +14496,8 @@ index 0000000000000000000000000000000000000000..1ab93f219246d0b4dcdfd0f685f47c13 + public static final class EntityDataLoadTask extends CallbackDataLoadTask { + + public EntityDataLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, -+ final int chunkZ, final PrioritisedExecutor.Priority priority) { -+ super(scheduler, world, chunkX, chunkZ, RegionFileIOThread.RegionFileType.ENTITY_DATA, priority); ++ final int chunkZ, final Priority priority) { ++ super(scheduler, world, chunkX, chunkZ, MoonriseRegionFileIO.RegionFileType.ENTITY_DATA, priority); + } + + @Override @@ -13686,12 +14511,12 @@ index 0000000000000000000000000000000000000000..1ab93f219246d0b4dcdfd0f685f47c13 + } + + @Override -+ protected PrioritisedExecutor.PrioritisedTask createOffMain(final Runnable run, final PrioritisedExecutor.Priority priority) { ++ protected PrioritisedExecutor.PrioritisedTask createOffMain(final Runnable run, final Priority priority) { + return this.scheduler.loadExecutor.createTask(run, priority); + } + + @Override -+ protected PrioritisedExecutor.PrioritisedTask createOnMain(final Runnable run, final PrioritisedExecutor.Priority priority) { ++ protected PrioritisedExecutor.PrioritisedTask createOnMain(final Runnable run, final Priority priority) { + throw new UnsupportedOperationException(); + } + @@ -13728,15 +14553,15 @@ index 0000000000000000000000000000000000000000..1ab93f219246d0b4dcdfd0f685f47c13 +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkProgressionTask.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkProgressionTask.java new file mode 100644 -index 0000000000000000000000000000000000000000..70e900b0f9c131900bf8b3f3ecbfbd5df5361205 +index 0000000000000000000000000000000000000000..002ee365aa70d8e6a6e6bd5c95988bd17db4395a --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkProgressionTask.java @@ -0,0 +1,101 @@ +package ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task; + +import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; +import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; ++import ca.spottedleaf.concurrentutil.util.Priority; +import ca.spottedleaf.moonrise.common.util.WorldUtil; +import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler; +import net.minecraft.server.level.ServerLevel; @@ -13780,15 +14605,15 @@ index 0000000000000000000000000000000000000000..70e900b0f9c131900bf8b3f3ecbfbd5d + /* May be called multiple times */ + public abstract void cancel(); + -+ public abstract PrioritisedExecutor.Priority getPriority(); ++ public abstract Priority getPriority(); + + /* Schedule lock is always held for the priority update calls */ + -+ public abstract void lowerPriority(final PrioritisedExecutor.Priority priority); ++ public abstract void lowerPriority(final Priority priority); + -+ public abstract void setPriority(final PrioritisedExecutor.Priority priority); ++ public abstract void setPriority(final Priority priority); + -+ public abstract void raisePriority(final PrioritisedExecutor.Priority priority); ++ public abstract void raisePriority(final Priority priority); + + public final void onComplete(final BiConsumer onComplete) { + if (!this.waiters.add(onComplete)) { @@ -13836,14 +14661,15 @@ index 0000000000000000000000000000000000000000..70e900b0f9c131900bf8b3f3ecbfbd5d \ No newline at end of file diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkUpgradeGenericStatusTask.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkUpgradeGenericStatusTask.java new file mode 100644 -index 0000000000000000000000000000000000000000..2c17d5589f15f1155be08be670d29acbe954a8fa +index 0000000000000000000000000000000000000000..25d8da4773dcee5096053e7e3788bfc224d705a7 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkUpgradeGenericStatusTask.java -@@ -0,0 +1,217 @@ +@@ -0,0 +1,218 @@ +package ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task; + -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; ++import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor; +import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; ++import ca.spottedleaf.concurrentutil.util.Priority; +import ca.spottedleaf.moonrise.common.util.WorldUtil; +import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemChunkStatus; +import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler; @@ -13878,9 +14704,9 @@ index 0000000000000000000000000000000000000000..2c17d5589f15f1155be08be670d29acb + + public ChunkUpgradeGenericStatusTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, + final int chunkZ, final ChunkAccess chunk, final StaticCache2D neighbours, -+ final ChunkStatus toStatus, final PrioritisedExecutor.Priority priority) { ++ final ChunkStatus toStatus, final Priority priority) { + super(scheduler, world, chunkX, chunkZ); -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { ++ if (!Priority.isValidPriority(priority)) { + throw new IllegalArgumentException("Invalid priority " + priority); + } + this.fromChunk = chunk; @@ -14029,29 +14855,29 @@ index 0000000000000000000000000000000000000000..2c17d5589f15f1155be08be670d29acb + } + + @Override -+ public PrioritisedExecutor.Priority getPriority() { ++ public Priority getPriority() { + return this.generateTask.getPriority(); + } + + @Override -+ public void lowerPriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { ++ public void lowerPriority(final Priority priority) { ++ if (!Priority.isValidPriority(priority)) { + throw new IllegalArgumentException("Invalid priority " + priority); + } + this.generateTask.lowerPriority(priority); + } + + @Override -+ public void setPriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { ++ public void setPriority(final Priority priority) { ++ if (!Priority.isValidPriority(priority)) { + throw new IllegalArgumentException("Invalid priority " + priority); + } + this.generateTask.setPriority(priority); + } + + @Override -+ public void raisePriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { ++ public void raisePriority(final Priority priority) { ++ if (!Priority.isValidPriority(priority)) { + throw new IllegalArgumentException("Invalid priority " + priority); + } + this.generateTask.raisePriority(priority); @@ -14059,19 +14885,20 @@ index 0000000000000000000000000000000000000000..2c17d5589f15f1155be08be670d29acb +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/GenericDataLoadTask.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/GenericDataLoadTask.java new file mode 100644 -index 0000000000000000000000000000000000000000..7a65d351b448873c6f2c145c975c92be314b876c +index 0000000000000000000000000000000000000000..bdcd1879457bafcca4e76523aac0555968f37c0b --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/GenericDataLoadTask.java -@@ -0,0 +1,673 @@ +@@ -0,0 +1,674 @@ +package ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task; + ++import ca.spottedleaf.concurrentutil.completable.CallbackCompletable; +import ca.spottedleaf.concurrentutil.completable.Completable; +import ca.spottedleaf.concurrentutil.executor.Cancellable; -+import ca.spottedleaf.concurrentutil.executor.standard.DelayedPrioritisedTask; -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; ++import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor; +import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; ++import ca.spottedleaf.concurrentutil.util.Priority; +import ca.spottedleaf.moonrise.common.util.WorldUtil; -+import ca.spottedleaf.moonrise.patches.chunk_system.io.RegionFileIOThread; ++import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO; +import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel; +import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler; +import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder; @@ -14112,11 +14939,11 @@ index 0000000000000000000000000000000000000000..7a65d351b448873c6f2c145c975c92be + protected final ServerLevel world; + protected final int chunkX; + protected final int chunkZ; -+ protected final RegionFileIOThread.RegionFileType type; ++ protected final MoonriseRegionFileIO.RegionFileType type; + + public GenericDataLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, -+ final int chunkZ, final RegionFileIOThread.RegionFileType type, -+ final PrioritisedExecutor.Priority priority) { ++ final int chunkZ, final MoonriseRegionFileIO.RegionFileType type, ++ final Priority priority) { + this.scheduler = scheduler; + this.world = world; + this.chunkX = chunkX; @@ -14154,9 +14981,9 @@ index 0000000000000000000000000000000000000000..7a65d351b448873c6f2c145c975c92be + + protected abstract boolean hasOnMain(); + -+ protected abstract PrioritisedExecutor.PrioritisedTask createOffMain(final Runnable run, final PrioritisedExecutor.Priority priority); ++ protected abstract PrioritisedExecutor.PrioritisedTask createOffMain(final Runnable run, final Priority priority); + -+ protected abstract PrioritisedExecutor.PrioritisedTask createOnMain(final Runnable run, final PrioritisedExecutor.Priority priority); ++ protected abstract PrioritisedExecutor.PrioritisedTask createOnMain(final Runnable run, final Priority priority); + + protected abstract TaskResult runOffMain(final CompoundTag data, final Throwable throwable); + @@ -14173,7 +15000,7 @@ index 0000000000000000000000000000000000000000..7a65d351b448873c6f2c145c975c92be + ", type: " + this.type.toString() + "}"; + } + -+ public PrioritisedExecutor.Priority getPriority() { ++ public Priority getPriority() { + if (this.processOnMain != null) { + return this.processOnMain.getPriority(); + } else { @@ -14181,7 +15008,7 @@ index 0000000000000000000000000000000000000000..7a65d351b448873c6f2c145c975c92be + } + } + -+ public void lowerPriority(final PrioritisedExecutor.Priority priority) { ++ public void lowerPriority(final Priority priority) { + // can't lower I/O tasks, we don't know what they affect + if (this.processOffMain != null) { + this.processOffMain.lowerPriority(priority); @@ -14191,7 +15018,7 @@ index 0000000000000000000000000000000000000000..7a65d351b448873c6f2c145c975c92be + } + } + -+ public void setPriority(final PrioritisedExecutor.Priority priority) { ++ public void setPriority(final Priority priority) { + // can't lower I/O tasks, we don't know what they affect + this.loadDataFromDiskTask.raisePriority(priority); + if (this.processOffMain != null) { @@ -14202,7 +15029,7 @@ index 0000000000000000000000000000000000000000..7a65d351b448873c6f2c145c975c92be + } + } + -+ public void raisePriority(final PrioritisedExecutor.Priority priority) { ++ public void raisePriority(final Priority priority) { + // can't lower I/O tasks, we don't know what they affect + this.loadDataFromDiskTask.raisePriority(priority); + if (this.processOffMain != null) { @@ -14447,10 +15274,10 @@ index 0000000000000000000000000000000000000000..7a65d351b448873c6f2c145c975c92be + private final int chunkX; + private final int chunkZ; + -+ private final RegionFileIOThread.RegionFileType type; ++ private final MoonriseRegionFileIO.RegionFileType type; + private Cancellable dataLoadTask; + private Cancellable dataUnloadCancellable; -+ private DelayedPrioritisedTask dataUnloadTask; ++ private PrioritisedExecutor.PrioritisedTask dataUnloadTask; + + private final BiConsumer onComplete; + private final AtomicBoolean scheduled = new AtomicBoolean(); @@ -14458,10 +15285,10 @@ index 0000000000000000000000000000000000000000..7a65d351b448873c6f2c145c975c92be + // onComplete should be caller sensitive, it may complete synchronously with schedule() - which does + // hold a priority lock. + public LoadDataFromDiskTask(final ServerLevel world, final int chunkX, final int chunkZ, -+ final RegionFileIOThread.RegionFileType type, ++ final MoonriseRegionFileIO.RegionFileType type, + final BiConsumer onComplete, -+ final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { ++ final Priority priority) { ++ if (!Priority.isValidPriority(priority)) { + throw new IllegalArgumentException("Invalid priority " + priority); + } + this.world = world; @@ -14491,8 +15318,8 @@ index 0000000000000000000000000000000000000000..7a65d351b448873c6f2c145c975c92be + return (this.getPriorityVolatile() & PRIORITY_EXECUTED) != 0; + } + -+ public void lowerPriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { ++ public void lowerPriority(final Priority priority) { ++ if (!Priority.isValidPriority(priority)) { + throw new IllegalArgumentException("Invalid priority " + priority); + } + @@ -14504,7 +15331,7 @@ index 0000000000000000000000000000000000000000..7a65d351b448873c6f2c145c975c92be + } + + if ((curr & PRIORITY_LOAD_SCHEDULED) != 0) { -+ RegionFileIOThread.lowerPriority(this.world, this.chunkX, this.chunkZ, this.type, priority); ++ MoonriseRegionFileIO.lowerPriority(this.world, this.chunkX, this.chunkZ, this.type, priority); + return; + } + @@ -14532,8 +15359,8 @@ index 0000000000000000000000000000000000000000..7a65d351b448873c6f2c145c975c92be + } + } + -+ public void setPriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { ++ public void setPriority(final Priority priority) { ++ if (!Priority.isValidPriority(priority)) { + throw new IllegalArgumentException("Invalid priority " + priority); + } + @@ -14545,7 +15372,7 @@ index 0000000000000000000000000000000000000000..7a65d351b448873c6f2c145c975c92be + } + + if ((curr & PRIORITY_LOAD_SCHEDULED) != 0) { -+ RegionFileIOThread.setPriority(this.world, this.chunkX, this.chunkZ, this.type, priority); ++ MoonriseRegionFileIO.setPriority(this.world, this.chunkX, this.chunkZ, this.type, priority); + return; + } + @@ -14569,8 +15396,8 @@ index 0000000000000000000000000000000000000000..7a65d351b448873c6f2c145c975c92be + } + } + -+ public void raisePriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { ++ public void raisePriority(final Priority priority) { ++ if (!Priority.isValidPriority(priority)) { + throw new IllegalArgumentException("Invalid priority " + priority); + } + @@ -14582,7 +15409,7 @@ index 0000000000000000000000000000000000000000..7a65d351b448873c6f2c145c975c92be + } + + if ((curr & PRIORITY_LOAD_SCHEDULED) != 0) { -+ RegionFileIOThread.raisePriority(this.world, this.chunkX, this.chunkZ, this.type, priority); ++ MoonriseRegionFileIO.raisePriority(this.world, this.chunkX, this.chunkZ, this.type, priority); + return; + } + @@ -14648,7 +15475,7 @@ index 0000000000000000000000000000000000000000..7a65d351b448873c6f2c145c975c92be + } // else: cancelled + }; + -+ final PrioritisedExecutor.Priority initialPriority = PrioritisedExecutor.Priority.getPriority(priority); ++ final Priority initialPriority = Priority.getPriority(priority); + boolean scheduledUnload = false; + + final NewChunkHolder holder = ((ChunkSystemServerLevel)this.world).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(this.chunkX, this.chunkZ); @@ -14658,13 +15485,13 @@ index 0000000000000000000000000000000000000000..7a65d351b448873c6f2c145c975c92be + consumer.accept(data, null); + } else { + // need to schedule task -+ LoadDataFromDiskTask.this.schedule(false, consumer, PrioritisedExecutor.Priority.getPriority(LoadDataFromDiskTask.this.getPriorityVolatile() & ~PRIORITY_FLAGS)); ++ LoadDataFromDiskTask.this.schedule(false, consumer, Priority.getPriority(LoadDataFromDiskTask.this.getPriorityVolatile() & ~PRIORITY_FLAGS)); + } + }; + Cancellable unloadCancellable = null; + CompoundTag syncComplete = null; + final NewChunkHolder.UnloadTask unloadTask = holder.getUnloadTask(this.type); // can be null if no task exists -+ final Completable unloadCompletable = unloadTask == null ? null : unloadTask.completable(); ++ final CallbackCompletable unloadCompletable = unloadTask == null ? null : unloadTask.completable(); + if (unloadCompletable != null) { + unloadCancellable = unloadCompletable.addAsynchronousWaiter(unloadConsumer); + if (unloadCancellable == null) { @@ -14687,7 +15514,7 @@ index 0000000000000000000000000000000000000000..7a65d351b448873c6f2c145c975c92be + this.schedule(scheduledUnload, consumer, initialPriority); + } + -+ private void schedule(final boolean scheduledUnload, final BiConsumer consumer, final PrioritisedExecutor.Priority initialPriority) { ++ private void schedule(final boolean scheduledUnload, final BiConsumer consumer, final Priority initialPriority) { + int priority = this.getPriorityVolatile(); + + if ((priority & PRIORITY_EXECUTED) != 0) { @@ -14696,9 +15523,9 @@ index 0000000000000000000000000000000000000000..7a65d351b448873c6f2c145c975c92be + } + + if (!scheduledUnload) { -+ this.dataLoadTask = RegionFileIOThread.loadDataAsync( ++ this.dataLoadTask = MoonriseRegionFileIO.loadDataAsync( + this.world, this.chunkX, this.chunkZ, this.type, consumer, -+ initialPriority.isHigherPriority(PrioritisedExecutor.Priority.NORMAL), initialPriority ++ initialPriority.isHigherPriority(Priority.NORMAL), initialPriority + ); + } + @@ -14722,10 +15549,10 @@ index 0000000000000000000000000000000000000000..7a65d351b448873c6f2c145c975c92be + + if (scheduledUnload) { + if (this.dataUnloadTask != null) { -+ this.dataUnloadTask.setPriority(PrioritisedExecutor.Priority.getPriority(priority & ~PRIORITY_FLAGS)); ++ this.dataUnloadTask.setPriority(Priority.getPriority(priority & ~PRIORITY_FLAGS)); + } + } else { -+ RegionFileIOThread.setPriority(this.world, this.chunkX, this.chunkZ, this.type, PrioritisedExecutor.Priority.getPriority(priority & ~PRIORITY_FLAGS)); ++ MoonriseRegionFileIO.setPriority(this.world, this.chunkX, this.chunkZ, this.type, Priority.getPriority(priority & ~PRIORITY_FLAGS)); + } + + ++failures; @@ -14766,6 +15593,24 @@ index 0000000000000000000000000000000000000000..ea759ce6f10f2a5a4e107ab7528030fe + public ChunkStatus moonrise$getRequiredStatusAtRadius(final int radius); + +} +diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java +new file mode 100644 +index 0000000000000000000000000000000000000000..51c126735ace8fdde89ad97b5cab62f244212db0 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java +@@ -0,0 +1,12 @@ ++package ca.spottedleaf.moonrise.patches.chunk_system.storage; ++ ++import net.minecraft.world.level.chunk.storage.RegionFile; ++import java.io.IOException; ++ ++public interface ChunkSystemChunkBuffer { ++ public boolean moonrise$getWriteOnClose(); ++ ++ public void moonrise$setWriteOnClose(final boolean value); ++ ++ public void moonrise$write(final RegionFile regionFile) throws IOException; ++} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkStorage.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkStorage.java new file mode 100644 index 0000000000000000000000000000000000000000..129a35ff2db5b3bb6736810fc180796ce55e1875 @@ -14781,6 +15626,24 @@ index 0000000000000000000000000000000000000000..129a35ff2db5b3bb6736810fc180796c + public RegionFileStorage moonrise$getRegionStorage(); + +} +diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemRegionFile.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemRegionFile.java +new file mode 100644 +index 0000000000000000000000000000000000000000..3bd1b59250dbab15097a64d515999b278636795a +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemRegionFile.java +@@ -0,0 +1,12 @@ ++package ca.spottedleaf.moonrise.patches.chunk_system.storage; ++ ++import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.world.level.ChunkPos; ++import java.io.IOException; ++ ++public interface ChunkSystemRegionFile { ++ ++ public MoonriseRegionFileIO.RegionDataController.WriteData moonrise$startWrite(final CompoundTag data, final ChunkPos pos) throws IOException; ++ ++} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ticket/ChunkSystemTicket.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ticket/ChunkSystemTicket.java new file mode 100644 index 0000000000000000000000000000000000000000..786e6ad17cd6216ef0aadaa7cf10044a0c19c933 @@ -14834,13 +15697,14 @@ index 0000000000000000000000000000000000000000..ce3bb903c9ccb7efa0f004cf79b291dc +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/util/ParallelSearchRadiusIteration.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/util/ParallelSearchRadiusIteration.java new file mode 100644 -index 0000000000000000000000000000000000000000..3a9a564edfdb99e006e4816cb8821bd1e9ecff43 +index 0000000000000000000000000000000000000000..93fd23027c00cef76562098306737272fda1350a --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/util/ParallelSearchRadiusIteration.java -@@ -0,0 +1,320 @@ +@@ -0,0 +1,321 @@ +package ca.spottedleaf.moonrise.patches.chunk_system.util; + +import ca.spottedleaf.moonrise.common.util.CoordinateUtils; ++import ca.spottedleaf.moonrise.common.util.MoonriseConstants; +import it.unimi.dsi.fastutil.HashCommon; +import it.unimi.dsi.fastutil.longs.LongArrayList; +import it.unimi.dsi.fastutil.longs.LongIterator; @@ -14853,7 +15717,7 @@ index 0000000000000000000000000000000000000000..3a9a564edfdb99e006e4816cb8821bd1 + + // expected that this list returns for a given radius, the set of chunks ordered + // by manhattan distance -+ private static final long[][] SEARCH_RADIUS_ITERATION_LIST = new long[64+2+1][]; ++ private static final long[][] SEARCH_RADIUS_ITERATION_LIST = new long[MoonriseConstants.MAX_VIEW_DISTANCE+2+1][]; + static { + for (int i = 0; i < SEARCH_RADIUS_ITERATION_LIST.length; ++i) { + // a BFS around -x, -z, +x, +z will give increasing manhatten distance @@ -15158,6 +16022,49 @@ index 0000000000000000000000000000000000000000..3a9a564edfdb99e006e4816cb8821bd1 + return ret.elements(); + } +} +diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/util/stream/ExternalChunkStreamMarker.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/util/stream/ExternalChunkStreamMarker.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7ef3dcca89ed7578c6c0f5565131889110063056 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/util/stream/ExternalChunkStreamMarker.java +@@ -0,0 +1,37 @@ ++package ca.spottedleaf.moonrise.patches.chunk_system.util.stream; ++ ++import java.io.DataInputStream; ++import java.io.FilterInputStream; ++import java.io.InputStream; ++import java.lang.reflect.Field; ++ ++/** ++ * Used to mark chunk data streams that are on external files ++ */ ++public class ExternalChunkStreamMarker extends DataInputStream { ++ ++ private static final Field IN_FIELD; ++ static { ++ Field field; ++ try { ++ field = FilterInputStream.class.getDeclaredField("in"); ++ field.setAccessible(true); ++ } catch (final Throwable throwable) { ++ field = null; ++ } ++ ++ IN_FIELD = field; ++ } ++ ++ private static InputStream getWrapped(final FilterInputStream in) { ++ try { ++ return (InputStream)IN_FIELD.get(in); ++ } catch (final Throwable throwable) { ++ return in; ++ } ++ } ++ ++ public ExternalChunkStreamMarker(final DataInputStream in) { ++ super(getWrapped(in)); ++ } ++} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/world/ChunkSystemEntityGetter.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/world/ChunkSystemEntityGetter.java new file mode 100644 index 0000000000000000000000000000000000000000..ea6b6ed27b212719feb31610faac974899688839 @@ -15230,22 +16137,65 @@ index 0000000000000000000000000000000000000000..f28fd0e01e2bdda0daf9d775e514a725 +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java new file mode 100644 -index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a27385126 +index 0000000000000000000000000000000000000000..3abd4ad6379c383c3a31931255292b42d9435694 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java -@@ -0,0 +1,1853 @@ +@@ -0,0 +1,2189 @@ +package ca.spottedleaf.moonrise.patches.collisions; + ++import ca.spottedleaf.moonrise.common.util.WorldUtil; ++import ca.spottedleaf.moonrise.patches.chunk_system.world.ChunkSystemEntityGetter; ++import ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState; ++import ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity; ++import ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData; ++import ca.spottedleaf.moonrise.patches.collisions.shape.CollisionDiscreteVoxelShape; ++import ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape; ++import ca.spottedleaf.moonrise.patches.block_counting.BlockCountingChunkSection; ++import it.unimi.dsi.fastutil.doubles.DoubleArrayList; ++import it.unimi.dsi.fastutil.doubles.DoubleList; ++import net.minecraft.core.BlockPos; ++import net.minecraft.core.Direction; ++import net.minecraft.util.Mth; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.item.Item; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.border.WorldBorder; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.ChunkSource; ++import net.minecraft.world.level.chunk.LevelChunkSection; ++import net.minecraft.world.level.chunk.PalettedContainer; ++import net.minecraft.world.level.chunk.status.ChunkStatus; ++import net.minecraft.world.level.material.FluidState; ++import net.minecraft.world.phys.AABB; ++import net.minecraft.world.phys.Vec3; ++import net.minecraft.world.phys.shapes.ArrayVoxelShape; ++import net.minecraft.world.phys.shapes.BitSetDiscreteVoxelShape; ++import net.minecraft.world.phys.shapes.BooleanOp; ++import net.minecraft.world.phys.shapes.CollisionContext; ++import net.minecraft.world.phys.shapes.DiscreteVoxelShape; ++import net.minecraft.world.phys.shapes.EntityCollisionContext; ++import net.minecraft.world.phys.shapes.OffsetDoubleList; ++import net.minecraft.world.phys.shapes.Shapes; ++import net.minecraft.world.phys.shapes.SliceShape; ++import net.minecraft.world.phys.shapes.VoxelShape; ++import java.util.Arrays; ++import java.util.List; ++import java.util.Objects; ++import java.util.function.BiPredicate; ++import java.util.function.Predicate; ++ +public final class CollisionUtil { + + public static final double COLLISION_EPSILON = 1.0E-7; -+ public static final it.unimi.dsi.fastutil.doubles.DoubleArrayList ZERO_ONE = it.unimi.dsi.fastutil.doubles.DoubleArrayList.wrap(new double[] { 0.0, 1.0 }); ++ public static final DoubleArrayList ZERO_ONE = DoubleArrayList.wrap(new double[] { 0.0, 1.0 }); + + public static boolean isSpecialCollidingBlock(final net.minecraft.world.level.block.state.BlockBehaviour.BlockStateBase block) { -+ return block.hasLargeCollisionShape() || block.getBlock() == net.minecraft.world.level.block.Blocks.MOVING_PISTON; ++ return block.hasLargeCollisionShape() || block.getBlock() == Blocks.MOVING_PISTON; + } + -+ public static boolean isEmpty(final net.minecraft.world.phys.AABB aabb) { ++ public static boolean isEmpty(final AABB aabb) { + return (aabb.maxX - aabb.minX) < COLLISION_EPSILON || (aabb.maxY - aabb.minY) < COLLISION_EPSILON || (aabb.maxZ - aabb.minZ) < COLLISION_EPSILON; + } + @@ -15254,11 +16204,11 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + return (maxX - minX) < COLLISION_EPSILON || (maxY - minY) < COLLISION_EPSILON || (maxZ - minZ) < COLLISION_EPSILON; + } + -+ public static net.minecraft.world.phys.AABB getBoxForChunk(final int chunkX, final int chunkZ) { ++ public static AABB getBoxForChunk(final int chunkX, final int chunkZ) { + double x = (double)(chunkX << 4); + double z = (double)(chunkZ << 4); + // use a bounding box bigger than the chunk to prevent entities from entering it on move -+ return new net.minecraft.world.phys.AABB(x - 3*COLLISION_EPSILON, Double.NEGATIVE_INFINITY, z - 3*COLLISION_EPSILON, ++ return new AABB(x - 3*COLLISION_EPSILON, Double.NEGATIVE_INFINITY, z - 3*COLLISION_EPSILON, + x + (16.0 + 3*COLLISION_EPSILON), Double.POSITIVE_INFINITY, z + (16.0 + 3*COLLISION_EPSILON)); + } + @@ -15279,21 +16229,21 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + (minZ1 - maxZ2) < -COLLISION_EPSILON && (maxZ1 - minZ2) > COLLISION_EPSILON; + } + -+ public static boolean voxelShapeIntersect(final net.minecraft.world.phys.AABB box, final double minX, final double minY, final double minZ, ++ public static boolean voxelShapeIntersect(final AABB box, final double minX, final double minY, final double minZ, + final double maxX, final double maxY, final double maxZ) { + return (box.minX - maxX) < -COLLISION_EPSILON && (box.maxX - minX) > COLLISION_EPSILON && + (box.minY - maxY) < -COLLISION_EPSILON && (box.maxY - minY) > COLLISION_EPSILON && + (box.minZ - maxZ) < -COLLISION_EPSILON && (box.maxZ - minZ) > COLLISION_EPSILON; + } + -+ public static boolean voxelShapeIntersect(final net.minecraft.world.phys.AABB box1, final net.minecraft.world.phys.AABB box2) { ++ public static boolean voxelShapeIntersect(final AABB box1, final AABB box2) { + return (box1.minX - box2.maxX) < -COLLISION_EPSILON && (box1.maxX - box2.minX) > COLLISION_EPSILON && + (box1.minY - box2.maxY) < -COLLISION_EPSILON && (box1.maxY - box2.minY) > COLLISION_EPSILON && + (box1.minZ - box2.maxZ) < -COLLISION_EPSILON && (box1.maxZ - box2.minZ) > COLLISION_EPSILON; + } + + // assume !isEmpty(target) && abs(source_move) >= COLLISION_EPSILON -+ public static double collideX(final net.minecraft.world.phys.AABB target, final net.minecraft.world.phys.AABB source, final double source_move) { ++ public static double collideX(final AABB target, final AABB source, final double source_move) { + if ((source.minY - target.maxY) < -COLLISION_EPSILON && (source.maxY - target.minY) > COLLISION_EPSILON && + (source.minZ - target.maxZ) < -COLLISION_EPSILON && (source.maxZ - target.minZ) > COLLISION_EPSILON) { + if (source_move >= 0.0) { @@ -15314,7 +16264,7 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + } + + // assume !isEmpty(target) && abs(source_move) >= COLLISION_EPSILON -+ public static double collideY(final net.minecraft.world.phys.AABB target, final net.minecraft.world.phys.AABB source, final double source_move) { ++ public static double collideY(final AABB target, final AABB source, final double source_move) { + if ((source.minX - target.maxX) < -COLLISION_EPSILON && (source.maxX - target.minX) > COLLISION_EPSILON && + (source.minZ - target.maxZ) < -COLLISION_EPSILON && (source.maxZ - target.minZ) > COLLISION_EPSILON) { + if (source_move >= 0.0) { @@ -15335,7 +16285,7 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + } + + // assume !isEmpty(target) && abs(source_move) >= COLLISION_EPSILON -+ public static double collideZ(final net.minecraft.world.phys.AABB target, final net.minecraft.world.phys.AABB source, final double source_move) { ++ public static double collideZ(final AABB target, final AABB source, final double source_move) { + if ((source.minX - target.maxX) < -COLLISION_EPSILON && (source.maxX - target.minX) > COLLISION_EPSILON && + (source.minY - target.maxY) < -COLLISION_EPSILON && (source.maxY - target.minY) > COLLISION_EPSILON) { + if (source_move >= 0.0) { @@ -15357,7 +16307,8 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + + // startIndex and endIndex inclusive + // assumes indices are in range of array -+ private static int findFloor(final double[] values, final double value, int startIndex, int endIndex) { ++ public static int findFloor(final double[] values, final double value, int startIndex, int endIndex) { ++ Objects.checkFromToIndex(startIndex, endIndex + 1, values.length); + do { + final int middle = (startIndex + endIndex) >>> 1; + final double middleVal = values[middle]; @@ -15372,7 +16323,217 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + return startIndex - 1; + } + -+ public static boolean voxelShapeIntersectNoEmpty(final net.minecraft.world.phys.shapes.VoxelShape voxel, final net.minecraft.world.phys.AABB aabb) { ++ private static VoxelShape sliceShapeVanilla(final VoxelShape src, final Direction.Axis axis, ++ final int index) { ++ return new SliceShape(src, axis, index); ++ } ++ ++ private static DoubleList offsetList(final double[] src, final double by) { ++ final DoubleArrayList wrap = DoubleArrayList.wrap(src); ++ if (by == 0.0) { ++ return wrap; ++ } ++ return new OffsetDoubleList(wrap, by); ++ } ++ ++ private static VoxelShape sliceShapeOptimised(final VoxelShape src, final Direction.Axis axis, ++ final int index) { ++ // assume index in range ++ final double off_x = ((CollisionVoxelShape)src).moonrise$offsetX(); ++ final double off_y = ((CollisionVoxelShape)src).moonrise$offsetY(); ++ final double off_z = ((CollisionVoxelShape)src).moonrise$offsetZ(); ++ ++ final double[] coords_x = ((CollisionVoxelShape)src).moonrise$rootCoordinatesX(); ++ final double[] coords_y = ((CollisionVoxelShape)src).moonrise$rootCoordinatesY(); ++ final double[] coords_z = ((CollisionVoxelShape)src).moonrise$rootCoordinatesZ(); ++ ++ final CachedShapeData cached_shape_data = ((CollisionVoxelShape)src).moonrise$getCachedVoxelData(); ++ ++ // note: size = coords.length - 1 ++ final int size_x = cached_shape_data.sizeX(); ++ final int size_y = cached_shape_data.sizeY(); ++ final int size_z = cached_shape_data.sizeZ(); ++ ++ final long[] bitset = cached_shape_data.voxelSet(); ++ ++ final DoubleList list_x; ++ final DoubleList list_y; ++ final DoubleList list_z; ++ final int shape_sx; ++ final int shape_ex; ++ final int shape_sy; ++ final int shape_ey; ++ final int shape_sz; ++ final int shape_ez; ++ ++ switch (axis) { ++ case X: { ++ // validate index ++ if (index < 0 || index >= size_x) { ++ return Shapes.empty(); ++ } ++ ++ // test if input is already "sliced" ++ if (coords_x.length == 2 && (coords_x[0] + off_x) == 0.0 && (coords_x[1] + off_x) == 1.0) { ++ return src; ++ } ++ ++ // test if result would be full box ++ if (coords_y.length == 2 && coords_z.length == 2 && ++ (coords_y[0] + off_y) == 0.0 && (coords_y[1] + off_y) == 1.0 && ++ (coords_z[0] + off_z) == 0.0 && (coords_z[1] + off_z) == 1.0) { ++ // note: size_y == size_z == 1 ++ final int bitIdx = 0 + 0*size_z + index*(size_z*size_y); ++ return (bitset[bitIdx >>> 6] & (1L << bitIdx)) == 0L ? Shapes.empty() : Shapes.block(); ++ } ++ ++ list_x = ZERO_ONE; ++ list_y = offsetList(coords_y, off_y); ++ list_z = offsetList(coords_z, off_z); ++ shape_sx = index; ++ shape_ex = index + 1; ++ shape_sy = 0; ++ shape_ey = size_y; ++ shape_sz = 0; ++ shape_ez = size_z; ++ ++ break; ++ } ++ case Y: { ++ // validate index ++ if (index < 0 || index >= size_y) { ++ return Shapes.empty(); ++ } ++ ++ // test if input is already "sliced" ++ if (coords_y.length == 2 && (coords_y[0] + off_y) == 0.0 && (coords_y[1] + off_y) == 1.0) { ++ return src; ++ } ++ ++ // test if result would be full box ++ if (coords_x.length == 2 && coords_z.length == 2 && ++ (coords_x[0] + off_x) == 0.0 && (coords_x[1] + off_x) == 1.0 && ++ (coords_z[0] + off_z) == 0.0 && (coords_z[1] + off_z) == 1.0) { ++ // note: size_x == size_z == 1 ++ final int bitIdx = 0 + index*size_z + 0*(size_z*size_y); ++ return (bitset[bitIdx >>> 6] & (1L << bitIdx)) == 0L ? Shapes.empty() : Shapes.block(); ++ } ++ ++ list_x = offsetList(coords_x, off_x); ++ list_y = ZERO_ONE; ++ list_z = offsetList(coords_z, off_z); ++ shape_sx = 0; ++ shape_ex = size_x; ++ shape_sy = index; ++ shape_ey = index + 1; ++ shape_sz = 0; ++ shape_ez = size_z; ++ ++ break; ++ } ++ case Z: { ++ // validate index ++ if (index < 0 || index >= size_z) { ++ return Shapes.empty(); ++ } ++ ++ // test if input is already "sliced" ++ if (coords_z.length == 2 && (coords_z[0] + off_z) == 0.0 && (coords_z[1] + off_z) == 1.0) { ++ return src; ++ } ++ ++ // test if result would be full box ++ if (coords_x.length == 2 && coords_y.length == 2 && ++ (coords_x[0] + off_x) == 0.0 && (coords_x[1] + off_x) == 1.0 && ++ (coords_y[0] + off_y) == 0.0 && (coords_y[1] + off_y) == 1.0) { ++ // note: size_x == size_y == 1 ++ final int bitIdx = index + 0*size_z + 0*(size_z*size_y); ++ return (bitset[bitIdx >>> 6] & (1L << bitIdx)) == 0L ? Shapes.empty() : Shapes.block(); ++ } ++ ++ list_x = offsetList(coords_x, off_x); ++ list_y = offsetList(coords_y, off_y); ++ list_z = ZERO_ONE; ++ shape_sx = 0; ++ shape_ex = size_x; ++ shape_sy = 0; ++ shape_ey = size_y; ++ shape_sz = index; ++ shape_ez = index + 1; ++ ++ break; ++ } ++ default: { ++ throw new IllegalStateException("Unknown axis: " + axis); ++ } ++ } ++ ++ final int local_len_x = shape_ex - shape_sx; ++ final int local_len_y = shape_ey - shape_sy; ++ final int local_len_z = shape_ez - shape_sz; ++ ++ final BitSetDiscreteVoxelShape shape = new BitSetDiscreteVoxelShape(local_len_x, local_len_y, local_len_z); ++ ++ final int bitset_mul_x = size_z*size_y; ++ final int idx_off = shape_sz + shape_sy*size_z + shape_sx*bitset_mul_x; ++ final int shape_mul_x = local_len_y*local_len_z; ++ for (int x = 0; x < local_len_x; ++x) { ++ boolean setX = false; ++ for (int y = 0; y < local_len_y; ++y) { ++ boolean setY = false; ++ for (int z = 0; z < local_len_z; ++z) { ++ final int unslicedIdx = idx_off + z + y*size_z + x*bitset_mul_x; ++ if ((bitset[unslicedIdx >>> 6] & (1L << unslicedIdx)) == 0L) { ++ continue; ++ } ++ ++ setY = true; ++ setX = true; ++ shape.zMin = Math.min(shape.zMin, z); ++ shape.zMax = Math.max(shape.zMax, z + 1); ++ ++ shape.storage.set( ++ z + y*local_len_z + x*shape_mul_x ++ ); ++ } ++ ++ if (setY) { ++ shape.yMin = Math.min(shape.yMin, y); ++ shape.yMax = Math.max(shape.yMax, y + 1); ++ } ++ } ++ if (setX) { ++ shape.xMin = Math.min(shape.xMin, x); ++ shape.xMax = Math.max(shape.xMax, x + 1); ++ } ++ } ++ ++ return shape.isEmpty() ? Shapes.empty() : new ArrayVoxelShape( ++ shape, list_x, list_y, list_z ++ ); ++ } ++ ++ private static final boolean DEBUG_SLICE_SHAPE = false; ++ ++ public static VoxelShape sliceShape(final VoxelShape src, final Direction.Axis axis, ++ final int index) { ++ final VoxelShape ret = sliceShapeOptimised(src, axis, index); ++ if (DEBUG_SLICE_SHAPE) { ++ final VoxelShape vanilla = sliceShapeVanilla(src, axis, index); ++ if (!equals(ret, vanilla)) { ++ // special case: SliceShape is not empty when it should be! ++ if (areAnyFull(ret.shape) || areAnyFull(vanilla.shape)) { ++ equals(ret, vanilla); ++ sliceShapeOptimised(src, axis, index); ++ throw new IllegalStateException("Slice shape mismatch"); ++ } ++ } ++ } ++ ++ return ret; ++ } ++ ++ public static boolean voxelShapeIntersectNoEmpty(final VoxelShape voxel, final AABB aabb) { + if (voxel.isEmpty()) { + return false; + } @@ -15380,15 +16541,15 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + // note: this function assumes that for any i in coords that coord[i + 1] - coord[i] > COLLISION_EPSILON is true + + // offsets that should be applied to coords -+ final double off_x = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$offsetX(); -+ final double off_y = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$offsetY(); -+ final double off_z = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$offsetZ(); ++ final double off_x = ((CollisionVoxelShape)voxel).moonrise$offsetX(); ++ final double off_y = ((CollisionVoxelShape)voxel).moonrise$offsetY(); ++ final double off_z = ((CollisionVoxelShape)voxel).moonrise$offsetZ(); + -+ final double[] coords_x = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$rootCoordinatesX(); -+ final double[] coords_y = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$rootCoordinatesY(); -+ final double[] coords_z = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$rootCoordinatesZ(); ++ final double[] coords_x = ((CollisionVoxelShape)voxel).moonrise$rootCoordinatesX(); ++ final double[] coords_y = ((CollisionVoxelShape)voxel).moonrise$rootCoordinatesY(); ++ final double[] coords_z = ((CollisionVoxelShape)voxel).moonrise$rootCoordinatesZ(); + -+ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData cached_shape_data = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$getCachedVoxelData(); ++ final CachedShapeData cached_shape_data = ((CollisionVoxelShape)voxel).moonrise$getCachedVoxelData(); + + // note: size = coords.length - 1 + final int size_x = cached_shape_data.sizeX(); @@ -15482,23 +16643,23 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + } + + // assume !target.isEmpty() && abs(source_move) >= COLLISION_EPSILON -+ public static double collideX(final net.minecraft.world.phys.shapes.VoxelShape target, final net.minecraft.world.phys.AABB source, final double source_move) { -+ final net.minecraft.world.phys.AABB single_aabb = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$getSingleAABBRepresentation(); ++ public static double collideX(final VoxelShape target, final AABB source, final double source_move) { ++ final AABB single_aabb = ((CollisionVoxelShape)target).moonrise$getSingleAABBRepresentation(); + if (single_aabb != null) { + return collideX(single_aabb, source, source_move); + } + // note: this function assumes that for any i in coords that coord[i + 1] - coord[i] > COLLISION_EPSILON is true + + // offsets that should be applied to coords -+ final double off_x = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$offsetX(); -+ final double off_y = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$offsetY(); -+ final double off_z = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$offsetZ(); ++ final double off_x = ((CollisionVoxelShape)target).moonrise$offsetX(); ++ final double off_y = ((CollisionVoxelShape)target).moonrise$offsetY(); ++ final double off_z = ((CollisionVoxelShape)target).moonrise$offsetZ(); + -+ final double[] coords_x = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$rootCoordinatesX(); -+ final double[] coords_y = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$rootCoordinatesY(); -+ final double[] coords_z = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$rootCoordinatesZ(); ++ final double[] coords_x = ((CollisionVoxelShape)target).moonrise$rootCoordinatesX(); ++ final double[] coords_y = ((CollisionVoxelShape)target).moonrise$rootCoordinatesY(); ++ final double[] coords_z = ((CollisionVoxelShape)target).moonrise$rootCoordinatesZ(); + -+ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData cached_shape_data = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$getCachedVoxelData(); ++ final CachedShapeData cached_shape_data = ((CollisionVoxelShape)target).moonrise$getCachedVoxelData(); + + // note: size = coords.length - 1 + final int size_x = cached_shape_data.sizeX(); @@ -15640,23 +16801,23 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + } + } + -+ public static double collideY(final net.minecraft.world.phys.shapes.VoxelShape target, final net.minecraft.world.phys.AABB source, final double source_move) { -+ final net.minecraft.world.phys.AABB single_aabb = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$getSingleAABBRepresentation(); ++ public static double collideY(final VoxelShape target, final AABB source, final double source_move) { ++ final AABB single_aabb = ((CollisionVoxelShape)target).moonrise$getSingleAABBRepresentation(); + if (single_aabb != null) { + return collideY(single_aabb, source, source_move); + } + // note: this function assumes that for any i in coords that coord[i + 1] - coord[i] > COLLISION_EPSILON is true + + // offsets that should be applied to coords -+ final double off_x = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$offsetX(); -+ final double off_y = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$offsetY(); -+ final double off_z = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$offsetZ(); ++ final double off_x = ((CollisionVoxelShape)target).moonrise$offsetX(); ++ final double off_y = ((CollisionVoxelShape)target).moonrise$offsetY(); ++ final double off_z = ((CollisionVoxelShape)target).moonrise$offsetZ(); + -+ final double[] coords_x = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$rootCoordinatesX(); -+ final double[] coords_y = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$rootCoordinatesY(); -+ final double[] coords_z = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$rootCoordinatesZ(); ++ final double[] coords_x = ((CollisionVoxelShape)target).moonrise$rootCoordinatesX(); ++ final double[] coords_y = ((CollisionVoxelShape)target).moonrise$rootCoordinatesY(); ++ final double[] coords_z = ((CollisionVoxelShape)target).moonrise$rootCoordinatesZ(); + -+ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData cached_shape_data = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$getCachedVoxelData(); ++ final CachedShapeData cached_shape_data = ((CollisionVoxelShape)target).moonrise$getCachedVoxelData(); + + // note: size = coords.length - 1 + final int size_x = cached_shape_data.sizeX(); @@ -15798,23 +16959,23 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + } + } + -+ public static double collideZ(final net.minecraft.world.phys.shapes.VoxelShape target, final net.minecraft.world.phys.AABB source, final double source_move) { -+ final net.minecraft.world.phys.AABB single_aabb = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$getSingleAABBRepresentation(); ++ public static double collideZ(final VoxelShape target, final AABB source, final double source_move) { ++ final AABB single_aabb = ((CollisionVoxelShape)target).moonrise$getSingleAABBRepresentation(); + if (single_aabb != null) { + return collideZ(single_aabb, source, source_move); + } + // note: this function assumes that for any i in coords that coord[i + 1] - coord[i] > COLLISION_EPSILON is true + + // offsets that should be applied to coords -+ final double off_x = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$offsetX(); -+ final double off_y = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$offsetY(); -+ final double off_z = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$offsetZ(); ++ final double off_x = ((CollisionVoxelShape)target).moonrise$offsetX(); ++ final double off_y = ((CollisionVoxelShape)target).moonrise$offsetY(); ++ final double off_z = ((CollisionVoxelShape)target).moonrise$offsetZ(); + -+ final double[] coords_x = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$rootCoordinatesX(); -+ final double[] coords_y = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$rootCoordinatesY(); -+ final double[] coords_z = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$rootCoordinatesZ(); ++ final double[] coords_x = ((CollisionVoxelShape)target).moonrise$rootCoordinatesX(); ++ final double[] coords_y = ((CollisionVoxelShape)target).moonrise$rootCoordinatesY(); ++ final double[] coords_z = ((CollisionVoxelShape)target).moonrise$rootCoordinatesZ(); + -+ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData cached_shape_data = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$getCachedVoxelData(); ++ final CachedShapeData cached_shape_data = ((CollisionVoxelShape)target).moonrise$getCachedVoxelData(); + + // note: size = coords.length - 1 + final int size_x = cached_shape_data.sizeX(); @@ -15957,13 +17118,13 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + } + + // does not use epsilon -+ public static boolean strictlyContains(final net.minecraft.world.phys.shapes.VoxelShape voxel, final net.minecraft.world.phys.Vec3 point) { ++ public static boolean strictlyContains(final VoxelShape voxel, final Vec3 point) { + return strictlyContains(voxel, point.x, point.y, point.z); + } + + // does not use epsilon -+ public static boolean strictlyContains(final net.minecraft.world.phys.shapes.VoxelShape voxel, double x, double y, double z) { -+ final net.minecraft.world.phys.AABB single_aabb = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$getSingleAABBRepresentation(); ++ public static boolean strictlyContains(final VoxelShape voxel, double x, double y, double z) { ++ final AABB single_aabb = ((CollisionVoxelShape)voxel).moonrise$getSingleAABBRepresentation(); + if (single_aabb != null) { + return single_aabb.contains(x, y, z); + } @@ -15974,15 +17135,15 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + } + + // offset input -+ x -= ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$offsetX(); -+ y -= ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$offsetY(); -+ z -= ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$offsetZ(); ++ x -= ((CollisionVoxelShape)voxel).moonrise$offsetX(); ++ y -= ((CollisionVoxelShape)voxel).moonrise$offsetY(); ++ z -= ((CollisionVoxelShape)voxel).moonrise$offsetZ(); + -+ final double[] coords_x = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$rootCoordinatesX(); -+ final double[] coords_y = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$rootCoordinatesY(); -+ final double[] coords_z = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$rootCoordinatesZ(); ++ final double[] coords_x = ((CollisionVoxelShape)voxel).moonrise$rootCoordinatesX(); ++ final double[] coords_y = ((CollisionVoxelShape)voxel).moonrise$rootCoordinatesY(); ++ final double[] coords_z = ((CollisionVoxelShape)voxel).moonrise$rootCoordinatesZ(); + -+ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData cached_shape_data = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$getCachedVoxelData(); ++ final CachedShapeData cached_shape_data = ((CollisionVoxelShape)voxel).moonrise$getCachedVoxelData(); + + // note: size = coords.length - 1 + final int size_x = cached_shape_data.sizeX(); @@ -16024,10 +17185,10 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + return ((ft ? 1 : 0) << 1) | ((tf ? 1 : 0) << 2) | ((tt ? 1 : 0) << 3); + } + -+ private static net.minecraft.world.phys.shapes.BitSetDiscreteVoxelShape merge(final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeDataFirst, final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeDataSecond, -+ final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedX, final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedY, -+ final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedZ, -+ final int booleanOp) { ++ private static BitSetDiscreteVoxelShape merge(final CachedShapeData shapeDataFirst, final CachedShapeData shapeDataSecond, ++ final MergedVoxelCoordinateList mergedX, final MergedVoxelCoordinateList mergedY, ++ final MergedVoxelCoordinateList mergedZ, ++ final int booleanOp) { + final int sizeX = mergedX.voxels; + final int sizeY = mergedY.voxels; + final int sizeZ = mergedZ.voxels; @@ -16042,7 +17203,7 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + final int s2Mul2 = s2Mul1 * shapeDataSecond.sizeY(); + + // note: indices may contain -1, but nothing > size -+ final net.minecraft.world.phys.shapes.BitSetDiscreteVoxelShape ret = new net.minecraft.world.phys.shapes.BitSetDiscreteVoxelShape(sizeX, sizeY, sizeZ); ++ final BitSetDiscreteVoxelShape ret = new BitSetDiscreteVoxelShape(sizeX, sizeY, sizeZ); + + boolean empty = true; + @@ -16059,10 +17220,11 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + final int s1z = mergedZ.firstIndices[idxZ]; + final int s2z = mergedZ.secondIndices[idxZ]; + -+ int idx; ++ int idx1; ++ int idx2; + -+ final int isS1Full = (s1x | s1y | s1z) < 0 ? 0 : (int)((s1Voxels[(idx = s1z + s1y*s1Mul1 + s1x*s1Mul2) >>> 6] >>> idx) & 1L); -+ final int isS2Full = (s2x | s2y | s2z) < 0 ? 0 : (int)((s2Voxels[(idx = s2z + s2y*s2Mul1 + s2x*s2Mul2) >>> 6] >>> idx) & 1L); ++ final int isS1Full = (s1x | s1y | s1z) < 0 ? 0 : (int)((s1Voxels[(idx1 = s1z + s1y*s1Mul1 + s1x*s1Mul2) >>> 6] >>> idx1) & 1L); ++ final int isS2Full = (s2x | s2y | s2z) < 0 ? 0 : (int)((s2Voxels[(idx2 = s2z + s2y*s2Mul1 + s2x*s2Mul2) >>> 6] >>> idx2) & 1L); + + // idx ff -> 0 + // idx ft -> 1 @@ -16097,9 +17259,9 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + return empty ? null : ret; + } + -+ private static boolean isMergeEmpty(final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeDataFirst, final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeDataSecond, -+ final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedX, final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedY, -+ final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedZ, ++ private static boolean isMergeEmpty(final CachedShapeData shapeDataFirst, final CachedShapeData shapeDataSecond, ++ final MergedVoxelCoordinateList mergedX, final MergedVoxelCoordinateList mergedY, ++ final MergedVoxelCoordinateList mergedZ, + final int booleanOp) { + final int sizeX = mergedX.voxels; + final int sizeY = mergedY.voxels; @@ -16125,10 +17287,11 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + final int s1z = mergedZ.firstIndices[idxZ]; + final int s2z = mergedZ.secondIndices[idxZ]; + -+ int idx; ++ int idx1; ++ int idx2; + -+ final int isS1Full = (s1x | s1y | s1z) < 0 ? 0 : (int)((s1Voxels[(idx = s1z + s1y*s1Mul1 + s1x*s1Mul2) >>> 6] >>> idx) & 1L); -+ final int isS2Full = (s2x | s2y | s2z) < 0 ? 0 : (int)((s2Voxels[(idx = s2z + s2y*s2Mul1 + s2x*s2Mul2) >>> 6] >>> idx) & 1L); ++ final int isS1Full = (s1x | s1y | s1z) < 0 ? 0 : (int)((s1Voxels[(idx1 = s1z + s1y*s1Mul1 + s1x*s1Mul2) >>> 6] >>> idx1) & 1L); ++ final int isS2Full = (s2x | s2y | s2z) < 0 ? 0 : (int)((s2Voxels[(idx2 = s2z + s2y*s2Mul1 + s2x*s2Mul2) >>> 6] >>> idx2) & 1L); + + // idx ff -> 0 + // idx ft -> 1 @@ -16147,11 +17310,11 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + return true; + } + -+ public static net.minecraft.world.phys.shapes.VoxelShape joinOptimized(final net.minecraft.world.phys.shapes.VoxelShape first, final net.minecraft.world.phys.shapes.VoxelShape second, final net.minecraft.world.phys.shapes.BooleanOp operator) { ++ public static VoxelShape joinOptimized(final VoxelShape first, final VoxelShape second, final BooleanOp operator) { + return joinUnoptimized(first, second, operator).optimize(); + } + -+ public static net.minecraft.world.phys.shapes.VoxelShape joinUnoptimized(final net.minecraft.world.phys.shapes.VoxelShape first, final net.minecraft.world.phys.shapes.VoxelShape second, final net.minecraft.world.phys.shapes.BooleanOp operator) { ++ public static VoxelShape joinUnoptimized(final VoxelShape first, final VoxelShape second, final BooleanOp operator) { + final boolean ff = operator.apply(false, false); + if (ff) { + // technically, should be an infinite box but that's clearly an error @@ -16161,23 +17324,23 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + final boolean tt = operator.apply(true, true); + + if (first == second) { -+ return tt ? first : net.minecraft.world.phys.shapes.Shapes.empty(); ++ return tt ? first : Shapes.empty(); + } + + final boolean ft = operator.apply(false, true); + final boolean tf = operator.apply(true, false); + + if (first.isEmpty()) { -+ return ft ? second : net.minecraft.world.phys.shapes.Shapes.empty(); ++ return ft ? second : Shapes.empty(); + } + if (second.isEmpty()) { -+ return tf ? first : net.minecraft.world.phys.shapes.Shapes.empty(); ++ return tf ? first : Shapes.empty(); + } + + if (!tt) { + // try to check for no intersection, since tt = false -+ final net.minecraft.world.phys.AABB aabbF = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$getSingleAABBRepresentation(); -+ final net.minecraft.world.phys.AABB aabbS = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$getSingleAABBRepresentation(); ++ final AABB aabbF = ((CollisionVoxelShape)first).moonrise$getSingleAABBRepresentation(); ++ final AABB aabbS = ((CollisionVoxelShape)second).moonrise$getSingleAABBRepresentation(); + + final boolean intersect; + @@ -16198,7 +17361,7 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + + if (!intersect) { + if (!tf & !ft) { -+ return net.minecraft.world.phys.shapes.Shapes.empty(); ++ return Shapes.empty(); + } + if (!tf | !ft) { + return tf ? first : second; @@ -16206,50 +17369,50 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + } + } + -+ final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedX = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.merge( -+ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$rootCoordinatesX(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$offsetX(), -+ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$rootCoordinatesX(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$offsetX(), ++ final MergedVoxelCoordinateList mergedX = MergedVoxelCoordinateList.merge( ++ ((CollisionVoxelShape)first).moonrise$rootCoordinatesX(), ((CollisionVoxelShape)first).moonrise$offsetX(), ++ ((CollisionVoxelShape)second).moonrise$rootCoordinatesX(), ((CollisionVoxelShape)second).moonrise$offsetX(), + ft, tf + ); -+ if (mergedX == ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.EMPTY) { -+ return net.minecraft.world.phys.shapes.Shapes.empty(); ++ if (mergedX == null) { ++ return Shapes.empty(); + } -+ final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedY = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.merge( -+ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$rootCoordinatesY(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$offsetY(), -+ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$rootCoordinatesY(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$offsetY(), ++ final MergedVoxelCoordinateList mergedY = MergedVoxelCoordinateList.merge( ++ ((CollisionVoxelShape)first).moonrise$rootCoordinatesY(), ((CollisionVoxelShape)first).moonrise$offsetY(), ++ ((CollisionVoxelShape)second).moonrise$rootCoordinatesY(), ((CollisionVoxelShape)second).moonrise$offsetY(), + ft, tf + ); -+ if (mergedY == ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.EMPTY) { -+ return net.minecraft.world.phys.shapes.Shapes.empty(); ++ if (mergedY == null) { ++ return Shapes.empty(); + } -+ final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedZ = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.merge( -+ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$rootCoordinatesZ(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$offsetZ(), -+ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$rootCoordinatesZ(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$offsetZ(), ++ final MergedVoxelCoordinateList mergedZ = MergedVoxelCoordinateList.merge( ++ ((CollisionVoxelShape)first).moonrise$rootCoordinatesZ(), ((CollisionVoxelShape)first).moonrise$offsetZ(), ++ ((CollisionVoxelShape)second).moonrise$rootCoordinatesZ(), ((CollisionVoxelShape)second).moonrise$offsetZ(), + ft, tf + ); -+ if (mergedZ == ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.EMPTY) { -+ return net.minecraft.world.phys.shapes.Shapes.empty(); ++ if (mergedZ == null) { ++ return Shapes.empty(); + } + -+ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeDataFirst = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$getCachedVoxelData(); -+ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeDataSecond = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$getCachedVoxelData(); ++ final CachedShapeData shapeDataFirst = ((CollisionVoxelShape)first).moonrise$getCachedVoxelData(); ++ final CachedShapeData shapeDataSecond = ((CollisionVoxelShape)second).moonrise$getCachedVoxelData(); + -+ final net.minecraft.world.phys.shapes.BitSetDiscreteVoxelShape mergedShape = merge( ++ final BitSetDiscreteVoxelShape mergedShape = merge( + shapeDataFirst, shapeDataSecond, + mergedX, mergedY, mergedZ, + makeBitset(ft, tf, tt) + ); + + if (mergedShape == null) { -+ return net.minecraft.world.phys.shapes.Shapes.empty(); ++ return Shapes.empty(); + } + -+ return new net.minecraft.world.phys.shapes.ArrayVoxelShape( ++ return new ArrayVoxelShape( + mergedShape, mergedX.wrapCoords(), mergedY.wrapCoords(), mergedZ.wrapCoords() + ); + } + -+ public static boolean isJoinNonEmpty(final net.minecraft.world.phys.shapes.VoxelShape first, final net.minecraft.world.phys.shapes.VoxelShape second, final net.minecraft.world.phys.shapes.BooleanOp operator) { ++ public static boolean isJoinNonEmpty(final VoxelShape first, final VoxelShape second, final BooleanOp operator) { + final boolean ff = operator.apply(false, false); + if (ff) { + // technically, should be an infinite box but that's clearly an error @@ -16271,8 +17434,8 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + final boolean tf = operator.apply(true, false); + + // try to check intersection -+ final net.minecraft.world.phys.AABB aabbF = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$getSingleAABBRepresentation(); -+ final net.minecraft.world.phys.AABB aabbS = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$getSingleAABBRepresentation(); ++ final AABB aabbF = ((CollisionVoxelShape)first).moonrise$getSingleAABBRepresentation(); ++ final AABB aabbS = ((CollisionVoxelShape)second).moonrise$getSingleAABBRepresentation(); + + final boolean intersect; + @@ -16304,33 +17467,33 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + } + } + -+ final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedX = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.merge( -+ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$rootCoordinatesX(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$offsetX(), -+ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$rootCoordinatesX(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$offsetX(), ++ final MergedVoxelCoordinateList mergedX = MergedVoxelCoordinateList.merge( ++ ((CollisionVoxelShape)first).moonrise$rootCoordinatesX(), ((CollisionVoxelShape)first).moonrise$offsetX(), ++ ((CollisionVoxelShape)second).moonrise$rootCoordinatesX(), ((CollisionVoxelShape)second).moonrise$offsetX(), + ft, tf + ); -+ if (mergedX == ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.EMPTY) { ++ if (mergedX == null) { + return false; + } -+ final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedY = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.merge( -+ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$rootCoordinatesY(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$offsetY(), -+ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$rootCoordinatesY(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$offsetY(), ++ final MergedVoxelCoordinateList mergedY = MergedVoxelCoordinateList.merge( ++ ((CollisionVoxelShape)first).moonrise$rootCoordinatesY(), ((CollisionVoxelShape)first).moonrise$offsetY(), ++ ((CollisionVoxelShape)second).moonrise$rootCoordinatesY(), ((CollisionVoxelShape)second).moonrise$offsetY(), + ft, tf + ); -+ if (mergedY == ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.EMPTY) { ++ if (mergedY == null) { + return false; + } -+ final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedZ = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.merge( -+ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$rootCoordinatesZ(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$offsetZ(), -+ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$rootCoordinatesZ(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$offsetZ(), ++ final MergedVoxelCoordinateList mergedZ = MergedVoxelCoordinateList.merge( ++ ((CollisionVoxelShape)first).moonrise$rootCoordinatesZ(), ((CollisionVoxelShape)first).moonrise$offsetZ(), ++ ((CollisionVoxelShape)second).moonrise$rootCoordinatesZ(), ((CollisionVoxelShape)second).moonrise$offsetZ(), + ft, tf + ); -+ if (mergedZ == ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.EMPTY) { ++ if (mergedZ == null) { + return false; + } + -+ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeDataFirst = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$getCachedVoxelData(); -+ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeDataSecond = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$getCachedVoxelData(); ++ final CachedShapeData shapeDataFirst = ((CollisionVoxelShape)first).moonrise$getCachedVoxelData(); ++ final CachedShapeData shapeDataSecond = ((CollisionVoxelShape)second).moonrise$getCachedVoxelData(); + + return !isMergeEmpty( + shapeDataFirst, shapeDataSecond, @@ -16348,10 +17511,6 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + } + } + -+ private static final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList EMPTY = new ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList( -+ new double[] { 0.0 }, 0.0, new int[0], new int[0], 0 -+ ); -+ + private static int[] getIndices(final int length) { + final int[] ret = new int[length]; + @@ -16378,25 +17537,25 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + this.voxels = voxels; + } + -+ public it.unimi.dsi.fastutil.doubles.DoubleList wrapCoords() { ++ public DoubleList wrapCoords() { + if (this.coordinateOffset == 0.0) { -+ return it.unimi.dsi.fastutil.doubles.DoubleArrayList.wrap(this.coordinates, this.voxels + 1); ++ return DoubleArrayList.wrap(this.coordinates, this.voxels + 1); + } -+ return new net.minecraft.world.phys.shapes.OffsetDoubleList(it.unimi.dsi.fastutil.doubles.DoubleArrayList.wrap(this.coordinates, this.voxels + 1), this.coordinateOffset); ++ return new OffsetDoubleList(DoubleArrayList.wrap(this.coordinates, this.voxels + 1), this.coordinateOffset); + } + + // assume coordinates.length > 1 -+ public static ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList getForSingle(final double[] coordinates, final double offset) { ++ public static MergedVoxelCoordinateList getForSingle(final double[] coordinates, final double offset) { + final int voxels = coordinates.length - 1; + final int[] indices = voxels < SIMPLE_INDICES_CACHE.length ? SIMPLE_INDICES_CACHE[voxels] : getIndices(voxels); + -+ return new ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList(coordinates, offset, indices, indices, voxels); ++ return new MergedVoxelCoordinateList(coordinates, offset, indices, indices, voxels); + } + + // assume coordinates.length > 1 -+ public static ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList merge(final double[] firstCoordinates, final double firstOffset, -+ final double[] secondCoordinates, final double secondOffset, -+ final boolean ft, final boolean tf) { ++ public static MergedVoxelCoordinateList merge(final double[] firstCoordinates, final double firstOffset, ++ final double[] secondCoordinates, final double secondOffset, ++ final boolean ft, final boolean tf) { + if (firstCoordinates == secondCoordinates && firstOffset == secondOffset) { + return getForSingle(firstCoordinates, firstOffset); + } @@ -16486,13 +17645,13 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + } + } + -+ return resultSize <= 1 ? EMPTY : new ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList(coordinates, 0.0, firstIndices, secondIndices, resultSize - 1); ++ return resultSize <= 1 ? null : new MergedVoxelCoordinateList(coordinates, 0.0, firstIndices, secondIndices, resultSize - 1); + } + } + -+ public static boolean equals(final net.minecraft.world.phys.shapes.DiscreteVoxelShape shape1, final net.minecraft.world.phys.shapes.DiscreteVoxelShape shape2) { -+ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData cachedShapeData1 = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionDiscreteVoxelShape)shape1).moonrise$getOrCreateCachedShapeData(); -+ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData cachedShapeData2 = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionDiscreteVoxelShape)shape2).moonrise$getOrCreateCachedShapeData(); ++ public static boolean equals(final DiscreteVoxelShape shape1, final DiscreteVoxelShape shape2) { ++ final CachedShapeData cachedShapeData1 = ((CollisionDiscreteVoxelShape)shape1).moonrise$getOrCreateCachedShapeData(); ++ final CachedShapeData cachedShapeData2 = ((CollisionDiscreteVoxelShape)shape2).moonrise$getOrCreateCachedShapeData(); + + final boolean isEmpty1 = cachedShapeData1.isEmpty(); + final boolean isEmpty2 = cachedShapeData2.isEmpty(); @@ -16501,7 +17660,7 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + return true; + } else if (isEmpty1 ^ isEmpty2) { + return false; -+ } ++ } // else: isEmpty1 = isEmpty2 = false + + if (cachedShapeData1.hasSingleAABB() != cachedShapeData2.hasSingleAABB()) { + return false; @@ -16517,153 +17676,237 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + return false; + } + -+ return java.util.Arrays.equals(cachedShapeData1.voxelSet(), cachedShapeData2.voxelSet()); ++ return Arrays.equals(cachedShapeData1.voxelSet(), cachedShapeData2.voxelSet()); + } + + // useful only for testing -+ public static boolean equals(final net.minecraft.world.phys.shapes.VoxelShape shape1, final net.minecraft.world.phys.shapes.VoxelShape shape2) { ++ public static boolean equals(final VoxelShape shape1, final VoxelShape shape2) { ++ if (shape1.isEmpty() & shape2.isEmpty()) { ++ return true; ++ } else if (shape1.isEmpty() ^ shape2.isEmpty()) { ++ return false; ++ } ++ + if (!equals(shape1.shape, shape2.shape)) { + return false; + } + -+ return shape1.getCoords(net.minecraft.core.Direction.Axis.X).equals(shape2.getCoords(net.minecraft.core.Direction.Axis.X)) && -+ shape1.getCoords(net.minecraft.core.Direction.Axis.Y).equals(shape2.getCoords(net.minecraft.core.Direction.Axis.Y)) && -+ shape1.getCoords(net.minecraft.core.Direction.Axis.Z).equals(shape2.getCoords(net.minecraft.core.Direction.Axis.Z)); ++ return shape1.getCoords(Direction.Axis.X).equals(shape2.getCoords(Direction.Axis.X)) && ++ shape1.getCoords(Direction.Axis.Y).equals(shape2.getCoords(Direction.Axis.Y)) && ++ shape1.getCoords(Direction.Axis.Z).equals(shape2.getCoords(Direction.Axis.Z)); + } + -+ public static net.minecraft.world.phys.AABB offsetX(final net.minecraft.world.phys.AABB box, final double dx) { -+ return new net.minecraft.world.phys.AABB(box.minX + dx, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ); ++ public static boolean areAnyFull(final DiscreteVoxelShape shape) { ++ if (shape.isEmpty()) { ++ return false; ++ } ++ ++ final int sizeX = shape.getXSize(); ++ final int sizeY = shape.getYSize(); ++ final int sizeZ = shape.getZSize(); ++ ++ for (int x = 0; x < sizeX; ++x) { ++ for (int y = 0; y < sizeY; ++y) { ++ for (int z = 0; z < sizeZ; ++z) { ++ if (shape.isFull(x, y, z)) { ++ return true; ++ } ++ } ++ } ++ } ++ ++ return false; + } + -+ public static net.minecraft.world.phys.AABB offsetY(final net.minecraft.world.phys.AABB box, final double dy) { -+ return new net.minecraft.world.phys.AABB(box.minX, box.minY + dy, box.minZ, box.maxX, box.maxY + dy, box.maxZ); ++ public static String shapeMismatch(final DiscreteVoxelShape shape1, final DiscreteVoxelShape shape2) { ++ final CachedShapeData cachedShapeData1 = ((CollisionDiscreteVoxelShape)shape1).moonrise$getOrCreateCachedShapeData(); ++ final CachedShapeData cachedShapeData2 = ((CollisionDiscreteVoxelShape)shape2).moonrise$getOrCreateCachedShapeData(); ++ ++ final boolean isEmpty1 = cachedShapeData1.isEmpty(); ++ final boolean isEmpty2 = cachedShapeData2.isEmpty(); ++ ++ if (isEmpty1 & isEmpty2) { ++ return null; ++ } else if (isEmpty1 ^ isEmpty2) { ++ return null; ++ } // else: isEmpty1 = isEmpty2 = false ++ ++ if (cachedShapeData1.sizeX() != cachedShapeData2.sizeX()) { ++ return "size x: " + cachedShapeData1.sizeX() + " != " + cachedShapeData2.sizeX(); ++ } ++ if (cachedShapeData1.sizeY() != cachedShapeData2.sizeY()) { ++ return "size y: " + cachedShapeData1.sizeY() + " != " + cachedShapeData2.sizeY(); ++ } ++ if (cachedShapeData1.sizeZ() != cachedShapeData2.sizeZ()) { ++ return "size z: " + cachedShapeData1.sizeZ() + " != " + cachedShapeData2.sizeZ(); ++ } ++ ++ final StringBuilder ret = new StringBuilder(); ++ ++ final int sizeX = cachedShapeData1.sizeX();; ++ final int sizeY = cachedShapeData1.sizeY(); ++ final int sizeZ = cachedShapeData1.sizeZ(); ++ ++ boolean first = true; ++ ++ for (int x = 0; x < sizeX; ++x) { ++ for (int y = 0; y < sizeY; ++y) { ++ for (int z = 0; z < sizeZ; ++z) { ++ final boolean isFull1 = shape1.isFull(x, y, z); ++ final boolean isFull2 = shape2.isFull(x, y, z); ++ ++ if (isFull1 == isFull2) { ++ continue; ++ } ++ ++ if (first) { ++ first = false; ++ } else { ++ ret.append(", "); ++ } ++ ++ ret.append("(").append(x).append(",").append(y).append(",").append(z) ++ .append("): shape1: ").append(isFull1).append(", shape2: ").append(isFull2); ++ } ++ } ++ } ++ ++ return ret.isEmpty() ? null : ret.toString(); + } + -+ public static net.minecraft.world.phys.AABB offsetZ(final net.minecraft.world.phys.AABB box, final double dz) { -+ return new net.minecraft.world.phys.AABB(box.minX, box.minY, box.minZ + dz, box.maxX, box.maxY, box.maxZ + dz); ++ public static AABB offsetX(final AABB box, final double dx) { ++ return new AABB(box.minX + dx, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ); + } + -+ public static net.minecraft.world.phys.AABB expandRight(final net.minecraft.world.phys.AABB box, final double dx) { // dx > 0.0 -+ return new net.minecraft.world.phys.AABB(box.minX, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ); ++ public static AABB offsetY(final AABB box, final double dy) { ++ return new AABB(box.minX, box.minY + dy, box.minZ, box.maxX, box.maxY + dy, box.maxZ); + } + -+ public static net.minecraft.world.phys.AABB expandLeft(final net.minecraft.world.phys.AABB box, final double dx) { // dx < 0.0 -+ return new net.minecraft.world.phys.AABB(box.minX - dx, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ); ++ public static AABB offsetZ(final AABB box, final double dz) { ++ return new AABB(box.minX, box.minY, box.minZ + dz, box.maxX, box.maxY, box.maxZ + dz); + } + -+ public static net.minecraft.world.phys.AABB expandUpwards(final net.minecraft.world.phys.AABB box, final double dy) { // dy > 0.0 -+ return new net.minecraft.world.phys.AABB(box.minX, box.minY, box.minZ, box.maxX, box.maxY + dy, box.maxZ); ++ public static AABB expandRight(final AABB box, final double dx) { // dx > 0.0 ++ return new AABB(box.minX, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ); + } + -+ public static net.minecraft.world.phys.AABB expandDownwards(final net.minecraft.world.phys.AABB box, final double dy) { // dy < 0.0 -+ return new net.minecraft.world.phys.AABB(box.minX, box.minY - dy, box.minZ, box.maxX, box.maxY, box.maxZ); ++ public static AABB expandLeft(final AABB box, final double dx) { // dx < 0.0 ++ return new AABB(box.minX - dx, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ); + } + -+ public static net.minecraft.world.phys.AABB expandForwards(final net.minecraft.world.phys.AABB box, final double dz) { // dz > 0.0 -+ return new net.minecraft.world.phys.AABB(box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ + dz); ++ public static AABB expandUpwards(final AABB box, final double dy) { // dy > 0.0 ++ return new AABB(box.minX, box.minY, box.minZ, box.maxX, box.maxY + dy, box.maxZ); + } + -+ public static net.minecraft.world.phys.AABB expandBackwards(final net.minecraft.world.phys.AABB box, final double dz) { // dz < 0.0 -+ return new net.minecraft.world.phys.AABB(box.minX, box.minY, box.minZ - dz, box.maxX, box.maxY, box.maxZ); ++ public static AABB expandDownwards(final AABB box, final double dy) { // dy < 0.0 ++ return new AABB(box.minX, box.minY - dy, box.minZ, box.maxX, box.maxY, box.maxZ); + } + -+ public static net.minecraft.world.phys.AABB cutRight(final net.minecraft.world.phys.AABB box, final double dx) { // dx > 0.0 -+ return new net.minecraft.world.phys.AABB(box.maxX, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ); ++ public static AABB expandForwards(final AABB box, final double dz) { // dz > 0.0 ++ return new AABB(box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ + dz); + } + -+ public static net.minecraft.world.phys.AABB cutLeft(final net.minecraft.world.phys.AABB box, final double dx) { // dx < 0.0 -+ return new net.minecraft.world.phys.AABB(box.minX + dx, box.minY, box.minZ, box.minX, box.maxY, box.maxZ); ++ public static AABB expandBackwards(final AABB box, final double dz) { // dz < 0.0 ++ return new AABB(box.minX, box.minY, box.minZ - dz, box.maxX, box.maxY, box.maxZ); + } + -+ public static net.minecraft.world.phys.AABB cutUpwards(final net.minecraft.world.phys.AABB box, final double dy) { // dy > 0.0 -+ return new net.minecraft.world.phys.AABB(box.minX, box.maxY, box.minZ, box.maxX, box.maxY + dy, box.maxZ); ++ public static AABB cutRight(final AABB box, final double dx) { // dx > 0.0 ++ return new AABB(box.maxX, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ); + } + -+ public static net.minecraft.world.phys.AABB cutDownwards(final net.minecraft.world.phys.AABB box, final double dy) { // dy < 0.0 -+ return new net.minecraft.world.phys.AABB(box.minX, box.minY + dy, box.minZ, box.maxX, box.minY, box.maxZ); ++ public static AABB cutLeft(final AABB box, final double dx) { // dx < 0.0 ++ return new AABB(box.minX + dx, box.minY, box.minZ, box.minX, box.maxY, box.maxZ); + } + -+ public static net.minecraft.world.phys.AABB cutForwards(final net.minecraft.world.phys.AABB box, final double dz) { // dz > 0.0 -+ return new net.minecraft.world.phys.AABB(box.minX, box.minY, box.maxZ, box.maxX, box.maxY, box.maxZ + dz); ++ public static AABB cutUpwards(final AABB box, final double dy) { // dy > 0.0 ++ return new AABB(box.minX, box.maxY, box.minZ, box.maxX, box.maxY + dy, box.maxZ); + } + -+ public static net.minecraft.world.phys.AABB cutBackwards(final net.minecraft.world.phys.AABB box, final double dz) { // dz < 0.0 -+ return new net.minecraft.world.phys.AABB(box.minX, box.minY, box.minZ + dz, box.maxX, box.maxY, box.minZ); ++ public static AABB cutDownwards(final AABB box, final double dy) { // dy < 0.0 ++ return new AABB(box.minX, box.minY + dy, box.minZ, box.maxX, box.minY, box.maxZ); + } + -+ public static double performAABBCollisionsX(final net.minecraft.world.phys.AABB currentBoundingBox, double value, final java.util.List potentialCollisions) { ++ public static AABB cutForwards(final AABB box, final double dz) { // dz > 0.0 ++ return new AABB(box.minX, box.minY, box.maxZ, box.maxX, box.maxY, box.maxZ + dz); ++ } ++ ++ public static AABB cutBackwards(final AABB box, final double dz) { // dz < 0.0 ++ return new AABB(box.minX, box.minY, box.minZ + dz, box.maxX, box.maxY, box.minZ); ++ } ++ ++ public static double performAABBCollisionsX(final AABB currentBoundingBox, double value, final List potentialCollisions) { + for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { + if (Math.abs(value) < COLLISION_EPSILON) { + return 0.0; + } -+ final net.minecraft.world.phys.AABB target = potentialCollisions.get(i); ++ final AABB target = potentialCollisions.get(i); + value = collideX(target, currentBoundingBox, value); + } + -+ return value; ++ return Math.abs(value) < COLLISION_EPSILON ? 0.0 : value; + } + -+ public static double performAABBCollisionsY(final net.minecraft.world.phys.AABB currentBoundingBox, double value, final java.util.List potentialCollisions) { ++ public static double performAABBCollisionsY(final AABB currentBoundingBox, double value, final List potentialCollisions) { + for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { + if (Math.abs(value) < COLLISION_EPSILON) { + return 0.0; + } -+ final net.minecraft.world.phys.AABB target = potentialCollisions.get(i); ++ final AABB target = potentialCollisions.get(i); + value = collideY(target, currentBoundingBox, value); + } + -+ return value; ++ return Math.abs(value) < COLLISION_EPSILON ? 0.0 : value; + } + -+ public static double performAABBCollisionsZ(final net.minecraft.world.phys.AABB currentBoundingBox, double value, final java.util.List potentialCollisions) { ++ public static double performAABBCollisionsZ(final AABB currentBoundingBox, double value, final List potentialCollisions) { + for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { + if (Math.abs(value) < COLLISION_EPSILON) { + return 0.0; + } -+ final net.minecraft.world.phys.AABB target = potentialCollisions.get(i); ++ final AABB target = potentialCollisions.get(i); + value = collideZ(target, currentBoundingBox, value); + } + -+ return value; ++ return Math.abs(value) < COLLISION_EPSILON ? 0.0 : value; + } + -+ public static double performVoxelCollisionsX(final net.minecraft.world.phys.AABB currentBoundingBox, double value, final java.util.List potentialCollisions) { ++ public static double performVoxelCollisionsX(final AABB currentBoundingBox, double value, final List potentialCollisions) { + for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { + if (Math.abs(value) < COLLISION_EPSILON) { + return 0.0; + } -+ final net.minecraft.world.phys.shapes.VoxelShape target = potentialCollisions.get(i); ++ final VoxelShape target = potentialCollisions.get(i); + value = collideX(target, currentBoundingBox, value); + } + -+ return value; ++ return Math.abs(value) < COLLISION_EPSILON ? 0.0 : value; + } + -+ public static double performVoxelCollisionsY(final net.minecraft.world.phys.AABB currentBoundingBox, double value, final java.util.List potentialCollisions) { ++ public static double performVoxelCollisionsY(final AABB currentBoundingBox, double value, final List potentialCollisions) { + for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { + if (Math.abs(value) < COLLISION_EPSILON) { + return 0.0; + } -+ final net.minecraft.world.phys.shapes.VoxelShape target = potentialCollisions.get(i); ++ final VoxelShape target = potentialCollisions.get(i); + value = collideY(target, currentBoundingBox, value); + } + -+ return value; ++ return Math.abs(value) < COLLISION_EPSILON ? 0.0 : value; + } + -+ public static double performVoxelCollisionsZ(final net.minecraft.world.phys.AABB currentBoundingBox, double value, final java.util.List potentialCollisions) { ++ public static double performVoxelCollisionsZ(final AABB currentBoundingBox, double value, final List potentialCollisions) { + for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { + if (Math.abs(value) < COLLISION_EPSILON) { + return 0.0; + } -+ final net.minecraft.world.phys.shapes.VoxelShape target = potentialCollisions.get(i); ++ final VoxelShape target = potentialCollisions.get(i); + value = collideZ(target, currentBoundingBox, value); + } + -+ return value; ++ return Math.abs(value) < COLLISION_EPSILON ? 0.0 : value; + } + -+ public static net.minecraft.world.phys.Vec3 performVoxelCollisions(final net.minecraft.world.phys.Vec3 moveVector, net.minecraft.world.phys.AABB axisalignedbb, final java.util.List potentialCollisions) { ++ public static Vec3 performVoxelCollisions(final Vec3 moveVector, AABB axisalignedbb, final List potentialCollisions) { + double x = moveVector.x; + double y = moveVector.y; + double z = moveVector.z; @@ -16695,10 +17938,10 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + z = performVoxelCollisionsZ(axisalignedbb, z, potentialCollisions); + } + -+ return new net.minecraft.world.phys.Vec3(x, y, z); ++ return new Vec3(x, y, z); + } + -+ public static net.minecraft.world.phys.Vec3 performAABBCollisions(final net.minecraft.world.phys.Vec3 moveVector, net.minecraft.world.phys.AABB axisalignedbb, final java.util.List potentialCollisions) { ++ public static Vec3 performAABBCollisions(final Vec3 moveVector, AABB axisalignedbb, final List potentialCollisions) { + double x = moveVector.x; + double y = moveVector.y; + double z = moveVector.z; @@ -16730,12 +17973,12 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + z = performAABBCollisionsZ(axisalignedbb, z, potentialCollisions); + } + -+ return new net.minecraft.world.phys.Vec3(x, y, z); ++ return new Vec3(x, y, z); + } + -+ public static net.minecraft.world.phys.Vec3 performCollisions(final net.minecraft.world.phys.Vec3 moveVector, net.minecraft.world.phys.AABB axisalignedbb, -+ final java.util.List voxels, -+ final java.util.List aabbs) { ++ public static Vec3 performCollisions(final Vec3 moveVector, AABB axisalignedbb, ++ final List voxels, ++ final List aabbs) { + if (voxels.isEmpty()) { + // fast track only AABBs + return performAABBCollisions(moveVector, axisalignedbb, aabbs); @@ -16776,14 +18019,14 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + z = performVoxelCollisionsZ(axisalignedbb, z, voxels); + } + -+ return new net.minecraft.world.phys.Vec3(x, y, z); ++ return new Vec3(x, y, z); + } + -+ public static boolean isCollidingWithBorder(final net.minecraft.world.level.border.WorldBorder worldborder, final net.minecraft.world.phys.AABB boundingBox) { ++ public static boolean isCollidingWithBorder(final WorldBorder worldborder, final AABB boundingBox) { + return isCollidingWithBorder(worldborder, boundingBox.minX, boundingBox.maxX, boundingBox.minZ, boundingBox.maxZ); + } + -+ public static boolean isCollidingWithBorder(final net.minecraft.world.level.border.WorldBorder worldborder, ++ public static boolean isCollidingWithBorder(final WorldBorder worldborder, + final double boxMinX, final double boxMaxX, + final double boxMinZ, final double boxMaxZ) { + final double borderMinX = Math.floor(worldborder.getMinX()); // -X @@ -16793,8 +18036,8 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + final double borderMaxZ = Math.ceil(worldborder.getMaxZ()); // +Z + + // inverted check for world border enclosing the specified box expanded by -EPSILON -+ return (borderMinX - boxMinX) > ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON || (borderMaxX - boxMaxX) < -ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON || -+ (borderMinZ - boxMinZ) > ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON || (borderMaxZ - boxMaxZ) < -ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON; ++ return (borderMinX - boxMinX) > CollisionUtil.COLLISION_EPSILON || (borderMaxX - boxMaxX) < -CollisionUtil.COLLISION_EPSILON || ++ (borderMinZ - boxMinZ) > CollisionUtil.COLLISION_EPSILON || (borderMaxZ - boxMaxZ) < -CollisionUtil.COLLISION_EPSILON; + } + + /* Math.max/min specify that any NaN argument results in a NaN return, unlike these functions */ @@ -16811,38 +18054,38 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + public static final int COLLISION_FLAG_CHECK_BORDER = 1 << 2; + public static final int COLLISION_FLAG_CHECK_ONLY = 1 << 3; + -+ public static boolean getCollisionsForBlocksOrWorldBorder(final net.minecraft.world.level.Level world, final net.minecraft.world.entity.Entity entity, final net.minecraft.world.phys.AABB aabb, -+ final java.util.List intoVoxel, final java.util.List intoAABB, -+ final int collisionFlags, final java.util.function.BiPredicate predicate) { ++ public static boolean getCollisionsForBlocksOrWorldBorder(final Level world, final Entity entity, final AABB aabb, ++ final List intoVoxel, final List intoAABB, ++ final int collisionFlags, final BiPredicate predicate) { + final boolean checkOnly = (collisionFlags & COLLISION_FLAG_CHECK_ONLY) != 0; + boolean ret = false; + + if ((collisionFlags & COLLISION_FLAG_CHECK_BORDER) != 0) { -+ final net.minecraft.world.level.border.WorldBorder worldBorder = world.getWorldBorder(); -+ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isCollidingWithBorder(worldBorder, aabb) && entity != null && worldBorder.isInsideCloseToBorder(entity, aabb)) { ++ final WorldBorder worldBorder = world.getWorldBorder(); ++ if (CollisionUtil.isCollidingWithBorder(worldBorder, aabb) && entity != null && worldBorder.isInsideCloseToBorder(entity, aabb)) { + if (checkOnly) { + return true; + } else { -+ final net.minecraft.world.phys.shapes.VoxelShape borderShape = worldBorder.getCollisionShape(); ++ final VoxelShape borderShape = worldBorder.getCollisionShape(); + intoVoxel.add(borderShape); + ret = true; + } + } + } + -+ final int minSection = ((ca.spottedleaf.moonrise.patches.collisions.world.CollisionLevel)world).moonrise$getMinSection(); ++ final int minSection = WorldUtil.getMinSection(world); + -+ final int minBlockX = net.minecraft.util.Mth.floor(aabb.minX - COLLISION_EPSILON) - 1; -+ final int maxBlockX = net.minecraft.util.Mth.floor(aabb.maxX + COLLISION_EPSILON) + 1; ++ final int minBlockX = Mth.floor(aabb.minX - COLLISION_EPSILON) - 1; ++ final int maxBlockX = Mth.floor(aabb.maxX + COLLISION_EPSILON) + 1; + -+ final int minBlockY = Math.max((minSection << 4) - 1, net.minecraft.util.Mth.floor(aabb.minY - COLLISION_EPSILON) - 1); -+ final int maxBlockY = Math.min((((ca.spottedleaf.moonrise.patches.collisions.world.CollisionLevel)world).moonrise$getMaxSection() << 4) + 16, net.minecraft.util.Mth.floor(aabb.maxY + COLLISION_EPSILON) + 1); ++ final int minBlockY = Math.max((minSection << 4) - 1, Mth.floor(aabb.minY - COLLISION_EPSILON) - 1); ++ final int maxBlockY = Math.min((WorldUtil.getMaxSection(world) << 4) + 16, Mth.floor(aabb.maxY + COLLISION_EPSILON) + 1); + -+ final int minBlockZ = net.minecraft.util.Mth.floor(aabb.minZ - COLLISION_EPSILON) - 1; -+ final int maxBlockZ = net.minecraft.util.Mth.floor(aabb.maxZ + COLLISION_EPSILON) + 1; ++ final int minBlockZ = Mth.floor(aabb.minZ - COLLISION_EPSILON) - 1; ++ final int maxBlockZ = Mth.floor(aabb.maxZ + COLLISION_EPSILON) + 1; + -+ final net.minecraft.core.BlockPos.MutableBlockPos mutablePos = new net.minecraft.core.BlockPos.MutableBlockPos(); -+ final net.minecraft.world.phys.shapes.CollisionContext collisionShape = new ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.LazyEntityCollisionContext(entity); ++ final BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos(); ++ final CollisionContext collisionShape = new LazyEntityCollisionContext(entity); + + // special cases: + if (minBlockY > maxBlockY) { @@ -16860,11 +18103,11 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + final int maxChunkZ = maxBlockZ >> 4; + + final boolean loadChunks = (collisionFlags & COLLISION_FLAG_LOAD_CHUNKS) != 0; -+ final net.minecraft.world.level.chunk.ChunkSource chunkSource = world.getChunkSource(); ++ final ChunkSource chunkSource = world.getChunkSource(); + + for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) { + for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) { -+ final net.minecraft.world.level.chunk.ChunkAccess chunk = chunkSource.getChunk(currChunkX, currChunkZ, net.minecraft.world.level.chunk.status.ChunkStatus.FULL, loadChunks); ++ final ChunkAccess chunk = chunkSource.getChunk(currChunkX, currChunkZ, ChunkStatus.FULL, loadChunks); + + if (chunk == null) { + if ((collisionFlags & COLLISION_FLAG_COLLIDE_WITH_UNLOADED_CHUNKS) != 0) { @@ -16878,7 +18121,7 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + continue; + } + -+ final net.minecraft.world.level.chunk.LevelChunkSection[] sections = chunk.getSections(); ++ final LevelChunkSection[] sections = chunk.getSections(); + + // bound y + for (int currChunkY = minChunkY; currChunkY <= maxChunkY; ++currChunkY) { @@ -16886,16 +18129,16 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + if (sectionIdx < 0 || sectionIdx >= sections.length) { + continue; + } -+ final net.minecraft.world.level.chunk.LevelChunkSection section = sections[sectionIdx]; -+ if (section == null || section.hasOnlyAir()) { ++ final LevelChunkSection section = sections[sectionIdx]; ++ if (section.hasOnlyAir()) { + // empty + continue; + } + -+ final boolean hasSpecial = ((ca.spottedleaf.moonrise.patches.block_counting.BlockCountingChunkSection)section).moonrise$getSpecialCollidingBlocks() != 0; ++ final boolean hasSpecial = ((BlockCountingChunkSection)section).moonrise$hasSpecialCollidingBlocks(); + final int sectionAdjust = !hasSpecial ? 1 : 0; + -+ final net.minecraft.world.level.chunk.PalettedContainer blocks = section.states; ++ final PalettedContainer blocks = section.states; + + final int minXIterate = currChunkX == minChunkX ? (minBlockX & 15) + sectionAdjust : 0; + final int maxXIterate = currChunkX == maxChunkX ? (maxBlockX & 15) - sectionAdjust : 15; @@ -16919,21 +18162,21 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + continue; + } + -+ final net.minecraft.world.level.block.state.BlockState blockData = blocks.get(localBlockIndex); ++ final BlockState blockData = blocks.get(localBlockIndex); + -+ if (((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)blockData).moonrise$emptyCollisionShape()) { ++ if (((CollisionBlockState)blockData).moonrise$emptyContextCollisionShape()) { + continue; + } + -+ net.minecraft.world.phys.shapes.VoxelShape blockCollision = ((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)blockData).moonrise$getConstantCollisionShape(); ++ VoxelShape blockCollision = ((CollisionBlockState)blockData).moonrise$getConstantContextCollisionShape(); + -+ if (edgeCount == 0 || ((edgeCount != 1 || blockData.hasLargeCollisionShape()) && (edgeCount != 2 || blockData.getBlock() == net.minecraft.world.level.block.Blocks.MOVING_PISTON))) { ++ if (edgeCount == 0 || ((edgeCount != 1 || blockData.hasLargeCollisionShape()) && (edgeCount != 2 || blockData.getBlock() == Blocks.MOVING_PISTON))) { + if (blockCollision == null) { + mutablePos.set(blockX, blockY, blockZ); + blockCollision = blockData.getCollisionShape(world, mutablePos, collisionShape); + } + -+ net.minecraft.world.phys.AABB singleAABB = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)blockCollision).moonrise$getSingleAABBRepresentation(); ++ AABB singleAABB = ((CollisionVoxelShape)blockCollision).moonrise$getSingleAABBRepresentation(); + if (singleAABB != null) { + singleAABB = singleAABB.move((double)blockX, (double)blockY, (double)blockZ); + if (!voxelShapeIntersect(aabb, singleAABB)) { @@ -16960,7 +18203,7 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + continue; + } + -+ final net.minecraft.world.phys.shapes.VoxelShape blockCollisionOffset = blockCollision.move((double)blockX, (double)blockY, (double)blockZ); ++ final VoxelShape blockCollisionOffset = blockCollision.move((double)blockX, (double)blockY, (double)blockZ); + + if (!voxelShapeIntersectNoEmpty(blockCollisionOffset, aabb)) { + continue; @@ -16991,8 +18234,8 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + return ret; + } + -+ public static boolean getEntityHardCollisions(final net.minecraft.world.level.Level world, final net.minecraft.world.entity.Entity entity, net.minecraft.world.phys.AABB aabb, -+ final java.util.List into, final int collisionFlags, final java.util.function.Predicate predicate) { ++ public static boolean getEntityHardCollisions(final Level world, final Entity entity, AABB aabb, ++ final List into, final int collisionFlags, final Predicate predicate) { + final boolean checkOnly = (collisionFlags & COLLISION_FLAG_CHECK_ONLY) != 0; + + boolean ret = false; @@ -17001,15 +18244,15 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + // Vanilla for hard collisions has this backwards, and they expand by +epsilon but this causes terrible problems + // specifically with boat collisions. + aabb = aabb.inflate(-COLLISION_EPSILON, -COLLISION_EPSILON, -COLLISION_EPSILON); -+ final java.util.List entities; -+ if (entity != null && ((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity)entity).moonrise$isHardColliding()) { ++ final List entities; ++ if (entity != null && ((ChunkSystemEntity)entity).moonrise$isHardColliding()) { + entities = world.getEntities(entity, aabb, predicate); + } else { -+ entities = ((ca.spottedleaf.moonrise.patches.chunk_system.world.ChunkSystemEntityGetter)world).moonrise$getHardCollidingEntities(entity, aabb, predicate); ++ entities = ((ChunkSystemEntityGetter)world).moonrise$getHardCollidingEntities(entity, aabb, predicate); + } + + for (int i = 0, len = entities.size(); i < len; ++i) { -+ final net.minecraft.world.entity.Entity otherEntity = entities.get(i); ++ final Entity otherEntity = entities.get(i); + + if (otherEntity.isSpectator()) { + continue; @@ -17028,10 +18271,10 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + return ret; + } + -+ public static boolean getCollisions(final net.minecraft.world.level.Level world, final net.minecraft.world.entity.Entity entity, final net.minecraft.world.phys.AABB aabb, -+ final java.util.List intoVoxel, final java.util.List intoAABB, final int collisionFlags, -+ final java.util.function.BiPredicate blockPredicate, -+ final java.util.function.Predicate entityPredicate) { ++ public static boolean getCollisions(final Level world, final Entity entity, final AABB aabb, ++ final List intoVoxel, final List intoAABB, final int collisionFlags, ++ final BiPredicate blockPredicate, ++ final Predicate entityPredicate) { + if ((collisionFlags & COLLISION_FLAG_CHECK_ONLY) != 0) { + return getCollisionsForBlocksOrWorldBorder(world, entity, aabb, intoVoxel, intoAABB, collisionFlags, blockPredicate) + || getEntityHardCollisions(world, entity, aabb, intoAABB, collisionFlags, entityPredicate); @@ -17041,12 +18284,12 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + } + } + -+ public static final class LazyEntityCollisionContext extends net.minecraft.world.phys.shapes.EntityCollisionContext { ++ public static final class LazyEntityCollisionContext extends EntityCollisionContext { + -+ private net.minecraft.world.phys.shapes.CollisionContext delegate; ++ private CollisionContext delegate; + private boolean delegated; + -+ public LazyEntityCollisionContext(final net.minecraft.world.entity.Entity entity) { ++ public LazyEntityCollisionContext(final Entity entity) { + super(false, 0.0, null, null, entity); + } + @@ -17056,10 +18299,10 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + return delegated; + } + -+ public net.minecraft.world.phys.shapes.CollisionContext getDelegate() { ++ public CollisionContext getDelegate() { + this.delegated = true; -+ final net.minecraft.world.entity.Entity entity = this.getEntity(); -+ return this.delegate == null ? this.delegate = (entity == null ? net.minecraft.world.phys.shapes.CollisionContext.empty() : net.minecraft.world.phys.shapes.CollisionContext.of(entity)) : this.delegate; ++ final Entity entity = this.getEntity(); ++ return this.delegate == null ? this.delegate = (entity == null ? CollisionContext.empty() : CollisionContext.of(entity)) : this.delegate; + } + + @Override @@ -17068,17 +18311,17 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a + } + + @Override -+ public boolean isAbove(final net.minecraft.world.phys.shapes.VoxelShape shape, final net.minecraft.core.BlockPos pos, final boolean defaultValue) { ++ public boolean isAbove(final VoxelShape shape, final BlockPos pos, final boolean defaultValue) { + return this.getDelegate().isAbove(shape, pos, defaultValue); + } + + @Override -+ public boolean isHoldingItem(final net.minecraft.world.item.Item item) { ++ public boolean isHoldingItem(final Item item) { + return this.getDelegate().isHoldingItem(item); + } + + @Override -+ public boolean canStandOnFluid(final net.minecraft.world.level.material.FluidState state, final net.minecraft.world.level.material.FluidState fluidState) { ++ public boolean canStandOnFluid(final FluidState state, final FluidState fluidState) { + return this.getDelegate().canStandOnFluid(state, fluidState); + } + } @@ -17089,25 +18332,30 @@ index 0000000000000000000000000000000000000000..748ab4d637ce463272bae4fdbab6842a +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/ExplosionBlockCache.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/ExplosionBlockCache.java new file mode 100644 -index 0000000000000000000000000000000000000000..eb7200657d5c7ac37ee93868ba43be0aefecac6d +index 0000000000000000000000000000000000000000..35c8aaf0bfa42717f45eed1d1072e1614874de91 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/ExplosionBlockCache.java -@@ -0,0 +1,23 @@ +@@ -0,0 +1,28 @@ +package ca.spottedleaf.moonrise.patches.collisions; + ++import net.minecraft.core.BlockPos; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.material.FluidState; ++import net.minecraft.world.phys.shapes.VoxelShape; ++ +public final class ExplosionBlockCache { + + public final long key; -+ public final net.minecraft.core.BlockPos immutablePos; -+ public final net.minecraft.world.level.block.state.BlockState blockState; -+ public final net.minecraft.world.level.material.FluidState fluidState; ++ public final BlockPos immutablePos; ++ public final BlockState blockState; ++ public final FluidState fluidState; + public final float resistance; + public final boolean outOfWorld; + public Boolean shouldExplode; // null -> not called yet -+ public net.minecraft.world.phys.shapes.VoxelShape cachedCollisionShape; ++ public VoxelShape cachedCollisionShape; + -+ public ExplosionBlockCache(final long key, final net.minecraft.core.BlockPos immutablePos, final net.minecraft.world.level.block.state.BlockState blockState, -+ final net.minecraft.world.level.material.FluidState fluidState, final float resistance, final boolean outOfWorld) { ++ public ExplosionBlockCache(final long key, final BlockPos immutablePos, final BlockState blockState, ++ final FluidState fluidState, final float resistance, final boolean outOfWorld) { + this.key = key; + this.immutablePos = immutablePos; + this.blockState = blockState; @@ -17118,12 +18366,14 @@ index 0000000000000000000000000000000000000000..eb7200657d5c7ac37ee93868ba43be0a +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/block/CollisionBlockState.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/block/CollisionBlockState.java new file mode 100644 -index 0000000000000000000000000000000000000000..02b29c563a298e06186de010de68a716bccba494 +index 0000000000000000000000000000000000000000..a38ab583200ebf68ca68fdddf2d12077720b72b7 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/block/CollisionBlockState.java -@@ -0,0 +1,26 @@ +@@ -0,0 +1,29 @@ +package ca.spottedleaf.moonrise.patches.collisions.block; + ++import net.minecraft.world.phys.shapes.VoxelShape; ++ +public interface CollisionBlockState { + + // note: this does not consider canOcclude, it is only based on the cached collision shape (i.e hasCache()) @@ -17133,6 +18383,9 @@ index 0000000000000000000000000000000000000000..02b29c563a298e06186de010de68a716 + // whether the cached collision shape exists and is empty + public boolean moonrise$emptyCollisionShape(); + ++ // whether the context-sensitive shape is constant and is empty ++ public boolean moonrise$emptyContextCollisionShape(); ++ + // indicates that occludesFullBlock is cached for the collision shape + public boolean moonrise$hasCache(); + @@ -17144,9 +18397,7 @@ index 0000000000000000000000000000000000000000..02b29c563a298e06186de010de68a716 + // value is still unique + public int moonrise$uniqueId2(); + -+ public net.minecraft.world.phys.shapes.VoxelShape moonrise$getConstantCollisionShape(); -+ -+ public net.minecraft.world.phys.AABB moonrise$getConstantCollisionAABB(); ++ public VoxelShape moonrise$getConstantContextCollisionShape(); +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CachedShapeData.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CachedShapeData.java new file mode 100644 @@ -17166,34 +18417,38 @@ index 0000000000000000000000000000000000000000..5a6b16be4b8c0cc92d017bc592bc4818 +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CachedToAABBs.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CachedToAABBs.java new file mode 100644 -index 0000000000000000000000000000000000000000..5fe1dad9dad368911aedbe6ba7fcd8f9b0189d32 +index 0000000000000000000000000000000000000000..9d33ead3a97d86b371e4d9ad9fed80d789bed844 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CachedToAABBs.java -@@ -0,0 +1,35 @@ +@@ -0,0 +1,39 @@ +package ca.spottedleaf.moonrise.patches.collisions.shape; + ++import net.minecraft.world.phys.AABB; ++import java.util.ArrayList; ++import java.util.List; ++ +public record CachedToAABBs( -+ java.util.List aabbs, ++ List aabbs, + boolean isOffset, + double offX, double offY, double offZ +) { + -+ public ca.spottedleaf.moonrise.patches.collisions.shape.CachedToAABBs removeOffset() { -+ final java.util.List toOffset = this.aabbs; ++ public CachedToAABBs removeOffset() { ++ final List toOffset = this.aabbs; + final double offX = this.offX; + final double offY = this.offY; + final double offZ = this.offZ; + -+ final java.util.List ret = new java.util.ArrayList<>(toOffset.size()); ++ final List ret = new ArrayList<>(toOffset.size()); + + for (int i = 0, len = toOffset.size(); i < len; ++i) { + ret.add(toOffset.get(i).move(offX, offY, offZ)); + } + -+ return new ca.spottedleaf.moonrise.patches.collisions.shape.CachedToAABBs(ret, false, 0.0, 0.0, 0.0); ++ return new CachedToAABBs(ret, false, 0.0, 0.0, 0.0); + } + -+ public static ca.spottedleaf.moonrise.patches.collisions.shape.CachedToAABBs offset(final ca.spottedleaf.moonrise.patches.collisions.shape.CachedToAABBs cache, final double offX, final double offY, final double offZ) { ++ public static CachedToAABBs offset(final CachedToAABBs cache, final double offX, final double offY, final double offZ) { + if (offX == 0.0 && offY == 0.0 && offZ == 0.0) { + return cache; + } @@ -17202,12 +18457,12 @@ index 0000000000000000000000000000000000000000..5fe1dad9dad368911aedbe6ba7fcd8f9 + final double resY = cache.offY + offY; + final double resZ = cache.offZ + offZ; + -+ return new ca.spottedleaf.moonrise.patches.collisions.shape.CachedToAABBs(cache.aabbs, true, resX, resY, resZ); ++ return new CachedToAABBs(cache.aabbs, true, resX, resY, resZ); + } +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionDiscreteVoxelShape.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionDiscreteVoxelShape.java new file mode 100644 -index 0000000000000000000000000000000000000000..a09efadea9b733840bbe69830dd8f2a303fe656f +index 0000000000000000000000000000000000000000..07fe5e02c2d0a27d2fe37bb45761654dc2d02e5d --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionDiscreteVoxelShape.java @@ -0,0 +1,7 @@ @@ -17215,17 +18470,21 @@ index 0000000000000000000000000000000000000000..a09efadea9b733840bbe69830dd8f2a3 + +public interface CollisionDiscreteVoxelShape { + -+ public ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData moonrise$getOrCreateCachedShapeData(); ++ public CachedShapeData moonrise$getOrCreateCachedShapeData(); + +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionVoxelShape.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionVoxelShape.java new file mode 100644 -index 0000000000000000000000000000000000000000..70371eb87c11a106e8513cdbc8d938dda088f745 +index 0000000000000000000000000000000000000000..05d7b3f9d8659c259f3ed0537c57e6e43eb6e288 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionVoxelShape.java -@@ -0,0 +1,36 @@ +@@ -0,0 +1,40 @@ +package ca.spottedleaf.moonrise.patches.collisions.shape; + ++import net.minecraft.core.Direction; ++import net.minecraft.world.phys.AABB; ++import net.minecraft.world.phys.shapes.VoxelShape; ++ +public interface CollisionVoxelShape { + + public double moonrise$offsetX(); @@ -17240,16 +18499,16 @@ index 0000000000000000000000000000000000000000..70371eb87c11a106e8513cdbc8d938dd + + public double[] moonrise$rootCoordinatesZ(); + -+ public ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData moonrise$getCachedVoxelData(); ++ public CachedShapeData moonrise$getCachedVoxelData(); + + // rets null if not possible to represent this shape as one AABB -+ public net.minecraft.world.phys.AABB moonrise$getSingleAABBRepresentation(); ++ public AABB moonrise$getSingleAABBRepresentation(); + + // ONLY USE INTERNALLY, ONLY FOR INITIALISING IN CONSTRUCTOR: VOXELSHAPES ARE STATIC + public void moonrise$initCache(); + + // this returns empty if not clamped to 1.0 or 0.0 depending on direction -+ public net.minecraft.world.phys.shapes.VoxelShape moonrise$getFaceShapeClamped(final net.minecraft.core.Direction direction); ++ public VoxelShape moonrise$getFaceShapeClamped(final Direction direction); + + public boolean moonrise$isFullBlock(); + @@ -17258,19 +18517,21 @@ index 0000000000000000000000000000000000000000..70371eb87c11a106e8513cdbc8d938dd + public boolean moonrise$occludesFullBlockIfCached(); + + // uses a cache internally -+ public net.minecraft.world.phys.shapes.VoxelShape moonrise$orUnoptimized(final net.minecraft.world.phys.shapes.VoxelShape other); ++ public VoxelShape moonrise$orUnoptimized(final VoxelShape other); +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/MergedORCache.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/MergedORCache.java new file mode 100644 -index 0000000000000000000000000000000000000000..4217426d3eca5e5cd2bc37e509f84da1d6fed0b2 +index 0000000000000000000000000000000000000000..44831fc18efb7534dc6e4822f3c9b5cdc4dcc33e --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/MergedORCache.java -@@ -0,0 +1,8 @@ +@@ -0,0 +1,10 @@ +package ca.spottedleaf.moonrise.patches.collisions.shape; + ++import net.minecraft.world.phys.shapes.VoxelShape; ++ +public record MergedORCache( -+ net.minecraft.world.phys.shapes.VoxelShape key, -+ net.minecraft.world.phys.shapes.VoxelShape result ++ VoxelShape key, ++ VoxelShape result +) { + +} @@ -17289,261 +18550,18 @@ index 0000000000000000000000000000000000000000..f62359e5d6aa9a9cdb015441dbdb6182 + public int moonrise$uniqueId(); + +} -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/EmptyStreamForMoveCall.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/EmptyStreamForMoveCall.java -new file mode 100644 -index 0000000000000000000000000000000000000000..673103f160cbe577c6e05f998706af4e6850011b ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/EmptyStreamForMoveCall.java -@@ -0,0 +1,225 @@ -+package ca.spottedleaf.moonrise.patches.collisions.util; -+ -+import java.util.Iterator; -+import java.util.Optional; -+import java.util.Spliterator; -+import java.util.stream.Stream; -+ -+public final class EmptyStreamForMoveCall implements java.util.stream.Stream { -+ -+ public static final ca.spottedleaf.moonrise.patches.collisions.util.EmptyStreamForMoveCall INSTANCE = new ca.spottedleaf.moonrise.patches.collisions.util.EmptyStreamForMoveCall(); -+ -+ @Override -+ public boolean noneMatch(java.util.function.Predicate predicate) { -+ return false; // important: ret false so the branch is never taken by mojang code -+ } -+ -+ @Override -+ public java.util.stream.Stream filter(java.util.function.Predicate predicate) { -+ return null; -+ } -+ -+ @Override -+ public java.util.stream.Stream map(java.util.function.Function mapper) { -+ return null; -+ } -+ -+ @Override -+ public java.util.stream.IntStream mapToInt(java.util.function.ToIntFunction mapper) { -+ return null; -+ } -+ -+ @Override -+ public java.util.stream.LongStream mapToLong(java.util.function.ToLongFunction mapper) { -+ return null; -+ } -+ -+ @Override -+ public java.util.stream.DoubleStream mapToDouble(java.util.function.ToDoubleFunction mapper) { -+ return null; -+ } -+ -+ @Override -+ public java.util.stream.Stream flatMap(java.util.function.Function> mapper) { -+ return null; -+ } -+ -+ @Override -+ public java.util.stream.IntStream flatMapToInt(java.util.function.Function mapper) { -+ return null; -+ } -+ -+ @Override -+ public java.util.stream.LongStream flatMapToLong(java.util.function.Function mapper) { -+ return null; -+ } -+ -+ @Override -+ public java.util.stream.DoubleStream flatMapToDouble(java.util.function.Function mapper) { -+ return null; -+ } -+ -+ @Override -+ public java.util.stream.Stream distinct() { -+ return null; -+ } -+ -+ @Override -+ public java.util.stream.Stream sorted() { -+ return null; -+ } -+ -+ @Override -+ public java.util.stream.Stream sorted(java.util.Comparator comparator) { -+ return null; -+ } -+ -+ @Override -+ public java.util.stream.Stream peek(java.util.function.Consumer action) { -+ return null; -+ } -+ -+ @Override -+ public java.util.stream.Stream limit(long maxSize) { -+ return null; -+ } -+ -+ @Override -+ public java.util.stream.Stream skip(long n) { -+ return null; -+ } -+ -+ @Override -+ public void forEach(java.util.function.Consumer action) { -+ -+ } -+ -+ @Override -+ public void forEachOrdered(java.util.function.Consumer action) { -+ -+ } -+ -+ @org.jetbrains.annotations.NotNull -+ @Override -+ public Object[] toArray() { -+ return new Object[0]; -+ } -+ -+ @org.jetbrains.annotations.NotNull -+ @Override -+ public A[] toArray(java.util.function.IntFunction generator) { -+ return null; -+ } -+ -+ @Override -+ public T reduce(T identity, java.util.function.BinaryOperator accumulator) { -+ return null; -+ } -+ -+ @org.jetbrains.annotations.NotNull -+ @Override -+ public Optional reduce(java.util.function.BinaryOperator accumulator) { -+ return java.util.Optional.empty(); -+ } -+ -+ @Override -+ public U reduce(U identity, java.util.function.BiFunction accumulator, java.util.function.BinaryOperator combiner) { -+ return null; -+ } -+ -+ @Override -+ public R collect(java.util.function.Supplier supplier, java.util.function.BiConsumer accumulator, java.util.function.BiConsumer combiner) { -+ return null; -+ } -+ -+ @Override -+ public R collect(java.util.stream.Collector collector) { -+ return null; -+ } -+ -+ @org.jetbrains.annotations.NotNull -+ @Override -+ public Optional min(java.util.Comparator comparator) { -+ return java.util.Optional.empty(); -+ } -+ -+ @org.jetbrains.annotations.NotNull -+ @Override -+ public Optional max(java.util.Comparator comparator) { -+ return java.util.Optional.empty(); -+ } -+ -+ @Override -+ public long count() { -+ return 0; -+ } -+ -+ @Override -+ public boolean anyMatch(java.util.function.Predicate predicate) { -+ return false; -+ } -+ -+ @Override -+ public boolean allMatch(java.util.function.Predicate predicate) { -+ return false; -+ } -+ -+ @org.jetbrains.annotations.NotNull -+ @Override -+ public Optional findFirst() { -+ return java.util.Optional.empty(); -+ } -+ -+ @org.jetbrains.annotations.NotNull -+ @Override -+ public Optional findAny() { -+ return java.util.Optional.empty(); -+ } -+ -+ -+ @org.jetbrains.annotations.NotNull -+ @Override -+ public Iterator iterator() { -+ return null; -+ } -+ -+ @org.jetbrains.annotations.NotNull -+ @Override -+ public Spliterator spliterator() { -+ return null; -+ } -+ -+ @Override -+ public boolean isParallel() { -+ return false; -+ } -+ -+ @org.jetbrains.annotations.NotNull -+ @Override -+ public Stream sequential() { -+ return null; -+ } -+ -+ @org.jetbrains.annotations.NotNull -+ @Override -+ public Stream parallel() { -+ return null; -+ } -+ -+ @org.jetbrains.annotations.NotNull -+ @Override -+ public Stream unordered() { -+ return null; -+ } -+ -+ @org.jetbrains.annotations.NotNull -+ @Override -+ public Stream onClose(Runnable closeHandler) { -+ return null; -+ } -+ -+ @Override -+ public void close() { -+ -+ } -+} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/FluidOcclusionCacheKey.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/FluidOcclusionCacheKey.java new file mode 100644 -index 0000000000000000000000000000000000000000..128267ff40b38c7b3ea0feb5133825cc6aae075b +index 0000000000000000000000000000000000000000..cf9ffdeff6bf0b62a45f7a44dbfe0dd7d17dc4f4 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/FluidOcclusionCacheKey.java -@@ -0,0 +1,4 @@ +@@ -0,0 +1,7 @@ +package ca.spottedleaf.moonrise.patches.collisions.util; + -+public record FluidOcclusionCacheKey(net.minecraft.world.level.block.state.BlockState first, net.minecraft.world.level.block.state.BlockState second, net.minecraft.core.Direction direction, boolean result) { -+} -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/world/CollisionLevel.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/world/CollisionLevel.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e851e81e13edbad6316df63fcb7095d48f85c5b0 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/world/CollisionLevel.java -@@ -0,0 +1,9 @@ -+package ca.spottedleaf.moonrise.patches.collisions.world; -+ -+public interface CollisionLevel { -+ -+ public int moonrise$getMinSection(); -+ -+ public int moonrise$getMaxSection(); ++import net.minecraft.core.Direction; ++import net.minecraft.world.level.block.state.BlockState; + ++public record FluidOcclusionCacheKey(BlockState first, BlockState second, Direction direction, boolean result) { +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/entity_tracker/EntityTrackerEntity.java b/src/main/java/ca/spottedleaf/moonrise/patches/entity_tracker/EntityTrackerEntity.java new file mode 100644 @@ -17564,10 +18582,10 @@ index 0000000000000000000000000000000000000000..5f5734c00ce8245a1ff69b2d4c303657 +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/entity_tracker/EntityTrackerTrackedEntity.java b/src/main/java/ca/spottedleaf/moonrise/patches/entity_tracker/EntityTrackerTrackedEntity.java new file mode 100644 -index 0000000000000000000000000000000000000000..1fa07bef57d82c6d5242aaaf66011f0913515231 +index 0000000000000000000000000000000000000000..8e7472157a98de607c03769a91f64c8369fd3ea6 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/entity_tracker/EntityTrackerTrackedEntity.java -@@ -0,0 +1,13 @@ +@@ -0,0 +1,15 @@ +package ca.spottedleaf.moonrise.patches.entity_tracker; + +import ca.spottedleaf.moonrise.common.misc.NearbyPlayers; @@ -17580,21 +18598,77 @@ index 0000000000000000000000000000000000000000..1fa07bef57d82c6d5242aaaf66011f09 + + public void moonrise$clearPlayers(); + ++ public boolean moonrise$hasPlayers(); ++ ++} +diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/fast_palette/FastPalette.java b/src/main/java/ca/spottedleaf/moonrise/patches/fast_palette/FastPalette.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4a7abd239a9c59aa98947e7993962d75e9051902 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/patches/fast_palette/FastPalette.java +@@ -0,0 +1,9 @@ ++package ca.spottedleaf.moonrise.patches.fast_palette; ++ ++public interface FastPalette { ++ ++ public default T[] moonrise$getRawPalette(final FastPaletteData src) { ++ return null; ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/fast_palette/FastPaletteData.java b/src/main/java/ca/spottedleaf/moonrise/patches/fast_palette/FastPaletteData.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4503f3495846a7d7ed082b9e24636044e4fbccd1 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/patches/fast_palette/FastPaletteData.java +@@ -0,0 +1,9 @@ ++package ca.spottedleaf.moonrise.patches.fast_palette; ++ ++public interface FastPaletteData { ++ ++ public T[] moonrise$getPalette(); ++ ++ public void moonrise$setPalette(final T[] palette); ++ ++} +diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/fluid/FluidFluidState.java b/src/main/java/ca/spottedleaf/moonrise/patches/fluid/FluidFluidState.java +new file mode 100644 +index 0000000000000000000000000000000000000000..107c97089354edd35f330582f5e0c8a18e792a6e +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/patches/fluid/FluidFluidState.java +@@ -0,0 +1,5 @@ ++package ca.spottedleaf.moonrise.patches.fluid; ++ ++public interface FluidFluidState { ++ public void moonrise$initCaches(); ++} +diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/getblock/GetBlockChunk.java b/src/main/java/ca/spottedleaf/moonrise/patches/getblock/GetBlockChunk.java +new file mode 100644 +index 0000000000000000000000000000000000000000..540c14a6d2c216cd3ef2a9c4056e15712bf8cb8c +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/patches/getblock/GetBlockChunk.java +@@ -0,0 +1,9 @@ ++package ca.spottedleaf.moonrise.patches.getblock; ++ ++import net.minecraft.world.level.block.state.BlockState; ++ ++public interface GetBlockChunk { ++ ++ public BlockState moonrise$getBlock(final int x, final int y, final int z); ++ +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/blockstate/StarlightAbstractBlockState.java b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/blockstate/StarlightAbstractBlockState.java new file mode 100644 -index 0000000000000000000000000000000000000000..2bfdf3721db9a45e36538d71cbefcb1d339e6c58 +index 0000000000000000000000000000000000000000..8e6d79b7c10ef25f5478b72c53c555423d615a2f --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/blockstate/StarlightAbstractBlockState.java -@@ -0,0 +1,9 @@ +@@ -0,0 +1,7 @@ +package ca.spottedleaf.moonrise.patches.starlight.blockstate; + +public interface StarlightAbstractBlockState { + + public boolean starlight$isConditionallyFullOpaque(); + -+ public int starlight$getOpacityIfCached(); -+ +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/chunk/StarlightChunk.java b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/chunk/StarlightChunk.java new file mode 100644 @@ -17622,15 +18696,17 @@ index 0000000000000000000000000000000000000000..ed80017c8f257b981d626a37ffc5480d +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/BlockStarLightEngine.java b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/BlockStarLightEngine.java new file mode 100644 -index 0000000000000000000000000000000000000000..154443ac1ee1d6d18b8ff0f40a307d638b213aeb +index 0000000000000000000000000000000000000000..fa7b784a89626e8528c249d7889a598bd7ee3d49 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/BlockStarLightEngine.java -@@ -0,0 +1,277 @@ +@@ -0,0 +1,280 @@ +package ca.spottedleaf.moonrise.patches.starlight.light; + ++import ca.spottedleaf.moonrise.common.PlatformHooks; +import ca.spottedleaf.moonrise.patches.starlight.blockstate.StarlightAbstractBlockState; +import ca.spottedleaf.moonrise.patches.starlight.chunk.StarlightChunk; +import net.minecraft.core.BlockPos; ++import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; @@ -17719,7 +18795,7 @@ index 0000000000000000000000000000000000000000..154443ac1ee1d6d18b8ff0f40a307d63 + + final int currentLevel = this.getLightLevel(worldX, worldY, worldZ); + final BlockState blockState = this.getBlockState(worldX, worldY, worldZ); -+ final int emittedLevel = blockState.getLightEmission() & emittedMask; ++ final int emittedLevel = (PlatformHooks.get().getLightEmission(blockState, lightAccess.getLevel(), this.lightEmissionPos.set(worldX, worldY, worldZ))) & emittedMask; + + this.setLightLevel(worldX, worldY, worldZ, emittedLevel); + // this accounts for change in emitted light that would cause an increase @@ -17747,37 +18823,32 @@ index 0000000000000000000000000000000000000000..154443ac1ee1d6d18b8ff0f40a307d63 + } + + protected final BlockPos.MutableBlockPos recalcCenterPos = new BlockPos.MutableBlockPos(); -+ protected final BlockPos.MutableBlockPos recalcNeighbourPos = new BlockPos.MutableBlockPos(); + + @Override + protected int calculateLightValue(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ, + final int expect) { ++ this.recalcCenterPos.set(worldX, worldY, worldZ); ++ + final BlockState centerState = this.getBlockState(worldX, worldY, worldZ); -+ int level = centerState.getLightEmission() & 0xF; ++ final BlockGetter world = lightAccess.getLevel(); ++ int level = (PlatformHooks.get().getLightEmission(centerState, world, this.recalcCenterPos)) & this.emittedLightMask; + + if (level >= (15 - 1) || level > expect) { + return level; + } + -+ final int sectionOffset = this.chunkSectionIndexOffset; -+ final BlockState conditionallyOpaqueState; -+ int opacity = ((StarlightAbstractBlockState)centerState).starlight$getOpacityIfCached(); -+ -+ if (opacity == -1) { -+ this.recalcCenterPos.set(worldX, worldY, worldZ); -+ opacity = centerState.getLightBlock(lightAccess.getLevel(), this.recalcCenterPos); -+ if (((StarlightAbstractBlockState)centerState).starlight$isConditionallyFullOpaque()) { -+ conditionallyOpaqueState = centerState; -+ } else { -+ conditionallyOpaqueState = null; -+ } -+ } else if (opacity >= 15) { ++ final int opacity = Math.max(1, centerState.getLightBlock()); ++ if (opacity >= 15) { + return level; ++ } ++ final BlockState conditionallyOpaqueState; ++ if (((StarlightAbstractBlockState)centerState).starlight$isConditionallyFullOpaque()) { ++ conditionallyOpaqueState = centerState; + } else { + conditionallyOpaqueState = null; + } -+ opacity = Math.max(1, opacity); + ++ final int sectionOffset = this.chunkSectionIndexOffset; + for (final AxisDirection direction : AXIS_DIRECTIONS) { + final int offX = worldX + direction.x; + final int offY = worldY + direction.y; @@ -17797,9 +18868,8 @@ index 0000000000000000000000000000000000000000..154443ac1ee1d6d18b8ff0f40a307d63 + // here the block can be conditionally opaque (i.e light cannot propagate from it), so we need to test that + // we don't read the blockstate because most of the time this is false, so using the faster + // known transparency lookup results in a net win -+ this.recalcNeighbourPos.set(offX, offY, offZ); -+ final VoxelShape neighbourFace = neighbourState.getFaceOcclusionShape(lightAccess.getLevel(), this.recalcNeighbourPos, direction.opposite.nms); -+ final VoxelShape thisFace = conditionallyOpaqueState == null ? Shapes.empty() : conditionallyOpaqueState.getFaceOcclusionShape(lightAccess.getLevel(), this.recalcCenterPos, direction.nms); ++ final VoxelShape neighbourFace = neighbourState.getFaceOcclusionShape(direction.opposite.nms); ++ final VoxelShape thisFace = conditionallyOpaqueState == null ? Shapes.empty() : conditionallyOpaqueState.getFaceOcclusionShape(direction.nms); + if (Shapes.faceShapeOccludes(thisFace, neighbourFace)) { + // not allowed to propagate + continue; @@ -17833,30 +18903,34 @@ index 0000000000000000000000000000000000000000..154443ac1ee1d6d18b8ff0f40a307d63 + final int offX = chunk.getPos().x << 4; + final int offZ = chunk.getPos().z << 4; + ++ final PlatformHooks platformHooks = PlatformHooks.get(); ++ ++ final BlockGetter world = lightAccess.getLevel(); + final LevelChunkSection[] sections = chunk.getSections(); + for (int sectionY = this.minSection; sectionY <= this.maxSection; ++sectionY) { + final LevelChunkSection section = sections[sectionY - this.minSection]; -+ if (section == null || section.hasOnlyAir()) { ++ if (section.hasOnlyAir()) { + // no sources in empty sections + continue; + } -+ if (!section.maybeHas((final BlockState state) -> { -+ return state.getLightEmission() > 0; -+ })) { ++ if (!section.maybeHas(platformHooks.maybeHasLightEmission())) { + // no light sources in palette + continue; + } + final PalettedContainer states = section.states; + final int offY = sectionY << 4; + ++ final BlockPos.MutableBlockPos mutablePos = this.lightEmissionPos; + for (int index = 0; index < (16 * 16 * 16); ++index) { + final BlockState state = states.get(index); -+ if (state.getLightEmission() <= 0) { ++ mutablePos.set(offX | (index & 15), offY | (index >>> 8), offZ | ((index >>> 4) & 15)); ++ ++ if ((platformHooks.getLightEmission(state, world, mutablePos)) == 0) { + continue; + } + + // index = x | (z << 4) | (y << 8) -+ sources.add(new BlockPos(offX | (index & 15), offY | (index >>> 8), offZ | ((index >>> 4) & 15))); ++ sources.add(mutablePos.immutable()); + } + } + @@ -17866,12 +18940,15 @@ index 0000000000000000000000000000000000000000..154443ac1ee1d6d18b8ff0f40a307d63 + @Override + public void lightChunk(final LightChunkGetter lightAccess, final ChunkAccess chunk, final boolean needsEdgeChecks) { + // setup sources ++ final BlockGetter world = lightAccess.getLevel(); ++ final PlatformHooks platformHooks = PlatformHooks.get(); ++ + final int emittedMask = this.emittedLightMask; + final List positions = this.getSources(lightAccess, chunk); + for (int i = 0, len = positions.size(); i < len; ++i) { + final BlockPos pos = positions.get(i); + final BlockState blockState = this.getBlockState(pos.getX(), pos.getY(), pos.getZ()); -+ final int emittedLight = blockState.getLightEmission() & emittedMask; ++ final int emittedLight = platformHooks.getLightEmission(blockState, world, pos) & emittedMask; + + if (emittedLight <= this.getLightLevel(pos.getX(), pos.getY(), pos.getZ())) { + // some other source is brighter @@ -18351,10 +19428,10 @@ index 0000000000000000000000000000000000000000..4ca68a903e67606fc4ef0bfa9862a737 +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/SkyStarLightEngine.java b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/SkyStarLightEngine.java new file mode 100644 -index 0000000000000000000000000000000000000000..fdbc015f498164c9d2c578cd84a73def568142a4 +index 0000000000000000000000000000000000000000..f9aef289e9a2d6f63c98c72c56ef32b8793f57f4 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/SkyStarLightEngine.java -@@ -0,0 +1,711 @@ +@@ -0,0 +1,681 @@ +package ca.spottedleaf.moonrise.patches.starlight.light; + +import ca.spottedleaf.moonrise.common.util.WorldUtil; @@ -18647,9 +19724,6 @@ index 0000000000000000000000000000000000000000..fdbc015f498164c9d2c578cd84a73def + ); + } + -+ protected final BlockPos.MutableBlockPos recalcCenterPos = new BlockPos.MutableBlockPos(); -+ protected final BlockPos.MutableBlockPos recalcNeighbourPos = new BlockPos.MutableBlockPos(); -+ + @Override + protected int calculateLightValue(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ, + final int expect) { @@ -18659,20 +19733,13 @@ index 0000000000000000000000000000000000000000..fdbc015f498164c9d2c578cd84a73def + + final int sectionOffset = this.chunkSectionIndexOffset; + final BlockState centerState = this.getBlockState(worldX, worldY, worldZ); -+ int opacity = ((StarlightAbstractBlockState)centerState).starlight$getOpacityIfCached(); + + final BlockState conditionallyOpaqueState; -+ if (opacity < 0) { -+ this.recalcCenterPos.set(worldX, worldY, worldZ); -+ opacity = Math.max(1, centerState.getLightBlock(lightAccess.getLevel(), this.recalcCenterPos)); -+ if (((StarlightAbstractBlockState)centerState).starlight$isConditionallyFullOpaque()) { -+ conditionallyOpaqueState = centerState; -+ } else { -+ conditionallyOpaqueState = null; -+ } ++ final int opacity = Math.max(1, centerState.getLightBlock()); ++ if (((StarlightAbstractBlockState)centerState).starlight$isConditionallyFullOpaque()) { ++ conditionallyOpaqueState = centerState; + } else { + conditionallyOpaqueState = null; -+ opacity = Math.max(1, opacity); + } + + int level = 0; @@ -18697,9 +19764,8 @@ index 0000000000000000000000000000000000000000..fdbc015f498164c9d2c578cd84a73def + // here the block can be conditionally opaque (i.e light cannot propagate from it), so we need to test that + // we don't read the blockstate because most of the time this is false, so using the faster + // known transparency lookup results in a net win -+ this.recalcNeighbourPos.set(offX, offY, offZ); -+ final VoxelShape neighbourFace = neighbourState.getFaceOcclusionShape(lightAccess.getLevel(), this.recalcNeighbourPos, direction.opposite.nms); -+ final VoxelShape thisFace = conditionallyOpaqueState == null ? Shapes.empty() : conditionallyOpaqueState.getFaceOcclusionShape(lightAccess.getLevel(), this.recalcCenterPos, direction.nms); ++ final VoxelShape neighbourFace = neighbourState.getFaceOcclusionShape(direction.opposite.nms); ++ final VoxelShape thisFace = conditionallyOpaqueState == null ? Shapes.empty() : conditionallyOpaqueState.getFaceOcclusionShape(direction.nms); + if (Shapes.faceShapeOccludes(thisFace, neighbourFace)) { + // not allowed to propagate + continue; @@ -18967,7 +20033,6 @@ index 0000000000000000000000000000000000000000..fdbc015f498164c9d2c578cd84a73def + // clobbering the light values will result in broken propagation) + protected final int tryPropagateSkylight(final BlockGetter world, final int worldX, int startY, final int worldZ, + final boolean extrudeInitialised, final boolean delayLightSet) { -+ final BlockPos.MutableBlockPos mutablePos = this.mutablePos3; + final int encodeOffset = this.coordinateOffset; + final long propagateDirection = AxisDirection.POSITIVE_Y.everythingButThisDirection; // just don't check upwards. + @@ -18989,8 +20054,7 @@ index 0000000000000000000000000000000000000000..fdbc015f498164c9d2c578cd84a73def + + final VoxelShape fromShape; + if (((StarlightAbstractBlockState)above).starlight$isConditionallyFullOpaque()) { -+ this.mutablePos2.set(worldX, startY + 1, worldZ); -+ fromShape = above.getFaceOcclusionShape(world, this.mutablePos2, AxisDirection.NEGATIVE_Y.nms); ++ fromShape = above.getFaceOcclusionShape(AxisDirection.NEGATIVE_Y.nms); + if (Shapes.faceShapeOccludes(Shapes.empty(), fromShape)) { + // above wont let us propagate + break; @@ -18999,49 +20063,32 @@ index 0000000000000000000000000000000000000000..fdbc015f498164c9d2c578cd84a73def + fromShape = Shapes.empty(); + } + -+ final int opacityIfCached = ((StarlightAbstractBlockState)current).starlight$getOpacityIfCached(); + // does light propagate from the top down? -+ if (opacityIfCached != -1) { -+ if (opacityIfCached != 0) { -+ // we cannot propagate 15 through this ++ long flags = 0L; ++ if (((StarlightAbstractBlockState)current).starlight$isConditionallyFullOpaque()) { ++ final VoxelShape cullingFace = current.getFaceOcclusionShape(AxisDirection.POSITIVE_Y.nms); ++ ++ if (Shapes.faceShapeOccludes(fromShape, cullingFace)) { ++ // can't propagate here, we're done on this column. + break; + } -+ // most of the time it falls here. -+ // add to propagate -+ // light set delayed until we determine if this nibble section is null -+ this.appendToIncreaseQueue( -+ ((worldX + (worldZ << 6) + (startY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (15L << (6 + 6 + 16)) // we know we're at full lit here -+ | (propagateDirection << (6 + 6 + 16 + 4)) -+ ); -+ } else { -+ mutablePos.set(worldX, startY, worldZ); -+ long flags = 0L; -+ if (((StarlightAbstractBlockState)current).starlight$isConditionallyFullOpaque()) { -+ final VoxelShape cullingFace = current.getFaceOcclusionShape(world, mutablePos, AxisDirection.POSITIVE_Y.nms); -+ -+ if (Shapes.faceShapeOccludes(fromShape, cullingFace)) { -+ // can't propagate here, we're done on this column. -+ break; -+ } -+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; -+ } -+ -+ final int opacity = current.getLightBlock(world, mutablePos); -+ if (opacity > 0) { -+ // let the queued value (if any) handle it from here. -+ break; -+ } -+ -+ // light set delayed until we determine if this nibble section is null -+ this.appendToIncreaseQueue( -+ ((worldX + (worldZ << 6) + (startY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (15L << (6 + 6 + 16)) // we know we're at full lit here -+ | (propagateDirection << (6 + 6 + 16 + 4)) -+ | flags -+ ); ++ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; + } + ++ final int opacity = current.getLightBlock(); ++ if (opacity > 0) { ++ // let the queued value (if any) handle it from here. ++ break; ++ } ++ ++ // light set delayed until we determine if this nibble section is null ++ this.appendToIncreaseQueue( ++ ((worldX + (worldZ << 6) + (startY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | (15L << (6 + 6 + 16)) // we know we're at full lit here ++ | (propagateDirection << (6 + 6 + 16 + 4)) ++ | flags ++ ); ++ + above = current; + + if (this.getNibbleFromCache(worldX >> 4, startY >> 4, worldZ >> 4) == null) { @@ -19068,13 +20115,14 @@ index 0000000000000000000000000000000000000000..fdbc015f498164c9d2c578cd84a73def +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/StarLightEngine.java b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/StarLightEngine.java new file mode 100644 -index 0000000000000000000000000000000000000000..382c9e445af0d6ad2428fc22d0f63017c58191e2 +index 0000000000000000000000000000000000000000..8aeb5fb87f94a35659347a09a638420699b52a6f --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/StarLightEngine.java -@@ -0,0 +1,1573 @@ +@@ -0,0 +1,1438 @@ +package ca.spottedleaf.moonrise.patches.starlight.light; + +import ca.spottedleaf.concurrentutil.util.IntegerUtil; ++import ca.spottedleaf.moonrise.common.PlatformHooks; +import ca.spottedleaf.moonrise.common.util.CoordinateUtils; +import ca.spottedleaf.moonrise.common.util.WorldUtil; +import ca.spottedleaf.moonrise.patches.starlight.blockstate.StarlightAbstractBlockState; @@ -19117,9 +20165,9 @@ index 0000000000000000000000000000000000000000..382c9e445af0d6ad2428fc22d0f63017 + protected static enum AxisDirection { + + // Declaration order is important and relied upon. Do not change without modifying propagation code. -+ POSITIVE_X(1, 0, 0), NEGATIVE_X(-1, 0, 0), -+ POSITIVE_Z(0, 0, 1), NEGATIVE_Z(0, 0, -1), -+ POSITIVE_Y(0, 1, 0), NEGATIVE_Y(0, -1, 0); ++ POSITIVE_X(1, 0, 0, Direction.EAST) , NEGATIVE_X(-1, 0, 0, Direction.WEST), ++ POSITIVE_Z(0, 0, 1, Direction.SOUTH), NEGATIVE_Z(0, 0, -1, Direction.NORTH), ++ POSITIVE_Y(0, 1, 0, Direction.UP) , NEGATIVE_Y(0, -1, 0, Direction.DOWN); + + static { + POSITIVE_X.opposite = NEGATIVE_X; NEGATIVE_X.opposite = POSITIVE_X; @@ -19136,11 +20184,11 @@ index 0000000000000000000000000000000000000000..382c9e445af0d6ad2428fc22d0f63017 + public final long everythingButThisDirection; + public final long everythingButTheOppositeDirection; + -+ AxisDirection(final int x, final int y, final int z) { ++ AxisDirection(final int x, final int y, final int z, final Direction nms) { + this.x = x; + this.y = y; + this.z = z; -+ this.nms = Direction.fromDelta(x, y, z); ++ this.nms = nms; + this.everythingButThisDirection = (long)(ALL_DIRECTIONS_BITSET ^ (1 << this.ordinal())); + // positive is always even, negative is always odd. Flip the 1 bit to get the negative direction. + this.everythingButTheOppositeDirection = (long)(ALL_DIRECTIONS_BITSET ^ (1 << (this.ordinal() ^ 1))); @@ -19180,9 +20228,7 @@ index 0000000000000000000000000000000000000000..382c9e445af0d6ad2428fc22d0f63017 + // index = x + (z * 5) + protected final boolean[][] emptinessMapCache = new boolean[5 * 5][]; + -+ protected final BlockPos.MutableBlockPos mutablePos1 = new BlockPos.MutableBlockPos(); -+ protected final BlockPos.MutableBlockPos mutablePos2 = new BlockPos.MutableBlockPos(); -+ protected final BlockPos.MutableBlockPos mutablePos3 = new BlockPos.MutableBlockPos(); ++ protected final BlockPos.MutableBlockPos lightEmissionPos = new BlockPos.MutableBlockPos(); + + protected int encodeOffsetX; + protected int encodeOffsetY; @@ -20224,69 +21270,46 @@ index 0000000000000000000000000000000000000000..382c9e445af0d6ad2428fc22d0f63017 + if (blockState == null) { + continue; + } -+ final int opacityCached = ((StarlightAbstractBlockState)blockState).starlight$getOpacityIfCached(); -+ if (opacityCached != -1) { -+ final int targetLevel = propagatedLightLevel - Math.max(1, opacityCached); -+ if (targetLevel > currentLevel) { -+ currentNibble.set(localIndex, targetLevel); -+ this.postLightUpdate(offX, offY, offZ); ++ long flags = 0; ++ if (((StarlightAbstractBlockState)blockState).starlight$isConditionallyFullOpaque()) { ++ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(propagate.getOpposite().nms); + -+ if (targetLevel > 1) { -+ if (queueLength >= queue.length) { -+ queue = this.resizeIncreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)); -+ continue; -+ } -+ } -+ continue; -+ } else { -+ this.mutablePos1.set(offX, offY, offZ); -+ long flags = 0; -+ if (((StarlightAbstractBlockState)blockState).starlight$isConditionallyFullOpaque()) { -+ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(world, this.mutablePos1, propagate.getOpposite().nms); -+ -+ if (Shapes.faceShapeOccludes(Shapes.empty(), cullingFace)) { -+ continue; -+ } -+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; -+ } -+ -+ final int opacity = blockState.getLightBlock(world, this.mutablePos1); -+ final int targetLevel = propagatedLightLevel - Math.max(1, opacity); -+ if (targetLevel <= currentLevel) { ++ if (Shapes.faceShapeOccludes(Shapes.empty(), cullingFace)) { + continue; + } ++ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; ++ } + -+ currentNibble.set(localIndex, targetLevel); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 1) { -+ if (queueLength >= queue.length) { -+ queue = this.resizeIncreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)) -+ | (flags); -+ } ++ final int opacity = blockState.getLightBlock(); ++ final int targetLevel = propagatedLightLevel - Math.max(1, opacity); ++ if (targetLevel <= currentLevel) { + continue; + } ++ ++ currentNibble.set(localIndex, targetLevel); ++ this.postLightUpdate(offX, offY, offZ); ++ ++ if (targetLevel > 1) { ++ if (queueLength >= queue.length) { ++ queue = this.resizeIncreaseQueue(); ++ } ++ queue[queueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((targetLevel & 0xFL) << (6 + 6 + 16)) ++ | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)) ++ | (flags); ++ } ++ continue; + } + } else { + // we actually need to worry about our state here + final BlockState fromBlock = this.getBlockState(posX, posY, posZ); -+ this.mutablePos2.set(posX, posY, posZ); + for (final AxisDirection propagate : checkDirections) { + final int offX = posX + propagate.x; + final int offY = posY + propagate.y; + final int offZ = posZ + propagate.z; + -+ final VoxelShape fromShape = (((StarlightAbstractBlockState)fromBlock).starlight$isConditionallyFullOpaque()) ? fromBlock.getFaceOcclusionShape(world, this.mutablePos2, propagate.nms) : Shapes.empty(); ++ final VoxelShape fromShape = (((StarlightAbstractBlockState)fromBlock).starlight$isConditionallyFullOpaque()) ? fromBlock.getFaceOcclusionShape(propagate.nms) : Shapes.empty(); + + if (fromShape != Shapes.empty() && Shapes.faceShapeOccludes(Shapes.empty(), fromShape)) { + continue; @@ -20306,58 +21329,36 @@ index 0000000000000000000000000000000000000000..382c9e445af0d6ad2428fc22d0f63017 + if (blockState == null) { + continue; + } -+ final int opacityCached = ((StarlightAbstractBlockState)blockState).starlight$getOpacityIfCached(); -+ if (opacityCached != -1) { -+ final int targetLevel = propagatedLightLevel - Math.max(1, opacityCached); -+ if (targetLevel > currentLevel) { -+ currentNibble.set(localIndex, targetLevel); -+ this.postLightUpdate(offX, offY, offZ); ++ long flags = 0; ++ if (((StarlightAbstractBlockState)blockState).starlight$isConditionallyFullOpaque()) { ++ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(propagate.getOpposite().nms); + -+ if (targetLevel > 1) { -+ if (queueLength >= queue.length) { -+ queue = this.resizeIncreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)); -+ continue; -+ } -+ } -+ continue; -+ } else { -+ this.mutablePos1.set(offX, offY, offZ); -+ long flags = 0; -+ if (((StarlightAbstractBlockState)blockState).starlight$isConditionallyFullOpaque()) { -+ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(world, this.mutablePos1, propagate.getOpposite().nms); -+ -+ if (Shapes.faceShapeOccludes(fromShape, cullingFace)) { -+ continue; -+ } -+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; -+ } -+ -+ final int opacity = blockState.getLightBlock(world, this.mutablePos1); -+ final int targetLevel = propagatedLightLevel - Math.max(1, opacity); -+ if (targetLevel <= currentLevel) { ++ if (Shapes.faceShapeOccludes(fromShape, cullingFace)) { + continue; + } ++ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; ++ } + -+ currentNibble.set(localIndex, targetLevel); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 1) { -+ if (queueLength >= queue.length) { -+ queue = this.resizeIncreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)) -+ | (flags); -+ } ++ final int opacity = blockState.getLightBlock(); ++ final int targetLevel = propagatedLightLevel - Math.max(1, opacity); ++ if (targetLevel <= currentLevel) { + continue; + } ++ ++ currentNibble.set(localIndex, targetLevel); ++ this.postLightUpdate(offX, offY, offZ); ++ ++ if (targetLevel > 1) { ++ if (queueLength >= queue.length) { ++ queue = this.resizeIncreaseQueue(); ++ } ++ queue[queueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((targetLevel & 0xFL) << (6 + 6 + 16)) ++ | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)) ++ | (flags); ++ } ++ continue; + } + } + } @@ -20378,6 +21379,8 @@ index 0000000000000000000000000000000000000000..382c9e445af0d6ad2428fc22d0f63017 + final int sectionOffset = this.chunkSectionIndexOffset; + final int emittedMask = this.emittedLightMask; + ++ final PlatformHooks platformHooks = PlatformHooks.get(); ++ + while (queueReadIndex < queueLength) { + final long queueValue = queue[queueReadIndex++]; + @@ -20409,109 +21412,63 @@ index 0000000000000000000000000000000000000000..382c9e445af0d6ad2428fc22d0f63017 + if (blockState == null) { + continue; + } -+ final int opacityCached = ((StarlightAbstractBlockState)blockState).starlight$getOpacityIfCached(); -+ if (opacityCached != -1) { -+ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacityCached)); -+ if (lightLevel > targetLevel) { -+ // it looks like another source propagated here, so re-propagate it -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((lightLevel & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | FLAG_RECHECK_LEVEL; ++ this.lightEmissionPos.set(offX, offY, offZ); ++ long flags = 0; ++ if (((StarlightAbstractBlockState)blockState).starlight$isConditionallyFullOpaque()) { ++ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(propagate.getOpposite().nms); ++ ++ if (Shapes.faceShapeOccludes(Shapes.empty(), cullingFace)) { + continue; + } -+ final int emittedLight = blockState.getLightEmission() & emittedMask; -+ if (emittedLight != 0) { -+ // re-propagate source -+ // note: do not set recheck level, or else the propagation will fail -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((emittedLight & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (((StarlightAbstractBlockState)blockState).starlight$isConditionallyFullOpaque() ? (FLAG_WRITE_LEVEL | FLAG_HAS_SIDED_TRANSPARENT_BLOCKS) : FLAG_WRITE_LEVEL); -+ } ++ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; ++ } + -+ currentNibble.set(localIndex, 0); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour... -+ if (queueLength >= queue.length) { -+ queue = this.resizeDecreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)); -+ continue; -+ } -+ continue; -+ } else { -+ this.mutablePos1.set(offX, offY, offZ); -+ long flags = 0; -+ if (((StarlightAbstractBlockState)blockState).starlight$isConditionallyFullOpaque()) { -+ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(world, this.mutablePos1, propagate.getOpposite().nms); -+ -+ if (Shapes.faceShapeOccludes(Shapes.empty(), cullingFace)) { -+ continue; -+ } -+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; -+ } -+ -+ final int opacity = blockState.getLightBlock(world, this.mutablePos1); -+ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacity)); -+ if (lightLevel > targetLevel) { -+ // it looks like another source propagated here, so re-propagate it -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((lightLevel & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (FLAG_RECHECK_LEVEL | flags); -+ continue; -+ } -+ final int emittedLight = blockState.getLightEmission() & emittedMask; -+ if (emittedLight != 0) { -+ // re-propagate source -+ // note: do not set recheck level, or else the propagation will fail -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((emittedLight & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (flags | FLAG_WRITE_LEVEL); -+ } -+ -+ currentNibble.set(localIndex, 0); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 0) { -+ if (queueLength >= queue.length) { -+ queue = this.resizeDecreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)) -+ | flags; ++ final int opacity = blockState.getLightBlock(); ++ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacity)); ++ if (lightLevel > targetLevel) { ++ // it looks like another source propagated here, so re-propagate it ++ if (increaseQueueLength >= increaseQueue.length) { ++ increaseQueue = this.resizeIncreaseQueue(); + } ++ increaseQueue[increaseQueueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((lightLevel & 0xFL) << (6 + 6 + 16)) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ | (FLAG_RECHECK_LEVEL | flags); + continue; + } ++ final int emittedLight = (platformHooks.getLightEmission(blockState, world, this.lightEmissionPos)) & emittedMask; ++ if (emittedLight != 0) { ++ // re-propagate source ++ // note: do not set recheck level, or else the propagation will fail ++ if (increaseQueueLength >= increaseQueue.length) { ++ increaseQueue = this.resizeIncreaseQueue(); ++ } ++ increaseQueue[increaseQueueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((emittedLight & 0xFL) << (6 + 6 + 16)) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ | (flags | FLAG_WRITE_LEVEL); ++ } ++ ++ currentNibble.set(localIndex, 0); ++ this.postLightUpdate(offX, offY, offZ); ++ ++ if (targetLevel > 0) { ++ if (queueLength >= queue.length) { ++ queue = this.resizeDecreaseQueue(); ++ } ++ queue[queueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((targetLevel & 0xFL) << (6 + 6 + 16)) ++ | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)) ++ | flags; ++ } ++ continue; + } + } else { + // we actually need to worry about our state here + final BlockState fromBlock = this.getBlockState(posX, posY, posZ); -+ this.mutablePos2.set(posX, posY, posZ); + for (final AxisDirection propagate : checkDirections) { + final int offX = posX + propagate.x; + final int offY = posY + propagate.y; @@ -20520,7 +21477,7 @@ index 0000000000000000000000000000000000000000..382c9e445af0d6ad2428fc22d0f63017 + final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; + final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8); + -+ final VoxelShape fromShape = (((StarlightAbstractBlockState)fromBlock).starlight$isConditionallyFullOpaque()) ? fromBlock.getFaceOcclusionShape(world, this.mutablePos2, propagate.nms) : Shapes.empty(); ++ final VoxelShape fromShape = (((StarlightAbstractBlockState)fromBlock).starlight$isConditionallyFullOpaque()) ? fromBlock.getFaceOcclusionShape(propagate.nms) : Shapes.empty(); + + if (fromShape != Shapes.empty() && Shapes.faceShapeOccludes(Shapes.empty(), fromShape)) { + continue; @@ -20538,104 +21495,59 @@ index 0000000000000000000000000000000000000000..382c9e445af0d6ad2428fc22d0f63017 + if (blockState == null) { + continue; + } -+ final int opacityCached = ((StarlightAbstractBlockState)blockState).starlight$getOpacityIfCached(); -+ if (opacityCached != -1) { -+ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacityCached)); -+ if (lightLevel > targetLevel) { -+ // it looks like another source propagated here, so re-propagate it -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((lightLevel & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | FLAG_RECHECK_LEVEL; ++ this.lightEmissionPos.set(offX, offY, offZ); ++ long flags = 0; ++ if (((StarlightAbstractBlockState)blockState).starlight$isConditionallyFullOpaque()) { ++ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(propagate.getOpposite().nms); ++ ++ if (Shapes.faceShapeOccludes(fromShape, cullingFace)) { + continue; + } -+ final int emittedLight = blockState.getLightEmission() & emittedMask; -+ if (emittedLight != 0) { -+ // re-propagate source -+ // note: do not set recheck level, or else the propagation will fail -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((emittedLight & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (((StarlightAbstractBlockState)blockState).starlight$isConditionallyFullOpaque() ? (FLAG_WRITE_LEVEL | FLAG_HAS_SIDED_TRANSPARENT_BLOCKS) : FLAG_WRITE_LEVEL); -+ } ++ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; ++ } + -+ currentNibble.set(localIndex, 0); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour... -+ if (queueLength >= queue.length) { -+ queue = this.resizeDecreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)); -+ continue; -+ } -+ continue; -+ } else { -+ this.mutablePos1.set(offX, offY, offZ); -+ long flags = 0; -+ if (((StarlightAbstractBlockState)blockState).starlight$isConditionallyFullOpaque()) { -+ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(world, this.mutablePos1, propagate.getOpposite().nms); -+ -+ if (Shapes.faceShapeOccludes(fromShape, cullingFace)) { -+ continue; -+ } -+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; -+ } -+ -+ final int opacity = blockState.getLightBlock(world, this.mutablePos1); -+ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacity)); -+ if (lightLevel > targetLevel) { -+ // it looks like another source propagated here, so re-propagate it -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((lightLevel & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (FLAG_RECHECK_LEVEL | flags); -+ continue; -+ } -+ final int emittedLight = blockState.getLightEmission() & emittedMask; -+ if (emittedLight != 0) { -+ // re-propagate source -+ // note: do not set recheck level, or else the propagation will fail -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((emittedLight & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (flags | FLAG_WRITE_LEVEL); -+ } -+ -+ currentNibble.set(localIndex, 0); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour... -+ if (queueLength >= queue.length) { -+ queue = this.resizeDecreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)) -+ | flags; ++ final int opacity = blockState.getLightBlock(); ++ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacity)); ++ if (lightLevel > targetLevel) { ++ // it looks like another source propagated here, so re-propagate it ++ if (increaseQueueLength >= increaseQueue.length) { ++ increaseQueue = this.resizeIncreaseQueue(); + } ++ increaseQueue[increaseQueueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((lightLevel & 0xFL) << (6 + 6 + 16)) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ | (FLAG_RECHECK_LEVEL | flags); + continue; + } ++ final int emittedLight = (platformHooks.getLightEmission(blockState, world, this.lightEmissionPos)) & emittedMask; ++ if (emittedLight != 0) { ++ // re-propagate source ++ // note: do not set recheck level, or else the propagation will fail ++ if (increaseQueueLength >= increaseQueue.length) { ++ increaseQueue = this.resizeIncreaseQueue(); ++ } ++ increaseQueue[increaseQueueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((emittedLight & 0xFL) << (6 + 6 + 16)) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ | (flags | FLAG_WRITE_LEVEL); ++ } ++ ++ currentNibble.set(localIndex, 0); ++ this.postLightUpdate(offX, offY, offZ); ++ ++ if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour... ++ if (queueLength >= queue.length) { ++ queue = this.resizeDecreaseQueue(); ++ } ++ queue[queueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((targetLevel & 0xFL) << (6 + 6 + 16)) ++ | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)) ++ | flags; ++ } ++ continue; + } + } + } @@ -20647,15 +21559,16 @@ index 0000000000000000000000000000000000000000..382c9e445af0d6ad2428fc22d0f63017 +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/StarLightInterface.java b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/StarLightInterface.java new file mode 100644 -index 0000000000000000000000000000000000000000..c64ab41198a5e0c7cbcbe6452af11f82f5938862 +index 0000000000000000000000000000000000000000..571db5f9bf94745a8afe2cd313e593fb15db5e37 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/StarLightInterface.java -@@ -0,0 +1,930 @@ +@@ -0,0 +1,931 @@ +package ca.spottedleaf.moonrise.patches.starlight.light; + +import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; ++import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor; +import ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable; ++import ca.spottedleaf.concurrentutil.util.Priority; +import ca.spottedleaf.moonrise.common.util.CoordinateUtils; +import ca.spottedleaf.moonrise.common.util.WorldUtil; +import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel; @@ -21398,34 +22311,34 @@ index 0000000000000000000000000000000000000000..c64ab41198a5e0c7cbcbe6452af11f82 + super(lightInterface); + } + -+ public void lowerPriority(final int chunkX, final int chunkZ, final PrioritisedExecutor.Priority priority) { ++ public void lowerPriority(final int chunkX, final int chunkZ, final Priority priority) { + final ServerChunkTasks task = this.chunkTasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); + if (task != null) { + task.lowerPriority(priority); + } + } + -+ public void setPriority(final int chunkX, final int chunkZ, final PrioritisedExecutor.Priority priority) { ++ public void setPriority(final int chunkX, final int chunkZ, final Priority priority) { + final ServerChunkTasks task = this.chunkTasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); + if (task != null) { + task.setPriority(priority); + } + } + -+ public void raisePriority(final int chunkX, final int chunkZ, final PrioritisedExecutor.Priority priority) { ++ public void raisePriority(final int chunkX, final int chunkZ, final Priority priority) { + final ServerChunkTasks task = this.chunkTasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); + if (task != null) { + task.raisePriority(priority); + } + } + -+ public PrioritisedExecutor.Priority getPriority(final int chunkX, final int chunkZ) { ++ public Priority getPriority(final int chunkX, final int chunkZ) { + final ServerChunkTasks task = this.chunkTasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); + if (task != null) { + return task.getPriority(); + } + -+ return PrioritisedExecutor.Priority.COMPLETING; ++ return Priority.COMPLETING; + } + + @Override @@ -21469,7 +22382,7 @@ index 0000000000000000000000000000000000000000..c64ab41198a5e0c7cbcbe6452af11f82 + return ret; + } + -+ public ServerChunkTasks queueChunkLightTask(final ChunkPos pos, final BooleanSupplier lightTask, final PrioritisedExecutor.Priority priority) { ++ public ServerChunkTasks queueChunkLightTask(final ChunkPos pos, final BooleanSupplier lightTask, final Priority priority) { + final ServerChunkTasks ret = this.chunkTasks.compute(CoordinateUtils.getChunkKey(pos), (final long keyInMap, ServerChunkTasks valueInMap) -> { + if (valueInMap == null) { + valueInMap = new ServerChunkTasks( @@ -21532,11 +22445,11 @@ index 0000000000000000000000000000000000000000..c64ab41198a5e0c7cbcbe6452af11f82 + + public ServerChunkTasks(final long chunkCoordinate, final StarLightInterface lightEngine, + final ServerLightQueue queue) { -+ this(chunkCoordinate, lightEngine, queue, PrioritisedExecutor.Priority.NORMAL); ++ this(chunkCoordinate, lightEngine, queue, Priority.NORMAL); + } + + public ServerChunkTasks(final long chunkCoordinate, final StarLightInterface lightEngine, -+ final ServerLightQueue queue, final PrioritisedExecutor.Priority priority) { ++ final ServerLightQueue queue, final Priority priority) { + super(chunkCoordinate, lightEngine, queue); + this.task = ((ChunkSystemServerLevel)(ServerLevel)lightEngine.getWorld()).moonrise$getChunkTaskScheduler().radiusAwareScheduler.createTask( + CoordinateUtils.getChunkX(chunkCoordinate), CoordinateUtils.getChunkZ(chunkCoordinate), @@ -21556,19 +22469,19 @@ index 0000000000000000000000000000000000000000..c64ab41198a5e0c7cbcbe6452af11f82 + return this.task.cancel(); + } + -+ public PrioritisedExecutor.Priority getPriority() { ++ public Priority getPriority() { + return this.task.getPriority(); + } + -+ public void lowerPriority(final PrioritisedExecutor.Priority priority) { ++ public void lowerPriority(final Priority priority) { + this.task.lowerPriority(priority); + } + -+ public void setPriority(final PrioritisedExecutor.Priority priority) { ++ public void setPriority(final Priority priority) { + this.task.setPriority(priority); + } + -+ public void raisePriority(final PrioritisedExecutor.Priority priority) { ++ public void raisePriority(final Priority priority) { + this.task.raisePriority(priority); + } + @@ -21616,12 +22529,31 @@ index 0000000000000000000000000000000000000000..7fe59ab70557aa6a484a02db2b2007fd + } + +} +diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/storage/StarlightSectionData.java b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/storage/StarlightSectionData.java +new file mode 100644 +index 0000000000000000000000000000000000000000..40d004afdc6449530f5bb2d7c7638b8ee3e3a577 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/storage/StarlightSectionData.java +@@ -0,0 +1,13 @@ ++package ca.spottedleaf.moonrise.patches.starlight.storage; ++ ++public interface StarlightSectionData { ++ ++ public int starlight$getBlockLightState(); ++ ++ public void starlight$setBlockLightState(final int state); ++ ++ public int starlight$getSkyLightState(); ++ ++ public void starlight$setSkyLightState(final int state); ++ ++} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/util/SaveUtil.java b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/util/SaveUtil.java new file mode 100644 -index 0000000000000000000000000000000000000000..57692a503e147a00ac4e1586cd78e12b71a80d3f +index 0000000000000000000000000000000000000000..689ce367164e79e0426eeecb81dbbc521d4bc742 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/util/SaveUtil.java -@@ -0,0 +1,188 @@ +@@ -0,0 +1,189 @@ +package ca.spottedleaf.moonrise.patches.starlight.util; + +import ca.spottedleaf.moonrise.common.util.WorldUtil; @@ -21638,19 +22570,20 @@ index 0000000000000000000000000000000000000000..57692a503e147a00ac4e1586cd78e12b +import net.minecraft.world.level.chunk.status.ChunkStatus; +import org.slf4j.Logger; + ++// note: keep in-sync with SerializableChunkDataMixin +public final class SaveUtil { + + private static final Logger LOGGER = LogUtils.getLogger(); + -+ private static final int STARLIGHT_LIGHT_VERSION = 9; ++ public static final int STARLIGHT_LIGHT_VERSION = 9; + + public static int getLightVersion() { + return STARLIGHT_LIGHT_VERSION; + } + -+ private static final String BLOCKLIGHT_STATE_TAG = "starlight.blocklight_state"; -+ private static final String SKYLIGHT_STATE_TAG = "starlight.skylight_state"; -+ private static final String STARLIGHT_VERSION_TAG = "starlight.light_version"; ++ public static final String BLOCKLIGHT_STATE_TAG = "starlight.blocklight_state"; ++ public static final String SKYLIGHT_STATE_TAG = "starlight.skylight_state"; ++ public static final String STARLIGHT_VERSION_TAG = "starlight.light_version"; + + public static void saveLightHook(final Level world, final ChunkAccess chunk, final CompoundTag nbt) { + try { @@ -22229,83 +23162,30 @@ index 0000000000000000000000000000000000000000..85950a1aa732ab8c01ad28bec9e0de14 + } +} diff --git a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -index b3f3408a986ae513c06e3b16b82e1c80d4604cd2..9bd509915b391e9d382fe47798e2c345b6e59a9a 100644 +index 36b96e0ed5c0d25068ec4678eddd8a19a020d345..b59613e3d97a9ca7d11bda28508021a5e9a9e92f 100644 --- a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java +++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -@@ -30,6 +30,45 @@ public class GlobalConfiguration extends ConfigurationPart { - public static GlobalConfiguration get() { - return instance; - } -+ -+ public ChunkLoadingBasic chunkLoadingBasic; -+ -+ public class ChunkLoadingBasic extends ConfigurationPart { -+ @Comment("The maximum rate in chunks per second that the server will send to any individual player. Set to -1 to disable this limit.") -+ public double playerMaxChunkSendRate = 75.0; -+ -+ @Comment( -+ "The maximum rate at which chunks will load for any individual player. " + -+ "Note that this setting also affects chunk generations, since a chunk load is always first issued to test if a" + -+ "chunk is already generated. Set to -1 to disable this limit." -+ ) -+ public double playerMaxChunkLoadRate = 100.0; -+ -+ @Comment("The maximum rate at which chunks will generate for any individual player. Set to -1 to disable this limit.") -+ public double playerMaxChunkGenerateRate = -1.0; -+ } -+ -+ public ChunkLoadingAdvanced chunkLoadingAdvanced; -+ -+ public class ChunkLoadingAdvanced extends ConfigurationPart { -+ @Comment( -+ "Set to true if the server will match the chunk send radius that clients have configured" + -+ "in their view distance settings if the client is less-than the server's send distance." -+ ) -+ public boolean autoConfigSendDistance = true; -+ -+ @Comment( -+ "Specifies the maximum amount of concurrent chunk loads that an individual player can have." + -+ "Set to 0 to let the server configure it automatically per player, or set it to -1 to disable the limit." -+ ) -+ public int playerMaxConcurrentChunkLoads = 0; -+ -+ @Comment( -+ "Specifies the maximum amount of concurrent chunk generations that an individual player can have." + -+ "Set to 0 to let the server configure it automatically per player, or set it to -1 to disable the limit." -+ ) -+ public int playerMaxConcurrentChunkGenerates = 0; -+ } - static void set(GlobalConfiguration instance) { - GlobalConfiguration.instance = instance; - } -@@ -145,21 +184,6 @@ public class GlobalConfiguration extends ConfigurationPart { - public int incomingPacketThreshold = 300; - } - -- public ChunkLoading chunkLoading; -- -- public class ChunkLoading extends ConfigurationPart { -- public int minLoadRadius = 2; -- public int maxConcurrentSends = 2; -- public boolean autoconfigSendDistance = true; -- public double targetPlayerChunkSendRate = 100.0; -- public double globalMaxChunkSendRate = -1.0; -- public boolean enableFrustumPriority = false; -- public double globalMaxChunkLoadRate = -1.0; -- public double playerMaxConcurrentLoads = 20.0; -- public double globalMaxConcurrentLoads = 500.0; -- public double playerMaxChunkLoadRate = -1.0; -- } -- - public UnsupportedSettings unsupportedSettings; - - public class UnsupportedSettings extends ConfigurationPart { -@@ -218,7 +242,7 @@ public class GlobalConfiguration extends ConfigurationPart { - +@@ -243,6 +243,23 @@ public class GlobalConfiguration extends ConfigurationPart { @PostProcess private void postProcess() { -- //io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.init(this); -+ ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler.init(this); + ca.spottedleaf.moonrise.common.util.MoonriseCommon.adjustWorkerThreads(this.workerThreads, this.ioThreads); ++ ++ String newChunkSystemGenParallelism = this.genParallelism; ++ if (newChunkSystemGenParallelism.equalsIgnoreCase("default")) { ++ newChunkSystemGenParallelism = "true"; ++ } ++ ++ boolean useParallelGen; ++ if (newChunkSystemGenParallelism.equalsIgnoreCase("on") || newChunkSystemGenParallelism.equalsIgnoreCase("enabled") ++ || newChunkSystemGenParallelism.equalsIgnoreCase("true")) { ++ useParallelGen = true; ++ } else if (newChunkSystemGenParallelism.equalsIgnoreCase("off") || newChunkSystemGenParallelism.equalsIgnoreCase("disabled") ++ || newChunkSystemGenParallelism.equalsIgnoreCase("false")) { ++ useParallelGen = false; ++ } else { ++ throw new IllegalStateException("Invalid option for gen-parallelism: must be one of [on, off, enabled, disabled, true, false, default]"); ++ } ++ ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler.init(useParallelGen); } } @@ -22434,6 +23314,38 @@ index 3fde5abde736b2c19d8819d9aec0397a0245ccd1..6548302d4983bf48cc6bc2b7f4833dc7 + } + // Paper end - optimise collisions } +diff --git a/src/main/java/net/minecraft/core/MappedRegistry.java b/src/main/java/net/minecraft/core/MappedRegistry.java +index 71e04e5c1bc0722abf8ca2e0738bd60b6d7ae21c..8e0dfaf02343d74ce786e4fc647bc4c1d73c0014 100644 +--- a/src/main/java/net/minecraft/core/MappedRegistry.java ++++ b/src/main/java/net/minecraft/core/MappedRegistry.java +@@ -50,6 +50,19 @@ public class MappedRegistry implements WritableRegistry { + return this.getTags(); + } + ++ // Paper start - fluid method optimisations ++ private void injectFluidRegister( ++ final ResourceKey resourceKey, ++ final T object ++ ) { ++ if (resourceKey.registryKey() == (Object)net.minecraft.core.registries.Registries.FLUID) { ++ for (final net.minecraft.world.level.material.FluidState possibleState : ((net.minecraft.world.level.material.Fluid)object).getStateDefinition().getPossibleStates()) { ++ ((ca.spottedleaf.moonrise.patches.fluid.FluidFluidState)(Object)possibleState).moonrise$initCaches(); ++ } ++ } ++ } ++ // Paper end - fluid method optimisations ++ + public MappedRegistry(ResourceKey> key, Lifecycle lifecycle) { + this(key, lifecycle, false); + } +@@ -114,6 +127,7 @@ public class MappedRegistry implements WritableRegistry { + this.toId.put(value, i); + this.registrationInfos.put(key, info); + this.registryLifecycle = this.registryLifecycle.add(info.lifecycle()); ++ this.injectFluidRegister(key, value); // Paper - fluid method optimisations + return reference; + } + } diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java index fc6ce3485dc890f5105a37fe3e344a1460867556..e114e687f2f4503546687fd6792226a643af8793 100644 --- a/src/main/java/net/minecraft/server/Main.java @@ -22447,7 +23359,7 @@ index fc6ce3485dc890f5105a37fe3e344a1460867556..e114e687f2f4503546687fd6792226a6 DedicatedServer dedicatedserver1 = new DedicatedServer(optionset, worldLoader.get(), thread, convertable_conversionsession, resourcepackrepository, worldstem, dedicatedserversettings, DataFixers.getDataFixer(), services, LoggerChunkProgressListener::createFromGameruleRadius); diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 53c9be615a0f2939cd989e24e304e81e6e27f39d..7c388e230c4a88edf6212dd8990e8238d3265ebf 100644 +index 53c9be615a0f2939cd989e24e304e81e6e27f39d..8a66012b7f2396031840c8c718f49f8aab716ee0 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -205,7 +205,7 @@ import org.bukkit.event.server.ServerLoadEvent; @@ -22606,15 +23518,22 @@ index 53c9be615a0f2939cd989e24e304e81e6e27f39d..7c388e230c4a88edf6212dd8990e8238 this.isSaving = false; this.resources.close(); -@@ -1042,6 +1106,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { return false; } : this::haveTime); @@ -22628,7 +23547,7 @@ index 53c9be615a0f2939cd989e24e304e81e6e27f39d..7c388e230c4a88edf6212dd8990e8238 this.tickFrame.end(); gameprofilerfiller.popPush("nextTickWait"); this.mayHaveDelayedTasks = true; -@@ -1428,6 +1500,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop> read(final ChunkPos pos) { @@ -23992,11 +24911,12 @@ index ec19eb88705a07db45f1a3541571fb7f43efb5a9..60adb0caf3d6f03a57fc553038520701 + @Override + public void flushWorker() { + ca.spottedleaf.moonrise.patches.chunk_system.io.RegionFileIOThread.flush(); - } ++ } + // Paper end - rewrite chunk system - ++ @Nullable public LevelChunk getChunkToSend(long pos) { + ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos); @@ -1061,7 +645,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } @@ -24152,7 +25072,7 @@ index ec19eb88705a07db45f1a3541571fb7f43efb5a9..60adb0caf3d6f03a57fc553038520701 playerchunkmap_entitytracker.updatePlayers(this.level.players()); if (entity instanceof ServerPlayer) { ServerPlayer entityplayer = (ServerPlayer) entity; -@@ -1362,16 +905,49 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1362,16 +905,38 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider playerchunkmap_entitytracker1.broadcastRemoved(); } @@ -24163,7 +25083,6 @@ index ec19eb88705a07db45f1a3541571fb7f43efb5a9..60adb0caf3d6f03a57fc553038520701 - Iterator iterator = this.playerMap.getAllPlayers().iterator(); + // Paper start - optimise entity tracker + private void newTrackerTick() { -+ final ca.spottedleaf.moonrise.common.misc.NearbyPlayers nearbyPlayers = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getNearbyPlayers(); + final ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup entityLookup = (ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup)((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getEntityLookup();; - while (iterator.hasNext()) { @@ -24176,38 +25095,28 @@ index ec19eb88705a07db45f1a3541571fb7f43efb5a9..60adb0caf3d6f03a57fc553038520701 + if (tracker == null) { + continue; + } -+ ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity)tracker).moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition())); -+ tracker.serverEntity.sendChanges(); -+ } -+ -+ // process unloads -+ final ca.spottedleaf.moonrise.common.list.ReferenceList unloadedEntities = entityLookup.trackerUnloadedEntities; -+ final Entity[] unloadedEntitiesRaw = java.util.Arrays.copyOf(unloadedEntities.getRawDataUnchecked(), unloadedEntities.size()); -+ unloadedEntities.clear(); - -- this.updateChunkTracking(entityplayer); -+ for (final Entity entity : unloadedEntitiesRaw) { -+ final ChunkMap.TrackedEntity tracker = ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity)entity).moonrise$getTrackedEntity(); -+ if (tracker == null) { -+ continue; ++ ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity)tracker).moonrise$tick(((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity)entity).moonrise$getChunkData().nearbyPlayers); ++ if (((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity)tracker).moonrise$hasPlayers() ++ || ((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity)entity).moonrise$getChunkStatus().isOrAfter(FullChunkStatus.ENTITY_TICKING)) { ++ tracker.serverEntity.sendChanges(); + } -+ ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity)tracker).moonrise$clearPlayers(); - } ++ } + } + // Paper end - optimise entity tracker -+ + +- this.updateChunkTracking(entityplayer); + protected void tick() { + // Paper start - optimise entity tracker + if (true) { + this.newTrackerTick(); + return; -+ } + } + // Paper end - optimise entity tracker + // Paper - rewrite chunk system List list = Lists.newArrayList(); List list1 = this.level.players(); -@@ -1478,27 +1054,25 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1478,27 +1043,25 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } public void waitForLightBeforeSending(ChunkPos centerPos, int radius) { @@ -24245,7 +25154,7 @@ index ec19eb88705a07db45f1a3541571fb7f43efb5a9..60adb0caf3d6f03a57fc553038520701 } @Nullable -@@ -1514,7 +1088,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1514,7 +1077,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } } @@ -24254,7 +25163,7 @@ index ec19eb88705a07db45f1a3541571fb7f43efb5a9..60adb0caf3d6f03a57fc553038520701 public final ServerEntity serverEntity; final Entity entity; -@@ -1522,6 +1096,84 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1522,6 +1085,89 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider SectionPos lastSectionPos; public final Set seenBy = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl @@ -24334,11 +25243,51 @@ index ec19eb88705a07db45f1a3541571fb7f43efb5a9..60adb0caf3d6f03a57fc553038520701 + this.removePlayer(player); + } + } ++ ++ @Override ++ public final boolean moonrise$hasPlayers() { ++ return !this.seenBy.isEmpty(); ++ } + // Paper end - optimise entity tracker + public TrackedEntity(final Entity entity, final int i, final int j, final boolean flag) { this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast, this.seenBy); // CraftBukkit this.entity = entity; +@@ -1610,20 +1256,24 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + + private int getEffectiveRange() { +- int i = this.range; +- Iterator iterator = this.entity.getIndirectPassengers().iterator(); ++ // Paper start - optimise entity tracker ++ final Entity entity = this.entity; ++ int range = ca.spottedleaf.moonrise.common.PlatformHooks.get().modifyEntityTrackingRange(entity, this.range); + +- while (iterator.hasNext()) { +- Entity entity = (Entity) iterator.next(); +- int j = entity.getType().clientTrackingRange() * 16; +- j = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, j); // Paper ++ if (entity.getPassengers() == ImmutableList.of()) { ++ return this.scaledRange(range); ++ } + +- if (j > i) { +- i = j; +- } ++ // note: we change to List ++ final List passengers = (List)entity.getIndirectPassengers(); ++ for (int i = 0, len = passengers.size(); i < len; ++i) { ++ final Entity passenger = passengers.get(i); ++ // note: max should be branchless ++ range = Math.max(range, ca.spottedleaf.moonrise.common.PlatformHooks.get().modifyEntityTrackingRange(passenger, passenger.getType().clientTrackingRange() << 4)); + } + +- return this.scaledRange(i); ++ return this.scaledRange(range); ++ // Paper end - optimise entity tracker + } + + public void updatePlayers(List players) { diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java index f7c2c03749d6be25bf33afd61e1da120770b3432..64da6726634fc223c0e6dcab4d83a6c8997ff196 100644 --- a/src/main/java/net/minecraft/server/level/DistanceManager.java @@ -25152,7 +26101,7 @@ index 65206fdfa5b94eaca139e433b4865c16b16641f3..bf4463bcb5dc439ac5a3fa08dd60845a } } diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index 7e5714fea4cda68b9ae21031c0e0d39061b07e2f..22157cbc4e4bb1e4010116bdf7429815d46bff2e 100644 +index 7e5714fea4cda68b9ae21031c0e0d39061b07e2f..23a13bfd23514cde6dcf8d59ba3b43d84f266aad 100644 --- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java +++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java @@ -52,7 +52,7 @@ import net.minecraft.world.level.storage.DimensionDataStorage; @@ -25164,7 +26113,7 @@ index 7e5714fea4cda68b9ae21031c0e0d39061b07e2f..22157cbc4e4bb1e4010116bdf7429815 private static final Logger LOGGER = LogUtils.getLogger(); private final DistanceManager distanceManager; -@@ -78,6 +78,62 @@ public class ServerChunkCache extends ChunkSource { +@@ -78,6 +78,71 @@ public class ServerChunkCache extends ChunkSource { private final ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable fullChunks = new ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<>(); long chunkFutureAwaitCounter; // Paper end @@ -25189,7 +26138,7 @@ index 7e5714fea4cda68b9ae21031c0e0d39061b07e2f..22157cbc4e4bb1e4010116bdf7429815 + final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler chunkTaskScheduler = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler(); + final CompletableFuture completable = new CompletableFuture<>(); + chunkTaskScheduler.scheduleChunkLoad( -+ chunkX, chunkZ, toStatus, true, ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.BLOCKING, ++ chunkX, chunkZ, toStatus, true, ca.spottedleaf.concurrentutil.util.Priority.BLOCKING, + completable::complete + ); + @@ -25220,6 +26169,15 @@ index 7e5714fea4cda68b9ae21031c0e0d39061b07e2f..22157cbc4e4bb1e4010116bdf7429815 + return ifPresent; + } + ++ final ca.spottedleaf.moonrise.common.PlatformHooks platformHooks = ca.spottedleaf.moonrise.common.PlatformHooks.get(); ++ ++ if (platformHooks.hasCurrentlyLoadingChunk() && currentChunk != null) { ++ final ChunkAccess loading = platformHooks.getCurrentlyLoadingChunk(currentChunk.vanillaChunkHolder); ++ if (loading != null && ca.spottedleaf.moonrise.common.util.TickThread.isTickThread()) { ++ return loading; ++ } ++ } ++ + return load ? this.syncLoad(chunkX, chunkZ, toStatus) : null; + } + // Paper end - rewrite chunk system @@ -25227,7 +26185,7 @@ index 7e5714fea4cda68b9ae21031c0e0d39061b07e2f..22157cbc4e4bb1e4010116bdf7429815 public ServerChunkCache(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor workerExecutor, ChunkGenerator chunkGenerator, int viewDistance, int simulationDistance, boolean dsync, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier persistentStateManagerFactory) { this.level = world; -@@ -109,13 +165,7 @@ public class ServerChunkCache extends ChunkSource { +@@ -109,13 +174,7 @@ public class ServerChunkCache extends ChunkSource { } // CraftBukkit end // Paper start @@ -25242,7 +26200,7 @@ index 7e5714fea4cda68b9ae21031c0e0d39061b07e2f..22157cbc4e4bb1e4010116bdf7429815 @Nullable public ChunkAccess getChunkAtImmediately(int x, int z) { -@@ -186,63 +236,25 @@ public class ServerChunkCache extends ChunkSource { +@@ -186,63 +245,42 @@ public class ServerChunkCache extends ChunkSource { @Nullable @Override public ChunkAccess getChunk(int x, int z, ChunkStatus leastStatus, boolean create) { @@ -25261,14 +26219,14 @@ index 7e5714fea4cda68b9ae21031c0e0d39061b07e2f..22157cbc4e4bb1e4010116bdf7429815 - - gameprofilerfiller.incrementCounter("getChunk"); - long k = ChunkPos.asLong(x, z); -- -- for (int l = 0; l < 4; ++l) { -- if (k == this.lastChunkPos[l] && leastStatus == this.lastChunkStatus[l]) { -- ChunkAccess ichunkaccess = this.lastChunk[l]; + // Paper start - rewrite chunk system + if (leastStatus == ChunkStatus.FULL) { + final LevelChunk ret = this.fullChunks.get(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(x, z)); +- for (int l = 0; l < 4; ++l) { +- if (k == this.lastChunkPos[l] && leastStatus == this.lastChunkStatus[l]) { +- ChunkAccess ichunkaccess = this.lastChunk[l]; +- - if (ichunkaccess != null) { // CraftBukkit - the chunk can become accessible in the meantime TODO for non-null chunks it might also make sense to check that the chunk's state hasn't changed in the meantime - return ichunkaccess; - } @@ -25311,12 +26269,28 @@ index 7e5714fea4cda68b9ae21031c0e0d39061b07e2f..22157cbc4e4bb1e4010116bdf7429815 - return null; - } else { - return this.getChunkAtIfLoadedMainThread(chunkX, chunkZ); // Paper - Perf: Optimise getChunkAt calls for loaded chunks -- } -+ return this.fullChunks.get(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkX, chunkZ)); // Paper - rewrite chunk system ++ // Paper start - rewrite chunk system ++ final LevelChunk ret = this.fullChunks.get(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkX, chunkZ)); ++ if (!ca.spottedleaf.moonrise.common.PlatformHooks.get().hasCurrentlyLoadingChunk()) { ++ return ret; + } ++ ++ if (ret != null || !ca.spottedleaf.moonrise.common.util.TickThread.isTickThread()) { ++ return ret; ++ } ++ ++ final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder holder = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler() ++ .chunkHolderManager.getChunkHolder(chunkX, chunkZ); ++ if (holder == null) { ++ return ret; ++ } ++ ++ return ca.spottedleaf.moonrise.common.PlatformHooks.get().getCurrentlyLoadingChunk(holder.vanillaChunkHolder); ++ // Paper end - rewrite chunk system } private void clearCache() { -@@ -273,56 +285,59 @@ public class ServerChunkCache extends ChunkSource { +@@ -273,56 +311,59 @@ public class ServerChunkCache extends ChunkSource { } private CompletableFuture> getChunkFutureMainThread(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create) { @@ -25374,7 +26348,7 @@ index 7e5714fea4cda68b9ae21031c0e0d39061b07e2f..22157cbc4e4bb1e4010116bdf7429815 + + ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().scheduleChunkLoad( + chunkX, chunkZ, leastStatus, true, -+ ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.HIGHER, ++ ca.spottedleaf.concurrentutil.util.Priority.HIGHER, + complete + ); @@ -25414,7 +26388,7 @@ index 7e5714fea4cda68b9ae21031c0e0d39061b07e2f..22157cbc4e4bb1e4010116bdf7429815 } @Override -@@ -335,16 +350,7 @@ public class ServerChunkCache extends ChunkSource { +@@ -335,16 +376,7 @@ public class ServerChunkCache extends ChunkSource { } public boolean runDistanceManagerUpdates() { // Paper - public @@ -25432,7 +26406,7 @@ index 7e5714fea4cda68b9ae21031c0e0d39061b07e2f..22157cbc4e4bb1e4010116bdf7429815 } // Paper start -@@ -354,17 +360,14 @@ public class ServerChunkCache extends ChunkSource { +@@ -354,17 +386,14 @@ public class ServerChunkCache extends ChunkSource { // Paper end public boolean isPositionTicking(long pos) { @@ -25455,7 +26429,7 @@ index 7e5714fea4cda68b9ae21031c0e0d39061b07e2f..22157cbc4e4bb1e4010116bdf7429815 try (co.aikar.timings.Timing timed = level.timings.chunkSaveData.startTiming()) { // Paper - Timings this.chunkMap.saveAllChunks(flush); } // Paper - Timings -@@ -377,17 +380,15 @@ public class ServerChunkCache extends ChunkSource { +@@ -377,17 +406,15 @@ public class ServerChunkCache extends ChunkSource { } public void close(boolean save) throws IOException { @@ -25476,7 +26450,7 @@ index 7e5714fea4cda68b9ae21031c0e0d39061b07e2f..22157cbc4e4bb1e4010116bdf7429815 ProfilerFiller gameprofilerfiller = Profiler.get(); gameprofilerfiller.push("purge"); -@@ -415,6 +416,7 @@ public class ServerChunkCache extends ChunkSource { +@@ -415,6 +442,7 @@ public class ServerChunkCache extends ChunkSource { gameprofilerfiller.popPush("chunks"); if (tickChunks) { this.level.timings.chunks.startTiming(); // Paper - timings @@ -25484,7 +26458,7 @@ index 7e5714fea4cda68b9ae21031c0e0d39061b07e2f..22157cbc4e4bb1e4010116bdf7429815 this.tickChunks(); this.level.timings.chunks.stopTiming(); // Paper - timings this.chunkMap.tick(); -@@ -546,11 +548,12 @@ public class ServerChunkCache extends ChunkSource { +@@ -546,11 +574,13 @@ public class ServerChunkCache extends ChunkSource { } private void getFullChunk(long pos, Consumer chunkConsumer) { @@ -25493,7 +26467,8 @@ index 7e5714fea4cda68b9ae21031c0e0d39061b07e2f..22157cbc4e4bb1e4010116bdf7429815 - if (playerchunk != null) { - ((ChunkResult) playerchunk.getFullChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).ifSuccess(chunkConsumer); + // Paper start - rewrite chunk system -+ final LevelChunk fullChunk = this.getChunkNow(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(pos), ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(pos)); ++ // note: bypass currentlyLoaded from getChunkNow ++ final LevelChunk fullChunk = this.fullChunks.get(pos); + if (fullChunk != null) { + chunkConsumer.accept(fullChunk); } @@ -25501,7 +26476,7 @@ index 7e5714fea4cda68b9ae21031c0e0d39061b07e2f..22157cbc4e4bb1e4010116bdf7429815 } -@@ -644,6 +647,12 @@ public class ServerChunkCache extends ChunkSource { +@@ -644,6 +674,12 @@ public class ServerChunkCache extends ChunkSource { this.chunkMap.setServerViewDistance(watchDistance); } @@ -25514,7 +26489,7 @@ index 7e5714fea4cda68b9ae21031c0e0d39061b07e2f..22157cbc4e4bb1e4010116bdf7429815 public void setSimulationDistance(int simulationDistance) { this.distanceManager.updateSimulationDistance(simulationDistance); } -@@ -735,21 +744,19 @@ public class ServerChunkCache extends ChunkSource { +@@ -735,21 +771,19 @@ public class ServerChunkCache extends ChunkSource { @Override // CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task public boolean pollTask() { @@ -25559,7 +26534,7 @@ index bc0f1aa61e68d2a8638d89c10bc5c71922d057f9..79c88b315481fe70f037bae834f2b072 if (!list.equals(this.lastPassengers)) { diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 509a67aff07bcdcad47eb77e923d442349a4f20c..af27a004f618912d6a5f1d2c82c8e7e9fb00a037 100644 +index 509a67aff07bcdcad47eb77e923d442349a4f20c..c7523387f0e9bbfe952abd237a936c8319f10200 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java @@ -187,7 +187,7 @@ import org.bukkit.event.weather.LightningStrikeEvent; @@ -25634,7 +26609,9 @@ index 509a67aff07bcdcad47eb77e923d442349a4f20c..af27a004f618912d6a5f1d2c82c8e7e9 + public final LevelChunk moonrise$getFullChunkIfLoaded(final int chunkX, final int chunkZ) { + return this.chunkSource.getChunkNow(chunkX, chunkZ); + } -+ + +- int requiredChunks = (maxChunkX - minChunkX + 1) * (maxChunkZ - minChunkZ + 1); +- int[] loadedChunks = new int[1]; + @Override + public final ChunkAccess moonrise$getAnyChunkIfLoaded(final int chunkX, final int chunkZ) { + final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder newChunkHolder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkX, chunkZ)); @@ -25644,7 +26621,8 @@ index 509a67aff07bcdcad47eb77e923d442349a4f20c..af27a004f618912d6a5f1d2c82c8e7e9 + final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder.ChunkCompletion lastCompletion = newChunkHolder.getLastChunkCompletion(); + return lastCompletion == null ? null : lastCompletion.chunk(); + } -+ + +- Long holderIdentifier = Long.valueOf(chunkProvider.chunkFutureAwaitCounter++); + @Override + public final ChunkAccess moonrise$getSpecificChunkIfLoaded(final int chunkX, final int chunkZ, final net.minecraft.world.level.chunk.status.ChunkStatus leastStatus) { + final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder newChunkHolder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(chunkX, chunkZ); @@ -25658,20 +26636,18 @@ index 509a67aff07bcdcad47eb77e923d442349a4f20c..af27a004f618912d6a5f1d2c82c8e7e9 + public final void moonrise$midTickTasks() { + ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks(); + } -+ + +- java.util.function.Consumer consumer = (net.minecraft.world.level.chunk.ChunkAccess chunk) -> { + @Override + public final ChunkAccess moonrise$syncLoadNonFull(final int chunkX, final int chunkZ, final net.minecraft.world.level.chunk.status.ChunkStatus status) { + return this.moonrise$getChunkTaskScheduler().syncLoadNonFull(chunkX, chunkZ, status); + } - -- int requiredChunks = (maxChunkX - minChunkX + 1) * (maxChunkZ - minChunkZ + 1); -- int[] loadedChunks = new int[1]; ++ + @Override + public final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler moonrise$getChunkTaskScheduler() { + return this.chunkTaskScheduler; + } - -- Long holderIdentifier = Long.valueOf(chunkProvider.chunkFutureAwaitCounter++); ++ + @Override + public final ca.spottedleaf.moonrise.patches.chunk_system.io.RegionFileIOThread.ChunkDataController moonrise$getChunkDataController() { + return this.chunkDataController; @@ -25681,8 +26657,7 @@ index 509a67aff07bcdcad47eb77e923d442349a4f20c..af27a004f618912d6a5f1d2c82c8e7e9 + public final ca.spottedleaf.moonrise.patches.chunk_system.io.RegionFileIOThread.ChunkDataController moonrise$getPoiChunkDataController() { + return this.poiDataController; + } - -- java.util.function.Consumer consumer = (net.minecraft.world.level.chunk.ChunkAccess chunk) -> { ++ + @Override + public final ca.spottedleaf.moonrise.patches.chunk_system.io.RegionFileIOThread.ChunkDataController moonrise$getEntityChunkDataController() { + return this.entityDataController; @@ -25798,8 +26773,7 @@ index 509a67aff07bcdcad47eb77e923d442349a4f20c..af27a004f618912d6a5f1d2c82c8e7e9 + @Override + public final long moonrise$getLastMidTickFailure() { + return this.lastMidTickFailure; - } -- // Paper end - optimise getPlayerByUUID ++ } + + @Override + public final void moonrise$setLastMidTickFailure(final long time) { @@ -25814,7 +26788,8 @@ index 509a67aff07bcdcad47eb77e923d442349a4f20c..af27a004f618912d6a5f1d2c82c8e7e9 + @Override + public final ca.spottedleaf.moonrise.common.list.ReferenceList moonrise$getLoadedChunks() { + return this.loadedChunks; -+ } + } +- // Paper end - optimise getPlayerByUUID + + @Override + public final ca.spottedleaf.moonrise.common.list.ReferenceList moonrise$getTickingChunks() { @@ -25895,16 +26870,18 @@ index 509a67aff07bcdcad47eb77e923d442349a4f20c..af27a004f618912d6a5f1d2c82c8e7e9 } protected void tickTime() { -@@ -613,6 +774,63 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -613,7 +774,60 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe }); } + // Paper start - optimise random ticking ++ private final ca.spottedleaf.moonrise.common.util.SimpleRandom simpleRandom = new ca.spottedleaf.moonrise.common.util.SimpleRandom(0L); ++ + private void optimiseRandomTick(final LevelChunk chunk, final int tickSpeed) { + final LevelChunkSection[] sections = chunk.getSections(); + final int minSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection((ServerLevel)(Object)this); -+ final RandomSource random = this.random; -+ final boolean tickFluids = false; // Paper - not configurable - MC-224294 ++ final ca.spottedleaf.moonrise.common.util.SimpleRandom simpleRandom = this.simpleRandom; ++ final boolean doubleTickFluids = !ca.spottedleaf.moonrise.common.PlatformHooks.get().configFixMC224294(); + + final ChunkPos cpos = chunk.getPos(); + final int offsetX = cpos.x << 4; @@ -25914,39 +26891,32 @@ index 509a67aff07bcdcad47eb77e923d442349a4f20c..af27a004f618912d6a5f1d2c82c8e7e9 + final int offsetY = (sectionIndex + minSection) << 4; + final LevelChunkSection section = sections[sectionIndex]; + final net.minecraft.world.level.chunk.PalettedContainer states = section.states; -+ if (section == null || !section.isRandomlyTickingBlocks()) { ++ if (!section.isRandomlyTickingBlocks()) { + continue; + } + -+ final ca.spottedleaf.moonrise.common.list.IBlockDataList tickList = ((ca.spottedleaf.moonrise.patches.block_counting.BlockCountingChunkSection)section).moonrise$getTickingBlockList(); -+ if (tickList.size() == 0) { -+ continue; -+ } ++ final ca.spottedleaf.moonrise.common.list.ShortList tickList = ((ca.spottedleaf.moonrise.patches.block_counting.BlockCountingChunkSection)section).moonrise$getTickingBlockList(); + + for (int i = 0; i < tickSpeed; ++i) { + final int tickingBlocks = tickList.size(); -+ final int index = random.nextInt() & ((16 * 16 * 16) - 1); ++ final int index = simpleRandom.nextInt() & ((16 * 16 * 16) - 1); + + if (index >= tickingBlocks) { + // most of the time we fall here + continue; + } + -+ final long raw = tickList.getRaw(index); -+ final int location = ca.spottedleaf.moonrise.common.list.IBlockDataList.getLocationFromRaw(raw); -+ final int randomX = (location & 15); -+ final int randomY = ((location >>> (4 + 4)) & 255); -+ final int randomZ = ((location >>> 4) & 15); -+ final BlockState state = states.get(randomX | (randomZ << 4) | (randomY << 8)); ++ final int location = (int)tickList.getRaw(index) & 0xFFFF; ++ final BlockState state = states.get(location); + + // do not use a mutable pos, as some random tick implementations store the input without calling immutable()! -+ final BlockPos pos = new BlockPos(randomX | offsetX, randomY | offsetY, randomZ | offsetZ); ++ final BlockPos pos = new BlockPos((location & 15) | offsetX, ((location >>> (4 + 4)) & 15) | offsetY, ((location >>> 4) & 15) | offsetZ); + -+ state.randomTick((ServerLevel)(Object)this, pos, random); -+ if (tickFluids) { ++ state.randomTick((ServerLevel)(Object)this, pos, simpleRandom); ++ if (doubleTickFluids) { + final FluidState fluidState = state.getFluidState(); + if (fluidState.isRandomlyTicking()) { -+ fluidState.randomTick((ServerLevel)(Object)this, pos, random); ++ fluidState.randomTick((ServerLevel)(Object)this, pos, simpleRandom); + } + } + } @@ -25957,9 +26927,29 @@ index 509a67aff07bcdcad47eb77e923d442349a4f20c..af27a004f618912d6a5f1d2c82c8e7e9 + // Paper end - optimise random ticking + public void tickChunk(LevelChunk chunk, int randomTickSpeed) { ++ final ca.spottedleaf.moonrise.common.util.SimpleRandom simpleRandom = this.simpleRandom; // Paper - optimise random ticking ChunkPos chunkcoordintpair = chunk.getPos(); boolean flag = this.isRaining(); -@@ -662,35 +880,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + int j = chunkcoordintpair.getMinBlockX(); +@@ -621,7 +835,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + ProfilerFiller gameprofilerfiller = Profiler.get(); + + gameprofilerfiller.push("thunder"); +- if (!this.paperConfig().environment.disableThunder && flag && this.isThundering() && this.spigotConfig.thunderChance > 0 && this.random.nextInt(this.spigotConfig.thunderChance) == 0) { // Spigot // Paper - Option to disable thunder ++ if (!this.paperConfig().environment.disableThunder && flag && this.isThundering() && this.spigotConfig.thunderChance > 0 && simpleRandom.nextInt(this.spigotConfig.thunderChance) == 0) { // Spigot // Paper - Option to disable thunder // Paper - optimise random ticking + BlockPos blockposition = this.findLightningTargetAround(this.getBlockRandomPos(j, 0, k, 15)); + + if (this.isRainingAt(blockposition)) { +@@ -653,7 +867,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + + if (!this.paperConfig().environment.disableIceAndSnow) { // Paper - Option to disable ice and snow + for (int l = 0; l < randomTickSpeed; ++l) { +- if (this.random.nextInt(48) == 0) { ++ if (simpleRandom.nextInt(48) == 0) { // Paper - optimise random ticking + this.tickPrecipitation(this.getBlockRandomPos(j, 0, k, 15)); + } + } +@@ -662,35 +876,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe gameprofilerfiller.popPush("tickBlocks"); timings.chunkTicksBlocks.startTiming(); // Paper if (randomTickSpeed > 0) { @@ -25996,7 +26986,7 @@ index 509a67aff07bcdcad47eb77e923d442349a4f20c..af27a004f618912d6a5f1d2c82c8e7e9 } timings.chunkTicksBlocks.stopTiming(); // Paper -@@ -964,6 +1154,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -964,6 +1150,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe if (fluid1.is(fluid)) { fluid1.tick(this, pos, iblockdata); } @@ -26008,7 +26998,7 @@ index 509a67aff07bcdcad47eb77e923d442349a4f20c..af27a004f618912d6a5f1d2c82c8e7e9 } -@@ -973,6 +1168,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -973,6 +1164,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe if (iblockdata.is(block)) { iblockdata.tick(this, pos, this.random); } @@ -26020,7 +27010,7 @@ index 509a67aff07bcdcad47eb77e923d442349a4f20c..af27a004f618912d6a5f1d2c82c8e7e9 } -@@ -1049,6 +1249,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1049,6 +1245,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe } public void save(@Nullable ProgressListener progressListener, boolean flush, boolean savingDisabled) { @@ -26032,7 +27022,7 @@ index 509a67aff07bcdcad47eb77e923d442349a4f20c..af27a004f618912d6a5f1d2c82c8e7e9 ServerChunkCache chunkproviderserver = this.getChunkSource(); if (!savingDisabled) { -@@ -1064,16 +1269,21 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1064,16 +1265,21 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe } timings.worldSaveChunks.startTiming(); // Paper @@ -26060,7 +27050,7 @@ index 509a67aff07bcdcad47eb77e923d442349a4f20c..af27a004f618912d6a5f1d2c82c8e7e9 // CraftBukkit start - moved from MinecraftServer.saveChunks ServerLevel worldserver1 = this; -@@ -1213,7 +1423,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1213,7 +1419,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe this.removePlayerImmediately((ServerPlayer) entity, Entity.RemovalReason.DISCARDED); } @@ -26069,7 +27059,7 @@ index 509a67aff07bcdcad47eb77e923d442349a4f20c..af27a004f618912d6a5f1d2c82c8e7e9 } // CraftBukkit start -@@ -1243,7 +1453,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1243,7 +1449,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe } // CraftBukkit end @@ -26078,7 +27068,7 @@ index 509a67aff07bcdcad47eb77e923d442349a4f20c..af27a004f618912d6a5f1d2c82c8e7e9 } } -@@ -1254,11 +1464,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1254,11 +1460,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe public boolean tryAddFreshEntityWithPassengers(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) { // CraftBukkit end @@ -26091,7 +27081,7 @@ index 509a67aff07bcdcad47eb77e923d442349a4f20c..af27a004f618912d6a5f1d2c82c8e7e9 return false; } else { this.addFreshEntityWithPassengers(entity, reason); // CraftBukkit -@@ -1891,7 +2097,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1891,7 +2093,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe } } @@ -26100,7 +27090,7 @@ index 509a67aff07bcdcad47eb77e923d442349a4f20c..af27a004f618912d6a5f1d2c82c8e7e9 bufferedwriter.write(String.format(Locale.ROOT, "block_entity_tickers: %d\n", this.blockEntityTickers.size())); bufferedwriter.write(String.format(Locale.ROOT, "block_ticks: %d\n", this.getBlockTicks().count())); bufferedwriter.write(String.format(Locale.ROOT, "fluid_ticks: %d\n", this.getFluidTicks().count())); -@@ -1940,7 +2146,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1940,7 +2142,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe BufferedWriter bufferedwriter2 = Files.newBufferedWriter(path1); try { @@ -26109,7 +27099,7 @@ index 509a67aff07bcdcad47eb77e923d442349a4f20c..af27a004f618912d6a5f1d2c82c8e7e9 } catch (Throwable throwable4) { if (bufferedwriter2 != null) { try { -@@ -1961,7 +2167,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1961,7 +2163,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe BufferedWriter bufferedwriter3 = Files.newBufferedWriter(path2); try { @@ -26118,7 +27108,7 @@ index 509a67aff07bcdcad47eb77e923d442349a4f20c..af27a004f618912d6a5f1d2c82c8e7e9 } catch (Throwable throwable6) { if (bufferedwriter3 != null) { try { -@@ -2103,7 +2309,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -2103,7 +2305,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @VisibleForTesting public String getWatchdogStats() { @@ -26127,7 +27117,7 @@ index 509a67aff07bcdcad47eb77e923d442349a4f20c..af27a004f618912d6a5f1d2c82c8e7e9 return BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString(); }), this.blockEntityTickers.size(), ServerLevel.getTypeCount(this.blockEntityTickers, TickingBlockEntity::getType), this.getBlockTicks().count(), this.getFluidTicks().count(), this.gatherChunkSourceStats()); } -@@ -2133,15 +2339,25 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -2133,15 +2335,25 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @Override public LevelEntityGetter getEntities() { org.spigotmc.AsyncCatcher.catchOp("Chunk getEntities call"); // Spigot @@ -26156,7 +27146,7 @@ index 509a67aff07bcdcad47eb77e923d442349a4f20c..af27a004f618912d6a5f1d2c82c8e7e9 } public void startTickingChunk(LevelChunk chunk) { -@@ -2161,34 +2377,47 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -2161,34 +2373,47 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @Override public void close() throws IOException { super.close(); @@ -26211,7 +27201,7 @@ index 509a67aff07bcdcad47eb77e923d442349a4f20c..af27a004f618912d6a5f1d2c82c8e7e9 } @Override -@@ -2234,7 +2463,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -2234,7 +2459,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe CrashReportCategory crashreportsystemdetails = super.fillReportDetails(report); crashreportsystemdetails.setDetail("Loaded entity count", () -> { @@ -26715,8 +27705,21 @@ index cdd66e6ce96e2613afe7f06ca8da3cfaa6704b2d..32634e45ac8433648e49e47e20081e15 handler.send(new ClientboundLevelChunkWithLightPacket(chunk, world.getLightEngine(), null, null)); // Paper start - PlayerChunkLoadEvent if (io.papermc.paper.event.packet.PlayerChunkLoadEvent.getHandlerList().getRegisteredListeners().length > 0) { +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 4fe3024e26b56c2d796acf703a1bc200ff309f09..7529b3d90e65036c7bf869af30475932d547b3ab 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -1407,7 +1407,7 @@ public abstract class PlayerList { + + public void setViewDistance(int viewDistance) { + this.viewDistance = viewDistance; +- this.broadcastAll(new ClientboundSetChunkCacheRadiusPacket(viewDistance)); ++ //this.broadcastAll(new ClientboundSetChunkCacheRadiusPacket(viewDistance)); // Paper - rewrite chunk system + Iterator iterator = this.server.getAllLevels().iterator(); + + while (iterator.hasNext()) { diff --git a/src/main/java/net/minecraft/util/BitStorage.java b/src/main/java/net/minecraft/util/BitStorage.java -index 68648c5a5e3ff079f832092af0f2f801c42d1ede..19661e106612b8e4e152085fb398db7bd06acc23 100644 +index 68648c5a5e3ff079f832092af0f2f801c42d1ede..e4e153cb8899e70273aa150b8ea26907cf68b15c 100644 --- a/src/main/java/net/minecraft/util/BitStorage.java +++ b/src/main/java/net/minecraft/util/BitStorage.java @@ -2,7 +2,7 @@ package net.minecraft.util; @@ -26736,60 +27739,234 @@ index 68648c5a5e3ff079f832092af0f2f801c42d1ede..19661e106612b8e4e152085fb398db7b + // Paper start - block counting + // provide default impl in case mods implement this... + @Override -+ public default it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap moonrise$countEntries() { -+ final it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap ret = new it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap<>(); ++ public default it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap moonrise$countEntries() { ++ final it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap ret = new it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap<>(); + + final int size = this.getSize(); + for (int index = 0; index < size; ++index) { + final int paletteIdx = this.get(index); + ret.computeIfAbsent(paletteIdx, (final int key) -> { -+ return new it.unimi.dsi.fastutil.ints.IntArrayList(); -+ }).add(index); ++ return new it.unimi.dsi.fastutil.shorts.ShortArrayList(); ++ }).add((short)index); + } + + return ret; + } + // Paper end - block counting } +diff --git a/src/main/java/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java b/src/main/java/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java +index 61dee55417bc802e25b9ba2f271d32d8c12844a9..a8a260a3caaa8e5004069b833ecc8b17b2fc8db5 100644 +--- a/src/main/java/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java ++++ b/src/main/java/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java +@@ -7,7 +7,7 @@ import java.util.Iterator; + import javax.annotation.Nullable; + import net.minecraft.core.IdMap; + +-public class CrudeIncrementalIntIdentityHashBiMap implements IdMap { ++public class CrudeIncrementalIntIdentityHashBiMap implements IdMap, ca.spottedleaf.moonrise.patches.fast_palette.FastPalette { // Paper - optimise palette reads + private static final int NOT_FOUND = -1; + private static final Object EMPTY_SLOT = null; + private static final float LOADFACTOR = 0.8F; +@@ -17,6 +17,16 @@ public class CrudeIncrementalIntIdentityHashBiMap implements IdMap { + private int nextId; + private int size; + ++ // Paper start - optimise palette reads ++ private ca.spottedleaf.moonrise.patches.fast_palette.FastPaletteData reference; ++ ++ @Override ++ public final K[] moonrise$getRawPalette(final ca.spottedleaf.moonrise.patches.fast_palette.FastPaletteData src) { ++ this.reference = src; ++ return this.byId; ++ } ++ // Paper end - optimise palette reads ++ + private CrudeIncrementalIntIdentityHashBiMap(int size) { + this.keys = (K[])(new Object[size]); + this.values = new int[size]; +@@ -88,6 +98,12 @@ public class CrudeIncrementalIntIdentityHashBiMap implements IdMap { + this.byId = crudeIncrementalIntIdentityHashBiMap.byId; + this.nextId = crudeIncrementalIntIdentityHashBiMap.nextId; + this.size = crudeIncrementalIntIdentityHashBiMap.size; ++ // Paper start - optimise palette reads ++ final ca.spottedleaf.moonrise.patches.fast_palette.FastPaletteData ref = this.reference; ++ if (ref != null) { ++ ref.moonrise$setPalette(this.byId); ++ } ++ // Paper end - optimise palette reads + } + + public void addMapping(K value, int id) { diff --git a/src/main/java/net/minecraft/util/SimpleBitStorage.java b/src/main/java/net/minecraft/util/SimpleBitStorage.java -index 9f438d9c6eb05e43d24e4af68188a3d4c46a938c..8acf2f2491a8d9d13392c5e89b2bd5c9918285e1 100644 +index 9f438d9c6eb05e43d24e4af68188a3d4c46a938c..d99ec470b4653beab630999a5b2c1a6428b20c38 100644 --- a/src/main/java/net/minecraft/util/SimpleBitStorage.java +++ b/src/main/java/net/minecraft/util/SimpleBitStorage.java -@@ -362,6 +362,40 @@ public class SimpleBitStorage implements BitStorage { +@@ -208,6 +208,20 @@ public class SimpleBitStorage implements BitStorage { + private final int divideAdd; private final long divideAddUnsigned; // Paper - Perf: Optimize SimpleBitStorage + private final int divideShift; + ++ // Paper start - optimise bitstorage read/write operations ++ private static final int[] BETTER_MAGIC = new int[33]; ++ static { ++ // 20 bits of precision ++ // since index is always [0, 4095] (i.e 12 bits), multiplication by a magic value here (20 bits) ++ // fits exactly in an int and allows us to use integer arithmetic ++ for (int bits = 1; bits < BETTER_MAGIC.length; ++bits) { ++ BETTER_MAGIC[bits] = (int)ca.spottedleaf.concurrentutil.util.IntegerUtil.getUnsignedDivisorMagic(64L / bits, 20); ++ } ++ } ++ private final int magic; ++ private final int mulBits; ++ // Paper end - optimise bitstorage read/write operations ++ + public SimpleBitStorage(int elementBits, int size, int[] data) { + this(elementBits, size); + int i = 0; +@@ -261,6 +275,13 @@ public class SimpleBitStorage implements BitStorage { + } else { + this.data = new long[j]; + } ++ // Paper start - optimise bitstorage read/write operations ++ this.magic = BETTER_MAGIC[this.bits]; ++ this.mulBits = (64 / this.bits) * this.bits; ++ if (this.size > 4096) { ++ throw new IllegalStateException("Size > 4096 not supported"); ++ } ++ // Paper end - optimise bitstorage read/write operations + } + + private int cellIndex(int index) { +@@ -273,31 +294,54 @@ public class SimpleBitStorage implements BitStorage { + public final int getAndSet(int index, int value) { // Paper - Perf: Optimize SimpleBitStorage + //Validate.inclusiveBetween(0L, (long)(this.size - 1), (long)index); // Paper - Perf: Optimize SimpleBitStorage + //Validate.inclusiveBetween(0L, this.mask, (long)value); // Paper - Perf: Optimize SimpleBitStorage +- int i = this.cellIndex(index); +- long l = this.data[i]; +- int j = (index - i * this.valuesPerLong) * this.bits; +- int k = (int)(l >> j & this.mask); +- this.data[i] = l & ~(this.mask << j) | ((long)value & this.mask) << j; +- return k; ++ // Paper start - optimise bitstorage read/write operations ++ final int full = this.magic * index; // 20 bits of magic + 12 bits of index = barely int ++ final int divQ = full >>> 20; ++ final int divR = (full & 0xFFFFF) * this.mulBits >>> 20; ++ ++ final long[] dataArray = this.data; ++ ++ final long data = dataArray[divQ]; ++ final long mask = this.mask; ++ ++ final long write = data & ~(mask << divR) | ((long)value & mask) << divR; ++ ++ dataArray[divQ] = write; ++ ++ return (int)(data >>> divR & mask); ++ // Paper end - optimise bitstorage read/write operations + } + + @Override + public final void set(int index, int value) { // Paper - Perf: Optimize SimpleBitStorage + //Validate.inclusiveBetween(0L, (long)(this.size - 1), (long)index); // Paper - Perf: Optimize SimpleBitStorage + //Validate.inclusiveBetween(0L, this.mask, (long)value); // Paper - Perf: Optimize SimpleBitStorage +- int i = this.cellIndex(index); +- long l = this.data[i]; +- int j = (index - i * this.valuesPerLong) * this.bits; +- this.data[i] = l & ~(this.mask << j) | ((long)value & this.mask) << j; ++ // Paper start - optimise bitstorage read/write operations ++ final int full = this.magic * index; // 20 bits of magic + 12 bits of index = barely int ++ final int divQ = full >>> 20; ++ final int divR = (full & 0xFFFFF) * this.mulBits >>> 20; ++ ++ final long[] dataArray = this.data; ++ ++ final long data = dataArray[divQ]; ++ final long mask = this.mask; ++ ++ final long write = data & ~(mask << divR) | ((long)value & mask) << divR; ++ ++ dataArray[divQ] = write; ++ // Paper end - optimise bitstorage read/write operations + } + + @Override + public final int get(int index) { // Paper - Perf: Optimize SimpleBitStorage + //Validate.inclusiveBetween(0L, (long)(this.size - 1), (long)index); // Paper - Perf: Optimize SimpleBitStorage +- int i = this.cellIndex(index); +- long l = this.data[i]; +- int j = (index - i * this.valuesPerLong) * this.bits; +- return (int)(l >> j & this.mask); ++ // Paper start - optimise bitstorage read/write operations ++ final int full = this.magic * index; // 20 bits of magic + 12 bits of index = barely int ++ final int divQ = full >>> 20; ++ final int divR = (full & 0xFFFFF) * this.mulBits >>> 20; ++ ++ return (int)(this.data[divQ] >>> divR & this.mask); ++ // Paper end - optimise bitstorage read/write operations + } + + @Override +@@ -362,6 +406,67 @@ public class SimpleBitStorage implements BitStorage { return new SimpleBitStorage(this.bits, this.size, (long[])this.data.clone()); } + // Paper start - block counting + @Override -+ public final it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap moonrise$countEntries() { ++ public final it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap moonrise$countEntries() { + final int valuesPerLong = this.valuesPerLong; + final int bits = this.bits; -+ final long mask = this.mask; ++ final long mask = (1L << bits) - 1L; + final int size = this.size; + -+ // we may be backed by global palette, so limit bits for init capacity -+ final it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap ret = new it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap<>( -+ 1 << Math.min(6, bits) -+ ); ++ if (bits <= 6) { ++ final it.unimi.dsi.fastutil.shorts.ShortArrayList[] byId = new it.unimi.dsi.fastutil.shorts.ShortArrayList[1 << bits]; ++ final it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap ret = new it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap<>(1 << bits); + -+ int index = 0; ++ int index = 0; + -+ for (long value : this.data) { -+ int li = 0; -+ do { -+ final int paletteIdx = (int)(value & mask); -+ value >>= bits; ++ for (long value : this.data) { ++ int li = 0; ++ do { ++ final int paletteIdx = (int)(value & mask); ++ value >>= bits; ++ ++li; + -+ ret.computeIfAbsent(paletteIdx, (final int key) -> { -+ return new it.unimi.dsi.fastutil.ints.IntArrayList(); -+ }).add(index); ++ final it.unimi.dsi.fastutil.shorts.ShortArrayList coords = byId[paletteIdx]; ++ if (coords != null) { ++ coords.add((short)index++); ++ continue; ++ } else { ++ final it.unimi.dsi.fastutil.shorts.ShortArrayList newCoords = new it.unimi.dsi.fastutil.shorts.ShortArrayList(64); ++ byId[paletteIdx] = newCoords; ++ newCoords.add((short)index++); ++ ret.put(paletteIdx, newCoords); ++ continue; ++ } ++ } while (li < valuesPerLong && index < size); ++ } + -+ ++li; -+ ++index; -+ } while (li < valuesPerLong && index < size); ++ return ret; ++ } else { ++ final it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap ret = new it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap<>( ++ 1 << 6 ++ ); ++ ++ int index = 0; ++ ++ for (long value : this.data) { ++ int li = 0; ++ do { ++ final int paletteIdx = (int)(value & mask); ++ value >>= bits; ++ ++li; ++ ++ ret.computeIfAbsent(paletteIdx, (final int key) -> { ++ return new it.unimi.dsi.fastutil.shorts.ShortArrayList(64); ++ }).add((short)index++); ++ } while (li < valuesPerLong && index < size); ++ } ++ ++ return ret; + } -+ -+ return ret; + } + // Paper end - block counting + @@ -26892,7 +28069,7 @@ index ea72dcb064a35bc6245bc5c94d592efedd8faf41..87ee8e51dfa7657ed7d83fcbceef48bf this.comparator = comparator; if (initialCapacity < 0) { diff --git a/src/main/java/net/minecraft/util/ZeroBitStorage.java b/src/main/java/net/minecraft/util/ZeroBitStorage.java -index 50040c497a819cd1229042ab3cb057d34a32cacc..15c5164d0ef41a978c16ee317fa73e97f2480207 100644 +index 50040c497a819cd1229042ab3cb057d34a32cacc..1f9c436a632e4f110be61cf76fcfc3b7eb80334e 100644 --- a/src/main/java/net/minecraft/util/ZeroBitStorage.java +++ b/src/main/java/net/minecraft/util/ZeroBitStorage.java @@ -62,4 +62,22 @@ public class ZeroBitStorage implements BitStorage { @@ -26902,24 +28079,24 @@ index 50040c497a819cd1229042ab3cb057d34a32cacc..15c5164d0ef41a978c16ee317fa73e97 + + // Paper start - block counting + @Override -+ public final it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap moonrise$countEntries() { ++ public final it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap moonrise$countEntries() { + final int size = this.size; + -+ final int[] raw = new int[size]; ++ final short[] raw = new short[size]; + for (int i = 0; i < size; ++i) { -+ raw[i] = i; ++ raw[i] = (short)i; + } + -+ final it.unimi.dsi.fastutil.ints.IntArrayList coordinates = it.unimi.dsi.fastutil.ints.IntArrayList.wrap(raw, size); ++ final it.unimi.dsi.fastutil.shorts.ShortArrayList coordinates = it.unimi.dsi.fastutil.shorts.ShortArrayList.wrap(raw, size); + -+ final it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap ret = new it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap<>(1); ++ final it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap ret = new it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap<>(1); + ret.put(0, coordinates); + return ret; + } + // Paper end - block counting } diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 8cdef637f6343119fc77f87e7478ee23e9b8efab..5410a0380c44629f1c9b4f0a8e6017cfc5a31a89 100644 +index 8cdef637f6343119fc77f87e7478ee23e9b8efab..a3b0363fbc207ed9edc8a4d6619b6fff9389a9c7 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -175,7 +175,7 @@ import org.bukkit.event.player.PlayerTeleportEvent; @@ -26931,13 +28108,14 @@ index 8cdef637f6343119fc77f87e7478ee23e9b8efab..5410a0380c44629f1c9b4f0a8e6017cf // CraftBukkit start private static final int CURRENT_LEVEL = 2; -@@ -445,6 +445,97 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -445,6 +445,156 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess return this.dimensions.makeBoundingBox(x, y, z); } // Paper end + // Paper start - rewrite chunk system + private final boolean isHardColliding = this.moonrise$isHardCollidingUncached(); + private net.minecraft.server.level.FullChunkStatus chunkStatus; ++ private ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData chunkData; + private int sectionX = Integer.MIN_VALUE; + private int sectionY = Integer.MIN_VALUE; + private int sectionZ = Integer.MIN_VALUE; @@ -26959,6 +28137,16 @@ index 8cdef637f6343119fc77f87e7478ee23e9b8efab..5410a0380c44629f1c9b4f0a8e6017cf + } + + @Override ++ public final ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData moonrise$getChunkData() { ++ return this.chunkData; ++ } ++ ++ @Override ++ public final void moonrise$setChunkData(final ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData chunkData) { ++ this.chunkData = chunkData; ++ } ++ ++ @Override + public final int moonrise$getSectionX() { + return this.sectionX; + } @@ -27006,6 +28194,54 @@ index 8cdef637f6343119fc77f87e7478ee23e9b8efab..5410a0380c44629f1c9b4f0a8e6017cf + return this.getIndirectPassengersStream().anyMatch((entity) -> entity instanceof Player); + } + // Paper end - rewrite chunk system ++ // Paper start - optimise collisions ++ private static float[] calculateStepHeights(final AABB box, final List voxels, final List aabbs, final float stepHeight, ++ final float collidedY) { ++ final FloatArraySet ret = new FloatArraySet(); ++ ++ for (int i = 0, len = voxels.size(); i < len; ++i) { ++ final VoxelShape shape = voxels.get(i); ++ ++ final double[] yCoords = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)shape).moonrise$rootCoordinatesY(); ++ final double yOffset = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)shape).moonrise$offsetY(); ++ ++ for (final double yUnoffset : yCoords) { ++ final double y = yUnoffset + yOffset; ++ ++ final float step = (float)(y - box.minY); ++ ++ if (step > stepHeight) { ++ break; ++ } ++ ++ if (step < 0.0f || !(step != collidedY)) { ++ continue; ++ } ++ ++ ret.add(step); ++ } ++ } ++ ++ for (int i = 0, len = aabbs.size(); i < len; ++i) { ++ final AABB shape = aabbs.get(i); ++ ++ final float step1 = (float)(shape.minY - box.minY); ++ final float step2 = (float)(shape.maxY - box.minY); ++ ++ if (!(step1 < 0.0f) && step1 != collidedY && !(step1 > stepHeight)) { ++ ret.add(step1); ++ } ++ ++ if (!(step2 < 0.0f) && step2 != collidedY && !(step2 > stepHeight)) { ++ ret.add(step2); ++ } ++ } ++ ++ final float[] steps = ret.toFloatArray(); ++ FloatArrays.unstableSort(steps); ++ return steps; ++ } ++ // Paper end - optimise collisions + // Paper start - optimise entity tracker + private net.minecraft.server.level.ChunkMap.TrackedEntity trackedEntity; + @@ -27029,7 +28265,7 @@ index 8cdef637f6343119fc77f87e7478ee23e9b8efab..5410a0380c44629f1c9b4f0a8e6017cf public Entity(EntityType type, Level world) { this.id = Entity.ENTITY_COUNTER.incrementAndGet(); -@@ -1303,41 +1394,82 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -1303,41 +1453,76 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess } private Vec3 collide(Vec3 movement) { @@ -27061,85 +28297,80 @@ index 8cdef637f6343119fc77f87e7478ee23e9b8efab..5410a0380c44629f1c9b4f0a8e6017cf - float[] afloat = Entity.collectCandidateStepUpHeights(axisalignedbb1, list1, this.maxUpStep(), f); - float[] afloat1 = afloat; - int i = afloat.length; -+ final Level world = this.level; -+ final AABB currBoundingBox = this.getBoundingBox(); ++ final AABB currentBox = this.getBoundingBox(); - for (int j = 0; j < i; ++j) { - float f1 = afloat1[j]; - Vec3 vec3d2 = Entity.collideWithShapes(new Vec3(movement.x, (double) f1, movement.z), axisalignedbb1, list1); -+ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isEmpty(currBoundingBox)) { -+ return movement; -+ } ++ final List potentialCollisionsVoxel = new ArrayList<>(); ++ final List potentialCollisionsBB = new ArrayList<>(); - if (vec3d2.horizontalDistanceSqr() > vec3d1.horizontalDistanceSqr()) { - double d0 = axisalignedbb.minY - axisalignedbb1.minY; -+ final List potentialCollisionsBB = new ArrayList<>(); -+ final List potentialCollisionsVoxel = new ArrayList<>(); -+ final double stepHeight = (double)this.maxUpStep(); -+ final AABB collisionBox; -+ final boolean onGround = this.onGround; ++ final AABB initialCollisionBox; ++ if (xZero & zZero) { ++ // note: xZero & zZero -> collision on x/z == 0 -> no step height calculation ++ // this specifically optimises entities standing still ++ initialCollisionBox = movement.y < 0.0 ? ++ ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.cutDownwards(currentBox, movement.y) : ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.cutUpwards(currentBox, movement.y); ++ } else { ++ initialCollisionBox = currentBox.expandTowards(movement); ++ } - return vec3d2.add(0.0D, -d0, 0.0D); -+ if (xZero & zZero) { -+ if (movement.y > 0.0) { -+ collisionBox = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.cutUpwards(currBoundingBox, movement.y); -+ } else { -+ collisionBox = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.cutDownwards(currBoundingBox, movement.y); -+ } -+ } else { -+ // note: xZero == false or zZero == false -+ if (stepHeight > 0.0 && (onGround || (movement.y < 0.0))) { -+ // don't bother getting the collisions if we don't need them. -+ if (movement.y <= 0.0) { -+ collisionBox = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.expandUpwards(currBoundingBox.expandTowards(movement.x, movement.y, movement.z), stepHeight); -+ } else { -+ collisionBox = currBoundingBox.expandTowards(movement.x, Math.max(stepHeight, movement.y), movement.z); - } -+ } else { -+ collisionBox = currBoundingBox.expandTowards(movement.x, movement.y, movement.z); +- } ++ final List entityAABBs = new ArrayList<>(); ++ ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.getEntityHardCollisions( ++ this.level, (Entity)(Object)this, initialCollisionBox, entityAABBs, 0, null ++ ); ++ ++ ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.getCollisionsForBlocksOrWorldBorder( ++ this.level, (Entity)(Object)this, initialCollisionBox, potentialCollisionsVoxel, potentialCollisionsBB, ++ ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_CHECK_BORDER, null ++ ); ++ potentialCollisionsBB.addAll(entityAABBs); ++ final Vec3 collided = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(movement, currentBox, potentialCollisionsVoxel, potentialCollisionsBB); ++ ++ final boolean collidedX = collided.x != movement.x; ++ final boolean collidedY = collided.y != movement.y; ++ final boolean collidedZ = collided.z != movement.z; ++ ++ final boolean collidedDownwards = collidedY && movement.y < 0.0; ++ ++ final double stepHeight; ++ ++ if ((!collidedDownwards && !this.onGround) || (!collidedX && !collidedZ) || (stepHeight = (double)this.maxUpStep()) <= 0.0) { ++ return collided; ++ } ++ ++ final AABB collidedYBox = collidedDownwards ? currentBox.move(0.0, collided.y, 0.0) : currentBox; ++ AABB stepRetrievalBox = collidedYBox.expandTowards(movement.x, stepHeight, movement.z); ++ if (!collidedDownwards) { ++ stepRetrievalBox = stepRetrievalBox.expandTowards(0.0, (double)-1.0E-5F, 0.0); ++ } ++ ++ final List stepVoxels = new ArrayList<>(); ++ final List stepAABBs = entityAABBs; ++ ++ ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.getCollisionsForBlocksOrWorldBorder( ++ this.level, (Entity)(Object)this, stepRetrievalBox, stepVoxels, stepAABBs, ++ ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_CHECK_BORDER, null ++ ); ++ ++ for (final float step : calculateStepHeights(collidedYBox, stepVoxels, stepAABBs, (float)stepHeight, (float)collided.y)) { ++ final Vec3 stepResult = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(new Vec3(movement.x, (double)step, movement.z), collidedYBox, stepVoxels, stepAABBs); ++ if (stepResult.horizontalDistanceSqr() > collided.horizontalDistanceSqr()) { ++ return stepResult.add(0.0, collidedYBox.minY - currentBox.minY, 0.0); } } - return vec3d1; -+ ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.getCollisions( -+ world, (Entity)(Object)this, collisionBox, potentialCollisionsVoxel, potentialCollisionsBB, -+ ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_CHECK_BORDER, -+ null, null -+ ); -+ -+ if (potentialCollisionsVoxel.isEmpty() && potentialCollisionsBB.isEmpty()) { -+ return movement; -+ } -+ -+ final Vec3 limitedMoveVector = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(movement, currBoundingBox, potentialCollisionsVoxel, potentialCollisionsBB); -+ -+ if (stepHeight > 0.0 -+ && (onGround || (limitedMoveVector.y != movement.y && movement.y < 0.0)) -+ && (limitedMoveVector.x != movement.x || limitedMoveVector.z != movement.z)) { -+ Vec3 vec3d2 = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(new Vec3(movement.x, stepHeight, movement.z), currBoundingBox, potentialCollisionsVoxel, potentialCollisionsBB); -+ final Vec3 vec3d3 = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(new Vec3(0.0, stepHeight, 0.0), currBoundingBox.expandTowards(movement.x, 0.0, movement.z), potentialCollisionsVoxel, potentialCollisionsBB); -+ -+ if (vec3d3.y < stepHeight) { -+ final Vec3 vec3d4 = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(new Vec3(movement.x, 0.0D, movement.z), currBoundingBox.move(vec3d3), potentialCollisionsVoxel, potentialCollisionsBB).add(vec3d3); -+ -+ if (vec3d4.horizontalDistanceSqr() > vec3d2.horizontalDistanceSqr()) { -+ vec3d2 = vec3d4; -+ } -+ } -+ -+ if (vec3d2.horizontalDistanceSqr() > limitedMoveVector.horizontalDistanceSqr()) { -+ return vec3d2.add(ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(new Vec3(0.0D, -vec3d2.y + movement.y, 0.0D), currBoundingBox.move(vec3d2), potentialCollisionsVoxel, potentialCollisionsBB)); -+ } -+ -+ return limitedMoveVector; -+ } else { -+ return limitedMoveVector; -+ } ++ return collided; + // Paper end - optimise collisions } private static float[] collectCandidateStepUpHeights(AABB collisionBox, List collisions, float f, float stepHeight) { -@@ -2699,18 +2831,75 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -2699,18 +2884,110 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess } public boolean isInWall() { @@ -27153,76 +28384,111 @@ index 8cdef637f6343119fc77f87e7478ee23e9b8efab..5410a0380c44629f1c9b4f0a8e6017cf - return BlockPos.betweenClosedStream(axisalignedbb).anyMatch((blockposition) -> { - BlockState iblockdata = this.level().getBlockState(blockposition); -+ final float reducedWith = this.dimensions.width() * 0.8F; -+ final AABB box = AABB.ofSize(this.getEyePosition(), reducedWith, 1.0E-6D, reducedWith); ++ final double reducedWith = (double)(this.dimensions.width() * 0.8F); ++ final AABB boundingBox = AABB.ofSize(this.getEyePosition(), reducedWith, 1.0E-6D, reducedWith); ++ final Level world = this.level; - return !iblockdata.isAir() && iblockdata.isSuffocating(this.level(), blockposition) && Shapes.joinIsNotEmpty(iblockdata.getCollisionShape(this.level(), blockposition).move((double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ()), Shapes.create(axisalignedbb), BooleanOp.AND); - }); -+ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isEmpty(box)) { ++ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isEmpty(boundingBox)) { + return false; -+ } + } + -+ final BlockPos.MutableBlockPos tempPos = new BlockPos.MutableBlockPos(); ++ final int minBlockX = Mth.floor(boundingBox.minX); ++ final int minBlockY = Mth.floor(boundingBox.minY); ++ final int minBlockZ = Mth.floor(boundingBox.minZ); + -+ final int minX = Mth.floor(box.minX); -+ final int minY = Mth.floor(box.minY); -+ final int minZ = Mth.floor(box.minZ); -+ final int maxX = Mth.floor(box.maxX); -+ final int maxY = Mth.floor(box.maxY); -+ final int maxZ = Mth.floor(box.maxZ); ++ final int maxBlockX = Mth.floor(boundingBox.maxX); ++ final int maxBlockY = Mth.floor(boundingBox.maxY); ++ final int maxBlockZ = Mth.floor(boundingBox.maxZ); + -+ final net.minecraft.world.level.chunk.ChunkSource chunkProvider = this.level.getChunkSource(); ++ final int minChunkX = minBlockX >> 4; ++ final int minChunkY = minBlockY >> 4; ++ final int minChunkZ = minBlockZ >> 4; + -+ long lastChunkKey = ChunkPos.INVALID_CHUNK_POS; -+ net.minecraft.world.level.chunk.LevelChunk lastChunk = null; -+ for (int fz = minZ; fz <= maxZ; ++fz) { -+ tempPos.setZ(fz); -+ for (int fx = minX; fx <= maxX; ++fx) { -+ final int newChunkX = fx >> 4; -+ final int newChunkZ = fz >> 4; -+ final net.minecraft.world.level.chunk.LevelChunk chunk = lastChunkKey == (lastChunkKey = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(newChunkX, newChunkZ)) ? -+ lastChunk : (lastChunk = (net.minecraft.world.level.chunk.LevelChunk)chunkProvider.getChunk(newChunkX, newChunkZ, net.minecraft.world.level.chunk.status.ChunkStatus.FULL, true)); -+ tempPos.setX(fx); -+ for (int fy = minY; fy <= maxY; ++fy) { -+ tempPos.setY(fy); ++ final int maxChunkX = maxBlockX >> 4; ++ final int maxChunkY = maxBlockY >> 4; ++ final int maxChunkZ = maxBlockZ >> 4; + -+ final BlockState state = chunk.getBlockState(tempPos); ++ final int minSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection(world); ++ final net.minecraft.world.level.chunk.ChunkSource chunkSource = world.getChunkSource(); ++ final BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos(); + -+ if (((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)state).moonrise$emptyCollisionShape() || !state.isSuffocating(this.level, tempPos)) { ++ for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) { ++ for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) { ++ final net.minecraft.world.level.chunk.LevelChunkSection[] sections = chunkSource.getChunk(currChunkX, currChunkZ, net.minecraft.world.level.chunk.status.ChunkStatus.FULL, true).getSections(); ++ ++ for (int currChunkY = minChunkY; currChunkY <= maxChunkY; ++currChunkY) { ++ final int sectionIdx = currChunkY - minSection; ++ if (sectionIdx < 0 || sectionIdx >= sections.length) { ++ continue; ++ } ++ final net.minecraft.world.level.chunk.LevelChunkSection section = sections[sectionIdx]; ++ if (section.hasOnlyAir()) { ++ // empty + continue; + } + -+ // Yes, it does not use the Entity context stuff. -+ final VoxelShape collisionShape = state.getCollisionShape(this.level, tempPos); ++ final net.minecraft.world.level.chunk.PalettedContainer blocks = section.states; + -+ if (collisionShape.isEmpty()) { -+ continue; -+ } ++ final int minXIterate = currChunkX == minChunkX ? (minBlockX & 15) : 0; ++ final int maxXIterate = currChunkX == maxChunkX ? (maxBlockX & 15) : 15; ++ final int minZIterate = currChunkZ == minChunkZ ? (minBlockZ & 15) : 0; ++ final int maxZIterate = currChunkZ == maxChunkZ ? (maxBlockZ & 15) : 15; ++ final int minYIterate = currChunkY == minChunkY ? (minBlockY & 15) : 0; ++ final int maxYIterate = currChunkY == maxChunkY ? (maxBlockY & 15) : 15; + -+ final AABB toCollide = box.move(-(double)fx, -(double)fy, -(double)fz); ++ for (int currY = minYIterate; currY <= maxYIterate; ++currY) { ++ final int blockY = currY | (currChunkY << 4); ++ mutablePos.setY(blockY); ++ for (int currZ = minZIterate; currZ <= maxZIterate; ++currZ) { ++ final int blockZ = currZ | (currChunkZ << 4); ++ mutablePos.setZ(blockZ); ++ for (int currX = minXIterate; currX <= maxXIterate; ++currX) { ++ final int blockX = currX | (currChunkX << 4); ++ mutablePos.setX(blockX); + -+ final AABB singleAABB = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)collisionShape).moonrise$getSingleAABBRepresentation(); -+ if (singleAABB != null) { -+ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.voxelShapeIntersect(singleAABB, toCollide)) { -+ return true; ++ final BlockState blockState = blocks.get((currX) | (currZ << 4) | ((currY) << 8)); ++ ++ if (((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)blockState).moonrise$emptyCollisionShape() ++ || !blockState.isSuffocating(world, mutablePos)) { ++ continue; ++ } ++ ++ // Yes, it does not use the Entity context stuff. ++ final VoxelShape collisionShape = blockState.getCollisionShape(world, mutablePos); ++ ++ if (collisionShape.isEmpty()) { ++ continue; ++ } ++ ++ final AABB toCollide = boundingBox.move(-(double)blockX, -(double)blockY, -(double)blockZ); ++ ++ final AABB singleAABB = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)collisionShape).moonrise$getSingleAABBRepresentation(); ++ if (singleAABB != null) { ++ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.voxelShapeIntersect(singleAABB, toCollide)) { ++ return true; ++ } ++ continue; ++ } ++ ++ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.voxelShapeIntersectNoEmpty(collisionShape, toCollide)) { ++ return true; ++ } ++ continue; ++ } + } -+ continue; + } -+ -+ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.voxelShapeIntersectNoEmpty(collisionShape, toCollide)) { -+ return true; -+ } -+ continue; + } + } - } ++ } + + return false; + // Paper end - optimise collisions } public InteractionResult interact(Player player, InteractionHand hand) { -@@ -4180,14 +4369,17 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -4180,14 +4457,17 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess } public Iterable getIndirectPassengers() { @@ -27247,7 +28513,202 @@ index 8cdef637f6343119fc77f87e7478ee23e9b8efab..5410a0380c44629f1c9b4f0a8e6017cf } private Iterable getIndirectPassengers_old() { // Paper end - Optimize indirect passenger iteration -@@ -4543,6 +4735,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -4316,82 +4596,136 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + return Mth.lerp(delta, this.yRotO, this.yRot); + } + +- public boolean updateFluidHeightAndDoFluidPushing(TagKey tag, double speed) { ++ // Paper start - optimise collisions ++ public boolean updateFluidHeightAndDoFluidPushing(final TagKey fluid, final double flowScale) { + if (this.touchingUnloadedChunk()) { + return false; +- } else { +- AABB axisalignedbb = this.getBoundingBox().deflate(0.001D); +- int i = Mth.floor(axisalignedbb.minX); +- int j = Mth.ceil(axisalignedbb.maxX); +- int k = Mth.floor(axisalignedbb.minY); +- int l = Mth.ceil(axisalignedbb.maxY); +- int i1 = Mth.floor(axisalignedbb.minZ); +- int j1 = Mth.ceil(axisalignedbb.maxZ); +- double d1 = 0.0D; +- boolean flag = this.isPushedByFluid(); +- boolean flag1 = false; +- Vec3 vec3d = Vec3.ZERO; +- int k1 = 0; +- BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos(); +- +- for (int l1 = i; l1 < j; ++l1) { +- for (int i2 = k; i2 < l; ++i2) { +- for (int j2 = i1; j2 < j1; ++j2) { +- blockposition_mutableblockposition.set(l1, i2, j2); +- FluidState fluid = this.level().getFluidState(blockposition_mutableblockposition); +- +- if (fluid.is(tag)) { +- double d2 = (double) ((float) i2 + fluid.getHeight(this.level(), blockposition_mutableblockposition)); +- +- if (d2 >= axisalignedbb.minY) { +- flag1 = true; +- d1 = Math.max(d2 - axisalignedbb.minY, d1); +- if (flag) { +- Vec3 vec3d1 = fluid.getFlow(this.level(), blockposition_mutableblockposition); +- +- if (d1 < 0.4D) { +- vec3d1 = vec3d1.scale(d1); +- } ++ } ++ ++ final AABB boundingBox = this.getBoundingBox().deflate(1.0E-3); ++ ++ final Level world = this.level; ++ final int minSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection(world); ++ ++ final int minBlockX = Mth.floor(boundingBox.minX); ++ final int minBlockY = Math.max((minSection << 4), Mth.floor(boundingBox.minY)); ++ final int minBlockZ = Mth.floor(boundingBox.minZ); ++ ++ // note: bounds are exclusive in Vanilla, so we subtract 1 - our loop expects bounds to be inclusive ++ final int maxBlockX = Mth.ceil(boundingBox.maxX) - 1; ++ final int maxBlockY = Math.min((ca.spottedleaf.moonrise.common.util.WorldUtil.getMaxSection(world) << 4) | 15, Mth.ceil(boundingBox.maxY) - 1); ++ final int maxBlockZ = Mth.ceil(boundingBox.maxZ) - 1; ++ ++ final boolean isPushable = this.isPushedByFluid(); ++ final BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos(); ++ ++ Vec3 pushVector = Vec3.ZERO; ++ double totalPushes = 0.0; ++ double maxHeightDiff = 0.0; ++ boolean inFluid = false; ++ ++ final int minChunkX = minBlockX >> 4; ++ final int maxChunkX = maxBlockX >> 4; ++ ++ final int minChunkY = minBlockY >> 4; ++ final int maxChunkY = maxBlockY >> 4; ++ ++ final int minChunkZ = minBlockZ >> 4; ++ final int maxChunkZ = maxBlockZ >> 4; ++ ++ final net.minecraft.world.level.chunk.ChunkSource chunkSource = world.getChunkSource(); ++ ++ for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) { ++ for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) { ++ final net.minecraft.world.level.chunk.LevelChunkSection[] sections = chunkSource.getChunk(currChunkX, currChunkZ, net.minecraft.world.level.chunk.status.ChunkStatus.FULL, false).getSections(); ++ ++ // bound y ++ for (int currChunkY = minChunkY; currChunkY <= maxChunkY; ++currChunkY) { ++ final int sectionIdx = currChunkY - minSection; ++ if (sectionIdx < 0 || sectionIdx >= sections.length) { ++ continue; ++ } ++ final net.minecraft.world.level.chunk.LevelChunkSection section = sections[sectionIdx]; ++ if (section.hasOnlyAir()) { ++ // empty ++ continue; ++ } ++ ++ final net.minecraft.world.level.chunk.PalettedContainer blocks = section.states; ++ ++ final int minXIterate = currChunkX == minChunkX ? (minBlockX & 15) : 0; ++ final int maxXIterate = currChunkX == maxChunkX ? (maxBlockX & 15) : 15; ++ final int minZIterate = currChunkZ == minChunkZ ? (minBlockZ & 15) : 0; ++ final int maxZIterate = currChunkZ == maxChunkZ ? (maxBlockZ & 15) : 15; ++ final int minYIterate = currChunkY == minChunkY ? (minBlockY & 15) : 0; ++ final int maxYIterate = currChunkY == maxChunkY ? (maxBlockY & 15) : 15; ++ ++ for (int currY = minYIterate; currY <= maxYIterate; ++currY) { ++ for (int currZ = minZIterate; currZ <= maxZIterate; ++currZ) { ++ for (int currX = minXIterate; currX <= maxXIterate; ++currX) { ++ final FluidState fluidState = blocks.get((currX) | (currZ << 4) | ((currY) << 8)).getFluidState(); ++ ++ if (fluidState.isEmpty() || !fluidState.is(fluid)) { ++ continue; ++ } ++ ++ mutablePos.set(currX | (currChunkX << 4), currY | (currChunkY << 4), currZ | (currChunkZ << 4)); + +- vec3d = vec3d.add(vec3d1); +- ++k1; ++ final double height = (double)((float)mutablePos.getY() + fluidState.getHeight(world, mutablePos)); ++ final double diff = height - boundingBox.minY; ++ ++ if (diff < 0.0) { ++ continue; + } +- // CraftBukkit start - store last lava contact location +- if (tag == FluidTags.LAVA) { +- this.lastLavaContact = blockposition_mutableblockposition.immutable(); ++ ++ inFluid = true; ++ maxHeightDiff = Math.max(maxHeightDiff, diff); ++ ++ if (!isPushable) { ++ continue; ++ } ++ ++ ++totalPushes; ++ ++ final Vec3 flow = fluidState.getFlow(world, mutablePos); ++ ++ if (diff < 0.4) { ++ pushVector = pushVector.add(flow.scale(diff)); ++ } else { ++ pushVector = pushVector.add(flow); + } +- // CraftBukkit end + } + } + } + } + } ++ } + +- if (vec3d.length() > 0.0D) { +- if (k1 > 0) { +- vec3d = vec3d.scale(1.0D / (double) k1); +- } ++ this.fluidHeight.put(fluid, maxHeightDiff); + +- if (!(this instanceof Player)) { +- vec3d = vec3d.normalize(); +- } ++ if (pushVector.lengthSqr() == 0.0) { ++ return inFluid; ++ } + +- Vec3 vec3d2 = this.getDeltaMovement(); ++ // note: totalPushes != 0 as pushVector != 0 ++ pushVector = pushVector.scale(1.0 / totalPushes); ++ final Vec3 currMovement = this.getDeltaMovement(); + +- vec3d = vec3d.scale(speed); +- double d3 = 0.003D; ++ if (!((Entity)(Object)this instanceof Player)) { ++ pushVector = pushVector.normalize(); ++ } + +- if (Math.abs(vec3d2.x) < 0.003D && Math.abs(vec3d2.z) < 0.003D && vec3d.length() < 0.0045000000000000005D) { +- vec3d = vec3d.normalize().scale(0.0045000000000000005D); +- } ++ pushVector = pushVector.scale(flowScale); ++ if (Math.abs(currMovement.x) < 0.003 && Math.abs(currMovement.z) < 0.003 && pushVector.length() < 0.0045000000000000005) { ++ pushVector = pushVector.normalize().scale(0.0045000000000000005); ++ } + +- this.setDeltaMovement(this.getDeltaMovement().add(vec3d)); +- } ++ this.setDeltaMovement(currMovement.add(pushVector)); + +- this.fluidHeight.put(tag, d1); +- return flag1; +- } ++ // note: inFluid = true here as pushVector != 0 ++ return true; + } ++ // Paper end - optimise collisions + + public boolean touchingUnloadedChunk() { + AABB axisalignedbb = this.getBoundingBox().inflate(1.0D); +@@ -4543,6 +4877,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess this.setPosRaw(x, y, z, false); } public final void setPosRaw(double x, double y, double z, boolean forceBoundingBoxUpdate) { @@ -27263,7 +28724,7 @@ index 8cdef637f6343119fc77f87e7478ee23e9b8efab..5410a0380c44629f1c9b4f0a8e6017cf if (!checkPosition(this, x, y, z)) { return; } -@@ -4672,6 +4873,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -4672,6 +5015,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess @Override public final void setRemoved(Entity.RemovalReason entity_removalreason, EntityRemoveEvent.Cause cause) { @@ -27276,7 +28737,7 @@ index 8cdef637f6343119fc77f87e7478ee23e9b8efab..5410a0380c44629f1c9b4f0a8e6017cf CraftEventFactory.callEntityRemoveEvent(this, cause); // CraftBukkit end if (this.removalReason == null) { -@@ -4682,7 +4889,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -4682,7 +5031,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess this.stopRiding(); } @@ -27285,7 +28746,7 @@ index 8cdef637f6343119fc77f87e7478ee23e9b8efab..5410a0380c44629f1c9b4f0a8e6017cf this.levelCallback.onRemove(entity_removalreason); this.onRemoval(entity_removalreason); } -@@ -4698,7 +4905,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -4698,7 +5047,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess @Override public boolean shouldBeSaved() { @@ -27295,10 +28756,10 @@ index 8cdef637f6343119fc77f87e7478ee23e9b8efab..5410a0380c44629f1c9b4f0a8e6017cf @Override diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java -index 96bc0ba60195e5e666d47b3a0b943b733986d96a..4cf6cb0abfeb7065c6d9381fb4194371c0cddc35 100644 +index 96bc0ba60195e5e666d47b3a0b943b733986d96a..5930a430983061afddf20e3208ff2462ca1b78cd 100644 --- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java +++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java -@@ -38,12 +38,130 @@ import net.minecraft.world.level.chunk.storage.RegionStorageInfo; +@@ -38,12 +38,137 @@ import net.minecraft.world.level.chunk.storage.RegionStorageInfo; import net.minecraft.world.level.chunk.storage.SectionStorage; import net.minecraft.world.level.chunk.storage.SimpleRegionStorage; @@ -27338,8 +28799,7 @@ index 96bc0ba60195e5e666d47b3a0b943b733986d96a..4cf6cb0abfeb7065c6d9381fb4194371 + + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Accessing poi chunk off-main"); + -+ final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager manager = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.world).moonrise$getChunkTaskScheduler().chunkHolderManager; -+ final ca.spottedleaf.moonrise.patches.chunk_system.level.poi.PoiChunk ret = manager.getPoiChunkIfLoaded(chunkX, chunkZ, true); ++ final ca.spottedleaf.moonrise.patches.chunk_system.level.poi.PoiChunk ret = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.world).moonrise$getChunkTaskScheduler().chunkHolderManager.getPoiChunkIfLoaded(chunkX, chunkZ, true); + + return ret == null ? Optional.empty() : ret.getSectionForVanilla(chunkY); + } @@ -27393,9 +28853,13 @@ index 96bc0ba60195e5e666d47b3a0b943b733986d96a..4cf6cb0abfeb7065c6d9381fb4194371 + public final void moonrise$onUnload(final long coordinate) { // Paper - rewrite chunk system + final int chunkX = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(coordinate); + final int chunkZ = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(coordinate); ++ ++ final int minY = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection(this.world); ++ final int maxY = ca.spottedleaf.moonrise.common.util.WorldUtil.getMaxSection(this.world); ++ + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Unloading poi chunk off-main"); -+ for (int section = this.levelHeightAccessor.getMinSection(); section < this.levelHeightAccessor.getMaxSection(); ++section) { -+ final long sectionPos = SectionPos.asLong(chunkX, section, chunkZ); ++ for (int sectionY = minY; sectionY <= maxY; ++sectionY) { ++ final long sectionPos = SectionPos.asLong(chunkX, sectionY, chunkZ); + this.updateDistanceTracking(sectionPos); + } + } @@ -27404,8 +28868,12 @@ index 96bc0ba60195e5e666d47b3a0b943b733986d96a..4cf6cb0abfeb7065c6d9381fb4194371 + public final void moonrise$loadInPoiChunk(final ca.spottedleaf.moonrise.patches.chunk_system.level.poi.PoiChunk poiChunk) { + final int chunkX = poiChunk.chunkX; + final int chunkZ = poiChunk.chunkZ; ++ ++ final int minY = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection(this.world); ++ final int maxY = ca.spottedleaf.moonrise.common.util.WorldUtil.getMaxSection(this.world); ++ + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Loading poi chunk off-main"); -+ for (int sectionY = this.levelHeightAccessor.getMinSection(); sectionY < this.levelHeightAccessor.getMaxSection(); ++sectionY) { ++ for (int sectionY = minY; sectionY <= maxY; ++sectionY) { + final PoiSection section = poiChunk.getSection(sectionY); + if (section != null && !((ca.spottedleaf.moonrise.patches.chunk_system.level.poi.ChunkSystemPoiSection)section).moonrise$isEmpty()) { + this.onSectionLoad(SectionPos.asLong(chunkX, sectionY, chunkZ)); @@ -27430,7 +28898,7 @@ index 96bc0ba60195e5e666d47b3a0b943b733986d96a..4cf6cb0abfeb7065c6d9381fb4194371 public PoiManager( RegionStorageInfo storageKey, Path directory, -@@ -64,6 +182,7 @@ public class PoiManager extends SectionStorage { +@@ -64,6 +189,7 @@ public class PoiManager extends SectionStorage { world ); this.distanceTracker = new PoiManager.DistanceTracker(); @@ -27438,18 +28906,20 @@ index 96bc0ba60195e5e666d47b3a0b943b733986d96a..4cf6cb0abfeb7065c6d9381fb4194371 } public void add(BlockPos pos, Holder type) { -@@ -197,8 +316,8 @@ public class PoiManager extends SectionStorage { +@@ -197,8 +323,10 @@ public class PoiManager extends SectionStorage { } public int sectionsToVillage(SectionPos pos) { - this.distanceTracker.runAllUpdates(); - return this.distanceTracker.getLevel(pos.asLong()); -+ this.villageDistanceTracker.propagateUpdates(); // Paper - rewrite chunk system -+ return convertBetweenLevels(this.villageDistanceTracker.getLevel(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkSectionKey(pos))); // Paper - rewrite chunk system ++ // Paper start - rewrite chunk system ++ this.villageDistanceTracker.propagateUpdates(); ++ return convertBetweenLevels(this.villageDistanceTracker.getLevel(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkSectionKey(pos))); ++ // Paper end - rewrite chunk system } boolean isVillageCenter(long pos) { -@@ -212,19 +331,26 @@ public class PoiManager extends SectionStorage { +@@ -212,19 +340,26 @@ public class PoiManager extends SectionStorage { @Override public void tick(BooleanSupplier shouldKeepTicking) { @@ -27482,7 +28952,7 @@ index 96bc0ba60195e5e666d47b3a0b943b733986d96a..4cf6cb0abfeb7065c6d9381fb4194371 } public void checkConsistencyWithBlocks(SectionPos sectionPos, LevelChunkSection chunkSection) { -@@ -263,7 +389,7 @@ public class PoiManager extends SectionStorage { +@@ -263,7 +398,7 @@ public class PoiManager extends SectionStorage { .map(sectionPos -> Pair.of(sectionPos, this.getOrLoad(sectionPos.asLong()))) .filter(pair -> !pair.getSecond().map(PoiSection::isValid).orElse(false)) .map(pair -> pair.getFirst().chunk()) @@ -27492,7 +28962,7 @@ index 96bc0ba60195e5e666d47b3a0b943b733986d96a..4cf6cb0abfeb7065c6d9381fb4194371 } diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java -index b9e0bc8f1e948614d986335de1f3d2df199eea81..f6f0d7c21ee81ff33d4af350c4d39aadfbe140df 100644 +index b9e0bc8f1e948614d986335de1f3d2df199eea81..712cbfc100e8aaf612d1d651dae64f57f892a768 100644 --- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java +++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java @@ -23,13 +23,27 @@ import net.minecraft.core.SectionPos; @@ -27508,7 +28978,7 @@ index b9e0bc8f1e948614d986335de1f3d2df199eea81..f6f0d7c21ee81ff33d4af350c4d39aad private boolean isValid; + // Paper start - rewrite chunk system -+ private final Optional noAllocOptional = Optional.of((PoiSection)(Object)this);; ++ private final Optional noAllocOptional = Optional.of((PoiSection)(Object)this); + + @Override + public final boolean moonrise$isEmpty() { @@ -27675,7 +29145,7 @@ index e185a33b5b1f8e8e0a0e666b24ba3e9186a8a7ff..5d7a6e4b73f032db356e7ec369b15001 // Paper start - Affects Spawning API diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index d048d0e4b16459b5bad44ebfa3c6a8f336f6762b..332dc7e6bdfb5b3741764d4877185a2e86a982f8 100644 +index d048d0e4b16459b5bad44ebfa3c6a8f336f6762b..40fe47c7c145587ac81f0f15c237ed72ea9c094d 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java @@ -83,6 +83,7 @@ import net.minecraft.world.level.storage.LevelData; @@ -27695,7 +29165,7 @@ index d048d0e4b16459b5bad44ebfa3c6a8f336f6762b..332dc7e6bdfb5b3741764d4877185a2e public static final Codec> RESOURCE_KEY_CODEC = ResourceKey.codec(Registries.DIMENSION); public static final ResourceKey OVERWORLD = ResourceKey.create(Registries.DIMENSION, ResourceLocation.withDefaultNamespace("overworld")); -@@ -190,6 +191,483 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -190,7 +191,584 @@ public abstract class Level implements LevelAccessor, AutoCloseable { public abstract ResourceKey getTypeKey(); @@ -27772,26 +29242,13 @@ index d048d0e4b16459b5bad44ebfa3c6a8f336f6762b..332dc7e6bdfb5b3741764d4877185a2e + } + // Paper end - rewrite chunk system + // Paper start - optimise collisions -+ private final int minSection; -+ private final int maxSection; -+ -+ @Override -+ public final int moonrise$getMinSection() { -+ return this.minSection; -+ } -+ -+ @Override -+ public final int moonrise$getMaxSection() { -+ return this.maxSection; -+ } -+ + /** + * Route to faster lookup. + * See {@link EntityGetter#isUnobstructed(Entity, VoxelShape)} for expected behavior + * @author Spottedleaf + */ + @Override -+ public final boolean isUnobstructed(final Entity entity) { ++ public boolean isUnobstructed(final Entity entity) { + final AABB boundingBox = entity.getBoundingBox(); + if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isEmpty(boundingBox)) { + return false; @@ -27821,7 +29278,7 @@ index d048d0e4b16459b5bad44ebfa3c6a8f336f6762b..332dc7e6bdfb5b3741764d4877185a2e + final Vec3 to = clipContext.getTo(); + final Vec3 from = clipContext.getFrom(); + -+ return net.minecraft.world.phys.BlockHitResult.miss(to, Direction.getNearest(from.x - to.x, from.y - to.y, from.z - to.z), BlockPos.containing(to.x, to.y, to.z)); ++ return net.minecraft.world.phys.BlockHitResult.miss(to, Direction.getApproximateNearest(from.x - to.x, from.y - to.y, from.z - to.z), BlockPos.containing(to.x, to.y, to.z)); + } + + private static final FluidState AIR_FLUIDSTATE = Fluids.EMPTY.defaultFluidState(); @@ -27875,7 +29332,7 @@ index d048d0e4b16459b5bad44ebfa3c6a8f336f6762b..332dc7e6bdfb5b3741764d4877185a2e + int lastChunkY = Integer.MIN_VALUE; + int lastChunkZ = Integer.MIN_VALUE; + -+ final int minSection = ((ca.spottedleaf.moonrise.patches.collisions.world.CollisionLevel)level).moonrise$getMinSection(); ++ final int minSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection(level); + + for (;;) { + currPos.set(currX, currY, currZ); @@ -27958,7 +29415,7 @@ index d048d0e4b16459b5bad44ebfa3c6a8f336f6762b..332dc7e6bdfb5b3741764d4877185a2e + * @author Spottedleaf + */ + @Override -+ public final net.minecraft.world.phys.BlockHitResult clip(final ClipContext clipContext) { ++ public net.minecraft.world.phys.BlockHitResult clip(final ClipContext clipContext) { + // can only do this in this class, as not everything that implements BlockGetter can retrieve chunks + return fastClip(clipContext.getFrom(), clipContext.getTo(), (Level)(Object)this, clipContext); + } @@ -27968,7 +29425,7 @@ index d048d0e4b16459b5bad44ebfa3c6a8f336f6762b..332dc7e6bdfb5b3741764d4877185a2e + * @author Spottedleaf + */ + @Override -+ public final boolean collidesWithSuffocatingBlock(final Entity entity, final AABB box) { ++ public boolean collidesWithSuffocatingBlock(final Entity entity, final AABB box) { + return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.getCollisionsForBlocksOrWorldBorder((Level)(Object)this, entity, box, null, null, + ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_CHECK_ONLY, + (final BlockState state, final BlockPos pos) -> { @@ -27994,8 +29451,8 @@ index d048d0e4b16459b5bad44ebfa3c6a8f336f6762b..332dc7e6bdfb5b3741764d4877185a2e + * @author Spottedleaf + */ + @Override -+ public final java.util.Optional findFreePosition(final Entity entity, final VoxelShape boundsShape, final Vec3 fromPosition, -+ final double rangeX, final double rangeY, final double rangeZ) { ++ public java.util.Optional findFreePosition(final Entity entity, final VoxelShape boundsShape, final Vec3 fromPosition, ++ final double rangeX, final double rangeY, final double rangeZ) { + if (boundsShape.isEmpty()) { + return java.util.Optional.empty(); + } @@ -28054,103 +29511,139 @@ index d048d0e4b16459b5bad44ebfa3c6a8f336f6762b..332dc7e6bdfb5b3741764d4877185a2e + * @author Spottedleaf + */ + @Override -+ public final java.util.Optional findSupportingBlock(final Entity entity, final AABB aabb) { ++ public java.util.Optional findSupportingBlock(final Entity entity, final AABB aabb) { ++ final int minSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection((Level)(Object)this); ++ + final int minBlockX = Mth.floor(aabb.minX - ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) - 1; + final int maxBlockX = Mth.floor(aabb.maxX + ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) + 1; + -+ final int minBlockY = Mth.floor(aabb.minY - ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) - 1; -+ final int maxBlockY = Mth.floor(aabb.maxY + ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) + 1; ++ final int minBlockY = Math.max((minSection << 4) - 1, Mth.floor(aabb.minY - ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) - 1); ++ final int maxBlockY = Math.min((ca.spottedleaf.moonrise.common.util.WorldUtil.getMaxSection((Level)(Object)this) << 4) + 16, Mth.floor(aabb.maxY + ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) + 1); + + final int minBlockZ = Mth.floor(aabb.minZ - ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) - 1; + final int maxBlockZ = Mth.floor(aabb.maxZ + ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) + 1; + -+ ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.LazyEntityCollisionContext collisionContext = null; -+ -+ final BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); ++ final BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos(); ++ final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.LazyEntityCollisionContext collisionShape = new ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.LazyEntityCollisionContext(entity); + BlockPos selected = null; + double selectedDistance = Double.MAX_VALUE; -+ + final Vec3 entityPos = entity.position(); + -+ LevelChunk lastChunk = null; -+ int lastChunkX = Integer.MIN_VALUE; -+ int lastChunkZ = Integer.MIN_VALUE; ++ // special cases: ++ if (minBlockY > maxBlockY) { ++ // no point in checking ++ return java.util.Optional.empty(); ++ } ++ ++ final int minChunkX = minBlockX >> 4; ++ final int maxChunkX = maxBlockX >> 4; ++ ++ final int minChunkY = minBlockY >> 4; ++ final int maxChunkY = maxBlockY >> 4; ++ ++ final int minChunkZ = minBlockZ >> 4; ++ final int maxChunkZ = maxBlockZ >> 4; + + final ChunkSource chunkSource = this.getChunkSource(); + -+ for (int currZ = minBlockZ; currZ <= maxBlockZ; ++currZ) { -+ pos.setZ(currZ); -+ for (int currX = minBlockX; currX <= maxBlockX; ++currX) { -+ pos.setX(currX); ++ for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) { ++ for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) { ++ final ChunkAccess chunk = chunkSource.getChunk(currChunkX, currChunkZ, ChunkStatus.FULL, false); + -+ final int newChunkX = currX >> 4; -+ final int newChunkZ = currZ >> 4; -+ -+ if (((newChunkX ^ lastChunkX) | (newChunkZ ^ lastChunkZ)) != 0) { -+ lastChunkX = newChunkX; -+ lastChunkZ = newChunkZ; -+ lastChunk = (LevelChunk)chunkSource.getChunk(newChunkX, newChunkZ, ChunkStatus.FULL, false); -+ } -+ -+ if (lastChunk == null) { ++ if (chunk == null) { + continue; + } -+ for (int currY = minBlockY; currY <= maxBlockY; ++currY) { -+ int edgeCount = ((currX == minBlockX || currX == maxBlockX) ? 1 : 0) + -+ ((currY == minBlockY || currY == maxBlockY) ? 1 : 0) + -+ ((currZ == minBlockZ || currZ == maxBlockZ) ? 1 : 0); -+ if (edgeCount == 3) { ++ ++ final net.minecraft.world.level.chunk.LevelChunkSection[] sections = chunk.getSections(); ++ ++ // bound y ++ for (int currChunkY = minChunkY; currChunkY <= maxChunkY; ++currChunkY) { ++ final int sectionIdx = currChunkY - minSection; ++ if (sectionIdx < 0 || sectionIdx >= sections.length) { ++ continue; ++ } ++ final net.minecraft.world.level.chunk.LevelChunkSection section = sections[sectionIdx]; ++ if (section.hasOnlyAir()) { ++ // empty + continue; + } + -+ pos.setY(currY); ++ final boolean hasSpecial = ((ca.spottedleaf.moonrise.patches.block_counting.BlockCountingChunkSection)section).moonrise$hasSpecialCollidingBlocks(); ++ final int sectionAdjust = !hasSpecial ? 1 : 0; + -+ final double distance = pos.distToCenterSqr(entityPos); -+ if (distance > selectedDistance || (distance == selectedDistance && selected.compareTo(pos) >= 0)) { -+ continue; -+ } ++ final net.minecraft.world.level.chunk.PalettedContainer blocks = section.states; + -+ final BlockState state = ((ca.spottedleaf.moonrise.patches.chunk_getblock.GetBlockChunk)lastChunk).moonrise$getBlock(currX, currY, currZ); -+ if (((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)state).moonrise$emptyCollisionShape()) { -+ continue; -+ } ++ final int minXIterate = currChunkX == minChunkX ? (minBlockX & 15) + sectionAdjust : 0; ++ final int maxXIterate = currChunkX == maxChunkX ? (maxBlockX & 15) - sectionAdjust : 15; ++ final int minZIterate = currChunkZ == minChunkZ ? (minBlockZ & 15) + sectionAdjust : 0; ++ final int maxZIterate = currChunkZ == maxChunkZ ? (maxBlockZ & 15) - sectionAdjust : 15; ++ final int minYIterate = currChunkY == minChunkY ? (minBlockY & 15) + sectionAdjust : 0; ++ final int maxYIterate = currChunkY == maxChunkY ? (maxBlockY & 15) - sectionAdjust : 15; + -+ VoxelShape blockCollision = ((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)state).moonrise$getConstantCollisionShape(); ++ for (int currY = minYIterate; currY <= maxYIterate; ++currY) { ++ final int blockY = currY | (currChunkY << 4); ++ mutablePos.setY(blockY); ++ for (int currZ = minZIterate; currZ <= maxZIterate; ++currZ) { ++ final int blockZ = currZ | (currChunkZ << 4); ++ mutablePos.setZ(blockZ); ++ for (int currX = minXIterate; currX <= maxXIterate; ++currX) { ++ final int localBlockIndex = (currX) | (currZ << 4) | ((currY) << 8); ++ final int blockX = currX | (currChunkX << 4); ++ mutablePos.setX(blockX); + -+ if ((edgeCount != 1 || state.hasLargeCollisionShape()) && (edgeCount != 2 || state.getBlock() == Blocks.MOVING_PISTON)) { -+ if (collisionContext == null) { -+ collisionContext = new ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.LazyEntityCollisionContext(entity); -+ } ++ final int edgeCount = hasSpecial ? ((blockX == minBlockX || blockX == maxBlockX) ? 1 : 0) + ++ ((blockY == minBlockY || blockY == maxBlockY) ? 1 : 0) + ++ ((blockZ == minBlockZ || blockZ == maxBlockZ) ? 1 : 0) : 0; ++ if (edgeCount == 3) { ++ continue; ++ } + -+ if (blockCollision == null) { -+ blockCollision = state.getCollisionShape((Level)(Object)this, pos, collisionContext); -+ } ++ final double distance = mutablePos.distToCenterSqr(entityPos); ++ if (distance > selectedDistance || (distance == selectedDistance && selected.compareTo(mutablePos) >= 0)) { ++ continue; ++ } + -+ if (blockCollision.isEmpty()) { -+ continue; -+ } ++ final BlockState blockData = blocks.get(localBlockIndex); + -+ // avoid VoxelShape#move by shifting the entity collision shape instead -+ final AABB shiftedAABB = aabb.move(-(double)currX, -(double)currY, -(double)currZ); ++ if (((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)blockData).moonrise$emptyContextCollisionShape()) { ++ continue; ++ } + -+ final AABB singleAABB = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)blockCollision).moonrise$getSingleAABBRepresentation(); -+ if (singleAABB != null) { -+ if (!ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.voxelShapeIntersect(singleAABB, shiftedAABB)) { -+ continue; ++ VoxelShape blockCollision = ((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)blockData).moonrise$getConstantContextCollisionShape(); ++ ++ if (edgeCount == 0 || ((edgeCount != 1 || blockData.hasLargeCollisionShape()) && (edgeCount != 2 || blockData.getBlock() == Blocks.MOVING_PISTON))) { ++ if (blockCollision == null) { ++ blockCollision = blockData.getCollisionShape((Level)(Object)this, mutablePos, collisionShape); ++ ++ if (blockCollision.isEmpty()) { ++ continue; ++ } ++ } ++ ++ // avoid VoxelShape#move by shifting the entity collision shape instead ++ final AABB shiftedAABB = aabb.move(-(double)blockX, -(double)blockY, -(double)blockZ); ++ ++ final AABB singleAABB = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)blockCollision).moonrise$getSingleAABBRepresentation(); ++ if (singleAABB != null) { ++ if (!ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.voxelShapeIntersect(singleAABB, shiftedAABB)) { ++ continue; ++ } ++ ++ selected = mutablePos.immutable(); ++ selectedDistance = distance; ++ continue; ++ } ++ ++ if (!ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.voxelShapeIntersectNoEmpty(blockCollision, shiftedAABB)) { ++ continue; ++ } ++ ++ selected = mutablePos.immutable(); ++ selectedDistance = distance; ++ continue; ++ } + } -+ -+ selected = pos.immutable(); -+ selectedDistance = distance; -+ continue; + } -+ -+ if (!ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.voxelShapeIntersectNoEmpty(blockCollision, shiftedAABB)) { -+ continue; -+ } -+ -+ selected = pos.immutable(); -+ selectedDistance = distance; -+ continue; + } + } + } @@ -28159,6 +29652,74 @@ index d048d0e4b16459b5bad44ebfa3c6a8f336f6762b..332dc7e6bdfb5b3741764d4877185a2e + return java.util.Optional.ofNullable(selected); + } + // Paper end - optimise collisions ++ // Paper start - getblock optimisations - cache world height/sections ++ private final int minY; ++ private final int height; ++ private final int maxY; ++ private final int minSectionY; ++ private final int maxSectionY; ++ private final int sectionsCount; ++ ++ @Override ++ public int getMinY() { ++ return this.minY; ++ } ++ ++ @Override ++ public int getHeight() { ++ return this.height; ++ } ++ ++ @Override ++ public int getMaxY() { ++ return this.maxY; ++ } ++ ++ @Override ++ public int getSectionsCount() { ++ return this.sectionsCount; ++ } ++ ++ @Override ++ public int getMinSectionY() { ++ return this.minSectionY; ++ } ++ ++ @Override ++ public int getMaxSectionY() { ++ return this.maxSectionY; ++ } ++ ++ @Override ++ public boolean isInsideBuildHeight(final int blockY) { ++ return blockY >= this.minY && blockY <= this.maxY; ++ } ++ ++ @Override ++ public boolean isOutsideBuildHeight(final BlockPos pos) { ++ return this.isOutsideBuildHeight(pos.getY()); ++ } ++ ++ @Override ++ public boolean isOutsideBuildHeight(final int blockY) { ++ return blockY < this.minY || blockY > this.maxY; ++ } ++ ++ @Override ++ public int getSectionIndex(final int blockY) { ++ return (blockY >> 4) - this.minSectionY; ++ } ++ ++ @Override ++ public int getSectionIndexFromSectionY(final int sectionY) { ++ return sectionY - this.minSectionY; ++ } ++ ++ @Override ++ public int getSectionYFromSectionIndex(final int sectionIdx) { ++ return sectionIdx + this.minSectionY; ++ } ++ // Paper end - getblock optimisations - cache world height/sections + // Paper start - optimise random ticking + @Override + public abstract Holder getUncachedNoiseBiome(final int x, final int y, final int z); @@ -28177,9 +29738,19 @@ index d048d0e4b16459b5bad44ebfa3c6a8f336f6762b..332dc7e6bdfb5b3741764d4877185a2e + // Paper end - optimise random ticking + protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, RegistryAccess iregistrycustom, Holder holder, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function paperWorldConfigCreator) { // Paper - create paper world config ++ // Paper start - getblock optimisations - cache world height/sections ++ final DimensionType dimType = holder.value(); ++ this.minY = dimType.minY(); ++ this.height = dimType.height(); ++ this.maxY = this.minY + this.height - 1; ++ this.minSectionY = this.minY >> 4; ++ this.maxSectionY = this.maxY >> 4; ++ this.sectionsCount = this.maxSectionY - this.minSectionY + 1; ++ // Paper end - getblock optimisations - cache world height/sections this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config -@@ -271,6 +749,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + this.generator = gen; +@@ -271,6 +849,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable { this.timings = new co.aikar.timings.WorldTimingsHandler(this); // Paper - code below can generate new world and access timings this.entityLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.entityMaxTickTime); this.tileLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.tileMaxTickTime); @@ -28191,7 +29762,7 @@ index d048d0e4b16459b5bad44ebfa3c6a8f336f6762b..332dc7e6bdfb5b3741764d4877185a2e } // Paper start - Cancel hit for vanished players -@@ -535,7 +1018,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -535,7 +1118,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { this.setBlocksDirty(blockposition, iblockdata1, iblockdata2); } @@ -28200,7 +29771,7 @@ index d048d0e4b16459b5bad44ebfa3c6a8f336f6762b..332dc7e6bdfb5b3741764d4877185a2e this.sendBlockUpdated(blockposition, iblockdata1, iblockdata, i); } -@@ -800,6 +1283,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -800,6 +1383,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable { // Iterator iterator = this.blockEntityTickers.iterator(); boolean flag = this.tickRateManager().runsNormally(); @@ -28209,7 +29780,7 @@ index d048d0e4b16459b5bad44ebfa3c6a8f336f6762b..332dc7e6bdfb5b3741764d4877185a2e int tilesThisCycle = 0; var toRemove = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet(); // Paper - Fix MC-117075; use removeAll toRemove.add(null); // Paper - Fix MC-117075 -@@ -815,6 +1300,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -815,6 +1400,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable { // Spigot end } else if (flag && this.shouldTickBlocksAt(tickingblockentity.getPos())) { tickingblockentity.tick(); @@ -28221,7 +29792,7 @@ index d048d0e4b16459b5bad44ebfa3c6a8f336f6762b..332dc7e6bdfb5b3741764d4877185a2e } } this.blockEntityTickers.removeAll(toRemove); // Paper - Fix MC-117075 -@@ -837,12 +1327,20 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -837,12 +1427,20 @@ public abstract class Level implements LevelAccessor, AutoCloseable { entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); // Paper end - Prevent block entity and entity crashes } @@ -28243,7 +29814,7 @@ index d048d0e4b16459b5bad44ebfa3c6a8f336f6762b..332dc7e6bdfb5b3741764d4877185a2e } // Paper end - Option to prevent armor stands from doing entity lookups -@@ -894,7 +1392,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -894,7 +1492,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { } // Paper end - Perf: Optimize capturedTileEntities lookup // CraftBukkit end @@ -28252,7 +29823,7 @@ index d048d0e4b16459b5bad44ebfa3c6a8f336f6762b..332dc7e6bdfb5b3741764d4877185a2e } public void setBlockEntity(BlockEntity blockEntity) { -@@ -986,26 +1484,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -986,26 +1584,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable { Profiler.get().incrementCounter("getEntities"); List list = Lists.newArrayList(); @@ -28260,17 +29831,17 @@ index d048d0e4b16459b5bad44ebfa3c6a8f336f6762b..332dc7e6bdfb5b3741764d4877185a2e - if (entity1 != except && predicate.test(entity1)) { - list.add(entity1); - } -+ // Paper start - rewrite chunk system -+ final List ret = new java.util.ArrayList<>(); - +- - if (entity1 instanceof EnderDragon) { - EnderDragonPart[] aentitycomplexpart = ((EnderDragon) entity1).getSubEntities(); - int i = aentitycomplexpart.length; -+ ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)this).moonrise$getEntityLookup().getEntities(except, box, ret, predicate); ++ // Paper start - rewrite chunk system ++ final List ret = new java.util.ArrayList<>(); - for (int j = 0; j < i; ++j) { - EnderDragonPart entitycomplexpart = aentitycomplexpart[j]; -- ++ ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)this).moonrise$getEntityLookup().getEntities(except, box, ret, predicate); + - if (entity1 != except && predicate.test(entitycomplexpart)) { - list.add(entitycomplexpart); - } @@ -28284,7 +29855,7 @@ index d048d0e4b16459b5bad44ebfa3c6a8f336f6762b..332dc7e6bdfb5b3741764d4877185a2e } @Override -@@ -1020,36 +1505,77 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -1020,36 +1605,86 @@ public abstract class Level implements LevelAccessor, AutoCloseable { this.getEntities(filter, box, predicate, result, Integer.MAX_VALUE); } @@ -28359,7 +29930,7 @@ index d048d0e4b16459b5bad44ebfa3c6a8f336f6762b..332dc7e6bdfb5b3741764d4877185a2e + } else { + ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)this).moonrise$getEntityLookup().getEntities((Entity)null, boundingBox, (List)into, (Predicate)modifiedPredicate); + return; - } ++ } + } else { + if (maxCount != Integer.MAX_VALUE) { + ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)this).moonrise$getEntityLookup().getEntities(base, null, boundingBox, (List)into, (Predicate)modifiedPredicate, maxCount); @@ -28370,15 +29941,24 @@ index d048d0e4b16459b5bad44ebfa3c6a8f336f6762b..332dc7e6bdfb5b3741764d4877185a2e + } + } + } - -- return AbortableIterationConsumer.Continuation.CONTINUE; -- }); ++ + public org.bukkit.entity.Entity[] getChunkEntities(int chunkX, int chunkZ) { + ca.spottedleaf.moonrise.patches.chunk_system.level.entity.ChunkEntitySlices slices = ((ServerLevel)this).moonrise$getEntityLookup().getChunk(chunkX, chunkZ); + if (slices == null) { + return new org.bukkit.entity.Entity[0]; + } -+ return slices.getChunkEntities(); ++ ++ List ret = new java.util.ArrayList<>(); ++ for (Entity entity : slices.getAllEntities()) { ++ org.bukkit.entity.Entity bukkit = entity.getBukkitEntity(); ++ if (bukkit != null && bukkit.isValid()) { ++ ret.add(bukkit); + } ++ } + +- return AbortableIterationConsumer.Continuation.CONTINUE; +- }); ++ return ret.toArray(new org.bukkit.entity.Entity[0]); } + // Paper end - rewrite chunk system @@ -28434,6 +30014,20 @@ index 8590de51b572c0f73d45aee60313d466e4671da5..b725eea9d3ca81d2ef7802f5d0346d92 } public boolean shouldFreeze(LevelReader world, BlockPos blockPos) { +diff --git a/src/main/java/net/minecraft/world/level/biome/BiomeManager.java b/src/main/java/net/minecraft/world/level/biome/BiomeManager.java +index 01352cc83b25eb0e30b7e0ff521fc7c1b3d5155b..90f8360f547ce709fd13ee34f8e67d8bfa94b498 100644 +--- a/src/main/java/net/minecraft/world/level/biome/BiomeManager.java ++++ b/src/main/java/net/minecraft/world/level/biome/BiomeManager.java +@@ -98,8 +98,7 @@ public class BiomeManager { + } + + private static double getFiddle(long l) { +- double d = (double)Math.floorMod(l >> 24, 1024) / 1024.0; +- return (d - 0.5) * 0.9; ++ return (double)(((l >> 24) & (1024 - 1)) - (1024/2)) * (0.9 / 1024.0); // Paper - avoid floorMod, fp division, and fp subtraction + } + + public interface NoiseBiomeSource { diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java index 4d140bd83ca0e1554afad80ec4fc6186188a79d8..3dd236d39535cfce866eb73673f8d7f1b6dc535c 100644 --- a/src/main/java/net/minecraft/world/level/block/Block.java @@ -28448,7 +30042,7 @@ index 4d140bd83ca0e1554afad80ec4fc6186188a79d8..3dd236d39535cfce866eb73673f8d7f1 public void animateTick(BlockState state, Level world, BlockPos pos, RandomSource random) {} diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -index 0665ca48fe2f8ab1ce1c0306b11be19b06445f74..a4b4fd83d201fff005c738c84fa5c1bc55d670bd 100644 +index 0665ca48fe2f8ab1ce1c0306b11be19b06445f74..8631655a181735df53f8a02c9eb98f0cc13f55bb 100644 --- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java @@ -793,7 +793,7 @@ public abstract class BlockBehaviour implements FeatureElement { @@ -28465,18 +30059,12 @@ index 0665ca48fe2f8ab1ce1c0306b11be19b06445f74..a4b4fd83d201fff005c738c84fa5c1bc private int lightBlock; + // Paper start - rewrite chunk system -+ private int opacityIfCached; + private boolean isConditionallyFullOpaque; + + @Override + public final boolean starlight$isConditionallyFullOpaque() { + return this.isConditionallyFullOpaque; + } -+ -+ @Override -+ public final int starlight$getOpacityIfCached() { -+ return this.opacityIfCached; -+ } + // Paper end - rewrite chunk system + // Paper start - optimise collisions + private static final int RANDOM_OFFSET = 704237939; @@ -28486,16 +30074,22 @@ index 0665ca48fe2f8ab1ce1c0306b11be19b06445f74..a4b4fd83d201fff005c738c84fa5c1bc + private final int id2 = it.unimi.dsi.fastutil.HashCommon.murmurHash3(it.unimi.dsi.fastutil.HashCommon.murmurHash3(ID_GENERATOR.getAndIncrement() + RANDOM_OFFSET) + RANDOM_OFFSET); + private boolean occludesFullBlock; + private boolean emptyCollisionShape; ++ private boolean emptyConstantCollisionShape; + private VoxelShape constantCollisionShape; -+ private AABB constantAABBCollision; + -+ private static void initCaches(final VoxelShape shape) { ++ private static void initCaches(final VoxelShape shape, final boolean neighbours) { + ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)shape).moonrise$isFullBlock(); + ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)shape).moonrise$occludesFullBlock(); + shape.toAabbs(); + if (!shape.isEmpty()) { + shape.bounds(); + } ++ if (neighbours) { ++ for (final Direction direction : DIRECTIONS_CACHED) { ++ initCaches(((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)shape).moonrise$getFaceShapeClamped(direction), false); ++ initCaches(shape.getFaceShape(direction), false); ++ } ++ } + } + + @Override @@ -28514,6 +30108,11 @@ index 0665ca48fe2f8ab1ce1c0306b11be19b06445f74..a4b4fd83d201fff005c738c84fa5c1bc + } + + @Override ++ public final boolean moonrise$emptyContextCollisionShape() { ++ return this.emptyConstantCollisionShape; ++ } ++ ++ @Override + public final int moonrise$uniqueId1() { + return this.id1; + } @@ -28524,63 +30123,399 @@ index 0665ca48fe2f8ab1ce1c0306b11be19b06445f74..a4b4fd83d201fff005c738c84fa5c1bc + } + + @Override -+ public final VoxelShape moonrise$getConstantCollisionShape() { ++ public final VoxelShape moonrise$getConstantContextCollisionShape() { + return this.constantCollisionShape; + } -+ -+ @Override -+ public final AABB moonrise$getConstantCollisionAABB() { -+ return this.constantAABBCollision; -+ } + // Paper end - optimise collisions + protected BlockStateBase(Block block, Reference2ObjectArrayMap, Comparable> propertyMap, MapCodec codec) { super(block, propertyMap, codec); this.fluidState = Fluids.EMPTY.defaultFluidState(); -@@ -921,6 +991,43 @@ public abstract class BlockBehaviour implements FeatureElement { +@@ -921,6 +991,41 @@ public abstract class BlockBehaviour implements FeatureElement { this.propagatesSkylightDown = ((Block) this.owner).propagatesSkylightDown(this.asState()); this.lightBlock = ((Block) this.owner).getLightBlock(this.asState()); + // Paper start - rewrite chunk system + this.isConditionallyFullOpaque = this.canOcclude & this.useShapeForLightOcclusion; -+ this.opacityIfCached = this.cache == null || this.isConditionallyFullOpaque ? -1 : this.cache.lightBlock; + // Paper end - rewrite chunk system + // Paper start - optimise collisions + if (this.cache != null) { + final VoxelShape collisionShape = this.cache.collisionShape; + try { + this.constantCollisionShape = this.getCollisionShape(null, null, null); -+ this.constantAABBCollision = this.constantCollisionShape == null ? null : ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)this.constantCollisionShape).moonrise$getSingleAABBRepresentation(); + } catch (final Throwable throwable) { + this.constantCollisionShape = null; -+ this.constantAABBCollision = null; + } + this.occludesFullBlock = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)collisionShape).moonrise$occludesFullBlock(); + this.emptyCollisionShape = collisionShape.isEmpty(); ++ this.emptyConstantCollisionShape = this.constantCollisionShape != null && this.constantCollisionShape.isEmpty(); + // init caches -+ initCaches(collisionShape); -+ if (collisionShape != Shapes.empty() && collisionShape != Shapes.block()) { -+ for (final Direction direction : DIRECTIONS_CACHED) { -+ // initialise the directional face shape cache as well -+ final VoxelShape shape = Shapes.getFaceShape(collisionShape, direction); -+ initCaches(shape); -+ } -+ } -+ if (this.cache.occlusionShapes != null) { -+ for (final VoxelShape shape : this.cache.occlusionShapes) { -+ initCaches(shape); -+ } ++ initCaches(collisionShape, true); ++ if (this.constantCollisionShape != null) { ++ initCaches(this.constantCollisionShape, true); + } + } else { + this.occludesFullBlock = false; + this.emptyCollisionShape = false; ++ this.emptyConstantCollisionShape = false; + this.constantCollisionShape = null; -+ this.constantAABBCollision = null; ++ } ++ ++ if (this.occlusionShape != null) { ++ initCaches(this.occlusionShape, true); ++ } ++ if (this.occlusionShapesByFace != null) { ++ for (final VoxelShape shape : this.occlusionShapesByFace) { ++ initCaches(shape, true); ++ } + } + // Paper end - optimise collisions } public Block getBlock() { +diff --git a/src/main/java/net/minecraft/world/level/block/state/StateHolder.java b/src/main/java/net/minecraft/world/level/block/state/StateHolder.java +index 422b364764e0df16ca250b4939d7b226e69c0840..2df28ffc731bd77e0d7af3541cfd3741aa5af83b 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/StateHolder.java ++++ b/src/main/java/net/minecraft/world/level/block/state/StateHolder.java +@@ -15,7 +15,7 @@ import java.util.stream.Collectors; + import javax.annotation.Nullable; + import net.minecraft.world.level.block.state.properties.Property; + +-public abstract class StateHolder { ++public abstract class StateHolder implements ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.PropertyAccessStateHolder { // Paper - optimise blockstate property access + public static final String NAME_TAG = "Name"; + public static final String PROPERTIES_TAG = "Properties"; + public static final Function, Comparable>, String> PROPERTY_ENTRY_TO_STRING_FUNCTION = new Function, Comparable>, String>() { +@@ -34,14 +34,28 @@ public abstract class StateHolder { + } + }; + protected final O owner; +- private final Reference2ObjectArrayMap, Comparable> values; ++ private Reference2ObjectArrayMap, Comparable> values; // Paper - optimise blockstate property access - remove final + private Map, S[]> neighbours; + protected final MapCodec propertiesCodec; + ++ // Paper start - optimise blockstate property access ++ protected ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.util.ZeroCollidingReferenceStateTable optimisedTable; ++ protected final long tableIndex; ++ ++ @Override ++ public final long moonrise$getTableIndex() { ++ return this.tableIndex; ++ } ++ // Paper end - optimise blockstate property access ++ + protected StateHolder(O owner, Reference2ObjectArrayMap, Comparable> propertyMap, MapCodec codec) { + this.owner = owner; + this.values = propertyMap; + this.propertiesCodec = codec; ++ // Paper start - optimise blockstate property access ++ this.optimisedTable = new ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.util.ZeroCollidingReferenceStateTable<>(this.values.keySet()); ++ this.tableIndex = this.optimisedTable.getIndex((StateHolder)(Object)this); ++ // Paper end - optimise blockstate property access + } + + public > S cycle(Property property) { +@@ -67,20 +81,21 @@ public abstract class StateHolder { + } + + public Collection> getProperties() { +- return Collections.unmodifiableCollection(this.values.keySet()); ++ return this.optimisedTable.getProperties(); // Paper - optimise blockstate property access + } + + public > boolean hasProperty(Property property) { +- return this.values.containsKey(property); ++ return property != null && this.optimisedTable.hasProperty(property); // Paper - optimise blockstate property access + } + + public > T getValue(Property property) { +- Comparable comparable = this.values.get(property); +- if (comparable == null) { +- throw new IllegalArgumentException("Cannot get property " + property + " as it does not exist in " + this.owner); +- } else { +- return property.getValueClass().cast(comparable); ++ // Paper start - optimise blockstate property access ++ final T ret = this.optimisedTable.get(this.tableIndex, property); ++ if (ret != null) { ++ return ret; + } ++ throw new IllegalArgumentException("Cannot get property " + property + " as it does not exist in " + this.owner); ++ // Paper end - optimise blockstate property access + } + + public > Optional getOptionalValue(Property property) { +@@ -93,22 +108,30 @@ public abstract class StateHolder { + + @Nullable + public > T getNullableValue(Property property) { +- Comparable comparable = this.values.get(property); +- return comparable == null ? null : property.getValueClass().cast(comparable); ++ return property == null ? null : this.optimisedTable.get(this.tableIndex, property); // Paper - optimise blockstate property access + } + + public , V extends T> S setValue(Property property, V value) { +- Comparable comparable = this.values.get(property); +- if (comparable == null) { +- throw new IllegalArgumentException("Cannot set property " + property + " as it does not exist in " + this.owner); +- } else { +- return this.setValueInternal(property, value, comparable); ++ // Paper start - optimise blockstate property access ++ final S ret = this.optimisedTable.set(this.tableIndex, property, value); ++ if (ret != null) { ++ return ret; + } ++ throw new IllegalArgumentException("Cannot set property " + property + " to " + value + " on " + this.owner); ++ // Paper end - optimise blockstate property access + } + + public , V extends T> S trySetValue(Property property, V value) { +- Comparable comparable = this.values.get(property); +- return (S)(comparable == null ? this : this.setValueInternal(property, value, comparable)); ++ // Paper start - optimise blockstate property access ++ if (property == null) { ++ return (S)(StateHolder)(Object)this; ++ } ++ final S ret = this.optimisedTable.trySet(this.tableIndex, property, value, (S)(StateHolder)(Object)this); ++ if (ret != null) { ++ return ret; ++ } ++ throw new IllegalArgumentException("Cannot set property " + property + " to " + value + " on " + this.owner); ++ // Paper end - optimise blockstate property access + } + + private , V extends T> S setValueInternal(Property property, V newValue, Comparable oldValue) { +@@ -125,18 +148,27 @@ public abstract class StateHolder { + } + + public void populateNeighbours(Map, Comparable>, S> states) { +- if (this.neighbours != null) { +- throw new IllegalStateException(); +- } else { +- Map, S[]> map = new Reference2ObjectArrayMap<>(this.values.size()); ++ // Paper start - optimise blockstate property access ++ final Map, Comparable>, S> map = states; ++ if (this.optimisedTable.isLoaded()) { ++ return; ++ } ++ this.optimisedTable.loadInTable(map); + +- for (Entry, Comparable> entry : this.values.entrySet()) { +- Property property = entry.getKey(); +- map.put(property, property.getPossibleValues().stream().map(value -> states.get(this.makeNeighbourValues(property, value))).toArray()); +- } ++ // de-duplicate the tables ++ for (final Map.Entry, Comparable>, S> entry : map.entrySet()) { ++ final S value = entry.getValue(); ++ ((StateHolder)value).optimisedTable = this.optimisedTable; ++ } + +- this.neighbours = map; ++ // remove values arrays ++ for (final Map.Entry, Comparable>, S> entry : map.entrySet()) { ++ final S value = entry.getValue(); ++ ((StateHolder)value).values = null; + } ++ ++ return; ++ // Paper end optimise blockstate property access + } + + private Map, Comparable> makeNeighbourValues(Property property, Comparable value) { +@@ -146,7 +178,11 @@ public abstract class StateHolder { + } + + public Map, Comparable> getValues() { +- return this.values; ++ // Paper start - optimise blockstate property access ++ ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.util.ZeroCollidingReferenceStateTable table = this.optimisedTable; ++ // We have to use this.values until the table is loaded ++ return table.isLoaded() ? table.getMapView(this.tableIndex) : this.values; ++ // Paper end - optimise blockstate property access + } + + protected static > Codec codec(Codec codec, Function ownerToStateFunction) { +diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java +index ea76aa490358e9e1d13350ba0ea246ec2c423894..98058505d36baf74008da08339afc196713b14a7 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java ++++ b/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java +@@ -3,13 +3,23 @@ package net.minecraft.world.level.block.state.properties; + import java.util.List; + import java.util.Optional; + +-public final class BooleanProperty extends Property { ++public final class BooleanProperty extends Property implements ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.PropertyAccess { // Paper - optimise blockstate property access + private static final List VALUES = List.of(true, false); + private static final int TRUE_INDEX = 0; + private static final int FALSE_INDEX = 1; + ++ // Paper start - optimise blockstate property access ++ private static final Boolean[] BY_ID = new Boolean[]{ Boolean.FALSE, Boolean.TRUE }; ++ ++ @Override ++ public final int moonrise$getIdFor(final Boolean value) { ++ return value.booleanValue() ? 1 : 0; ++ } ++ // Paper end - optimise blockstate property access ++ + private BooleanProperty(String name) { + super(name, Boolean.class); ++ this.moonrise$setById(BY_ID); // Paper - optimise blockstate property access + } + + @Override +diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java +index 85a197232be9377c0313ec00e8f935551e2c60e0..30b2fce9e47ffcc3de1542b1d0f073f5640127a7 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java ++++ b/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java +@@ -10,11 +10,39 @@ import java.util.function.Predicate; + import java.util.stream.Collectors; + import net.minecraft.util.StringRepresentable; + +-public final class EnumProperty & StringRepresentable> extends Property { ++public final class EnumProperty & StringRepresentable> extends Property implements ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.PropertyAccess { // Paper - optimise blockstate property access + private final List values; + private final Map names; + private final int[] ordinalToIndex; + ++ // Paper start - optimise blockstate property access ++ private int[] idLookupTable; ++ ++ @Override ++ public final int moonrise$getIdFor(final T value) { ++ final Class target = this.getValueClass(); ++ return ((value.getClass() != target && value.getDeclaringClass() != target)) ? -1 : this.idLookupTable[value.ordinal()]; ++ } ++ ++ private void init() { ++ final java.util.Collection values = this.getPossibleValues(); ++ final Class clazz = this.getValueClass(); ++ ++ int id = 0; ++ this.idLookupTable = new int[clazz.getEnumConstants().length]; ++ Arrays.fill(this.idLookupTable, -1); ++ final T[] byId = (T[])java.lang.reflect.Array.newInstance(clazz, values.size()); ++ ++ for (final T value : values) { ++ final int valueId = id++; ++ this.idLookupTable[value.ordinal()] = valueId; ++ byId[valueId] = value; ++ } ++ ++ this.moonrise$setById(byId); ++ } ++ // Paper end - optimise blockstate property access ++ + private EnumProperty(String name, Class type, List values) { + super(name, type); + if (values.isEmpty()) { +@@ -37,6 +65,7 @@ public final class EnumProperty & StringRepresentable> extends + + this.names = builder.buildOrThrow(); + } ++ this.init(); // Paper - optimise blockstate property access + } + + @Override +diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java +index 55a87592a99105dbf57b26fb6ccba695295fce24..986365acc9983331a7982ea2e1eac2b0efe1506d 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java ++++ b/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java +@@ -5,11 +5,33 @@ import java.util.List; + import java.util.Optional; + import java.util.stream.IntStream; + +-public final class IntegerProperty extends Property { ++public final class IntegerProperty extends Property implements ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.PropertyAccess { // Paper - optimise blockstate property access + private final IntImmutableList values; + public final int min; + public final int max; + ++ // Paper start - optimise blockstate property access ++ @Override ++ public final int moonrise$getIdFor(final Integer value) { ++ final int val = value.intValue(); ++ final int ret = val - this.min; ++ ++ return ret | ((this.max - ret) >> 31); ++ } ++ ++ private void init() { ++ final int min = this.min; ++ final int max = this.max; ++ ++ final Integer[] byId = new Integer[max - min + 1]; ++ for (int i = min; i <= max; ++i) { ++ byId[i - min] = Integer.valueOf(i); ++ } ++ ++ this.moonrise$setById(byId); ++ } ++ // Paper end - optimise blockstate property access ++ + private IntegerProperty(String name, int min, int max) { + super(name, Integer.class); + if (min < 0) { +@@ -21,6 +43,7 @@ public final class IntegerProperty extends Property { + this.max = max; + this.values = IntImmutableList.toList(IntStream.range(min, max + 1)); + } ++ this.init(); // Paper - optimise blockstate property access + } + + @Override +diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/Property.java b/src/main/java/net/minecraft/world/level/block/state/properties/Property.java +index fcf04c5c58ff35d38c5bf0df562ae2f8dc98a0ee..0b116160924300a9d62ad5948bfaf276f0386e4d 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/properties/Property.java ++++ b/src/main/java/net/minecraft/world/level/block/state/properties/Property.java +@@ -10,7 +10,7 @@ import java.util.stream.Stream; + import javax.annotation.Nullable; + import net.minecraft.world.level.block.state.StateHolder; + +-public abstract class Property> { ++public abstract class Property> implements ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.PropertyAccess { // Paper - optimise blockstate property access + private final Class clazz; + private final String name; + @Nullable +@@ -24,9 +24,38 @@ public abstract class Property> { + ); + private final Codec> valueCodec = this.codec.xmap(this::value, Property.Value::value); + ++ // Paper start - optimise blockstate property access ++ private static final java.util.concurrent.atomic.AtomicInteger ID_GENERATOR = new java.util.concurrent.atomic.AtomicInteger(); ++ private final int id; ++ private T[] byId; ++ ++ @Override ++ public final int moonrise$getId() { ++ return this.id; ++ } ++ ++ @Override ++ public final T moonrise$getById(final int id) { ++ final T[] byId = this.byId; ++ return id < 0 || id >= byId.length ? null : this.byId[id]; ++ } ++ ++ @Override ++ public final void moonrise$setById(final T[] byId) { ++ if (this.byId != null) { ++ throw new IllegalStateException(); ++ } ++ this.byId = byId; ++ } ++ ++ @Override ++ public abstract int moonrise$getIdFor(final T value); ++ // Paper end - optimise blockstate property access ++ + protected Property(String name, Class type) { + this.clazz = type; + this.name = name; ++ this.id = ID_GENERATOR.getAndIncrement(); // Paper - optimise blockstate property access + } + + public Property.Value value(T value) { diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java index 37795b9e264c571efe9c718fa9996197dca4ed54..0601f454758cb1447cca2cbff4ef5fd7633fece5 100644 --- a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java @@ -28816,6 +30751,31 @@ index dcc0acd259920463a4464213b9a5e793603852f9..ef4161884574d3d137e12591d983dc95 @Override public BlockState getBlockState(BlockPos pos) { return Blocks.VOID_AIR.defaultBlockState(); +diff --git a/src/main/java/net/minecraft/world/level/chunk/HashMapPalette.java b/src/main/java/net/minecraft/world/level/chunk/HashMapPalette.java +index 98dbeaf8bde15940e5b5d5d1f13fd4bb32f0a10d..7beea075b5a7ef738a4ac0558b99f4c5708f2c4a 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/HashMapPalette.java ++++ b/src/main/java/net/minecraft/world/level/chunk/HashMapPalette.java +@@ -8,12 +8,19 @@ import net.minecraft.network.FriendlyByteBuf; + import net.minecraft.network.VarInt; + import net.minecraft.util.CrudeIncrementalIntIdentityHashBiMap; + +-public class HashMapPalette implements Palette { ++public class HashMapPalette implements Palette, ca.spottedleaf.moonrise.patches.fast_palette.FastPalette { // Paper - optimise palette reads + private final IdMap registry; + private final CrudeIncrementalIntIdentityHashBiMap values; + private final PaletteResize resizeHandler; + private final int bits; + ++ // Paper start - optimise palette reads ++ @Override ++ public final T[] moonrise$getRawPalette(final ca.spottedleaf.moonrise.patches.fast_palette.FastPaletteData container) { ++ return ((ca.spottedleaf.moonrise.patches.fast_palette.FastPalette)this.values).moonrise$getRawPalette(container); ++ } ++ // Paper end - optimise palette reads ++ + public HashMapPalette(IdMap idList, int bits, PaletteResize listener, List entries) { + this(idList, bits, listener); + entries.forEach(this.values::add); diff --git a/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java b/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java index 7cce66d4c6efe6fd3cc22a6acf72878c964c61ae..30ee3df2278d0d9bd7478b49eda5fff27b8a504c 100644 --- a/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java @@ -28879,7 +30839,7 @@ index 7cce66d4c6efe6fd3cc22a6acf72878c964c61ae..30ee3df2278d0d9bd7478b49eda5fff2 @Override public BlockEntity getBlockEntity(BlockPos pos) { diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -index 7181acfafad91aa5f6ab7ce663d9be4a1b65b02a..a61294befc2f855fcecb2336a2d5444ce60e0a3a 100644 +index 7181acfafad91aa5f6ab7ce663d9be4a1b65b02a..a0e51681731dc7b487d5b14ae0d44a881bd5cb09 100644 --- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java @@ -54,7 +54,7 @@ import net.minecraft.world.ticks.LevelChunkTicks; @@ -28887,7 +30847,7 @@ index 7181acfafad91aa5f6ab7ce663d9be4a1b65b02a..a61294befc2f855fcecb2336a2d5444c import org.slf4j.Logger; -public class LevelChunk extends ChunkAccess { -+public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk, ca.spottedleaf.moonrise.patches.starlight.chunk.StarlightChunk, ca.spottedleaf.moonrise.patches.chunk_getblock.GetBlockChunk { // Paper - rewrite chunk system // Paper - get block chunk optimisation ++public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk, ca.spottedleaf.moonrise.patches.starlight.chunk.StarlightChunk, ca.spottedleaf.moonrise.patches.getblock.GetBlockChunk { // Paper - rewrite chunk system // Paper - get block chunk optimisation static final Logger LOGGER = LogUtils.getLogger(); private static final TickingBlockEntity NULL_TICKER = new TickingBlockEntity() { @@ -28994,7 +30954,7 @@ index 7181acfafad91aa5f6ab7ce663d9be4a1b65b02a..a61294befc2f855fcecb2336a2d5444c */ org.bukkit.Chunk bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this); server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkLoadEvent(bukkitChunk, this.needsDecoration)); -+ ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(this.locX, this.locZ).getEntityChunk().callEntitiesLoadEvent(); // Paper - rewrite chunk system ++ org.bukkit.craftbukkit.event.CraftEventFactory.callEntitiesLoadEvent(this.level, this.chunkPos, ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(this.locX, this.locZ).getEntityChunk().getAllEntities()); // Paper - rewrite chunk system if (this.needsDecoration) { try (co.aikar.timings.Timing ignored = this.level.timings.chunkLoadPopulate.startTiming()) { // Paper @@ -29004,7 +30964,7 @@ index 7181acfafad91aa5f6ab7ce663d9be4a1b65b02a..a61294befc2f855fcecb2336a2d5444c public void unloadCallback() { + if (!this.loadedTicketLevel) { LOGGER.error("Double calling chunk unload!", new Throwable()); } // Paper org.bukkit.Server server = this.level.getCraftServer(); -+ ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(this.locX, this.locZ).getEntityChunk().callEntitiesUnloadEvent(); // Paper - rewrite chunk system ++ org.bukkit.craftbukkit.event.CraftEventFactory.callEntitiesUnloadEvent(this.level, this.chunkPos, ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(this.locX, this.locZ).getEntityChunk().getAllEntities()); // Paper - rewrite chunk system org.bukkit.Chunk bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this); - org.bukkit.event.world.ChunkUnloadEvent unloadEvent = new org.bukkit.event.world.ChunkUnloadEvent(bukkitChunk, this.isUnsaved()); + org.bukkit.event.world.ChunkUnloadEvent unloadEvent = new org.bukkit.event.world.ChunkUnloadEvent(bukkitChunk, true); // Paper - rewrite chunk system - force save to true so that mustNotSave is correctly set below @@ -29054,7 +31014,7 @@ index 7181acfafad91aa5f6ab7ce663d9be4a1b65b02a..a61294befc2f855fcecb2336a2d5444c @Nullable diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java -index 52f44f14bbda60fe771c351e01e6ff470d7371e6..161211124f3f8390530af7ab21f3a0f1025209c5 100644 +index 52f44f14bbda60fe771c351e01e6ff470d7371e6..4167ed830382c6a76bb281e9d753919925c6bd00 100644 --- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java @@ -13,7 +13,7 @@ import net.minecraft.world.level.block.Blocks; @@ -29066,20 +31026,22 @@ index 52f44f14bbda60fe771c351e01e6ff470d7371e6..161211124f3f8390530af7ab21f3a0f1 public static final int SECTION_WIDTH = 16; public static final int SECTION_HEIGHT = 16; -@@ -25,6 +25,28 @@ public class LevelChunkSection { +@@ -25,6 +25,30 @@ public class LevelChunkSection { public final PalettedContainer states; private PalettedContainer> biomes; // CraftBukkit - read/write + // Paper start - block counting -+ private static final it.unimi.dsi.fastutil.ints.IntArrayList FULL_LIST = new it.unimi.dsi.fastutil.ints.IntArrayList(16*16*16); ++ private static final it.unimi.dsi.fastutil.shorts.ShortArrayList FULL_LIST = new it.unimi.dsi.fastutil.shorts.ShortArrayList(16*16*16); + static { -+ for (int i = 0; i < (16*16*16); ++i) { ++ for (short i = 0; i < (16*16*16); ++i) { + FULL_LIST.add(i); + } + } + -+ private int specialCollidingBlocks; -+ private final ca.spottedleaf.moonrise.common.list.IBlockDataList tickingBlocks = new ca.spottedleaf.moonrise.common.list.IBlockDataList(); ++ private boolean isClient; ++ private static final short CLIENT_FORCED_SPECIAL_COLLIDING_BLOCKS = (short)9999; ++ private short specialCollidingBlocks; ++ private final ca.spottedleaf.moonrise.common.list.ShortList tickingBlocks = new ca.spottedleaf.moonrise.common.list.ShortList(); + + @Override + public final int moonrise$getSpecialCollidingBlocks() { @@ -29087,7 +31049,7 @@ index 52f44f14bbda60fe771c351e01e6ff470d7371e6..161211124f3f8390530af7ab21f3a0f1 + } + + @Override -+ public final ca.spottedleaf.moonrise.common.list.IBlockDataList moonrise$getTickingBlockList() { ++ public final ca.spottedleaf.moonrise.common.list.ShortList moonrise$getTickingBlockList() { + return this.tickingBlocks; + } + // Paper end - block counting @@ -29095,30 +31057,76 @@ index 52f44f14bbda60fe771c351e01e6ff470d7371e6..161211124f3f8390530af7ab21f3a0f1 private LevelChunkSection(LevelChunkSection section) { this.nonEmptyBlockCount = section.nonEmptyBlockCount; this.tickingBlockCount = section.tickingBlockCount; -@@ -98,6 +120,22 @@ public class LevelChunkSection { +@@ -64,6 +88,45 @@ public class LevelChunkSection { + return this.setBlockState(x, y, z, state, true); + } + ++ // Paper start - block counting ++ private void updateBlockCallback(final int x, final int y, final int z, final BlockState newState, ++ final BlockState oldState) { ++ if (oldState == newState) { ++ return; ++ } ++ ++ if (this.isClient) { ++ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isSpecialCollidingBlock(newState)) { ++ this.specialCollidingBlocks = CLIENT_FORCED_SPECIAL_COLLIDING_BLOCKS; ++ } ++ return; ++ } ++ ++ final boolean isSpecialOld = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isSpecialCollidingBlock(oldState); ++ final boolean isSpecialNew = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isSpecialCollidingBlock(newState); ++ if (isSpecialOld != isSpecialNew) { ++ if (isSpecialOld) { ++ --this.specialCollidingBlocks; ++ } else { ++ ++this.specialCollidingBlocks; ++ } ++ } ++ ++ final boolean oldTicking = oldState.isRandomlyTicking(); ++ final boolean newTicking = newState.isRandomlyTicking(); ++ if (oldTicking != newTicking) { ++ final ca.spottedleaf.moonrise.common.list.ShortList tickingBlocks = this.tickingBlocks; ++ final short position = (short)(x | (z << 4) | (y << (4+4))); ++ ++ if (oldTicking) { ++ tickingBlocks.remove(position); ++ } else { ++ tickingBlocks.add(position); ++ } ++ } ++ } ++ // Paper end - block counting ++ + public BlockState setBlockState(int x, int y, int z, BlockState state, boolean lock) { + BlockState iblockdata1; + +@@ -83,7 +146,7 @@ public class LevelChunkSection { + } + } + +- if (!fluid.isEmpty()) { ++ if (!!fluid.isRandomlyTicking()) { // Paper - block counting + --this.tickingFluidCount; + } + +@@ -94,10 +157,12 @@ public class LevelChunkSection { + } + } + +- if (!fluid1.isEmpty()) { ++ if (!!fluid1.isRandomlyTicking()) { // Paper - block counting ++this.tickingFluidCount; } -+ // Paper start - block counting -+ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isSpecialCollidingBlock(iblockdata1)) { -+ --this.specialCollidingBlocks; -+ } -+ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isSpecialCollidingBlock(state)) { -+ ++this.specialCollidingBlocks; -+ } -+ -+ if (iblockdata1.isRandomlyTicking()) { -+ this.tickingBlocks.remove(x, y, z); -+ } -+ if (state.isRandomlyTicking()) { -+ this.tickingBlocks.add(x, y, z, state); -+ } -+ // Paper end - block counting ++ this.updateBlockCallback(x, y, z, state, iblockdata1); // Paper - block counting + return iblockdata1; } -@@ -118,40 +156,65 @@ public class LevelChunkSection { +@@ -118,40 +183,70 @@ public class LevelChunkSection { } public void recalcBlockCounts() { @@ -29137,47 +31145,52 @@ index 52f44f14bbda60fe771c351e01e6ff470d7371e6..161211124f3f8390530af7ab21f3a0f1 + final int paletteSize = palette.getSize(); + final net.minecraft.util.BitStorage storage = data.storage(); + -+ final it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap counts; ++ final it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap counts; + if (paletteSize == 1) { + counts = new it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap<>(1); + counts.put(0, FULL_LIST); + } else { + counts = ((ca.spottedleaf.moonrise.patches.block_counting.BlockCountingBitStorage)storage).moonrise$countEntries(); + } ++ ++ for (final java.util.Iterator> iterator = counts.int2ObjectEntrySet().fastIterator(); iterator.hasNext();) { ++ final it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry entry = iterator.next(); ++ final int paletteIdx = entry.getIntKey(); ++ final it.unimi.dsi.fastutil.shorts.ShortArrayList coordinates = entry.getValue(); ++ final int paletteCount = coordinates.size(); - public int nonEmptyBlockCount; - public int tickingBlockCount; - public int tickingFluidCount; -+ for (final java.util.Iterator> iterator = counts.int2ObjectEntrySet().fastIterator(); iterator.hasNext();) { -+ final it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry entry = iterator.next(); -+ final int paletteIdx = entry.getIntKey(); -+ final it.unimi.dsi.fastutil.ints.IntArrayList coordinates = entry.getValue(); -+ final int paletteCount = coordinates.size(); - -- a(final LevelChunkSection chunksection) {} + final BlockState state = palette.valueFor(paletteIdx); -- public void accept(BlockState iblockdata, int i) { -- FluidState fluid = iblockdata.getFluidState(); +- a(final LevelChunkSection chunksection) {} + if (state.isAir()) { + continue; + } ++ ++ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isSpecialCollidingBlock(state)) { ++ this.specialCollidingBlocks += (short)paletteCount; ++ } ++ this.nonEmptyBlockCount += (short)paletteCount; ++ if (state.isRandomlyTicking()) { ++ this.tickingBlockCount += (short)paletteCount; ++ final short[] raw = coordinates.elements(); ++ final int rawLen = raw.length; ++ ++ final ca.spottedleaf.moonrise.common.list.ShortList tickingBlocks = this.tickingBlocks; + +- public void accept(BlockState iblockdata, int i) { +- FluidState fluid = iblockdata.getFluidState(); ++ tickingBlocks.setMinCapacity(Math.min((rawLen + tickingBlocks.size()) * 3 / 2, 16*16*16)); - if (!iblockdata.isAir()) { - this.nonEmptyBlockCount += i; - if (iblockdata.isRandomlyTicking()) { - this.tickingBlockCount += i; -+ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isSpecialCollidingBlock(state)) { -+ this.specialCollidingBlocks += paletteCount; -+ } -+ this.nonEmptyBlockCount += paletteCount; -+ if (state.isRandomlyTicking()) { -+ this.tickingBlockCount += paletteCount; -+ final int[] raw = coordinates.elements(); -+ + java.util.Objects.checkFromToIndex(0, paletteCount, raw.length); + for (int i = 0; i < paletteCount; ++i) { -+ this.tickingBlocks.add(raw[i], state); ++ tickingBlocks.add(raw[i]); } } @@ -29185,10 +31198,10 @@ index 52f44f14bbda60fe771c351e01e6ff470d7371e6..161211124f3f8390530af7ab21f3a0f1 + if (!fluid.isEmpty()) { - this.nonEmptyBlockCount += i; -+ //this.nonEmptyBlockCount += count; // fix vanilla bug: make non empty block count correct ++ //this.nonEmptyBlockCount += count; // fix vanilla bug: make non-empty block count correct if (fluid.isRandomlyTicking()) { - this.tickingFluidCount += i; -+ this.tickingFluidCount += paletteCount; ++ this.tickingFluidCount += (short)paletteCount; } } - @@ -29205,16 +31218,59 @@ index 52f44f14bbda60fe771c351e01e6ff470d7371e6..161211124f3f8390530af7ab21f3a0f1 } public PalettedContainer getStates() { -@@ -169,6 +232,7 @@ public class LevelChunkSection { +@@ -169,6 +264,11 @@ public class LevelChunkSection { datapaletteblock.read(buf); this.biomes = datapaletteblock; -+ this.recalcBlockCounts(); // Paper - block counting ++ // Paper start - block counting ++ this.isClient = true; ++ // force has special colliding blocks to be true ++ this.specialCollidingBlocks = this.nonEmptyBlockCount != (short)0 && this.maybeHas(ca.spottedleaf.moonrise.patches.collisions.CollisionUtil::isSpecialCollidingBlock) ? CLIENT_FORCED_SPECIAL_COLLIDING_BLOCKS : (short)0; ++ // Paper end - block counting } public void readBiomes(FriendlyByteBuf buf) { +diff --git a/src/main/java/net/minecraft/world/level/chunk/LinearPalette.java b/src/main/java/net/minecraft/world/level/chunk/LinearPalette.java +index bc4d9452bbeb05a691fd285603e49491f41d3ad2..f8d9892970c9092f7cc84434d4fbf34354ce1195 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LinearPalette.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LinearPalette.java +@@ -7,13 +7,20 @@ import net.minecraft.network.FriendlyByteBuf; + import net.minecraft.network.VarInt; + import org.apache.commons.lang3.Validate; + +-public class LinearPalette implements Palette { ++public class LinearPalette implements Palette, ca.spottedleaf.moonrise.patches.fast_palette.FastPalette { // Paper - optimise palette reads + private final IdMap registry; + private final T[] values; + private final PaletteResize resizeHandler; + private final int bits; + private int size; + ++ // Paper start - optimise palette reads ++ @Override ++ public final T[] moonrise$getRawPalette(final ca.spottedleaf.moonrise.patches.fast_palette.FastPaletteData container) { ++ return this.values; ++ } ++ // Paper end - optimise palette reads ++ + private LinearPalette(IdMap idList, int bits, PaletteResize listener, List list) { + this.registry = idList; + this.values = (T[])(new Object[1 << bits]); +diff --git a/src/main/java/net/minecraft/world/level/chunk/Palette.java b/src/main/java/net/minecraft/world/level/chunk/Palette.java +index b8922e4a13df535cdc5701e893a6e460b33ff90d..100807f8b8337f56f49cdb818ccc75be2f08ecd1 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/Palette.java ++++ b/src/main/java/net/minecraft/world/level/chunk/Palette.java +@@ -5,7 +5,7 @@ import java.util.function.Predicate; + import net.minecraft.core.IdMap; + import net.minecraft.network.FriendlyByteBuf; + +-public interface Palette { ++public interface Palette extends ca.spottedleaf.moonrise.patches.fast_palette.FastPalette { // Paper - optimise palette reads + int idFor(T object); + + boolean maybeHas(Predicate predicate); diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -index 112d1259dd37743076ff6c67ffd711d084ba8698..b46c58c952e183bd74854c3eb70d64979af70f18 100644 +index 112d1259dd37743076ff6c67ffd711d084ba8698..533167eaa8bd39006fb1c7e193c81359973da9af 100644 --- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java @@ -28,7 +28,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer @@ -29226,15 +31282,156 @@ index 112d1259dd37743076ff6c67ffd711d084ba8698..b46c58c952e183bd74854c3eb70d6497 private final PalettedContainer.Strategy strategy; // private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); // Paper - unused -@@ -161,7 +161,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer +@@ -71,6 +71,33 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + ); + } + ++ // Paper start - optimise palette reads ++ private void updateData(final PalettedContainer.Data data) { ++ if (data != null) { ++ ((ca.spottedleaf.moonrise.patches.fast_palette.FastPaletteData)(Object)data).moonrise$setPalette( ++ ((ca.spottedleaf.moonrise.patches.fast_palette.FastPalette)data.palette).moonrise$getRawPalette((ca.spottedleaf.moonrise.patches.fast_palette.FastPaletteData)(Object)data) ++ ); ++ } ++ } ++ ++ private T readPaletteSlow(final PalettedContainer.Data data, final int paletteIdx) { ++ return data.palette.valueFor(paletteIdx); ++ } ++ ++ private T readPalette(final PalettedContainer.Data data, final int paletteIdx) { ++ final T[] palette = ((ca.spottedleaf.moonrise.patches.fast_palette.FastPaletteData)(Object)data).moonrise$getPalette(); ++ if (palette == null) { ++ return this.readPaletteSlow(data, paletteIdx); ++ } ++ ++ final T ret = palette[paletteIdx]; ++ if (ret == null) { ++ throw new IllegalArgumentException("Palette index out of bounds"); ++ } ++ return ret; ++ } ++ // Paper end - optimise palette reads ++ + public PalettedContainer( + IdMap idList, + PalettedContainer.Strategy paletteProvider, +@@ -81,12 +108,14 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + this.registry = idList; + this.strategy = paletteProvider; + this.data = new PalettedContainer.Data<>(dataProvider, storage, dataProvider.factory().create(dataProvider.bits(), idList, this, paletteEntries)); ++ this.updateData(this.data); // Paper - optimise palette reads + } + + private PalettedContainer(IdMap idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Data data) { + this.registry = idList; + this.strategy = paletteProvider; + this.data = data; ++ this.updateData(this.data); // Paper - optimise palette reads + } + + private PalettedContainer(PalettedContainer container) { +@@ -100,6 +129,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + this.registry = idList; + this.data = this.createOrReuseData(null, 0); + this.data.palette.idFor(object); ++ this.updateData(this.data); // Paper - optimise palette reads + } + + private PalettedContainer.Data createOrReuseData(@Nullable PalettedContainer.Data previousData, int bits) { +@@ -115,6 +145,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + PalettedContainer.Data data2 = this.createOrReuseData(data, newBits); + data2.copyFrom(data.palette, data.storage); + this.data = data2; ++ this.updateData(this.data); // Paper - optimise palette reads + return data2.palette.idFor(object); + } + +@@ -136,9 +167,12 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + } + + private synchronized T getAndSet(int index, T value) { // Paper - synchronize +- int i = this.data.palette.idFor(value); +- int j = this.data.storage.getAndSet(index, i); +- return this.data.palette.valueFor(j); ++ // Paper start - optimise palette reads ++ final int paletteIdx = this.data.palette.idFor(value); ++ final PalettedContainer.Data data = this.data; ++ final int prev = data.storage.getAndSet(index, paletteIdx); ++ return this.readPalette(data, prev); ++ // Paper end - optimise palette reads + } + + public void set(int x, int y, int z, T value) { +@@ -161,9 +195,11 @@ public class PalettedContainer implements PaletteResize, PalettedContainer return this.get(this.strategy.getIndex(x, y, z)); } - protected T get(int index) { +- PalettedContainer.Data data = this.data; +- return data.palette.valueFor(data.storage.get(index)); + public T get(int index) { // Paper - public - PalettedContainer.Data data = this.data; - return data.palette.valueFor(data.storage.get(index)); ++ // Paper start - optimise palette reads ++ final PalettedContainer.Data data = this.data; ++ return this.readPalette(data, data.storage.get(index)); ++ // Paper end - optimise palette reads } + + @Override +@@ -183,6 +219,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + data.palette.read(buf); + buf.readLongArray(data.storage.getRaw()); + this.data = data; ++ this.updateData(this.data); // Paper - optimise palette reads + } finally { + this.release(); + } +@@ -323,7 +360,44 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + void accept(T object, int count); + } + +- static record Data(PalettedContainer.Configuration configuration, BitStorage storage, Palette palette) { ++ // Paper start - optimise palette reads ++ public static final class Data implements ca.spottedleaf.moonrise.patches.fast_palette.FastPaletteData { ++ ++ private final PalettedContainer.Configuration configuration; ++ private final BitStorage storage; ++ private final Palette palette; ++ ++ private T[] moonrise$palette; ++ ++ public Data(final PalettedContainer.Configuration configuration, final BitStorage storage, final Palette palette) { ++ this.configuration = configuration; ++ this.storage = storage; ++ this.palette = palette; ++ } ++ ++ public PalettedContainer.Configuration configuration() { ++ return this.configuration; ++ } ++ ++ public BitStorage storage() { ++ return this.storage; ++ } ++ ++ public Palette palette() { ++ return this.palette; ++ } ++ ++ @Override ++ public final T[] moonrise$getPalette() { ++ return this.moonrise$palette; ++ } ++ ++ @Override ++ public final void moonrise$setPalette(final T[] palette) { ++ this.moonrise$palette = palette; ++ } ++ // Paper end - optimise palette reads ++ + public void copyFrom(Palette palette, BitStorage storage) { + for (int i = 0; i < storage.getSize(); i++) { + T object = palette.valueFor(storage.get(i)); diff --git a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java index 5321109ca638036572df9a7e17eafcef2b4f5112..5304254587372465c8ce821d7aa38b39a979f46b 100644 --- a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java @@ -29248,6 +31445,60 @@ index 5321109ca638036572df9a7e17eafcef2b4f5112..5304254587372465c8ce821d7aa38b39 this.lightEngine.checkBlock(pos); } } +diff --git a/src/main/java/net/minecraft/world/level/chunk/SingleValuePalette.java b/src/main/java/net/minecraft/world/level/chunk/SingleValuePalette.java +index a45e6410600afc5464e5d29932c193786ce0a6fb..a1ba68c95c2cdebdc0d7782cce7895529918073c 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/SingleValuePalette.java ++++ b/src/main/java/net/minecraft/world/level/chunk/SingleValuePalette.java +@@ -8,12 +8,24 @@ import net.minecraft.network.FriendlyByteBuf; + import net.minecraft.network.VarInt; + import org.apache.commons.lang3.Validate; + +-public class SingleValuePalette implements Palette { ++public class SingleValuePalette implements Palette, ca.spottedleaf.moonrise.patches.fast_palette.FastPalette { // Paper - optimise palette reads + private final IdMap registry; + @Nullable + private T value; + private final PaletteResize resizeHandler; + ++ // Paper start - optimise palette reads ++ private T[] rawPalette; ++ ++ @Override ++ public final T[] moonrise$getRawPalette(final ca.spottedleaf.moonrise.patches.fast_palette.FastPaletteData container) { ++ if (this.rawPalette != null) { ++ return this.rawPalette; ++ } ++ return this.rawPalette = (T[])new Object[] { this.value }; ++ } ++ // Paper end - optimise palette reads ++ + public SingleValuePalette(IdMap idList, PaletteResize listener, List entries) { + this.registry = idList; + this.resizeHandler = listener; +@@ -33,6 +45,11 @@ public class SingleValuePalette implements Palette { + return this.resizeHandler.onResize(1, object); + } else { + this.value = object; ++ // Paper start - optimise palette reads ++ if (this.rawPalette != null) { ++ this.rawPalette[0] = object; ++ } ++ // Paper end - optimise palette reads + return 0; + } + } +@@ -58,6 +75,11 @@ public class SingleValuePalette implements Palette { + @Override + public void read(FriendlyByteBuf buf) { + this.value = this.registry.byIdOrThrow(buf.readVarInt()); ++ // Paper start - optimise palette reads ++ if (this.rawPalette != null) { ++ this.rawPalette[0] = this.value; ++ } ++ // Paper end - optimise palette reads + } + + @Override diff --git a/src/main/java/net/minecraft/world/level/chunk/status/ChunkPyramid.java b/src/main/java/net/minecraft/world/level/chunk/status/ChunkPyramid.java index b1058bf0dcda544a074f4d3772d7899b94f98927..b7bf82f6b6023bd628d3e7ea84d2d6755a0d931a 100644 --- a/src/main/java/net/minecraft/world/level/chunk/status/ChunkPyramid.java @@ -29647,8 +31898,100 @@ index cb823d342e41b5861adfc847a313c265fb702a4c..2b1ea97199d5976e5ff4bd049c1e6c8b private final SequencedMap pendingWrites = new LinkedHashMap<>(); private final Long2ObjectLinkedOpenHashMap> regionCacheForBlender = new Long2ObjectLinkedOpenHashMap<>(); private static final int REGION_CACHE_SIZE = 1024; +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java +index f1237f6fd6414900ffbad0caee31aa83310eeef4..8071ce70d66909bb4bda45792bf329a939d6f918 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java +@@ -25,7 +25,7 @@ import net.minecraft.util.profiling.jfr.JvmProfiler; + import net.minecraft.world.level.ChunkPos; + import org.slf4j.Logger; + +-public class RegionFile implements AutoCloseable { ++public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemRegionFile { // Paper - rewrite chunk system + + private static final Logger LOGGER = LogUtils.getLogger(); + private static final int SECTOR_BYTES = 4096; +@@ -49,6 +49,21 @@ public class RegionFile implements AutoCloseable { + @VisibleForTesting + protected final RegionBitmap usedSectors; + ++ // Paper start - rewrite chunk system ++ @Override ++ public final ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData moonrise$startWrite(final net.minecraft.nbt.CompoundTag data, final ChunkPos pos) throws IOException { ++ final RegionFile.ChunkBuffer buffer = ((RegionFile)(Object)this).new ChunkBuffer(pos); ++ ((ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemChunkBuffer)buffer).moonrise$setWriteOnClose(false); ++ ++ final DataOutputStream out = new DataOutputStream(this.version.wrap(buffer)); ++ ++ return new ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData( ++ data, ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData.WriteResult.WRITE, ++ out, ((ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemChunkBuffer)buffer)::moonrise$write ++ ); ++ } ++ // Paper end - rewrite chunk system ++ + public RegionFile(RegionStorageInfo storageKey, Path directory, Path path, boolean dsync) throws IOException { + this(storageKey, directory, path, RegionFileVersion.getSelected(), dsync); + } +@@ -220,6 +235,16 @@ public class RegionFile implements AutoCloseable { + + @Nullable + private DataInputStream createExternalChunkInputStream(ChunkPos pos, byte flags) throws IOException { ++ // Paper start - rewrite chunk system ++ final DataInputStream is = this.createExternalChunkInputStream0(pos, flags); ++ if (is == null) { ++ return is; ++ } ++ return new ca.spottedleaf.moonrise.patches.chunk_system.util.stream.ExternalChunkStreamMarker(is); ++ } ++ @Nullable ++ private DataInputStream createExternalChunkInputStream0(ChunkPos pos, byte flags) throws IOException { ++ // Paper end - rewrite chunk system + Path path = this.getExternalChunkPath(pos); + + if (!Files.isRegularFile(path, new LinkOption[0])) { +@@ -443,10 +468,29 @@ public class RegionFile implements AutoCloseable { + } + + public static final int MAX_CHUNK_SIZE = 500 * 1024 * 1024; // Paper - don't write garbage data to disk if writing serialization fails +- private class ChunkBuffer extends ByteArrayOutputStream { ++ private class ChunkBuffer extends ByteArrayOutputStream implements ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemChunkBuffer { // Paper - rewrite chunk system + + private final ChunkPos pos; + ++ // Paper start - rewrite chunk system ++ private boolean writeOnClose = true; ++ ++ @Override ++ public final boolean moonrise$getWriteOnClose() { ++ return this.writeOnClose; ++ } ++ ++ @Override ++ public final void moonrise$setWriteOnClose(final boolean value) { ++ this.writeOnClose = value; ++ } ++ ++ @Override ++ public final void moonrise$write(final RegionFile regionFile) throws IOException { ++ regionFile.write(this.pos, ByteBuffer.wrap(this.buf, 0, this.count)); ++ } ++ // Paper end - rewrite chunk system ++ + public ChunkBuffer(final ChunkPos chunkcoordintpair) { + super(8096); + super.write(0); +@@ -480,7 +524,7 @@ public class RegionFile implements AutoCloseable { + + JvmProfiler.INSTANCE.onRegionFileWrite(RegionFile.this.info, this.pos, RegionFile.this.version, i); + bytebuffer.putInt(0, i); +- RegionFile.this.write(this.pos, bytebuffer); ++ if (this.writeOnClose) { RegionFile.this.write(this.pos, bytebuffer); } // Paper - rewrite chunk system + } + } + diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -index 4c1212c6ef48594e766fa9e35a6e15916602d587..18054304e08c8a6346c0135a0e6a68e77fe5c37c 100644 +index 4c1212c6ef48594e766fa9e35a6e15916602d587..9dbc9e2f9d5aab71720bb81803efe76e2f361f04 100644 --- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java @@ -17,7 +17,7 @@ import net.minecraft.nbt.StreamTagVisitor; @@ -29660,15 +32003,15 @@ index 4c1212c6ef48594e766fa9e35a6e15916602d587..18054304e08c8a6346c0135a0e6a68e7 public static final String ANVIL_EXTENSION = ".mca"; private static final int MAX_CACHE_SIZE = 256; -@@ -26,33 +26,122 @@ public final class RegionFileStorage implements AutoCloseable { +@@ -26,33 +26,219 @@ public final class RegionFileStorage implements AutoCloseable { private final Path folder; private final boolean sync; - RegionFileStorage(RegionStorageInfo storageKey, Path directory, boolean dsync) { + // Paper start - rewrite chunk system + private static final int REGION_SHIFT = 5; -+ private static final int MAX_NON_EXISTING_CACHE = 1024 * 64; -+ private final it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet nonExistingRegionFiles = new it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet(MAX_NON_EXISTING_CACHE+1); ++ private static final int MAX_NON_EXISTING_CACHE = 1024 * 4; ++ private final it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet nonExistingRegionFiles = new it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet(); + private static String getRegionFileName(final int chunkX, final int chunkZ) { + return "r." + (chunkX >> REGION_SHIFT) + "." + (chunkZ >> REGION_SHIFT) + ".mca"; + } @@ -29743,6 +32086,97 @@ index 4c1212c6ef48594e766fa9e35a6e15916602d587..18054304e08c8a6346c0135a0e6a68e7 + + return ret; + } ++ ++ @Override ++ public final ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData moonrise$startWrite( ++ final int chunkX, final int chunkZ, final CompoundTag compound ++ ) throws IOException { ++ if (compound == null) { ++ return new ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData( ++ compound, ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData.WriteResult.DELETE, ++ null, null ++ ); ++ } ++ ++ final ChunkPos pos = new ChunkPos(chunkX, chunkZ); ++ final RegionFile regionFile = this.getRegionFile(pos); ++ ++ // note: not required to keep regionfile loaded after this call, as the write param takes a regionfile as input ++ // (and, the regionfile parameter is unused for writing until the write call) ++ final ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData writeData = ((ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemRegionFile)regionFile).moonrise$startWrite(compound, pos); ++ ++ try { ++ NbtIo.write(compound, writeData.output()); ++ } finally { ++ writeData.output().close(); ++ } ++ ++ return writeData; ++ } ++ ++ @Override ++ public final void moonrise$finishWrite( ++ final int chunkX, final int chunkZ, final ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData writeData ++ ) throws IOException { ++ final ChunkPos pos = new ChunkPos(chunkX, chunkZ); ++ if (writeData.result() == ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData.WriteResult.DELETE) { ++ final RegionFile regionFile = this.moonrise$getRegionFileIfExists(chunkX, chunkZ); ++ if (regionFile != null) { ++ regionFile.clear(pos); ++ } // else: didn't exist ++ ++ return; ++ } ++ ++ writeData.write().run(this.getRegionFile(pos)); ++ } ++ ++ @Override ++ public final ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData moonrise$readData( ++ final int chunkX, final int chunkZ ++ ) throws IOException { ++ final RegionFile regionFile = this.moonrise$getRegionFileIfExists(chunkX, chunkZ); ++ ++ final DataInputStream input = regionFile == null ? null : regionFile.getChunkDataInputStream(new ChunkPos(chunkX, chunkZ)); ++ ++ if (input == null) { ++ return new ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData( ++ ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData.ReadResult.NO_DATA, null, null ++ ); ++ } ++ ++ final ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData ret = new ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData( ++ ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData.ReadResult.HAS_DATA, input, null ++ ); ++ ++ if (!(input instanceof ca.spottedleaf.moonrise.patches.chunk_system.util.stream.ExternalChunkStreamMarker)) { ++ // internal stream, which is fully read ++ return ret; ++ } ++ ++ final CompoundTag syncRead = this.moonrise$finishRead(chunkX, chunkZ, ret); ++ ++ if (syncRead == null) { ++ // need to try again ++ return this.moonrise$readData(chunkX, chunkZ); ++ } ++ ++ return new ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData( ++ ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData.ReadResult.SYNC_READ, null, syncRead ++ ); ++ } ++ ++ // if the return value is null, then the caller needs to re-try with a new call to readData() ++ @Override ++ public final CompoundTag moonrise$finishRead( ++ final int chunkX, final int chunkZ, final ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData readData ++ ) throws IOException { ++ try { ++ return NbtIo.read(readData.input()); ++ } finally { ++ readData.input().close(); ++ } ++ } + // Paper end - rewrite chunk system + + protected RegionFileStorage(RegionStorageInfo storageKey, Path directory, boolean dsync) { // Paper - protected @@ -29754,6 +32188,17 @@ index 4c1212c6ef48594e766fa9e35a6e15916602d587..18054304e08c8a6346c0135a0e6a68e7 - private RegionFile getRegionFile(ChunkPos chunkcoordintpair, boolean existingOnly) throws IOException { // CraftBukkit - long i = ChunkPos.asLong(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ()); - RegionFile regionfile = (RegionFile) this.regionCache.getAndMoveToFirst(i); ++ // Paper start - rewrite chunk system ++ public RegionFile getRegionFile(ChunkPos chunkcoordintpair) throws IOException { ++ return this.getRegionFile(chunkcoordintpair, false); ++ } ++ // Paper end - rewrite chunk system + +- if (regionfile != null) { +- return regionfile; +- } else { +- if (this.regionCache.size() >= io.papermc.paper.configuration.GlobalConfiguration.get().misc.regionFileCacheSize) { // Paper - Sanitise RegionFileCache and make configurable +- ((RegionFile) this.regionCache.removeLast()).close(); + public RegionFile getRegionFile(ChunkPos chunkcoordintpair, boolean existingOnly) throws IOException { // CraftBukkit // Paper - public + // Paper start - rewrite chunk system + if (existingOnly) { @@ -29761,12 +32206,7 @@ index 4c1212c6ef48594e766fa9e35a6e15916602d587..18054304e08c8a6346c0135a0e6a68e7 + } + synchronized (this) { + final long key = ChunkPos.asLong(chunkcoordintpair.x >> REGION_SHIFT, chunkcoordintpair.z >> REGION_SHIFT); - -- if (regionfile != null) { -- return regionfile; -- } else { -- if (this.regionCache.size() >= io.papermc.paper.configuration.GlobalConfiguration.get().misc.regionFileCacheSize) { // Paper - Sanitise RegionFileCache and make configurable -- ((RegionFile) this.regionCache.removeLast()).close(); ++ + RegionFile ret = this.regionCache.getAndMoveToFirst(key); + if (ret != null) { + return ret; @@ -29799,7 +32239,7 @@ index 4c1212c6ef48594e766fa9e35a6e15916602d587..18054304e08c8a6346c0135a0e6a68e7 } @Nullable -@@ -132,8 +221,14 @@ public final class RegionFileStorage implements AutoCloseable { +@@ -132,8 +318,14 @@ public final class RegionFileStorage implements AutoCloseable { } @@ -29816,7 +32256,7 @@ index 4c1212c6ef48594e766fa9e35a6e15916602d587..18054304e08c8a6346c0135a0e6a68e7 // Paper start - Chunk save reattempt int attempts = 0; Exception lastException = null; -@@ -182,30 +277,37 @@ public final class RegionFileStorage implements AutoCloseable { +@@ -182,30 +374,37 @@ public final class RegionFileStorage implements AutoCloseable { } public void close() throws IOException { @@ -30393,6 +32833,257 @@ index 8d90e783967280025d711c709facbcc87f611f8a..987e3397503cd07d3a2f172cede34129 } public int getLightSectionCount() { +diff --git a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java +index 261e5994d13f8bc30490b86691c80c0a21e7640a..f4fbcbb8ff6d2677af1a02a0801a323c06dce9b1 100644 +--- a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java ++++ b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java +@@ -55,6 +55,48 @@ public abstract class FlowingFluid extends Fluid { + }); + private final Map shapes = Maps.newIdentityHashMap(); + ++ // Paper start - fluid method optimisations ++ private FluidState sourceFalling; ++ private FluidState sourceNotFalling; ++ ++ private static final int TOTAL_FLOWING_STATES = FALLING.getPossibleValues().size() * LEVEL.getPossibleValues().size(); ++ private static final int MIN_LEVEL = LEVEL.getPossibleValues().stream().sorted().findFirst().get().intValue(); ++ ++ // index = (falling ? 1 : 0) + level*2 ++ private FluidState[] flowingLookUp; ++ private volatile boolean init; ++ ++ private static final int COLLISION_OCCLUSION_CACHE_SIZE = 2048; ++ private static final ThreadLocal COLLISION_OCCLUSION_CACHE = ThreadLocal.withInitial(() -> new ca.spottedleaf.moonrise.patches.collisions.util.FluidOcclusionCacheKey[COLLISION_OCCLUSION_CACHE_SIZE]); ++ ++ ++ /** ++ * Due to init order, we need to use callbacks to initialise our state ++ */ ++ private void init() { ++ synchronized (this) { ++ if (this.init) { ++ return; ++ } ++ this.flowingLookUp = new FluidState[TOTAL_FLOWING_STATES]; ++ final FluidState defaultFlowState = this.getFlowing().defaultFluidState(); ++ for (int i = 0; i < TOTAL_FLOWING_STATES; ++i) { ++ final int falling = i & 1; ++ final int level = (i >>> 1) + MIN_LEVEL; ++ ++ this.flowingLookUp[i] = defaultFlowState.setValue(FALLING, falling == 1 ? Boolean.TRUE : Boolean.FALSE) ++ .setValue(LEVEL, Integer.valueOf(level)); ++ } ++ ++ final FluidState defaultFallState = this.getSource().defaultFluidState(); ++ this.sourceFalling = defaultFallState.setValue(FALLING, Boolean.TRUE); ++ this.sourceNotFalling = defaultFallState.setValue(FALLING, Boolean.FALSE); ++ ++ this.init = true; ++ } ++ } ++ // Paper end - fluid method optimisations ++ + public FlowingFluid() {} + + @Override +@@ -246,65 +288,70 @@ public abstract class FlowingFluid extends Fluid { + } + } + +- private static boolean canPassThroughWall(Direction face, BlockGetter world, BlockPos pos, BlockState state, BlockPos fromPos, BlockState fromState) { +- VoxelShape voxelshape = fromState.getCollisionShape(world, fromPos); ++ // Paper start - fluid method optimisations ++ private static boolean canPassThroughWall(final Direction direction, final BlockGetter level, ++ final BlockPos fromPos, final BlockState fromState, ++ final BlockPos toPos, final BlockState toState) { ++ if (((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)fromState).moonrise$emptyCollisionShape() & ((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)toState).moonrise$emptyCollisionShape()) { ++ // don't even try to cache simple cases ++ return true; ++ } + +- if (voxelshape == Shapes.block()) { ++ if (((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)fromState).moonrise$occludesFullBlock() | ((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)toState).moonrise$occludesFullBlock()) { ++ // don't even try to cache simple cases + return false; +- } else { +- VoxelShape voxelshape1 = state.getCollisionShape(world, pos); +- +- if (voxelshape1 == Shapes.block()) { +- return false; +- } else if (voxelshape1 == Shapes.empty() && voxelshape == Shapes.empty()) { +- return true; +- } else { +- Object2ByteLinkedOpenHashMap object2bytelinkedopenhashmap; +- +- if (!state.getBlock().hasDynamicShape() && !fromState.getBlock().hasDynamicShape()) { +- object2bytelinkedopenhashmap = (Object2ByteLinkedOpenHashMap) FlowingFluid.OCCLUSION_CACHE.get(); +- } else { +- object2bytelinkedopenhashmap = null; +- } ++ } + +- FlowingFluid.BlockStatePairKey fluidtypeflowing_a; ++ final ca.spottedleaf.moonrise.patches.collisions.util.FluidOcclusionCacheKey[] cache = ((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)fromState).moonrise$hasCache() & ((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)toState).moonrise$hasCache() ? ++ COLLISION_OCCLUSION_CACHE.get() : null; + +- if (object2bytelinkedopenhashmap != null) { +- fluidtypeflowing_a = new FlowingFluid.BlockStatePairKey(state, fromState, face); +- byte b0 = object2bytelinkedopenhashmap.getAndMoveToFirst(fluidtypeflowing_a); ++ final int keyIndex ++ = (((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)fromState).moonrise$uniqueId1() ^ ((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)toState).moonrise$uniqueId2() ^ ((ca.spottedleaf.moonrise.patches.collisions.util.CollisionDirection)(Object)direction).moonrise$uniqueId()) ++ & (COLLISION_OCCLUSION_CACHE_SIZE - 1); + +- if (b0 != 127) { +- return b0 != 0; +- } +- } else { +- fluidtypeflowing_a = null; +- } +- +- boolean flag = !Shapes.mergedFaceOccludes(voxelshape1, voxelshape, face); ++ if (cache != null) { ++ final ca.spottedleaf.moonrise.patches.collisions.util.FluidOcclusionCacheKey cached = cache[keyIndex]; ++ if (cached != null && cached.first() == fromState && cached.second() == toState && cached.direction() == direction) { ++ return cached.result(); ++ } ++ } + +- if (object2bytelinkedopenhashmap != null) { +- if (object2bytelinkedopenhashmap.size() == 200) { +- object2bytelinkedopenhashmap.removeLastByte(); +- } ++ final VoxelShape shape1 = fromState.getCollisionShape(level, fromPos); ++ final VoxelShape shape2 = toState.getCollisionShape(level, toPos); + +- object2bytelinkedopenhashmap.putAndMoveToFirst(fluidtypeflowing_a, (byte) (flag ? 1 : 0)); +- } ++ final boolean result = !Shapes.mergedFaceOccludes(shape1, shape2, direction); + +- return flag; +- } ++ if (cache != null) { ++ // we can afford to replace in-use keys more often due to the excessive caching the collision patch does in mergedFaceOccludes ++ cache[keyIndex] = new ca.spottedleaf.moonrise.patches.collisions.util.FluidOcclusionCacheKey(fromState, toState, direction, result); + } ++ ++ return result; + } ++ // Paper end - fluid method optimisations + + public abstract Fluid getFlowing(); + + public FluidState getFlowing(int level, boolean falling) { +- return (FluidState) ((FluidState) this.getFlowing().defaultFluidState().setValue(FlowingFluid.LEVEL, level)).setValue(FlowingFluid.FALLING, falling); ++ // Paper start - fluid method optimisations ++ final int amount = level; ++ if (!this.init) { ++ this.init(); ++ } ++ final int index = (falling ? 1 : 0) | ((amount - MIN_LEVEL) << 1); ++ return this.flowingLookUp[index]; ++ // Paper end - fluid method optimisations + } + + public abstract Fluid getSource(); + + public FluidState getSource(boolean falling) { +- return (FluidState) this.getSource().defaultFluidState().setValue(FlowingFluid.FALLING, falling); ++ // Paper start - fluid method optimisations ++ if (!this.init) { ++ this.init(); ++ } ++ return falling ? this.sourceFalling : this.sourceNotFalling; ++ // Paper end - fluid method optimisations + } + + protected abstract boolean canConvertToSource(ServerLevel world); +diff --git a/src/main/java/net/minecraft/world/level/material/FluidState.java b/src/main/java/net/minecraft/world/level/material/FluidState.java +index 87adfe152abd1b8b4d547034576883c5d1cdf134..2d50d72bf026d0cf9c546a3c6fc1859379bfd805 100644 +--- a/src/main/java/net/minecraft/world/level/material/FluidState.java ++++ b/src/main/java/net/minecraft/world/level/material/FluidState.java +@@ -22,12 +22,30 @@ import net.minecraft.world.level.block.state.properties.Property; + import net.minecraft.world.phys.Vec3; + import net.minecraft.world.phys.shapes.VoxelShape; + +-public final class FluidState extends StateHolder { ++public final class FluidState extends StateHolder implements ca.spottedleaf.moonrise.patches.fluid.FluidFluidState { // Paper - fluid method optimisations + public static final Codec CODEC = codec(BuiltInRegistries.FLUID.byNameCodec(), Fluid::defaultFluidState).stable(); + public static final int AMOUNT_MAX = 9; + public static final int AMOUNT_FULL = 8; + protected final boolean isEmpty; // Paper - Perf: moved from isEmpty() + ++ // Paper start - fluid method optimisations ++ private int amount; ++ //private boolean isEmpty; ++ private boolean isSource; ++ private float ownHeight; ++ private boolean isRandomlyTicking; ++ private BlockState legacyBlock; ++ ++ @Override ++ public final void moonrise$initCaches() { ++ this.amount = this.getType().getAmount((FluidState)(Object)this); ++ //this.isEmpty = this.getType().isEmpty(); ++ this.isSource = this.getType().isSource((FluidState)(Object)this); ++ this.ownHeight = this.getType().getOwnHeight((FluidState)(Object)this); ++ this.isRandomlyTicking = this.getType().isRandomlyTicking(); ++ } ++ // Paper end - fluid method optimisations ++ + public FluidState(Fluid fluid, Reference2ObjectArrayMap, Comparable> propertyMap, MapCodec codec) { + super(fluid, propertyMap, codec); + this.isEmpty = fluid.isEmpty(); // Paper - Perf: moved from isEmpty() +@@ -38,11 +56,11 @@ public final class FluidState extends StateHolder { + } + + public boolean isSource() { +- return this.getType().isSource(this); ++ return this.isSource; // Paper - fluid method optimisations + } + + public boolean isSourceOfType(Fluid fluid) { +- return this.owner == fluid && this.owner.isSource(this); ++ return this.isSource && this.owner == fluid; // Paper - fluid method optimisations + } + + public boolean isEmpty() { +@@ -54,11 +72,11 @@ public final class FluidState extends StateHolder { + } + + public float getOwnHeight() { +- return this.getType().getOwnHeight(this); ++ return this.ownHeight; // Paper - fluid method optimisations + } + + public int getAmount() { +- return this.getType().getAmount(this); ++ return this.amount; // Paper - fluid method optimisations + } + + public boolean shouldRenderBackwardUpFace(BlockGetter world, BlockPos pos) { +@@ -84,7 +102,7 @@ public final class FluidState extends StateHolder { + } + + public boolean isRandomlyTicking() { +- return this.getType().isRandomlyTicking(); ++ return this.isRandomlyTicking; // Paper - fluid method optimisations + } + + public void randomTick(ServerLevel world, BlockPos pos, RandomSource random) { +@@ -96,7 +114,12 @@ public final class FluidState extends StateHolder { + } + + public BlockState createLegacyBlock() { +- return this.getType().createLegacyBlock(this); ++ // Paper start - fluid method optimisations ++ if (this.legacyBlock != null) { ++ return this.legacyBlock; ++ } ++ return this.legacyBlock = this.getType().createLegacyBlock((FluidState)(Object)this); ++ // Paper end - fluid method optimisations + } + + @Nullable diff --git a/src/main/java/net/minecraft/world/phys/AABB.java b/src/main/java/net/minecraft/world/phys/AABB.java index 5dc2674b537f4a61b2e21a21bdb2e8dc090d3a3c..6cf6d4ec7b9e43c7b2b4c0e2fb080964ff588130 100644 --- a/src/main/java/net/minecraft/world/phys/AABB.java @@ -30602,7 +33293,7 @@ index d812949c7329ae2696b38dc792fa011ba87decb9..7743495c7ec3fc5e17947144457cef7b @Override diff --git a/src/main/java/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java -index 01693ba050b12b9debcdaefceeff9cbcd503b369..1d36f8dcffd22cf844448d3d8351fb8718cf5227 100644 +index 01693ba050b12b9debcdaefceeff9cbcd503b369..fbe0c4b0fdbb992b7002f6afe1e74d63cbb420f2 100644 --- a/src/main/java/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java +++ b/src/main/java/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java @@ -3,12 +3,79 @@ package net.minecraft.world.phys.shapes; @@ -30664,7 +33355,7 @@ index 01693ba050b12b9debcdaefceeff9cbcd503b369..1d36f8dcffd22cf844448d3d8351fb87 + } + } + -+ final boolean hasSingleAABB = sizeX == 1 && sizeY == 1 && sizeZ == 1 && !isEmpty && discreteVoxelShape.isFull(0, 0, 0); ++ final boolean hasSingleAABB = sizeX == 1 && sizeY == 1 && sizeZ == 1 && !isEmpty && (voxelSet[0] & 1L) != 0L; + + final int minFullX = discreteVoxelShape.firstFull(Direction.Axis.X); + final int minFullY = discreteVoxelShape.firstFull(Direction.Axis.Y); @@ -30702,7 +33393,7 @@ index 7ec02a7849437a18860aa0df7d9ddd71b2447d4c..5e45e49ab09344cb95736f4124b1c6e0 public OffsetDoubleList(DoubleList oldList, double offset) { this.delegate = oldList; diff --git a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java -index 5a0b0b47da3d796c391ac15eb573af25a1dfcd32..672a2038c6d8b31090403766460c6149a75adf8b 100644 +index 5a0b0b47da3d796c391ac15eb573af25a1dfcd32..513bed7f11aee667c87046db4cf912b80e8f3638 100644 --- a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java +++ b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java @@ -16,9 +16,15 @@ public final class Shapes { @@ -30858,13 +33549,13 @@ index 5a0b0b47da3d796c391ac15eb573af25a1dfcd32..672a2038c6d8b31090403766460c6149 + final VoxelShape first = tmp[i]; + final VoxelShape second = tmp[next]; + -+ tmp[newSize++] = Shapes.or(first, second); ++ tmp[newSize++] = Shapes.joinUnoptimized(first, second, BooleanOp.OR); + } + } + size = newSize; + } + -+ return tmp[0]; ++ return tmp[0].optimize(); + // Paper end - optimise collisions } @@ -31108,10 +33799,10 @@ index b07f1c58e00d232e7c83e6df3499e4b677645609..b88c71f27996d24d29048e06a69a0046 private static DiscreteVoxelShape makeSlice(DiscreteVoxelShape voxelSet, Direction.Axis axis, int sliceWidth) { diff --git a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java -index bcb79462c8b3309ae8701cba4753b27a9d22eb2e..d850a7de74150a04622da71d9614320f3d5d69e8 100644 +index bcb79462c8b3309ae8701cba4753b27a9d22eb2e..3f8e7e29c3e52211a29e6f0a32890f6b53bfd9a8 100644 --- a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java +++ b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java -@@ -15,38 +15,505 @@ import net.minecraft.world.phys.AABB; +@@ -15,61 +15,546 @@ import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.Vec3; @@ -31264,13 +33955,13 @@ index bcb79462c8b3309ae8701cba4753b27a9d22eb2e..d850a7de74150a04622da71d9614320f + + if (direction.getAxisDirection() == Direction.AxisDirection.POSITIVE) { + if (DoubleMath.fuzzyEquals(this.max(axis), 1.0, ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON)) { -+ ret = tryForceBlock(new SliceShape((VoxelShape)(Object)this, axis, this.shape.getSize(axis) - 1)); ++ ret = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.sliceShape((VoxelShape)(Object)this, axis, this.shape.getSize(axis) - 1); + } else { + ret = Shapes.empty(); + } + } else { + if (DoubleMath.fuzzyEquals(this.min(axis), 0.0, ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON)) { -+ ret = tryForceBlock(new SliceShape((VoxelShape)(Object)this, axis, 0)); ++ ret = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.sliceShape((VoxelShape)(Object)this, axis, 0); + } else { + ret = Shapes.empty(); + } @@ -31281,23 +33972,6 @@ index bcb79462c8b3309ae8701cba4753b27a9d22eb2e..d850a7de74150a04622da71d9614320f + return ret; + } + -+ private static VoxelShape tryForceBlock(final VoxelShape other) { -+ if (other == Shapes.block()) { -+ return other; -+ } -+ -+ final AABB otherAABB = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)other).moonrise$getSingleAABBRepresentation(); -+ if (otherAABB == null) { -+ return other; -+ } -+ -+ if (((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)Shapes.block()).moonrise$getSingleAABBRepresentation().equals(otherAABB)) { -+ return Shapes.block(); -+ } -+ -+ return other; -+ } -+ + private boolean computeOccludesFullBlock() { + if (this.isEmpty) { + this.occludesFullBlock = Boolean.FALSE; @@ -31395,18 +34069,21 @@ index bcb79462c8b3309ae8701cba4753b27a9d22eb2e..d850a7de74150a04622da71d9614320f + return result; + } + -+ private static DoubleList offsetList(final DoubleList src, final double by) { -+ if (src instanceof OffsetDoubleList offsetDoubleList) { -+ return new OffsetDoubleList(offsetDoubleList.delegate, by + offsetDoubleList.offset); ++ private static DoubleList offsetList(final double[] src, final double by) { ++ final it.unimi.dsi.fastutil.doubles.DoubleArrayList wrap = it.unimi.dsi.fastutil.doubles.DoubleArrayList.wrap(src); ++ if (by == 0.0) { ++ return wrap; + } -+ return new OffsetDoubleList(src, by); ++ return new OffsetDoubleList(wrap, by); + } + + private List toAabbsUncached() { -+ final List ret = new java.util.ArrayList<>(); ++ final List ret; + if (this.singleAABBRepresentation != null) { ++ ret = new java.util.ArrayList<>(1); + ret.add(this.singleAABBRepresentation); + } else { ++ ret = new java.util.ArrayList<>(); + final double[] coordsX = this.rootCoordinatesX; + final double[] coordsY = this.rootCoordinatesY; + final double[] coordsZ = this.rootCoordinatesZ; @@ -31528,6 +34205,26 @@ index bcb79462c8b3309ae8701cba4753b27a9d22eb2e..d850a7de74150a04622da71d9614320f + final double minDistance = minDistanceArr[0]; + return new BlockHitResult(from.add(minDistance * diffX, minDistance * diffY, minDistance * diffZ), direction, offset, false); + } ++ ++ private VoxelShape calculateFaceDirect(final Direction direction, final Direction.Axis axis, final double[] coords, final double offset) { ++ if (coords.length == 2 && ++ DoubleMath.fuzzyEquals(coords[0] + offset, 0.0, ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) && ++ DoubleMath.fuzzyEquals(coords[1] + offset, 1.0, ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON)) { ++ return (VoxelShape)(Object)this; ++ } ++ ++ final boolean positiveDir = direction.getAxisDirection() == Direction.AxisDirection.POSITIVE; ++ ++ // see findIndex ++ final int index = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.findFloor( ++ coords, (positiveDir ? (1.0 - ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) : (0.0 + ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON)) - offset, ++ 0, coords.length - 1 ++ ); ++ ++ return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.sliceShape( ++ (VoxelShape)(Object)this, axis, index ++ ); ++ } + // Paper end - optimise collisions + protected VoxelShape(DiscreteVoxelShape voxels) { @@ -31634,7 +34331,45 @@ index bcb79462c8b3309ae8701cba4753b27a9d22eb2e..d850a7de74150a04622da71d9614320f } public VoxelShape singleEncompassing() { -@@ -69,7 +536,7 @@ public abstract class VoxelShape { +- return this.isEmpty() +- ? Shapes.empty() +- : Shapes.box( +- this.min(Direction.Axis.X), +- this.min(Direction.Axis.Y), +- this.min(Direction.Axis.Z), +- this.max(Direction.Axis.X), +- this.max(Direction.Axis.Y), +- this.max(Direction.Axis.Z) +- ); ++ // Paper start - optimise collisions ++ if (this.isEmpty) { ++ return Shapes.empty(); ++ } ++ return Shapes.create(this.bounds()); ++ // Paper end - optimise collisions + } + + protected double get(Direction.Axis axis, int index) { +- return this.getCoords(axis).getDouble(index); ++ // Paper start - optimise collisions ++ final int idx = index; ++ switch (axis) { ++ case X: { ++ return this.rootCoordinatesX[idx] + this.offsetX; ++ } ++ case Y: { ++ return this.rootCoordinatesY[idx] + this.offsetY; ++ } ++ case Z: { ++ return this.rootCoordinatesZ[idx] + this.offsetZ; ++ } ++ default: { ++ throw new IllegalStateException("Unknown axis: " + axis); ++ } ++ } ++ // Paper end - optimise collisions + } + public abstract DoubleList getCoords(Direction.Axis axis); public boolean isEmpty() { @@ -31643,7 +34378,7 @@ index bcb79462c8b3309ae8701cba4753b27a9d22eb2e..d850a7de74150a04622da71d9614320f } public VoxelShape move(Vec3 vec3d) { -@@ -77,24 +544,91 @@ public abstract class VoxelShape { +@@ -77,24 +562,96 @@ public abstract class VoxelShape { } public VoxelShape move(double x, double y, double z) { @@ -31662,9 +34397,9 @@ index bcb79462c8b3309ae8701cba4753b27a9d22eb2e..d850a7de74150a04622da71d9614320f + + final ArrayVoxelShape ret = new ArrayVoxelShape( + this.shape, -+ offsetList(this.getCoords(Direction.Axis.X), x), -+ offsetList(this.getCoords(Direction.Axis.Y), y), -+ offsetList(this.getCoords(Direction.Axis.Z), z) ++ offsetList(this.rootCoordinatesX, this.offsetX + x), ++ offsetList(this.rootCoordinatesY, this.offsetY + y), ++ offsetList(this.rootCoordinatesZ, this.offsetZ + z) + ); + + final ca.spottedleaf.moonrise.patches.collisions.shape.CachedToAABBs cachedToAABBs = this.cachedToAABBs; @@ -31696,6 +34431,11 @@ index bcb79462c8b3309ae8701cba4753b27a9d22eb2e..d850a7de74150a04622da71d9614320f + + final List aabbs = this.toAabbs(); + ++ if (aabbs.isEmpty()) { ++ // We are a SliceShape, which does not properly fill isEmpty for every case ++ return Shapes.empty(); ++ } ++ + if (aabbs.size() == 1) { + final AABB singleAABB = aabbs.get(0); + final VoxelShape ret = Shapes.create(singleAABB); @@ -31750,7 +34490,7 @@ index bcb79462c8b3309ae8701cba4753b27a9d22eb2e..d850a7de74150a04622da71d9614320f } public void forAllEdges(Shapes.DoubleLineConsumer consumer) { -@@ -131,9 +665,24 @@ public abstract class VoxelShape { +@@ -131,9 +688,24 @@ public abstract class VoxelShape { } public List toAabbs() { @@ -31778,7 +34518,37 @@ index bcb79462c8b3309ae8701cba4753b27a9d22eb2e..d850a7de74150a04622da71d9614320f } public double min(Direction.Axis axis, double from, double to) { -@@ -159,42 +708,63 @@ public abstract class VoxelShape { +@@ -155,46 +727,92 @@ public abstract class VoxelShape { + } + + protected int findIndex(Direction.Axis axis, double coord) { +- return Mth.binarySearch(0, this.shape.getSize(axis) + 1, i -> coord < this.get(axis, i)) - 1; ++ // Paper start - optimise collisions ++ final double value = coord; ++ switch (axis) { ++ case X: { ++ final double[] values = this.rootCoordinatesX; ++ return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.findFloor( ++ values, value - this.offsetX, 0, values.length - 1 ++ ); ++ } ++ case Y: { ++ final double[] values = this.rootCoordinatesY; ++ return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.findFloor( ++ values, value - this.offsetY, 0, values.length - 1 ++ ); ++ } ++ case Z: { ++ final double[] values = this.rootCoordinatesZ; ++ return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.findFloor( ++ values, value - this.offsetZ, 0, values.length - 1 ++ ); ++ } ++ default: { ++ throw new IllegalStateException("Unknown axis: " + axis); ++ } ++ } ++ // Paper end - optimise collisions } @Nullable @@ -31817,13 +34587,13 @@ index bcb79462c8b3309ae8701cba4753b27a9d22eb2e..d850a7de74150a04622da71d9614320f + final AABB singleAABB = this.singleAABBRepresentation; + if (singleAABB != null) { + if (singleAABB.contains(fromBehindOffsetX, fromBehindOffsetY, fromBehindOffsetZ)) { -+ return new BlockHitResult(fromBehind, Direction.getNearest(directionOpposite.x, directionOpposite.y, directionOpposite.z).getOpposite(), offset, true); ++ return new BlockHitResult(fromBehind, Direction.getApproximateNearest(directionOpposite.x, directionOpposite.y, directionOpposite.z).getOpposite(), offset, true); } + return clip(singleAABB, from, to, offset); + } + + if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.strictlyContains((VoxelShape)(Object)this, fromBehindOffsetX, fromBehindOffsetY, fromBehindOffsetZ)) { -+ return new BlockHitResult(fromBehind, Direction.getNearest(directionOpposite.x, directionOpposite.y, directionOpposite.z).getOpposite(), offset, true); ++ return new BlockHitResult(fromBehind, Direction.getApproximateNearest(directionOpposite.x, directionOpposite.y, directionOpposite.z).getOpposite(), offset, true); } + + return AABB.clip(((VoxelShape)(Object)this).toAabbs(), from, to, offset); @@ -31871,7 +34641,44 @@ index bcb79462c8b3309ae8701cba4753b27a9d22eb2e..d850a7de74150a04622da71d9614320f } public VoxelShape getFaceShape(Direction facing) { -@@ -249,9 +819,30 @@ public abstract class VoxelShape { +@@ -216,20 +834,24 @@ public abstract class VoxelShape { + } + } + +- private VoxelShape calculateFace(Direction facing) { +- Direction.Axis axis = facing.getAxis(); +- if (this.isCubeLikeAlong(axis)) { +- return this; +- } else { +- Direction.AxisDirection axisDirection = facing.getAxisDirection(); +- int i = this.findIndex(axis, axisDirection == Direction.AxisDirection.POSITIVE ? 0.9999999 : 1.0E-7); +- SliceShape sliceShape = new SliceShape(this, axis, i); +- if (sliceShape.isEmpty()) { +- return Shapes.empty(); +- } else { +- return (VoxelShape)(sliceShape.isCubeLike() ? Shapes.block() : sliceShape); ++ private VoxelShape calculateFace(Direction direction) { ++ // Paper start - optimise collisions ++ final Direction.Axis axis = direction.getAxis(); ++ switch (axis) { ++ case X: { ++ return this.calculateFaceDirect(direction, axis, this.rootCoordinatesX, this.offsetX); ++ } ++ case Y: { ++ return this.calculateFaceDirect(direction, axis, this.rootCoordinatesY, this.offsetY); ++ } ++ case Z: { ++ return this.calculateFaceDirect(direction, axis, this.rootCoordinatesZ, this.offsetZ); ++ } ++ default: { ++ throw new IllegalStateException("Unknown axis: " + axis); + } + } ++ // Paper end - optimise collisions + } + + protected boolean isCubeLike() { +@@ -249,9 +871,30 @@ public abstract class VoxelShape { && DoubleMath.fuzzyEquals(doubleList.getDouble(1), 1.0, 1.0E-7); } diff --git a/patches/server/0828-fixup-Moonrise-optimisation-patches.patch b/patches/server/0828-fixup-Moonrise-optimisation-patches.patch deleted file mode 100644 index e2197234c7..0000000000 --- a/patches/server/0828-fixup-Moonrise-optimisation-patches.patch +++ /dev/null @@ -1,13349 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Mon, 21 Oct 2024 11:06:24 -0700 -Subject: [PATCH] fixup! Moonrise optimisation patches - - -diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java b/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java -index b61611351bf23efc1e90bab8a850ebbe6ffdd516..fc029c8fb22a7c8eeb23bfc171812f6da91c60fa 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java -+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java -@@ -6,11 +6,13 @@ import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel - import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk; - import ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader; - import ca.spottedleaf.moonrise.patches.chunk_system.world.ChunkSystemServerChunkCache; -+import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickServerLevel; - import com.mojang.logging.LogUtils; - import net.minecraft.server.level.ChunkHolder; - import net.minecraft.server.level.FullChunkStatus; - import net.minecraft.server.level.ServerLevel; - import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.server.level.progress.ChunkProgressListener; - import net.minecraft.world.entity.Entity; - import net.minecraft.world.level.chunk.ChunkAccess; - import net.minecraft.world.level.chunk.LevelChunk; -@@ -80,7 +82,13 @@ public final class ChunkSystem { - } - - public static void onChunkHolderDelete(final ServerLevel level, final ChunkHolder holder) { -- -+ // Update progress listener for LevelLoadingScreen -+ final ChunkProgressListener progressListener = level.getChunkSource().chunkMap.progressListener; -+ if (progressListener != null) { -+ ChunkSystem.scheduleChunkTask(level, holder.getPos().x, holder.getPos().z, () -> { -+ progressListener.onStatusChange(holder.getPos(), null); -+ }); -+ } - } - - public static void onChunkPreBorder(final LevelChunk chunk, final ChunkHolder holder) { -@@ -112,16 +120,18 @@ public final class ChunkSystem { - ((ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder() - ); - if (!((ChunkSystemLevelChunk)chunk).moonrise$isPostProcessingDone()) { -- chunk.postProcessGeneration(); -+ chunk.postProcessGeneration((ServerLevel)chunk.getLevel()); - } - ((ServerLevel)chunk.getLevel()).startTickingChunk(chunk); - ((ServerLevel)chunk.getLevel()).getChunkSource().chunkMap.tickingGenerated.incrementAndGet(); -+ ((ChunkTickServerLevel)(ServerLevel)chunk.getLevel()).moonrise$markChunkForPlayerTicking(chunk); // Moonrise - chunk tick iteration - } - - public static void onChunkNotTicking(final LevelChunk chunk, final ChunkHolder holder) { - ((ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getTickingChunks().remove( - ((ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder() - ); -+ ((ChunkTickServerLevel)(ServerLevel)chunk.getLevel()).moonrise$removeChunkForPlayerTicking(chunk); // Moonrise - chunk tick iteration - } - - public static void onChunkEntityTicking(final LevelChunk chunk, final ChunkHolder holder) { -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/block_counting/BlockCountingBitStorage.java b/src/main/java/ca/spottedleaf/moonrise/patches/block_counting/BlockCountingBitStorage.java -index aef4fc0d3c272febe675d1ac846b88e58b4e7533..93bc56daec4526f373c84763b8c7ccb4a30e800b 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/block_counting/BlockCountingBitStorage.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/block_counting/BlockCountingBitStorage.java -@@ -1,10 +1,10 @@ - package ca.spottedleaf.moonrise.patches.block_counting; - - import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; --import it.unimi.dsi.fastutil.ints.IntArrayList; -+import it.unimi.dsi.fastutil.shorts.ShortArrayList; - - public interface BlockCountingBitStorage { - -- public Int2ObjectOpenHashMap moonrise$countEntries(); -+ public Int2ObjectOpenHashMap moonrise$countEntries(); - - } -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/block_counting/BlockCountingChunkSection.java b/src/main/java/ca/spottedleaf/moonrise/patches/block_counting/BlockCountingChunkSection.java -index a08ddb0598d44368af5b6bace971ee31edf9919e..0d1443a113c07d7655e7b927a899447f70db8fa9 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/block_counting/BlockCountingChunkSection.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/block_counting/BlockCountingChunkSection.java -@@ -1,11 +1,11 @@ - package ca.spottedleaf.moonrise.patches.block_counting; - --import ca.spottedleaf.moonrise.common.list.IBlockDataList; -+import ca.spottedleaf.moonrise.common.list.ShortList; - - public interface BlockCountingChunkSection { - -- public int moonrise$getSpecialCollidingBlocks(); -+ public boolean moonrise$hasSpecialCollidingBlocks(); - -- public IBlockDataList moonrise$getTickingBlockList(); -+ public ShortList moonrise$getTickingBlockList(); - - } -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccess.java b/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccess.java -new file mode 100644 -index 0000000000000000000000000000000000000000..89e75b454695e174c5619104eeb15eb923a2d9a7 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccess.java -@@ -0,0 +1,12 @@ -+package ca.spottedleaf.moonrise.patches.blockstate_propertyaccess; -+ -+public interface PropertyAccess { -+ -+ public int moonrise$getId(); -+ -+ public int moonrise$getIdFor(final T value); -+ -+ public T moonrise$getById(final int id); -+ -+ public void moonrise$setById(final T[] values); -+} -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccessStateHolder.java b/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccessStateHolder.java -new file mode 100644 -index 0000000000000000000000000000000000000000..01da52b9e8a786824f199a057b62ce0431ecbc43 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccessStateHolder.java -@@ -0,0 +1,7 @@ -+package ca.spottedleaf.moonrise.patches.blockstate_propertyaccess; -+ -+public interface PropertyAccessStateHolder { -+ -+ public long moonrise$getTableIndex(); -+ -+} -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/util/ZeroCollidingReferenceStateTable.java b/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/util/ZeroCollidingReferenceStateTable.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b5335a2a8cb5dc7637c7112c8f7193389d726489 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/util/ZeroCollidingReferenceStateTable.java -@@ -0,0 +1,230 @@ -+package ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.util; -+ -+import ca.spottedleaf.concurrentutil.util.IntegerUtil; -+import ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.PropertyAccess; -+import ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.PropertyAccessStateHolder; -+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -+import it.unimi.dsi.fastutil.objects.AbstractObjectSet; -+import it.unimi.dsi.fastutil.objects.AbstractReference2ObjectMap; -+import it.unimi.dsi.fastutil.objects.ObjectIterator; -+import it.unimi.dsi.fastutil.objects.ObjectSet; -+import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; -+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; -+import java.util.ArrayList; -+import java.util.Collection; -+import java.util.Collections; -+import java.util.Iterator; -+import java.util.List; -+import java.util.Map; -+import net.minecraft.world.level.block.state.StateHolder; -+import net.minecraft.world.level.block.state.properties.Property; -+ -+public final class ZeroCollidingReferenceStateTable { -+ -+ private final Int2ObjectOpenHashMap propertyToIndexer; -+ private S[] lookup; -+ private final Collection> properties; -+ -+ public ZeroCollidingReferenceStateTable(final Collection> properties) { -+ this.propertyToIndexer = new Int2ObjectOpenHashMap<>(properties.size()); -+ this.properties = new ReferenceOpenHashSet<>(properties); -+ -+ final List> sortedProperties = new ArrayList<>(properties); -+ -+ // important that each table sees the same property order given the same _set_ of properties, -+ // as each table will calculate the index for the block state -+ sortedProperties.sort((final Property p1, final Property p2) -> { -+ return Integer.compare( -+ ((PropertyAccess)p1).moonrise$getId(), -+ ((PropertyAccess)p2).moonrise$getId() -+ ); -+ }); -+ -+ int currentMultiple = 1; -+ for (final Property property : sortedProperties) { -+ final int totalValues = property.getPossibleValues().size(); -+ -+ this.propertyToIndexer.put( -+ ((PropertyAccess)property).moonrise$getId(), -+ new Indexer( -+ totalValues, -+ currentMultiple, -+ IntegerUtil.getUnsignedDivisorMagic((long)currentMultiple, 32), -+ IntegerUtil.getUnsignedDivisorMagic((long)totalValues, 32) -+ ) -+ ); -+ -+ currentMultiple *= totalValues; -+ } -+ } -+ -+ public > boolean hasProperty(final Property property) { -+ return this.propertyToIndexer.containsKey(((PropertyAccess)property).moonrise$getId()); -+ } -+ -+ public long getIndex(final StateHolder stateHolder) { -+ long ret = 0L; -+ -+ for (final Map.Entry, Comparable> entry : stateHolder.getValues().entrySet()) { -+ final Property property = entry.getKey(); -+ final Comparable value = entry.getValue(); -+ -+ final Indexer indexer = this.propertyToIndexer.get(((PropertyAccess)property).moonrise$getId()); -+ -+ ret += (((PropertyAccess)property).moonrise$getIdFor(value)) * indexer.multiple; -+ } -+ -+ return ret; -+ } -+ -+ public boolean isLoaded() { -+ return this.lookup != null; -+ } -+ -+ public void loadInTable(final Map, Comparable>, S> universe) { -+ if (this.lookup != null) { -+ throw new IllegalStateException(); -+ } -+ -+ this.lookup = (S[])new StateHolder[universe.size()]; -+ -+ for (final Map.Entry, Comparable>, S> entry : universe.entrySet()) { -+ final S value = entry.getValue(); -+ if (value == null) { -+ continue; -+ } -+ this.lookup[(int)((PropertyAccessStateHolder)(StateHolder)value).moonrise$getTableIndex()] = value; -+ } -+ -+ for (final S value : this.lookup) { -+ if (value == null) { -+ throw new IllegalStateException(); -+ } -+ } -+ } -+ -+ public > T get(final long index, final Property property) { -+ final Indexer indexer = this.propertyToIndexer.get(((PropertyAccess)property).moonrise$getId()); -+ if (indexer == null) { -+ return null; -+ } -+ -+ final long divided = (index * indexer.multipleDivMagic) >>> 32; -+ final long modded = (((divided * indexer.modMagic) & 0xFFFFFFFFL) * indexer.totalValues) >>> 32; -+ // equiv to: divided = index / multiple -+ // modded = divided % totalValues -+ -+ return ((PropertyAccess)property).moonrise$getById((int)modded); -+ } -+ -+ public > S set(final long index, final Property property, final T with) { -+ final int newValueId = ((PropertyAccess)property).moonrise$getIdFor(with); -+ if (newValueId < 0) { -+ return null; -+ } -+ -+ final Indexer indexer = this.propertyToIndexer.get(((PropertyAccess)property).moonrise$getId()); -+ if (indexer == null) { -+ return null; -+ } -+ -+ final long divided = (index * indexer.multipleDivMagic) >>> 32; -+ final long modded = (((divided * indexer.modMagic) & 0xFFFFFFFFL) * indexer.totalValues) >>> 32; -+ // equiv to: divided = index / multiple -+ // modded = divided % totalValues -+ -+ // subtract out the old value, add in the new -+ final long newIndex = (((long)newValueId - modded) * indexer.multiple) + index; -+ -+ return this.lookup[(int)newIndex]; -+ } -+ -+ public > S trySet(final long index, final Property property, final T with, final S dfl) { -+ final Indexer indexer = this.propertyToIndexer.get(((PropertyAccess)property).moonrise$getId()); -+ if (indexer == null) { -+ return dfl; -+ } -+ -+ final int newValueId = ((PropertyAccess)property).moonrise$getIdFor(with); -+ if (newValueId < 0) { -+ return null; -+ } -+ -+ final long divided = (index * indexer.multipleDivMagic) >>> 32; -+ final long modded = (((divided * indexer.modMagic) & 0xFFFFFFFFL) * indexer.totalValues) >>> 32; -+ // equiv to: divided = index / multiple -+ // modded = divided % totalValues -+ -+ // subtract out the old value, add in the new -+ final long newIndex = (((long)newValueId - modded) * indexer.multiple) + index; -+ -+ return this.lookup[(int)newIndex]; -+ } -+ -+ public Collection> getProperties() { -+ return Collections.unmodifiableCollection(this.properties); -+ } -+ -+ public Map, Comparable> getMapView(final long stateIndex) { -+ return new MapView(stateIndex); -+ } -+ -+ private static final record Indexer( -+ int totalValues, int multiple, long multipleDivMagic, long modMagic -+ ) {} -+ -+ private class MapView extends AbstractReference2ObjectMap, Comparable> { -+ private final long stateIndex; -+ private EntrySet entrySet; -+ -+ MapView(final long stateIndex) { -+ this.stateIndex = stateIndex; -+ } -+ -+ @Override -+ public boolean containsKey(final Object key) { -+ return key instanceof Property prop && ZeroCollidingReferenceStateTable.this.hasProperty(prop); -+ } -+ -+ @Override -+ public int size() { -+ return ZeroCollidingReferenceStateTable.this.properties.size(); -+ } -+ -+ @Override -+ public ObjectSet, Comparable>> reference2ObjectEntrySet() { -+ if (this.entrySet == null) -+ this.entrySet = new EntrySet(); -+ return this.entrySet; -+ } -+ -+ @Override -+ public Comparable get(final Object key) { -+ return key instanceof Property prop ? ZeroCollidingReferenceStateTable.this.get(this.stateIndex, prop) : null; -+ } -+ -+ class EntrySet extends AbstractObjectSet, Comparable>> { -+ @Override -+ public ObjectIterator, Comparable>> iterator() { -+ final Iterator> propIterator = ZeroCollidingReferenceStateTable.this.properties.iterator(); -+ return new ObjectIterator<>() { -+ @Override -+ public boolean hasNext() { -+ return propIterator.hasNext(); -+ } -+ -+ @Override -+ public Entry, Comparable> next() { -+ Property prop = propIterator.next(); -+ return new AbstractReference2ObjectMap.BasicEntry<>(prop, ZeroCollidingReferenceStateTable.this.get(MapView.this.stateIndex, prop)); -+ } -+ }; -+ } -+ -+ @Override -+ public int size() { -+ return ZeroCollidingReferenceStateTable.this.properties.size(); -+ } -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ChunkSystemConverters.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ChunkSystemConverters.java -index 49160a30b8e19e5c5ada811fbcae2a05959524f3..44bb25554634af2ec0b2e9b3d9231304d5dff034 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ChunkSystemConverters.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ChunkSystemConverters.java -@@ -1,10 +1,11 @@ - package ca.spottedleaf.moonrise.patches.chunk_system; - -+import ca.spottedleaf.moonrise.common.PlatformHooks; - import net.minecraft.SharedConstants; - import net.minecraft.nbt.CompoundTag; - import net.minecraft.nbt.Tag; - import net.minecraft.server.level.ServerLevel; --import net.minecraft.util.datafix.DataFixTypes; -+import net.minecraft.util.datafix.fixes.References; - - public final class ChunkSystemConverters { - -@@ -25,13 +26,13 @@ public final class ChunkSystemConverters { - public static CompoundTag convertPoiCompoundTag(final CompoundTag data, final ServerLevel world) { - final int dataVersion = getDataVersion(data, DEFAULT_POI_DATA_VERSION); - -- return DataFixTypes.POI_CHUNK.update(world.getServer().getFixerUpper(), data, dataVersion, getCurrentVersion()); -+ return PlatformHooks.get().convertNBT(References.POI_CHUNK, world.getServer().getFixerUpper(), data, dataVersion, getCurrentVersion()); - } - - public static CompoundTag convertEntityChunkCompoundTag(final CompoundTag data, final ServerLevel world) { - final int dataVersion = getDataVersion(data, DEFAULT_ENTITY_CHUNK_DATA_VERSION); - -- return DataFixTypes.ENTITY_CHUNK.update(world.getServer().getFixerUpper(), data, dataVersion, getCurrentVersion()); -+ return PlatformHooks.get().convertNBT(References.ENTITY_CHUNK, world.getServer().getFixerUpper(), data, dataVersion, getCurrentVersion()); - } - - private ChunkSystemConverters() {} -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ChunkSystemFeatures.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ChunkSystemFeatures.java -deleted file mode 100644 -index 67f6dd9a4855611cfe242c2e37e90f6d27d4c823..0000000000000000000000000000000000000000 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ChunkSystemFeatures.java -+++ /dev/null -@@ -1,36 +0,0 @@ --package ca.spottedleaf.moonrise.patches.chunk_system; -- --import ca.spottedleaf.moonrise.patches.chunk_system.async_save.AsyncChunkSaveData; --import net.minecraft.nbt.CompoundTag; --import net.minecraft.server.level.ServerLevel; --import net.minecraft.world.level.chunk.ChunkAccess; -- --public final class ChunkSystemFeatures { -- -- public static boolean supportsAsyncChunkSave() { -- // uncertain how to properly pass AsyncSaveData to ChunkSerializer#write -- // additionally, there may be mods hooking into the write() call which may not be thread-safe to call -- return true; -- } -- -- public static AsyncChunkSaveData getAsyncSaveData(final ServerLevel world, final ChunkAccess chunk) { -- return net.minecraft.world.level.chunk.storage.ChunkSerializer.getAsyncSaveData(world, chunk); -- } -- -- public static CompoundTag saveChunkAsync(final ServerLevel world, final ChunkAccess chunk, final AsyncChunkSaveData asyncSaveData) { -- return net.minecraft.world.level.chunk.storage.ChunkSerializer.saveChunk(world, chunk, asyncSaveData); -- } -- -- public static boolean forceNoSave(final ChunkAccess chunk) { -- // support for CB chunk mustNotSave -- return chunk instanceof net.minecraft.world.level.chunk.LevelChunk levelChunk && levelChunk.mustNotSave; -- } -- -- public static boolean supportsAsyncChunkDeserialization() { -- // as it stands, the current problem with supporting this in Moonrise is that we are unsure that any mods -- // hooking into ChunkSerializer#read() are thread-safe to call -- return true; -- } -- -- private ChunkSystemFeatures() {} --} -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/async_save/AsyncChunkSaveData.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/async_save/AsyncChunkSaveData.java -deleted file mode 100644 -index becd1c6d54ed6c912aee3a9178a970e2751d3694..0000000000000000000000000000000000000000 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/async_save/AsyncChunkSaveData.java -+++ /dev/null -@@ -1,11 +0,0 @@ --package ca.spottedleaf.moonrise.patches.chunk_system.async_save; -- --import net.minecraft.nbt.ListTag; --import net.minecraft.nbt.Tag; -- --public record AsyncChunkSaveData( -- Tag blockTickList, // non-null if we had to go to the server's tick list -- Tag fluidTickList, // non-null if we had to go to the server's tick list -- ListTag blockEntities, -- long worldTime --) {} -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/entity/ChunkSystemEntity.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/entity/ChunkSystemEntity.java -index 2c279854bdf214538380fa354e4298ec4bd9ac4e..c7da23900228aab3a5673eb5adfada5091140319 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/entity/ChunkSystemEntity.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/entity/ChunkSystemEntity.java -@@ -1,5 +1,6 @@ - package ca.spottedleaf.moonrise.patches.chunk_system.entity; - -+import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData; - import net.minecraft.server.level.FullChunkStatus; - import net.minecraft.world.entity.Entity; - import net.minecraft.world.entity.monster.Shulker; -@@ -19,6 +20,10 @@ public interface ChunkSystemEntity { - - public void moonrise$setChunkStatus(final FullChunkStatus status); - -+ public ChunkData moonrise$getChunkData(); -+ -+ public void moonrise$setChunkData(final ChunkData chunkData); -+ - public int moonrise$getSectionX(); - - public void moonrise$setSectionX(final int x); -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java -index 73df26b27146bbad2106d57b22dd3c792ed3dd1d..a814512fcfb85312474ae2c2c21443843bf57831 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java -@@ -1,5 +1,6 @@ - package ca.spottedleaf.moonrise.patches.chunk_system.io; - -+import net.minecraft.nbt.CompoundTag; - import net.minecraft.world.level.chunk.storage.RegionFile; - import java.io.IOException; - -@@ -11,4 +12,20 @@ public interface ChunkSystemRegionFileStorage { - - public RegionFile moonrise$getRegionFileIfExists(final int chunkX, final int chunkZ) throws IOException; - -+ public MoonriseRegionFileIO.RegionDataController.WriteData moonrise$startWrite( -+ final int chunkX, final int chunkZ, final CompoundTag compound -+ ) throws IOException; -+ -+ public void moonrise$finishWrite( -+ final int chunkX, final int chunkZ, final MoonriseRegionFileIO.RegionDataController.WriteData writeData -+ ) throws IOException; -+ -+ public MoonriseRegionFileIO.RegionDataController.ReadData moonrise$readData( -+ final int chunkX, final int chunkZ -+ ) throws IOException; -+ -+ // if the return value is null, then the caller needs to re-try with a new call to readData() -+ public CompoundTag moonrise$finishRead( -+ final int chunkX, final int chunkZ, final MoonriseRegionFileIO.RegionDataController.ReadData readData -+ ) throws IOException; - } -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java -new file mode 100644 -index 0000000000000000000000000000000000000000..99f6f3e58b11b8967e6f1c3391c190d9a860ab7f ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java -@@ -0,0 +1,1707 @@ -+package ca.spottedleaf.moonrise.patches.chunk_system.io; -+ -+import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; -+import ca.spottedleaf.concurrentutil.completable.CallbackCompletable; -+import ca.spottedleaf.concurrentutil.completable.Completable; -+import ca.spottedleaf.concurrentutil.executor.Cancellable; -+import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.executor.queue.PrioritisedTaskQueue; -+import ca.spottedleaf.concurrentutil.function.BiLong1Function; -+import ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable; -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import ca.spottedleaf.concurrentutil.util.Priority; -+import ca.spottedleaf.moonrise.common.util.CoordinateUtils; -+import ca.spottedleaf.moonrise.common.util.TickThread; -+import ca.spottedleaf.moonrise.common.util.WorldUtil; -+import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel; -+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.level.chunk.storage.RegionFile; -+import net.minecraft.world.level.chunk.storage.RegionFileStorage; -+import org.slf4j.Logger; -+import org.slf4j.LoggerFactory; -+import java.io.DataInputStream; -+import java.io.DataOutputStream; -+import java.io.IOException; -+import java.lang.invoke.VarHandle; -+import java.util.concurrent.CompletableFuture; -+import java.util.concurrent.CompletionException; -+import java.util.concurrent.atomic.AtomicInteger; -+import java.util.concurrent.atomic.AtomicLong; -+import java.util.function.BiConsumer; -+import java.util.function.Consumer; -+ -+public final class MoonriseRegionFileIO { -+ -+ private static final int REGION_FILE_SHIFT = 5; -+ private static final Logger LOGGER = LoggerFactory.getLogger(MoonriseRegionFileIO.class); -+ -+ /** -+ * The types of RegionFiles controlled by the I/O thread(s). -+ */ -+ public static enum RegionFileType { -+ CHUNK_DATA, -+ POI_DATA, -+ ENTITY_DATA; -+ } -+ -+ public static RegionDataController getControllerFor(final ServerLevel world, final RegionFileType type) { -+ switch (type) { -+ case CHUNK_DATA: -+ return ((ChunkSystemServerLevel)world).moonrise$getChunkDataController(); -+ case POI_DATA: -+ return ((ChunkSystemServerLevel)world).moonrise$getPoiChunkDataController(); -+ case ENTITY_DATA: -+ return ((ChunkSystemServerLevel)world).moonrise$getEntityChunkDataController(); -+ default: -+ throw new IllegalStateException("Unknown controller type " + type); -+ } -+ } -+ -+ private static final RegionFileType[] CACHED_REGIONFILE_TYPES = RegionFileType.values(); -+ -+ /** -+ * Collects RegionFile data for a certain chunk. -+ */ -+ public static final class RegionFileData { -+ -+ private final boolean[] hasResult = new boolean[CACHED_REGIONFILE_TYPES.length]; -+ private final CompoundTag[] data = new CompoundTag[CACHED_REGIONFILE_TYPES.length]; -+ private final Throwable[] throwables = new Throwable[CACHED_REGIONFILE_TYPES.length]; -+ -+ /** -+ * Sets the result associated with the specified RegionFile type. Note that -+ * results can only be set once per RegionFile type. -+ * -+ * @param type The RegionFile type. -+ * @param data The result to set. -+ */ -+ public void setData(final MoonriseRegionFileIO.RegionFileType type, final CompoundTag data) { -+ final int index = type.ordinal(); -+ -+ if (this.hasResult[index]) { -+ throw new IllegalArgumentException("Result already exists for type " + type); -+ } -+ this.hasResult[index] = true; -+ this.data[index] = data; -+ } -+ -+ /** -+ * Sets the result associated with the specified RegionFile type. Note that -+ * results can only be set once per RegionFile type. -+ * -+ * @param type The RegionFile type. -+ * @param throwable The result to set. -+ */ -+ public void setThrowable(final MoonriseRegionFileIO.RegionFileType type, final Throwable throwable) { -+ final int index = type.ordinal(); -+ -+ if (this.hasResult[index]) { -+ throw new IllegalArgumentException("Result already exists for type " + type); -+ } -+ this.hasResult[index] = true; -+ this.throwables[index] = throwable; -+ } -+ -+ /** -+ * Returns whether there is a result for the specified RegionFile type. -+ * -+ * @param type Specified RegionFile type. -+ * -+ * @return Whether a result exists for {@code type}. -+ */ -+ public boolean hasResult(final MoonriseRegionFileIO.RegionFileType type) { -+ return this.hasResult[type.ordinal()]; -+ } -+ -+ /** -+ * Returns the data result for the RegionFile type. -+ * -+ * @param type Specified RegionFile type. -+ * -+ * @throws IllegalArgumentException If the result has not been set for {@code type}. -+ * @return The data result for the specified type. If the result is a {@code Throwable}, -+ * then returns {@code null}. -+ */ -+ public CompoundTag getData(final MoonriseRegionFileIO.RegionFileType type) { -+ final int index = type.ordinal(); -+ -+ if (!this.hasResult[index]) { -+ throw new IllegalArgumentException("Result does not exist for type " + type); -+ } -+ -+ return this.data[index]; -+ } -+ -+ /** -+ * Returns the throwable result for the RegionFile type. -+ * -+ * @param type Specified RegionFile type. -+ * -+ * @throws IllegalArgumentException If the result has not been set for {@code type}. -+ * @return The throwable result for the specified type. If the result is an {@code CompoundTag}, -+ * then returns {@code null}. -+ */ -+ public Throwable getThrowable(final MoonriseRegionFileIO.RegionFileType type) { -+ final int index = type.ordinal(); -+ -+ if (!this.hasResult[index]) { -+ throw new IllegalArgumentException("Result does not exist for type " + type); -+ } -+ -+ return this.throwables[index]; -+ } -+ } -+ -+ public static void flushRegionStorages(final ServerLevel world) throws IOException { -+ for (final RegionFileType type : CACHED_REGIONFILE_TYPES) { -+ flushRegionStorages(world, type); -+ } -+ } -+ -+ public static void flushRegionStorages(final ServerLevel world, final RegionFileType type) throws IOException { -+ getControllerFor(world, type).getCache().flush(); -+ } -+ -+ public static void flush(final MinecraftServer server) { -+ for (final ServerLevel world : server.getAllLevels()) { -+ flush(world); -+ } -+ } -+ -+ public static void flush(final ServerLevel world) { -+ for (final RegionFileType regionFileType : CACHED_REGIONFILE_TYPES) { -+ flush(world, regionFileType); -+ } -+ } -+ -+ public static void flush(final ServerLevel world, final RegionFileType type) { -+ final RegionDataController taskController = getControllerFor(world, type); -+ -+ long failures = 1L; // start at 0.13ms -+ -+ while (taskController.hasTasks()) { -+ Thread.yield(); -+ failures = ConcurrentUtil.linearLongBackoff(failures, 125_000L, 5_000_000L); // 125us, 5ms -+ } -+ } -+ -+ public static void partialFlush(final ServerLevel world, final int tasksRemaining) { -+ for (long failures = 1L;;) { // start at 0.13ms -+ long totalTasks = 0L; -+ for (final RegionFileType regionFileType : CACHED_REGIONFILE_TYPES) { -+ totalTasks += getControllerFor(world, regionFileType).getTotalWorkingTasks(); -+ } -+ -+ if (totalTasks > (long)tasksRemaining) { -+ Thread.yield(); -+ failures = ConcurrentUtil.linearLongBackoff(failures, 125_000L, 5_000_000L); // 125us, 5ms -+ } else { -+ return; -+ } -+ } -+ } -+ -+ /** -+ * Returns the priority associated with blocking I/O based on the current thread. The goal is to avoid -+ * dumb plugins from taking away priority from threads we consider crucial. -+ * @return The priroity to use with blocking I/O on the current thread. -+ */ -+ public static Priority getIOBlockingPriorityForCurrentThread() { -+ if (TickThread.isTickThread()) { -+ return Priority.BLOCKING; -+ } -+ return Priority.HIGHEST; -+ } -+ -+ /** -+ * Returns the priority for the specified regionfile type for the specified chunk. -+ * @param world Specified world. -+ * @param chunkX Specified chunk x. -+ * @param chunkZ Specified chunk z. -+ * @param type Specified regionfile type. -+ * @return The priority for the chunk -+ */ -+ public static Priority getPriority(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type) { -+ final RegionDataController taskController = getControllerFor(world, type); -+ final ChunkIOTask task = taskController.chunkTasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ -+ if (task == null) { -+ return Priority.COMPLETING; -+ } -+ -+ return task.getPriority(); -+ } -+ -+ /** -+ * Sets the priority for all regionfile types for the specified chunk. Note that great care should -+ * be taken using this method, as there can be multiple tasks tied to the same chunk that want different -+ * priorities. -+ * -+ * @param world Specified world. -+ * @param chunkX Specified chunk x. -+ * @param chunkZ Specified chunk z. -+ * @param priority New priority. -+ * -+ * @see #raisePriority(ServerLevel, int, int, Priority) -+ * @see #raisePriority(ServerLevel, int, int, RegionFileType, Priority) -+ * @see #lowerPriority(ServerLevel, int, int, Priority) -+ * @see #lowerPriority(ServerLevel, int, int, RegionFileType, Priority) -+ */ -+ public static void setPriority(final ServerLevel world, final int chunkX, final int chunkZ, -+ final Priority priority) { -+ for (final RegionFileType type : CACHED_REGIONFILE_TYPES) { -+ MoonriseRegionFileIO.setPriority(world, chunkX, chunkZ, type, priority); -+ } -+ } -+ -+ /** -+ * Sets the priority for the specified regionfile type for the specified chunk. Note that great care should -+ * be taken using this method, as there can be multiple tasks tied to the same chunk that want different -+ * priorities. -+ * -+ * @param world Specified world. -+ * @param chunkX Specified chunk x. -+ * @param chunkZ Specified chunk z. -+ * @param type Specified regionfile type. -+ * @param priority New priority. -+ * -+ * @see #raisePriority(ServerLevel, int, int, Priority) -+ * @see #raisePriority(ServerLevel, int, int, RegionFileType, Priority) -+ * @see #lowerPriority(ServerLevel, int, int, Priority) -+ * @see #lowerPriority(ServerLevel, int, int, RegionFileType, Priority) -+ */ -+ public static void setPriority(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type, -+ final Priority priority) { -+ final RegionDataController taskController = getControllerFor(world, type); -+ final ChunkIOTask task = taskController.chunkTasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ -+ if (task != null) { -+ task.setPriority(priority); -+ } -+ } -+ -+ /** -+ * Raises the priority for all regionfile types for the specified chunk. -+ * -+ * @param world Specified world. -+ * @param chunkX Specified chunk x. -+ * @param chunkZ Specified chunk z. -+ * @param priority New priority. -+ * -+ * @see #setPriority(ServerLevel, int, int, Priority) -+ * @see #setPriority(ServerLevel, int, int, RegionFileType, Priority) -+ * @see #lowerPriority(ServerLevel, int, int, Priority) -+ * @see #lowerPriority(ServerLevel, int, int, RegionFileType, Priority) -+ */ -+ public static void raisePriority(final ServerLevel world, final int chunkX, final int chunkZ, -+ final Priority priority) { -+ for (final RegionFileType type : CACHED_REGIONFILE_TYPES) { -+ MoonriseRegionFileIO.raisePriority(world, chunkX, chunkZ, type, priority); -+ } -+ } -+ -+ /** -+ * Raises the priority for the specified regionfile type for the specified chunk. -+ * -+ * @param world Specified world. -+ * @param chunkX Specified chunk x. -+ * @param chunkZ Specified chunk z. -+ * @param type Specified regionfile type. -+ * @param priority New priority. -+ * -+ * @see #setPriority(ServerLevel, int, int, Priority) -+ * @see #setPriority(ServerLevel, int, int, RegionFileType, Priority) -+ * @see #lowerPriority(ServerLevel, int, int, Priority) -+ * @see #lowerPriority(ServerLevel, int, int, RegionFileType, Priority) -+ */ -+ public static void raisePriority(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type, -+ final Priority priority) { -+ final RegionDataController taskController = getControllerFor(world, type); -+ final ChunkIOTask task = taskController.chunkTasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ -+ if (task != null) { -+ task.raisePriority(priority); -+ } -+ } -+ -+ /** -+ * Lowers the priority for all regionfile types for the specified chunk. -+ * -+ * @param world Specified world. -+ * @param chunkX Specified chunk x. -+ * @param chunkZ Specified chunk z. -+ * @param priority New priority. -+ * -+ * @see #raisePriority(ServerLevel, int, int, Priority) -+ * @see #raisePriority(ServerLevel, int, int, RegionFileType, Priority) -+ * @see #setPriority(ServerLevel, int, int, Priority) -+ * @see #setPriority(ServerLevel, int, int, RegionFileType, Priority) -+ */ -+ public static void lowerPriority(final ServerLevel world, final int chunkX, final int chunkZ, -+ final Priority priority) { -+ for (final RegionFileType type : CACHED_REGIONFILE_TYPES) { -+ MoonriseRegionFileIO.lowerPriority(world, chunkX, chunkZ, type, priority); -+ } -+ } -+ -+ /** -+ * Lowers the priority for the specified regionfile type for the specified chunk. -+ * -+ * @param world Specified world. -+ * @param chunkX Specified chunk x. -+ * @param chunkZ Specified chunk z. -+ * @param type Specified regionfile type. -+ * @param priority New priority. -+ * -+ * @see #raisePriority(ServerLevel, int, int, Priority) -+ * @see #raisePriority(ServerLevel, int, int, RegionFileType, Priority) -+ * @see #setPriority(ServerLevel, int, int, Priority) -+ * @see #setPriority(ServerLevel, int, int, RegionFileType, Priority) -+ */ -+ public static void lowerPriority(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type, -+ final Priority priority) { -+ final RegionDataController taskController = getControllerFor(world, type); -+ final ChunkIOTask task = taskController.chunkTasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ -+ if (task != null) { -+ task.lowerPriority(priority); -+ } -+ } -+ -+ /** -+ * Schedules the chunk data to be written asynchronously. -+ *

    -+ * Impl notes: -+ *

    -+ *
  • -+ * This function presumes a chunk load for the coordinates is not called during this function (anytime after is OK). This means -+ * saves must be scheduled before a chunk is unloaded. -+ *
  • -+ *
  • -+ * Writes may be called concurrently, although only the "later" write will go through. -+ *
  • -+ * -+ * @param world Chunk's world -+ * @param chunkX Chunk's x coordinate -+ * @param chunkZ Chunk's z coordinate -+ * @param data Chunk's data -+ * @param type The regionfile type to write to. -+ * -+ * @throws IllegalStateException If the file io thread has shutdown. -+ */ -+ public static void scheduleSave(final ServerLevel world, final int chunkX, final int chunkZ, final CompoundTag data, -+ final RegionFileType type) { -+ MoonriseRegionFileIO.scheduleSave(world, chunkX, chunkZ, data, type, Priority.NORMAL); -+ } -+ -+ /** -+ * Schedules the chunk data to be written asynchronously. -+ *

    -+ * Impl notes: -+ *

    -+ *
  • -+ * This function presumes a chunk load for the coordinates is not called during this function (anytime after is OK). This means -+ * saves must be scheduled before a chunk is unloaded. -+ *
  • -+ *
  • -+ * Writes may be called concurrently, although only the "later" write will go through. -+ *
  • -+ * -+ * @param world Chunk's world -+ * @param chunkX Chunk's x coordinate -+ * @param chunkZ Chunk's z coordinate -+ * @param data Chunk's data -+ * @param type The regionfile type to write to. -+ * @param priority The minimum priority to schedule at. -+ * -+ * @throws IllegalStateException If the file io thread has shutdown. -+ */ -+ public static void scheduleSave(final ServerLevel world, final int chunkX, final int chunkZ, final CompoundTag data, -+ final RegionFileType type, final Priority priority) { -+ scheduleSave( -+ world, chunkX, chunkZ, -+ (final BiConsumer consumer) -> { -+ consumer.accept(data, null); -+ }, null, type, priority -+ ); -+ } -+ -+ /** -+ * Schedules the chunk data to be written asynchronously. -+ *

    -+ * Impl notes: -+ *

    -+ *
  • -+ * This function presumes a chunk load for the coordinates is not called during this function (anytime after is OK). This means -+ * saves must be scheduled before a chunk is unloaded. -+ *
  • -+ *
  • -+ * Writes may be called concurrently, although only the "later" write will go through. -+ *
  • -+ *
  • -+ * The specified write task, if not null, will have its priority controlled by the scheduler. -+ *
  • -+ * -+ * @param world Chunk's world -+ * @param chunkX Chunk's x coordinate -+ * @param chunkZ Chunk's z coordinate -+ * @param completable Chunk's pending data -+ * @param writeTask The task responsible for completing the pending chunk data -+ * @param type The regionfile type to write to. -+ * @param priority The minimum priority to schedule at. -+ * -+ * @throws IllegalStateException If the file io thread has shutdown. -+ */ -+ public static void scheduleSave(final ServerLevel world, final int chunkX, final int chunkZ, final CallbackCompletable completable, -+ final PrioritisedExecutor.PrioritisedTask writeTask, final RegionFileType type, final Priority priority) { -+ scheduleSave(world, chunkX, chunkZ, completable::addWaiter, writeTask, type, priority); -+ } -+ -+ /** -+ * Schedules the chunk data to be written asynchronously. -+ *

    -+ * Impl notes: -+ *

    -+ *
  • -+ * This function presumes a chunk load for the coordinates is not called during this function (anytime after is OK). This means -+ * saves must be scheduled before a chunk is unloaded. -+ *
  • -+ *
  • -+ * Writes may be called concurrently, although only the "later" write will go through. -+ *
  • -+ *
  • -+ * The specified write task, if not null, will have its priority controlled by the scheduler. -+ *
  • -+ * -+ * @param world Chunk's world -+ * @param chunkX Chunk's x coordinate -+ * @param chunkZ Chunk's z coordinate -+ * @param completable Chunk's pending data -+ * @param writeTask The task responsible for completing the pending chunk data -+ * @param type The regionfile type to write to. -+ * @param priority The minimum priority to schedule at. -+ * -+ * @throws IllegalStateException If the file io thread has shutdown. -+ */ -+ public static void scheduleSave(final ServerLevel world, final int chunkX, final int chunkZ, final Completable completable, -+ final PrioritisedExecutor.PrioritisedTask writeTask, final RegionFileType type, final Priority priority) { -+ scheduleSave(world, chunkX, chunkZ, completable::whenComplete, writeTask, type, priority); -+ } -+ -+ private static void scheduleSave(final ServerLevel world, final int chunkX, final int chunkZ, final Consumer> scheduler, -+ final PrioritisedExecutor.PrioritisedTask writeTask, final RegionFileType type, final Priority priority) { -+ final RegionDataController taskController = getControllerFor(world, type); -+ -+ final boolean[] created = new boolean[1]; -+ final ChunkIOTask.InProgressWrite write = new ChunkIOTask.InProgressWrite(writeTask); -+ final ChunkIOTask task = taskController.chunkTasks.compute(CoordinateUtils.getChunkKey(chunkX, chunkZ), -+ (final long keyInMap, final ChunkIOTask taskRunning) -> { -+ if (taskRunning == null || taskRunning.failedWrite) { -+ // no task is scheduled or the previous write failed - meaning we need to overwrite it -+ -+ // create task -+ final ChunkIOTask newTask = new ChunkIOTask( -+ world, taskController, chunkX, chunkZ, priority, new ChunkIOTask.InProgressRead() -+ ); -+ -+ newTask.pushPendingWrite(write); -+ -+ created[0] = true; -+ -+ return newTask; -+ } -+ -+ taskRunning.pushPendingWrite(write); -+ -+ return taskRunning; -+ } -+ ); -+ -+ write.schedule(task, scheduler); -+ -+ if (created[0]) { -+ taskController.startTask(task); -+ task.scheduleWriteCompress(); -+ } else { -+ task.raisePriority(priority); -+ } -+ } -+ -+ /** -+ * Schedules a load to be executed asynchronously. This task will load all regionfile types, and then call -+ * {@code onComplete}. This is a bulk load operation, see {@link #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean)} -+ * for single load. -+ *

    -+ * Impl notes: -+ *

    -+ *
  • -+ * The {@code onComplete} parameter may be completed during the execution of this function synchronously or it may -+ * be completed asynchronously on this file io thread. Interacting with the file IO thread in the completion of -+ * data is undefined behaviour, and can cause deadlock. -+ *
  • -+ * -+ * @param world Chunk's world -+ * @param chunkX Chunk's x coordinate -+ * @param chunkZ Chunk's z coordinate -+ * @param onComplete Consumer to execute once this task has completed -+ * @param intendingToBlock Whether the caller is intending to block on completion. This only affects the cost -+ * of this call. -+ * -+ * @return The {@link Cancellable} for this chunk load. Cancelling it will not affect other loads for the same chunk data. -+ * -+ * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean) -+ * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean, Priority) -+ * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, RegionFileType...) -+ * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, Priority, RegionFileType...) -+ */ -+ public static Cancellable loadAllChunkData(final ServerLevel world, final int chunkX, final int chunkZ, -+ final Consumer onComplete, final boolean intendingToBlock) { -+ return MoonriseRegionFileIO.loadAllChunkData(world, chunkX, chunkZ, onComplete, intendingToBlock, Priority.NORMAL); -+ } -+ -+ /** -+ * Schedules a load to be executed asynchronously. This task will load all regionfile types, and then call -+ * {@code onComplete}. This is a bulk load operation, see {@link #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean, Priority)} -+ * for single load. -+ *

    -+ * Impl notes: -+ *

    -+ *
  • -+ * The {@code onComplete} parameter may be completed during the execution of this function synchronously or it may -+ * be completed asynchronously on this file io thread. Interacting with the file IO thread in the completion of -+ * data is undefined behaviour, and can cause deadlock. -+ *
  • -+ * -+ * @param world Chunk's world -+ * @param chunkX Chunk's x coordinate -+ * @param chunkZ Chunk's z coordinate -+ * @param onComplete Consumer to execute once this task has completed -+ * @param intendingToBlock Whether the caller is intending to block on completion. This only affects the cost -+ * of this call. -+ * @param priority The minimum priority to load the data at. -+ * -+ * @return The {@link Cancellable} for this chunk load. Cancelling it will not affect other loads for the same chunk data. -+ * -+ * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean) -+ * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean, Priority) -+ * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, RegionFileType...) -+ * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, Priority, RegionFileType...) -+ */ -+ public static Cancellable loadAllChunkData(final ServerLevel world, final int chunkX, final int chunkZ, -+ final Consumer onComplete, final boolean intendingToBlock, -+ final Priority priority) { -+ return MoonriseRegionFileIO.loadChunkData(world, chunkX, chunkZ, onComplete, intendingToBlock, priority, CACHED_REGIONFILE_TYPES); -+ } -+ -+ /** -+ * Schedules a load to be executed asynchronously. This task will load data for the specified regionfile type(s), and -+ * then call {@code onComplete}. This is a bulk load operation, see {@link #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean)} -+ * for single load. -+ *

    -+ * Impl notes: -+ *

    -+ *
  • -+ * The {@code onComplete} parameter may be completed during the execution of this function synchronously or it may -+ * be completed asynchronously on this file io thread. Interacting with the file IO thread in the completion of -+ * data is undefined behaviour, and can cause deadlock. -+ *
  • -+ * -+ * @param world Chunk's world -+ * @param chunkX Chunk's x coordinate -+ * @param chunkZ Chunk's z coordinate -+ * @param onComplete Consumer to execute once this task has completed -+ * @param intendingToBlock Whether the caller is intending to block on completion. This only affects the cost -+ * of this call. -+ * @param types The regionfile type(s) to load. -+ * -+ * @return The {@link Cancellable} for this chunk load. Cancelling it will not affect other loads for the same chunk data. -+ * -+ * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean) -+ * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean, Priority) -+ * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean) -+ * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean, Priority) -+ */ -+ public static Cancellable loadChunkData(final ServerLevel world, final int chunkX, final int chunkZ, -+ final Consumer onComplete, final boolean intendingToBlock, -+ final RegionFileType... types) { -+ return MoonriseRegionFileIO.loadChunkData(world, chunkX, chunkZ, onComplete, intendingToBlock, Priority.NORMAL, types); -+ } -+ -+ /** -+ * Schedules a load to be executed asynchronously. This task will load data for the specified regionfile type(s), and -+ * then call {@code onComplete}. This is a bulk load operation, see {@link #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean, Priority)} -+ * for single load. -+ *

    -+ * Impl notes: -+ *

    -+ *
  • -+ * The {@code onComplete} parameter may be completed during the execution of this function synchronously or it may -+ * be completed asynchronously on this file io thread. Interacting with the file IO thread in the completion of -+ * data is undefined behaviour, and can cause deadlock. -+ *
  • -+ * -+ * @param world Chunk's world -+ * @param chunkX Chunk's x coordinate -+ * @param chunkZ Chunk's z coordinate -+ * @param onComplete Consumer to execute once this task has completed -+ * @param intendingToBlock Whether the caller is intending to block on completion. This only affects the cost -+ * of this call. -+ * @param types The regionfile type(s) to load. -+ * @param priority The minimum priority to load the data at. -+ * -+ * @return The {@link Cancellable} for this chunk load. Cancelling it will not affect other loads for the same chunk data. -+ * -+ * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean) -+ * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean, Priority) -+ * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean) -+ * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean, Priority) -+ */ -+ public static Cancellable loadChunkData(final ServerLevel world, final int chunkX, final int chunkZ, -+ final Consumer onComplete, final boolean intendingToBlock, -+ final Priority priority, final RegionFileType... types) { -+ if (types == null) { -+ throw new NullPointerException("Types cannot be null"); -+ } -+ if (types.length == 0) { -+ throw new IllegalArgumentException("Types cannot be empty"); -+ } -+ -+ final RegionFileData ret = new RegionFileData(); -+ -+ final Cancellable[] reads = new CancellableRead[types.length]; -+ final AtomicInteger completions = new AtomicInteger(); -+ final int expectedCompletions = types.length; -+ -+ for (int i = 0; i < expectedCompletions; ++i) { -+ final RegionFileType type = types[i]; -+ reads[i] = MoonriseRegionFileIO.loadDataAsync(world, chunkX, chunkZ, type, -+ (final CompoundTag data, final Throwable throwable) -> { -+ if (throwable != null) { -+ ret.setThrowable(type, throwable); -+ } else { -+ ret.setData(type, data); -+ } -+ -+ if (completions.incrementAndGet() == expectedCompletions) { -+ onComplete.accept(ret); -+ } -+ }, intendingToBlock, priority); -+ } -+ -+ return new CancellableReads(reads); -+ } -+ -+ /** -+ * Schedules a load to be executed asynchronously. This task will load the specified regionfile type, and then call -+ * {@code onComplete}. -+ *

    -+ * Impl notes: -+ *

    -+ *
  • -+ * The {@code onComplete} parameter may be completed during the execution of this function synchronously or it may -+ * be completed asynchronously on this file io thread. Interacting with the file IO thread in the completion of -+ * data is undefined behaviour, and can cause deadlock. -+ *
  • -+ * -+ * @param world Chunk's world -+ * @param chunkX Chunk's x coordinate -+ * @param chunkZ Chunk's z coordinate -+ * @param onComplete Consumer to execute once this task has completed -+ * @param intendingToBlock Whether the caller is intending to block on completion. This only affects the cost -+ * of this call. -+ * -+ * @return The {@link Cancellable} for this chunk load. Cancelling it will not affect other loads for the same chunk data. -+ * -+ * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, RegionFileType...) -+ * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, Priority, RegionFileType...) -+ * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean) -+ * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean, Priority) -+ */ -+ public static Cancellable loadDataAsync(final ServerLevel world, final int chunkX, final int chunkZ, -+ final RegionFileType type, final BiConsumer onComplete, -+ final boolean intendingToBlock) { -+ return MoonriseRegionFileIO.loadDataAsync(world, chunkX, chunkZ, type, onComplete, intendingToBlock, Priority.NORMAL); -+ } -+ -+ /** -+ * Schedules a load to be executed asynchronously. This task will load the specified regionfile type, and then call -+ * {@code onComplete}. -+ *

    -+ * Impl notes: -+ *

    -+ *
  • -+ * The {@code onComplete} parameter may be completed during the execution of this function synchronously or it may -+ * be completed asynchronously on this file io thread. Interacting with the file IO thread in the completion of -+ * data is undefined behaviour, and can cause deadlock. -+ *
  • -+ * -+ * @param world Chunk's world -+ * @param chunkX Chunk's x coordinate -+ * @param chunkZ Chunk's z coordinate -+ * @param onComplete Consumer to execute once this task has completed -+ * @param intendingToBlock Whether the caller is intending to block on completion. This only affects the cost -+ * of this call. -+ * @param priority Minimum priority to load the data at. -+ * -+ * @return The {@link Cancellable} for this chunk load. Cancelling it will not affect other loads for the same chunk data. -+ * -+ * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, RegionFileType...) -+ * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, Priority, RegionFileType...) -+ * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean) -+ * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean, Priority) -+ */ -+ public static Cancellable loadDataAsync(final ServerLevel world, final int chunkX, final int chunkZ, -+ final RegionFileType type, final BiConsumer onComplete, -+ final boolean intendingToBlock, final Priority priority) { -+ final RegionDataController taskController = getControllerFor(world, type); -+ -+ final ImmediateCallbackCompletion callbackInfo = new ImmediateCallbackCompletion(); -+ -+ final long key = CoordinateUtils.getChunkKey(chunkX, chunkZ); -+ final BiLong1Function compute = (final long keyInMap, final ChunkIOTask running) -> { -+ if (running == null) { -+ // not scheduled -+ -+ // set up task -+ final ChunkIOTask newTask = new ChunkIOTask( -+ world, taskController, chunkX, chunkZ, priority, new ChunkIOTask.InProgressRead() -+ ); -+ newTask.inProgressRead.addToAsyncWaiters(onComplete); -+ -+ callbackInfo.tasksNeedReadScheduling = true; -+ return newTask; -+ } -+ -+ final ChunkIOTask.InProgressWrite pendingWrite = running.inProgressWrite; -+ -+ if (pendingWrite == null) { -+ // need to add to waiters here, because the regionfile thread will use compute() to lock and check for cancellations -+ if (!running.inProgressRead.addToAsyncWaiters(onComplete)) { -+ callbackInfo.data = running.inProgressRead.value; -+ callbackInfo.throwable = running.inProgressRead.throwable; -+ callbackInfo.completeNow = true; -+ return running; -+ } -+ -+ callbackInfo.read = running.inProgressRead; -+ -+ return running; -+ } -+ -+ // at this stage we have to use the in progress write's data to avoid an order issue -+ -+ if (!pendingWrite.addToAsyncWaiters(onComplete)) { -+ // data is ready now -+ callbackInfo.data = pendingWrite.value; -+ callbackInfo.throwable = pendingWrite.throwable; -+ callbackInfo.completeNow = true; -+ return running; -+ } -+ -+ callbackInfo.write = pendingWrite; -+ -+ return running; -+ }; -+ -+ final ChunkIOTask ret = taskController.chunkTasks.compute(key, compute); -+ -+ // needs to be scheduled -+ if (callbackInfo.tasksNeedReadScheduling) { -+ taskController.startTask(ret); -+ ret.scheduleReadIO(); -+ } else if (callbackInfo.completeNow) { -+ try { -+ onComplete.accept(callbackInfo.data == null ? null : callbackInfo.data.copy(), callbackInfo.throwable); -+ } catch (final Throwable thr) { -+ LOGGER.error("Callback " + ConcurrentUtil.genericToString(onComplete) + " synchronously failed to handle chunk data for task " + ret.toString(), thr); -+ } -+ } else { -+ // we're waiting on a task we didn't schedule, so raise its priority to what we want -+ ret.raisePriority(priority); -+ } -+ -+ return new CancellableRead(onComplete, callbackInfo.read, callbackInfo.write); -+ } -+ -+ private static final class ImmediateCallbackCompletion { -+ -+ private CompoundTag data; -+ private Throwable throwable; -+ private boolean completeNow; -+ private boolean tasksNeedReadScheduling; -+ private ChunkIOTask.InProgressRead read; -+ private ChunkIOTask.InProgressWrite write; -+ -+ } -+ -+ /** -+ * Schedules a load task to be executed asynchronously, and blocks on that task. -+ * -+ * @param world Chunk's world -+ * @param chunkX Chunk's x coordinate -+ * @param chunkZ Chunk's z coordinate -+ * @param type Regionfile type -+ * @param priority Minimum priority to load the data at. -+ * -+ * @return The chunk data for the chunk. Note that a {@code null} result means the chunk or regionfile does not exist on disk. -+ * -+ * @throws IOException If the load fails for any reason -+ */ -+ public static CompoundTag loadData(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type, -+ final Priority priority) throws IOException { -+ final CompletableFuture ret = new CompletableFuture<>(); -+ -+ MoonriseRegionFileIO.loadDataAsync(world, chunkX, chunkZ, type, (final CompoundTag compound, final Throwable thr) -> { -+ if (thr != null) { -+ ret.completeExceptionally(thr); -+ } else { -+ ret.complete(compound); -+ } -+ }, true, priority); -+ -+ try { -+ return ret.join(); -+ } catch (final CompletionException ex) { -+ throw new IOException(ex); -+ } -+ } -+ -+ private static final class CancellableRead implements Cancellable { -+ -+ private BiConsumer callback; -+ private ChunkIOTask.InProgressRead read; -+ private ChunkIOTask.InProgressWrite write; -+ -+ private CancellableRead(final BiConsumer callback, -+ final ChunkIOTask.InProgressRead read, -+ final ChunkIOTask.InProgressWrite write) { -+ this.callback = callback; -+ this.read = read; -+ this.write = write; -+ } -+ -+ @Override -+ public boolean cancel() { -+ final BiConsumer callback = this.callback; -+ final ChunkIOTask.InProgressRead read = this.read; -+ final ChunkIOTask.InProgressWrite write = this.write; -+ -+ if (callback == null || (read == null && write == null)) { -+ return false; -+ } -+ -+ this.callback = null; -+ this.read = null; -+ this.write = null; -+ -+ if (read != null) { -+ return read.cancel(callback); -+ } -+ if (write != null) { -+ return write.cancel(callback); -+ } -+ -+ // unreachable -+ throw new InternalError(); -+ } -+ } -+ -+ private static final class CancellableReads implements Cancellable { -+ -+ private Cancellable[] reads; -+ private static final VarHandle READS_HANDLE = ConcurrentUtil.getVarHandle(CancellableReads.class, "reads", Cancellable[].class); -+ -+ private CancellableReads(final Cancellable[] reads) { -+ this.reads = reads; -+ } -+ -+ @Override -+ public boolean cancel() { -+ final Cancellable[] reads = (Cancellable[])READS_HANDLE.getAndSet((CancellableReads)this, (Cancellable[])null); -+ -+ if (reads == null) { -+ return false; -+ } -+ -+ boolean ret = false; -+ -+ for (final Cancellable read : reads) { -+ ret |= read.cancel(); -+ } -+ -+ return ret; -+ } -+ } -+ -+ private static final class ChunkIOTask { -+ -+ private final ServerLevel world; -+ private final RegionDataController regionDataController; -+ private final int chunkX; -+ private final int chunkZ; -+ private Priority priority; -+ private PrioritisedExecutor.PrioritisedTask currentTask; -+ -+ private final InProgressRead inProgressRead; -+ private volatile InProgressWrite inProgressWrite; -+ private final ReferenceOpenHashSet allPendingWrites = new ReferenceOpenHashSet<>(); -+ -+ private RegionDataController.ReadData readData; -+ private RegionDataController.WriteData writeData; -+ private boolean failedWrite; -+ -+ public ChunkIOTask(final ServerLevel world, final RegionDataController regionDataController, -+ final int chunkX, final int chunkZ, final Priority priority, final InProgressRead inProgressRead) { -+ this.world = world; -+ this.regionDataController = regionDataController; -+ this.chunkX = chunkX; -+ this.chunkZ = chunkZ; -+ this.priority = priority; -+ this.inProgressRead = inProgressRead; -+ } -+ -+ public Priority getPriority() { -+ synchronized (this) { -+ return this.priority; -+ } -+ } -+ -+ // must hold lock on this object -+ private void updatePriority(final Priority priority) { -+ this.priority = priority; -+ if (this.currentTask != null) { -+ this.currentTask.setPriority(priority); -+ } -+ for (final InProgressWrite write : this.allPendingWrites) { -+ if (write.writeTask != null) { -+ write.writeTask.setPriority(priority); -+ } -+ } -+ } -+ -+ public boolean setPriority(final Priority priority) { -+ synchronized (this) { -+ if (this.priority == priority) { -+ return false; -+ } -+ -+ this.updatePriority(priority); -+ -+ return true; -+ } -+ } -+ -+ public boolean raisePriority(final Priority priority) { -+ synchronized (this) { -+ if (this.priority.isHigherOrEqualPriority(priority)) { -+ return false; -+ } -+ -+ this.updatePriority(priority); -+ -+ return true; -+ } -+ } -+ -+ public boolean lowerPriority(final Priority priority) { -+ synchronized (this) { -+ if (this.priority.isLowerOrEqualPriority(priority)) { -+ return false; -+ } -+ -+ this.updatePriority(priority); -+ -+ return true; -+ } -+ } -+ -+ private void pushPendingWrite(final InProgressWrite write) { -+ this.inProgressWrite = write; -+ synchronized (this) { -+ this.allPendingWrites.add(write); -+ if (write.writeTask != null) { -+ write.writeTask.setPriority(this.priority); -+ } -+ } -+ } -+ -+ private void pendingWriteComplete(final InProgressWrite write) { -+ synchronized (this) { -+ this.allPendingWrites.remove(write); -+ } -+ } -+ -+ public void scheduleReadIO() { -+ final PrioritisedExecutor.PrioritisedTask task; -+ synchronized (this) { -+ task = this.regionDataController.ioScheduler.createTask(this.chunkX, this.chunkZ, this::performReadIO, this.priority); -+ this.currentTask = task; -+ } -+ task.queue(); -+ } -+ -+ private void performReadIO() { -+ final InProgressRead read = this.inProgressRead; -+ final long chunkKey = CoordinateUtils.getChunkKey(this.chunkX, this.chunkZ); -+ -+ final boolean[] canRead = new boolean[] { true }; -+ -+ if (read.hasNoWaiters()) { -+ // cancelled read? go to task controller to confirm -+ final ChunkIOTask inMap = this.regionDataController.chunkTasks.compute(chunkKey, (final long keyInMap, final ChunkIOTask valueInMap) -> { -+ if (valueInMap == null) { -+ throw new IllegalStateException("Write completed concurrently, expected this task: " + ChunkIOTask.this.toString() + ", report this!"); -+ } -+ if (valueInMap != ChunkIOTask.this) { -+ throw new IllegalStateException("Chunk task mismatch, expected this task: " + ChunkIOTask.this.toString() + ", got: " + valueInMap.toString() + ", report this!"); -+ } -+ -+ if (!read.hasNoWaiters()) { -+ return valueInMap; -+ } else { -+ canRead[0] = false; -+ } -+ -+ if (valueInMap.inProgressWrite != null) { -+ return valueInMap; -+ } -+ -+ return null; -+ }); -+ -+ if (inMap == null) { -+ this.regionDataController.endTask(this); -+ // read is cancelled - and no write pending, so we're done -+ return; -+ } -+ // if there is a write in progress, we don't actually have to worry about waiters gaining new entries - -+ // the readers will just use the in progress write, so the value in canRead is good to use without -+ // further synchronisation. -+ } -+ -+ if (canRead[0]) { -+ RegionDataController.ReadData readData = null; -+ Throwable throwable = null; -+ -+ try { -+ readData = this.regionDataController.readData(this.chunkX, this.chunkZ); -+ } catch (final Throwable thr) { -+ throwable = thr; -+ LOGGER.error("Failed to read chunk data for task: " + this.toString(), thr); -+ } -+ -+ if (throwable != null) { -+ this.finishRead(null, throwable); -+ } else { -+ switch (readData.result()) { -+ case NO_DATA: -+ case SYNC_READ: { -+ this.finishRead(readData.syncRead(), null); -+ break; -+ } -+ case HAS_DATA: { -+ this.readData = readData; -+ this.scheduleReadDecompress(); -+ // read will handle write scheduling -+ return; -+ } -+ default: { -+ throw new IllegalStateException("Unknown state: " + readData.result()); -+ } -+ } -+ } -+ } -+ -+ if (!this.tryAbortWrite()) { -+ this.scheduleWriteCompress(); -+ } -+ } -+ -+ private void scheduleReadDecompress() { -+ final PrioritisedExecutor.PrioritisedTask task; -+ synchronized (this) { -+ task = this.regionDataController.compressionExecutor.createTask(this::performReadDecompress, this.priority); -+ this.currentTask = task; -+ } -+ task.queue(); -+ } -+ -+ private void performReadDecompress() { -+ final RegionDataController.ReadData readData = this.readData; -+ this.readData = null; -+ -+ CompoundTag compoundTag = null; -+ Throwable throwable = null; -+ -+ try { -+ compoundTag = this.regionDataController.finishRead(this.chunkX, this.chunkZ, readData); -+ } catch (final Throwable thr) { -+ throwable = thr; -+ LOGGER.error("Failed to decompress chunk data for task: " + this.toString(), thr); -+ } -+ -+ if (compoundTag == null) { -+ // need to re-try from the start -+ this.scheduleReadIO(); -+ return; -+ } -+ -+ this.finishRead(compoundTag, throwable); -+ if (!this.tryAbortWrite()) { -+ this.scheduleWriteCompress(); -+ } -+ } -+ -+ private void finishRead(final CompoundTag compoundTag, final Throwable throwable) { -+ this.inProgressRead.complete(this, compoundTag, throwable); -+ } -+ -+ public void scheduleWriteCompress() { -+ final InProgressWrite inProgressWrite = this.inProgressWrite; -+ -+ final PrioritisedExecutor.PrioritisedTask task; -+ synchronized (this) { -+ task = this.regionDataController.compressionExecutor.createTask(() -> { -+ ChunkIOTask.this.performWriteCompress(inProgressWrite); -+ }, this.priority); -+ this.currentTask = task; -+ } -+ -+ inProgressWrite.addToWaiters(this, (final CompoundTag data, final Throwable throwable) -> { -+ task.queue(); -+ }); -+ } -+ -+ private boolean tryAbortWrite() { -+ final long chunkKey = CoordinateUtils.getChunkKey(this.chunkX, this.chunkZ); -+ if (this.inProgressWrite == null) { -+ final ChunkIOTask inMap = this.regionDataController.chunkTasks.compute(chunkKey, (final long keyInMap, final ChunkIOTask valueInMap) -> { -+ if (valueInMap == null) { -+ throw new IllegalStateException("Write completed concurrently, expected this task: " + ChunkIOTask.this.toString() + ", report this!"); -+ } -+ if (valueInMap != ChunkIOTask.this) { -+ throw new IllegalStateException("Chunk task mismatch, expected this task: " + ChunkIOTask.this.toString() + ", got: " + valueInMap.toString() + ", report this!"); -+ } -+ -+ if (valueInMap.inProgressWrite != null) { -+ return valueInMap; -+ } -+ -+ return null; -+ }); -+ -+ if (inMap == null) { -+ this.regionDataController.endTask(this); -+ return true; // set the task value to null, indicating we're done -+ } // else: inProgressWrite changed, so now we have something to write -+ } -+ -+ return false; -+ } -+ -+ private void performWriteCompress(final InProgressWrite inProgressWrite) { -+ final CompoundTag write = inProgressWrite.value; -+ if (!inProgressWrite.isComplete()) { -+ throw new IllegalStateException("Should be writable"); -+ } -+ -+ RegionDataController.WriteData writeData = null; -+ boolean failedWrite = false; -+ -+ try { -+ writeData = this.regionDataController.startWrite(this.chunkX, this.chunkZ, write); -+ } catch (final Throwable thr) { -+ // TODO implement this? -+ /*if (thr instanceof RegionFileStorage.RegionFileSizeException) { -+ final int maxSize = RegionFile.MAX_CHUNK_SIZE / (1024 * 1024); -+ LOGGER.error("Chunk at (" + this.chunkX + "," + this.chunkZ + ") in '" + WorldUtil.getWorldName(this.world) + "' exceeds max size of " + maxSize + "MiB, it has been deleted from disk."); -+ } else */ -+ { -+ failedWrite = thr instanceof IOException; -+ LOGGER.error("Failed to write chunk data for task: " + this.toString(), thr); -+ } -+ } -+ -+ if (writeData == null) { -+ // null if a throwable was encountered -+ -+ // we cannot continue to the I/O stage here, so try to complete -+ -+ if (this.tryCompleteWrite(inProgressWrite, failedWrite)) { -+ return; -+ } else { -+ // fetch new data and try again -+ this.scheduleWriteCompress(); -+ return; -+ } -+ } else { -+ // writeData != null && !failedWrite -+ // we can continue to I/O stage -+ this.writeData = writeData; -+ this.scheduleWriteIO(inProgressWrite); -+ return; -+ } -+ } -+ -+ private void scheduleWriteIO(final InProgressWrite inProgressWrite) { -+ final PrioritisedExecutor.PrioritisedTask task; -+ synchronized (this) { -+ task = this.regionDataController.ioScheduler.createTask(this.chunkX, this.chunkZ, () -> { -+ ChunkIOTask.this.runWriteIO(inProgressWrite); -+ }, this.priority); -+ this.currentTask = task; -+ } -+ task.queue(); -+ } -+ -+ private void runWriteIO(final InProgressWrite inProgressWrite) { -+ RegionDataController.WriteData writeData = this.writeData; -+ this.writeData = null; -+ -+ boolean failedWrite = false; -+ -+ try { -+ this.regionDataController.finishWrite(this.chunkX, this.chunkZ, writeData); -+ } catch (final Throwable thr) { -+ failedWrite = thr instanceof IOException; -+ LOGGER.error("Failed to write chunk data for task: " + this.toString(), thr); -+ } -+ -+ if (!this.tryCompleteWrite(inProgressWrite, failedWrite)) { -+ // fetch new data and try again -+ this.scheduleWriteCompress(); -+ } -+ return; -+ } -+ -+ private boolean tryCompleteWrite(final InProgressWrite written, final boolean failedWrite) { -+ final long chunkKey = CoordinateUtils.getChunkKey(this.chunkX, this.chunkZ); -+ -+ final boolean[] done = new boolean[] { false }; -+ -+ this.regionDataController.chunkTasks.compute(chunkKey, (final long keyInMap, final ChunkIOTask valueInMap) -> { -+ if (valueInMap == null) { -+ throw new IllegalStateException("Write completed concurrently, expected this task: " + ChunkIOTask.this.toString() + ", report this!"); -+ } -+ if (valueInMap != ChunkIOTask.this) { -+ throw new IllegalStateException("Chunk task mismatch, expected this task: " + ChunkIOTask.this.toString() + ", got: " + valueInMap.toString() + ", report this!"); -+ } -+ if (valueInMap.inProgressWrite == written) { -+ valueInMap.failedWrite = failedWrite; -+ done[0] = true; -+ // keep the data in map if we failed the write so we can try to prevent data loss -+ return failedWrite ? valueInMap : null; -+ } -+ // different data than expected, means we need to retry write -+ return valueInMap; -+ }); -+ -+ if (done[0]) { -+ this.regionDataController.endTask(this); -+ return true; -+ } -+ return false; -+ } -+ -+ @Override -+ public String toString() { -+ return "Task for world: '" + WorldUtil.getWorldName(this.world) + "' at (" + this.chunkX + "," -+ + this.chunkZ + ") type: " + this.regionDataController.type.name() + ", hash: " + this.hashCode(); -+ } -+ -+ private static final class InProgressRead { -+ -+ private static final Logger LOGGER = LoggerFactory.getLogger(InProgressRead.class); -+ -+ private CompoundTag value; -+ private Throwable throwable; -+ private final MultiThreadedQueue> callbacks = new MultiThreadedQueue<>(); -+ -+ public boolean hasNoWaiters() { -+ return this.callbacks.isEmpty(); -+ } -+ -+ public boolean addToAsyncWaiters(final BiConsumer callback) { -+ return this.callbacks.add(callback); -+ } -+ -+ public boolean cancel(final BiConsumer callback) { -+ return this.callbacks.remove(callback); -+ } -+ -+ public void complete(final ChunkIOTask task, final CompoundTag value, final Throwable throwable) { -+ this.value = value; -+ this.throwable = throwable; -+ -+ BiConsumer consumer; -+ while ((consumer = this.callbacks.pollOrBlockAdds()) != null) { -+ try { -+ consumer.accept(value == null ? null : value.copy(), throwable); -+ } catch (final Throwable thr) { -+ LOGGER.error("Callback " + ConcurrentUtil.genericToString(consumer) + " failed to handle chunk data (read) for task " + task.toString(), thr); -+ } -+ } -+ } -+ } -+ -+ private static final class InProgressWrite { -+ -+ private static final Logger LOGGER = LoggerFactory.getLogger(InProgressWrite.class); -+ -+ private CompoundTag value; -+ private Throwable throwable; -+ private volatile boolean complete; -+ private final MultiThreadedQueue> callbacks = new MultiThreadedQueue<>(); -+ -+ private final PrioritisedExecutor.PrioritisedTask writeTask; -+ -+ public InProgressWrite(final PrioritisedExecutor.PrioritisedTask writeTask) { -+ this.writeTask = writeTask; -+ } -+ -+ public boolean isComplete() { -+ return this.complete; -+ } -+ -+ public void schedule(final ChunkIOTask task, final Consumer> scheduler) { -+ scheduler.accept((final CompoundTag data, final Throwable throwable) -> { -+ InProgressWrite.this.complete(task, data, throwable); -+ }); -+ } -+ -+ public boolean addToAsyncWaiters(final BiConsumer callback) { -+ return this.callbacks.add(callback); -+ } -+ -+ public void addToWaiters(final ChunkIOTask task, final BiConsumer consumer) { -+ if (!this.callbacks.add(consumer)) { -+ this.syncAccept(task, consumer, this.value, this.throwable); -+ } -+ } -+ -+ private void syncAccept(final ChunkIOTask task, final BiConsumer consumer, final CompoundTag value, final Throwable throwable) { -+ try { -+ consumer.accept(value == null ? null : value.copy(), throwable); -+ } catch (final Throwable thr) { -+ LOGGER.error("Callback " + ConcurrentUtil.genericToString(consumer) + " failed to handle chunk data (write) for task " + task.toString(), thr); -+ } -+ } -+ -+ public void complete(final ChunkIOTask task, final CompoundTag value, final Throwable throwable) { -+ this.value = value; -+ this.throwable = throwable; -+ this.complete = true; -+ -+ task.pendingWriteComplete(this); -+ -+ BiConsumer consumer; -+ while ((consumer = this.callbacks.pollOrBlockAdds()) != null) { -+ this.syncAccept(task, consumer, value, throwable); -+ } -+ } -+ -+ public boolean cancel(final BiConsumer callback) { -+ return this.callbacks.remove(callback); -+ } -+ } -+ } -+ -+ public static abstract class RegionDataController { -+ -+ public final RegionFileType type; -+ private final PrioritisedExecutor compressionExecutor; -+ private final IOScheduler ioScheduler; -+ private final ConcurrentLong2ReferenceChainedHashTable chunkTasks = new ConcurrentLong2ReferenceChainedHashTable<>(); -+ -+ private final AtomicLong inProgressTasks = new AtomicLong(); -+ -+ public RegionDataController(final RegionFileType type, final PrioritisedExecutor ioExecutor, -+ final PrioritisedExecutor compressionExecutor) { -+ this.type = type; -+ this.compressionExecutor = compressionExecutor; -+ this.ioScheduler = new IOScheduler(ioExecutor); -+ } -+ -+ final void startTask(final ChunkIOTask task) { -+ this.inProgressTasks.getAndIncrement(); -+ } -+ -+ final void endTask(final ChunkIOTask task) { -+ this.inProgressTasks.getAndDecrement(); -+ } -+ -+ public boolean hasTasks() { -+ return this.inProgressTasks.get() != 0L; -+ } -+ -+ public long getTotalWorkingTasks() { -+ return this.inProgressTasks.get(); -+ } -+ -+ public abstract RegionFileStorage getCache(); -+ -+ public static record WriteData(CompoundTag input, WriteResult result, DataOutputStream output, IORunnable write) { -+ public static enum WriteResult { -+ WRITE, -+ DELETE; -+ } -+ } -+ -+ public abstract WriteData startWrite(final int chunkX, final int chunkZ, final CompoundTag compound) throws IOException; -+ -+ public abstract void finishWrite(final int chunkX, final int chunkZ, final WriteData writeData) throws IOException; -+ -+ public static record ReadData(ReadResult result, DataInputStream input, CompoundTag syncRead) { -+ public static enum ReadResult { -+ NO_DATA, -+ HAS_DATA, -+ SYNC_READ; -+ } -+ } -+ -+ public abstract ReadData readData(final int chunkX, final int chunkZ) throws IOException; -+ -+ // if the return value is null, then the caller needs to re-try with a new call to readData() -+ public abstract CompoundTag finishRead(final int chunkX, final int chunkZ, final ReadData readData) throws IOException; -+ -+ public static interface IORunnable { -+ -+ public void run(final RegionFile regionFile) throws IOException; -+ -+ } -+ } -+ -+ private static final class IOScheduler { -+ -+ private final ConcurrentLong2ReferenceChainedHashTable regionTasks = new ConcurrentLong2ReferenceChainedHashTable<>(); -+ private final PrioritisedExecutor executor; -+ -+ public IOScheduler(final PrioritisedExecutor executor) { -+ this.executor = executor; -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask createTask(final int chunkX, final int chunkZ, -+ final Runnable run, final Priority priority) { -+ final PrioritisedExecutor.PrioritisedTask[] ret = new PrioritisedExecutor.PrioritisedTask[1]; -+ final long subOrder = this.executor.generateNextSubOrder(); -+ this.regionTasks.compute(CoordinateUtils.getChunkKey(chunkX >> REGION_FILE_SHIFT, chunkZ >> REGION_FILE_SHIFT), -+ (final long regionKey, final RegionIOTasks existing) -> { -+ final RegionIOTasks res; -+ if (existing != null) { -+ res = existing; -+ } else { -+ res = new RegionIOTasks(regionKey, IOScheduler.this); -+ } -+ -+ ret[0] = res.createTask(run, priority, subOrder); -+ -+ return res; -+ }); -+ -+ return ret[0]; -+ } -+ } -+ -+ private static final class RegionIOTasks implements Runnable { -+ -+ private static final Logger LOGGER = LoggerFactory.getLogger(RegionIOTasks.class); -+ -+ private final PrioritisedTaskQueue queue = new PrioritisedTaskQueue(); -+ private final long regionKey; -+ private final IOScheduler ioScheduler; -+ private long createdTasks; -+ private long executedTasks; -+ -+ private PrioritisedExecutor.PrioritisedTask task; -+ -+ public RegionIOTasks(final long regionKey, final IOScheduler ioScheduler) { -+ this.regionKey = regionKey; -+ this.ioScheduler = ioScheduler; -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask createTask(final Runnable run, final Priority priority, -+ final long subOrder) { -+ ++this.createdTasks; -+ return new WrappedTask(this.queue.createTask(run, priority, subOrder)); -+ } -+ -+ private void adjustTaskPriority() { -+ final PrioritisedTaskQueue.PrioritySubOrderPair priority = this.queue.getHighestPrioritySubOrder(); -+ if (this.task == null) { -+ if (priority == null) { -+ return; -+ } -+ this.task = this.ioScheduler.executor.createTask(this, priority.priority(), priority.subOrder()); -+ this.task.queue(); -+ } else { -+ if (priority == null) { -+ throw new IllegalStateException(); -+ } else { -+ this.task.setPriorityAndSubOrder(priority.priority(), priority.subOrder()); -+ } -+ } -+ } -+ -+ @Override -+ public void run() { -+ final Runnable run; -+ synchronized (this) { -+ run = this.queue.pollTask(); -+ } -+ -+ try { -+ run.run(); -+ } finally { -+ synchronized (this) { -+ this.task = null; -+ this.adjustTaskPriority(); -+ } -+ this.ioScheduler.regionTasks.compute(this.regionKey, (final long keyInMap, final RegionIOTasks tasks) -> { -+ if (tasks != RegionIOTasks.this) { -+ throw new IllegalStateException("Region task mismatch"); -+ } -+ ++tasks.executedTasks; -+ if (tasks.createdTasks != tasks.executedTasks) { -+ return tasks; -+ } -+ -+ if (tasks.task != null) { -+ throw new IllegalStateException("Task may not be null when created==executed"); -+ } -+ -+ return null; -+ }); -+ } -+ } -+ -+ private final class WrappedTask implements PrioritisedExecutor.PrioritisedTask { -+ -+ private final PrioritisedExecutor.PrioritisedTask wrapped; -+ -+ public WrappedTask(final PrioritisedExecutor.PrioritisedTask wrap) { -+ this.wrapped = wrap; -+ } -+ -+ @Override -+ public PrioritisedExecutor getExecutor() { -+ return RegionIOTasks.this.ioScheduler.executor; -+ } -+ -+ @Override -+ public boolean queue() { -+ synchronized (RegionIOTasks.this) { -+ if (this.wrapped.queue()) { -+ RegionIOTasks.this.adjustTaskPriority(); -+ return true; -+ } -+ return false; -+ } -+ } -+ -+ @Override -+ public boolean isQueued() { -+ return this.wrapped.isQueued(); -+ } -+ -+ @Override -+ public boolean cancel() { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public boolean execute() { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public Priority getPriority() { -+ return this.wrapped.getPriority(); -+ } -+ -+ @Override -+ public boolean setPriority(final Priority priority) { -+ synchronized (RegionIOTasks.this) { -+ if (this.wrapped.setPriority(priority) && this.wrapped.isQueued()) { -+ RegionIOTasks.this.adjustTaskPriority(); -+ return true; -+ } -+ return false; -+ } -+ } -+ -+ @Override -+ public boolean raisePriority(final Priority priority) { -+ synchronized (RegionIOTasks.this) { -+ if (this.wrapped.raisePriority(priority) && this.wrapped.isQueued()) { -+ RegionIOTasks.this.adjustTaskPriority(); -+ return true; -+ } -+ return false; -+ } -+ } -+ -+ @Override -+ public boolean lowerPriority(final Priority priority) { -+ synchronized (RegionIOTasks.this) { -+ if (this.wrapped.lowerPriority(priority) && this.wrapped.isQueued()) { -+ RegionIOTasks.this.adjustTaskPriority(); -+ return true; -+ } -+ return false; -+ } -+ } -+ -+ @Override -+ public long getSubOrder() { -+ return this.wrapped.getSubOrder(); -+ } -+ -+ @Override -+ public boolean setSubOrder(final long subOrder) { -+ synchronized (RegionIOTasks.this) { -+ if (this.wrapped.setSubOrder(subOrder) && this.wrapped.isQueued()) { -+ RegionIOTasks.this.adjustTaskPriority(); -+ return true; -+ } -+ return false; -+ } -+ } -+ -+ @Override -+ public boolean raiseSubOrder(final long subOrder) { -+ synchronized (RegionIOTasks.this) { -+ if (this.wrapped.raiseSubOrder(subOrder) && this.wrapped.isQueued()) { -+ RegionIOTasks.this.adjustTaskPriority(); -+ return true; -+ } -+ return false; -+ } -+ } -+ -+ @Override -+ public boolean lowerSubOrder(final long subOrder) { -+ synchronized (RegionIOTasks.this) { -+ if (this.wrapped.lowerSubOrder(subOrder) && this.wrapped.isQueued()) { -+ RegionIOTasks.this.adjustTaskPriority(); -+ return true; -+ } -+ return false; -+ } -+ } -+ -+ @Override -+ public boolean setPriorityAndSubOrder(final Priority priority, final long subOrder) { -+ synchronized (RegionIOTasks.this) { -+ if (this.wrapped.setPriorityAndSubOrder(priority, subOrder) && this.wrapped.isQueued()) { -+ RegionIOTasks.this.adjustTaskPriority(); -+ return true; -+ } -+ return false; -+ } -+ } -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/RegionFileIOThread.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/RegionFileIOThread.java -deleted file mode 100644 -index 3218cbf84f54daf06e84442d5eb1a36d8da6b215..0000000000000000000000000000000000000000 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/RegionFileIOThread.java -+++ /dev/null -@@ -1,1240 +0,0 @@ --package ca.spottedleaf.moonrise.patches.chunk_system.io; -- --import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; --import ca.spottedleaf.concurrentutil.executor.Cancellable; --import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; --import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedQueueExecutorThread; --import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadedTaskQueue; --import ca.spottedleaf.concurrentutil.function.BiLong1Function; --import ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable; --import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; --import ca.spottedleaf.moonrise.common.util.CoordinateUtils; --import ca.spottedleaf.moonrise.common.util.TickThread; --import ca.spottedleaf.moonrise.common.util.WorldUtil; --import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel; --import net.minecraft.nbt.CompoundTag; --import net.minecraft.server.level.ServerLevel; --import net.minecraft.world.level.ChunkPos; --import net.minecraft.world.level.chunk.storage.RegionFile; --import net.minecraft.world.level.chunk.storage.RegionFileStorage; --import org.slf4j.Logger; --import org.slf4j.LoggerFactory; --import java.io.IOException; --import java.lang.invoke.VarHandle; --import java.util.concurrent.CompletableFuture; --import java.util.concurrent.CompletionException; --import java.util.concurrent.atomic.AtomicInteger; --import java.util.function.BiConsumer; --import java.util.function.Consumer; --import java.util.function.Function; -- --/** -- * Prioritised RegionFile I/O executor, responsible for all RegionFile access. -- *

    -- * All functions provided are MT-Safe, however certain ordering constraints are recommended: -- *

  • -- * Chunk saves may not occur for unloaded chunks. -- *
  • -- *
  • -- * Tasks must be scheduled on the chunk scheduler thread. -- *
  • -- * By following these constraints, no chunk data loss should occur with the exception of underlying I/O problems. -- *

    -- */ --public final class RegionFileIOThread extends PrioritisedQueueExecutorThread { -- -- private static final Logger LOGGER = LoggerFactory.getLogger(RegionFileIOThread.class); -- -- /** -- * The kinds of region files controlled by the region file thread. Add more when needed, and ensure -- * getControllerFor is updated. -- */ -- public static enum RegionFileType { -- CHUNK_DATA, -- POI_DATA, -- ENTITY_DATA; -- } -- -- private static final RegionFileType[] CACHED_REGIONFILE_TYPES = RegionFileType.values(); -- -- public static ChunkDataController getControllerFor(final ServerLevel world, final RegionFileType type) { -- switch (type) { -- case CHUNK_DATA: -- return ((ChunkSystemServerLevel)world).moonrise$getChunkDataController(); -- case POI_DATA: -- return ((ChunkSystemServerLevel)world).moonrise$getPoiChunkDataController(); -- case ENTITY_DATA: -- return ((ChunkSystemServerLevel)world).moonrise$getEntityChunkDataController(); -- default: -- throw new IllegalStateException("Unknown controller type " + type); -- } -- } -- -- /** -- * Collects regionfile data for a certain chunk. -- */ -- public static final class RegionFileData { -- -- private final boolean[] hasResult = new boolean[CACHED_REGIONFILE_TYPES.length]; -- private final CompoundTag[] data = new CompoundTag[CACHED_REGIONFILE_TYPES.length]; -- private final Throwable[] throwables = new Throwable[CACHED_REGIONFILE_TYPES.length]; -- -- /** -- * Sets the result associated with the specified regionfile type. Note that -- * results can only be set once per regionfile type. -- * -- * @param type The regionfile type. -- * @param data The result to set. -- */ -- public void setData(final RegionFileType type, final CompoundTag data) { -- final int index = type.ordinal(); -- -- if (this.hasResult[index]) { -- throw new IllegalArgumentException("Result already exists for type " + type); -- } -- this.hasResult[index] = true; -- this.data[index] = data; -- } -- -- /** -- * Sets the result associated with the specified regionfile type. Note that -- * results can only be set once per regionfile type. -- * -- * @param type The regionfile type. -- * @param throwable The result to set. -- */ -- public void setThrowable(final RegionFileType type, final Throwable throwable) { -- final int index = type.ordinal(); -- -- if (this.hasResult[index]) { -- throw new IllegalArgumentException("Result already exists for type " + type); -- } -- this.hasResult[index] = true; -- this.throwables[index] = throwable; -- } -- -- /** -- * Returns whether there is a result for the specified regionfile type. -- * -- * @param type Specified regionfile type. -- * -- * @return Whether a result exists for {@code type}. -- */ -- public boolean hasResult(final RegionFileType type) { -- return this.hasResult[type.ordinal()]; -- } -- -- /** -- * Returns the data result for the regionfile type. -- * -- * @param type Specified regionfile type. -- * -- * @throws IllegalArgumentException If the result has not been set for {@code type}. -- * @return The data result for the specified type. If the result is a {@code Throwable}, -- * then returns {@code null}. -- */ -- public CompoundTag getData(final RegionFileType type) { -- final int index = type.ordinal(); -- -- if (!this.hasResult[index]) { -- throw new IllegalArgumentException("Result does not exist for type " + type); -- } -- -- return this.data[index]; -- } -- -- /** -- * Returns the throwable result for the regionfile type. -- * -- * @param type Specified regionfile type. -- * -- * @throws IllegalArgumentException If the result has not been set for {@code type}. -- * @return The throwable result for the specified type. If the result is an {@code CompoundTag}, -- * then returns {@code null}. -- */ -- public Throwable getThrowable(final RegionFileType type) { -- final int index = type.ordinal(); -- -- if (!this.hasResult[index]) { -- throw new IllegalArgumentException("Result does not exist for type " + type); -- } -- -- return this.throwables[index]; -- } -- } -- -- private static final Object INIT_LOCK = new Object(); -- -- static RegionFileIOThread[] threads; -- -- /* needs to be consistent given a set of parameters */ -- static RegionFileIOThread selectThread(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type) { -- if (threads == null) { -- throw new IllegalStateException("Threads not initialised"); -- } -- -- final int regionX = chunkX >> 5; -- final int regionZ = chunkZ >> 5; -- final int typeOffset = type.ordinal(); -- -- return threads[(System.identityHashCode(world) + regionX + regionZ + typeOffset) % threads.length]; -- } -- -- /** -- * Shuts down the I/O executor(s). Watis for all tasks to complete if specified. -- * Tasks queued during this call might not be accepted, and tasks queued after will not be accepted. -- * -- * @param wait Whether to wait until all tasks have completed. -- */ -- public static void close(final boolean wait) { -- for (int i = 0, len = threads.length; i < len; ++i) { -- threads[i].close(false, true); -- } -- if (wait) { -- RegionFileIOThread.flush(); -- } -- } -- -- public static long[] getExecutedTasks() { -- final long[] ret = new long[threads.length]; -- for (int i = 0, len = threads.length; i < len; ++i) { -- ret[i] = threads[i].getTotalTasksExecuted(); -- } -- -- return ret; -- } -- -- public static long[] getTasksScheduled() { -- final long[] ret = new long[threads.length]; -- for (int i = 0, len = threads.length; i < len; ++i) { -- ret[i] = threads[i].getTotalTasksScheduled(); -- } -- return ret; -- } -- -- public static void flush() { -- for (int i = 0, len = threads.length; i < len; ++i) { -- threads[i].waitUntilAllExecuted(); -- } -- } -- -- public static void flushRegionStorages(final ServerLevel world) throws IOException { -- for (final RegionFileType type : CACHED_REGIONFILE_TYPES) { -- getControllerFor(world, type).getCache().flush(); -- } -- } -- -- public static void partialFlush(final int totalTasksRemaining) { -- long failures = 1L; // start out at 0.25ms -- -- for (;;) { -- final long[] executed = getExecutedTasks(); -- final long[] scheduled = getTasksScheduled(); -- -- long sum = 0; -- for (int i = 0; i < executed.length; ++i) { -- sum += scheduled[i] - executed[i]; -- } -- -- if (sum <= totalTasksRemaining) { -- break; -- } -- -- failures = ConcurrentUtil.linearLongBackoff(failures, 250_000L, 5_000_000L); // 500us, 5ms -- } -- } -- -- /** -- * Inits the executor with the specified number of threads. -- * -- * @param threads Specified number of threads. -- */ -- public static void init(final int threads) { -- synchronized (INIT_LOCK) { -- if (RegionFileIOThread.threads != null) { -- throw new IllegalStateException("Already initialised threads"); -- } -- -- RegionFileIOThread.threads = new RegionFileIOThread[threads]; -- -- for (int i = 0; i < threads; ++i) { -- RegionFileIOThread.threads[i] = new RegionFileIOThread(i); -- RegionFileIOThread.threads[i].start(); -- } -- } -- } -- -- public static void deinit() { -- if (true) { // Paper -- // TODO does this cause issues with mods? how to implement -- close(true); -- synchronized (INIT_LOCK) { -- RegionFileIOThread.threads = null; -- } -- } else { RegionFileIOThread.flush(); } -- } -- -- private RegionFileIOThread(final int threadNumber) { -- super(new PrioritisedThreadedTaskQueue(), (int)(1.0e6)); // 1.0ms spinwait time -- this.setName("RegionFile I/O Thread #" + threadNumber); -- this.setPriority(Thread.NORM_PRIORITY - 2); // we keep priority close to normal because threads can wait on us -- this.setUncaughtExceptionHandler((final Thread thread, final Throwable thr) -> { -- LOGGER.error("Uncaught exception thrown from I/O thread, report this! Thread: " + thread.getName(), thr); -- }); -- } -- -- /** -- * Returns whether the current thread is a regionfile I/O executor. -- * @return Whether the current thread is a regionfile I/O executor. -- */ -- public static boolean isRegionFileThread() { -- return Thread.currentThread() instanceof RegionFileIOThread; -- } -- -- /** -- * Returns the priority associated with blocking I/O based on the current thread. The goal is to avoid -- * dumb plugins from taking away priority from threads we consider crucial. -- * @return The priroity to use with blocking I/O on the current thread. -- */ -- public static Priority getIOBlockingPriorityForCurrentThread() { -- if (TickThread.isTickThread()) { -- return Priority.BLOCKING; -- } -- return Priority.HIGHEST; -- } -- -- /** -- * Returns the current {@code CompoundTag} pending for write for the specified chunk & regionfile type. -- * Note that this does not copy the result, so do not modify the result returned. -- * -- * @param world Specified world. -- * @param chunkX Specified chunk x. -- * @param chunkZ Specified chunk z. -- * @param type Specified regionfile type. -- * -- * @return The compound tag associated for the specified chunk. {@code null} if no write was pending, or if {@code null} is the write pending. -- */ -- public static CompoundTag getPendingWrite(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type) { -- final RegionFileIOThread thread = RegionFileIOThread.selectThread(world, chunkX, chunkZ, type); -- return thread.getPendingWriteInternal(world, chunkX, chunkZ, type); -- } -- -- CompoundTag getPendingWriteInternal(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type) { -- final ChunkDataController taskController = getControllerFor(world, type); -- final ChunkDataTask task = taskController.tasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -- -- if (task == null) { -- return null; -- } -- -- final CompoundTag ret = task.inProgressWrite; -- -- return ret == ChunkDataTask.NOTHING_TO_WRITE ? null : ret; -- } -- -- /** -- * Returns the priority for the specified regionfile type for the specified chunk. -- * @param world Specified world. -- * @param chunkX Specified chunk x. -- * @param chunkZ Specified chunk z. -- * @param type Specified regionfile type. -- * @return The priority for the chunk -- */ -- public static Priority getPriority(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type) { -- final RegionFileIOThread thread = RegionFileIOThread.selectThread(world, chunkX, chunkZ, type); -- return thread.getPriorityInternal(world, chunkX, chunkZ, type); -- } -- -- Priority getPriorityInternal(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type) { -- final ChunkDataController taskController = getControllerFor(world, type); -- final ChunkDataTask task = taskController.tasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -- -- if (task == null) { -- return Priority.COMPLETING; -- } -- -- return task.prioritisedTask.getPriority(); -- } -- -- /** -- * Sets the priority for all regionfile types for the specified chunk. Note that great care should -- * be taken using this method, as there can be multiple tasks tied to the same chunk that want different -- * priorities. -- * -- * @param world Specified world. -- * @param chunkX Specified chunk x. -- * @param chunkZ Specified chunk z. -- * @param priority New priority. -- * -- * @see #raisePriority(ServerLevel, int, int, Priority) -- * @see #raisePriority(ServerLevel, int, int, RegionFileType, Priority) -- * @see #lowerPriority(ServerLevel, int, int, Priority) -- * @see #lowerPriority(ServerLevel, int, int, RegionFileType, Priority) -- */ -- public static void setPriority(final ServerLevel world, final int chunkX, final int chunkZ, -- final Priority priority) { -- for (final RegionFileType type : CACHED_REGIONFILE_TYPES) { -- RegionFileIOThread.setPriority(world, chunkX, chunkZ, type, priority); -- } -- } -- -- /** -- * Sets the priority for the specified regionfile type for the specified chunk. Note that great care should -- * be taken using this method, as there can be multiple tasks tied to the same chunk that want different -- * priorities. -- * -- * @param world Specified world. -- * @param chunkX Specified chunk x. -- * @param chunkZ Specified chunk z. -- * @param type Specified regionfile type. -- * @param priority New priority. -- * -- * @see #raisePriority(ServerLevel, int, int, Priority) -- * @see #raisePriority(ServerLevel, int, int, RegionFileType, Priority) -- * @see #lowerPriority(ServerLevel, int, int, Priority) -- * @see #lowerPriority(ServerLevel, int, int, RegionFileType, Priority) -- */ -- public static void setPriority(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type, -- final Priority priority) { -- final RegionFileIOThread thread = RegionFileIOThread.selectThread(world, chunkX, chunkZ, type); -- thread.setPriorityInternal(world, chunkX, chunkZ, type, priority); -- } -- -- void setPriorityInternal(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type, -- final Priority priority) { -- final ChunkDataController taskController = getControllerFor(world, type); -- final ChunkDataTask task = taskController.tasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -- -- if (task != null) { -- task.prioritisedTask.setPriority(priority); -- } -- } -- -- /** -- * Raises the priority for all regionfile types for the specified chunk. -- * -- * @param world Specified world. -- * @param chunkX Specified chunk x. -- * @param chunkZ Specified chunk z. -- * @param priority New priority. -- * -- * @see #setPriority(ServerLevel, int, int, Priority) -- * @see #setPriority(ServerLevel, int, int, RegionFileType, Priority) -- * @see #lowerPriority(ServerLevel, int, int, Priority) -- * @see #lowerPriority(ServerLevel, int, int, RegionFileType, Priority) -- */ -- public static void raisePriority(final ServerLevel world, final int chunkX, final int chunkZ, -- final Priority priority) { -- for (final RegionFileType type : CACHED_REGIONFILE_TYPES) { -- RegionFileIOThread.raisePriority(world, chunkX, chunkZ, type, priority); -- } -- } -- -- /** -- * Raises the priority for the specified regionfile type for the specified chunk. -- * -- * @param world Specified world. -- * @param chunkX Specified chunk x. -- * @param chunkZ Specified chunk z. -- * @param type Specified regionfile type. -- * @param priority New priority. -- * -- * @see #setPriority(ServerLevel, int, int, Priority) -- * @see #setPriority(ServerLevel, int, int, RegionFileType, Priority) -- * @see #lowerPriority(ServerLevel, int, int, Priority) -- * @see #lowerPriority(ServerLevel, int, int, RegionFileType, Priority) -- */ -- public static void raisePriority(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type, -- final Priority priority) { -- final RegionFileIOThread thread = RegionFileIOThread.selectThread(world, chunkX, chunkZ, type); -- thread.raisePriorityInternal(world, chunkX, chunkZ, type, priority); -- } -- -- void raisePriorityInternal(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type, -- final Priority priority) { -- final ChunkDataController taskController = getControllerFor(world, type); -- final ChunkDataTask task = taskController.tasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -- -- if (task != null) { -- task.prioritisedTask.raisePriority(priority); -- } -- } -- -- /** -- * Lowers the priority for all regionfile types for the specified chunk. -- * -- * @param world Specified world. -- * @param chunkX Specified chunk x. -- * @param chunkZ Specified chunk z. -- * @param priority New priority. -- * -- * @see #raisePriority(ServerLevel, int, int, Priority) -- * @see #raisePriority(ServerLevel, int, int, RegionFileType, Priority) -- * @see #setPriority(ServerLevel, int, int, Priority) -- * @see #setPriority(ServerLevel, int, int, RegionFileType, Priority) -- */ -- public static void lowerPriority(final ServerLevel world, final int chunkX, final int chunkZ, -- final Priority priority) { -- for (final RegionFileType type : CACHED_REGIONFILE_TYPES) { -- RegionFileIOThread.lowerPriority(world, chunkX, chunkZ, type, priority); -- } -- } -- -- /** -- * Lowers the priority for the specified regionfile type for the specified chunk. -- * -- * @param world Specified world. -- * @param chunkX Specified chunk x. -- * @param chunkZ Specified chunk z. -- * @param type Specified regionfile type. -- * @param priority New priority. -- * -- * @see #raisePriority(ServerLevel, int, int, Priority) -- * @see #raisePriority(ServerLevel, int, int, RegionFileType, Priority) -- * @see #setPriority(ServerLevel, int, int, Priority) -- * @see #setPriority(ServerLevel, int, int, RegionFileType, Priority) -- */ -- public static void lowerPriority(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type, -- final Priority priority) { -- final RegionFileIOThread thread = RegionFileIOThread.selectThread(world, chunkX, chunkZ, type); -- thread.lowerPriorityInternal(world, chunkX, chunkZ, type, priority); -- } -- -- void lowerPriorityInternal(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type, -- final Priority priority) { -- final ChunkDataController taskController = getControllerFor(world, type); -- final ChunkDataTask task = taskController.tasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -- -- if (task != null) { -- task.prioritisedTask.lowerPriority(priority); -- } -- } -- -- /** -- * Schedules the chunk data to be written asynchronously. -- *

    -- * Impl notes: -- *

    -- *
  • -- * This function presumes a chunk load for the coordinates is not called during this function (anytime after is OK). This means -- * saves must be scheduled before a chunk is unloaded. -- *
  • -- *
  • -- * Writes may be called concurrently, although only the "later" write will go through. -- *
  • -- * -- * @param world Chunk's world -- * @param chunkX Chunk's x coordinate -- * @param chunkZ Chunk's z coordinate -- * @param data Chunk's data -- * @param type The regionfile type to write to. -- * -- * @throws IllegalStateException If the file io thread has shutdown. -- */ -- public static void scheduleSave(final ServerLevel world, final int chunkX, final int chunkZ, final CompoundTag data, -- final RegionFileType type) { -- RegionFileIOThread.scheduleSave(world, chunkX, chunkZ, data, type, Priority.NORMAL); -- } -- -- /** -- * Schedules the chunk data to be written asynchronously. -- *

    -- * Impl notes: -- *

    -- *
  • -- * This function presumes a chunk load for the coordinates is not called during this function (anytime after is OK). This means -- * saves must be scheduled before a chunk is unloaded. -- *
  • -- *
  • -- * Writes may be called concurrently, although only the "later" write will go through. -- *
  • -- * -- * @param world Chunk's world -- * @param chunkX Chunk's x coordinate -- * @param chunkZ Chunk's z coordinate -- * @param data Chunk's data -- * @param type The regionfile type to write to. -- * @param priority The minimum priority to schedule at. -- * -- * @throws IllegalStateException If the file io thread has shutdown. -- */ -- public static void scheduleSave(final ServerLevel world, final int chunkX, final int chunkZ, final CompoundTag data, -- final RegionFileType type, final Priority priority) { -- final RegionFileIOThread thread = RegionFileIOThread.selectThread(world, chunkX, chunkZ, type); -- thread.scheduleSaveInternal(world, chunkX, chunkZ, data, type, priority); -- } -- -- void scheduleSaveInternal(final ServerLevel world, final int chunkX, final int chunkZ, final CompoundTag data, -- final RegionFileType type, final Priority priority) { -- final ChunkDataController taskController = getControllerFor(world, type); -- -- final boolean[] created = new boolean[1]; -- final long key = CoordinateUtils.getChunkKey(chunkX, chunkZ); -- final ChunkDataTask task = taskController.tasks.compute(key, (final long keyInMap, final ChunkDataTask taskRunning) -> { -- if (taskRunning == null || taskRunning.failedWrite) { -- // no task is scheduled or the previous write failed - meaning we need to overwrite it -- -- // create task -- final ChunkDataTask newTask = new ChunkDataTask(world, chunkX, chunkZ, taskController, RegionFileIOThread.this, priority); -- newTask.inProgressWrite = data; -- created[0] = true; -- -- return newTask; -- } -- -- taskRunning.inProgressWrite = data; -- -- return taskRunning; -- }); -- -- if (created[0]) { -- task.prioritisedTask.queue(); -- } else { -- task.prioritisedTask.raisePriority(priority); -- } -- } -- -- /** -- * Schedules a load to be executed asynchronously. This task will load all regionfile types, and then call -- * {@code onComplete}. This is a bulk load operation, see {@link #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean)} -- * for single load. -- *

    -- * Impl notes: -- *

    -- *
  • -- * The {@code onComplete} parameter may be completed during the execution of this function synchronously or it may -- * be completed asynchronously on this file io thread. Interacting with the file IO thread in the completion of -- * data is undefined behaviour, and can cause deadlock. -- *
  • -- * -- * @param world Chunk's world -- * @param chunkX Chunk's x coordinate -- * @param chunkZ Chunk's z coordinate -- * @param onComplete Consumer to execute once this task has completed -- * @param intendingToBlock Whether the caller is intending to block on completion. This only affects the cost -- * of this call. -- * -- * @return The {@link Cancellable} for this chunk load. Cancelling it will not affect other loads for the same chunk data. -- * -- * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean) -- * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean, Priority) -- * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, RegionFileType...) -- * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, Priority, RegionFileType...) -- */ -- public static Cancellable loadAllChunkData(final ServerLevel world, final int chunkX, final int chunkZ, -- final Consumer onComplete, final boolean intendingToBlock) { -- return RegionFileIOThread.loadAllChunkData(world, chunkX, chunkZ, onComplete, intendingToBlock, Priority.NORMAL); -- } -- -- /** -- * Schedules a load to be executed asynchronously. This task will load all regionfile types, and then call -- * {@code onComplete}. This is a bulk load operation, see {@link #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean, Priority)} -- * for single load. -- *

    -- * Impl notes: -- *

    -- *
  • -- * The {@code onComplete} parameter may be completed during the execution of this function synchronously or it may -- * be completed asynchronously on this file io thread. Interacting with the file IO thread in the completion of -- * data is undefined behaviour, and can cause deadlock. -- *
  • -- * -- * @param world Chunk's world -- * @param chunkX Chunk's x coordinate -- * @param chunkZ Chunk's z coordinate -- * @param onComplete Consumer to execute once this task has completed -- * @param intendingToBlock Whether the caller is intending to block on completion. This only affects the cost -- * of this call. -- * @param priority The minimum priority to load the data at. -- * -- * @return The {@link Cancellable} for this chunk load. Cancelling it will not affect other loads for the same chunk data. -- * -- * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean) -- * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean, Priority) -- * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, RegionFileType...) -- * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, Priority, RegionFileType...) -- */ -- public static Cancellable loadAllChunkData(final ServerLevel world, final int chunkX, final int chunkZ, -- final Consumer onComplete, final boolean intendingToBlock, -- final Priority priority) { -- return RegionFileIOThread.loadChunkData(world, chunkX, chunkZ, onComplete, intendingToBlock, priority, CACHED_REGIONFILE_TYPES); -- } -- -- /** -- * Schedules a load to be executed asynchronously. This task will load data for the specified regionfile type(s), and -- * then call {@code onComplete}. This is a bulk load operation, see {@link #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean)} -- * for single load. -- *

    -- * Impl notes: -- *

    -- *
  • -- * The {@code onComplete} parameter may be completed during the execution of this function synchronously or it may -- * be completed asynchronously on this file io thread. Interacting with the file IO thread in the completion of -- * data is undefined behaviour, and can cause deadlock. -- *
  • -- * -- * @param world Chunk's world -- * @param chunkX Chunk's x coordinate -- * @param chunkZ Chunk's z coordinate -- * @param onComplete Consumer to execute once this task has completed -- * @param intendingToBlock Whether the caller is intending to block on completion. This only affects the cost -- * of this call. -- * @param types The regionfile type(s) to load. -- * -- * @return The {@link Cancellable} for this chunk load. Cancelling it will not affect other loads for the same chunk data. -- * -- * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean) -- * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean, Priority) -- * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean) -- * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean, Priority) -- */ -- public static Cancellable loadChunkData(final ServerLevel world, final int chunkX, final int chunkZ, -- final Consumer onComplete, final boolean intendingToBlock, -- final RegionFileType... types) { -- return RegionFileIOThread.loadChunkData(world, chunkX, chunkZ, onComplete, intendingToBlock, Priority.NORMAL, types); -- } -- -- /** -- * Schedules a load to be executed asynchronously. This task will load data for the specified regionfile type(s), and -- * then call {@code onComplete}. This is a bulk load operation, see {@link #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean, Priority)} -- * for single load. -- *

    -- * Impl notes: -- *

    -- *
  • -- * The {@code onComplete} parameter may be completed during the execution of this function synchronously or it may -- * be completed asynchronously on this file io thread. Interacting with the file IO thread in the completion of -- * data is undefined behaviour, and can cause deadlock. -- *
  • -- * -- * @param world Chunk's world -- * @param chunkX Chunk's x coordinate -- * @param chunkZ Chunk's z coordinate -- * @param onComplete Consumer to execute once this task has completed -- * @param intendingToBlock Whether the caller is intending to block on completion. This only affects the cost -- * of this call. -- * @param types The regionfile type(s) to load. -- * @param priority The minimum priority to load the data at. -- * -- * @return The {@link Cancellable} for this chunk load. Cancelling it will not affect other loads for the same chunk data. -- * -- * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean) -- * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean, Priority) -- * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean) -- * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean, Priority) -- */ -- public static Cancellable loadChunkData(final ServerLevel world, final int chunkX, final int chunkZ, -- final Consumer onComplete, final boolean intendingToBlock, -- final Priority priority, final RegionFileType... types) { -- if (types == null) { -- throw new NullPointerException("Types cannot be null"); -- } -- if (types.length == 0) { -- throw new IllegalArgumentException("Types cannot be empty"); -- } -- -- final RegionFileData ret = new RegionFileData(); -- -- final Cancellable[] reads = new CancellableRead[types.length]; -- final AtomicInteger completions = new AtomicInteger(); -- final int expectedCompletions = types.length; -- -- for (int i = 0; i < expectedCompletions; ++i) { -- final RegionFileType type = types[i]; -- reads[i] = RegionFileIOThread.loadDataAsync(world, chunkX, chunkZ, type, -- (final CompoundTag data, final Throwable throwable) -> { -- if (throwable != null) { -- ret.setThrowable(type, throwable); -- } else { -- ret.setData(type, data); -- } -- -- if (completions.incrementAndGet() == expectedCompletions) { -- onComplete.accept(ret); -- } -- }, intendingToBlock, priority); -- } -- -- return new CancellableReads(reads); -- } -- -- /** -- * Schedules a load to be executed asynchronously. This task will load the specified regionfile type, and then call -- * {@code onComplete}. -- *

    -- * Impl notes: -- *

    -- *
  • -- * The {@code onComplete} parameter may be completed during the execution of this function synchronously or it may -- * be completed asynchronously on this file io thread. Interacting with the file IO thread in the completion of -- * data is undefined behaviour, and can cause deadlock. -- *
  • -- * -- * @param world Chunk's world -- * @param chunkX Chunk's x coordinate -- * @param chunkZ Chunk's z coordinate -- * @param onComplete Consumer to execute once this task has completed -- * @param intendingToBlock Whether the caller is intending to block on completion. This only affects the cost -- * of this call. -- * -- * @return The {@link Cancellable} for this chunk load. Cancelling it will not affect other loads for the same chunk data. -- * -- * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, RegionFileType...) -- * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, Priority, RegionFileType...) -- * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean) -- * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean, Priority) -- */ -- public static Cancellable loadDataAsync(final ServerLevel world, final int chunkX, final int chunkZ, -- final RegionFileType type, final BiConsumer onComplete, -- final boolean intendingToBlock) { -- return RegionFileIOThread.loadDataAsync(world, chunkX, chunkZ, type, onComplete, intendingToBlock, Priority.NORMAL); -- } -- -- /** -- * Schedules a load to be executed asynchronously. This task will load the specified regionfile type, and then call -- * {@code onComplete}. -- *

    -- * Impl notes: -- *

    -- *
  • -- * The {@code onComplete} parameter may be completed during the execution of this function synchronously or it may -- * be completed asynchronously on this file io thread. Interacting with the file IO thread in the completion of -- * data is undefined behaviour, and can cause deadlock. -- *
  • -- * -- * @param world Chunk's world -- * @param chunkX Chunk's x coordinate -- * @param chunkZ Chunk's z coordinate -- * @param onComplete Consumer to execute once this task has completed -- * @param intendingToBlock Whether the caller is intending to block on completion. This only affects the cost -- * of this call. -- * @param priority Minimum priority to load the data at. -- * -- * @return The {@link Cancellable} for this chunk load. Cancelling it will not affect other loads for the same chunk data. -- * -- * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, RegionFileType...) -- * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, Priority, RegionFileType...) -- * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean) -- * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean, Priority) -- */ -- public static Cancellable loadDataAsync(final ServerLevel world, final int chunkX, final int chunkZ, -- final RegionFileType type, final BiConsumer onComplete, -- final boolean intendingToBlock, final Priority priority) { -- final RegionFileIOThread thread = RegionFileIOThread.selectThread(world, chunkX, chunkZ, type); -- return thread.loadDataAsyncInternal(world, chunkX, chunkZ, type, onComplete, intendingToBlock, priority); -- } -- -- Cancellable loadDataAsyncInternal(final ServerLevel world, final int chunkX, final int chunkZ, -- final RegionFileType type, final BiConsumer onComplete, -- final boolean intendingToBlock, final Priority priority) { -- final ChunkDataController taskController = getControllerFor(world, type); -- -- final ImmediateCallbackCompletion callbackInfo = new ImmediateCallbackCompletion(); -- -- final long key = CoordinateUtils.getChunkKey(chunkX, chunkZ); -- final BiLong1Function compute = (final long keyInMap, final ChunkDataTask running) -> { -- if (running == null) { -- // not scheduled -- -- // set up task -- final ChunkDataTask newTask = new ChunkDataTask( -- world, chunkX, chunkZ, taskController, RegionFileIOThread.this, priority -- ); -- newTask.inProgressRead = new InProgressRead(); -- newTask.inProgressRead.addToAsyncWaiters(onComplete); -- -- callbackInfo.tasksNeedsScheduling = true; -- return newTask; -- } -- -- final CompoundTag pendingWrite = running.inProgressWrite; -- -- if (pendingWrite == ChunkDataTask.NOTHING_TO_WRITE) { -- // need to add to waiters here, because the regionfile thread will use compute() to lock and check for cancellations -- if (!running.inProgressRead.addToAsyncWaiters(onComplete)) { -- callbackInfo.data = running.inProgressRead.value; -- callbackInfo.throwable = running.inProgressRead.throwable; -- callbackInfo.completeNow = true; -- } -- return running; -- } -- -- // at this stage we have to use the in progress write's data to avoid an order issue -- callbackInfo.data = pendingWrite; -- callbackInfo.throwable = null; -- callbackInfo.completeNow = true; -- return running; -- }; -- -- final ChunkDataTask ret = taskController.tasks.compute(key, compute); -- -- // needs to be scheduled -- if (callbackInfo.tasksNeedsScheduling) { -- ret.prioritisedTask.queue(); -- } else if (callbackInfo.completeNow) { -- try { -- onComplete.accept(callbackInfo.data == null ? null : callbackInfo.data.copy(), callbackInfo.throwable); -- } catch (final Throwable thr) { -- LOGGER.error("Callback " + ConcurrentUtil.genericToString(onComplete) + " synchronously failed to handle chunk data for task " + ret.toString(), thr); -- } -- } else { -- // we're waiting on a task we didn't schedule, so raise its priority to what we want -- ret.prioritisedTask.raisePriority(priority); -- } -- -- return new CancellableRead(onComplete, ret); -- } -- -- /** -- * Schedules a load task to be executed asynchronously, and blocks on that task. -- * -- * @param world Chunk's world -- * @param chunkX Chunk's x coordinate -- * @param chunkZ Chunk's z coordinate -- * @param type Regionfile type -- * @param priority Minimum priority to load the data at. -- * -- * @return The chunk data for the chunk. Note that a {@code null} result means the chunk or regionfile does not exist on disk. -- * -- * @throws IOException If the load fails for any reason -- */ -- public static CompoundTag loadData(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type, -- final Priority priority) throws IOException { -- final CompletableFuture ret = new CompletableFuture<>(); -- -- RegionFileIOThread.loadDataAsync(world, chunkX, chunkZ, type, (final CompoundTag compound, final Throwable thr) -> { -- if (thr != null) { -- ret.completeExceptionally(thr); -- } else { -- ret.complete(compound); -- } -- }, true, priority); -- -- try { -- return ret.join(); -- } catch (final CompletionException ex) { -- throw new IOException(ex); -- } -- } -- -- private static final class ImmediateCallbackCompletion { -- -- public CompoundTag data; -- public Throwable throwable; -- public boolean completeNow; -- public boolean tasksNeedsScheduling; -- -- } -- -- private static final class CancellableRead implements Cancellable { -- -- private BiConsumer callback; -- private ChunkDataTask task; -- -- CancellableRead(final BiConsumer callback, final ChunkDataTask task) { -- this.callback = callback; -- this.task = task; -- } -- -- @Override -- public boolean cancel() { -- final BiConsumer callback = this.callback; -- final ChunkDataTask task = this.task; -- -- if (callback == null || task == null) { -- return false; -- } -- -- this.callback = null; -- this.task = null; -- -- final InProgressRead read = task.inProgressRead; -- -- // read can be null if no read was scheduled (i.e no regionfile existed or chunk in regionfile didn't) -- return read != null && read.cancel(callback); -- } -- } -- -- private static final class CancellableReads implements Cancellable { -- -- private Cancellable[] reads; -- -- private static final VarHandle READS_HANDLE = ConcurrentUtil.getVarHandle(CancellableReads.class, "reads", Cancellable[].class); -- -- CancellableReads(final Cancellable[] reads) { -- this.reads = reads; -- } -- -- @Override -- public boolean cancel() { -- final Cancellable[] reads = (Cancellable[])READS_HANDLE.getAndSet((CancellableReads)this, (Cancellable[])null); -- -- if (reads == null) { -- return false; -- } -- -- boolean ret = false; -- -- for (final Cancellable read : reads) { -- ret |= read.cancel(); -- } -- -- return ret; -- } -- } -- -- private static final class InProgressRead { -- -- private static final Logger LOGGER = LoggerFactory.getLogger(InProgressRead.class); -- -- private CompoundTag value; -- private Throwable throwable; -- private final MultiThreadedQueue> callbacks = new MultiThreadedQueue<>(); -- -- public boolean hasNoWaiters() { -- return this.callbacks.isEmpty(); -- } -- -- public boolean addToAsyncWaiters(final BiConsumer callback) { -- return this.callbacks.add(callback); -- } -- -- public boolean cancel(final BiConsumer callback) { -- return this.callbacks.remove(callback); -- } -- -- public void complete(final ChunkDataTask task, final CompoundTag value, final Throwable throwable) { -- this.value = value; -- this.throwable = throwable; -- -- BiConsumer consumer; -- while ((consumer = this.callbacks.pollOrBlockAdds()) != null) { -- try { -- consumer.accept(value == null ? null : value.copy(), throwable); -- } catch (final Throwable thr) { -- LOGGER.error("Callback " + ConcurrentUtil.genericToString(consumer) + " failed to handle chunk data for task " + task.toString(), thr); -- } -- } -- } -- } -- -- public static abstract class ChunkDataController { -- -- // ConcurrentHashMap synchronizes per chain, so reduce the chance of task's hashes colliding. -- private final ConcurrentLong2ReferenceChainedHashTable tasks = ConcurrentLong2ReferenceChainedHashTable.createWithCapacity(8192, 0.5f); -- -- public final RegionFileType type; -- -- public ChunkDataController(final RegionFileType type) { -- this.type = type; -- } -- -- public abstract RegionFileStorage getCache(); -- -- public abstract void writeData(final int chunkX, final int chunkZ, final CompoundTag compound) throws IOException; -- -- public abstract CompoundTag readData(final int chunkX, final int chunkZ) throws IOException; -- -- public boolean hasTasks() { -- return !this.tasks.isEmpty(); -- } -- -- public boolean doesRegionFileNotExist(final int chunkX, final int chunkZ) { -- return ((ChunkSystemRegionFileStorage)(Object)this.getCache()).moonrise$doesRegionFileNotExistNoIO(chunkX, chunkZ); -- } -- -- public T computeForRegionFile(final int chunkX, final int chunkZ, final boolean existingOnly, final Function function) { -- final RegionFileStorage cache = this.getCache(); -- final RegionFile regionFile; -- synchronized (cache) { -- try { -- if (existingOnly) { -- regionFile = ((ChunkSystemRegionFileStorage)(Object)cache).moonrise$getRegionFileIfExists(chunkX, chunkZ); -- } else { -- regionFile = cache.getRegionFile(new ChunkPos(chunkX, chunkZ), existingOnly); -- } -- } catch (final IOException ex) { -- throw new RuntimeException(ex); -- } -- -- return function.apply(regionFile); -- } -- } -- -- public T computeForRegionFileIfLoaded(final int chunkX, final int chunkZ, final Function function) { -- final RegionFileStorage cache = this.getCache(); -- final RegionFile regionFile; -- -- synchronized (cache) { -- regionFile = ((ChunkSystemRegionFileStorage)(Object)cache).moonrise$getRegionFileIfLoaded(chunkX, chunkZ); -- -- return function.apply(regionFile); -- } -- } -- } -- -- private static final class ChunkDataTask implements Runnable { -- -- private static final CompoundTag NOTHING_TO_WRITE = new CompoundTag(); -- -- private static final Logger LOGGER = LoggerFactory.getLogger(ChunkDataTask.class); -- -- private InProgressRead inProgressRead; -- private volatile CompoundTag inProgressWrite = NOTHING_TO_WRITE; // only needs to be acquire/release -- -- private boolean failedWrite; -- -- private final ServerLevel world; -- private final int chunkX; -- private final int chunkZ; -- private final ChunkDataController taskController; -- -- private final PrioritisedTask prioritisedTask; -- -- /* -- * IO thread will perform reads before writes for a given chunk x and z -- * -- * How reads/writes are scheduled: -- * -- * If read is scheduled while scheduling write, take no special action and just schedule write -- * If read is scheduled while scheduling read and no write is scheduled, chain the read task -- * -- * -- * If write is scheduled while scheduling read, use the pending write data and ret immediately (so no read is scheduled) -- * If write is scheduled while scheduling write (ignore read in progress), overwrite the write in progress data -- * -- * This allows the reads and writes to act as if they occur synchronously to the thread scheduling them, however -- * it fails to properly propagate write failures thanks to writes overwriting each other -- */ -- -- public ChunkDataTask(final ServerLevel world, final int chunkX, final int chunkZ, final ChunkDataController taskController, -- final PrioritisedExecutor executor, final Priority priority) { -- this.world = world; -- this.chunkX = chunkX; -- this.chunkZ = chunkZ; -- this.taskController = taskController; -- this.prioritisedTask = executor.createTask(this, priority); -- } -- -- @Override -- public String toString() { -- return "Task for world: '" + WorldUtil.getWorldName(this.world) + "' at (" + this.chunkX + "," + this.chunkZ + -- ") type: " + this.taskController.type.name() + ", hash: " + this.hashCode(); -- } -- -- @Override -- public void run() { -- final InProgressRead read = this.inProgressRead; -- final long chunkKey = CoordinateUtils.getChunkKey(this.chunkX, this.chunkZ); -- -- if (read != null) { -- final boolean[] canRead = new boolean[] { true }; -- -- if (read.hasNoWaiters()) { -- // cancelled read? go to task controller to confirm -- final ChunkDataTask inMap = this.taskController.tasks.compute(chunkKey, (final long keyInMap, final ChunkDataTask valueInMap) -> { -- if (valueInMap == null) { -- throw new IllegalStateException("Write completed concurrently, expected this task: " + ChunkDataTask.this.toString() + ", report this!"); -- } -- if (valueInMap != ChunkDataTask.this) { -- throw new IllegalStateException("Chunk task mismatch, expected this task: " + ChunkDataTask.this.toString() + ", got: " + valueInMap.toString() + ", report this!"); -- } -- -- if (!read.hasNoWaiters()) { -- return valueInMap; -- } else { -- canRead[0] = false; -- } -- -- return valueInMap.inProgressWrite == NOTHING_TO_WRITE ? null : valueInMap; -- }); -- -- if (inMap == null) { -- // read is cancelled - and no write pending, so we're done -- return; -- } -- // if there is a write in progress, we don't actually have to worry about waiters gaining new entries - -- // the readers will just use the in progress write, so the value in canRead is good to use without -- // further synchronisation. -- } -- -- if (canRead[0]) { -- CompoundTag compound = null; -- Throwable throwable = null; -- -- try { -- compound = this.taskController.readData(this.chunkX, this.chunkZ); -- } catch (final Throwable thr) { -- throwable = thr; -- LOGGER.error("Failed to read chunk data for task: " + this.toString(), thr); -- } -- read.complete(this, compound, throwable); -- } -- } -- -- CompoundTag write = this.inProgressWrite; -- -- if (write == NOTHING_TO_WRITE) { -- final ChunkDataTask inMap = this.taskController.tasks.compute(chunkKey, (final long keyInMap, final ChunkDataTask valueInMap) -> { -- if (valueInMap == null) { -- throw new IllegalStateException("Write completed concurrently, expected this task: " + ChunkDataTask.this.toString() + ", report this!"); -- } -- if (valueInMap != ChunkDataTask.this) { -- throw new IllegalStateException("Chunk task mismatch, expected this task: " + ChunkDataTask.this.toString() + ", got: " + valueInMap.toString() + ", report this!"); -- } -- return valueInMap.inProgressWrite == NOTHING_TO_WRITE ? null : valueInMap; -- }); -- -- if (inMap == null) { -- return; // set the task value to null, indicating we're done -- } // else: inProgressWrite changed, so now we have something to write -- } -- -- for (;;) { -- write = this.inProgressWrite; -- final CompoundTag dataWritten = write; -- -- boolean failedWrite = false; -- -- try { -- this.taskController.writeData(this.chunkX, this.chunkZ, write); -- } catch (final Throwable thr) { -- if (thr instanceof RegionFileStorage.RegionFileSizeException) { -- final int maxSize = RegionFile.MAX_CHUNK_SIZE / (1024 * 1024); -- LOGGER.error("Chunk at (" + this.chunkX + "," + this.chunkZ + ") in '" + WorldUtil.getWorldName(this.world) + "' exceeds max size of " + maxSize + "MiB, it has been deleted from disk."); -- } else { -- failedWrite = thr instanceof IOException; -- LOGGER.error("Failed to write chunk data for task: " + this.toString(), thr); -- } -- } -- -- final boolean finalFailWrite = failedWrite; -- final boolean[] done = new boolean[] { false }; -- -- this.taskController.tasks.compute(chunkKey, (final long keyInMap, final ChunkDataTask valueInMap) -> { -- if (valueInMap == null) { -- throw new IllegalStateException("Write completed concurrently, expected this task: " + ChunkDataTask.this.toString() + ", report this!"); -- } -- if (valueInMap != ChunkDataTask.this) { -- throw new IllegalStateException("Chunk task mismatch, expected this task: " + ChunkDataTask.this.toString() + ", got: " + valueInMap.toString() + ", report this!"); -- } -- if (valueInMap.inProgressWrite == dataWritten) { -- valueInMap.failedWrite = finalFailWrite; -- done[0] = true; -- // keep the data in map if we failed the write so we can try to prevent data loss -- return finalFailWrite ? valueInMap : null; -- } -- // different data than expected, means we need to retry write -- return valueInMap; -- }); -- -- if (done[0]) { -- return; -- } -- -- // fetch & write new data -- continue; -- } -- } -- } --} -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/ChunkDataController.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/ChunkDataController.java -index c35e0c29700be48dda3e53e7d2db224766ef17b7..a36ab89f5c37f5f9ab0152f087bb4cf3560f8581 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/ChunkDataController.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/ChunkDataController.java -@@ -1,22 +1,24 @@ - package ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller; - --import ca.spottedleaf.moonrise.patches.chunk_system.io.RegionFileIOThread; -+import ca.spottedleaf.moonrise.patches.chunk_system.io.ChunkSystemRegionFileStorage; -+import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO; -+import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemChunkMap; -+import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler; - import ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemChunkStorage; - import net.minecraft.nbt.CompoundTag; - import net.minecraft.server.level.ServerLevel; - import net.minecraft.world.level.ChunkPos; - import net.minecraft.world.level.chunk.storage.RegionFileStorage; - import java.io.IOException; --import java.util.Optional; - import java.util.concurrent.CompletableFuture; - import java.util.concurrent.CompletionException; - --public final class ChunkDataController extends RegionFileIOThread.ChunkDataController { -+public final class ChunkDataController extends MoonriseRegionFileIO.RegionDataController { - - private final ServerLevel world; - -- public ChunkDataController(final ServerLevel world) { -- super(RegionFileIOThread.RegionFileType.CHUNK_DATA); -+ public ChunkDataController(final ServerLevel world, final ChunkTaskScheduler taskScheduler) { -+ super(MoonriseRegionFileIO.RegionFileType.CHUNK_DATA, taskScheduler.ioExecutor, taskScheduler.compressionExecutor); - this.world = world; - } - -@@ -26,31 +28,23 @@ public final class ChunkDataController extends RegionFileIOThread.ChunkDataContr - } - - @Override -- public void writeData(final int chunkX, final int chunkZ, final CompoundTag compound) throws IOException { -- final CompletableFuture future = this.world.getChunkSource().chunkMap.write(new ChunkPos(chunkX, chunkZ), compound); -- -- try { -- if (future != null) { -- // rets non-null when sync writing (i.e. future should be completed here) -- future.join(); -- } -- } catch (final CompletionException ex) { -- if (ex.getCause() instanceof IOException ioException) { -- throw ioException; -- } -- throw ex; -- } -+ public WriteData startWrite(final int chunkX, final int chunkZ, final CompoundTag compound) throws IOException { -+ return ((ChunkSystemRegionFileStorage)this.getCache()).moonrise$startWrite(chunkX, chunkZ, compound); - } - - @Override -- public CompoundTag readData(final int chunkX, final int chunkZ) throws IOException { -- try { -- return this.world.getChunkSource().chunkMap.read(new ChunkPos(chunkX, chunkZ)).join().orElse(null); -- } catch (final CompletionException ex) { -- if (ex.getCause() instanceof IOException ioException) { -- throw ioException; -- } -- throw ex; -- } -+ public void finishWrite(final int chunkX, final int chunkZ, final WriteData writeData) throws IOException { -+ ((ChunkSystemChunkMap)this.world.getChunkSource().chunkMap).moonrise$writeFinishCallback(new ChunkPos(chunkX, chunkZ)); -+ ((ChunkSystemRegionFileStorage)this.getCache()).moonrise$finishWrite(chunkX, chunkZ, writeData); -+ } -+ -+ @Override -+ public ReadData readData(final int chunkX, final int chunkZ) throws IOException { -+ return ((ChunkSystemRegionFileStorage)this.getCache()).moonrise$readData(chunkX, chunkZ); -+ } -+ -+ @Override -+ public CompoundTag finishRead(final int chunkX, final int chunkZ, final ReadData readData) throws IOException { -+ return ((ChunkSystemRegionFileStorage)this.getCache()).moonrise$finishRead(chunkX, chunkZ, readData); - } - } -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/EntityDataController.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/EntityDataController.java -index fdd189ef056187941d43809c5d61cab717aecf60..828c868f68c2a20bf90d0f7ec253fdeb591f15f6 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/EntityDataController.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/EntityDataController.java -@@ -1,6 +1,8 @@ - package ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller; - --import ca.spottedleaf.moonrise.patches.chunk_system.io.RegionFileIOThread; -+import ca.spottedleaf.moonrise.patches.chunk_system.io.ChunkSystemRegionFileStorage; -+import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO; -+import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler; - import net.minecraft.nbt.CompoundTag; - import net.minecraft.world.level.ChunkPos; - import net.minecraft.world.level.chunk.storage.EntityStorage; -@@ -9,12 +11,12 @@ import net.minecraft.world.level.chunk.storage.RegionStorageInfo; - import java.io.IOException; - import java.nio.file.Path; - --public final class EntityDataController extends RegionFileIOThread.ChunkDataController { -+public final class EntityDataController extends MoonriseRegionFileIO.RegionDataController { - - private final EntityRegionFileStorage storage; - -- public EntityDataController(final EntityRegionFileStorage storage) { -- super(RegionFileIOThread.RegionFileType.ENTITY_DATA); -+ public EntityDataController(final EntityRegionFileStorage storage, final ChunkTaskScheduler taskScheduler) { -+ super(MoonriseRegionFileIO.RegionFileType.ENTITY_DATA, taskScheduler.ioExecutor, taskScheduler.compressionExecutor); - this.storage = storage; - } - -@@ -24,13 +26,35 @@ public final class EntityDataController extends RegionFileIOThread.ChunkDataCont - } - - @Override -- public void writeData(final int chunkX, final int chunkZ, final CompoundTag compound) throws IOException { -- this.storage.write(new ChunkPos(chunkX, chunkZ), compound); -+ public WriteData startWrite(final int chunkX, final int chunkZ, final CompoundTag compound) throws IOException { -+ checkPosition(new ChunkPos(chunkX, chunkZ), compound); -+ -+ return ((ChunkSystemRegionFileStorage)this.getCache()).moonrise$startWrite(chunkX, chunkZ, compound); -+ } -+ -+ @Override -+ public void finishWrite(final int chunkX, final int chunkZ, final WriteData writeData) throws IOException { -+ ((ChunkSystemRegionFileStorage)this.getCache()).moonrise$finishWrite(chunkX, chunkZ, writeData); -+ } -+ -+ @Override -+ public ReadData readData(final int chunkX, final int chunkZ) throws IOException { -+ return ((ChunkSystemRegionFileStorage)this.getCache()).moonrise$readData(chunkX, chunkZ); - } - - @Override -- public CompoundTag readData(final int chunkX, final int chunkZ) throws IOException { -- return this.storage.read(new ChunkPos(chunkX, chunkZ)); -+ public CompoundTag finishRead(final int chunkX, final int chunkZ, final ReadData readData) throws IOException { -+ return ((ChunkSystemRegionFileStorage)this.getCache()).moonrise$finishRead(chunkX, chunkZ, readData); -+ } -+ -+ private static void checkPosition(final ChunkPos pos, final CompoundTag nbt) { -+ final ChunkPos nbtPos = nbt == null ? null : EntityStorage.readChunkPos(nbt); -+ if (nbtPos != null && !pos.equals(nbtPos)) { -+ throw new IllegalArgumentException( -+ "Entity chunk coordinate and serialized data do not have matching coordinates, trying to serialize coordinate " + pos.toString() -+ + " but compound says coordinate is " + nbtPos -+ ); -+ } - } - - public static final class EntityRegionFileStorage extends RegionFileStorage { -@@ -42,13 +66,7 @@ public final class EntityDataController extends RegionFileIOThread.ChunkDataCont - - @Override - public void write(final ChunkPos pos, final CompoundTag nbt) throws IOException { -- final ChunkPos nbtPos = nbt == null ? null : EntityStorage.readChunkPos(nbt); -- if (nbtPos != null && !pos.equals(nbtPos)) { -- throw new IllegalArgumentException( -- "Entity chunk coordinate and serialized data do not have matching coordinates, trying to serialize coordinate " + pos.toString() -- + " but compound says coordinate is " + nbtPos + " for world: " + this -- ); -- } -+ checkPosition(pos, nbt); - super.write(pos, nbt); - } - } -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/PoiDataController.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/PoiDataController.java -index af867f8fedd0bb8f675e94243aa1a3f17363483b..bd0d782852f9cfe5bc0b5339ecf4d82c10332ec9 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/PoiDataController.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/PoiDataController.java -@@ -1,18 +1,20 @@ - package ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller; - --import ca.spottedleaf.moonrise.patches.chunk_system.io.RegionFileIOThread; -+import ca.spottedleaf.moonrise.patches.chunk_system.io.ChunkSystemRegionFileStorage; -+import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO; - import ca.spottedleaf.moonrise.patches.chunk_system.level.storage.ChunkSystemSectionStorage; -+import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler; - import net.minecraft.nbt.CompoundTag; - import net.minecraft.server.level.ServerLevel; - import net.minecraft.world.level.chunk.storage.RegionFileStorage; - import java.io.IOException; - --public final class PoiDataController extends RegionFileIOThread.ChunkDataController { -+public final class PoiDataController extends MoonriseRegionFileIO.RegionDataController { - - private final ServerLevel world; - -- public PoiDataController(final ServerLevel world) { -- super(RegionFileIOThread.RegionFileType.POI_DATA); -+ public PoiDataController(final ServerLevel world, final ChunkTaskScheduler taskScheduler) { -+ super(MoonriseRegionFileIO.RegionFileType.POI_DATA, taskScheduler.ioExecutor, taskScheduler.compressionExecutor); - this.world = world; - } - -@@ -22,12 +24,22 @@ public final class PoiDataController extends RegionFileIOThread.ChunkDataControl - } - - @Override -- public void writeData(final int chunkX, final int chunkZ, final CompoundTag compound) throws IOException { -- ((ChunkSystemSectionStorage)this.world.getPoiManager()).moonrise$write(chunkX, chunkZ, compound); -+ public WriteData startWrite(final int chunkX, final int chunkZ, final CompoundTag compound) throws IOException { -+ return ((ChunkSystemRegionFileStorage)this.getCache()).moonrise$startWrite(chunkX, chunkZ, compound); - } - - @Override -- public CompoundTag readData(final int chunkX, final int chunkZ) throws IOException { -- return ((ChunkSystemSectionStorage)this.world.getPoiManager()).moonrise$read(chunkX, chunkZ); -+ public void finishWrite(final int chunkX, final int chunkZ, final WriteData writeData) throws IOException { -+ ((ChunkSystemRegionFileStorage)this.getCache()).moonrise$finishWrite(chunkX, chunkZ, writeData); -+ } -+ -+ @Override -+ public ReadData readData(final int chunkX, final int chunkZ) throws IOException { -+ return ((ChunkSystemRegionFileStorage)this.getCache()).moonrise$readData(chunkX, chunkZ); -+ } -+ -+ @Override -+ public CompoundTag finishRead(final int chunkX, final int chunkZ, final ReadData readData) throws IOException { -+ return ((ChunkSystemRegionFileStorage)this.getCache()).moonrise$finishRead(chunkX, chunkZ, readData); - } - } -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemChunkMap.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemChunkMap.java -new file mode 100644 -index 0000000000000000000000000000000000000000..47a4d3376d08dde94a39254bec21473ff27f53e6 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemChunkMap.java -@@ -0,0 +1,10 @@ -+package ca.spottedleaf.moonrise.patches.chunk_system.level; -+ -+import net.minecraft.world.level.ChunkPos; -+import java.io.IOException; -+ -+public interface ChunkSystemChunkMap { -+ -+ public void moonrise$writeFinishCallback(final ChunkPos pos) throws IOException; -+ -+} -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemLevel.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemLevel.java -index efcd9057f008f0b9cf0d22b2b21d1851205841e5..5d4d650186b18eb00782429d53d861564d8e4ba9 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemLevel.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemLevel.java -@@ -1,5 +1,6 @@ - package ca.spottedleaf.moonrise.patches.chunk_system.level; - -+import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData; - import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.EntityLookup; - import net.minecraft.world.level.chunk.ChunkAccess; - import net.minecraft.world.level.chunk.LevelChunk; -@@ -19,4 +20,14 @@ public interface ChunkSystemLevel { - - public void moonrise$midTickTasks(); - -+ public ChunkData moonrise$getChunkData(final long chunkKey); -+ -+ public ChunkData moonrise$getChunkData(final int chunkX, final int chunkZ); -+ -+ public ChunkData moonrise$requestChunkData(final long chunkKey); -+ -+ public ChunkData moonrise$releaseChunkData(final long chunkKey); -+ -+ public boolean moonrise$areChunksLoaded(final int fromX, final int fromZ, final int toX, final int toZ); -+ - } -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemServerLevel.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemServerLevel.java -index b8a87b7e6505feb76ce1bd58c84615256cf6faa6..9d46482476f9ed9032a2b0f89afc20e03ed42dbb 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemServerLevel.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemServerLevel.java -@@ -1,12 +1,13 @@ - package ca.spottedleaf.moonrise.patches.chunk_system.level; - --import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.util.Priority; - import ca.spottedleaf.moonrise.common.list.ReferenceList; - import ca.spottedleaf.moonrise.common.misc.NearbyPlayers; --import ca.spottedleaf.moonrise.patches.chunk_system.io.RegionFileIOThread; -+import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO; - import ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader; - import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler; - import net.minecraft.core.BlockPos; -+import net.minecraft.server.level.ChunkHolder; - import net.minecraft.server.level.ServerChunkCache; - import net.minecraft.world.level.chunk.ChunkAccess; - import net.minecraft.world.level.chunk.status.ChunkStatus; -@@ -17,32 +18,34 @@ public interface ChunkSystemServerLevel extends ChunkSystemLevel { - - public ChunkTaskScheduler moonrise$getChunkTaskScheduler(); - -- public RegionFileIOThread.ChunkDataController moonrise$getChunkDataController(); -+ public MoonriseRegionFileIO.RegionDataController moonrise$getChunkDataController(); - -- public RegionFileIOThread.ChunkDataController moonrise$getPoiChunkDataController(); -+ public MoonriseRegionFileIO.RegionDataController moonrise$getPoiChunkDataController(); - -- public RegionFileIOThread.ChunkDataController moonrise$getEntityChunkDataController(); -+ public MoonriseRegionFileIO.RegionDataController moonrise$getEntityChunkDataController(); - - public int moonrise$getRegionChunkShift(); - -- // Paper - marked closing not needed on CB -+ public boolean moonrise$isMarkedClosing(); -+ -+ public void moonrise$setMarkedClosing(final boolean value); - - public RegionizedPlayerChunkLoader moonrise$getPlayerChunkLoader(); - - public void moonrise$loadChunksAsync(final BlockPos pos, final int radiusBlocks, -- final PrioritisedExecutor.Priority priority, -+ final Priority priority, - final Consumer> onLoad); - - public void moonrise$loadChunksAsync(final BlockPos pos, final int radiusBlocks, -- final ChunkStatus chunkStatus, final PrioritisedExecutor.Priority priority, -+ final ChunkStatus chunkStatus, final Priority priority, - final Consumer> onLoad); - - public void moonrise$loadChunksAsync(final int minChunkX, final int maxChunkX, final int minChunkZ, final int maxChunkZ, -- final PrioritisedExecutor.Priority priority, -+ final Priority priority, - final Consumer> onLoad); - - public void moonrise$loadChunksAsync(final int minChunkX, final int maxChunkX, final int minChunkZ, final int maxChunkZ, -- final ChunkStatus chunkStatus, final PrioritisedExecutor.Priority priority, -+ final ChunkStatus chunkStatus, final Priority priority, - final Consumer> onLoad); - - public RegionizedPlayerChunkLoader.ViewDistanceHolder moonrise$getViewDistanceHolder(); -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkData.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkData.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8b9dc582627b46843f4b5ea6f8c3df2d8cac46fa ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkData.java -@@ -0,0 +1,21 @@ -+package ca.spottedleaf.moonrise.patches.chunk_system.level.chunk; -+ -+import ca.spottedleaf.moonrise.common.misc.NearbyPlayers; -+ -+public final class ChunkData { -+ -+ private int referenceCount = 0; -+ public NearbyPlayers.TrackedChunk nearbyPlayers; // Moonrise - nearby players -+ -+ public ChunkData() { -+ -+ } -+ -+ public int increaseRef() { -+ return ++this.referenceCount; -+ } -+ -+ public int decreaseRef() { -+ return --this.referenceCount; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemDistanceManager.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemDistanceManager.java -index 883fe6401f1b9711fa544d18a815b4d638f580df..aacd543f03b35908011d0c2891e978cc093ebcf5 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemDistanceManager.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemDistanceManager.java -@@ -1,9 +1,12 @@ - package ca.spottedleaf.moonrise.patches.chunk_system.level.chunk; - -+import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager; - import net.minecraft.server.level.ChunkMap; - - public interface ChunkSystemDistanceManager { - - public ChunkMap moonrise$getChunkMap(); - -+ public ChunkHolderManager moonrise$getChunkHolderManager(); -+ - } -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java -index 997b05167c19472acb98edac32d4548cc65efa8e..5ed6599d1f9a2edf8c904f3602b06d26d857600c 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java -@@ -1,6 +1,8 @@ - package ca.spottedleaf.moonrise.patches.chunk_system.level.entity; - -+import ca.spottedleaf.moonrise.common.PlatformHooks; - import ca.spottedleaf.moonrise.common.list.EntityList; -+import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData; - import ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity; - import com.google.common.collect.ImmutableList; - import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; -@@ -13,6 +15,7 @@ import net.minecraft.server.level.FullChunkStatus; - import net.minecraft.server.level.ServerLevel; - import net.minecraft.util.Mth; - import net.minecraft.world.entity.Entity; -+import net.minecraft.world.entity.EntitySpawnReason; - import net.minecraft.world.entity.EntityType; - import net.minecraft.world.entity.boss.EnderDragonPart; - import net.minecraft.world.entity.boss.enderdragon.EnderDragon; -@@ -26,7 +29,6 @@ import java.util.Arrays; - import java.util.Iterator; - import java.util.List; - import java.util.function.Predicate; --import org.bukkit.event.entity.EntityRemoveEvent; - - public final class ChunkEntitySlices { - -@@ -43,6 +45,7 @@ public final class ChunkEntitySlices { - private final EntityList entities = new EntityList(); - - public FullChunkStatus status; -+ public final ChunkData chunkData; - - private boolean isTransient; - -@@ -55,7 +58,7 @@ public final class ChunkEntitySlices { - } - - public ChunkEntitySlices(final Level world, final int chunkX, final int chunkZ, final FullChunkStatus status, -- final int minSection, final int maxSection) { // inclusive, inclusive -+ final ChunkData chunkData, final int minSection, final int maxSection) { // inclusive, inclusive - this.minSection = minSection; - this.maxSection = maxSection; - this.chunkX = chunkX; -@@ -68,11 +71,12 @@ public final class ChunkEntitySlices { - this.entitiesByType = new Reference2ObjectOpenHashMap<>(); - - this.status = status; -+ this.chunkData = chunkData; - } - - public static List readEntities(final ServerLevel world, final CompoundTag compoundTag) { - // TODO check this and below on update for format changes -- return EntityType.loadEntitiesRecursive(compoundTag.getList("Entities", 10), world).collect(ImmutableList.toImmutableList()); -+ return EntityType.loadEntitiesRecursive(compoundTag.getList("Entities", 10), world, EntitySpawnReason.LOAD).collect(ImmutableList.toImmutableList()); - } - - // Paper start - rewrite chunk system -@@ -100,7 +104,7 @@ public final class ChunkEntitySlices { - } - - final ListTag entitiesTag = new ListTag(); -- for (final Entity entity : entities) { -+ for (final Entity entity : PlatformHooks.get().modifySavedEntities(world, chunkPos.x, chunkPos.z, entities)) { - CompoundTag compoundTag = new CompoundTag(); - if (entity.save(compoundTag)) { - entitiesTag.add(compoundTag); -@@ -147,12 +151,12 @@ public final class ChunkEntitySlices { - continue; - } - if (entity.shouldBeSaved()) { -- entity.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK, EntityRemoveEvent.Cause.UNLOAD); -+ PlatformHooks.get().unloadEntity(entity); - if (entity.isVehicle()) { - // we cannot assume that these entities are contained within this chunk, because entities can - // desync - so we need to remove them all - for (final Entity passenger : entity.getIndirectPassengers()) { -- passenger.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK, EntityRemoveEvent.Cause.UNLOAD); -+ PlatformHooks.get().unloadEntity(passenger); - } - } - } -@@ -161,34 +165,7 @@ public final class ChunkEntitySlices { - return this.entities.size() != 0; - } - -- // Paper start -- public org.bukkit.entity.Entity[] getChunkEntities() { -- List ret = new java.util.ArrayList<>(); -- final Entity[] entities = this.entities.getRawData(); -- for (int i = 0, size = Math.min(entities.length, this.entities.size()); i < size; ++i) { -- final Entity entity = entities[i]; -- if (entity == null) { -- continue; -- } -- final org.bukkit.entity.Entity bukkit = entity.getBukkitEntity(); -- if (bukkit != null && bukkit.isValid()) { -- ret.add(bukkit); -- } -- } -- -- return ret.toArray(new org.bukkit.entity.Entity[0]); -- } -- -- public void callEntitiesLoadEvent() { -- org.bukkit.craftbukkit.event.CraftEventFactory.callEntitiesLoadEvent(this.world, new ChunkPos(this.chunkX, this.chunkZ), this.getAllEntities()); -- } -- -- public void callEntitiesUnloadEvent() { -- org.bukkit.craftbukkit.event.CraftEventFactory.callEntitiesUnloadEvent(this.world, new ChunkPos(this.chunkX, this.chunkZ), this.getAllEntities()); -- } -- // Paper end -- -- private List getAllEntities() { -+ public List getAllEntities() { - final int len = this.entities.size(); - if (len == 0) { - return new ArrayList<>(); -@@ -251,6 +228,7 @@ public final class ChunkEntitySlices { - return false; - } - ((ChunkSystemEntity)entity).moonrise$setChunkStatus(this.status); -+ ((ChunkSystemEntity)entity).moonrise$setChunkData(this.chunkData); - final int sectionIndex = chunkSection - this.minSection; - - this.allEntities.addEntity(entity, sectionIndex); -@@ -284,6 +262,7 @@ public final class ChunkEntitySlices { - return false; - } - ((ChunkSystemEntity)entity).moonrise$setChunkStatus(null); -+ ((ChunkSystemEntity)entity).moonrise$setChunkData(null); - final int sectionIndex = chunkSection - this.minSection; - - this.allEntities.removeEntity(entity, sectionIndex); -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java -index efc0c1acc8239dd7b00211a1d3bfd3fc3b2c810c..93335de8cf514dc8417e4b9b2d495663deda2904 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java -@@ -46,8 +46,6 @@ public abstract class EntityLookup implements LevelEntityGetter { - - protected final SWMRLong2ObjectHashTable regions = new SWMRLong2ObjectHashTable<>(128, 0.5f); - -- protected final int minSection; // inclusive -- protected final int maxSection; // inclusive - protected final LevelCallback worldCallback; - - protected final ConcurrentLong2ReferenceChainedHashTable entityById = new ConcurrentLong2ReferenceChainedHashTable<>(); -@@ -56,8 +54,6 @@ public abstract class EntityLookup implements LevelEntityGetter { - - public EntityLookup(final Level world, final LevelCallback worldCallback) { - this.world = world; -- this.minSection = WorldUtil.getMinSection(world); -- this.maxSection = WorldUtil.getMaxSection(world); - this.worldCallback = worldCallback; - } - -@@ -91,7 +87,7 @@ public abstract class EntityLookup implements LevelEntityGetter { - - protected abstract void entityEndTicking(final Entity entity); - -- protected abstract boolean screenEntity(final Entity entity); -+ protected abstract boolean screenEntity(final Entity entity, final boolean fromDisk, final boolean event); - - private static Entity maskNonAccessible(final Entity entity) { - if (entity == null) { -@@ -347,7 +343,7 @@ public abstract class EntityLookup implements LevelEntityGetter { - } - - protected void addRecursivelySafe(final Entity root, final boolean fromDisk) { -- if (!this.addEntity(root, fromDisk)) { -+ if (!this.addEntity(root, fromDisk, true)) { - // possible we are a passenger, and so should dismount from any valid entity in the world - root.stopRiding(); - return; -@@ -386,7 +382,11 @@ public abstract class EntityLookup implements LevelEntityGetter { - } - - public boolean addNewEntity(final Entity entity) { -- return this.addEntity(entity, false); -+ return this.addNewEntity(entity, true); -+ } -+ -+ public boolean addNewEntity(final Entity entity, final boolean event) { -+ return this.addEntity(entity, false, event); - } - - public static Visibility getEntityStatus(final Entity entity) { -@@ -397,10 +397,10 @@ public abstract class EntityLookup implements LevelEntityGetter { - return Visibility.fromFullChunkStatus(entityStatus == null ? FullChunkStatus.INACCESSIBLE : entityStatus); - } - -- protected boolean addEntity(final Entity entity, final boolean fromDisk) { -+ protected boolean addEntity(final Entity entity, final boolean fromDisk, final boolean event) { - final BlockPos pos = entity.blockPosition(); - final int sectionX = pos.getX() >> 4; -- final int sectionY = Mth.clamp(pos.getY() >> 4, this.minSection, this.maxSection); -+ final int sectionY = Mth.clamp(pos.getY() >> 4, WorldUtil.getMinSection(this.world), WorldUtil.getMaxSection(this.world)); - final int sectionZ = pos.getZ() >> 4; - this.checkThread(sectionX, sectionZ, "Cannot add entity off-main thread"); - -@@ -414,7 +414,7 @@ public abstract class EntityLookup implements LevelEntityGetter { - return false; - } - -- if (!this.screenEntity(entity)) { -+ if (!this.screenEntity(entity, fromDisk, event)) { - return false; - } - -@@ -519,7 +519,7 @@ public abstract class EntityLookup implements LevelEntityGetter { - final int sectionZ = ((ChunkSystemEntity)entity).moonrise$getSectionZ(); - final BlockPos newPos = entity.blockPosition(); - final int newSectionX = newPos.getX() >> 4; -- final int newSectionY = Mth.clamp(newPos.getY() >> 4, this.minSection, this.maxSection); -+ final int newSectionY = Mth.clamp(newPos.getY() >> 4, WorldUtil.getMinSection(this.world), WorldUtil.getMaxSection(this.world)); - final int newSectionZ = newPos.getZ() >> 4; - - if (newSectionX == sectionX && newSectionY == sectionY && newSectionZ == sectionZ) { -@@ -959,7 +959,7 @@ public abstract class EntityLookup implements LevelEntityGetter { - - public ChunkEntitySlices getOrCreateChunk(final int chunkX, final int chunkZ) { - final ChunkSlicesRegion region = this.getRegion(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT); -- ChunkEntitySlices ret; -+ final ChunkEntitySlices ret; - if (region == null || (ret = region.get((chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT))) == null) { - return this.createEntityChunk(chunkX, chunkZ, true); - } -@@ -1057,7 +1057,7 @@ public abstract class EntityLookup implements LevelEntityGetter { - @Override - public void onRemove(final Entity.RemovalReason reason) { - final Entity entity = this.entity; -- EntityLookup.this.checkThread(entity, "Cannot remove entity off-main"); // Paper - rewrite chunk system -+ EntityLookup.this.checkThread(entity, "Cannot remove entity off-main"); - final Visibility tickingState = EntityLookup.getEntityStatus(entity); - - EntityLookup.this.removeEntity(entity); -@@ -1080,4 +1080,4 @@ public abstract class EntityLookup implements LevelEntityGetter { - @Override - public void onRemove(final Entity.RemovalReason reason) {} - } --} -\ No newline at end of file -+} -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/client/ClientEntityLookup.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/client/ClientEntityLookup.java -index edcde00206d068bd79175fea33efa05b0e8c1562..a038215156a163b0b1cbc870ada5b4ac85ed1335 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/client/ClientEntityLookup.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/client/ClientEntityLookup.java -@@ -1,5 +1,6 @@ - package ca.spottedleaf.moonrise.patches.chunk_system.level.entity.client; - -+import ca.spottedleaf.moonrise.common.PlatformHooks; - import ca.spottedleaf.moonrise.common.util.CoordinateUtils; - import ca.spottedleaf.moonrise.common.util.WorldUtil; - import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.ChunkEntitySlices; -@@ -45,7 +46,8 @@ public final class ClientEntityLookup extends EntityLookup { - - final ChunkEntitySlices ret = new ChunkEntitySlices( - this.world, chunkX, chunkZ, -- ticking ? FullChunkStatus.ENTITY_TICKING : FullChunkStatus.FULL, WorldUtil.getMinSection(this.world), WorldUtil.getMaxSection(this.world) -+ ticking ? FullChunkStatus.ENTITY_TICKING : FullChunkStatus.FULL, null, -+ WorldUtil.getMinSection(this.world), WorldUtil.getMaxSection(this.world) - ); - - // note: not handled by superclass -@@ -63,7 +65,11 @@ public final class ClientEntityLookup extends EntityLookup { - protected void entitySectionChangeCallback(final Entity entity, - final int oldSectionX, final int oldSectionY, final int oldSectionZ, - final int newSectionX, final int newSectionY, final int newSectionZ) { -- -+ PlatformHooks.get().entityMove( -+ entity, -+ CoordinateUtils.getChunkSectionKey(oldSectionX, oldSectionY, oldSectionZ), -+ CoordinateUtils.getChunkSectionKey(newSectionX, newSectionY, newSectionZ) -+ ); - } - - @Override -@@ -97,7 +103,7 @@ public final class ClientEntityLookup extends EntityLookup { - } - - @Override -- protected boolean screenEntity(final Entity entity) { -+ protected boolean screenEntity(final Entity entity, final boolean fromDisk, final boolean event) { - return true; - } - -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/dfl/DefaultEntityLookup.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/dfl/DefaultEntityLookup.java -index 465469e44346c50f30f3abd6b44f4173ccfcf248..2ff58cf753c60913ee73aae015182e9c5560d529 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/dfl/DefaultEntityLookup.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/dfl/DefaultEntityLookup.java -@@ -32,7 +32,7 @@ public final class DefaultEntityLookup extends EntityLookup { - protected ChunkEntitySlices createEntityChunk(final int chunkX, final int chunkZ, final boolean transientChunk) { - final ChunkEntitySlices ret = new ChunkEntitySlices( - this.world, chunkX, chunkZ, FullChunkStatus.FULL, -- WorldUtil.getMinSection(this.world), WorldUtil.getMaxSection(this.world) -+ null, WorldUtil.getMinSection(this.world), WorldUtil.getMaxSection(this.world) - ); - - // note: not handled by superclass -@@ -84,7 +84,7 @@ public final class DefaultEntityLookup extends EntityLookup { - } - - @Override -- protected boolean screenEntity(final Entity entity) { -+ protected boolean screenEntity(final Entity entity, final boolean fromDisk, final boolean event) { - return true; - } - -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java -index dacf2b2988ce603879fe525a3418ac77f8a663f7..58d9187adc188b693b6becc400f766e069bf1bf5 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java -@@ -1,6 +1,8 @@ - package ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server; - -+import ca.spottedleaf.moonrise.common.PlatformHooks; - import ca.spottedleaf.moonrise.common.list.ReferenceList; -+import ca.spottedleaf.moonrise.common.util.CoordinateUtils; - import ca.spottedleaf.moonrise.common.util.TickThread; - import ca.spottedleaf.moonrise.common.util.ChunkSystem; - import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel; -@@ -17,7 +19,6 @@ public final class ServerEntityLookup extends EntityLookup { - - private final ServerLevel serverWorld; - public final ReferenceList trackerEntities = new ReferenceList<>(EMPTY_ENTITY_ARRAY); // Moonrise - entity tracker -- public final ReferenceList trackerUnloadedEntities = new ReferenceList<>(EMPTY_ENTITY_ARRAY); // Moonrise - entity tracker - - public ServerEntityLookup(final ServerLevel world, final LevelCallback worldCallback) { - super(world, worldCallback); -@@ -63,6 +64,11 @@ public final class ServerEntityLookup extends EntityLookup { - if (entity instanceof ServerPlayer player) { - ((ChunkSystemServerLevel)this.serverWorld).moonrise$getNearbyPlayers().tickPlayer(player); - } -+ PlatformHooks.get().entityMove( -+ entity, -+ CoordinateUtils.getChunkSectionKey(oldSectionX, oldSectionY, oldSectionZ), -+ CoordinateUtils.getChunkSectionKey(newSectionX, newSectionY, newSectionZ) -+ ); - } - - @Override -@@ -77,14 +83,12 @@ public final class ServerEntityLookup extends EntityLookup { - if (entity instanceof ServerPlayer player) { - ((ChunkSystemServerLevel)this.serverWorld).moonrise$getNearbyPlayers().removePlayer(player); - } -- this.trackerUnloadedEntities.remove(entity); // Moonrise - entity tracker - } - - @Override - protected void entityStartLoaded(final Entity entity) { - // Moonrise start - entity tracker - this.trackerEntities.add(entity); -- this.trackerUnloadedEntities.remove(entity); - // Moonrise end - entity tracker - } - -@@ -92,7 +96,6 @@ public final class ServerEntityLookup extends EntityLookup { - protected void entityEndLoaded(final Entity entity) { - // Moonrise start - entity tracker - this.trackerEntities.remove(entity); -- this.trackerUnloadedEntities.add(entity); - // Moonrise end - entity tracker - } - -@@ -107,7 +110,7 @@ public final class ServerEntityLookup extends EntityLookup { - } - - @Override -- protected boolean screenEntity(final Entity entity) { -- return ChunkSystem.screenEntity(this.serverWorld, entity); -+ protected boolean screenEntity(final Entity entity, final boolean fromDisk, final boolean event) { -+ return ChunkSystem.screenEntity(this.serverWorld, entity, fromDisk, event); - } - } -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/PoiChunk.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/PoiChunk.java -index fd35e4db0c8fec8f86b8743bcc2b15ed2e7433f1..bbf9d6c1c9525d97160806819a57be03eca290f1 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/PoiChunk.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/PoiChunk.java -@@ -3,7 +3,6 @@ package ca.spottedleaf.moonrise.patches.chunk_system.level.poi; - import ca.spottedleaf.moonrise.common.util.CoordinateUtils; - import ca.spottedleaf.moonrise.common.util.TickThread; - import ca.spottedleaf.moonrise.common.util.WorldUtil; --import com.mojang.serialization.Codec; - import com.mojang.serialization.DataResult; - import net.minecraft.SharedConstants; - import net.minecraft.nbt.CompoundTag; -@@ -123,7 +122,6 @@ public final class PoiChunk { - ret.putInt("DataVersion", SharedConstants.getCurrentVersion().getDataVersion().getVersion()); - - final ServerLevel world = this.world; -- final PoiManager poiManager = world.getPoiManager(); - final int chunkX = this.chunkX; - final int chunkZ = this.chunkZ; - -@@ -133,13 +131,8 @@ public final class PoiChunk { - continue; - } - -- final long key = CoordinateUtils.getChunkSectionKey(chunkX, sectionY, chunkZ); -- // codecs are honestly such a fucking disaster. What the fuck is this trash? -- final Codec codec = PoiSection.codec(() -> { -- poiManager.setDirty(key); -- }); -- -- final DataResult serializedResult = codec.encodeStart(registryOps, section); -+ // I do not believe asynchronously converting to CompoundTag is worth the scheduling. -+ final DataResult serializedResult = PoiSection.Packed.CODEC.encodeStart(registryOps, section.pack()); - final int finalSectionY = sectionY; - final Tag serialized = serializedResult.resultOrPartial((final String description) -> { - LOGGER.error("Failed to serialize poi chunk for world: " + WorldUtil.getWorldName(world) + ", chunk: (" + chunkX + "," + finalSectionY + "," + chunkZ + "); description: " + description); -@@ -183,19 +176,18 @@ public final class PoiChunk { - continue; - } - -- final long coordinateKey = CoordinateUtils.getChunkSectionKey(chunkX, sectionY, chunkZ); -- // codecs are honestly such a fucking disaster. What the fuck is this trash? -- final Codec codec = PoiSection.codec(() -> { -- poiManager.setDirty(coordinateKey); -- }); -- - final CompoundTag section = sections.getCompound(key); -- final DataResult deserializeResult = codec.parse(registryOps, section); -+ final DataResult deserializeResult = PoiSection.Packed.CODEC.parse(registryOps, section); - final int finalSectionY = sectionY; -- final PoiSection deserialized = deserializeResult.resultOrPartial((final String description) -> { -+ final PoiSection.Packed packed = deserializeResult.resultOrPartial((final String description) -> { - LOGGER.error("Failed to deserialize poi chunk for world: " + WorldUtil.getWorldName(world) + ", chunk: (" + chunkX + "," + finalSectionY + "," + chunkZ + "); description: " + description); - }).orElse(null); - -+ final long coordinateKey = CoordinateUtils.getChunkSectionKey(chunkX, sectionY, chunkZ); -+ final PoiSection deserialized = packed == null ? null : packed.unpack(() -> { -+ poiManager.setDirty(coordinateKey); -+ }); -+ - if (deserialized == null || ((ChunkSystemPoiSection)deserialized).moonrise$isEmpty()) { - // completely empty, no point in storing this - continue; -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/storage/ChunkSystemSectionStorage.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/storage/ChunkSystemSectionStorage.java -index fb87d7ece6ebccfd0ffd2f1a609b45a0d2461d9e..524752744e37a2db0e3ea089468bdf497129bfef 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/storage/ChunkSystemSectionStorage.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/storage/ChunkSystemSectionStorage.java -@@ -1,12 +1,8 @@ - package ca.spottedleaf.moonrise.patches.chunk_system.level.storage; - --import com.mojang.serialization.Dynamic; - import net.minecraft.nbt.CompoundTag; --import net.minecraft.nbt.Tag; - import net.minecraft.world.level.chunk.storage.RegionFileStorage; - import java.io.IOException; --import java.util.Optional; --import java.util.concurrent.CompletableFuture; - - public interface ChunkSystemSectionStorage { - -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java -index 852d75a73dae7448cbe1e2f5e164b235efa8a969..b2fa9883aefb07f64bb5db7e0052218d2ad09aba 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java -@@ -1,7 +1,8 @@ - package ca.spottedleaf.moonrise.patches.chunk_system.player; - --import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; - import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import ca.spottedleaf.concurrentutil.util.Priority; -+import ca.spottedleaf.moonrise.common.PlatformHooks; - import ca.spottedleaf.moonrise.common.misc.AllocatingRateLimiter; - import ca.spottedleaf.moonrise.common.misc.SingleUserAreaMap; - import ca.spottedleaf.moonrise.common.util.CoordinateUtils; -@@ -412,7 +413,11 @@ public final class RegionizedPlayerChunkLoader { - if (this.sentChunks.add(CoordinateUtils.getChunkKey(chunkX, chunkZ))) { - ((ChunkSystemChunkHolder)((ChunkSystemServerLevel)this.world).moonrise$getChunkTaskScheduler().chunkHolderManager - .getChunkHolder(chunkX, chunkZ).vanillaChunkHolder).moonrise$addReceivedChunk(this.player); -- PlayerChunkSender.sendChunk(this.player.connection, this.world, ((ChunkSystemLevel)this.world).moonrise$getFullChunkIfLoaded(chunkX, chunkZ)); -+ -+ final LevelChunk chunk = ((ChunkSystemLevel)this.world).moonrise$getFullChunkIfLoaded(chunkX, chunkZ); -+ -+ PlatformHooks.get().onChunkWatch(this.world, chunk, this.player); -+ PlayerChunkSender.sendChunk(this.player.connection, this.world, chunk); - return; - } - throw new IllegalStateException(); -@@ -426,17 +431,12 @@ public final class RegionizedPlayerChunkLoader { - } - - private void sendUnloadChunkRaw(final int chunkX, final int chunkZ) { -+ PlatformHooks.get().onChunkUnWatch(this.world, new ChunkPos(chunkX, chunkZ), this.player); - // Note: Check PlayerChunkSender#dropChunk for other logic - // Note: drop isAlive() check so that chunks properly unload client-side when the player dies - ((ChunkSystemChunkHolder)((ChunkSystemServerLevel)this.world).moonrise$getChunkTaskScheduler().chunkHolderManager - .getChunkHolder(chunkX, chunkZ).vanillaChunkHolder).moonrise$removeReceivedChunk(this.player); -- final ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ); -- this.player.connection.send(new ClientboundForgetLevelChunkPacket(chunkPos)); -- // Paper start - PlayerChunkUnloadEvent -- if (io.papermc.paper.event.packet.PlayerChunkUnloadEvent.getHandlerList().getRegisteredListeners().length > 0) { -- new io.papermc.paper.event.packet.PlayerChunkUnloadEvent(this.world.getWorld().getChunkAt(chunkPos.longKey), this.player.getBukkitEntity()).callEvent(); -- } -- // Paper end - PlayerChunkUnloadEvent -+ this.player.connection.send(new ClientboundForgetLevelChunkPacket(new ChunkPos(chunkX, chunkZ))); - } - - private final SingleUserAreaMap broadcastMap = new SingleUserAreaMap<>(this) { -@@ -529,7 +529,7 @@ public final class RegionizedPlayerChunkLoader { - final int playerSendViewDistance, final int worldSendViewDistance) { - return Math.min( - loadViewDistance - 1, -- playerSendViewDistance < 0 ? (!io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.autoConfigSendDistance || clientViewDistance < 0 ? (worldSendViewDistance < 0 ? (loadViewDistance - 1) : worldSendViewDistance) : clientViewDistance + 1) : playerSendViewDistance -+ playerSendViewDistance < 0 ? (!PlatformHooks.get().configAutoConfigSendDistance() || clientViewDistance < 0 ? (worldSendViewDistance < 0 ? (loadViewDistance - 1) : worldSendViewDistance) : clientViewDistance + 1) : playerSendViewDistance - ); - } - -@@ -554,26 +554,26 @@ public final class RegionizedPlayerChunkLoader { - } - - private double getMaxChunkLoadRate() { -- final double configRate = io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkLoadRate; -+ final double configRate = PlatformHooks.get().configPlayerMaxLoadRate(); - - return configRate <= 0.0 || configRate > (double)MAX_RATE ? (double)MAX_RATE : Math.max(1.0, configRate); - } - - private double getMaxChunkGenRate() { -- final double configRate = io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkGenerateRate; -+ final double configRate = PlatformHooks.get().configPlayerMaxGenRate(); - - return configRate <= 0.0 || configRate > (double)MAX_RATE ? (double)MAX_RATE : Math.max(1.0, configRate); - } - - private double getMaxChunkSendRate() { -- final double configRate = io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkSendRate; -+ final double configRate = PlatformHooks.get().configPlayerMaxSendRate(); - - return configRate <= 0.0 || configRate > (double)MAX_RATE ? (double)MAX_RATE : Math.max(1.0, configRate); - } - - private long getMaxChunkLoads() { - final long radiusChunks = (2L * this.lastLoadDistance + 1L) * (2L * this.lastLoadDistance + 1L); -- long configLimit = io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkLoads; -+ long configLimit = (long)PlatformHooks.get().configPlayerMaxConcurrentLoads(); - if (configLimit == 0L) { - // by default, only allow 1/5th of the chunks in the view distance to be concurrently active - configLimit = Math.max(5L, radiusChunks / 5L); -@@ -587,7 +587,7 @@ public final class RegionizedPlayerChunkLoader { - - private long getMaxChunkGenerates() { - final long radiusChunks = (2L * this.lastLoadDistance + 1L) * (2L * this.lastLoadDistance + 1L); -- long configLimit = io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkGenerates; -+ long configLimit = (long)PlatformHooks.get().configPlayerMaxConcurrentGens(); - if (configLimit == 0L) { - // by default, only allow 1/5th of the chunks in the view distance to be concurrently active - configLimit = Math.max(5L, radiusChunks / 5L); -@@ -709,7 +709,7 @@ public final class RegionizedPlayerChunkLoader { - final int queuedChunkX = CoordinateUtils.getChunkX(queuedLoadChunk); - final int queuedChunkZ = CoordinateUtils.getChunkZ(queuedLoadChunk); - ((ChunkSystemServerLevel)this.world).moonrise$getChunkTaskScheduler().scheduleChunkLoad( -- queuedChunkX, queuedChunkZ, ChunkStatus.EMPTY, false, PrioritisedExecutor.Priority.NORMAL, null -+ queuedChunkX, queuedChunkZ, ChunkStatus.EMPTY, false, Priority.NORMAL, null - ); - if (this.removed) { - return; -@@ -825,7 +825,7 @@ public final class RegionizedPlayerChunkLoader { - } - if (!((ChunkSystemLevelChunk)chunk).moonrise$isPostProcessingDone()) { - // not yet post-processed, need to do this so that tile entities can properly be sent to clients -- chunk.postProcessGeneration(); -+ chunk.postProcessGeneration(this.world); - // check if there was any recursive action - if (this.removed || this.sendQueue.isEmpty() || this.sendQueue.firstLong() != pendingSend) { - return; -@@ -864,7 +864,6 @@ public final class RegionizedPlayerChunkLoader { - final int clientViewDistance = getClientViewDistance(this.player); - final int sendViewDistance = getSendViewDistance(loadViewDistance, clientViewDistance, playerDistances.sendViewDistance, worldDistances.sendViewDistance); - -- // TODO check PlayerList diff in paper chunk system patch - // send view distances - this.player.connection.send(this.updateClientChunkRadius(sendViewDistance)); - this.player.connection.send(this.updateClientSimulationDistance(tickViewDistance)); -@@ -1078,5 +1077,9 @@ public final class RegionizedPlayerChunkLoader { - - // now all tickets should be removed, which is all of our external state - } -+ -+ public LongOpenHashSet getSentChunksRaw() { -+ return this.sentChunks; -+ } - } - } -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java -index 58d3d1a47e9f2423c467bb329c2d5f4b58a8b5ef..f98df65eaed2abedc66f3a49790e0cfb65354ed9 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java -@@ -1,14 +1,14 @@ - package ca.spottedleaf.moonrise.patches.chunk_system.scheduling; - --import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; - import ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock; - import ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable; -+import ca.spottedleaf.concurrentutil.util.Priority; -+import ca.spottedleaf.moonrise.common.PlatformHooks; - import ca.spottedleaf.moonrise.common.util.CoordinateUtils; --import ca.spottedleaf.moonrise.common.util.MoonriseCommon; - import ca.spottedleaf.moonrise.common.util.TickThread; - import ca.spottedleaf.moonrise.common.util.WorldUtil; - import ca.spottedleaf.moonrise.common.util.ChunkSystem; --import ca.spottedleaf.moonrise.patches.chunk_system.io.RegionFileIOThread; -+import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO; - import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel; - import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.ChunkEntitySlices; - import ca.spottedleaf.moonrise.patches.chunk_system.level.poi.PoiChunk; -@@ -20,7 +20,6 @@ import ca.spottedleaf.moonrise.patches.chunk_system.ticket.ChunkSystemTicket; - import ca.spottedleaf.moonrise.patches.chunk_system.util.ChunkSystemSortedArraySet; - import com.google.gson.JsonArray; - import com.google.gson.JsonObject; --import com.mojang.logging.LogUtils; - import it.unimi.dsi.fastutil.longs.Long2ByteLinkedOpenHashMap; - import it.unimi.dsi.fastutil.longs.Long2ByteMap; - import it.unimi.dsi.fastutil.longs.Long2IntMap; -@@ -40,7 +39,9 @@ import net.minecraft.server.level.TicketType; - import net.minecraft.util.SortedArraySet; - import net.minecraft.util.Unit; - import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.chunk.LevelChunk; - import org.slf4j.Logger; -+import org.slf4j.LoggerFactory; - import java.io.IOException; - import java.text.DecimalFormat; - import java.util.ArrayDeque; -@@ -58,7 +59,7 @@ import java.util.function.Predicate; - - public final class ChunkHolderManager { - -- private static final Logger LOGGER = LogUtils.getClassLogger(); -+ private static final Logger LOGGER = LoggerFactory.getLogger(ChunkHolderManager.class); - - public static final int FULL_LOADED_TICKET_LEVEL = ChunkLevel.FULL_CHUNK_LEVEL; - public static final int BLOCK_TICKING_TICKET_LEVEL = ChunkLevel.BLOCK_TICKING_LEVEL; -@@ -189,7 +190,7 @@ public final class ChunkHolderManager { - if (halt) { - LOGGER.info("Waiting 60s for chunk system to halt for world '" + WorldUtil.getWorldName(this.world) + "'"); - if (!this.taskScheduler.halt(true, TimeUnit.SECONDS.toNanos(60L))) { -- LOGGER.warn("Failed to halt world generation/loading tasks for world '" + WorldUtil.getWorldName(this.world) + "'"); -+ LOGGER.warn("Failed to halt generation/loading tasks for world '" + WorldUtil.getWorldName(this.world) + "'"); - } else { - LOGGER.info("Halted chunk system for world '" + WorldUtil.getWorldName(this.world) + "'"); - } -@@ -199,21 +200,21 @@ public final class ChunkHolderManager { - this.saveAllChunks(true, true, true); - } - -- boolean hasTasks = false; -- for (final RegionFileIOThread.RegionFileType type : RegionFileIOThread.RegionFileType.values()) { -- if (RegionFileIOThread.getControllerFor(this.world, type).hasTasks()) { -- hasTasks = true; -- break; -+ MoonriseRegionFileIO.flush(this.world); -+ -+ if (halt) { -+ LOGGER.info("Waiting 60s for chunk I/O to halt for world '" + WorldUtil.getWorldName(this.world) + "'"); -+ if (!this.taskScheduler.haltIO(true, TimeUnit.SECONDS.toNanos(60L))) { -+ LOGGER.warn("Failed to halt I/O tasks for world '" + WorldUtil.getWorldName(this.world) + "'"); -+ } else { -+ LOGGER.info("Halted I/O scheduler for world '" + WorldUtil.getWorldName(this.world) + "'"); - } - } -- if (hasTasks) { -- RegionFileIOThread.flush(); -- } - - // kill regionfile cache -- for (final RegionFileIOThread.RegionFileType type : RegionFileIOThread.RegionFileType.values()) { -+ for (final MoonriseRegionFileIO.RegionFileType type : MoonriseRegionFileIO.RegionFileType.values()) { - try { -- RegionFileIOThread.getControllerFor(this.world, type).getCache().close(); -+ MoonriseRegionFileIO.getControllerFor(this.world, type).getCache().close(); - } catch (final IOException ex) { - LOGGER.error("Failed to close '" + type.name() + "' regionfile cache for world '" + WorldUtil.getWorldName(this.world) + "'", ex); - } -@@ -230,8 +231,8 @@ public final class ChunkHolderManager { - public void autoSave() { - final List reschedule = new ArrayList<>(); - final long currentTick = this.currentTick; -- final long maxSaveTime = currentTick - Math.max(1L, this.world.paperConfig().chunks.autoSaveInterval.value()); -- final int maxToSave = this.world.paperConfig().chunks.maxAutoSaveChunksPerTick; -+ final long maxSaveTime = currentTick - Math.max(1L, PlatformHooks.get().configAutoSaveInterval()); -+ final int maxToSave = PlatformHooks.get().configMaxAutoSavePerTick(); - for (int autoSaved = 0; autoSaved < maxToSave && !this.autoSaveQueue.isEmpty();) { - final NewChunkHolder holder = this.autoSaveQueue.first(); - -@@ -271,55 +272,74 @@ public final class ChunkHolderManager { - - long start = System.nanoTime(); - long lastLog = start; -- boolean needsFlush = false; -- final int flushInterval = 50; -+ final int flushInterval = 200; -+ int lastFlush = 0; - - int savedChunk = 0; - int savedEntity = 0; - int savedPoi = 0; - -+ if (shutdown) { -+ // Normal unload process does not occur during shutdown: fire event manually -+ // for mods that expect ChunkEvent.Unload to fire on shutdown (before LevelEvent.Unload) -+ for (int i = 0, len = holders.size(); i < len; ++i) { -+ final NewChunkHolder holder = holders.get(i); -+ if (holder.getCurrentChunk() instanceof LevelChunk levelChunk) { -+ PlatformHooks.get().chunkUnloadFromWorld(levelChunk); -+ } -+ } -+ } - for (int i = 0, len = holders.size(); i < len; ++i) { - final NewChunkHolder holder = holders.get(i); - try { - final NewChunkHolder.SaveStat saveStat = holder.save(shutdown); - if (saveStat != null) { -- ++saved; -- needsFlush = flush; - if (saveStat.savedChunk()) { - ++savedChunk; -+ ++saved; - } - if (saveStat.savedEntityChunk()) { - ++savedEntity; -+ ++saved; - } - if (saveStat.savedPoiChunk()) { - ++savedPoi; -+ ++saved; - } - } - } catch (final Throwable thr) { - LOGGER.error("Failed to save chunk (" + holder.chunkX + "," + holder.chunkZ + ") in world '" + WorldUtil.getWorldName(this.world) + "'", thr); - } -- if (needsFlush && (saved % flushInterval) == 0) { -- needsFlush = false; -- RegionFileIOThread.partialFlush(flushInterval / 2); -+ if (flush && (saved - lastFlush) > (flushInterval / 2)) { -+ lastFlush = saved; -+ MoonriseRegionFileIO.partialFlush(this.world, flushInterval / 2); - } - if (logProgress) { - final long currTime = System.nanoTime(); - if ((currTime - lastLog) > TimeUnit.SECONDS.toNanos(10L)) { - lastLog = currTime; -- LOGGER.info("Saved " + saved + " chunks (" + format.format((double)(i+1)/(double)len * 100.0) + "%) in world '" + WorldUtil.getWorldName(this.world) + "'"); -+ LOGGER.info( -+ "Saved " + savedChunk + " block chunks, " + savedEntity + " entity chunks, " + savedPoi -+ + " poi chunks in world '" + WorldUtil.getWorldName(this.world) + "', progress: " -+ + format.format((double)(i+1)/(double)len * 100.0) -+ ); - } - } - } - if (flush) { -- RegionFileIOThread.flush(); -+ MoonriseRegionFileIO.flush(this.world); - try { -- RegionFileIOThread.flushRegionStorages(this.world); -+ MoonriseRegionFileIO.flushRegionStorages(this.world); - } catch (final IOException ex) { - LOGGER.error("Exception when flushing regions in world '" + WorldUtil.getWorldName(this.world) + "'", ex); - } - } - if (logProgress) { -- LOGGER.info("Saved " + savedChunk + " block chunks, " + savedEntity + " entity chunks, " + savedPoi + " poi chunks in world '" + WorldUtil.getWorldName(this.world) + "' in " + format.format(1.0E-9 * (System.nanoTime() - start)) + "s"); -+ LOGGER.info( -+ "Saved " + savedChunk + " block chunks, " + savedEntity + " entity chunks, " + savedPoi -+ + " poi chunks in world '" + WorldUtil.getWorldName(this.world) + "' in " -+ + format.format(1.0E-9 * (System.nanoTime() - start)) + "s" -+ ); - } - } - -@@ -798,21 +818,21 @@ public final class ChunkHolderManager { - return this.chunkHolders.get(position); - } - -- public void raisePriority(final int x, final int z, final PrioritisedExecutor.Priority priority) { -+ public void raisePriority(final int x, final int z, final Priority priority) { - final NewChunkHolder chunkHolder = this.getChunkHolder(x, z); - if (chunkHolder != null) { - chunkHolder.raisePriority(priority); - } - } - -- public void setPriority(final int x, final int z, final PrioritisedExecutor.Priority priority) { -+ public void setPriority(final int x, final int z, final Priority priority) { - final NewChunkHolder chunkHolder = this.getChunkHolder(x, z); - if (chunkHolder != null) { - chunkHolder.setPriority(priority); - } - } - -- public void lowerPriority(final int x, final int z, final PrioritisedExecutor.Priority priority) { -+ public void lowerPriority(final int x, final int z, final Priority priority) { - final NewChunkHolder chunkHolder = this.getChunkHolder(x, z); - if (chunkHolder != null) { - chunkHolder.lowerPriority(priority); -@@ -895,7 +915,7 @@ public final class ChunkHolderManager { - final ChunkLoadTask.EntityDataLoadTask entityLoad = current.getEntityDataLoadTask(); - - if (entityLoad != null) { -- entityLoad.raisePriority(PrioritisedExecutor.Priority.BLOCKING); -+ entityLoad.raisePriority(Priority.BLOCKING); - } - } - } -@@ -971,7 +991,7 @@ public final class ChunkHolderManager { - final ChunkLoadTask.PoiDataLoadTask poiLoad = current.getPoiDataLoadTask(); - - if (poiLoad != null) { -- poiLoad.raisePriority(PrioritisedExecutor.Priority.BLOCKING); -+ poiLoad.raisePriority(Priority.BLOCKING); - } - } - } finally { -@@ -1018,7 +1038,7 @@ public final class ChunkHolderManager { - } - - ChunkHolderManager.this.processPendingFullUpdate(); -- }, PrioritisedExecutor.Priority.HIGHEST); -+ }, Priority.HIGHEST); - } else { - final ArrayDeque pendingFullLoadUpdate = this.pendingFullLoadUpdate; - for (int i = 0, len = changedFullStatus.size(); i < len; ++i) { -@@ -1028,11 +1048,10 @@ public final class ChunkHolderManager { - } - - private void removeChunkHolder(final NewChunkHolder holder) { -- holder.markUnloaded(); -+ holder.onUnload(); - this.autoSaveQueue.remove(holder); - ChunkSystem.onChunkHolderDelete(this.world, holder.vanillaChunkHolder); - this.chunkHolders.remove(CoordinateUtils.getChunkKey(holder.chunkX, holder.chunkZ)); -- - } - - // note: never call while inside the chunk system, this will absolutely break everything -@@ -1313,6 +1332,9 @@ public final class ChunkHolderManager { - if (BLOCK_TICKET_UPDATES.get() == Boolean.TRUE) { - throw new IllegalStateException("Cannot update ticket level while unloading chunks or updating entity manager"); - } -+ if (!PlatformHooks.get().allowAsyncTicketUpdates() && !TickThread.isTickThread()) { -+ TickThread.ensureTickThread("Cannot asynchronously process ticket updates"); -+ } - - List changedFullStatus = null; - -@@ -1328,10 +1350,15 @@ public final class ChunkHolderManager { - } - changedFullStatus = new ArrayList<>(); - -- ret |= this.ticketLevelPropagator.performUpdates( -- this.ticketLockArea, this.taskScheduler.schedulingLockArea, -- scheduledTasks, changedFullStatus -- ); -+ this.blockTicketUpdates(); -+ try { -+ ret |= this.ticketLevelPropagator.performUpdates( -+ this.ticketLockArea, this.taskScheduler.schedulingLockArea, -+ scheduledTasks, changedFullStatus -+ ); -+ } finally { -+ this.unblockTicketUpdates(Boolean.FALSE); -+ } - } - - if (changedFullStatus != null) { -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java -index 8671a90e969d16c7a57ddc38fedb7cf01815f64c..120ce31729dc8d4bba0901ca06d3212f3158d089 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java -@@ -1,16 +1,17 @@ - package ca.spottedleaf.moonrise.patches.chunk_system.scheduling; - --import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; --import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadPool; --import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadedTaskQueue; -+import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.executor.queue.PrioritisedTaskQueue; -+import ca.spottedleaf.concurrentutil.executor.thread.PrioritisedThreadPool; - import ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock; - import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import ca.spottedleaf.concurrentutil.util.Priority; -+import ca.spottedleaf.moonrise.common.config.moonrise.MoonriseConfig; - import ca.spottedleaf.moonrise.common.util.CoordinateUtils; - import ca.spottedleaf.moonrise.common.util.JsonUtil; - import ca.spottedleaf.moonrise.common.util.MoonriseCommon; - import ca.spottedleaf.moonrise.common.util.TickThread; - import ca.spottedleaf.moonrise.common.util.WorldUtil; --import ca.spottedleaf.moonrise.patches.chunk_system.io.RegionFileIOThread; - import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel; - import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemChunkStatus; - import ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer; -@@ -23,7 +24,6 @@ import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task.ChunkUpgrade - import ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer; - import ca.spottedleaf.moonrise.patches.chunk_system.status.ChunkSystemChunkStep; - import ca.spottedleaf.moonrise.patches.chunk_system.util.ParallelSearchRadiusIteration; --import com.mojang.logging.LogUtils; - import com.google.gson.JsonArray; - import com.google.gson.JsonObject; - import net.minecraft.CrashReport; -@@ -48,6 +48,7 @@ import net.minecraft.world.level.chunk.status.ChunkStatus; - import net.minecraft.world.level.chunk.status.ChunkStep; - import net.minecraft.world.phys.Vec3; - import org.slf4j.Logger; -+import org.slf4j.LoggerFactory; - import java.io.File; - import java.time.LocalDateTime; - import java.time.format.DateTimeFormatter; -@@ -63,51 +64,14 @@ import java.util.function.Consumer; - - public final class ChunkTaskScheduler { - -- private static final Logger LOGGER = LogUtils.getClassLogger(); -+ private static final Logger LOGGER = LoggerFactory.getLogger(ChunkTaskScheduler.class); - -- static int newChunkSystemIOThreads; -- static int newChunkSystemGenParallelism; -- static int newChunkSystemGenPopulationParallelism; -- static int newChunkSystemLoadParallelism; -- -- private static boolean initialised = false; -- -- public static void init(io.papermc.paper.configuration.GlobalConfiguration.ChunkSystem chunkSystem) { -- if (initialised) { -- return; -- } -- initialised = true; -- MoonriseCommon.init(chunkSystem); // Paper -- newChunkSystemIOThreads = chunkSystem.ioThreads; -- if (newChunkSystemIOThreads <= 0) { -- newChunkSystemIOThreads = 1; -- } else { -- newChunkSystemIOThreads = Math.max(1, newChunkSystemIOThreads); -- } -- -- String newChunkSystemGenParallelism = chunkSystem.genParallelism; -- if (newChunkSystemGenParallelism.equalsIgnoreCase("default")) { -- newChunkSystemGenParallelism = "true"; -- } -- -- boolean useParallelGen; -- if (newChunkSystemGenParallelism.equalsIgnoreCase("on") || newChunkSystemGenParallelism.equalsIgnoreCase("enabled") -- || newChunkSystemGenParallelism.equalsIgnoreCase("true")) { -- useParallelGen = true; -- } else if (newChunkSystemGenParallelism.equalsIgnoreCase("off") || newChunkSystemGenParallelism.equalsIgnoreCase("disabled") -- || newChunkSystemGenParallelism.equalsIgnoreCase("false")) { -- useParallelGen = false; -- } else { -- throw new IllegalStateException("Invalid option for gen-parallelism: must be one of [on, off, enabled, disabled, true, false, default]"); -+ public static void init(final boolean useParallelGen) { -+ for (final PrioritisedThreadPool.ExecutorGroup.ThreadPoolExecutor executor : MoonriseCommon.RADIUS_AWARE_GROUP.getAllExecutors()) { -+ executor.setMaxParallelism(useParallelGen ? -1 : 1); - } - -- ChunkTaskScheduler.newChunkSystemGenParallelism = MoonriseCommon.WORKER_THREADS; -- ChunkTaskScheduler.newChunkSystemGenPopulationParallelism = useParallelGen ? MoonriseCommon.WORKER_THREADS : 1; -- ChunkTaskScheduler.newChunkSystemLoadParallelism = MoonriseCommon.WORKER_THREADS; -- -- RegionFileIOThread.init(newChunkSystemIOThreads); -- -- LOGGER.info("Chunk system is using " + newChunkSystemIOThreads + " I/O threads, " + MoonriseCommon.WORKER_THREADS + " worker threads, and population gen parallelism of " + ChunkTaskScheduler.newChunkSystemGenPopulationParallelism + " threads"); -+ LOGGER.info("Chunk system is using population gen parallelism: " + useParallelGen); - } - - public static final TicketType CHUNK_LOAD = TicketType.create("chunk_system:chunk_load", Long::compareTo); -@@ -151,13 +115,15 @@ public final class ChunkTaskScheduler { - } - - public final ServerLevel world; -- public final PrioritisedThreadPool workers; - public final RadiusAwarePrioritisedExecutor radiusAwareScheduler; -- public final PrioritisedThreadPool.PrioritisedPoolExecutor parallelGenExecutor; -- private final PrioritisedThreadPool.PrioritisedPoolExecutor radiusAwareGenExecutor; -- public final PrioritisedThreadPool.PrioritisedPoolExecutor loadExecutor; -+ public final PrioritisedThreadPool.ExecutorGroup.ThreadPoolExecutor parallelGenExecutor; -+ private final PrioritisedThreadPool.ExecutorGroup.ThreadPoolExecutor radiusAwareGenExecutor; -+ public final PrioritisedThreadPool.ExecutorGroup.ThreadPoolExecutor loadExecutor; -+ public final PrioritisedThreadPool.ExecutorGroup.ThreadPoolExecutor ioExecutor; -+ public final PrioritisedThreadPool.ExecutorGroup.ThreadPoolExecutor compressionExecutor; -+ public final PrioritisedThreadPool.ExecutorGroup.ThreadPoolExecutor saveExecutor; - -- private final PrioritisedThreadedTaskQueue mainThreadExecutor = new PrioritisedThreadedTaskQueue(); -+ private final PrioritisedTaskQueue mainThreadExecutor = new PrioritisedTaskQueue(); - - public final ChunkHolderManager chunkHolderManager; - -@@ -306,9 +272,8 @@ public final class ChunkTaskScheduler { - return this.lockShift; - } - -- public ChunkTaskScheduler(final ServerLevel world, final PrioritisedThreadPool workers) { -+ public ChunkTaskScheduler(final ServerLevel world) { - this.world = world; -- this.workers = workers; - // must be >= region shift (in paper, doesn't exist) and must be >= ticket propagator section shift - // it must be >= region shift since the regioniser assumes ticket updates do not occur in parallel for the region sections - // it must be >= ticket propagator section shift so that the ticket propagator can assume that owning a position implies owning -@@ -317,11 +282,14 @@ public final class ChunkTaskScheduler { - this.lockShift = Math.max(((ChunkSystemServerLevel)world).moonrise$getRegionChunkShift(), ThreadedTicketLevelPropagator.SECTION_SHIFT); - this.schedulingLockArea = new ReentrantAreaLock(this.getChunkSystemLockShift()); - -- final String worldName = WorldUtil.getWorldName(world); -- this.parallelGenExecutor = workers.createExecutor("Chunk parallel generation executor for world '" + worldName + "'", 1, Math.max(1, newChunkSystemGenParallelism)); -- this.radiusAwareGenExecutor = workers.createExecutor("Chunk radius aware generator for world '" + worldName + "'", 1, Math.max(1, newChunkSystemGenPopulationParallelism)); -- this.loadExecutor = workers.createExecutor("Chunk load executor for world '" + worldName + "'", 1, newChunkSystemLoadParallelism); -- this.radiusAwareScheduler = new RadiusAwarePrioritisedExecutor(this.radiusAwareGenExecutor, Math.max(2, 1 + newChunkSystemGenPopulationParallelism)); -+ this.parallelGenExecutor = MoonriseCommon.PARALLEL_GEN_GROUP.createExecutor(-1, MoonriseCommon.WORKER_QUEUE_HOLD_TIME, 0); -+ this.radiusAwareGenExecutor = MoonriseCommon.RADIUS_AWARE_GROUP.createExecutor(1, MoonriseCommon.WORKER_QUEUE_HOLD_TIME, 0); -+ this.loadExecutor = MoonriseCommon.LOAD_GROUP.createExecutor(-1, MoonriseCommon.WORKER_QUEUE_HOLD_TIME, 0); -+ this.radiusAwareScheduler = new RadiusAwarePrioritisedExecutor(this.radiusAwareGenExecutor, 16); -+ this.ioExecutor = MoonriseCommon.SERVER_REGION_IO_GROUP.createExecutor(-1, MoonriseCommon.IO_QUEUE_HOLD_TIME, 0); -+ // we need a separate executor here so that on shutdown we can continue to process I/O tasks -+ this.compressionExecutor = MoonriseCommon.LOAD_GROUP.createExecutor(-1, MoonriseCommon.WORKER_QUEUE_HOLD_TIME, 0); -+ this.saveExecutor = MoonriseCommon.LOAD_GROUP.createExecutor(-1, MoonriseCommon.WORKER_QUEUE_HOLD_TIME, 0); - this.chunkHolderManager = new ChunkHolderManager(world, this); - } - -@@ -360,7 +328,7 @@ public final class ChunkTaskScheduler { - }; - - // this may not be good enough, specifically thanks to stupid ass plugins swallowing exceptions -- this.scheduleChunkTask(chunkX, chunkZ, crash, PrioritisedExecutor.Priority.BLOCKING); -+ this.scheduleChunkTask(chunkX, chunkZ, crash, Priority.BLOCKING); - // so, make the main thread pick it up - ((ChunkSystemMinecraftServer)this.world.getServer()).moonrise$setChunkSystemCrash(new RuntimeException("Chunk system crash propagated from unrecoverableChunkSystemFailure", reportedException)); - } -@@ -370,20 +338,20 @@ public final class ChunkTaskScheduler { - return this.mainThreadExecutor.executeTask(); - } - -- public void raisePriority(final int x, final int z, final PrioritisedExecutor.Priority priority) { -+ public void raisePriority(final int x, final int z, final Priority priority) { - this.chunkHolderManager.raisePriority(x, z, priority); - } - -- public void setPriority(final int x, final int z, final PrioritisedExecutor.Priority priority) { -+ public void setPriority(final int x, final int z, final Priority priority) { - this.chunkHolderManager.setPriority(x, z, priority); - } - -- public void lowerPriority(final int x, final int z, final PrioritisedExecutor.Priority priority) { -+ public void lowerPriority(final int x, final int z, final Priority priority) { - this.chunkHolderManager.lowerPriority(x, z, priority); - } - - public void scheduleTickingState(final int chunkX, final int chunkZ, final FullChunkStatus toStatus, -- final boolean addTicket, final PrioritisedExecutor.Priority priority, -+ final boolean addTicket, final Priority priority, - final Consumer onComplete) { - final int radius = toStatus.ordinal() - 1; // 0 -> BORDER, 1 -> TICKING, 2 -> ENTITY_TICKING - -@@ -479,7 +447,7 @@ public final class ChunkTaskScheduler { - } - - public void scheduleChunkLoad(final int chunkX, final int chunkZ, final boolean gen, final ChunkStatus toStatus, final boolean addTicket, -- final PrioritisedExecutor.Priority priority, final Consumer onComplete) { -+ final Priority priority, final Consumer onComplete) { - if (gen) { - this.scheduleChunkLoad(chunkX, chunkZ, toStatus, addTicket, priority, onComplete); - return; -@@ -503,7 +471,7 @@ public final class ChunkTaskScheduler { - - // only appropriate to use with syncLoadNonFull - public boolean beginChunkLoadForNonFullSync(final int chunkX, final int chunkZ, final ChunkStatus toStatus, -- final PrioritisedExecutor.Priority priority) { -+ final Priority priority) { - final int accessRadius = getAccessRadius(toStatus); - final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ); - final int minLevel = ChunkTaskScheduler.getTicketLevel(toStatus); -@@ -548,6 +516,11 @@ public final class ChunkTaskScheduler { - if (status == null || status.isOrAfter(ChunkStatus.FULL)) { - throw new IllegalArgumentException("Status: " + status); - } -+ -+ if (!TickThread.isTickThread()) { -+ return this.world.getChunkSource().getChunk(chunkX, chunkZ, status, true); -+ } -+ - ChunkAccess loaded = ((ChunkSystemServerLevel)this.world).moonrise$getSpecificChunkIfLoaded(chunkX, chunkZ, status); - if (loaded != null) { - return loaded; -@@ -558,7 +531,7 @@ public final class ChunkTaskScheduler { - this.chunkHolderManager.addTicketAtLevel(NON_FULL_CHUNK_LOAD, chunkX, chunkZ, ticketLevel, ticketId); - this.chunkHolderManager.processTicketUpdates(); - -- this.beginChunkLoadForNonFullSync(chunkX, chunkZ, status, PrioritisedExecutor.Priority.BLOCKING); -+ this.beginChunkLoadForNonFullSync(chunkX, chunkZ, status, Priority.BLOCKING); - - // we could do a simple spinwait here, since we do not need to process tasks while performing this load - // but we process tasks only because it's a better use of the time spent -@@ -578,7 +551,7 @@ public final class ChunkTaskScheduler { - } - - public void scheduleChunkLoad(final int chunkX, final int chunkZ, final ChunkStatus toStatus, final boolean addTicket, -- final PrioritisedExecutor.Priority priority, final Consumer onComplete) { -+ final Priority priority, final Consumer onComplete) { - if (!TickThread.isTickThreadFor(this.world, chunkX, chunkZ)) { - this.scheduleChunkTask(chunkX, chunkZ, () -> { - ChunkTaskScheduler.this.scheduleChunkLoad(chunkX, chunkZ, toStatus, addTicket, priority, onComplete); -@@ -670,7 +643,7 @@ public final class ChunkTaskScheduler { - - private ChunkProgressionTask createTask(final int chunkX, final int chunkZ, final ChunkAccess chunk, - final NewChunkHolder chunkHolder, final StaticCache2D neighbours, -- final ChunkStatus toStatus, final PrioritisedExecutor.Priority initialPriority) { -+ final ChunkStatus toStatus, final Priority initialPriority) { - if (toStatus == ChunkStatus.EMPTY) { - return new ChunkLoadTask(this, this.world, chunkX, chunkZ, chunkHolder, initialPriority); - } -@@ -686,7 +659,7 @@ public final class ChunkTaskScheduler { - - ChunkProgressionTask schedule(final int chunkX, final int chunkZ, final ChunkStatus targetStatus, final NewChunkHolder chunkHolder, - final List allTasks) { -- return this.schedule(chunkX, chunkZ, targetStatus, chunkHolder, allTasks, chunkHolder.getEffectivePriority(PrioritisedExecutor.Priority.NORMAL)); -+ return this.schedule(chunkX, chunkZ, targetStatus, chunkHolder, allTasks, chunkHolder.getEffectivePriority(Priority.NORMAL)); - } - - // rets new task scheduled for the _specified_ chunk -@@ -695,7 +668,7 @@ public final class ChunkTaskScheduler { - // schedule will ignore the generation target, so it should be checked by the caller to ensure the target is not regressed! - private ChunkProgressionTask schedule(final int chunkX, final int chunkZ, final ChunkStatus targetStatus, - final NewChunkHolder chunkHolder, final List allTasks, -- final PrioritisedExecutor.Priority minPriority) { -+ final Priority minPriority) { - if (!this.schedulingLockArea.isHeldByCurrentThread(chunkX, chunkZ, getAccessRadius(targetStatus))) { - throw new IllegalStateException("Not holding scheduling lock"); - } -@@ -705,8 +678,8 @@ public final class ChunkTaskScheduler { - return null; - } - -- final PrioritisedExecutor.Priority requestedPriority = PrioritisedExecutor.Priority.max( -- minPriority, chunkHolder.getEffectivePriority(PrioritisedExecutor.Priority.NORMAL) -+ final Priority requestedPriority = Priority.max( -+ minPriority, chunkHolder.getEffectivePriority(Priority.NORMAL) - ); - final ChunkStatus currentGenStatus = chunkHolder.getCurrentGenStatus(); - final ChunkAccess chunk = chunkHolder.getCurrentChunk(); -@@ -743,7 +716,7 @@ public final class ChunkTaskScheduler { - - final int neighbourReadRadius = Math.max( - 0, -- chunkPyramid.getStepTo(toStatus).getAccumulatedRadiusOf(ChunkStatus.EMPTY) -+ chunkStep.getAccumulatedRadiusOf(ChunkStatus.EMPTY) - ); - - boolean unGeneratedNeighbours = false; -@@ -783,7 +756,7 @@ public final class ChunkTaskScheduler { - - final ChunkProgressionTask task = this.createTask( - chunkX, chunkZ, chunk, chunkHolder, neighbours, toStatus, -- chunkHolder.getEffectivePriority(PrioritisedExecutor.Priority.NORMAL) -+ chunkHolder.getEffectivePriority(Priority.NORMAL) - ); - allTasks.add(task); - -@@ -794,7 +767,7 @@ public final class ChunkTaskScheduler { - - // rets true if the neighbour is not at the required status, false otherwise - private boolean checkNeighbour(final int chunkX, final int chunkZ, final ChunkStatus requiredStatus, final NewChunkHolder center, -- final List tasks, final PrioritisedExecutor.Priority minPriority) { -+ final List tasks, final Priority minPriority) { - final NewChunkHolder chunkHolder = this.chunkHolderManager.getChunkHolder(chunkX, chunkZ); - - if (chunkHolder == null) { -@@ -830,41 +803,41 @@ public final class ChunkTaskScheduler { - */ - @Deprecated - public PrioritisedExecutor.PrioritisedTask scheduleChunkTask(final Runnable run) { -- return this.scheduleChunkTask(run, PrioritisedExecutor.Priority.NORMAL); -+ return this.scheduleChunkTask(run, Priority.NORMAL); - } - - /** - * @deprecated Chunk tasks must be tied to coordinates in the future - */ - @Deprecated -- public PrioritisedExecutor.PrioritisedTask scheduleChunkTask(final Runnable run, final PrioritisedExecutor.Priority priority) { -- return this.mainThreadExecutor.queueRunnable(run, priority); -+ public PrioritisedExecutor.PrioritisedTask scheduleChunkTask(final Runnable run, final Priority priority) { -+ return this.mainThreadExecutor.queueTask(run, priority); - } - - public PrioritisedExecutor.PrioritisedTask createChunkTask(final int chunkX, final int chunkZ, final Runnable run) { -- return this.createChunkTask(chunkX, chunkZ, run, PrioritisedExecutor.Priority.NORMAL); -+ return this.createChunkTask(chunkX, chunkZ, run, Priority.NORMAL); - } - - public PrioritisedExecutor.PrioritisedTask createChunkTask(final int chunkX, final int chunkZ, final Runnable run, -- final PrioritisedExecutor.Priority priority) { -+ final Priority priority) { - return this.mainThreadExecutor.createTask(run, priority); - } - - public PrioritisedExecutor.PrioritisedTask scheduleChunkTask(final int chunkX, final int chunkZ, final Runnable run) { -- return this.scheduleChunkTask(chunkX, chunkZ, run, PrioritisedExecutor.Priority.NORMAL); -+ return this.scheduleChunkTask(chunkX, chunkZ, run, Priority.NORMAL); - } - - public PrioritisedExecutor.PrioritisedTask scheduleChunkTask(final int chunkX, final int chunkZ, final Runnable run, -- final PrioritisedExecutor.Priority priority) { -- return this.mainThreadExecutor.queueRunnable(run, priority); -+ final Priority priority) { -+ return this.mainThreadExecutor.queueTask(run, priority); - } - - public boolean halt(final boolean sync, final long maxWaitNS) { - this.radiusAwareGenExecutor.halt(); - this.parallelGenExecutor.halt(); - this.loadExecutor.halt(); -- final long time = System.nanoTime(); - if (sync) { -+ final long time = System.nanoTime(); - for (long failures = 9L;; failures = ConcurrentUtil.linearLongBackoff(failures, 500_000L, 50_000_000L)) { - if ( - !this.radiusAwareGenExecutor.isActive() && -@@ -882,6 +855,29 @@ public final class ChunkTaskScheduler { - return true; - } - -+ public boolean haltIO(final boolean sync, final long maxWaitNS) { -+ this.ioExecutor.halt(); -+ this.saveExecutor.halt(); -+ this.compressionExecutor.halt(); -+ if (sync) { -+ final long time = System.nanoTime(); -+ for (long failures = 9L;; failures = ConcurrentUtil.linearLongBackoff(failures, 500_000L, 50_000_000L)) { -+ if ( -+ !this.ioExecutor.isActive() && -+ !this.saveExecutor.isActive() && -+ !this.compressionExecutor.isActive() -+ ) { -+ return true; -+ } -+ if ((System.nanoTime() - time) >= maxWaitNS) { -+ return false; -+ } -+ } -+ } -+ -+ return true; -+ } -+ - public static final ArrayDeque WAITING_CHUNKS = new ArrayDeque<>(); // stack - - public static final class ChunkInfo { -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java -index 45eda96fd8a1acb87dbb69ce5495fec7e451416f..381631e405895ba3eede1cd2e1011c64aadbd662 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java -@@ -1,18 +1,20 @@ - package ca.spottedleaf.moonrise.patches.chunk_system.scheduling; - --import ca.spottedleaf.concurrentutil.completable.Completable; -+import ca.spottedleaf.concurrentutil.completable.CallbackCompletable; - import ca.spottedleaf.concurrentutil.executor.Cancellable; --import ca.spottedleaf.concurrentutil.executor.standard.DelayedPrioritisedTask; --import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor; - import ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock; - import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import ca.spottedleaf.concurrentutil.util.Priority; -+import ca.spottedleaf.moonrise.common.PlatformHooks; -+import ca.spottedleaf.moonrise.common.misc.LazyRunnable; - import ca.spottedleaf.moonrise.common.util.CoordinateUtils; - import ca.spottedleaf.moonrise.common.util.TickThread; - import ca.spottedleaf.moonrise.common.util.WorldUtil; - import ca.spottedleaf.moonrise.common.util.ChunkSystem; --import ca.spottedleaf.moonrise.patches.chunk_system.ChunkSystemFeatures; --import ca.spottedleaf.moonrise.patches.chunk_system.async_save.AsyncChunkSaveData; --import ca.spottedleaf.moonrise.patches.chunk_system.io.RegionFileIOThread; -+import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData; -+import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO; -+import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel; - import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel; - import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemChunkHolder; - import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemChunkStatus; -@@ -36,13 +38,14 @@ import net.minecraft.server.level.ChunkHolder; - import net.minecraft.server.level.ChunkLevel; - import net.minecraft.server.level.FullChunkStatus; - import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.progress.ChunkProgressListener; - import net.minecraft.world.entity.Entity; - import net.minecraft.world.level.ChunkPos; - import net.minecraft.world.level.chunk.ChunkAccess; - import net.minecraft.world.level.chunk.ImposterProtoChunk; - import net.minecraft.world.level.chunk.LevelChunk; - import net.minecraft.world.level.chunk.status.ChunkStatus; --import net.minecraft.world.level.chunk.storage.ChunkSerializer; -+import net.minecraft.world.level.chunk.storage.SerializableChunkData; - import org.slf4j.Logger; - import org.slf4j.LoggerFactory; - import java.lang.invoke.VarHandle; -@@ -58,6 +61,8 @@ public final class NewChunkHolder { - - private static final Logger LOGGER = LoggerFactory.getLogger(NewChunkHolder.class); - -+ public final ChunkData holderData; -+ - public final ServerLevel world; - public final int chunkX; - public final int chunkZ; -@@ -89,7 +94,7 @@ public final class NewChunkHolder { - if (this.entityChunk == null) { - ret = this.entityChunk = new ChunkEntitySlices( - this.world, this.chunkX, this.chunkZ, this.getChunkStatus(), -- WorldUtil.getMinSection(this.world), WorldUtil.getMaxSection(this.world) -+ this.holderData, WorldUtil.getMinSection(this.world), WorldUtil.getMaxSection(this.world) - ); - - ret.setTransient(transientChunk); -@@ -173,7 +178,7 @@ public final class NewChunkHolder { - // no tasks to schedule _for_ - } else { - entityDataLoadTask = this.entityDataLoadTask = new ChunkLoadTask.EntityDataLoadTask( -- this.scheduler, this.world, this.chunkX, this.chunkZ, this.getEffectivePriority(PrioritisedExecutor.Priority.NORMAL) -+ this.scheduler, this.world, this.chunkX, this.chunkZ, this.getEffectivePriority(Priority.NORMAL) - ); - entityDataLoadTask.addCallback(this::completeEntityLoad); - // need one schedule() per waiter -@@ -220,7 +225,7 @@ public final class NewChunkHolder { - - if (this.entityDataLoadTask == null) { - this.entityDataLoadTask = new ChunkLoadTask.EntityDataLoadTask( -- this.scheduler, this.world, this.chunkX, this.chunkZ, this.getEffectivePriority(PrioritisedExecutor.Priority.NORMAL) -+ this.scheduler, this.world, this.chunkX, this.chunkZ, this.getEffectivePriority(Priority.NORMAL) - ); - this.entityDataLoadTask.addCallback(this::completeEntityLoad); - this.entityDataLoadTaskWaiters = new ArrayList<>(); -@@ -294,7 +299,7 @@ public final class NewChunkHolder { - // no tasks to schedule _for_ - } else { - poiDataLoadTask = this.poiDataLoadTask = new ChunkLoadTask.PoiDataLoadTask( -- this.scheduler, this.world, this.chunkX, this.chunkZ, this.getEffectivePriority(PrioritisedExecutor.Priority.NORMAL) -+ this.scheduler, this.world, this.chunkX, this.chunkZ, this.getEffectivePriority(Priority.NORMAL) - ); - poiDataLoadTask.addCallback(this::completePoiLoad); - // need one schedule() per waiter -@@ -340,7 +345,7 @@ public final class NewChunkHolder { - - if (this.poiDataLoadTask == null) { - this.poiDataLoadTask = new ChunkLoadTask.PoiDataLoadTask( -- this.scheduler, this.world, this.chunkX, this.chunkZ, this.getEffectivePriority(PrioritisedExecutor.Priority.NORMAL) -+ this.scheduler, this.world, this.chunkX, this.chunkZ, this.getEffectivePriority(Priority.NORMAL) - ); - this.poiDataLoadTask.addCallback(this::completePoiLoad); - this.poiDataLoadTaskWaiters = new ArrayList<>(); -@@ -519,15 +524,15 @@ public final class NewChunkHolder { - // priority state - - // the target priority for this chunk to generate at -- private PrioritisedExecutor.Priority priority = null; -+ private Priority priority = null; - private boolean priorityLocked; - - // the priority neighbouring chunks have requested this chunk generate at -- private PrioritisedExecutor.Priority neighbourRequestedPriority = null; -+ private Priority neighbourRequestedPriority = null; - -- public PrioritisedExecutor.Priority getEffectivePriority(final PrioritisedExecutor.Priority dfl) { -- final PrioritisedExecutor.Priority neighbour = this.neighbourRequestedPriority; -- final PrioritisedExecutor.Priority us = this.priority; -+ public Priority getEffectivePriority(final Priority dfl) { -+ final Priority neighbour = this.neighbourRequestedPriority; -+ final Priority us = this.priority; - - if (neighbour == null) { - return us == null ? dfl : us; -@@ -536,7 +541,7 @@ public final class NewChunkHolder { - return neighbour; - } - -- return PrioritisedExecutor.Priority.max(us, neighbour); -+ return Priority.max(us, neighbour); - } - - private void recalculateNeighbourRequestedPriority() { -@@ -545,18 +550,18 @@ public final class NewChunkHolder { - return; - } - -- PrioritisedExecutor.Priority max = null; -+ Priority max = null; - - for (final NewChunkHolder holder : this.neighboursWaitingForUs.keySet()) { -- final PrioritisedExecutor.Priority neighbourPriority = holder.getEffectivePriority(null); -+ final Priority neighbourPriority = holder.getEffectivePriority(null); - if (neighbourPriority != null && (max == null || neighbourPriority.isHigherPriority(max))) { - max = neighbourPriority; - } - } - -- final PrioritisedExecutor.Priority current = this.getEffectivePriority(PrioritisedExecutor.Priority.NORMAL); -+ final Priority current = this.getEffectivePriority(Priority.NORMAL); - this.neighbourRequestedPriority = max; -- final PrioritisedExecutor.Priority next = this.getEffectivePriority(PrioritisedExecutor.Priority.NORMAL); -+ final Priority next = this.getEffectivePriority(Priority.NORMAL); - - if (current == next) { - return; -@@ -578,7 +583,7 @@ public final class NewChunkHolder { - } - - // must hold scheduling lock -- public void raisePriority(final PrioritisedExecutor.Priority priority) { -+ public void raisePriority(final Priority priority) { - if (this.priority != null && this.priority.isHigherOrEqualPriority(priority)) { - return; - } -@@ -591,13 +596,13 @@ public final class NewChunkHolder { - } - - // must hold scheduling lock -- public void setPriority(final PrioritisedExecutor.Priority priority) { -+ public void setPriority(final Priority priority) { - if (this.priorityLocked) { - return; - } -- final PrioritisedExecutor.Priority old = this.getEffectivePriority(null); -+ final Priority old = this.getEffectivePriority(null); - this.priority = priority; -- final PrioritisedExecutor.Priority newPriority = this.getEffectivePriority(PrioritisedExecutor.Priority.NORMAL); -+ final Priority newPriority = this.getEffectivePriority(Priority.NORMAL); - - if (old != newPriority) { - if (this.generationTask != null) { -@@ -609,7 +614,7 @@ public final class NewChunkHolder { - } - - // must hold scheduling lock -- public void lowerPriority(final PrioritisedExecutor.Priority priority) { -+ public void lowerPriority(final Priority priority) { - if (this.priority != null && this.priority.isLowerOrEqualPriority(priority)) { - return; - } -@@ -632,7 +637,7 @@ public final class NewChunkHolder { - } - - // ticket level state -- public int oldTicketLevel = ChunkHolderManager.MAX_TICKET_LEVEL + 1; -+ private int oldTicketLevel = ChunkHolderManager.MAX_TICKET_LEVEL + 1; - private int currentTicketLevel = ChunkHolderManager.MAX_TICKET_LEVEL + 1; - - public int getTicketLevel() { -@@ -651,6 +656,7 @@ public final class NewChunkHolder { - world.getLightEngine(), null, world.getChunkSource().chunkMap - ); - ((ChunkSystemChunkHolder)this.vanillaChunkHolder).moonrise$setRealChunkHolder(this); -+ this.holderData = ((ChunkSystemLevel)this.world).moonrise$requestChunkData(CoordinateUtils.getChunkKey(chunkX, chunkZ)); - } - - public ChunkAccess getCurrentChunk() { -@@ -750,8 +756,9 @@ public final class NewChunkHolder { - /** Unloaded from chunk map */ - private boolean unloaded; - -- void markUnloaded() { -+ void onUnload() { - this.unloaded = true; -+ ((ChunkSystemLevel)this.world).moonrise$releaseChunkData(CoordinateUtils.getChunkKey(this.chunkX, this.chunkZ)); - } - - private boolean inUnloadQueue = false; -@@ -788,9 +795,10 @@ public final class NewChunkHolder { - private UnloadTask entityDataUnload; - private UnloadTask poiDataUnload; - -- public static final record UnloadTask(Completable completable, DelayedPrioritisedTask task) {} -+ public static final record UnloadTask(CallbackCompletable completable, PrioritisedExecutor.PrioritisedTask task, -+ LazyRunnable toRun) {} - -- public UnloadTask getUnloadTask(final RegionFileIOThread.RegionFileType type) { -+ public UnloadTask getUnloadTask(final MoonriseRegionFileIO.RegionFileType type) { - switch (type) { - case CHUNK_DATA: - return this.chunkDataUnload; -@@ -803,7 +811,7 @@ public final class NewChunkHolder { - } - } - -- private void removeUnloadTask(final RegionFileIOThread.RegionFileType type) { -+ private void removeUnloadTask(final MoonriseRegionFileIO.RegionFileType type) { - switch (type) { - case CHUNK_DATA: { - this.chunkDataUnload = null; -@@ -836,10 +844,10 @@ public final class NewChunkHolder { - // chunk state - this.currentChunk = null; - this.currentGenStatus = null; -- this.lastChunkCompletion = null; - for (int i = 0; i < this.chunkCompletions.length; ++i) { -- CHUNK_COMPLETION_ARRAY_HANDLE.setVolatile(this.chunkCompletions, i, (ChunkCompletion)null); -+ CHUNK_COMPLETION_ARRAY_HANDLE.setRelease(this.chunkCompletions, i, (ChunkCompletion)null); - } -+ this.lastChunkCompletion = null; - // entity chunk state - this.entityChunk = null; - this.pendingEntityChunk = null; -@@ -851,22 +859,23 @@ public final class NewChunkHolder { - this.priorityLocked = false; - - if (chunk != null) { -- this.chunkDataUnload = new UnloadTask(new Completable<>(), new DelayedPrioritisedTask(PrioritisedExecutor.Priority.NORMAL)); -+ final LazyRunnable toRun = new LazyRunnable(); -+ this.chunkDataUnload = new UnloadTask(new CallbackCompletable<>(), this.scheduler.saveExecutor.createTask(toRun), toRun); - } - if (poiChunk != null) { -- this.poiDataUnload = new UnloadTask(new Completable<>(), null); -+ this.poiDataUnload = new UnloadTask(new CallbackCompletable<>(), null, null); - } - if (entityChunk != null) { -- this.entityDataUnload = new UnloadTask(new Completable<>(), null); -+ this.entityDataUnload = new UnloadTask(new CallbackCompletable<>(), null, null); - } - - return this.unloadState = (chunk != null || entityChunk != null || poiChunk != null) ? new UnloadState(this, chunk, entityChunk, poiChunk) : null; - } - - // data is null if failed or does not need to be saved -- void completeAsyncUnloadDataSave(final RegionFileIOThread.RegionFileType type, final CompoundTag data) { -+ void completeAsyncUnloadDataSave(final MoonriseRegionFileIO.RegionFileType type, final CompoundTag data) { - if (data != null) { -- RegionFileIOThread.scheduleSave(this.world, this.chunkX, this.chunkZ, data, type); -+ MoonriseRegionFileIO.scheduleSave(this.world, this.chunkX, this.chunkZ, data, type); - } - - this.getUnloadTask(type).completable().complete(data); -@@ -886,18 +895,19 @@ public final class NewChunkHolder { - final ChunkEntitySlices entityChunk = state.entityChunk(); - final PoiChunk poiChunk = state.poiChunk(); - -- final boolean shouldLevelChunkNotSave = ChunkSystemFeatures.forceNoSave(chunk); -+ final boolean shouldLevelChunkNotSave = PlatformHooks.get().forceNoSave(chunk); - - // unload chunk data - if (chunk != null) { - if (chunk instanceof LevelChunk levelChunk) { - levelChunk.setLoaded(false); -+ PlatformHooks.get().chunkUnloadFromWorld(levelChunk); - } - - if (!shouldLevelChunkNotSave) { - this.saveChunk(chunk, true); - } else { -- this.completeAsyncUnloadDataSave(RegionFileIOThread.RegionFileType.CHUNK_DATA, null); -+ this.completeAsyncUnloadDataSave(MoonriseRegionFileIO.RegionFileType.CHUNK_DATA, null); - } - - if (chunk instanceof LevelChunk levelChunk) { -@@ -1066,6 +1076,9 @@ public final class NewChunkHolder { - if (oldUnloaded != newUnloaded) { - this.checkUnload(); - } -+ -+ // Don't really have a choice but to place this hook here -+ PlatformHooks.get().onChunkHolderTicketChange(this.world, this, oldLevel, newLevel); - } - - static final int NEIGHBOUR_RADIUS = 2; -@@ -1111,24 +1124,6 @@ public final class NewChunkHolder { - private static final long CHUNK_LOADED_MASK_RAD1 = getLoadedMask(1); - private static final long CHUNK_LOADED_MASK_RAD2 = getLoadedMask(2); - -- public static boolean areNeighboursFullLoaded(final long bitset, final int radius) { -- switch (radius) { -- case 0: { -- return (bitset & CHUNK_LOADED_MASK_RAD0) == CHUNK_LOADED_MASK_RAD0; -- } -- case 1: { -- return (bitset & CHUNK_LOADED_MASK_RAD1) == CHUNK_LOADED_MASK_RAD1; -- } -- case 2: { -- return (bitset & CHUNK_LOADED_MASK_RAD2) == CHUNK_LOADED_MASK_RAD2; -- } -- -- default: { -- throw new IllegalArgumentException("Radius not recognized: " + radius); -- } -- } -- } -- - // only updated while holding scheduling lock - private FullChunkStatus pendingFullChunkStatus = FullChunkStatus.INACCESSIBLE; - // updated while holding no locks, but adds a ticket before to prevent pending status from dropping -@@ -1363,6 +1358,17 @@ public final class NewChunkHolder { - } - - private void completeStatusConsumers(ChunkStatus status, final ChunkAccess chunk) { -+ // Update progress listener for LevelLoadingScreen -+ if (chunk != null) { -+ final ChunkProgressListener progressListener = this.world.getChunkSource().chunkMap.progressListener; -+ if (progressListener != null) { -+ final ChunkStatus finalStatus = status; -+ this.scheduler.scheduleChunkTask(this.chunkX, this.chunkZ, () -> { -+ progressListener.onStatusChange(this.vanillaChunkHolder.getPos(), finalStatus); -+ }); -+ } -+ } -+ - // need to tell future statuses to complete if cancelled - do { - this.completeStatusConsumers0(status, chunk); -@@ -1386,7 +1392,7 @@ public final class NewChunkHolder { - LOGGER.error("Failed to process chunk status callback", thr); - } - } -- }, PrioritisedExecutor.Priority.HIGHEST); -+ }, Priority.HIGHEST); - } - - private final Reference2ObjectOpenHashMap>> fullStatusWaiters = new Reference2ObjectOpenHashMap<>(); -@@ -1414,7 +1420,7 @@ public final class NewChunkHolder { - LOGGER.error("Failed to process chunk status callback", thr); - } - } -- }, PrioritisedExecutor.Priority.HIGHEST); -+ }, Priority.HIGHEST); - } - - // note: must hold scheduling lock -@@ -1670,6 +1676,8 @@ public final class NewChunkHolder { - - public static final record SaveStat(boolean savedChunk, boolean savedEntityChunk, boolean savedPoiChunk) {} - -+ private static final MoonriseRegionFileIO.RegionFileType[] REGION_FILE_TYPES = MoonriseRegionFileIO.RegionFileType.values(); -+ - public SaveStat save(final boolean shutdown) { - TickThread.ensureTickThread(this.world, this.chunkX, this.chunkZ, "Cannot save data off-main"); - -@@ -1677,6 +1685,7 @@ public final class NewChunkHolder { - PoiChunk poi = this.getPoiChunk(); - ChunkEntitySlices entities = this.getEntityChunk(); - boolean executedUnloadTask = false; -+ final boolean[] executedUnloadTasks = new boolean[REGION_FILE_TYPES.length]; - - if (shutdown) { - // make sure that the async unloads complete -@@ -1686,17 +1695,22 @@ public final class NewChunkHolder { - poi = this.unloadState.poiChunk(); - entities = this.unloadState.entityChunk(); - } -- final UnloadTask chunkUnloadTask = this.chunkDataUnload; -- final DelayedPrioritisedTask chunkDataUnloadTask = chunkUnloadTask == null ? null : chunkUnloadTask.task(); -- if (chunkDataUnloadTask != null) { -- final PrioritisedExecutor.PrioritisedTask unloadTask = chunkDataUnloadTask.getTask(); -- if (unloadTask != null) { -- executedUnloadTask = unloadTask.execute(); -+ for (final MoonriseRegionFileIO.RegionFileType regionFileType : REGION_FILE_TYPES) { -+ final UnloadTask unloadTask = this.getUnloadTask(regionFileType); -+ if (unloadTask == null) { -+ continue; -+ } -+ -+ final PrioritisedExecutor.PrioritisedTask task = unloadTask.task(); -+ if (task != null && task.isQueued()) { -+ final boolean executed = task.execute(); -+ executedUnloadTask |= executed; -+ executedUnloadTasks[regionFileType.ordinal()] = executed; - } - } - } - -- final boolean forceNoSaveChunk = ChunkSystemFeatures.forceNoSave(chunk); -+ final boolean forceNoSaveChunk = PlatformHooks.get().forceNoSave(chunk); - - // can only synchronously save worldgen chunks during shutdown - boolean canSaveChunk = !forceNoSaveChunk && (chunk != null && ((shutdown || chunk instanceof LevelChunk) && chunk.isUnsaved())); -@@ -1717,106 +1731,55 @@ public final class NewChunkHolder { - } - } - -- return executedUnloadTask | canSaveChunk | canSaveEntities | canSavePOI ? new SaveStat(executedUnloadTask || canSaveChunk, canSaveEntities, canSavePOI): null; -- } -- -- static final class AsyncChunkSerializeTask implements Runnable { -- -- private final ServerLevel world; -- private final ChunkAccess chunk; -- private final AsyncChunkSaveData asyncSaveData; -- private final NewChunkHolder toComplete; -- -- public AsyncChunkSerializeTask(final ServerLevel world, final ChunkAccess chunk, final AsyncChunkSaveData asyncSaveData, -- final NewChunkHolder toComplete) { -- this.world = world; -- this.chunk = chunk; -- this.asyncSaveData = asyncSaveData; -- this.toComplete = toComplete; -- } -- -- @Override -- public void run() { -- final CompoundTag toSerialize; -- try { -- toSerialize = ChunkSystemFeatures.saveChunkAsync(this.world, this.chunk, this.asyncSaveData); -- } catch (final Throwable throwable) { -- LOGGER.error("Failed to asynchronously save chunk " + this.chunk.getPos() + " for world '" + WorldUtil.getWorldName(this.world) + "', falling back to synchronous save", throwable); -- final ChunkPos pos = this.chunk.getPos(); -- ((ChunkSystemServerLevel)this.world).moonrise$getChunkTaskScheduler().scheduleChunkTask(pos.x, pos.z, () -> { -- final CompoundTag synchronousSave; -- try { -- synchronousSave = ChunkSystemFeatures.saveChunkAsync(AsyncChunkSerializeTask.this.world, AsyncChunkSerializeTask.this.chunk, AsyncChunkSerializeTask.this.asyncSaveData); -- } catch (final Throwable throwable2) { -- LOGGER.error("Failed to synchronously save chunk " + AsyncChunkSerializeTask.this.chunk.getPos() + " for world '" + WorldUtil.getWorldName(AsyncChunkSerializeTask.this.world) + "', chunk data will be lost", throwable2); -- AsyncChunkSerializeTask.this.toComplete.completeAsyncUnloadDataSave(RegionFileIOThread.RegionFileType.CHUNK_DATA, null); -- return; -- } -- -- AsyncChunkSerializeTask.this.toComplete.completeAsyncUnloadDataSave(RegionFileIOThread.RegionFileType.CHUNK_DATA, synchronousSave); -- LOGGER.info("Successfully serialized chunk " + AsyncChunkSerializeTask.this.chunk.getPos() + " for world '" + WorldUtil.getWorldName(AsyncChunkSerializeTask.this.world) + "' synchronously"); -- -- }, PrioritisedExecutor.Priority.HIGHEST); -- return; -- } -- this.toComplete.completeAsyncUnloadDataSave(RegionFileIOThread.RegionFileType.CHUNK_DATA, toSerialize); -- } -- -- @Override -- public String toString() { -- return "AsyncChunkSerializeTask{" + -- "chunk={pos=" + this.chunk.getPos() + ",world=\"" + WorldUtil.getWorldName(this.world) + "\"}" + -- "}"; -- } -+ return executedUnloadTask | canSaveChunk | canSaveEntities | canSavePOI ? -+ new SaveStat( -+ canSaveChunk | executedUnloadTasks[MoonriseRegionFileIO.RegionFileType.CHUNK_DATA.ordinal()], -+ canSaveEntities | executedUnloadTasks[MoonriseRegionFileIO.RegionFileType.ENTITY_DATA.ordinal()], -+ canSavePOI | executedUnloadTasks[MoonriseRegionFileIO.RegionFileType.POI_DATA.ordinal()] -+ ) -+ : null; - } - - private boolean saveChunk(final ChunkAccess chunk, final boolean unloading) { - if (!chunk.isUnsaved()) { - if (unloading) { -- this.completeAsyncUnloadDataSave(RegionFileIOThread.RegionFileType.CHUNK_DATA, null); -+ this.completeAsyncUnloadDataSave(MoonriseRegionFileIO.RegionFileType.CHUNK_DATA, null); - } - return false; - } -- boolean completing = false; -- boolean failedAsyncPrepare = false; - try { -- if (unloading && ChunkSystemFeatures.supportsAsyncChunkSave()) { -- try { -- final AsyncChunkSaveData asyncSaveData = ChunkSystemFeatures.getAsyncSaveData(this.world, chunk); -+ final SerializableChunkData chunkData = SerializableChunkData.copyOf(this.world, chunk); -+ PlatformHooks.get().chunkSyncSave(this.world, chunk, chunkData); - -- final PrioritisedExecutor.PrioritisedTask task = this.scheduler.loadExecutor.createTask(new AsyncChunkSerializeTask(this.world, chunk, asyncSaveData, this)); -+ chunk.tryMarkSaved(); - -- this.chunkDataUnload.task().setTask(task); -+ final CallbackCompletable completable = new CallbackCompletable<>(); - -- chunk.setUnsaved(false); -+ final Runnable run = () -> { -+ final CompoundTag data = chunkData.write(); - -- task.queue(); -+ completable.complete(data); - -- return true; -- } catch (final Throwable thr) { -- LOGGER.error("Failed to prepare async chunk data (" + this.chunkX + "," + this.chunkZ + ") in world '" + WorldUtil.getWorldName(this.world) + "', falling back to synchronous save", thr); -- failedAsyncPrepare = true; -- // fall through to synchronous save -+ if (unloading) { -+ NewChunkHolder.this.completeAsyncUnloadDataSave(MoonriseRegionFileIO.RegionFileType.CHUNK_DATA, data); - } -- } -- -- final CompoundTag save = ChunkSerializer.write(this.world, chunk); -+ }; - -+ final PrioritisedExecutor.PrioritisedTask task; - if (unloading) { -- completing = true; -- this.completeAsyncUnloadDataSave(RegionFileIOThread.RegionFileType.CHUNK_DATA, save); -- if (failedAsyncPrepare) { -- LOGGER.info("Successfully serialized chunk data (" + this.chunkX + "," + this.chunkZ + ") in world '" + WorldUtil.getWorldName(this.world) + "' synchronously"); -- } -+ this.chunkDataUnload.toRun().setRunnable(run); -+ task = this.chunkDataUnload.task(); - } else { -- RegionFileIOThread.scheduleSave(this.world, this.chunkX, this.chunkZ, save, RegionFileIOThread.RegionFileType.CHUNK_DATA); -+ task = this.scheduler.saveExecutor.createTask(run); - } -- chunk.setUnsaved(false); -+ -+ task.queue(); -+ -+ MoonriseRegionFileIO.scheduleSave( -+ this.world, this.chunkX, this.chunkZ, completable, task, MoonriseRegionFileIO.RegionFileType.CHUNK_DATA, Priority.NORMAL -+ ); - } catch (final Throwable thr) { - LOGGER.error("Failed to save chunk data (" + this.chunkX + "," + this.chunkZ + ") in world '" + WorldUtil.getWorldName(this.world) + "'", thr); -- if (unloading && !completing) { -- this.completeAsyncUnloadDataSave(RegionFileIOThread.RegionFileType.CHUNK_DATA, null); -- } - } - - return true; -@@ -1834,7 +1797,7 @@ public final class NewChunkHolder { - return false; - } - try { -- mergeFrom = RegionFileIOThread.loadData(this.world, this.chunkX, this.chunkZ, RegionFileIOThread.RegionFileType.ENTITY_DATA, PrioritisedExecutor.Priority.BLOCKING); -+ mergeFrom = MoonriseRegionFileIO.loadData(this.world, this.chunkX, this.chunkZ, MoonriseRegionFileIO.RegionFileType.ENTITY_DATA, Priority.BLOCKING); - } catch (final Exception ex) { - LOGGER.error("Cannot merge transient entities for chunk (" + this.chunkX + "," + this.chunkZ + ") in world '" + WorldUtil.getWorldName(this.world) + "', data on disk will be replaced", ex); - } -@@ -1853,7 +1816,7 @@ public final class NewChunkHolder { - return false; - } - -- RegionFileIOThread.scheduleSave(this.world, this.chunkX, this.chunkZ, save, RegionFileIOThread.RegionFileType.ENTITY_DATA); -+ MoonriseRegionFileIO.scheduleSave(this.world, this.chunkX, this.chunkZ, save, MoonriseRegionFileIO.RegionFileType.ENTITY_DATA); - this.lastEntitySaveNull = save == null; - if (unloading) { - this.lastEntityUnload = save; -@@ -1877,7 +1840,7 @@ public final class NewChunkHolder { - return false; - } - -- RegionFileIOThread.scheduleSave(this.world, this.chunkX, this.chunkZ, save, RegionFileIOThread.RegionFileType.POI_DATA); -+ MoonriseRegionFileIO.scheduleSave(this.world, this.chunkX, this.chunkZ, save, MoonriseRegionFileIO.RegionFileType.POI_DATA); - this.lastPoiSaveNull = save == null; - if (unloading) { - this.poiDataUnload.completable().complete(save); -@@ -1924,7 +1887,7 @@ public final class NewChunkHolder { - return element == null ? JsonNull.INSTANCE : new JsonPrimitive(element.toString()); - } - -- private static JsonObject serializeCompletable(final Completable completable) { -+ private static JsonObject serializeCompletable(final CallbackCompletable completable) { - final JsonObject ret = new JsonObject(); - - if (completable == null) { -@@ -2019,13 +1982,13 @@ public final class NewChunkHolder { - ret.add("poi_unload_completable", serializeCompletable(poiDataUnload == null ? null : poiDataUnload.completable())); - ret.add("chunk_unload_completable", serializeCompletable(chunkDataUnload == null ? null : chunkDataUnload.completable())); - -- final DelayedPrioritisedTask unloadTask = chunkDataUnload == null ? null : chunkDataUnload.task(); -+ final PrioritisedExecutor.PrioritisedTask unloadTask = chunkDataUnload == null ? null : chunkDataUnload.task(); - if (unloadTask == null) { - ret.addProperty("unload_task_priority", "null"); -- ret.addProperty("unload_task_priority_raw", "null"); -+ ret.addProperty("unload_task_suborder", Long.valueOf(0L)); - } else { - ret.addProperty("unload_task_priority", Objects.toString(unloadTask.getPriority())); -- ret.addProperty("unload_task_priority_raw", Integer.valueOf(unloadTask.getPriorityInternal())); -+ ret.addProperty("unload_task_suborder", Long.valueOf(unloadTask.getSubOrder())); - } - - ret.addProperty("killed", Boolean.valueOf(this.unloaded)); -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/PriorityHolder.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/PriorityHolder.java -index 261e09454f49d04eb159c984ec695d7c7aa6a3a8..6b468c621b74449a6218391f6477cf63cfc98c7c 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/PriorityHolder.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/PriorityHolder.java -@@ -1,7 +1,7 @@ - package ca.spottedleaf.moonrise.patches.chunk_system.scheduling; - --import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; - import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import ca.spottedleaf.concurrentutil.util.Priority; - import java.lang.invoke.VarHandle; - - public abstract class PriorityHolder { -@@ -28,8 +28,8 @@ public abstract class PriorityHolder { - PRIORITY_HANDLE.set((PriorityHolder)this, (int)val); - } - -- protected PriorityHolder(final PrioritisedExecutor.Priority priority) { -- if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ protected PriorityHolder(final Priority priority) { -+ if (!Priority.isValidPriority(priority)) { - throw new IllegalArgumentException("Invalid priority " + priority); - } - this.setPriorityPlain(priority.priority); -@@ -69,7 +69,7 @@ public abstract class PriorityHolder { - return; - } - -- this.scheduleTask(PrioritisedExecutor.Priority.getPriority(priority)); -+ this.scheduleTask(Priority.getPriority(priority)); - - int failures = 0; - for (;;) { -@@ -86,7 +86,7 @@ public abstract class PriorityHolder { - return; - } - -- this.setPriorityScheduled(PrioritisedExecutor.Priority.getPriority(priority)); -+ this.setPriorityScheduled(Priority.getPriority(priority)); - - ++failures; - for (int i = 0; i < failures; ++i) { -@@ -95,19 +95,19 @@ public abstract class PriorityHolder { - } - } - -- public final PrioritisedExecutor.Priority getPriority() { -+ public final Priority getPriority() { - final int ret = this.getPriorityVolatile(); - if ((ret & PRIORITY_EXECUTED) != 0) { -- return PrioritisedExecutor.Priority.COMPLETING; -+ return Priority.COMPLETING; - } - if ((ret & PRIORITY_SCHEDULED) != 0) { - return this.getScheduledPriority(); - } -- return PrioritisedExecutor.Priority.getPriority(ret); -+ return Priority.getPriority(ret); - } - -- public final void lowerPriority(final PrioritisedExecutor.Priority priority) { -- if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ public final void lowerPriority(final Priority priority) { -+ if (!Priority.isValidPriority(priority)) { - throw new IllegalArgumentException("Invalid priority " + priority); - } - -@@ -139,8 +139,8 @@ public abstract class PriorityHolder { - } - } - -- public final void setPriority(final PrioritisedExecutor.Priority priority) { -- if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ public final void setPriority(final Priority priority) { -+ if (!Priority.isValidPriority(priority)) { - throw new IllegalArgumentException("Invalid priority " + priority); - } - -@@ -168,8 +168,8 @@ public abstract class PriorityHolder { - } - } - -- public final void raisePriority(final PrioritisedExecutor.Priority priority) { -- if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ public final void raisePriority(final Priority priority) { -+ if (!Priority.isValidPriority(priority)) { - throw new IllegalArgumentException("Invalid priority " + priority); - } - -@@ -203,13 +203,13 @@ public abstract class PriorityHolder { - - protected abstract void cancelScheduled(); - -- protected abstract PrioritisedExecutor.Priority getScheduledPriority(); -+ protected abstract Priority getScheduledPriority(); - -- protected abstract void scheduleTask(final PrioritisedExecutor.Priority priority); -+ protected abstract void scheduleTask(final Priority priority); - -- protected abstract void lowerPriorityScheduled(final PrioritisedExecutor.Priority priority); -+ protected abstract void lowerPriorityScheduled(final Priority priority); - -- protected abstract void setPriorityScheduled(final PrioritisedExecutor.Priority priority); -+ protected abstract void setPriorityScheduled(final Priority priority); - -- protected abstract void raisePriorityScheduled(final PrioritisedExecutor.Priority priority); -+ protected abstract void raisePriorityScheduled(final Priority priority); - } -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/executor/RadiusAwarePrioritisedExecutor.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/executor/RadiusAwarePrioritisedExecutor.java -index e0b26ccb63596748b80fc6a5e47e373ba811ba8b..5f4b99d8c5453f8ad2e600a57ea4e7dafa2d45f8 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/executor/RadiusAwarePrioritisedExecutor.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/executor/RadiusAwarePrioritisedExecutor.java -@@ -1,10 +1,10 @@ - package ca.spottedleaf.moonrise.patches.chunk_system.scheduling.executor; - --import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.util.Priority; - import ca.spottedleaf.moonrise.common.util.CoordinateUtils; - import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; - import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; -- - import java.util.ArrayList; - import java.util.Comparator; - import java.util.List; -@@ -16,15 +16,36 @@ public class RadiusAwarePrioritisedExecutor { - return Long.compare(t1.id, t2.id); - }; - -- private final DependencyTree[] queues = new DependencyTree[PrioritisedExecutor.Priority.TOTAL_SCHEDULABLE_PRIORITIES]; -+ private final PrioritisedExecutor executor; -+ private final DependencyTree[] queues = new DependencyTree[Priority.TOTAL_SCHEDULABLE_PRIORITIES]; - private static final int NO_TASKS_QUEUED = -1; - private int selectedQueue = NO_TASKS_QUEUED; - private boolean canQueueTasks = true; - - public RadiusAwarePrioritisedExecutor(final PrioritisedExecutor executor, final int maxToSchedule) { -+ this.executor = executor; -+ - for (int i = 0; i < this.queues.length; ++i) { -- this.queues[i] = new DependencyTree(this, executor, maxToSchedule, i); -+ this.queues[i] = new DependencyTree(this, executor, maxToSchedule); -+ } -+ } -+ -+ public void setMaxToSchedule(final int maxToSchedule) { -+ final List tasks; -+ -+ synchronized (this) { -+ for (final DependencyTree dependencyTree : this.queues) { -+ dependencyTree.maxToSchedule = maxToSchedule; -+ } -+ -+ if (this.selectedQueue == NO_TASKS_QUEUED || !this.canQueueTasks) { -+ return; -+ } -+ -+ tasks = this.queues[this.selectedQueue].tryPushTasks(); - } -+ -+ scheduleTasks(tasks); - } - - private boolean canQueueTasks() { -@@ -56,7 +77,7 @@ public class RadiusAwarePrioritisedExecutor { - return null; - } - -- private List queue(final Task task, final PrioritisedExecutor.Priority priority) { -+ private List queue(final Task task, final Priority priority) { - final int priorityId = priority.priority; - final DependencyTree queue = this.queues[priorityId]; - -@@ -79,7 +100,7 @@ public class RadiusAwarePrioritisedExecutor { - return null; - } - -- if (PrioritisedExecutor.Priority.isHigherPriority(priorityId, this.selectedQueue)) { -+ if (Priority.isHigherPriority(priorityId, this.selectedQueue)) { - // prevent the lower priority tree from queueing more tasks - this.canQueueTasks = false; - return null; -@@ -90,7 +111,7 @@ public class RadiusAwarePrioritisedExecutor { - } - - public PrioritisedExecutor.PrioritisedTask createTask(final int chunkX, final int chunkZ, final int radius, -- final Runnable run, final PrioritisedExecutor.Priority priority) { -+ final Runnable run, final Priority priority) { - if (radius < 0) { - throw new IllegalArgumentException("Radius must be > 0: " + radius); - } -@@ -99,11 +120,11 @@ public class RadiusAwarePrioritisedExecutor { - - public PrioritisedExecutor.PrioritisedTask createTask(final int chunkX, final int chunkZ, final int radius, - final Runnable run) { -- return this.createTask(chunkX, chunkZ, radius, run, PrioritisedExecutor.Priority.NORMAL); -+ return this.createTask(chunkX, chunkZ, radius, run, Priority.NORMAL); - } - - public PrioritisedExecutor.PrioritisedTask queueTask(final int chunkX, final int chunkZ, final int radius, -- final Runnable run, final PrioritisedExecutor.Priority priority) { -+ final Runnable run, final Priority priority) { - final PrioritisedExecutor.PrioritisedTask ret = this.createTask(chunkX, chunkZ, radius, run, priority); - - ret.queue(); -@@ -120,15 +141,15 @@ public class RadiusAwarePrioritisedExecutor { - return ret; - } - -- public PrioritisedExecutor.PrioritisedTask createInfiniteRadiusTask(final Runnable run, final PrioritisedExecutor.Priority priority) { -+ public PrioritisedExecutor.PrioritisedTask createInfiniteRadiusTask(final Runnable run, final Priority priority) { - return new Task(this, 0, 0, -1, run, priority); - } - - public PrioritisedExecutor.PrioritisedTask createInfiniteRadiusTask(final Runnable run) { -- return this.createInfiniteRadiusTask(run, PrioritisedExecutor.Priority.NORMAL); -+ return this.createInfiniteRadiusTask(run, Priority.NORMAL); - } - -- public PrioritisedExecutor.PrioritisedTask queueInfiniteRadiusTask(final Runnable run, final PrioritisedExecutor.Priority priority) { -+ public PrioritisedExecutor.PrioritisedTask queueInfiniteRadiusTask(final Runnable run, final Priority priority) { - final PrioritisedExecutor.PrioritisedTask ret = this.createInfiniteRadiusTask(run, priority); - - ret.queue(); -@@ -137,20 +158,27 @@ public class RadiusAwarePrioritisedExecutor { - } - - public PrioritisedExecutor.PrioritisedTask queueInfiniteRadiusTask(final Runnable run) { -- final PrioritisedExecutor.PrioritisedTask ret = this.createInfiniteRadiusTask(run, PrioritisedExecutor.Priority.NORMAL); -+ final PrioritisedExecutor.PrioritisedTask ret = this.createInfiniteRadiusTask(run, Priority.NORMAL); - - ret.queue(); - - return ret; - } - -+ private static void scheduleTasks(final List toSchedule) { -+ if (toSchedule != null) { -+ for (int i = 0, len = toSchedule.size(); i < len; ++i) { -+ toSchedule.get(i).queue(); -+ } -+ } -+ } -+ - // all accesses must be synchronised by the radius aware object - private static final class DependencyTree { - - private final RadiusAwarePrioritisedExecutor scheduler; - private final PrioritisedExecutor executor; -- private final int maxToSchedule; -- private final int treeIndex; -+ private int maxToSchedule; - - private int currentlyExecuting; - private long idGenerator; -@@ -163,11 +191,10 @@ public class RadiusAwarePrioritisedExecutor { - private final Long2ReferenceOpenHashMap nodeByPosition = new Long2ReferenceOpenHashMap<>(); - - public DependencyTree(final RadiusAwarePrioritisedExecutor scheduler, final PrioritisedExecutor executor, -- final int maxToSchedule, final int treeIndex) { -+ final int maxToSchedule) { - this.scheduler = scheduler; - this.executor = executor; - this.maxToSchedule = maxToSchedule; -- this.treeIndex = treeIndex; - } - - public boolean hasWaitingTasks() { -@@ -412,13 +439,13 @@ public class RadiusAwarePrioritisedExecutor { - private final int chunkZ; - private final int radius; - private Runnable run; -- private PrioritisedExecutor.Priority priority; -+ private Priority priority; - - private DependencyNode dependencyNode; - private PrioritisedExecutor.PrioritisedTask queuedTask; - - private Task(final RadiusAwarePrioritisedExecutor scheduler, final int chunkX, final int chunkZ, final int radius, -- final Runnable run, final PrioritisedExecutor.Priority priority) { -+ final Runnable run, final Priority priority) { - this.scheduler = scheduler; - this.chunkX = chunkX; - this.chunkZ = chunkZ; -@@ -441,14 +468,6 @@ public class RadiusAwarePrioritisedExecutor { - run.run(); - } - -- private static void scheduleTasks(final List toSchedule) { -- if (toSchedule != null) { -- for (int i = 0, len = toSchedule.size(); i < len; ++i) { -- toSchedule.get(i).queue(); -- } -- } -- } -- - private void returnNode() { - final List toSchedule; - synchronized (this.scheduler) { -@@ -460,6 +479,11 @@ public class RadiusAwarePrioritisedExecutor { - scheduleTasks(toSchedule); - } - -+ @Override -+ public PrioritisedExecutor getExecutor() { -+ return this.scheduler.executor; -+ } -+ - @Override - public void run() { - final Runnable run = this.run; -@@ -475,7 +499,7 @@ public class RadiusAwarePrioritisedExecutor { - public boolean queue() { - final List toSchedule; - synchronized (this.scheduler) { -- if (this.queuedTask != null || this.dependencyNode != null || this.priority == PrioritisedExecutor.Priority.COMPLETING) { -+ if (this.queuedTask != null || this.dependencyNode != null || this.priority == Priority.COMPLETING) { - return false; - } - -@@ -486,16 +510,23 @@ public class RadiusAwarePrioritisedExecutor { - return true; - } - -+ @Override -+ public boolean isQueued() { -+ synchronized (this.scheduler) { -+ return (this.queuedTask != null || this.dependencyNode != null) && this.priority != Priority.COMPLETING; -+ } -+ } -+ - @Override - public boolean cancel() { - final PrioritisedExecutor.PrioritisedTask task; - synchronized (this.scheduler) { - if ((task = this.queuedTask) == null) { -- if (this.priority == PrioritisedExecutor.Priority.COMPLETING) { -+ if (this.priority == Priority.COMPLETING) { - return false; - } - -- this.priority = PrioritisedExecutor.Priority.COMPLETING; -+ this.priority = Priority.COMPLETING; - if (this.dependencyNode != null) { - this.dependencyNode.purged = true; - this.dependencyNode = null; -@@ -519,11 +550,11 @@ public class RadiusAwarePrioritisedExecutor { - final PrioritisedExecutor.PrioritisedTask task; - synchronized (this.scheduler) { - if ((task = this.queuedTask) == null) { -- if (this.priority == PrioritisedExecutor.Priority.COMPLETING) { -+ if (this.priority == Priority.COMPLETING) { - return false; - } - -- this.priority = PrioritisedExecutor.Priority.COMPLETING; -+ this.priority = Priority.COMPLETING; - if (this.dependencyNode != null) { - this.dependencyNode.purged = true; - this.dependencyNode = null; -@@ -543,7 +574,7 @@ public class RadiusAwarePrioritisedExecutor { - } - - @Override -- public PrioritisedExecutor.Priority getPriority() { -+ public Priority getPriority() { - final PrioritisedExecutor.PrioritisedTask task; - synchronized (this.scheduler) { - if ((task = this.queuedTask) == null) { -@@ -555,8 +586,8 @@ public class RadiusAwarePrioritisedExecutor { - } - - @Override -- public boolean setPriority(final PrioritisedExecutor.Priority priority) { -- if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ public boolean setPriority(final Priority priority) { -+ if (!Priority.isValidPriority(priority)) { - throw new IllegalArgumentException("Invalid priority " + priority); - } - -@@ -564,7 +595,7 @@ public class RadiusAwarePrioritisedExecutor { - List toSchedule = null; - synchronized (this.scheduler) { - if ((task = this.queuedTask) == null) { -- if (this.priority == PrioritisedExecutor.Priority.COMPLETING) { -+ if (this.priority == Priority.COMPLETING) { - return false; - } - -@@ -592,8 +623,8 @@ public class RadiusAwarePrioritisedExecutor { - } - - @Override -- public boolean raisePriority(final PrioritisedExecutor.Priority priority) { -- if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ public boolean raisePriority(final Priority priority) { -+ if (!Priority.isValidPriority(priority)) { - throw new IllegalArgumentException("Invalid priority " + priority); - } - -@@ -601,7 +632,7 @@ public class RadiusAwarePrioritisedExecutor { - List toSchedule = null; - synchronized (this.scheduler) { - if ((task = this.queuedTask) == null) { -- if (this.priority == PrioritisedExecutor.Priority.COMPLETING) { -+ if (this.priority == Priority.COMPLETING) { - return false; - } - -@@ -629,8 +660,8 @@ public class RadiusAwarePrioritisedExecutor { - } - - @Override -- public boolean lowerPriority(final PrioritisedExecutor.Priority priority) { -- if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ public boolean lowerPriority(final Priority priority) { -+ if (!Priority.isValidPriority(priority)) { - throw new IllegalArgumentException("Invalid priority " + priority); - } - -@@ -638,7 +669,7 @@ public class RadiusAwarePrioritisedExecutor { - List toSchedule = null; - synchronized (this.scheduler) { - if ((task = this.queuedTask) == null) { -- if (this.priority == PrioritisedExecutor.Priority.COMPLETING) { -+ if (this.priority == Priority.COMPLETING) { - return false; - } - -@@ -664,5 +695,35 @@ public class RadiusAwarePrioritisedExecutor { - - return true; - } -+ -+ @Override -+ public long getSubOrder() { -+ // TODO implement -+ return 0; -+ } -+ -+ @Override -+ public boolean setSubOrder(final long subOrder) { -+ // TODO implement -+ return false; -+ } -+ -+ @Override -+ public boolean raiseSubOrder(final long subOrder) { -+ // TODO implement -+ return false; -+ } -+ -+ @Override -+ public boolean lowerSubOrder(final long subOrder) { -+ // TODO implement -+ return false; -+ } -+ -+ @Override -+ public boolean setPriorityAndSubOrder(final Priority priority, final long subOrder) { -+ // TODO implement -+ return this.setPriority(priority); -+ } - } --} -\ No newline at end of file -+} -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkFullTask.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkFullTask.java -index fbdf721e8b4cfe6cef4ee60c53c680cbfc858d88..6ab353b0d2465c3680bb3c8d0852ba0f65c00fd2 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkFullTask.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkFullTask.java -@@ -1,7 +1,9 @@ - package ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task; - --import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor; - import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import ca.spottedleaf.concurrentutil.util.Priority; -+import ca.spottedleaf.moonrise.common.PlatformHooks; - import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel; - import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk; - import ca.spottedleaf.moonrise.patches.chunk_system.level.poi.ChunkSystemPoiManager; -@@ -29,7 +31,7 @@ public final class ChunkFullTask extends ChunkProgressionTask implements Runnabl - private final PrioritisedExecutor.PrioritisedTask convertToFullTask; - - public ChunkFullTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, final int chunkZ, -- final NewChunkHolder chunkHolder, final ChunkAccess fromChunk, final PrioritisedExecutor.Priority priority) { -+ final NewChunkHolder chunkHolder, final ChunkAccess fromChunk, final Priority priority) { - super(scheduler, world, chunkX, chunkZ); - this.chunkHolder = chunkHolder; - this.fromChunk = fromChunk; -@@ -43,6 +45,8 @@ public final class ChunkFullTask extends ChunkProgressionTask implements Runnabl - - @Override - public void run() { -+ final PlatformHooks platformHooks = PlatformHooks.get(); -+ - // See Vanilla ChunkPyramid#LOADING_PYRAMID.FULL for what this function should be doing - final LevelChunk chunk; - try { -@@ -61,7 +65,7 @@ public final class ChunkFullTask extends ChunkProgressionTask implements Runnabl - final ServerLevel world = this.world; - final ProtoChunk protoChunk = (ProtoChunk)this.fromChunk; - chunk = new LevelChunk(this.world, protoChunk, (final LevelChunk unused) -> { -- ChunkStatusTasks.postLoadProtoChunk(world, protoChunk.getEntities(), protoChunk.getPos()); // Paper - pass chunk pos -+ PlatformHooks.get().postLoadProtoChunk(world, protoChunk); - }); - this.chunkHolder.replaceProtoChunk(new ImposterProtoChunk(chunk, false)); - } -@@ -71,16 +75,21 @@ public final class ChunkFullTask extends ChunkProgressionTask implements Runnabl - final NewChunkHolder chunkHolder = this.chunkHolder; - - chunk.setFullStatus(chunkHolder::getChunkStatus); -- chunk.runPostLoad(); -- // Unlike Vanilla, we load the entity chunk here, as we load the NBT in empty status (unlike Vanilla) -- // This brings entity addition back in line with older versions of the game -- // Since we load the NBT in the empty status, this will never block for I/O -- ((ChunkSystemServerLevel)this.world).moonrise$getChunkTaskScheduler().chunkHolderManager.getOrCreateEntityChunk(this.chunkX, this.chunkZ, false); -- -- // we don't need the entitiesInLevel, not sure why it's there -- chunk.setLoaded(true); -- chunk.registerAllBlockEntitiesAfterLevelLoad(); -- chunk.registerTickContainerInLevel(this.world); -+ try { -+ platformHooks.setCurrentlyLoading(this.chunkHolder.vanillaChunkHolder, chunk); -+ chunk.runPostLoad(); -+ // Unlike Vanilla, we load the entity chunk here, as we load the NBT in empty status (unlike Vanilla) -+ // This brings entity addition back in line with older versions of the game -+ // Since we load the NBT in the empty status, this will never block for I/O -+ ((ChunkSystemServerLevel)this.world).moonrise$getChunkTaskScheduler().chunkHolderManager.getOrCreateEntityChunk(this.chunkX, this.chunkZ, false); -+ chunk.setLoaded(true); -+ chunk.registerAllBlockEntitiesAfterLevelLoad(); -+ chunk.registerTickContainerInLevel(this.world); -+ chunk.setUnsavedListener(this.world.getChunkSource().chunkMap.worldGenContext.unsavedListener()); -+ platformHooks.chunkFullStatusComplete(chunk, (ProtoChunk)this.fromChunk); -+ } finally { -+ platformHooks.setCurrentlyLoading(this.chunkHolder.vanillaChunkHolder, null); -+ } - } catch (final Throwable throwable) { - this.complete(null, throwable); - return; -@@ -112,29 +121,29 @@ public final class ChunkFullTask extends ChunkProgressionTask implements Runnabl - } - - @Override -- public PrioritisedExecutor.Priority getPriority() { -+ public Priority getPriority() { - return this.convertToFullTask.getPriority(); - } - - @Override -- public void lowerPriority(final PrioritisedExecutor.Priority priority) { -- if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ public void lowerPriority(final Priority priority) { -+ if (!Priority.isValidPriority(priority)) { - throw new IllegalArgumentException("Invalid priority " + priority); - } - this.convertToFullTask.lowerPriority(priority); - } - - @Override -- public void setPriority(final PrioritisedExecutor.Priority priority) { -- if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ public void setPriority(final Priority priority) { -+ if (!Priority.isValidPriority(priority)) { - throw new IllegalArgumentException("Invalid priority " + priority); - } - this.convertToFullTask.setPriority(priority); - } - - @Override -- public void raisePriority(final PrioritisedExecutor.Priority priority) { -- if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ public void raisePriority(final Priority priority) { -+ if (!Priority.isValidPriority(priority)) { - throw new IllegalArgumentException("Invalid priority " + priority); - } - this.convertToFullTask.raisePriority(priority); -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkLightTask.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkLightTask.java -index 7c2e6752228fac175c4aa97fa3d817b8a938922f..4538ccfaea83d217ed85eaf16e82393c7f286489 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkLightTask.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkLightTask.java -@@ -1,6 +1,6 @@ - package ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task; - --import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.util.Priority; - import ca.spottedleaf.moonrise.common.util.WorldUtil; - import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler; - import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.PriorityHolder; -@@ -25,9 +25,9 @@ public final class ChunkLightTask extends ChunkProgressionTask { - private final LightTaskPriorityHolder priorityHolder; - - public ChunkLightTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, final int chunkZ, -- final ChunkAccess chunk, final PrioritisedExecutor.Priority priority) { -+ final ChunkAccess chunk, final Priority priority) { - super(scheduler, world, chunkX, chunkZ); -- if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ if (!Priority.isValidPriority(priority)) { - throw new IllegalArgumentException("Invalid priority " + priority); - } - this.priorityHolder = new LightTaskPriorityHolder(priority, this); -@@ -55,22 +55,22 @@ public final class ChunkLightTask extends ChunkProgressionTask { - } - - @Override -- public PrioritisedExecutor.Priority getPriority() { -+ public Priority getPriority() { - return this.priorityHolder.getPriority(); - } - - @Override -- public void lowerPriority(final PrioritisedExecutor.Priority priority) { -+ public void lowerPriority(final Priority priority) { - this.priorityHolder.raisePriority(priority); - } - - @Override -- public void setPriority(final PrioritisedExecutor.Priority priority) { -+ public void setPriority(final Priority priority) { - this.priorityHolder.setPriority(priority); - } - - @Override -- public void raisePriority(final PrioritisedExecutor.Priority priority) { -+ public void raisePriority(final Priority priority) { - this.priorityHolder.raisePriority(priority); - } - -@@ -78,7 +78,7 @@ public final class ChunkLightTask extends ChunkProgressionTask { - - private final ChunkLightTask task; - -- private LightTaskPriorityHolder(final PrioritisedExecutor.Priority priority, final ChunkLightTask task) { -+ private LightTaskPriorityHolder(final Priority priority, final ChunkLightTask task) { - super(priority); - this.task = task; - } -@@ -90,13 +90,13 @@ public final class ChunkLightTask extends ChunkProgressionTask { - } - - @Override -- protected PrioritisedExecutor.Priority getScheduledPriority() { -+ protected Priority getScheduledPriority() { - final ChunkLightTask task = this.task; - return ((StarLightLightingProvider)task.world.getChunkSource().getLightEngine()).starlight$getLightEngine().getServerLightQueue().getPriority(task.chunkX, task.chunkZ); - } - - @Override -- protected void scheduleTask(final PrioritisedExecutor.Priority priority) { -+ protected void scheduleTask(final Priority priority) { - final ChunkLightTask task = this.task; - final StarLightInterface starLightInterface = ((StarLightLightingProvider)task.world.getChunkSource().getLightEngine()).starlight$getLightEngine(); - final StarLightInterface.ServerLightQueue lightQueue = starLightInterface.getServerLightQueue(); -@@ -105,7 +105,7 @@ public final class ChunkLightTask extends ChunkProgressionTask { - } - - @Override -- protected void lowerPriorityScheduled(final PrioritisedExecutor.Priority priority) { -+ protected void lowerPriorityScheduled(final Priority priority) { - final ChunkLightTask task = this.task; - final StarLightInterface starLightInterface = ((StarLightLightingProvider)task.world.getChunkSource().getLightEngine()).starlight$getLightEngine(); - final StarLightInterface.ServerLightQueue lightQueue = starLightInterface.getServerLightQueue(); -@@ -113,7 +113,7 @@ public final class ChunkLightTask extends ChunkProgressionTask { - } - - @Override -- protected void setPriorityScheduled(final PrioritisedExecutor.Priority priority) { -+ protected void setPriorityScheduled(final Priority priority) { - final ChunkLightTask task = this.task; - final StarLightInterface starLightInterface = ((StarLightLightingProvider)task.world.getChunkSource().getLightEngine()).starlight$getLightEngine(); - final StarLightInterface.ServerLightQueue lightQueue = starLightInterface.getServerLightQueue(); -@@ -121,7 +121,7 @@ public final class ChunkLightTask extends ChunkProgressionTask { - } - - @Override -- protected void raisePriorityScheduled(final PrioritisedExecutor.Priority priority) { -+ protected void raisePriorityScheduled(final Priority priority) { - final ChunkLightTask task = this.task; - final StarLightInterface starLightInterface = ((StarLightLightingProvider)task.world.getChunkSource().getLightEngine()).starlight$getLightEngine(); - final StarLightInterface.ServerLightQueue lightQueue = starLightInterface.getServerLightQueue(); -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkLoadTask.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkLoadTask.java -index 1ab93f219246d0b4dcdfd0f685f47c13091425f8..e0a88615a8b6d58191f29b1ff1a26427f0a4c1a6 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkLoadTask.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkLoadTask.java -@@ -1,12 +1,13 @@ - package ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task; - - import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; --import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor; - import ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock; - import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import ca.spottedleaf.concurrentutil.util.Priority; -+import ca.spottedleaf.moonrise.common.PlatformHooks; - import ca.spottedleaf.moonrise.patches.chunk_system.ChunkSystemConverters; --import ca.spottedleaf.moonrise.patches.chunk_system.ChunkSystemFeatures; --import ca.spottedleaf.moonrise.patches.chunk_system.io.RegionFileIOThread; -+import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO; - import ca.spottedleaf.moonrise.patches.chunk_system.level.poi.PoiChunk; - import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler; - import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder; -@@ -18,7 +19,7 @@ import net.minecraft.world.level.chunk.ChunkAccess; - import net.minecraft.world.level.chunk.ProtoChunk; - import net.minecraft.world.level.chunk.UpgradeData; - import net.minecraft.world.level.chunk.status.ChunkStatus; --import net.minecraft.world.level.chunk.storage.ChunkSerializer; -+import net.minecraft.world.level.chunk.storage.SerializableChunkData; - import net.minecraft.world.level.levelgen.blending.BlendingData; - import org.slf4j.Logger; - import org.slf4j.LoggerFactory; -@@ -41,7 +42,7 @@ public final class ChunkLoadTask extends ChunkProgressionTask { - private final AtomicInteger taskCountToComplete = new AtomicInteger(3); // one for poi, one for entity, and one for chunk data - - public ChunkLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, final int chunkZ, -- final NewChunkHolder chunkHolder, final PrioritisedExecutor.Priority priority) { -+ final NewChunkHolder chunkHolder, final Priority priority) { - super(scheduler, world, chunkX, chunkZ); - this.chunkHolder = chunkHolder; - this.loadTask = new ChunkDataLoadTask(scheduler, world, chunkX, chunkZ, priority); -@@ -170,12 +171,12 @@ public final class ChunkLoadTask extends ChunkProgressionTask { - } - - @Override -- public PrioritisedExecutor.Priority getPriority() { -+ public Priority getPriority() { - return this.loadTask.getPriority(); - } - - @Override -- public void lowerPriority(final PrioritisedExecutor.Priority priority) { -+ public void lowerPriority(final Priority priority) { - final EntityDataLoadTask entityLoad = this.chunkHolder.getEntityDataLoadTask(); - if (entityLoad != null) { - entityLoad.lowerPriority(priority); -@@ -191,7 +192,7 @@ public final class ChunkLoadTask extends ChunkProgressionTask { - } - - @Override -- public void setPriority(final PrioritisedExecutor.Priority priority) { -+ public void setPriority(final Priority priority) { - final EntityDataLoadTask entityLoad = this.chunkHolder.getEntityDataLoadTask(); - if (entityLoad != null) { - entityLoad.setPriority(priority); -@@ -207,7 +208,7 @@ public final class ChunkLoadTask extends ChunkProgressionTask { - } - - @Override -- public void raisePriority(final PrioritisedExecutor.Priority priority) { -+ public void raisePriority(final Priority priority) { - final EntityDataLoadTask entityLoad = this.chunkHolder.getEntityDataLoadTask(); - if (entityLoad != null) { - entityLoad.raisePriority(priority); -@@ -231,8 +232,8 @@ public final class ChunkLoadTask extends ChunkProgressionTask { - protected static final VarHandle COMPLETED_HANDLE = ConcurrentUtil.getVarHandle(CallbackDataLoadTask.class, "completed", boolean.class); - - protected CallbackDataLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, -- final int chunkZ, final RegionFileIOThread.RegionFileType type, -- final PrioritisedExecutor.Priority priority) { -+ final int chunkZ, final MoonriseRegionFileIO.RegionFileType type, -+ final Priority priority) { - super(scheduler, world, chunkX, chunkZ, type, priority); - } - -@@ -272,10 +273,13 @@ public final class ChunkLoadTask extends ChunkProgressionTask { - } - } - -- private static final class ChunkDataLoadTask extends CallbackDataLoadTask { -+ -+ private static record ReadChunk(ProtoChunk protoChunk, SerializableChunkData chunkData) {} -+ -+ private static final class ChunkDataLoadTask extends CallbackDataLoadTask { - private ChunkDataLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, -- final int chunkZ, final PrioritisedExecutor.Priority priority) { -- super(scheduler, world, chunkX, chunkZ, RegionFileIOThread.RegionFileType.CHUNK_DATA, priority); -+ final int chunkZ, final Priority priority) { -+ super(scheduler, world, chunkX, chunkZ, MoonriseRegionFileIO.RegionFileType.CHUNK_DATA, priority); - } - - @Override -@@ -289,40 +293,42 @@ public final class ChunkLoadTask extends ChunkProgressionTask { - } - - @Override -- protected PrioritisedExecutor.PrioritisedTask createOffMain(final Runnable run, final PrioritisedExecutor.Priority priority) { -+ protected PrioritisedExecutor.PrioritisedTask createOffMain(final Runnable run, final Priority priority) { - return this.scheduler.loadExecutor.createTask(run, priority); - } - - @Override -- protected PrioritisedExecutor.PrioritisedTask createOnMain(final Runnable run, final PrioritisedExecutor.Priority priority) { -+ protected PrioritisedExecutor.PrioritisedTask createOnMain(final Runnable run, final Priority priority) { - return this.scheduler.createChunkTask(this.chunkX, this.chunkZ, run, priority); - } - - @Override -- protected TaskResult completeOnMainOffMain(final CompoundTag data, final Throwable throwable) { -+ protected TaskResult completeOnMainOffMain(final ReadChunk data, final Throwable throwable) { - if (throwable != null) { - return new TaskResult<>(null, throwable); - } -- if (data == null) { -+ -+ if (data == null || data.protoChunk() == null) { - return new TaskResult<>(this.getEmptyChunk(), null); - } - -- if (ChunkSystemFeatures.supportsAsyncChunkDeserialization()) { -- return this.deserialize(data); -+ if (!PlatformHooks.get().hasMainChunkLoadHook()) { -+ return new TaskResult<>(data.protoChunk(), null); - } -- // need to deserialize on main thread -+ -+ // need to invoke the callback for loading on the main thread - return null; - } - - private ProtoChunk getEmptyChunk() { - return new ProtoChunk( - new ChunkPos(this.chunkX, this.chunkZ), UpgradeData.EMPTY, this.world, -- this.world.registryAccess().registryOrThrow(Registries.BIOME), (BlendingData)null -+ this.world.registryAccess().lookupOrThrow(Registries.BIOME), (BlendingData)null - ); - } - - @Override -- protected TaskResult runOffMain(final CompoundTag data, final Throwable throwable) { -+ protected TaskResult runOffMain(final CompoundTag data, final Throwable throwable) { - if (throwable != null) { - LOGGER.error("Failed to load chunk data for task: " + this.toString() + ", chunk data will be lost", throwable); - return new TaskResult<>(null, null); -@@ -334,42 +340,43 @@ public final class ChunkLoadTask extends ChunkProgressionTask { - - try { - // run converters -- final CompoundTag converted = this.world.getChunkSource().chunkMap.upgradeChunkTag(data, new net.minecraft.world.level.ChunkPos(this.chunkX, this.chunkZ)); -+ final CompoundTag converted = this.world.getChunkSource().chunkMap.upgradeChunkTag(data); - -- return new TaskResult<>(converted, null); -- } catch (final Throwable thr2) { -- LOGGER.error("Failed to parse chunk data for task: " + this.toString() + ", chunk data will be lost", thr2); -- return new TaskResult<>(null, null); -- } -- } -+ // unpack the data -+ final SerializableChunkData chunkData = SerializableChunkData.parse( -+ this.world, this.world.registryAccess(), converted -+ ); - -- private TaskResult deserialize(final CompoundTag data) { -- try { -- final ChunkAccess deserialized = ChunkSerializer.read( -- this.world, this.world.getPoiManager(), this.world.getChunkSource().chunkMap.storageInfo(), new ChunkPos(this.chunkX, this.chunkZ), data -+ if (chunkData == null) { -+ LOGGER.error("Deserialized chunk for task: " + this.toString() + " produced null, chunk data will be lost?"); -+ } -+ -+ // read into ProtoChunk -+ final ProtoChunk chunk = chunkData == null ? null : chunkData.read( -+ this.world, this.world.getPoiManager(), this.world.getChunkSource().chunkMap.storageInfo(), -+ new ChunkPos(this.chunkX, this.chunkZ) - ); -- return new TaskResult<>(deserialized, null); -+ -+ return new TaskResult<>(new ReadChunk(chunk, chunkData), null); - } catch (final Throwable thr2) { - LOGGER.error("Failed to parse chunk data for task: " + this.toString() + ", chunk data will be lost", thr2); -- return new TaskResult<>(this.getEmptyChunk(), null); -+ return new TaskResult<>(null, null); - } - } - - @Override -- protected TaskResult runOnMain(final CompoundTag data, final Throwable throwable) { -- // data != null && throwable == null -- if (ChunkSystemFeatures.supportsAsyncChunkDeserialization()) { -- throw new UnsupportedOperationException(); -- } -- return this.deserialize(data); -+ protected TaskResult runOnMain(final ReadChunk data, final Throwable throwable) { -+ PlatformHooks.get().mainChunkLoad(data.protoChunk(), data.chunkData()); -+ -+ return new TaskResult<>(data.protoChunk(), null); - } - } - - public static final class PoiDataLoadTask extends CallbackDataLoadTask { - - public PoiDataLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, -- final int chunkZ, final PrioritisedExecutor.Priority priority) { -- super(scheduler, world, chunkX, chunkZ, RegionFileIOThread.RegionFileType.POI_DATA, priority); -+ final int chunkZ, final Priority priority) { -+ super(scheduler, world, chunkX, chunkZ, MoonriseRegionFileIO.RegionFileType.POI_DATA, priority); - } - - @Override -@@ -383,12 +390,12 @@ public final class ChunkLoadTask extends ChunkProgressionTask { - } - - @Override -- protected PrioritisedExecutor.PrioritisedTask createOffMain(final Runnable run, final PrioritisedExecutor.Priority priority) { -+ protected PrioritisedExecutor.PrioritisedTask createOffMain(final Runnable run, final Priority priority) { - return this.scheduler.loadExecutor.createTask(run, priority); - } - - @Override -- protected PrioritisedExecutor.PrioritisedTask createOnMain(final Runnable run, final PrioritisedExecutor.Priority priority) { -+ protected PrioritisedExecutor.PrioritisedTask createOnMain(final Runnable run, final Priority priority) { - throw new UnsupportedOperationException(); - } - -@@ -430,8 +437,8 @@ public final class ChunkLoadTask extends ChunkProgressionTask { - public static final class EntityDataLoadTask extends CallbackDataLoadTask { - - public EntityDataLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, -- final int chunkZ, final PrioritisedExecutor.Priority priority) { -- super(scheduler, world, chunkX, chunkZ, RegionFileIOThread.RegionFileType.ENTITY_DATA, priority); -+ final int chunkZ, final Priority priority) { -+ super(scheduler, world, chunkX, chunkZ, MoonriseRegionFileIO.RegionFileType.ENTITY_DATA, priority); - } - - @Override -@@ -445,12 +452,12 @@ public final class ChunkLoadTask extends ChunkProgressionTask { - } - - @Override -- protected PrioritisedExecutor.PrioritisedTask createOffMain(final Runnable run, final PrioritisedExecutor.Priority priority) { -+ protected PrioritisedExecutor.PrioritisedTask createOffMain(final Runnable run, final Priority priority) { - return this.scheduler.loadExecutor.createTask(run, priority); - } - - @Override -- protected PrioritisedExecutor.PrioritisedTask createOnMain(final Runnable run, final PrioritisedExecutor.Priority priority) { -+ protected PrioritisedExecutor.PrioritisedTask createOnMain(final Runnable run, final Priority priority) { - throw new UnsupportedOperationException(); - } - -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkProgressionTask.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkProgressionTask.java -index 70e900b0f9c131900bf8b3f3ecbfbd5df5361205..002ee365aa70d8e6a6e6bd5c95988bd17db4395a 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkProgressionTask.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkProgressionTask.java -@@ -1,8 +1,8 @@ - package ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task; - - import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; --import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; - import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import ca.spottedleaf.concurrentutil.util.Priority; - import ca.spottedleaf.moonrise.common.util.WorldUtil; - import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler; - import net.minecraft.server.level.ServerLevel; -@@ -46,15 +46,15 @@ public abstract class ChunkProgressionTask { - /* May be called multiple times */ - public abstract void cancel(); - -- public abstract PrioritisedExecutor.Priority getPriority(); -+ public abstract Priority getPriority(); - - /* Schedule lock is always held for the priority update calls */ - -- public abstract void lowerPriority(final PrioritisedExecutor.Priority priority); -+ public abstract void lowerPriority(final Priority priority); - -- public abstract void setPriority(final PrioritisedExecutor.Priority priority); -+ public abstract void setPriority(final Priority priority); - -- public abstract void raisePriority(final PrioritisedExecutor.Priority priority); -+ public abstract void raisePriority(final Priority priority); - - public final void onComplete(final BiConsumer onComplete) { - if (!this.waiters.add(onComplete)) { -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkUpgradeGenericStatusTask.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkUpgradeGenericStatusTask.java -index 2c17d5589f15f1155be08be670d29acbe954a8fa..25d8da4773dcee5096053e7e3788bfc224d705a7 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkUpgradeGenericStatusTask.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkUpgradeGenericStatusTask.java -@@ -1,7 +1,8 @@ - package ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task; - --import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor; - import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import ca.spottedleaf.concurrentutil.util.Priority; - import ca.spottedleaf.moonrise.common.util.WorldUtil; - import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemChunkStatus; - import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler; -@@ -36,9 +37,9 @@ public final class ChunkUpgradeGenericStatusTask extends ChunkProgressionTask im - - public ChunkUpgradeGenericStatusTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, - final int chunkZ, final ChunkAccess chunk, final StaticCache2D neighbours, -- final ChunkStatus toStatus, final PrioritisedExecutor.Priority priority) { -+ final ChunkStatus toStatus, final Priority priority) { - super(scheduler, world, chunkX, chunkZ); -- if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ if (!Priority.isValidPriority(priority)) { - throw new IllegalArgumentException("Invalid priority " + priority); - } - this.fromChunk = chunk; -@@ -187,29 +188,29 @@ public final class ChunkUpgradeGenericStatusTask extends ChunkProgressionTask im - } - - @Override -- public PrioritisedExecutor.Priority getPriority() { -+ public Priority getPriority() { - return this.generateTask.getPriority(); - } - - @Override -- public void lowerPriority(final PrioritisedExecutor.Priority priority) { -- if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ public void lowerPriority(final Priority priority) { -+ if (!Priority.isValidPriority(priority)) { - throw new IllegalArgumentException("Invalid priority " + priority); - } - this.generateTask.lowerPriority(priority); - } - - @Override -- public void setPriority(final PrioritisedExecutor.Priority priority) { -- if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ public void setPriority(final Priority priority) { -+ if (!Priority.isValidPriority(priority)) { - throw new IllegalArgumentException("Invalid priority " + priority); - } - this.generateTask.setPriority(priority); - } - - @Override -- public void raisePriority(final PrioritisedExecutor.Priority priority) { -- if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ public void raisePriority(final Priority priority) { -+ if (!Priority.isValidPriority(priority)) { - throw new IllegalArgumentException("Invalid priority " + priority); - } - this.generateTask.raisePriority(priority); -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/GenericDataLoadTask.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/GenericDataLoadTask.java -index 7a65d351b448873c6f2c145c975c92be314b876c..bdcd1879457bafcca4e76523aac0555968f37c0b 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/GenericDataLoadTask.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/GenericDataLoadTask.java -@@ -1,12 +1,13 @@ - package ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task; - -+import ca.spottedleaf.concurrentutil.completable.CallbackCompletable; - import ca.spottedleaf.concurrentutil.completable.Completable; - import ca.spottedleaf.concurrentutil.executor.Cancellable; --import ca.spottedleaf.concurrentutil.executor.standard.DelayedPrioritisedTask; --import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor; - import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import ca.spottedleaf.concurrentutil.util.Priority; - import ca.spottedleaf.moonrise.common.util.WorldUtil; --import ca.spottedleaf.moonrise.patches.chunk_system.io.RegionFileIOThread; -+import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO; - import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel; - import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler; - import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder; -@@ -47,11 +48,11 @@ public abstract class GenericDataLoadTask { - protected final ServerLevel world; - protected final int chunkX; - protected final int chunkZ; -- protected final RegionFileIOThread.RegionFileType type; -+ protected final MoonriseRegionFileIO.RegionFileType type; - - public GenericDataLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, -- final int chunkZ, final RegionFileIOThread.RegionFileType type, -- final PrioritisedExecutor.Priority priority) { -+ final int chunkZ, final MoonriseRegionFileIO.RegionFileType type, -+ final Priority priority) { - this.scheduler = scheduler; - this.world = world; - this.chunkX = chunkX; -@@ -89,9 +90,9 @@ public abstract class GenericDataLoadTask { - - protected abstract boolean hasOnMain(); - -- protected abstract PrioritisedExecutor.PrioritisedTask createOffMain(final Runnable run, final PrioritisedExecutor.Priority priority); -+ protected abstract PrioritisedExecutor.PrioritisedTask createOffMain(final Runnable run, final Priority priority); - -- protected abstract PrioritisedExecutor.PrioritisedTask createOnMain(final Runnable run, final PrioritisedExecutor.Priority priority); -+ protected abstract PrioritisedExecutor.PrioritisedTask createOnMain(final Runnable run, final Priority priority); - - protected abstract TaskResult runOffMain(final CompoundTag data, final Throwable throwable); - -@@ -108,7 +109,7 @@ public abstract class GenericDataLoadTask { - ", type: " + this.type.toString() + "}"; - } - -- public PrioritisedExecutor.Priority getPriority() { -+ public Priority getPriority() { - if (this.processOnMain != null) { - return this.processOnMain.getPriority(); - } else { -@@ -116,7 +117,7 @@ public abstract class GenericDataLoadTask { - } - } - -- public void lowerPriority(final PrioritisedExecutor.Priority priority) { -+ public void lowerPriority(final Priority priority) { - // can't lower I/O tasks, we don't know what they affect - if (this.processOffMain != null) { - this.processOffMain.lowerPriority(priority); -@@ -126,7 +127,7 @@ public abstract class GenericDataLoadTask { - } - } - -- public void setPriority(final PrioritisedExecutor.Priority priority) { -+ public void setPriority(final Priority priority) { - // can't lower I/O tasks, we don't know what they affect - this.loadDataFromDiskTask.raisePriority(priority); - if (this.processOffMain != null) { -@@ -137,7 +138,7 @@ public abstract class GenericDataLoadTask { - } - } - -- public void raisePriority(final PrioritisedExecutor.Priority priority) { -+ public void raisePriority(final Priority priority) { - // can't lower I/O tasks, we don't know what they affect - this.loadDataFromDiskTask.raisePriority(priority); - if (this.processOffMain != null) { -@@ -382,10 +383,10 @@ public abstract class GenericDataLoadTask { - private final int chunkX; - private final int chunkZ; - -- private final RegionFileIOThread.RegionFileType type; -+ private final MoonriseRegionFileIO.RegionFileType type; - private Cancellable dataLoadTask; - private Cancellable dataUnloadCancellable; -- private DelayedPrioritisedTask dataUnloadTask; -+ private PrioritisedExecutor.PrioritisedTask dataUnloadTask; - - private final BiConsumer onComplete; - private final AtomicBoolean scheduled = new AtomicBoolean(); -@@ -393,10 +394,10 @@ public abstract class GenericDataLoadTask { - // onComplete should be caller sensitive, it may complete synchronously with schedule() - which does - // hold a priority lock. - public LoadDataFromDiskTask(final ServerLevel world, final int chunkX, final int chunkZ, -- final RegionFileIOThread.RegionFileType type, -+ final MoonriseRegionFileIO.RegionFileType type, - final BiConsumer onComplete, -- final PrioritisedExecutor.Priority priority) { -- if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ final Priority priority) { -+ if (!Priority.isValidPriority(priority)) { - throw new IllegalArgumentException("Invalid priority " + priority); - } - this.world = world; -@@ -426,8 +427,8 @@ public abstract class GenericDataLoadTask { - return (this.getPriorityVolatile() & PRIORITY_EXECUTED) != 0; - } - -- public void lowerPriority(final PrioritisedExecutor.Priority priority) { -- if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ public void lowerPriority(final Priority priority) { -+ if (!Priority.isValidPriority(priority)) { - throw new IllegalArgumentException("Invalid priority " + priority); - } - -@@ -439,7 +440,7 @@ public abstract class GenericDataLoadTask { - } - - if ((curr & PRIORITY_LOAD_SCHEDULED) != 0) { -- RegionFileIOThread.lowerPriority(this.world, this.chunkX, this.chunkZ, this.type, priority); -+ MoonriseRegionFileIO.lowerPriority(this.world, this.chunkX, this.chunkZ, this.type, priority); - return; - } - -@@ -467,8 +468,8 @@ public abstract class GenericDataLoadTask { - } - } - -- public void setPriority(final PrioritisedExecutor.Priority priority) { -- if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ public void setPriority(final Priority priority) { -+ if (!Priority.isValidPriority(priority)) { - throw new IllegalArgumentException("Invalid priority " + priority); - } - -@@ -480,7 +481,7 @@ public abstract class GenericDataLoadTask { - } - - if ((curr & PRIORITY_LOAD_SCHEDULED) != 0) { -- RegionFileIOThread.setPriority(this.world, this.chunkX, this.chunkZ, this.type, priority); -+ MoonriseRegionFileIO.setPriority(this.world, this.chunkX, this.chunkZ, this.type, priority); - return; - } - -@@ -504,8 +505,8 @@ public abstract class GenericDataLoadTask { - } - } - -- public void raisePriority(final PrioritisedExecutor.Priority priority) { -- if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ public void raisePriority(final Priority priority) { -+ if (!Priority.isValidPriority(priority)) { - throw new IllegalArgumentException("Invalid priority " + priority); - } - -@@ -517,7 +518,7 @@ public abstract class GenericDataLoadTask { - } - - if ((curr & PRIORITY_LOAD_SCHEDULED) != 0) { -- RegionFileIOThread.raisePriority(this.world, this.chunkX, this.chunkZ, this.type, priority); -+ MoonriseRegionFileIO.raisePriority(this.world, this.chunkX, this.chunkZ, this.type, priority); - return; - } - -@@ -583,7 +584,7 @@ public abstract class GenericDataLoadTask { - } // else: cancelled - }; - -- final PrioritisedExecutor.Priority initialPriority = PrioritisedExecutor.Priority.getPriority(priority); -+ final Priority initialPriority = Priority.getPriority(priority); - boolean scheduledUnload = false; - - final NewChunkHolder holder = ((ChunkSystemServerLevel)this.world).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(this.chunkX, this.chunkZ); -@@ -593,13 +594,13 @@ public abstract class GenericDataLoadTask { - consumer.accept(data, null); - } else { - // need to schedule task -- LoadDataFromDiskTask.this.schedule(false, consumer, PrioritisedExecutor.Priority.getPriority(LoadDataFromDiskTask.this.getPriorityVolatile() & ~PRIORITY_FLAGS)); -+ LoadDataFromDiskTask.this.schedule(false, consumer, Priority.getPriority(LoadDataFromDiskTask.this.getPriorityVolatile() & ~PRIORITY_FLAGS)); - } - }; - Cancellable unloadCancellable = null; - CompoundTag syncComplete = null; - final NewChunkHolder.UnloadTask unloadTask = holder.getUnloadTask(this.type); // can be null if no task exists -- final Completable unloadCompletable = unloadTask == null ? null : unloadTask.completable(); -+ final CallbackCompletable unloadCompletable = unloadTask == null ? null : unloadTask.completable(); - if (unloadCompletable != null) { - unloadCancellable = unloadCompletable.addAsynchronousWaiter(unloadConsumer); - if (unloadCancellable == null) { -@@ -622,7 +623,7 @@ public abstract class GenericDataLoadTask { - this.schedule(scheduledUnload, consumer, initialPriority); - } - -- private void schedule(final boolean scheduledUnload, final BiConsumer consumer, final PrioritisedExecutor.Priority initialPriority) { -+ private void schedule(final boolean scheduledUnload, final BiConsumer consumer, final Priority initialPriority) { - int priority = this.getPriorityVolatile(); - - if ((priority & PRIORITY_EXECUTED) != 0) { -@@ -631,9 +632,9 @@ public abstract class GenericDataLoadTask { - } - - if (!scheduledUnload) { -- this.dataLoadTask = RegionFileIOThread.loadDataAsync( -+ this.dataLoadTask = MoonriseRegionFileIO.loadDataAsync( - this.world, this.chunkX, this.chunkZ, this.type, consumer, -- initialPriority.isHigherPriority(PrioritisedExecutor.Priority.NORMAL), initialPriority -+ initialPriority.isHigherPriority(Priority.NORMAL), initialPriority - ); - } - -@@ -657,10 +658,10 @@ public abstract class GenericDataLoadTask { - - if (scheduledUnload) { - if (this.dataUnloadTask != null) { -- this.dataUnloadTask.setPriority(PrioritisedExecutor.Priority.getPriority(priority & ~PRIORITY_FLAGS)); -+ this.dataUnloadTask.setPriority(Priority.getPriority(priority & ~PRIORITY_FLAGS)); - } - } else { -- RegionFileIOThread.setPriority(this.world, this.chunkX, this.chunkZ, this.type, PrioritisedExecutor.Priority.getPriority(priority & ~PRIORITY_FLAGS)); -+ MoonriseRegionFileIO.setPriority(this.world, this.chunkX, this.chunkZ, this.type, Priority.getPriority(priority & ~PRIORITY_FLAGS)); - } - - ++failures; -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java -new file mode 100644 -index 0000000000000000000000000000000000000000..51c126735ace8fdde89ad97b5cab62f244212db0 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java -@@ -0,0 +1,12 @@ -+package ca.spottedleaf.moonrise.patches.chunk_system.storage; -+ -+import net.minecraft.world.level.chunk.storage.RegionFile; -+import java.io.IOException; -+ -+public interface ChunkSystemChunkBuffer { -+ public boolean moonrise$getWriteOnClose(); -+ -+ public void moonrise$setWriteOnClose(final boolean value); -+ -+ public void moonrise$write(final RegionFile regionFile) throws IOException; -+} -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemRegionFile.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemRegionFile.java -new file mode 100644 -index 0000000000000000000000000000000000000000..3bd1b59250dbab15097a64d515999b278636795a ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemRegionFile.java -@@ -0,0 +1,12 @@ -+package ca.spottedleaf.moonrise.patches.chunk_system.storage; -+ -+import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.world.level.ChunkPos; -+import java.io.IOException; -+ -+public interface ChunkSystemRegionFile { -+ -+ public MoonriseRegionFileIO.RegionDataController.WriteData moonrise$startWrite(final CompoundTag data, final ChunkPos pos) throws IOException; -+ -+} -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/util/ParallelSearchRadiusIteration.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/util/ParallelSearchRadiusIteration.java -index 3a9a564edfdb99e006e4816cb8821bd1e9ecff43..93fd23027c00cef76562098306737272fda1350a 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/util/ParallelSearchRadiusIteration.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/util/ParallelSearchRadiusIteration.java -@@ -1,6 +1,7 @@ - package ca.spottedleaf.moonrise.patches.chunk_system.util; - - import ca.spottedleaf.moonrise.common.util.CoordinateUtils; -+import ca.spottedleaf.moonrise.common.util.MoonriseConstants; - import it.unimi.dsi.fastutil.HashCommon; - import it.unimi.dsi.fastutil.longs.LongArrayList; - import it.unimi.dsi.fastutil.longs.LongIterator; -@@ -13,7 +14,7 @@ public final class ParallelSearchRadiusIteration { - - // expected that this list returns for a given radius, the set of chunks ordered - // by manhattan distance -- private static final long[][] SEARCH_RADIUS_ITERATION_LIST = new long[64+2+1][]; -+ private static final long[][] SEARCH_RADIUS_ITERATION_LIST = new long[MoonriseConstants.MAX_VIEW_DISTANCE+2+1][]; - static { - for (int i = 0; i < SEARCH_RADIUS_ITERATION_LIST.length; ++i) { - // a BFS around -x, -z, +x, +z will give increasing manhatten distance -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/util/stream/ExternalChunkStreamMarker.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/util/stream/ExternalChunkStreamMarker.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7ef3dcca89ed7578c6c0f5565131889110063056 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/util/stream/ExternalChunkStreamMarker.java -@@ -0,0 +1,37 @@ -+package ca.spottedleaf.moonrise.patches.chunk_system.util.stream; -+ -+import java.io.DataInputStream; -+import java.io.FilterInputStream; -+import java.io.InputStream; -+import java.lang.reflect.Field; -+ -+/** -+ * Used to mark chunk data streams that are on external files -+ */ -+public class ExternalChunkStreamMarker extends DataInputStream { -+ -+ private static final Field IN_FIELD; -+ static { -+ Field field; -+ try { -+ field = FilterInputStream.class.getDeclaredField("in"); -+ field.setAccessible(true); -+ } catch (final Throwable throwable) { -+ field = null; -+ } -+ -+ IN_FIELD = field; -+ } -+ -+ private static InputStream getWrapped(final FilterInputStream in) { -+ try { -+ return (InputStream)IN_FIELD.get(in); -+ } catch (final Throwable throwable) { -+ return in; -+ } -+ } -+ -+ public ExternalChunkStreamMarker(final DataInputStream in) { -+ super(getWrapped(in)); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java -index 748ab4d637ce463272bae4fdbab6842a27385126..3abd4ad6379c383c3a31931255292b42d9435694 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java -@@ -1,15 +1,58 @@ - package ca.spottedleaf.moonrise.patches.collisions; - -+import ca.spottedleaf.moonrise.common.util.WorldUtil; -+import ca.spottedleaf.moonrise.patches.chunk_system.world.ChunkSystemEntityGetter; -+import ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState; -+import ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity; -+import ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData; -+import ca.spottedleaf.moonrise.patches.collisions.shape.CollisionDiscreteVoxelShape; -+import ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape; -+import ca.spottedleaf.moonrise.patches.block_counting.BlockCountingChunkSection; -+import it.unimi.dsi.fastutil.doubles.DoubleArrayList; -+import it.unimi.dsi.fastutil.doubles.DoubleList; -+import net.minecraft.core.BlockPos; -+import net.minecraft.core.Direction; -+import net.minecraft.util.Mth; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.item.Item; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.block.Blocks; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.border.WorldBorder; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.ChunkSource; -+import net.minecraft.world.level.chunk.LevelChunkSection; -+import net.minecraft.world.level.chunk.PalettedContainer; -+import net.minecraft.world.level.chunk.status.ChunkStatus; -+import net.minecraft.world.level.material.FluidState; -+import net.minecraft.world.phys.AABB; -+import net.minecraft.world.phys.Vec3; -+import net.minecraft.world.phys.shapes.ArrayVoxelShape; -+import net.minecraft.world.phys.shapes.BitSetDiscreteVoxelShape; -+import net.minecraft.world.phys.shapes.BooleanOp; -+import net.minecraft.world.phys.shapes.CollisionContext; -+import net.minecraft.world.phys.shapes.DiscreteVoxelShape; -+import net.minecraft.world.phys.shapes.EntityCollisionContext; -+import net.minecraft.world.phys.shapes.OffsetDoubleList; -+import net.minecraft.world.phys.shapes.Shapes; -+import net.minecraft.world.phys.shapes.SliceShape; -+import net.minecraft.world.phys.shapes.VoxelShape; -+import java.util.Arrays; -+import java.util.List; -+import java.util.Objects; -+import java.util.function.BiPredicate; -+import java.util.function.Predicate; -+ - public final class CollisionUtil { - - public static final double COLLISION_EPSILON = 1.0E-7; -- public static final it.unimi.dsi.fastutil.doubles.DoubleArrayList ZERO_ONE = it.unimi.dsi.fastutil.doubles.DoubleArrayList.wrap(new double[] { 0.0, 1.0 }); -+ public static final DoubleArrayList ZERO_ONE = DoubleArrayList.wrap(new double[] { 0.0, 1.0 }); - - public static boolean isSpecialCollidingBlock(final net.minecraft.world.level.block.state.BlockBehaviour.BlockStateBase block) { -- return block.hasLargeCollisionShape() || block.getBlock() == net.minecraft.world.level.block.Blocks.MOVING_PISTON; -+ return block.hasLargeCollisionShape() || block.getBlock() == Blocks.MOVING_PISTON; - } - -- public static boolean isEmpty(final net.minecraft.world.phys.AABB aabb) { -+ public static boolean isEmpty(final AABB aabb) { - return (aabb.maxX - aabb.minX) < COLLISION_EPSILON || (aabb.maxY - aabb.minY) < COLLISION_EPSILON || (aabb.maxZ - aabb.minZ) < COLLISION_EPSILON; - } - -@@ -18,11 +61,11 @@ public final class CollisionUtil { - return (maxX - minX) < COLLISION_EPSILON || (maxY - minY) < COLLISION_EPSILON || (maxZ - minZ) < COLLISION_EPSILON; - } - -- public static net.minecraft.world.phys.AABB getBoxForChunk(final int chunkX, final int chunkZ) { -+ public static AABB getBoxForChunk(final int chunkX, final int chunkZ) { - double x = (double)(chunkX << 4); - double z = (double)(chunkZ << 4); - // use a bounding box bigger than the chunk to prevent entities from entering it on move -- return new net.minecraft.world.phys.AABB(x - 3*COLLISION_EPSILON, Double.NEGATIVE_INFINITY, z - 3*COLLISION_EPSILON, -+ return new AABB(x - 3*COLLISION_EPSILON, Double.NEGATIVE_INFINITY, z - 3*COLLISION_EPSILON, - x + (16.0 + 3*COLLISION_EPSILON), Double.POSITIVE_INFINITY, z + (16.0 + 3*COLLISION_EPSILON)); - } - -@@ -43,21 +86,21 @@ public final class CollisionUtil { - (minZ1 - maxZ2) < -COLLISION_EPSILON && (maxZ1 - minZ2) > COLLISION_EPSILON; - } - -- public static boolean voxelShapeIntersect(final net.minecraft.world.phys.AABB box, final double minX, final double minY, final double minZ, -+ public static boolean voxelShapeIntersect(final AABB box, final double minX, final double minY, final double minZ, - final double maxX, final double maxY, final double maxZ) { - return (box.minX - maxX) < -COLLISION_EPSILON && (box.maxX - minX) > COLLISION_EPSILON && - (box.minY - maxY) < -COLLISION_EPSILON && (box.maxY - minY) > COLLISION_EPSILON && - (box.minZ - maxZ) < -COLLISION_EPSILON && (box.maxZ - minZ) > COLLISION_EPSILON; - } - -- public static boolean voxelShapeIntersect(final net.minecraft.world.phys.AABB box1, final net.minecraft.world.phys.AABB box2) { -+ public static boolean voxelShapeIntersect(final AABB box1, final AABB box2) { - return (box1.minX - box2.maxX) < -COLLISION_EPSILON && (box1.maxX - box2.minX) > COLLISION_EPSILON && - (box1.minY - box2.maxY) < -COLLISION_EPSILON && (box1.maxY - box2.minY) > COLLISION_EPSILON && - (box1.minZ - box2.maxZ) < -COLLISION_EPSILON && (box1.maxZ - box2.minZ) > COLLISION_EPSILON; - } - - // assume !isEmpty(target) && abs(source_move) >= COLLISION_EPSILON -- public static double collideX(final net.minecraft.world.phys.AABB target, final net.minecraft.world.phys.AABB source, final double source_move) { -+ public static double collideX(final AABB target, final AABB source, final double source_move) { - if ((source.minY - target.maxY) < -COLLISION_EPSILON && (source.maxY - target.minY) > COLLISION_EPSILON && - (source.minZ - target.maxZ) < -COLLISION_EPSILON && (source.maxZ - target.minZ) > COLLISION_EPSILON) { - if (source_move >= 0.0) { -@@ -78,7 +121,7 @@ public final class CollisionUtil { - } - - // assume !isEmpty(target) && abs(source_move) >= COLLISION_EPSILON -- public static double collideY(final net.minecraft.world.phys.AABB target, final net.minecraft.world.phys.AABB source, final double source_move) { -+ public static double collideY(final AABB target, final AABB source, final double source_move) { - if ((source.minX - target.maxX) < -COLLISION_EPSILON && (source.maxX - target.minX) > COLLISION_EPSILON && - (source.minZ - target.maxZ) < -COLLISION_EPSILON && (source.maxZ - target.minZ) > COLLISION_EPSILON) { - if (source_move >= 0.0) { -@@ -99,7 +142,7 @@ public final class CollisionUtil { - } - - // assume !isEmpty(target) && abs(source_move) >= COLLISION_EPSILON -- public static double collideZ(final net.minecraft.world.phys.AABB target, final net.minecraft.world.phys.AABB source, final double source_move) { -+ public static double collideZ(final AABB target, final AABB source, final double source_move) { - if ((source.minX - target.maxX) < -COLLISION_EPSILON && (source.maxX - target.minX) > COLLISION_EPSILON && - (source.minY - target.maxY) < -COLLISION_EPSILON && (source.maxY - target.minY) > COLLISION_EPSILON) { - if (source_move >= 0.0) { -@@ -121,7 +164,8 @@ public final class CollisionUtil { - - // startIndex and endIndex inclusive - // assumes indices are in range of array -- private static int findFloor(final double[] values, final double value, int startIndex, int endIndex) { -+ public static int findFloor(final double[] values, final double value, int startIndex, int endIndex) { -+ Objects.checkFromToIndex(startIndex, endIndex + 1, values.length); - do { - final int middle = (startIndex + endIndex) >>> 1; - final double middleVal = values[middle]; -@@ -136,7 +180,217 @@ public final class CollisionUtil { - return startIndex - 1; - } - -- public static boolean voxelShapeIntersectNoEmpty(final net.minecraft.world.phys.shapes.VoxelShape voxel, final net.minecraft.world.phys.AABB aabb) { -+ private static VoxelShape sliceShapeVanilla(final VoxelShape src, final Direction.Axis axis, -+ final int index) { -+ return new SliceShape(src, axis, index); -+ } -+ -+ private static DoubleList offsetList(final double[] src, final double by) { -+ final DoubleArrayList wrap = DoubleArrayList.wrap(src); -+ if (by == 0.0) { -+ return wrap; -+ } -+ return new OffsetDoubleList(wrap, by); -+ } -+ -+ private static VoxelShape sliceShapeOptimised(final VoxelShape src, final Direction.Axis axis, -+ final int index) { -+ // assume index in range -+ final double off_x = ((CollisionVoxelShape)src).moonrise$offsetX(); -+ final double off_y = ((CollisionVoxelShape)src).moonrise$offsetY(); -+ final double off_z = ((CollisionVoxelShape)src).moonrise$offsetZ(); -+ -+ final double[] coords_x = ((CollisionVoxelShape)src).moonrise$rootCoordinatesX(); -+ final double[] coords_y = ((CollisionVoxelShape)src).moonrise$rootCoordinatesY(); -+ final double[] coords_z = ((CollisionVoxelShape)src).moonrise$rootCoordinatesZ(); -+ -+ final CachedShapeData cached_shape_data = ((CollisionVoxelShape)src).moonrise$getCachedVoxelData(); -+ -+ // note: size = coords.length - 1 -+ final int size_x = cached_shape_data.sizeX(); -+ final int size_y = cached_shape_data.sizeY(); -+ final int size_z = cached_shape_data.sizeZ(); -+ -+ final long[] bitset = cached_shape_data.voxelSet(); -+ -+ final DoubleList list_x; -+ final DoubleList list_y; -+ final DoubleList list_z; -+ final int shape_sx; -+ final int shape_ex; -+ final int shape_sy; -+ final int shape_ey; -+ final int shape_sz; -+ final int shape_ez; -+ -+ switch (axis) { -+ case X: { -+ // validate index -+ if (index < 0 || index >= size_x) { -+ return Shapes.empty(); -+ } -+ -+ // test if input is already "sliced" -+ if (coords_x.length == 2 && (coords_x[0] + off_x) == 0.0 && (coords_x[1] + off_x) == 1.0) { -+ return src; -+ } -+ -+ // test if result would be full box -+ if (coords_y.length == 2 && coords_z.length == 2 && -+ (coords_y[0] + off_y) == 0.0 && (coords_y[1] + off_y) == 1.0 && -+ (coords_z[0] + off_z) == 0.0 && (coords_z[1] + off_z) == 1.0) { -+ // note: size_y == size_z == 1 -+ final int bitIdx = 0 + 0*size_z + index*(size_z*size_y); -+ return (bitset[bitIdx >>> 6] & (1L << bitIdx)) == 0L ? Shapes.empty() : Shapes.block(); -+ } -+ -+ list_x = ZERO_ONE; -+ list_y = offsetList(coords_y, off_y); -+ list_z = offsetList(coords_z, off_z); -+ shape_sx = index; -+ shape_ex = index + 1; -+ shape_sy = 0; -+ shape_ey = size_y; -+ shape_sz = 0; -+ shape_ez = size_z; -+ -+ break; -+ } -+ case Y: { -+ // validate index -+ if (index < 0 || index >= size_y) { -+ return Shapes.empty(); -+ } -+ -+ // test if input is already "sliced" -+ if (coords_y.length == 2 && (coords_y[0] + off_y) == 0.0 && (coords_y[1] + off_y) == 1.0) { -+ return src; -+ } -+ -+ // test if result would be full box -+ if (coords_x.length == 2 && coords_z.length == 2 && -+ (coords_x[0] + off_x) == 0.0 && (coords_x[1] + off_x) == 1.0 && -+ (coords_z[0] + off_z) == 0.0 && (coords_z[1] + off_z) == 1.0) { -+ // note: size_x == size_z == 1 -+ final int bitIdx = 0 + index*size_z + 0*(size_z*size_y); -+ return (bitset[bitIdx >>> 6] & (1L << bitIdx)) == 0L ? Shapes.empty() : Shapes.block(); -+ } -+ -+ list_x = offsetList(coords_x, off_x); -+ list_y = ZERO_ONE; -+ list_z = offsetList(coords_z, off_z); -+ shape_sx = 0; -+ shape_ex = size_x; -+ shape_sy = index; -+ shape_ey = index + 1; -+ shape_sz = 0; -+ shape_ez = size_z; -+ -+ break; -+ } -+ case Z: { -+ // validate index -+ if (index < 0 || index >= size_z) { -+ return Shapes.empty(); -+ } -+ -+ // test if input is already "sliced" -+ if (coords_z.length == 2 && (coords_z[0] + off_z) == 0.0 && (coords_z[1] + off_z) == 1.0) { -+ return src; -+ } -+ -+ // test if result would be full box -+ if (coords_x.length == 2 && coords_y.length == 2 && -+ (coords_x[0] + off_x) == 0.0 && (coords_x[1] + off_x) == 1.0 && -+ (coords_y[0] + off_y) == 0.0 && (coords_y[1] + off_y) == 1.0) { -+ // note: size_x == size_y == 1 -+ final int bitIdx = index + 0*size_z + 0*(size_z*size_y); -+ return (bitset[bitIdx >>> 6] & (1L << bitIdx)) == 0L ? Shapes.empty() : Shapes.block(); -+ } -+ -+ list_x = offsetList(coords_x, off_x); -+ list_y = offsetList(coords_y, off_y); -+ list_z = ZERO_ONE; -+ shape_sx = 0; -+ shape_ex = size_x; -+ shape_sy = 0; -+ shape_ey = size_y; -+ shape_sz = index; -+ shape_ez = index + 1; -+ -+ break; -+ } -+ default: { -+ throw new IllegalStateException("Unknown axis: " + axis); -+ } -+ } -+ -+ final int local_len_x = shape_ex - shape_sx; -+ final int local_len_y = shape_ey - shape_sy; -+ final int local_len_z = shape_ez - shape_sz; -+ -+ final BitSetDiscreteVoxelShape shape = new BitSetDiscreteVoxelShape(local_len_x, local_len_y, local_len_z); -+ -+ final int bitset_mul_x = size_z*size_y; -+ final int idx_off = shape_sz + shape_sy*size_z + shape_sx*bitset_mul_x; -+ final int shape_mul_x = local_len_y*local_len_z; -+ for (int x = 0; x < local_len_x; ++x) { -+ boolean setX = false; -+ for (int y = 0; y < local_len_y; ++y) { -+ boolean setY = false; -+ for (int z = 0; z < local_len_z; ++z) { -+ final int unslicedIdx = idx_off + z + y*size_z + x*bitset_mul_x; -+ if ((bitset[unslicedIdx >>> 6] & (1L << unslicedIdx)) == 0L) { -+ continue; -+ } -+ -+ setY = true; -+ setX = true; -+ shape.zMin = Math.min(shape.zMin, z); -+ shape.zMax = Math.max(shape.zMax, z + 1); -+ -+ shape.storage.set( -+ z + y*local_len_z + x*shape_mul_x -+ ); -+ } -+ -+ if (setY) { -+ shape.yMin = Math.min(shape.yMin, y); -+ shape.yMax = Math.max(shape.yMax, y + 1); -+ } -+ } -+ if (setX) { -+ shape.xMin = Math.min(shape.xMin, x); -+ shape.xMax = Math.max(shape.xMax, x + 1); -+ } -+ } -+ -+ return shape.isEmpty() ? Shapes.empty() : new ArrayVoxelShape( -+ shape, list_x, list_y, list_z -+ ); -+ } -+ -+ private static final boolean DEBUG_SLICE_SHAPE = false; -+ -+ public static VoxelShape sliceShape(final VoxelShape src, final Direction.Axis axis, -+ final int index) { -+ final VoxelShape ret = sliceShapeOptimised(src, axis, index); -+ if (DEBUG_SLICE_SHAPE) { -+ final VoxelShape vanilla = sliceShapeVanilla(src, axis, index); -+ if (!equals(ret, vanilla)) { -+ // special case: SliceShape is not empty when it should be! -+ if (areAnyFull(ret.shape) || areAnyFull(vanilla.shape)) { -+ equals(ret, vanilla); -+ sliceShapeOptimised(src, axis, index); -+ throw new IllegalStateException("Slice shape mismatch"); -+ } -+ } -+ } -+ -+ return ret; -+ } -+ -+ public static boolean voxelShapeIntersectNoEmpty(final VoxelShape voxel, final AABB aabb) { - if (voxel.isEmpty()) { - return false; - } -@@ -144,15 +398,15 @@ public final class CollisionUtil { - // note: this function assumes that for any i in coords that coord[i + 1] - coord[i] > COLLISION_EPSILON is true - - // offsets that should be applied to coords -- final double off_x = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$offsetX(); -- final double off_y = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$offsetY(); -- final double off_z = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$offsetZ(); -+ final double off_x = ((CollisionVoxelShape)voxel).moonrise$offsetX(); -+ final double off_y = ((CollisionVoxelShape)voxel).moonrise$offsetY(); -+ final double off_z = ((CollisionVoxelShape)voxel).moonrise$offsetZ(); - -- final double[] coords_x = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$rootCoordinatesX(); -- final double[] coords_y = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$rootCoordinatesY(); -- final double[] coords_z = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$rootCoordinatesZ(); -+ final double[] coords_x = ((CollisionVoxelShape)voxel).moonrise$rootCoordinatesX(); -+ final double[] coords_y = ((CollisionVoxelShape)voxel).moonrise$rootCoordinatesY(); -+ final double[] coords_z = ((CollisionVoxelShape)voxel).moonrise$rootCoordinatesZ(); - -- final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData cached_shape_data = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$getCachedVoxelData(); -+ final CachedShapeData cached_shape_data = ((CollisionVoxelShape)voxel).moonrise$getCachedVoxelData(); - - // note: size = coords.length - 1 - final int size_x = cached_shape_data.sizeX(); -@@ -246,23 +500,23 @@ public final class CollisionUtil { - } - - // assume !target.isEmpty() && abs(source_move) >= COLLISION_EPSILON -- public static double collideX(final net.minecraft.world.phys.shapes.VoxelShape target, final net.minecraft.world.phys.AABB source, final double source_move) { -- final net.minecraft.world.phys.AABB single_aabb = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$getSingleAABBRepresentation(); -+ public static double collideX(final VoxelShape target, final AABB source, final double source_move) { -+ final AABB single_aabb = ((CollisionVoxelShape)target).moonrise$getSingleAABBRepresentation(); - if (single_aabb != null) { - return collideX(single_aabb, source, source_move); - } - // note: this function assumes that for any i in coords that coord[i + 1] - coord[i] > COLLISION_EPSILON is true - - // offsets that should be applied to coords -- final double off_x = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$offsetX(); -- final double off_y = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$offsetY(); -- final double off_z = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$offsetZ(); -+ final double off_x = ((CollisionVoxelShape)target).moonrise$offsetX(); -+ final double off_y = ((CollisionVoxelShape)target).moonrise$offsetY(); -+ final double off_z = ((CollisionVoxelShape)target).moonrise$offsetZ(); - -- final double[] coords_x = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$rootCoordinatesX(); -- final double[] coords_y = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$rootCoordinatesY(); -- final double[] coords_z = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$rootCoordinatesZ(); -+ final double[] coords_x = ((CollisionVoxelShape)target).moonrise$rootCoordinatesX(); -+ final double[] coords_y = ((CollisionVoxelShape)target).moonrise$rootCoordinatesY(); -+ final double[] coords_z = ((CollisionVoxelShape)target).moonrise$rootCoordinatesZ(); - -- final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData cached_shape_data = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$getCachedVoxelData(); -+ final CachedShapeData cached_shape_data = ((CollisionVoxelShape)target).moonrise$getCachedVoxelData(); - - // note: size = coords.length - 1 - final int size_x = cached_shape_data.sizeX(); -@@ -404,23 +658,23 @@ public final class CollisionUtil { - } - } - -- public static double collideY(final net.minecraft.world.phys.shapes.VoxelShape target, final net.minecraft.world.phys.AABB source, final double source_move) { -- final net.minecraft.world.phys.AABB single_aabb = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$getSingleAABBRepresentation(); -+ public static double collideY(final VoxelShape target, final AABB source, final double source_move) { -+ final AABB single_aabb = ((CollisionVoxelShape)target).moonrise$getSingleAABBRepresentation(); - if (single_aabb != null) { - return collideY(single_aabb, source, source_move); - } - // note: this function assumes that for any i in coords that coord[i + 1] - coord[i] > COLLISION_EPSILON is true - - // offsets that should be applied to coords -- final double off_x = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$offsetX(); -- final double off_y = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$offsetY(); -- final double off_z = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$offsetZ(); -+ final double off_x = ((CollisionVoxelShape)target).moonrise$offsetX(); -+ final double off_y = ((CollisionVoxelShape)target).moonrise$offsetY(); -+ final double off_z = ((CollisionVoxelShape)target).moonrise$offsetZ(); - -- final double[] coords_x = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$rootCoordinatesX(); -- final double[] coords_y = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$rootCoordinatesY(); -- final double[] coords_z = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$rootCoordinatesZ(); -+ final double[] coords_x = ((CollisionVoxelShape)target).moonrise$rootCoordinatesX(); -+ final double[] coords_y = ((CollisionVoxelShape)target).moonrise$rootCoordinatesY(); -+ final double[] coords_z = ((CollisionVoxelShape)target).moonrise$rootCoordinatesZ(); - -- final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData cached_shape_data = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$getCachedVoxelData(); -+ final CachedShapeData cached_shape_data = ((CollisionVoxelShape)target).moonrise$getCachedVoxelData(); - - // note: size = coords.length - 1 - final int size_x = cached_shape_data.sizeX(); -@@ -562,23 +816,23 @@ public final class CollisionUtil { - } - } - -- public static double collideZ(final net.minecraft.world.phys.shapes.VoxelShape target, final net.minecraft.world.phys.AABB source, final double source_move) { -- final net.minecraft.world.phys.AABB single_aabb = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$getSingleAABBRepresentation(); -+ public static double collideZ(final VoxelShape target, final AABB source, final double source_move) { -+ final AABB single_aabb = ((CollisionVoxelShape)target).moonrise$getSingleAABBRepresentation(); - if (single_aabb != null) { - return collideZ(single_aabb, source, source_move); - } - // note: this function assumes that for any i in coords that coord[i + 1] - coord[i] > COLLISION_EPSILON is true - - // offsets that should be applied to coords -- final double off_x = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$offsetX(); -- final double off_y = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$offsetY(); -- final double off_z = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$offsetZ(); -+ final double off_x = ((CollisionVoxelShape)target).moonrise$offsetX(); -+ final double off_y = ((CollisionVoxelShape)target).moonrise$offsetY(); -+ final double off_z = ((CollisionVoxelShape)target).moonrise$offsetZ(); - -- final double[] coords_x = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$rootCoordinatesX(); -- final double[] coords_y = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$rootCoordinatesY(); -- final double[] coords_z = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$rootCoordinatesZ(); -+ final double[] coords_x = ((CollisionVoxelShape)target).moonrise$rootCoordinatesX(); -+ final double[] coords_y = ((CollisionVoxelShape)target).moonrise$rootCoordinatesY(); -+ final double[] coords_z = ((CollisionVoxelShape)target).moonrise$rootCoordinatesZ(); - -- final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData cached_shape_data = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$getCachedVoxelData(); -+ final CachedShapeData cached_shape_data = ((CollisionVoxelShape)target).moonrise$getCachedVoxelData(); - - // note: size = coords.length - 1 - final int size_x = cached_shape_data.sizeX(); -@@ -721,13 +975,13 @@ public final class CollisionUtil { - } - - // does not use epsilon -- public static boolean strictlyContains(final net.minecraft.world.phys.shapes.VoxelShape voxel, final net.minecraft.world.phys.Vec3 point) { -+ public static boolean strictlyContains(final VoxelShape voxel, final Vec3 point) { - return strictlyContains(voxel, point.x, point.y, point.z); - } - - // does not use epsilon -- public static boolean strictlyContains(final net.minecraft.world.phys.shapes.VoxelShape voxel, double x, double y, double z) { -- final net.minecraft.world.phys.AABB single_aabb = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$getSingleAABBRepresentation(); -+ public static boolean strictlyContains(final VoxelShape voxel, double x, double y, double z) { -+ final AABB single_aabb = ((CollisionVoxelShape)voxel).moonrise$getSingleAABBRepresentation(); - if (single_aabb != null) { - return single_aabb.contains(x, y, z); - } -@@ -738,15 +992,15 @@ public final class CollisionUtil { - } - - // offset input -- x -= ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$offsetX(); -- y -= ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$offsetY(); -- z -= ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$offsetZ(); -+ x -= ((CollisionVoxelShape)voxel).moonrise$offsetX(); -+ y -= ((CollisionVoxelShape)voxel).moonrise$offsetY(); -+ z -= ((CollisionVoxelShape)voxel).moonrise$offsetZ(); - -- final double[] coords_x = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$rootCoordinatesX(); -- final double[] coords_y = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$rootCoordinatesY(); -- final double[] coords_z = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$rootCoordinatesZ(); -+ final double[] coords_x = ((CollisionVoxelShape)voxel).moonrise$rootCoordinatesX(); -+ final double[] coords_y = ((CollisionVoxelShape)voxel).moonrise$rootCoordinatesY(); -+ final double[] coords_z = ((CollisionVoxelShape)voxel).moonrise$rootCoordinatesZ(); - -- final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData cached_shape_data = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$getCachedVoxelData(); -+ final CachedShapeData cached_shape_data = ((CollisionVoxelShape)voxel).moonrise$getCachedVoxelData(); - - // note: size = coords.length - 1 - final int size_x = cached_shape_data.sizeX(); -@@ -788,10 +1042,10 @@ public final class CollisionUtil { - return ((ft ? 1 : 0) << 1) | ((tf ? 1 : 0) << 2) | ((tt ? 1 : 0) << 3); - } - -- private static net.minecraft.world.phys.shapes.BitSetDiscreteVoxelShape merge(final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeDataFirst, final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeDataSecond, -- final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedX, final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedY, -- final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedZ, -- final int booleanOp) { -+ private static BitSetDiscreteVoxelShape merge(final CachedShapeData shapeDataFirst, final CachedShapeData shapeDataSecond, -+ final MergedVoxelCoordinateList mergedX, final MergedVoxelCoordinateList mergedY, -+ final MergedVoxelCoordinateList mergedZ, -+ final int booleanOp) { - final int sizeX = mergedX.voxels; - final int sizeY = mergedY.voxels; - final int sizeZ = mergedZ.voxels; -@@ -806,7 +1060,7 @@ public final class CollisionUtil { - final int s2Mul2 = s2Mul1 * shapeDataSecond.sizeY(); - - // note: indices may contain -1, but nothing > size -- final net.minecraft.world.phys.shapes.BitSetDiscreteVoxelShape ret = new net.minecraft.world.phys.shapes.BitSetDiscreteVoxelShape(sizeX, sizeY, sizeZ); -+ final BitSetDiscreteVoxelShape ret = new BitSetDiscreteVoxelShape(sizeX, sizeY, sizeZ); - - boolean empty = true; - -@@ -823,10 +1077,11 @@ public final class CollisionUtil { - final int s1z = mergedZ.firstIndices[idxZ]; - final int s2z = mergedZ.secondIndices[idxZ]; - -- int idx; -+ int idx1; -+ int idx2; - -- final int isS1Full = (s1x | s1y | s1z) < 0 ? 0 : (int)((s1Voxels[(idx = s1z + s1y*s1Mul1 + s1x*s1Mul2) >>> 6] >>> idx) & 1L); -- final int isS2Full = (s2x | s2y | s2z) < 0 ? 0 : (int)((s2Voxels[(idx = s2z + s2y*s2Mul1 + s2x*s2Mul2) >>> 6] >>> idx) & 1L); -+ final int isS1Full = (s1x | s1y | s1z) < 0 ? 0 : (int)((s1Voxels[(idx1 = s1z + s1y*s1Mul1 + s1x*s1Mul2) >>> 6] >>> idx1) & 1L); -+ final int isS2Full = (s2x | s2y | s2z) < 0 ? 0 : (int)((s2Voxels[(idx2 = s2z + s2y*s2Mul1 + s2x*s2Mul2) >>> 6] >>> idx2) & 1L); - - // idx ff -> 0 - // idx ft -> 1 -@@ -861,9 +1116,9 @@ public final class CollisionUtil { - return empty ? null : ret; - } - -- private static boolean isMergeEmpty(final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeDataFirst, final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeDataSecond, -- final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedX, final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedY, -- final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedZ, -+ private static boolean isMergeEmpty(final CachedShapeData shapeDataFirst, final CachedShapeData shapeDataSecond, -+ final MergedVoxelCoordinateList mergedX, final MergedVoxelCoordinateList mergedY, -+ final MergedVoxelCoordinateList mergedZ, - final int booleanOp) { - final int sizeX = mergedX.voxels; - final int sizeY = mergedY.voxels; -@@ -889,10 +1144,11 @@ public final class CollisionUtil { - final int s1z = mergedZ.firstIndices[idxZ]; - final int s2z = mergedZ.secondIndices[idxZ]; - -- int idx; -+ int idx1; -+ int idx2; - -- final int isS1Full = (s1x | s1y | s1z) < 0 ? 0 : (int)((s1Voxels[(idx = s1z + s1y*s1Mul1 + s1x*s1Mul2) >>> 6] >>> idx) & 1L); -- final int isS2Full = (s2x | s2y | s2z) < 0 ? 0 : (int)((s2Voxels[(idx = s2z + s2y*s2Mul1 + s2x*s2Mul2) >>> 6] >>> idx) & 1L); -+ final int isS1Full = (s1x | s1y | s1z) < 0 ? 0 : (int)((s1Voxels[(idx1 = s1z + s1y*s1Mul1 + s1x*s1Mul2) >>> 6] >>> idx1) & 1L); -+ final int isS2Full = (s2x | s2y | s2z) < 0 ? 0 : (int)((s2Voxels[(idx2 = s2z + s2y*s2Mul1 + s2x*s2Mul2) >>> 6] >>> idx2) & 1L); - - // idx ff -> 0 - // idx ft -> 1 -@@ -911,11 +1167,11 @@ public final class CollisionUtil { - return true; - } - -- public static net.minecraft.world.phys.shapes.VoxelShape joinOptimized(final net.minecraft.world.phys.shapes.VoxelShape first, final net.minecraft.world.phys.shapes.VoxelShape second, final net.minecraft.world.phys.shapes.BooleanOp operator) { -+ public static VoxelShape joinOptimized(final VoxelShape first, final VoxelShape second, final BooleanOp operator) { - return joinUnoptimized(first, second, operator).optimize(); - } - -- public static net.minecraft.world.phys.shapes.VoxelShape joinUnoptimized(final net.minecraft.world.phys.shapes.VoxelShape first, final net.minecraft.world.phys.shapes.VoxelShape second, final net.minecraft.world.phys.shapes.BooleanOp operator) { -+ public static VoxelShape joinUnoptimized(final VoxelShape first, final VoxelShape second, final BooleanOp operator) { - final boolean ff = operator.apply(false, false); - if (ff) { - // technically, should be an infinite box but that's clearly an error -@@ -925,23 +1181,23 @@ public final class CollisionUtil { - final boolean tt = operator.apply(true, true); - - if (first == second) { -- return tt ? first : net.minecraft.world.phys.shapes.Shapes.empty(); -+ return tt ? first : Shapes.empty(); - } - - final boolean ft = operator.apply(false, true); - final boolean tf = operator.apply(true, false); - - if (first.isEmpty()) { -- return ft ? second : net.minecraft.world.phys.shapes.Shapes.empty(); -+ return ft ? second : Shapes.empty(); - } - if (second.isEmpty()) { -- return tf ? first : net.minecraft.world.phys.shapes.Shapes.empty(); -+ return tf ? first : Shapes.empty(); - } - - if (!tt) { - // try to check for no intersection, since tt = false -- final net.minecraft.world.phys.AABB aabbF = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$getSingleAABBRepresentation(); -- final net.minecraft.world.phys.AABB aabbS = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$getSingleAABBRepresentation(); -+ final AABB aabbF = ((CollisionVoxelShape)first).moonrise$getSingleAABBRepresentation(); -+ final AABB aabbS = ((CollisionVoxelShape)second).moonrise$getSingleAABBRepresentation(); - - final boolean intersect; - -@@ -962,7 +1218,7 @@ public final class CollisionUtil { - - if (!intersect) { - if (!tf & !ft) { -- return net.minecraft.world.phys.shapes.Shapes.empty(); -+ return Shapes.empty(); - } - if (!tf | !ft) { - return tf ? first : second; -@@ -970,50 +1226,50 @@ public final class CollisionUtil { - } - } - -- final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedX = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.merge( -- ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$rootCoordinatesX(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$offsetX(), -- ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$rootCoordinatesX(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$offsetX(), -+ final MergedVoxelCoordinateList mergedX = MergedVoxelCoordinateList.merge( -+ ((CollisionVoxelShape)first).moonrise$rootCoordinatesX(), ((CollisionVoxelShape)first).moonrise$offsetX(), -+ ((CollisionVoxelShape)second).moonrise$rootCoordinatesX(), ((CollisionVoxelShape)second).moonrise$offsetX(), - ft, tf - ); -- if (mergedX == ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.EMPTY) { -- return net.minecraft.world.phys.shapes.Shapes.empty(); -+ if (mergedX == null) { -+ return Shapes.empty(); - } -- final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedY = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.merge( -- ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$rootCoordinatesY(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$offsetY(), -- ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$rootCoordinatesY(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$offsetY(), -+ final MergedVoxelCoordinateList mergedY = MergedVoxelCoordinateList.merge( -+ ((CollisionVoxelShape)first).moonrise$rootCoordinatesY(), ((CollisionVoxelShape)first).moonrise$offsetY(), -+ ((CollisionVoxelShape)second).moonrise$rootCoordinatesY(), ((CollisionVoxelShape)second).moonrise$offsetY(), - ft, tf - ); -- if (mergedY == ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.EMPTY) { -- return net.minecraft.world.phys.shapes.Shapes.empty(); -+ if (mergedY == null) { -+ return Shapes.empty(); - } -- final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedZ = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.merge( -- ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$rootCoordinatesZ(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$offsetZ(), -- ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$rootCoordinatesZ(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$offsetZ(), -+ final MergedVoxelCoordinateList mergedZ = MergedVoxelCoordinateList.merge( -+ ((CollisionVoxelShape)first).moonrise$rootCoordinatesZ(), ((CollisionVoxelShape)first).moonrise$offsetZ(), -+ ((CollisionVoxelShape)second).moonrise$rootCoordinatesZ(), ((CollisionVoxelShape)second).moonrise$offsetZ(), - ft, tf - ); -- if (mergedZ == ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.EMPTY) { -- return net.minecraft.world.phys.shapes.Shapes.empty(); -+ if (mergedZ == null) { -+ return Shapes.empty(); - } - -- final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeDataFirst = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$getCachedVoxelData(); -- final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeDataSecond = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$getCachedVoxelData(); -+ final CachedShapeData shapeDataFirst = ((CollisionVoxelShape)first).moonrise$getCachedVoxelData(); -+ final CachedShapeData shapeDataSecond = ((CollisionVoxelShape)second).moonrise$getCachedVoxelData(); - -- final net.minecraft.world.phys.shapes.BitSetDiscreteVoxelShape mergedShape = merge( -+ final BitSetDiscreteVoxelShape mergedShape = merge( - shapeDataFirst, shapeDataSecond, - mergedX, mergedY, mergedZ, - makeBitset(ft, tf, tt) - ); - - if (mergedShape == null) { -- return net.minecraft.world.phys.shapes.Shapes.empty(); -+ return Shapes.empty(); - } - -- return new net.minecraft.world.phys.shapes.ArrayVoxelShape( -+ return new ArrayVoxelShape( - mergedShape, mergedX.wrapCoords(), mergedY.wrapCoords(), mergedZ.wrapCoords() - ); - } - -- public static boolean isJoinNonEmpty(final net.minecraft.world.phys.shapes.VoxelShape first, final net.minecraft.world.phys.shapes.VoxelShape second, final net.minecraft.world.phys.shapes.BooleanOp operator) { -+ public static boolean isJoinNonEmpty(final VoxelShape first, final VoxelShape second, final BooleanOp operator) { - final boolean ff = operator.apply(false, false); - if (ff) { - // technically, should be an infinite box but that's clearly an error -@@ -1035,8 +1291,8 @@ public final class CollisionUtil { - final boolean tf = operator.apply(true, false); - - // try to check intersection -- final net.minecraft.world.phys.AABB aabbF = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$getSingleAABBRepresentation(); -- final net.minecraft.world.phys.AABB aabbS = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$getSingleAABBRepresentation(); -+ final AABB aabbF = ((CollisionVoxelShape)first).moonrise$getSingleAABBRepresentation(); -+ final AABB aabbS = ((CollisionVoxelShape)second).moonrise$getSingleAABBRepresentation(); - - final boolean intersect; - -@@ -1068,33 +1324,33 @@ public final class CollisionUtil { - } - } - -- final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedX = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.merge( -- ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$rootCoordinatesX(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$offsetX(), -- ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$rootCoordinatesX(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$offsetX(), -+ final MergedVoxelCoordinateList mergedX = MergedVoxelCoordinateList.merge( -+ ((CollisionVoxelShape)first).moonrise$rootCoordinatesX(), ((CollisionVoxelShape)first).moonrise$offsetX(), -+ ((CollisionVoxelShape)second).moonrise$rootCoordinatesX(), ((CollisionVoxelShape)second).moonrise$offsetX(), - ft, tf - ); -- if (mergedX == ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.EMPTY) { -+ if (mergedX == null) { - return false; - } -- final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedY = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.merge( -- ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$rootCoordinatesY(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$offsetY(), -- ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$rootCoordinatesY(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$offsetY(), -+ final MergedVoxelCoordinateList mergedY = MergedVoxelCoordinateList.merge( -+ ((CollisionVoxelShape)first).moonrise$rootCoordinatesY(), ((CollisionVoxelShape)first).moonrise$offsetY(), -+ ((CollisionVoxelShape)second).moonrise$rootCoordinatesY(), ((CollisionVoxelShape)second).moonrise$offsetY(), - ft, tf - ); -- if (mergedY == ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.EMPTY) { -+ if (mergedY == null) { - return false; - } -- final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedZ = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.merge( -- ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$rootCoordinatesZ(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$offsetZ(), -- ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$rootCoordinatesZ(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$offsetZ(), -+ final MergedVoxelCoordinateList mergedZ = MergedVoxelCoordinateList.merge( -+ ((CollisionVoxelShape)first).moonrise$rootCoordinatesZ(), ((CollisionVoxelShape)first).moonrise$offsetZ(), -+ ((CollisionVoxelShape)second).moonrise$rootCoordinatesZ(), ((CollisionVoxelShape)second).moonrise$offsetZ(), - ft, tf - ); -- if (mergedZ == ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.EMPTY) { -+ if (mergedZ == null) { - return false; - } - -- final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeDataFirst = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$getCachedVoxelData(); -- final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeDataSecond = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$getCachedVoxelData(); -+ final CachedShapeData shapeDataFirst = ((CollisionVoxelShape)first).moonrise$getCachedVoxelData(); -+ final CachedShapeData shapeDataSecond = ((CollisionVoxelShape)second).moonrise$getCachedVoxelData(); - - return !isMergeEmpty( - shapeDataFirst, shapeDataSecond, -@@ -1112,10 +1368,6 @@ public final class CollisionUtil { - } - } - -- private static final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList EMPTY = new ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList( -- new double[] { 0.0 }, 0.0, new int[0], new int[0], 0 -- ); -- - private static int[] getIndices(final int length) { - final int[] ret = new int[length]; - -@@ -1142,25 +1394,25 @@ public final class CollisionUtil { - this.voxels = voxels; - } - -- public it.unimi.dsi.fastutil.doubles.DoubleList wrapCoords() { -+ public DoubleList wrapCoords() { - if (this.coordinateOffset == 0.0) { -- return it.unimi.dsi.fastutil.doubles.DoubleArrayList.wrap(this.coordinates, this.voxels + 1); -+ return DoubleArrayList.wrap(this.coordinates, this.voxels + 1); - } -- return new net.minecraft.world.phys.shapes.OffsetDoubleList(it.unimi.dsi.fastutil.doubles.DoubleArrayList.wrap(this.coordinates, this.voxels + 1), this.coordinateOffset); -+ return new OffsetDoubleList(DoubleArrayList.wrap(this.coordinates, this.voxels + 1), this.coordinateOffset); - } - - // assume coordinates.length > 1 -- public static ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList getForSingle(final double[] coordinates, final double offset) { -+ public static MergedVoxelCoordinateList getForSingle(final double[] coordinates, final double offset) { - final int voxels = coordinates.length - 1; - final int[] indices = voxels < SIMPLE_INDICES_CACHE.length ? SIMPLE_INDICES_CACHE[voxels] : getIndices(voxels); - -- return new ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList(coordinates, offset, indices, indices, voxels); -+ return new MergedVoxelCoordinateList(coordinates, offset, indices, indices, voxels); - } - - // assume coordinates.length > 1 -- public static ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList merge(final double[] firstCoordinates, final double firstOffset, -- final double[] secondCoordinates, final double secondOffset, -- final boolean ft, final boolean tf) { -+ public static MergedVoxelCoordinateList merge(final double[] firstCoordinates, final double firstOffset, -+ final double[] secondCoordinates, final double secondOffset, -+ final boolean ft, final boolean tf) { - if (firstCoordinates == secondCoordinates && firstOffset == secondOffset) { - return getForSingle(firstCoordinates, firstOffset); - } -@@ -1250,13 +1502,13 @@ public final class CollisionUtil { - } - } - -- return resultSize <= 1 ? EMPTY : new ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList(coordinates, 0.0, firstIndices, secondIndices, resultSize - 1); -+ return resultSize <= 1 ? null : new MergedVoxelCoordinateList(coordinates, 0.0, firstIndices, secondIndices, resultSize - 1); - } - } - -- public static boolean equals(final net.minecraft.world.phys.shapes.DiscreteVoxelShape shape1, final net.minecraft.world.phys.shapes.DiscreteVoxelShape shape2) { -- final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData cachedShapeData1 = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionDiscreteVoxelShape)shape1).moonrise$getOrCreateCachedShapeData(); -- final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData cachedShapeData2 = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionDiscreteVoxelShape)shape2).moonrise$getOrCreateCachedShapeData(); -+ public static boolean equals(final DiscreteVoxelShape shape1, final DiscreteVoxelShape shape2) { -+ final CachedShapeData cachedShapeData1 = ((CollisionDiscreteVoxelShape)shape1).moonrise$getOrCreateCachedShapeData(); -+ final CachedShapeData cachedShapeData2 = ((CollisionDiscreteVoxelShape)shape2).moonrise$getOrCreateCachedShapeData(); - - final boolean isEmpty1 = cachedShapeData1.isEmpty(); - final boolean isEmpty2 = cachedShapeData2.isEmpty(); -@@ -1265,7 +1517,7 @@ public final class CollisionUtil { - return true; - } else if (isEmpty1 ^ isEmpty2) { - return false; -- } -+ } // else: isEmpty1 = isEmpty2 = false - - if (cachedShapeData1.hasSingleAABB() != cachedShapeData2.hasSingleAABB()) { - return false; -@@ -1281,153 +1533,237 @@ public final class CollisionUtil { - return false; - } - -- return java.util.Arrays.equals(cachedShapeData1.voxelSet(), cachedShapeData2.voxelSet()); -+ return Arrays.equals(cachedShapeData1.voxelSet(), cachedShapeData2.voxelSet()); - } - - // useful only for testing -- public static boolean equals(final net.minecraft.world.phys.shapes.VoxelShape shape1, final net.minecraft.world.phys.shapes.VoxelShape shape2) { -+ public static boolean equals(final VoxelShape shape1, final VoxelShape shape2) { -+ if (shape1.isEmpty() & shape2.isEmpty()) { -+ return true; -+ } else if (shape1.isEmpty() ^ shape2.isEmpty()) { -+ return false; -+ } -+ - if (!equals(shape1.shape, shape2.shape)) { - return false; - } - -- return shape1.getCoords(net.minecraft.core.Direction.Axis.X).equals(shape2.getCoords(net.minecraft.core.Direction.Axis.X)) && -- shape1.getCoords(net.minecraft.core.Direction.Axis.Y).equals(shape2.getCoords(net.minecraft.core.Direction.Axis.Y)) && -- shape1.getCoords(net.minecraft.core.Direction.Axis.Z).equals(shape2.getCoords(net.minecraft.core.Direction.Axis.Z)); -+ return shape1.getCoords(Direction.Axis.X).equals(shape2.getCoords(Direction.Axis.X)) && -+ shape1.getCoords(Direction.Axis.Y).equals(shape2.getCoords(Direction.Axis.Y)) && -+ shape1.getCoords(Direction.Axis.Z).equals(shape2.getCoords(Direction.Axis.Z)); -+ } -+ -+ public static boolean areAnyFull(final DiscreteVoxelShape shape) { -+ if (shape.isEmpty()) { -+ return false; -+ } -+ -+ final int sizeX = shape.getXSize(); -+ final int sizeY = shape.getYSize(); -+ final int sizeZ = shape.getZSize(); -+ -+ for (int x = 0; x < sizeX; ++x) { -+ for (int y = 0; y < sizeY; ++y) { -+ for (int z = 0; z < sizeZ; ++z) { -+ if (shape.isFull(x, y, z)) { -+ return true; -+ } -+ } -+ } -+ } -+ -+ return false; -+ } -+ -+ public static String shapeMismatch(final DiscreteVoxelShape shape1, final DiscreteVoxelShape shape2) { -+ final CachedShapeData cachedShapeData1 = ((CollisionDiscreteVoxelShape)shape1).moonrise$getOrCreateCachedShapeData(); -+ final CachedShapeData cachedShapeData2 = ((CollisionDiscreteVoxelShape)shape2).moonrise$getOrCreateCachedShapeData(); -+ -+ final boolean isEmpty1 = cachedShapeData1.isEmpty(); -+ final boolean isEmpty2 = cachedShapeData2.isEmpty(); -+ -+ if (isEmpty1 & isEmpty2) { -+ return null; -+ } else if (isEmpty1 ^ isEmpty2) { -+ return null; -+ } // else: isEmpty1 = isEmpty2 = false -+ -+ if (cachedShapeData1.sizeX() != cachedShapeData2.sizeX()) { -+ return "size x: " + cachedShapeData1.sizeX() + " != " + cachedShapeData2.sizeX(); -+ } -+ if (cachedShapeData1.sizeY() != cachedShapeData2.sizeY()) { -+ return "size y: " + cachedShapeData1.sizeY() + " != " + cachedShapeData2.sizeY(); -+ } -+ if (cachedShapeData1.sizeZ() != cachedShapeData2.sizeZ()) { -+ return "size z: " + cachedShapeData1.sizeZ() + " != " + cachedShapeData2.sizeZ(); -+ } -+ -+ final StringBuilder ret = new StringBuilder(); -+ -+ final int sizeX = cachedShapeData1.sizeX();; -+ final int sizeY = cachedShapeData1.sizeY(); -+ final int sizeZ = cachedShapeData1.sizeZ(); -+ -+ boolean first = true; -+ -+ for (int x = 0; x < sizeX; ++x) { -+ for (int y = 0; y < sizeY; ++y) { -+ for (int z = 0; z < sizeZ; ++z) { -+ final boolean isFull1 = shape1.isFull(x, y, z); -+ final boolean isFull2 = shape2.isFull(x, y, z); -+ -+ if (isFull1 == isFull2) { -+ continue; -+ } -+ -+ if (first) { -+ first = false; -+ } else { -+ ret.append(", "); -+ } -+ -+ ret.append("(").append(x).append(",").append(y).append(",").append(z) -+ .append("): shape1: ").append(isFull1).append(", shape2: ").append(isFull2); -+ } -+ } -+ } -+ -+ return ret.isEmpty() ? null : ret.toString(); - } - -- public static net.minecraft.world.phys.AABB offsetX(final net.minecraft.world.phys.AABB box, final double dx) { -- return new net.minecraft.world.phys.AABB(box.minX + dx, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ); -+ public static AABB offsetX(final AABB box, final double dx) { -+ return new AABB(box.minX + dx, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ); - } - -- public static net.minecraft.world.phys.AABB offsetY(final net.minecraft.world.phys.AABB box, final double dy) { -- return new net.minecraft.world.phys.AABB(box.minX, box.minY + dy, box.minZ, box.maxX, box.maxY + dy, box.maxZ); -+ public static AABB offsetY(final AABB box, final double dy) { -+ return new AABB(box.minX, box.minY + dy, box.minZ, box.maxX, box.maxY + dy, box.maxZ); - } - -- public static net.minecraft.world.phys.AABB offsetZ(final net.minecraft.world.phys.AABB box, final double dz) { -- return new net.minecraft.world.phys.AABB(box.minX, box.minY, box.minZ + dz, box.maxX, box.maxY, box.maxZ + dz); -+ public static AABB offsetZ(final AABB box, final double dz) { -+ return new AABB(box.minX, box.minY, box.minZ + dz, box.maxX, box.maxY, box.maxZ + dz); - } - -- public static net.minecraft.world.phys.AABB expandRight(final net.minecraft.world.phys.AABB box, final double dx) { // dx > 0.0 -- return new net.minecraft.world.phys.AABB(box.minX, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ); -+ public static AABB expandRight(final AABB box, final double dx) { // dx > 0.0 -+ return new AABB(box.minX, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ); - } - -- public static net.minecraft.world.phys.AABB expandLeft(final net.minecraft.world.phys.AABB box, final double dx) { // dx < 0.0 -- return new net.minecraft.world.phys.AABB(box.minX - dx, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ); -+ public static AABB expandLeft(final AABB box, final double dx) { // dx < 0.0 -+ return new AABB(box.minX - dx, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ); - } - -- public static net.minecraft.world.phys.AABB expandUpwards(final net.minecraft.world.phys.AABB box, final double dy) { // dy > 0.0 -- return new net.minecraft.world.phys.AABB(box.minX, box.minY, box.minZ, box.maxX, box.maxY + dy, box.maxZ); -+ public static AABB expandUpwards(final AABB box, final double dy) { // dy > 0.0 -+ return new AABB(box.minX, box.minY, box.minZ, box.maxX, box.maxY + dy, box.maxZ); - } - -- public static net.minecraft.world.phys.AABB expandDownwards(final net.minecraft.world.phys.AABB box, final double dy) { // dy < 0.0 -- return new net.minecraft.world.phys.AABB(box.minX, box.minY - dy, box.minZ, box.maxX, box.maxY, box.maxZ); -+ public static AABB expandDownwards(final AABB box, final double dy) { // dy < 0.0 -+ return new AABB(box.minX, box.minY - dy, box.minZ, box.maxX, box.maxY, box.maxZ); - } - -- public static net.minecraft.world.phys.AABB expandForwards(final net.minecraft.world.phys.AABB box, final double dz) { // dz > 0.0 -- return new net.minecraft.world.phys.AABB(box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ + dz); -+ public static AABB expandForwards(final AABB box, final double dz) { // dz > 0.0 -+ return new AABB(box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ + dz); - } - -- public static net.minecraft.world.phys.AABB expandBackwards(final net.minecraft.world.phys.AABB box, final double dz) { // dz < 0.0 -- return new net.minecraft.world.phys.AABB(box.minX, box.minY, box.minZ - dz, box.maxX, box.maxY, box.maxZ); -+ public static AABB expandBackwards(final AABB box, final double dz) { // dz < 0.0 -+ return new AABB(box.minX, box.minY, box.minZ - dz, box.maxX, box.maxY, box.maxZ); - } - -- public static net.minecraft.world.phys.AABB cutRight(final net.minecraft.world.phys.AABB box, final double dx) { // dx > 0.0 -- return new net.minecraft.world.phys.AABB(box.maxX, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ); -+ public static AABB cutRight(final AABB box, final double dx) { // dx > 0.0 -+ return new AABB(box.maxX, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ); - } - -- public static net.minecraft.world.phys.AABB cutLeft(final net.minecraft.world.phys.AABB box, final double dx) { // dx < 0.0 -- return new net.minecraft.world.phys.AABB(box.minX + dx, box.minY, box.minZ, box.minX, box.maxY, box.maxZ); -+ public static AABB cutLeft(final AABB box, final double dx) { // dx < 0.0 -+ return new AABB(box.minX + dx, box.minY, box.minZ, box.minX, box.maxY, box.maxZ); - } - -- public static net.minecraft.world.phys.AABB cutUpwards(final net.minecraft.world.phys.AABB box, final double dy) { // dy > 0.0 -- return new net.minecraft.world.phys.AABB(box.minX, box.maxY, box.minZ, box.maxX, box.maxY + dy, box.maxZ); -+ public static AABB cutUpwards(final AABB box, final double dy) { // dy > 0.0 -+ return new AABB(box.minX, box.maxY, box.minZ, box.maxX, box.maxY + dy, box.maxZ); - } - -- public static net.minecraft.world.phys.AABB cutDownwards(final net.minecraft.world.phys.AABB box, final double dy) { // dy < 0.0 -- return new net.minecraft.world.phys.AABB(box.minX, box.minY + dy, box.minZ, box.maxX, box.minY, box.maxZ); -+ public static AABB cutDownwards(final AABB box, final double dy) { // dy < 0.0 -+ return new AABB(box.minX, box.minY + dy, box.minZ, box.maxX, box.minY, box.maxZ); - } - -- public static net.minecraft.world.phys.AABB cutForwards(final net.minecraft.world.phys.AABB box, final double dz) { // dz > 0.0 -- return new net.minecraft.world.phys.AABB(box.minX, box.minY, box.maxZ, box.maxX, box.maxY, box.maxZ + dz); -+ public static AABB cutForwards(final AABB box, final double dz) { // dz > 0.0 -+ return new AABB(box.minX, box.minY, box.maxZ, box.maxX, box.maxY, box.maxZ + dz); - } - -- public static net.minecraft.world.phys.AABB cutBackwards(final net.minecraft.world.phys.AABB box, final double dz) { // dz < 0.0 -- return new net.minecraft.world.phys.AABB(box.minX, box.minY, box.minZ + dz, box.maxX, box.maxY, box.minZ); -+ public static AABB cutBackwards(final AABB box, final double dz) { // dz < 0.0 -+ return new AABB(box.minX, box.minY, box.minZ + dz, box.maxX, box.maxY, box.minZ); - } - -- public static double performAABBCollisionsX(final net.minecraft.world.phys.AABB currentBoundingBox, double value, final java.util.List potentialCollisions) { -+ public static double performAABBCollisionsX(final AABB currentBoundingBox, double value, final List potentialCollisions) { - for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { - if (Math.abs(value) < COLLISION_EPSILON) { - return 0.0; - } -- final net.minecraft.world.phys.AABB target = potentialCollisions.get(i); -+ final AABB target = potentialCollisions.get(i); - value = collideX(target, currentBoundingBox, value); - } - -- return value; -+ return Math.abs(value) < COLLISION_EPSILON ? 0.0 : value; - } - -- public static double performAABBCollisionsY(final net.minecraft.world.phys.AABB currentBoundingBox, double value, final java.util.List potentialCollisions) { -+ public static double performAABBCollisionsY(final AABB currentBoundingBox, double value, final List potentialCollisions) { - for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { - if (Math.abs(value) < COLLISION_EPSILON) { - return 0.0; - } -- final net.minecraft.world.phys.AABB target = potentialCollisions.get(i); -+ final AABB target = potentialCollisions.get(i); - value = collideY(target, currentBoundingBox, value); - } - -- return value; -+ return Math.abs(value) < COLLISION_EPSILON ? 0.0 : value; - } - -- public static double performAABBCollisionsZ(final net.minecraft.world.phys.AABB currentBoundingBox, double value, final java.util.List potentialCollisions) { -+ public static double performAABBCollisionsZ(final AABB currentBoundingBox, double value, final List potentialCollisions) { - for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { - if (Math.abs(value) < COLLISION_EPSILON) { - return 0.0; - } -- final net.minecraft.world.phys.AABB target = potentialCollisions.get(i); -+ final AABB target = potentialCollisions.get(i); - value = collideZ(target, currentBoundingBox, value); - } - -- return value; -+ return Math.abs(value) < COLLISION_EPSILON ? 0.0 : value; - } - -- public static double performVoxelCollisionsX(final net.minecraft.world.phys.AABB currentBoundingBox, double value, final java.util.List potentialCollisions) { -+ public static double performVoxelCollisionsX(final AABB currentBoundingBox, double value, final List potentialCollisions) { - for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { - if (Math.abs(value) < COLLISION_EPSILON) { - return 0.0; - } -- final net.minecraft.world.phys.shapes.VoxelShape target = potentialCollisions.get(i); -+ final VoxelShape target = potentialCollisions.get(i); - value = collideX(target, currentBoundingBox, value); - } - -- return value; -+ return Math.abs(value) < COLLISION_EPSILON ? 0.0 : value; - } - -- public static double performVoxelCollisionsY(final net.minecraft.world.phys.AABB currentBoundingBox, double value, final java.util.List potentialCollisions) { -+ public static double performVoxelCollisionsY(final AABB currentBoundingBox, double value, final List potentialCollisions) { - for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { - if (Math.abs(value) < COLLISION_EPSILON) { - return 0.0; - } -- final net.minecraft.world.phys.shapes.VoxelShape target = potentialCollisions.get(i); -+ final VoxelShape target = potentialCollisions.get(i); - value = collideY(target, currentBoundingBox, value); - } - -- return value; -+ return Math.abs(value) < COLLISION_EPSILON ? 0.0 : value; - } - -- public static double performVoxelCollisionsZ(final net.minecraft.world.phys.AABB currentBoundingBox, double value, final java.util.List potentialCollisions) { -+ public static double performVoxelCollisionsZ(final AABB currentBoundingBox, double value, final List potentialCollisions) { - for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { - if (Math.abs(value) < COLLISION_EPSILON) { - return 0.0; - } -- final net.minecraft.world.phys.shapes.VoxelShape target = potentialCollisions.get(i); -+ final VoxelShape target = potentialCollisions.get(i); - value = collideZ(target, currentBoundingBox, value); - } - -- return value; -+ return Math.abs(value) < COLLISION_EPSILON ? 0.0 : value; - } - -- public static net.minecraft.world.phys.Vec3 performVoxelCollisions(final net.minecraft.world.phys.Vec3 moveVector, net.minecraft.world.phys.AABB axisalignedbb, final java.util.List potentialCollisions) { -+ public static Vec3 performVoxelCollisions(final Vec3 moveVector, AABB axisalignedbb, final List potentialCollisions) { - double x = moveVector.x; - double y = moveVector.y; - double z = moveVector.z; -@@ -1459,10 +1795,10 @@ public final class CollisionUtil { - z = performVoxelCollisionsZ(axisalignedbb, z, potentialCollisions); - } - -- return new net.minecraft.world.phys.Vec3(x, y, z); -+ return new Vec3(x, y, z); - } - -- public static net.minecraft.world.phys.Vec3 performAABBCollisions(final net.minecraft.world.phys.Vec3 moveVector, net.minecraft.world.phys.AABB axisalignedbb, final java.util.List potentialCollisions) { -+ public static Vec3 performAABBCollisions(final Vec3 moveVector, AABB axisalignedbb, final List potentialCollisions) { - double x = moveVector.x; - double y = moveVector.y; - double z = moveVector.z; -@@ -1494,12 +1830,12 @@ public final class CollisionUtil { - z = performAABBCollisionsZ(axisalignedbb, z, potentialCollisions); - } - -- return new net.minecraft.world.phys.Vec3(x, y, z); -+ return new Vec3(x, y, z); - } - -- public static net.minecraft.world.phys.Vec3 performCollisions(final net.minecraft.world.phys.Vec3 moveVector, net.minecraft.world.phys.AABB axisalignedbb, -- final java.util.List voxels, -- final java.util.List aabbs) { -+ public static Vec3 performCollisions(final Vec3 moveVector, AABB axisalignedbb, -+ final List voxels, -+ final List aabbs) { - if (voxels.isEmpty()) { - // fast track only AABBs - return performAABBCollisions(moveVector, axisalignedbb, aabbs); -@@ -1540,14 +1876,14 @@ public final class CollisionUtil { - z = performVoxelCollisionsZ(axisalignedbb, z, voxels); - } - -- return new net.minecraft.world.phys.Vec3(x, y, z); -+ return new Vec3(x, y, z); - } - -- public static boolean isCollidingWithBorder(final net.minecraft.world.level.border.WorldBorder worldborder, final net.minecraft.world.phys.AABB boundingBox) { -+ public static boolean isCollidingWithBorder(final WorldBorder worldborder, final AABB boundingBox) { - return isCollidingWithBorder(worldborder, boundingBox.minX, boundingBox.maxX, boundingBox.minZ, boundingBox.maxZ); - } - -- public static boolean isCollidingWithBorder(final net.minecraft.world.level.border.WorldBorder worldborder, -+ public static boolean isCollidingWithBorder(final WorldBorder worldborder, - final double boxMinX, final double boxMaxX, - final double boxMinZ, final double boxMaxZ) { - final double borderMinX = Math.floor(worldborder.getMinX()); // -X -@@ -1557,8 +1893,8 @@ public final class CollisionUtil { - final double borderMaxZ = Math.ceil(worldborder.getMaxZ()); // +Z - - // inverted check for world border enclosing the specified box expanded by -EPSILON -- return (borderMinX - boxMinX) > ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON || (borderMaxX - boxMaxX) < -ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON || -- (borderMinZ - boxMinZ) > ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON || (borderMaxZ - boxMaxZ) < -ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON; -+ return (borderMinX - boxMinX) > CollisionUtil.COLLISION_EPSILON || (borderMaxX - boxMaxX) < -CollisionUtil.COLLISION_EPSILON || -+ (borderMinZ - boxMinZ) > CollisionUtil.COLLISION_EPSILON || (borderMaxZ - boxMaxZ) < -CollisionUtil.COLLISION_EPSILON; - } - - /* Math.max/min specify that any NaN argument results in a NaN return, unlike these functions */ -@@ -1575,38 +1911,38 @@ public final class CollisionUtil { - public static final int COLLISION_FLAG_CHECK_BORDER = 1 << 2; - public static final int COLLISION_FLAG_CHECK_ONLY = 1 << 3; - -- public static boolean getCollisionsForBlocksOrWorldBorder(final net.minecraft.world.level.Level world, final net.minecraft.world.entity.Entity entity, final net.minecraft.world.phys.AABB aabb, -- final java.util.List intoVoxel, final java.util.List intoAABB, -- final int collisionFlags, final java.util.function.BiPredicate predicate) { -+ public static boolean getCollisionsForBlocksOrWorldBorder(final Level world, final Entity entity, final AABB aabb, -+ final List intoVoxel, final List intoAABB, -+ final int collisionFlags, final BiPredicate predicate) { - final boolean checkOnly = (collisionFlags & COLLISION_FLAG_CHECK_ONLY) != 0; - boolean ret = false; - - if ((collisionFlags & COLLISION_FLAG_CHECK_BORDER) != 0) { -- final net.minecraft.world.level.border.WorldBorder worldBorder = world.getWorldBorder(); -- if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isCollidingWithBorder(worldBorder, aabb) && entity != null && worldBorder.isInsideCloseToBorder(entity, aabb)) { -+ final WorldBorder worldBorder = world.getWorldBorder(); -+ if (CollisionUtil.isCollidingWithBorder(worldBorder, aabb) && entity != null && worldBorder.isInsideCloseToBorder(entity, aabb)) { - if (checkOnly) { - return true; - } else { -- final net.minecraft.world.phys.shapes.VoxelShape borderShape = worldBorder.getCollisionShape(); -+ final VoxelShape borderShape = worldBorder.getCollisionShape(); - intoVoxel.add(borderShape); - ret = true; - } - } - } - -- final int minSection = ((ca.spottedleaf.moonrise.patches.collisions.world.CollisionLevel)world).moonrise$getMinSection(); -+ final int minSection = WorldUtil.getMinSection(world); - -- final int minBlockX = net.minecraft.util.Mth.floor(aabb.minX - COLLISION_EPSILON) - 1; -- final int maxBlockX = net.minecraft.util.Mth.floor(aabb.maxX + COLLISION_EPSILON) + 1; -+ final int minBlockX = Mth.floor(aabb.minX - COLLISION_EPSILON) - 1; -+ final int maxBlockX = Mth.floor(aabb.maxX + COLLISION_EPSILON) + 1; - -- final int minBlockY = Math.max((minSection << 4) - 1, net.minecraft.util.Mth.floor(aabb.minY - COLLISION_EPSILON) - 1); -- final int maxBlockY = Math.min((((ca.spottedleaf.moonrise.patches.collisions.world.CollisionLevel)world).moonrise$getMaxSection() << 4) + 16, net.minecraft.util.Mth.floor(aabb.maxY + COLLISION_EPSILON) + 1); -+ final int minBlockY = Math.max((minSection << 4) - 1, Mth.floor(aabb.minY - COLLISION_EPSILON) - 1); -+ final int maxBlockY = Math.min((WorldUtil.getMaxSection(world) << 4) + 16, Mth.floor(aabb.maxY + COLLISION_EPSILON) + 1); - -- final int minBlockZ = net.minecraft.util.Mth.floor(aabb.minZ - COLLISION_EPSILON) - 1; -- final int maxBlockZ = net.minecraft.util.Mth.floor(aabb.maxZ + COLLISION_EPSILON) + 1; -+ final int minBlockZ = Mth.floor(aabb.minZ - COLLISION_EPSILON) - 1; -+ final int maxBlockZ = Mth.floor(aabb.maxZ + COLLISION_EPSILON) + 1; - -- final net.minecraft.core.BlockPos.MutableBlockPos mutablePos = new net.minecraft.core.BlockPos.MutableBlockPos(); -- final net.minecraft.world.phys.shapes.CollisionContext collisionShape = new ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.LazyEntityCollisionContext(entity); -+ final BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos(); -+ final CollisionContext collisionShape = new LazyEntityCollisionContext(entity); - - // special cases: - if (minBlockY > maxBlockY) { -@@ -1624,11 +1960,11 @@ public final class CollisionUtil { - final int maxChunkZ = maxBlockZ >> 4; - - final boolean loadChunks = (collisionFlags & COLLISION_FLAG_LOAD_CHUNKS) != 0; -- final net.minecraft.world.level.chunk.ChunkSource chunkSource = world.getChunkSource(); -+ final ChunkSource chunkSource = world.getChunkSource(); - - for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) { - for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) { -- final net.minecraft.world.level.chunk.ChunkAccess chunk = chunkSource.getChunk(currChunkX, currChunkZ, net.minecraft.world.level.chunk.status.ChunkStatus.FULL, loadChunks); -+ final ChunkAccess chunk = chunkSource.getChunk(currChunkX, currChunkZ, ChunkStatus.FULL, loadChunks); - - if (chunk == null) { - if ((collisionFlags & COLLISION_FLAG_COLLIDE_WITH_UNLOADED_CHUNKS) != 0) { -@@ -1642,7 +1978,7 @@ public final class CollisionUtil { - continue; - } - -- final net.minecraft.world.level.chunk.LevelChunkSection[] sections = chunk.getSections(); -+ final LevelChunkSection[] sections = chunk.getSections(); - - // bound y - for (int currChunkY = minChunkY; currChunkY <= maxChunkY; ++currChunkY) { -@@ -1650,16 +1986,16 @@ public final class CollisionUtil { - if (sectionIdx < 0 || sectionIdx >= sections.length) { - continue; - } -- final net.minecraft.world.level.chunk.LevelChunkSection section = sections[sectionIdx]; -- if (section == null || section.hasOnlyAir()) { -+ final LevelChunkSection section = sections[sectionIdx]; -+ if (section.hasOnlyAir()) { - // empty - continue; - } - -- final boolean hasSpecial = ((ca.spottedleaf.moonrise.patches.block_counting.BlockCountingChunkSection)section).moonrise$getSpecialCollidingBlocks() != 0; -+ final boolean hasSpecial = ((BlockCountingChunkSection)section).moonrise$hasSpecialCollidingBlocks(); - final int sectionAdjust = !hasSpecial ? 1 : 0; - -- final net.minecraft.world.level.chunk.PalettedContainer blocks = section.states; -+ final PalettedContainer blocks = section.states; - - final int minXIterate = currChunkX == minChunkX ? (minBlockX & 15) + sectionAdjust : 0; - final int maxXIterate = currChunkX == maxChunkX ? (maxBlockX & 15) - sectionAdjust : 15; -@@ -1683,21 +2019,21 @@ public final class CollisionUtil { - continue; - } - -- final net.minecraft.world.level.block.state.BlockState blockData = blocks.get(localBlockIndex); -+ final BlockState blockData = blocks.get(localBlockIndex); - -- if (((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)blockData).moonrise$emptyCollisionShape()) { -+ if (((CollisionBlockState)blockData).moonrise$emptyContextCollisionShape()) { - continue; - } - -- net.minecraft.world.phys.shapes.VoxelShape blockCollision = ((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)blockData).moonrise$getConstantCollisionShape(); -+ VoxelShape blockCollision = ((CollisionBlockState)blockData).moonrise$getConstantContextCollisionShape(); - -- if (edgeCount == 0 || ((edgeCount != 1 || blockData.hasLargeCollisionShape()) && (edgeCount != 2 || blockData.getBlock() == net.minecraft.world.level.block.Blocks.MOVING_PISTON))) { -+ if (edgeCount == 0 || ((edgeCount != 1 || blockData.hasLargeCollisionShape()) && (edgeCount != 2 || blockData.getBlock() == Blocks.MOVING_PISTON))) { - if (blockCollision == null) { - mutablePos.set(blockX, blockY, blockZ); - blockCollision = blockData.getCollisionShape(world, mutablePos, collisionShape); - } - -- net.minecraft.world.phys.AABB singleAABB = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)blockCollision).moonrise$getSingleAABBRepresentation(); -+ AABB singleAABB = ((CollisionVoxelShape)blockCollision).moonrise$getSingleAABBRepresentation(); - if (singleAABB != null) { - singleAABB = singleAABB.move((double)blockX, (double)blockY, (double)blockZ); - if (!voxelShapeIntersect(aabb, singleAABB)) { -@@ -1724,7 +2060,7 @@ public final class CollisionUtil { - continue; - } - -- final net.minecraft.world.phys.shapes.VoxelShape blockCollisionOffset = blockCollision.move((double)blockX, (double)blockY, (double)blockZ); -+ final VoxelShape blockCollisionOffset = blockCollision.move((double)blockX, (double)blockY, (double)blockZ); - - if (!voxelShapeIntersectNoEmpty(blockCollisionOffset, aabb)) { - continue; -@@ -1755,8 +2091,8 @@ public final class CollisionUtil { - return ret; - } - -- public static boolean getEntityHardCollisions(final net.minecraft.world.level.Level world, final net.minecraft.world.entity.Entity entity, net.minecraft.world.phys.AABB aabb, -- final java.util.List into, final int collisionFlags, final java.util.function.Predicate predicate) { -+ public static boolean getEntityHardCollisions(final Level world, final Entity entity, AABB aabb, -+ final List into, final int collisionFlags, final Predicate predicate) { - final boolean checkOnly = (collisionFlags & COLLISION_FLAG_CHECK_ONLY) != 0; - - boolean ret = false; -@@ -1765,15 +2101,15 @@ public final class CollisionUtil { - // Vanilla for hard collisions has this backwards, and they expand by +epsilon but this causes terrible problems - // specifically with boat collisions. - aabb = aabb.inflate(-COLLISION_EPSILON, -COLLISION_EPSILON, -COLLISION_EPSILON); -- final java.util.List entities; -- if (entity != null && ((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity)entity).moonrise$isHardColliding()) { -+ final List entities; -+ if (entity != null && ((ChunkSystemEntity)entity).moonrise$isHardColliding()) { - entities = world.getEntities(entity, aabb, predicate); - } else { -- entities = ((ca.spottedleaf.moonrise.patches.chunk_system.world.ChunkSystemEntityGetter)world).moonrise$getHardCollidingEntities(entity, aabb, predicate); -+ entities = ((ChunkSystemEntityGetter)world).moonrise$getHardCollidingEntities(entity, aabb, predicate); - } - - for (int i = 0, len = entities.size(); i < len; ++i) { -- final net.minecraft.world.entity.Entity otherEntity = entities.get(i); -+ final Entity otherEntity = entities.get(i); - - if (otherEntity.isSpectator()) { - continue; -@@ -1792,10 +2128,10 @@ public final class CollisionUtil { - return ret; - } - -- public static boolean getCollisions(final net.minecraft.world.level.Level world, final net.minecraft.world.entity.Entity entity, final net.minecraft.world.phys.AABB aabb, -- final java.util.List intoVoxel, final java.util.List intoAABB, final int collisionFlags, -- final java.util.function.BiPredicate blockPredicate, -- final java.util.function.Predicate entityPredicate) { -+ public static boolean getCollisions(final Level world, final Entity entity, final AABB aabb, -+ final List intoVoxel, final List intoAABB, final int collisionFlags, -+ final BiPredicate blockPredicate, -+ final Predicate entityPredicate) { - if ((collisionFlags & COLLISION_FLAG_CHECK_ONLY) != 0) { - return getCollisionsForBlocksOrWorldBorder(world, entity, aabb, intoVoxel, intoAABB, collisionFlags, blockPredicate) - || getEntityHardCollisions(world, entity, aabb, intoAABB, collisionFlags, entityPredicate); -@@ -1805,12 +2141,12 @@ public final class CollisionUtil { - } - } - -- public static final class LazyEntityCollisionContext extends net.minecraft.world.phys.shapes.EntityCollisionContext { -+ public static final class LazyEntityCollisionContext extends EntityCollisionContext { - -- private net.minecraft.world.phys.shapes.CollisionContext delegate; -+ private CollisionContext delegate; - private boolean delegated; - -- public LazyEntityCollisionContext(final net.minecraft.world.entity.Entity entity) { -+ public LazyEntityCollisionContext(final Entity entity) { - super(false, 0.0, null, null, entity); - } - -@@ -1820,10 +2156,10 @@ public final class CollisionUtil { - return delegated; - } - -- public net.minecraft.world.phys.shapes.CollisionContext getDelegate() { -+ public CollisionContext getDelegate() { - this.delegated = true; -- final net.minecraft.world.entity.Entity entity = this.getEntity(); -- return this.delegate == null ? this.delegate = (entity == null ? net.minecraft.world.phys.shapes.CollisionContext.empty() : net.minecraft.world.phys.shapes.CollisionContext.of(entity)) : this.delegate; -+ final Entity entity = this.getEntity(); -+ return this.delegate == null ? this.delegate = (entity == null ? CollisionContext.empty() : CollisionContext.of(entity)) : this.delegate; - } - - @Override -@@ -1832,17 +2168,17 @@ public final class CollisionUtil { - } - - @Override -- public boolean isAbove(final net.minecraft.world.phys.shapes.VoxelShape shape, final net.minecraft.core.BlockPos pos, final boolean defaultValue) { -+ public boolean isAbove(final VoxelShape shape, final BlockPos pos, final boolean defaultValue) { - return this.getDelegate().isAbove(shape, pos, defaultValue); - } - - @Override -- public boolean isHoldingItem(final net.minecraft.world.item.Item item) { -+ public boolean isHoldingItem(final Item item) { - return this.getDelegate().isHoldingItem(item); - } - - @Override -- public boolean canStandOnFluid(final net.minecraft.world.level.material.FluidState state, final net.minecraft.world.level.material.FluidState fluidState) { -+ public boolean canStandOnFluid(final FluidState state, final FluidState fluidState) { - return this.getDelegate().canStandOnFluid(state, fluidState); - } - } -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/ExplosionBlockCache.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/ExplosionBlockCache.java -index eb7200657d5c7ac37ee93868ba43be0aefecac6d..35c8aaf0bfa42717f45eed1d1072e1614874de91 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/ExplosionBlockCache.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/ExplosionBlockCache.java -@@ -1,18 +1,23 @@ - package ca.spottedleaf.moonrise.patches.collisions; - -+import net.minecraft.core.BlockPos; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.material.FluidState; -+import net.minecraft.world.phys.shapes.VoxelShape; -+ - public final class ExplosionBlockCache { - - public final long key; -- public final net.minecraft.core.BlockPos immutablePos; -- public final net.minecraft.world.level.block.state.BlockState blockState; -- public final net.minecraft.world.level.material.FluidState fluidState; -+ public final BlockPos immutablePos; -+ public final BlockState blockState; -+ public final FluidState fluidState; - public final float resistance; - public final boolean outOfWorld; - public Boolean shouldExplode; // null -> not called yet -- public net.minecraft.world.phys.shapes.VoxelShape cachedCollisionShape; -+ public VoxelShape cachedCollisionShape; - -- public ExplosionBlockCache(final long key, final net.minecraft.core.BlockPos immutablePos, final net.minecraft.world.level.block.state.BlockState blockState, -- final net.minecraft.world.level.material.FluidState fluidState, final float resistance, final boolean outOfWorld) { -+ public ExplosionBlockCache(final long key, final BlockPos immutablePos, final BlockState blockState, -+ final FluidState fluidState, final float resistance, final boolean outOfWorld) { - this.key = key; - this.immutablePos = immutablePos; - this.blockState = blockState; -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/block/CollisionBlockState.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/block/CollisionBlockState.java -index 02b29c563a298e06186de010de68a716bccba494..a38ab583200ebf68ca68fdddf2d12077720b72b7 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/block/CollisionBlockState.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/block/CollisionBlockState.java -@@ -1,5 +1,7 @@ - package ca.spottedleaf.moonrise.patches.collisions.block; - -+import net.minecraft.world.phys.shapes.VoxelShape; -+ - public interface CollisionBlockState { - - // note: this does not consider canOcclude, it is only based on the cached collision shape (i.e hasCache()) -@@ -9,6 +11,9 @@ public interface CollisionBlockState { - // whether the cached collision shape exists and is empty - public boolean moonrise$emptyCollisionShape(); - -+ // whether the context-sensitive shape is constant and is empty -+ public boolean moonrise$emptyContextCollisionShape(); -+ - // indicates that occludesFullBlock is cached for the collision shape - public boolean moonrise$hasCache(); - -@@ -20,7 +25,5 @@ public interface CollisionBlockState { - // value is still unique - public int moonrise$uniqueId2(); - -- public net.minecraft.world.phys.shapes.VoxelShape moonrise$getConstantCollisionShape(); -- -- public net.minecraft.world.phys.AABB moonrise$getConstantCollisionAABB(); -+ public VoxelShape moonrise$getConstantContextCollisionShape(); - } -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CachedToAABBs.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CachedToAABBs.java -index 5fe1dad9dad368911aedbe6ba7fcd8f9b0189d32..9d33ead3a97d86b371e4d9ad9fed80d789bed844 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CachedToAABBs.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CachedToAABBs.java -@@ -1,27 +1,31 @@ - package ca.spottedleaf.moonrise.patches.collisions.shape; - -+import net.minecraft.world.phys.AABB; -+import java.util.ArrayList; -+import java.util.List; -+ - public record CachedToAABBs( -- java.util.List aabbs, -+ List aabbs, - boolean isOffset, - double offX, double offY, double offZ - ) { - -- public ca.spottedleaf.moonrise.patches.collisions.shape.CachedToAABBs removeOffset() { -- final java.util.List toOffset = this.aabbs; -+ public CachedToAABBs removeOffset() { -+ final List toOffset = this.aabbs; - final double offX = this.offX; - final double offY = this.offY; - final double offZ = this.offZ; - -- final java.util.List ret = new java.util.ArrayList<>(toOffset.size()); -+ final List ret = new ArrayList<>(toOffset.size()); - - for (int i = 0, len = toOffset.size(); i < len; ++i) { - ret.add(toOffset.get(i).move(offX, offY, offZ)); - } - -- return new ca.spottedleaf.moonrise.patches.collisions.shape.CachedToAABBs(ret, false, 0.0, 0.0, 0.0); -+ return new CachedToAABBs(ret, false, 0.0, 0.0, 0.0); - } - -- public static ca.spottedleaf.moonrise.patches.collisions.shape.CachedToAABBs offset(final ca.spottedleaf.moonrise.patches.collisions.shape.CachedToAABBs cache, final double offX, final double offY, final double offZ) { -+ public static CachedToAABBs offset(final CachedToAABBs cache, final double offX, final double offY, final double offZ) { - if (offX == 0.0 && offY == 0.0 && offZ == 0.0) { - return cache; - } -@@ -30,6 +34,6 @@ public record CachedToAABBs( - final double resY = cache.offY + offY; - final double resZ = cache.offZ + offZ; - -- return new ca.spottedleaf.moonrise.patches.collisions.shape.CachedToAABBs(cache.aabbs, true, resX, resY, resZ); -+ return new CachedToAABBs(cache.aabbs, true, resX, resY, resZ); - } - } -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionDiscreteVoxelShape.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionDiscreteVoxelShape.java -index a09efadea9b733840bbe69830dd8f2a303fe656f..07fe5e02c2d0a27d2fe37bb45761654dc2d02e5d 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionDiscreteVoxelShape.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionDiscreteVoxelShape.java -@@ -2,6 +2,6 @@ package ca.spottedleaf.moonrise.patches.collisions.shape; - - public interface CollisionDiscreteVoxelShape { - -- public ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData moonrise$getOrCreateCachedShapeData(); -+ public CachedShapeData moonrise$getOrCreateCachedShapeData(); - - } -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionVoxelShape.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionVoxelShape.java -index 70371eb87c11a106e8513cdbc8d938dda088f745..05d7b3f9d8659c259f3ed0537c57e6e43eb6e288 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionVoxelShape.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionVoxelShape.java -@@ -1,5 +1,9 @@ - package ca.spottedleaf.moonrise.patches.collisions.shape; - -+import net.minecraft.core.Direction; -+import net.minecraft.world.phys.AABB; -+import net.minecraft.world.phys.shapes.VoxelShape; -+ - public interface CollisionVoxelShape { - - public double moonrise$offsetX(); -@@ -14,16 +18,16 @@ public interface CollisionVoxelShape { - - public double[] moonrise$rootCoordinatesZ(); - -- public ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData moonrise$getCachedVoxelData(); -+ public CachedShapeData moonrise$getCachedVoxelData(); - - // rets null if not possible to represent this shape as one AABB -- public net.minecraft.world.phys.AABB moonrise$getSingleAABBRepresentation(); -+ public AABB moonrise$getSingleAABBRepresentation(); - - // ONLY USE INTERNALLY, ONLY FOR INITIALISING IN CONSTRUCTOR: VOXELSHAPES ARE STATIC - public void moonrise$initCache(); - - // this returns empty if not clamped to 1.0 or 0.0 depending on direction -- public net.minecraft.world.phys.shapes.VoxelShape moonrise$getFaceShapeClamped(final net.minecraft.core.Direction direction); -+ public VoxelShape moonrise$getFaceShapeClamped(final Direction direction); - - public boolean moonrise$isFullBlock(); - -@@ -32,5 +36,5 @@ public interface CollisionVoxelShape { - public boolean moonrise$occludesFullBlockIfCached(); - - // uses a cache internally -- public net.minecraft.world.phys.shapes.VoxelShape moonrise$orUnoptimized(final net.minecraft.world.phys.shapes.VoxelShape other); -+ public VoxelShape moonrise$orUnoptimized(final VoxelShape other); - } -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/MergedORCache.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/MergedORCache.java -index 4217426d3eca5e5cd2bc37e509f84da1d6fed0b2..44831fc18efb7534dc6e4822f3c9b5cdc4dcc33e 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/MergedORCache.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/MergedORCache.java -@@ -1,8 +1,10 @@ - package ca.spottedleaf.moonrise.patches.collisions.shape; - -+import net.minecraft.world.phys.shapes.VoxelShape; -+ - public record MergedORCache( -- net.minecraft.world.phys.shapes.VoxelShape key, -- net.minecraft.world.phys.shapes.VoxelShape result -+ VoxelShape key, -+ VoxelShape result - ) { - - } -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/EmptyStreamForMoveCall.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/EmptyStreamForMoveCall.java -deleted file mode 100644 -index 673103f160cbe577c6e05f998706af4e6850011b..0000000000000000000000000000000000000000 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/EmptyStreamForMoveCall.java -+++ /dev/null -@@ -1,225 +0,0 @@ --package ca.spottedleaf.moonrise.patches.collisions.util; -- --import java.util.Iterator; --import java.util.Optional; --import java.util.Spliterator; --import java.util.stream.Stream; -- --public final class EmptyStreamForMoveCall implements java.util.stream.Stream { -- -- public static final ca.spottedleaf.moonrise.patches.collisions.util.EmptyStreamForMoveCall INSTANCE = new ca.spottedleaf.moonrise.patches.collisions.util.EmptyStreamForMoveCall(); -- -- @Override -- public boolean noneMatch(java.util.function.Predicate predicate) { -- return false; // important: ret false so the branch is never taken by mojang code -- } -- -- @Override -- public java.util.stream.Stream filter(java.util.function.Predicate predicate) { -- return null; -- } -- -- @Override -- public java.util.stream.Stream map(java.util.function.Function mapper) { -- return null; -- } -- -- @Override -- public java.util.stream.IntStream mapToInt(java.util.function.ToIntFunction mapper) { -- return null; -- } -- -- @Override -- public java.util.stream.LongStream mapToLong(java.util.function.ToLongFunction mapper) { -- return null; -- } -- -- @Override -- public java.util.stream.DoubleStream mapToDouble(java.util.function.ToDoubleFunction mapper) { -- return null; -- } -- -- @Override -- public java.util.stream.Stream flatMap(java.util.function.Function> mapper) { -- return null; -- } -- -- @Override -- public java.util.stream.IntStream flatMapToInt(java.util.function.Function mapper) { -- return null; -- } -- -- @Override -- public java.util.stream.LongStream flatMapToLong(java.util.function.Function mapper) { -- return null; -- } -- -- @Override -- public java.util.stream.DoubleStream flatMapToDouble(java.util.function.Function mapper) { -- return null; -- } -- -- @Override -- public java.util.stream.Stream distinct() { -- return null; -- } -- -- @Override -- public java.util.stream.Stream sorted() { -- return null; -- } -- -- @Override -- public java.util.stream.Stream sorted(java.util.Comparator comparator) { -- return null; -- } -- -- @Override -- public java.util.stream.Stream peek(java.util.function.Consumer action) { -- return null; -- } -- -- @Override -- public java.util.stream.Stream limit(long maxSize) { -- return null; -- } -- -- @Override -- public java.util.stream.Stream skip(long n) { -- return null; -- } -- -- @Override -- public void forEach(java.util.function.Consumer action) { -- -- } -- -- @Override -- public void forEachOrdered(java.util.function.Consumer action) { -- -- } -- -- @org.jetbrains.annotations.NotNull -- @Override -- public Object[] toArray() { -- return new Object[0]; -- } -- -- @org.jetbrains.annotations.NotNull -- @Override -- public
    A[] toArray(java.util.function.IntFunction generator) { -- return null; -- } -- -- @Override -- public T reduce(T identity, java.util.function.BinaryOperator accumulator) { -- return null; -- } -- -- @org.jetbrains.annotations.NotNull -- @Override -- public Optional reduce(java.util.function.BinaryOperator accumulator) { -- return java.util.Optional.empty(); -- } -- -- @Override -- public U reduce(U identity, java.util.function.BiFunction accumulator, java.util.function.BinaryOperator combiner) { -- return null; -- } -- -- @Override -- public R collect(java.util.function.Supplier supplier, java.util.function.BiConsumer accumulator, java.util.function.BiConsumer combiner) { -- return null; -- } -- -- @Override -- public R collect(java.util.stream.Collector collector) { -- return null; -- } -- -- @org.jetbrains.annotations.NotNull -- @Override -- public Optional min(java.util.Comparator comparator) { -- return java.util.Optional.empty(); -- } -- -- @org.jetbrains.annotations.NotNull -- @Override -- public Optional max(java.util.Comparator comparator) { -- return java.util.Optional.empty(); -- } -- -- @Override -- public long count() { -- return 0; -- } -- -- @Override -- public boolean anyMatch(java.util.function.Predicate predicate) { -- return false; -- } -- -- @Override -- public boolean allMatch(java.util.function.Predicate predicate) { -- return false; -- } -- -- @org.jetbrains.annotations.NotNull -- @Override -- public Optional findFirst() { -- return java.util.Optional.empty(); -- } -- -- @org.jetbrains.annotations.NotNull -- @Override -- public Optional findAny() { -- return java.util.Optional.empty(); -- } -- -- -- @org.jetbrains.annotations.NotNull -- @Override -- public Iterator iterator() { -- return null; -- } -- -- @org.jetbrains.annotations.NotNull -- @Override -- public Spliterator spliterator() { -- return null; -- } -- -- @Override -- public boolean isParallel() { -- return false; -- } -- -- @org.jetbrains.annotations.NotNull -- @Override -- public Stream sequential() { -- return null; -- } -- -- @org.jetbrains.annotations.NotNull -- @Override -- public Stream parallel() { -- return null; -- } -- -- @org.jetbrains.annotations.NotNull -- @Override -- public Stream unordered() { -- return null; -- } -- -- @org.jetbrains.annotations.NotNull -- @Override -- public Stream onClose(Runnable closeHandler) { -- return null; -- } -- -- @Override -- public void close() { -- -- } --} -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/FluidOcclusionCacheKey.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/FluidOcclusionCacheKey.java -index 128267ff40b38c7b3ea0feb5133825cc6aae075b..cf9ffdeff6bf0b62a45f7a44dbfe0dd7d17dc4f4 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/FluidOcclusionCacheKey.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/FluidOcclusionCacheKey.java -@@ -1,4 +1,7 @@ - package ca.spottedleaf.moonrise.patches.collisions.util; - --public record FluidOcclusionCacheKey(net.minecraft.world.level.block.state.BlockState first, net.minecraft.world.level.block.state.BlockState second, net.minecraft.core.Direction direction, boolean result) { -+import net.minecraft.core.Direction; -+import net.minecraft.world.level.block.state.BlockState; -+ -+public record FluidOcclusionCacheKey(BlockState first, BlockState second, Direction direction, boolean result) { - } -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/world/CollisionLevel.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/world/CollisionLevel.java -deleted file mode 100644 -index e851e81e13edbad6316df63fcb7095d48f85c5b0..0000000000000000000000000000000000000000 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/world/CollisionLevel.java -+++ /dev/null -@@ -1,9 +0,0 @@ --package ca.spottedleaf.moonrise.patches.collisions.world; -- --public interface CollisionLevel { -- -- public int moonrise$getMinSection(); -- -- public int moonrise$getMaxSection(); -- --} -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/entity_tracker/EntityTrackerTrackedEntity.java b/src/main/java/ca/spottedleaf/moonrise/patches/entity_tracker/EntityTrackerTrackedEntity.java -index 1fa07bef57d82c6d5242aaaf66011f0913515231..8e7472157a98de607c03769a91f64c8369fd3ea6 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/entity_tracker/EntityTrackerTrackedEntity.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/entity_tracker/EntityTrackerTrackedEntity.java -@@ -10,4 +10,6 @@ public interface EntityTrackerTrackedEntity { - - public void moonrise$clearPlayers(); - -+ public boolean moonrise$hasPlayers(); -+ - } -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/fast_palette/FastPalette.java b/src/main/java/ca/spottedleaf/moonrise/patches/fast_palette/FastPalette.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4a7abd239a9c59aa98947e7993962d75e9051902 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/fast_palette/FastPalette.java -@@ -0,0 +1,9 @@ -+package ca.spottedleaf.moonrise.patches.fast_palette; -+ -+public interface FastPalette { -+ -+ public default T[] moonrise$getRawPalette(final FastPaletteData src) { -+ return null; -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/fast_palette/FastPaletteData.java b/src/main/java/ca/spottedleaf/moonrise/patches/fast_palette/FastPaletteData.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4503f3495846a7d7ed082b9e24636044e4fbccd1 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/fast_palette/FastPaletteData.java -@@ -0,0 +1,9 @@ -+package ca.spottedleaf.moonrise.patches.fast_palette; -+ -+public interface FastPaletteData { -+ -+ public T[] moonrise$getPalette(); -+ -+ public void moonrise$setPalette(final T[] palette); -+ -+} -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/fluid/FluidFluidState.java b/src/main/java/ca/spottedleaf/moonrise/patches/fluid/FluidFluidState.java -new file mode 100644 -index 0000000000000000000000000000000000000000..107c97089354edd35f330582f5e0c8a18e792a6e ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/fluid/FluidFluidState.java -@@ -0,0 +1,5 @@ -+package ca.spottedleaf.moonrise.patches.fluid; -+ -+public interface FluidFluidState { -+ public void moonrise$initCaches(); -+} -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_getblock/GetBlockChunk.java b/src/main/java/ca/spottedleaf/moonrise/patches/getblock/GetBlockChunk.java -similarity index 75% -rename from src/main/java/ca/spottedleaf/moonrise/patches/chunk_getblock/GetBlockChunk.java -rename to src/main/java/ca/spottedleaf/moonrise/patches/getblock/GetBlockChunk.java -index 08338917dc61c856eaba0b76e05c1497c458399d..540c14a6d2c216cd3ef2a9c4056e15712bf8cb8c 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_getblock/GetBlockChunk.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/getblock/GetBlockChunk.java -@@ -1,4 +1,4 @@ --package ca.spottedleaf.moonrise.patches.chunk_getblock; -+package ca.spottedleaf.moonrise.patches.getblock; - - import net.minecraft.world.level.block.state.BlockState; - -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/blockstate/StarlightAbstractBlockState.java b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/blockstate/StarlightAbstractBlockState.java -index 2bfdf3721db9a45e36538d71cbefcb1d339e6c58..8e6d79b7c10ef25f5478b72c53c555423d615a2f 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/blockstate/StarlightAbstractBlockState.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/blockstate/StarlightAbstractBlockState.java -@@ -4,6 +4,4 @@ public interface StarlightAbstractBlockState { - - public boolean starlight$isConditionallyFullOpaque(); - -- public int starlight$getOpacityIfCached(); -- - } -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/BlockStarLightEngine.java b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/BlockStarLightEngine.java -index 154443ac1ee1d6d18b8ff0f40a307d638b213aeb..fa7b784a89626e8528c249d7889a598bd7ee3d49 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/BlockStarLightEngine.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/BlockStarLightEngine.java -@@ -1,8 +1,10 @@ - package ca.spottedleaf.moonrise.patches.starlight.light; - -+import ca.spottedleaf.moonrise.common.PlatformHooks; - import ca.spottedleaf.moonrise.patches.starlight.blockstate.StarlightAbstractBlockState; - import ca.spottedleaf.moonrise.patches.starlight.chunk.StarlightChunk; - import net.minecraft.core.BlockPos; -+import net.minecraft.world.level.BlockGetter; - import net.minecraft.world.level.Level; - import net.minecraft.world.level.block.state.BlockState; - import net.minecraft.world.level.chunk.ChunkAccess; -@@ -91,7 +93,7 @@ public final class BlockStarLightEngine extends StarLightEngine { - - final int currentLevel = this.getLightLevel(worldX, worldY, worldZ); - final BlockState blockState = this.getBlockState(worldX, worldY, worldZ); -- final int emittedLevel = blockState.getLightEmission() & emittedMask; -+ final int emittedLevel = (PlatformHooks.get().getLightEmission(blockState, lightAccess.getLevel(), this.lightEmissionPos.set(worldX, worldY, worldZ))) & emittedMask; - - this.setLightLevel(worldX, worldY, worldZ, emittedLevel); - // this accounts for change in emitted light that would cause an increase -@@ -119,37 +121,32 @@ public final class BlockStarLightEngine extends StarLightEngine { - } - - protected final BlockPos.MutableBlockPos recalcCenterPos = new BlockPos.MutableBlockPos(); -- protected final BlockPos.MutableBlockPos recalcNeighbourPos = new BlockPos.MutableBlockPos(); - - @Override - protected int calculateLightValue(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ, - final int expect) { -+ this.recalcCenterPos.set(worldX, worldY, worldZ); -+ - final BlockState centerState = this.getBlockState(worldX, worldY, worldZ); -- int level = centerState.getLightEmission() & 0xF; -+ final BlockGetter world = lightAccess.getLevel(); -+ int level = (PlatformHooks.get().getLightEmission(centerState, world, this.recalcCenterPos)) & this.emittedLightMask; - - if (level >= (15 - 1) || level > expect) { - return level; - } - -- final int sectionOffset = this.chunkSectionIndexOffset; -- final BlockState conditionallyOpaqueState; -- int opacity = ((StarlightAbstractBlockState)centerState).starlight$getOpacityIfCached(); -- -- if (opacity == -1) { -- this.recalcCenterPos.set(worldX, worldY, worldZ); -- opacity = centerState.getLightBlock(lightAccess.getLevel(), this.recalcCenterPos); -- if (((StarlightAbstractBlockState)centerState).starlight$isConditionallyFullOpaque()) { -- conditionallyOpaqueState = centerState; -- } else { -- conditionallyOpaqueState = null; -- } -- } else if (opacity >= 15) { -+ final int opacity = Math.max(1, centerState.getLightBlock()); -+ if (opacity >= 15) { - return level; -+ } -+ final BlockState conditionallyOpaqueState; -+ if (((StarlightAbstractBlockState)centerState).starlight$isConditionallyFullOpaque()) { -+ conditionallyOpaqueState = centerState; - } else { - conditionallyOpaqueState = null; - } -- opacity = Math.max(1, opacity); - -+ final int sectionOffset = this.chunkSectionIndexOffset; - for (final AxisDirection direction : AXIS_DIRECTIONS) { - final int offX = worldX + direction.x; - final int offY = worldY + direction.y; -@@ -169,9 +166,8 @@ public final class BlockStarLightEngine extends StarLightEngine { - // here the block can be conditionally opaque (i.e light cannot propagate from it), so we need to test that - // we don't read the blockstate because most of the time this is false, so using the faster - // known transparency lookup results in a net win -- this.recalcNeighbourPos.set(offX, offY, offZ); -- final VoxelShape neighbourFace = neighbourState.getFaceOcclusionShape(lightAccess.getLevel(), this.recalcNeighbourPos, direction.opposite.nms); -- final VoxelShape thisFace = conditionallyOpaqueState == null ? Shapes.empty() : conditionallyOpaqueState.getFaceOcclusionShape(lightAccess.getLevel(), this.recalcCenterPos, direction.nms); -+ final VoxelShape neighbourFace = neighbourState.getFaceOcclusionShape(direction.opposite.nms); -+ final VoxelShape thisFace = conditionallyOpaqueState == null ? Shapes.empty() : conditionallyOpaqueState.getFaceOcclusionShape(direction.nms); - if (Shapes.faceShapeOccludes(thisFace, neighbourFace)) { - // not allowed to propagate - continue; -@@ -205,30 +201,34 @@ public final class BlockStarLightEngine extends StarLightEngine { - final int offX = chunk.getPos().x << 4; - final int offZ = chunk.getPos().z << 4; - -+ final PlatformHooks platformHooks = PlatformHooks.get(); -+ -+ final BlockGetter world = lightAccess.getLevel(); - final LevelChunkSection[] sections = chunk.getSections(); - for (int sectionY = this.minSection; sectionY <= this.maxSection; ++sectionY) { - final LevelChunkSection section = sections[sectionY - this.minSection]; -- if (section == null || section.hasOnlyAir()) { -+ if (section.hasOnlyAir()) { - // no sources in empty sections - continue; - } -- if (!section.maybeHas((final BlockState state) -> { -- return state.getLightEmission() > 0; -- })) { -+ if (!section.maybeHas(platformHooks.maybeHasLightEmission())) { - // no light sources in palette - continue; - } - final PalettedContainer states = section.states; - final int offY = sectionY << 4; - -+ final BlockPos.MutableBlockPos mutablePos = this.lightEmissionPos; - for (int index = 0; index < (16 * 16 * 16); ++index) { - final BlockState state = states.get(index); -- if (state.getLightEmission() <= 0) { -+ mutablePos.set(offX | (index & 15), offY | (index >>> 8), offZ | ((index >>> 4) & 15)); -+ -+ if ((platformHooks.getLightEmission(state, world, mutablePos)) == 0) { - continue; - } - - // index = x | (z << 4) | (y << 8) -- sources.add(new BlockPos(offX | (index & 15), offY | (index >>> 8), offZ | ((index >>> 4) & 15))); -+ sources.add(mutablePos.immutable()); - } - } - -@@ -238,12 +238,15 @@ public final class BlockStarLightEngine extends StarLightEngine { - @Override - public void lightChunk(final LightChunkGetter lightAccess, final ChunkAccess chunk, final boolean needsEdgeChecks) { - // setup sources -+ final BlockGetter world = lightAccess.getLevel(); -+ final PlatformHooks platformHooks = PlatformHooks.get(); -+ - final int emittedMask = this.emittedLightMask; - final List positions = this.getSources(lightAccess, chunk); - for (int i = 0, len = positions.size(); i < len; ++i) { - final BlockPos pos = positions.get(i); - final BlockState blockState = this.getBlockState(pos.getX(), pos.getY(), pos.getZ()); -- final int emittedLight = blockState.getLightEmission() & emittedMask; -+ final int emittedLight = platformHooks.getLightEmission(blockState, world, pos) & emittedMask; - - if (emittedLight <= this.getLightLevel(pos.getX(), pos.getY(), pos.getZ())) { - // some other source is brighter -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/SkyStarLightEngine.java b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/SkyStarLightEngine.java -index fdbc015f498164c9d2c578cd84a73def568142a4..f9aef289e9a2d6f63c98c72c56ef32b8793f57f4 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/SkyStarLightEngine.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/SkyStarLightEngine.java -@@ -290,9 +290,6 @@ public final class SkyStarLightEngine extends StarLightEngine { - ); - } - -- protected final BlockPos.MutableBlockPos recalcCenterPos = new BlockPos.MutableBlockPos(); -- protected final BlockPos.MutableBlockPos recalcNeighbourPos = new BlockPos.MutableBlockPos(); -- - @Override - protected int calculateLightValue(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ, - final int expect) { -@@ -302,20 +299,13 @@ public final class SkyStarLightEngine extends StarLightEngine { - - final int sectionOffset = this.chunkSectionIndexOffset; - final BlockState centerState = this.getBlockState(worldX, worldY, worldZ); -- int opacity = ((StarlightAbstractBlockState)centerState).starlight$getOpacityIfCached(); - - final BlockState conditionallyOpaqueState; -- if (opacity < 0) { -- this.recalcCenterPos.set(worldX, worldY, worldZ); -- opacity = Math.max(1, centerState.getLightBlock(lightAccess.getLevel(), this.recalcCenterPos)); -- if (((StarlightAbstractBlockState)centerState).starlight$isConditionallyFullOpaque()) { -- conditionallyOpaqueState = centerState; -- } else { -- conditionallyOpaqueState = null; -- } -+ final int opacity = Math.max(1, centerState.getLightBlock()); -+ if (((StarlightAbstractBlockState)centerState).starlight$isConditionallyFullOpaque()) { -+ conditionallyOpaqueState = centerState; - } else { - conditionallyOpaqueState = null; -- opacity = Math.max(1, opacity); - } - - int level = 0; -@@ -340,9 +330,8 @@ public final class SkyStarLightEngine extends StarLightEngine { - // here the block can be conditionally opaque (i.e light cannot propagate from it), so we need to test that - // we don't read the blockstate because most of the time this is false, so using the faster - // known transparency lookup results in a net win -- this.recalcNeighbourPos.set(offX, offY, offZ); -- final VoxelShape neighbourFace = neighbourState.getFaceOcclusionShape(lightAccess.getLevel(), this.recalcNeighbourPos, direction.opposite.nms); -- final VoxelShape thisFace = conditionallyOpaqueState == null ? Shapes.empty() : conditionallyOpaqueState.getFaceOcclusionShape(lightAccess.getLevel(), this.recalcCenterPos, direction.nms); -+ final VoxelShape neighbourFace = neighbourState.getFaceOcclusionShape(direction.opposite.nms); -+ final VoxelShape thisFace = conditionallyOpaqueState == null ? Shapes.empty() : conditionallyOpaqueState.getFaceOcclusionShape(direction.nms); - if (Shapes.faceShapeOccludes(thisFace, neighbourFace)) { - // not allowed to propagate - continue; -@@ -610,7 +599,6 @@ public final class SkyStarLightEngine extends StarLightEngine { - // clobbering the light values will result in broken propagation) - protected final int tryPropagateSkylight(final BlockGetter world, final int worldX, int startY, final int worldZ, - final boolean extrudeInitialised, final boolean delayLightSet) { -- final BlockPos.MutableBlockPos mutablePos = this.mutablePos3; - final int encodeOffset = this.coordinateOffset; - final long propagateDirection = AxisDirection.POSITIVE_Y.everythingButThisDirection; // just don't check upwards. - -@@ -632,8 +620,7 @@ public final class SkyStarLightEngine extends StarLightEngine { - - final VoxelShape fromShape; - if (((StarlightAbstractBlockState)above).starlight$isConditionallyFullOpaque()) { -- this.mutablePos2.set(worldX, startY + 1, worldZ); -- fromShape = above.getFaceOcclusionShape(world, this.mutablePos2, AxisDirection.NEGATIVE_Y.nms); -+ fromShape = above.getFaceOcclusionShape(AxisDirection.NEGATIVE_Y.nms); - if (Shapes.faceShapeOccludes(Shapes.empty(), fromShape)) { - // above wont let us propagate - break; -@@ -642,49 +629,32 @@ public final class SkyStarLightEngine extends StarLightEngine { - fromShape = Shapes.empty(); - } - -- final int opacityIfCached = ((StarlightAbstractBlockState)current).starlight$getOpacityIfCached(); - // does light propagate from the top down? -- if (opacityIfCached != -1) { -- if (opacityIfCached != 0) { -- // we cannot propagate 15 through this -- break; -- } -- // most of the time it falls here. -- // add to propagate -- // light set delayed until we determine if this nibble section is null -- this.appendToIncreaseQueue( -- ((worldX + (worldZ << 6) + (startY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -- | (15L << (6 + 6 + 16)) // we know we're at full lit here -- | (propagateDirection << (6 + 6 + 16 + 4)) -- ); -- } else { -- mutablePos.set(worldX, startY, worldZ); -- long flags = 0L; -- if (((StarlightAbstractBlockState)current).starlight$isConditionallyFullOpaque()) { -- final VoxelShape cullingFace = current.getFaceOcclusionShape(world, mutablePos, AxisDirection.POSITIVE_Y.nms); -+ long flags = 0L; -+ if (((StarlightAbstractBlockState)current).starlight$isConditionallyFullOpaque()) { -+ final VoxelShape cullingFace = current.getFaceOcclusionShape(AxisDirection.POSITIVE_Y.nms); - -- if (Shapes.faceShapeOccludes(fromShape, cullingFace)) { -- // can't propagate here, we're done on this column. -- break; -- } -- flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; -- } -- -- final int opacity = current.getLightBlock(world, mutablePos); -- if (opacity > 0) { -- // let the queued value (if any) handle it from here. -+ if (Shapes.faceShapeOccludes(fromShape, cullingFace)) { -+ // can't propagate here, we're done on this column. - break; - } -+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; -+ } - -- // light set delayed until we determine if this nibble section is null -- this.appendToIncreaseQueue( -- ((worldX + (worldZ << 6) + (startY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -- | (15L << (6 + 6 + 16)) // we know we're at full lit here -- | (propagateDirection << (6 + 6 + 16 + 4)) -- | flags -- ); -+ final int opacity = current.getLightBlock(); -+ if (opacity > 0) { -+ // let the queued value (if any) handle it from here. -+ break; - } - -+ // light set delayed until we determine if this nibble section is null -+ this.appendToIncreaseQueue( -+ ((worldX + (worldZ << 6) + (startY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (15L << (6 + 6 + 16)) // we know we're at full lit here -+ | (propagateDirection << (6 + 6 + 16 + 4)) -+ | flags -+ ); -+ - above = current; - - if (this.getNibbleFromCache(worldX >> 4, startY >> 4, worldZ >> 4) == null) { -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/StarLightEngine.java b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/StarLightEngine.java -index 382c9e445af0d6ad2428fc22d0f63017c58191e2..8aeb5fb87f94a35659347a09a638420699b52a6f 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/StarLightEngine.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/StarLightEngine.java -@@ -1,6 +1,7 @@ - package ca.spottedleaf.moonrise.patches.starlight.light; - - import ca.spottedleaf.concurrentutil.util.IntegerUtil; -+import ca.spottedleaf.moonrise.common.PlatformHooks; - import ca.spottedleaf.moonrise.common.util.CoordinateUtils; - import ca.spottedleaf.moonrise.common.util.WorldUtil; - import ca.spottedleaf.moonrise.patches.starlight.blockstate.StarlightAbstractBlockState; -@@ -43,9 +44,9 @@ public abstract class StarLightEngine { - protected static enum AxisDirection { - - // Declaration order is important and relied upon. Do not change without modifying propagation code. -- POSITIVE_X(1, 0, 0), NEGATIVE_X(-1, 0, 0), -- POSITIVE_Z(0, 0, 1), NEGATIVE_Z(0, 0, -1), -- POSITIVE_Y(0, 1, 0), NEGATIVE_Y(0, -1, 0); -+ POSITIVE_X(1, 0, 0, Direction.EAST) , NEGATIVE_X(-1, 0, 0, Direction.WEST), -+ POSITIVE_Z(0, 0, 1, Direction.SOUTH), NEGATIVE_Z(0, 0, -1, Direction.NORTH), -+ POSITIVE_Y(0, 1, 0, Direction.UP) , NEGATIVE_Y(0, -1, 0, Direction.DOWN); - - static { - POSITIVE_X.opposite = NEGATIVE_X; NEGATIVE_X.opposite = POSITIVE_X; -@@ -62,11 +63,11 @@ public abstract class StarLightEngine { - public final long everythingButThisDirection; - public final long everythingButTheOppositeDirection; - -- AxisDirection(final int x, final int y, final int z) { -+ AxisDirection(final int x, final int y, final int z, final Direction nms) { - this.x = x; - this.y = y; - this.z = z; -- this.nms = Direction.fromDelta(x, y, z); -+ this.nms = nms; - this.everythingButThisDirection = (long)(ALL_DIRECTIONS_BITSET ^ (1 << this.ordinal())); - // positive is always even, negative is always odd. Flip the 1 bit to get the negative direction. - this.everythingButTheOppositeDirection = (long)(ALL_DIRECTIONS_BITSET ^ (1 << (this.ordinal() ^ 1))); -@@ -106,9 +107,7 @@ public abstract class StarLightEngine { - // index = x + (z * 5) - protected final boolean[][] emptinessMapCache = new boolean[5 * 5][]; - -- protected final BlockPos.MutableBlockPos mutablePos1 = new BlockPos.MutableBlockPos(); -- protected final BlockPos.MutableBlockPos mutablePos2 = new BlockPos.MutableBlockPos(); -- protected final BlockPos.MutableBlockPos mutablePos3 = new BlockPos.MutableBlockPos(); -+ protected final BlockPos.MutableBlockPos lightEmissionPos = new BlockPos.MutableBlockPos(); - - protected int encodeOffsetX; - protected int encodeOffsetY; -@@ -1150,69 +1149,46 @@ public abstract class StarLightEngine { - if (blockState == null) { - continue; - } -- final int opacityCached = ((StarlightAbstractBlockState)blockState).starlight$getOpacityIfCached(); -- if (opacityCached != -1) { -- final int targetLevel = propagatedLightLevel - Math.max(1, opacityCached); -- if (targetLevel > currentLevel) { -- currentNibble.set(localIndex, targetLevel); -- this.postLightUpdate(offX, offY, offZ); -- -- if (targetLevel > 1) { -- if (queueLength >= queue.length) { -- queue = this.resizeIncreaseQueue(); -- } -- queue[queueLength++] = -- ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -- | ((targetLevel & 0xFL) << (6 + 6 + 16)) -- | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)); -- continue; -- } -- } -- continue; -- } else { -- this.mutablePos1.set(offX, offY, offZ); -- long flags = 0; -- if (((StarlightAbstractBlockState)blockState).starlight$isConditionallyFullOpaque()) { -- final VoxelShape cullingFace = blockState.getFaceOcclusionShape(world, this.mutablePos1, propagate.getOpposite().nms); -+ long flags = 0; -+ if (((StarlightAbstractBlockState)blockState).starlight$isConditionallyFullOpaque()) { -+ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(propagate.getOpposite().nms); - -- if (Shapes.faceShapeOccludes(Shapes.empty(), cullingFace)) { -- continue; -- } -- flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; -- } -- -- final int opacity = blockState.getLightBlock(world, this.mutablePos1); -- final int targetLevel = propagatedLightLevel - Math.max(1, opacity); -- if (targetLevel <= currentLevel) { -+ if (Shapes.faceShapeOccludes(Shapes.empty(), cullingFace)) { - continue; - } -+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; -+ } - -- currentNibble.set(localIndex, targetLevel); -- this.postLightUpdate(offX, offY, offZ); -+ final int opacity = blockState.getLightBlock(); -+ final int targetLevel = propagatedLightLevel - Math.max(1, opacity); -+ if (targetLevel <= currentLevel) { -+ continue; -+ } - -- if (targetLevel > 1) { -- if (queueLength >= queue.length) { -- queue = this.resizeIncreaseQueue(); -- } -- queue[queueLength++] = -- ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -- | ((targetLevel & 0xFL) << (6 + 6 + 16)) -- | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)) -- | (flags); -+ currentNibble.set(localIndex, targetLevel); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 1) { -+ if (queueLength >= queue.length) { -+ queue = this.resizeIncreaseQueue(); - } -- continue; -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)) -+ | (flags); - } -+ continue; - } - } else { - // we actually need to worry about our state here - final BlockState fromBlock = this.getBlockState(posX, posY, posZ); -- this.mutablePos2.set(posX, posY, posZ); - for (final AxisDirection propagate : checkDirections) { - final int offX = posX + propagate.x; - final int offY = posY + propagate.y; - final int offZ = posZ + propagate.z; - -- final VoxelShape fromShape = (((StarlightAbstractBlockState)fromBlock).starlight$isConditionallyFullOpaque()) ? fromBlock.getFaceOcclusionShape(world, this.mutablePos2, propagate.nms) : Shapes.empty(); -+ final VoxelShape fromShape = (((StarlightAbstractBlockState)fromBlock).starlight$isConditionallyFullOpaque()) ? fromBlock.getFaceOcclusionShape(propagate.nms) : Shapes.empty(); - - if (fromShape != Shapes.empty() && Shapes.faceShapeOccludes(Shapes.empty(), fromShape)) { - continue; -@@ -1232,58 +1208,36 @@ public abstract class StarLightEngine { - if (blockState == null) { - continue; - } -- final int opacityCached = ((StarlightAbstractBlockState)blockState).starlight$getOpacityIfCached(); -- if (opacityCached != -1) { -- final int targetLevel = propagatedLightLevel - Math.max(1, opacityCached); -- if (targetLevel > currentLevel) { -- currentNibble.set(localIndex, targetLevel); -- this.postLightUpdate(offX, offY, offZ); -- -- if (targetLevel > 1) { -- if (queueLength >= queue.length) { -- queue = this.resizeIncreaseQueue(); -- } -- queue[queueLength++] = -- ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -- | ((targetLevel & 0xFL) << (6 + 6 + 16)) -- | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)); -- continue; -- } -- } -- continue; -- } else { -- this.mutablePos1.set(offX, offY, offZ); -- long flags = 0; -- if (((StarlightAbstractBlockState)blockState).starlight$isConditionallyFullOpaque()) { -- final VoxelShape cullingFace = blockState.getFaceOcclusionShape(world, this.mutablePos1, propagate.getOpposite().nms); -- -- if (Shapes.faceShapeOccludes(fromShape, cullingFace)) { -- continue; -- } -- flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; -- } -+ long flags = 0; -+ if (((StarlightAbstractBlockState)blockState).starlight$isConditionallyFullOpaque()) { -+ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(propagate.getOpposite().nms); - -- final int opacity = blockState.getLightBlock(world, this.mutablePos1); -- final int targetLevel = propagatedLightLevel - Math.max(1, opacity); -- if (targetLevel <= currentLevel) { -+ if (Shapes.faceShapeOccludes(fromShape, cullingFace)) { - continue; - } -+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; -+ } - -- currentNibble.set(localIndex, targetLevel); -- this.postLightUpdate(offX, offY, offZ); -+ final int opacity = blockState.getLightBlock(); -+ final int targetLevel = propagatedLightLevel - Math.max(1, opacity); -+ if (targetLevel <= currentLevel) { -+ continue; -+ } - -- if (targetLevel > 1) { -- if (queueLength >= queue.length) { -- queue = this.resizeIncreaseQueue(); -- } -- queue[queueLength++] = -- ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -- | ((targetLevel & 0xFL) << (6 + 6 + 16)) -- | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)) -- | (flags); -+ currentNibble.set(localIndex, targetLevel); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 1) { -+ if (queueLength >= queue.length) { -+ queue = this.resizeIncreaseQueue(); - } -- continue; -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)) -+ | (flags); - } -+ continue; - } - } - } -@@ -1304,6 +1258,8 @@ public abstract class StarLightEngine { - final int sectionOffset = this.chunkSectionIndexOffset; - final int emittedMask = this.emittedLightMask; - -+ final PlatformHooks platformHooks = PlatformHooks.get(); -+ - while (queueReadIndex < queueLength) { - final long queueValue = queue[queueReadIndex++]; - -@@ -1335,109 +1291,63 @@ public abstract class StarLightEngine { - if (blockState == null) { - continue; - } -- final int opacityCached = ((StarlightAbstractBlockState)blockState).starlight$getOpacityIfCached(); -- if (opacityCached != -1) { -- final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacityCached)); -- if (lightLevel > targetLevel) { -- // it looks like another source propagated here, so re-propagate it -- if (increaseQueueLength >= increaseQueue.length) { -- increaseQueue = this.resizeIncreaseQueue(); -- } -- increaseQueue[increaseQueueLength++] = -- ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -- | ((lightLevel & 0xFL) << (6 + 6 + 16)) -- | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -- | FLAG_RECHECK_LEVEL; -- continue; -- } -- final int emittedLight = blockState.getLightEmission() & emittedMask; -- if (emittedLight != 0) { -- // re-propagate source -- // note: do not set recheck level, or else the propagation will fail -- if (increaseQueueLength >= increaseQueue.length) { -- increaseQueue = this.resizeIncreaseQueue(); -- } -- increaseQueue[increaseQueueLength++] = -- ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -- | ((emittedLight & 0xFL) << (6 + 6 + 16)) -- | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -- | (((StarlightAbstractBlockState)blockState).starlight$isConditionallyFullOpaque() ? (FLAG_WRITE_LEVEL | FLAG_HAS_SIDED_TRANSPARENT_BLOCKS) : FLAG_WRITE_LEVEL); -- } -- -- currentNibble.set(localIndex, 0); -- this.postLightUpdate(offX, offY, offZ); -+ this.lightEmissionPos.set(offX, offY, offZ); -+ long flags = 0; -+ if (((StarlightAbstractBlockState)blockState).starlight$isConditionallyFullOpaque()) { -+ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(propagate.getOpposite().nms); - -- if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour... -- if (queueLength >= queue.length) { -- queue = this.resizeDecreaseQueue(); -- } -- queue[queueLength++] = -- ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -- | ((targetLevel & 0xFL) << (6 + 6 + 16)) -- | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)); -+ if (Shapes.faceShapeOccludes(Shapes.empty(), cullingFace)) { - continue; - } -- continue; -- } else { -- this.mutablePos1.set(offX, offY, offZ); -- long flags = 0; -- if (((StarlightAbstractBlockState)blockState).starlight$isConditionallyFullOpaque()) { -- final VoxelShape cullingFace = blockState.getFaceOcclusionShape(world, this.mutablePos1, propagate.getOpposite().nms); -- -- if (Shapes.faceShapeOccludes(Shapes.empty(), cullingFace)) { -- continue; -- } -- flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; -- } -+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; -+ } - -- final int opacity = blockState.getLightBlock(world, this.mutablePos1); -- final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacity)); -- if (lightLevel > targetLevel) { -- // it looks like another source propagated here, so re-propagate it -- if (increaseQueueLength >= increaseQueue.length) { -- increaseQueue = this.resizeIncreaseQueue(); -- } -- increaseQueue[increaseQueueLength++] = -- ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -- | ((lightLevel & 0xFL) << (6 + 6 + 16)) -- | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -- | (FLAG_RECHECK_LEVEL | flags); -- continue; -+ final int opacity = blockState.getLightBlock(); -+ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacity)); -+ if (lightLevel > targetLevel) { -+ // it looks like another source propagated here, so re-propagate it -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); - } -- final int emittedLight = blockState.getLightEmission() & emittedMask; -- if (emittedLight != 0) { -- // re-propagate source -- // note: do not set recheck level, or else the propagation will fail -- if (increaseQueueLength >= increaseQueue.length) { -- increaseQueue = this.resizeIncreaseQueue(); -- } -- increaseQueue[increaseQueueLength++] = -- ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -- | ((emittedLight & 0xFL) << (6 + 6 + 16)) -- | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -- | (flags | FLAG_WRITE_LEVEL); -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((lightLevel & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (FLAG_RECHECK_LEVEL | flags); -+ continue; -+ } -+ final int emittedLight = (platformHooks.getLightEmission(blockState, world, this.lightEmissionPos)) & emittedMask; -+ if (emittedLight != 0) { -+ // re-propagate source -+ // note: do not set recheck level, or else the propagation will fail -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); - } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((emittedLight & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (flags | FLAG_WRITE_LEVEL); -+ } - -- currentNibble.set(localIndex, 0); -- this.postLightUpdate(offX, offY, offZ); -+ currentNibble.set(localIndex, 0); -+ this.postLightUpdate(offX, offY, offZ); - -- if (targetLevel > 0) { -- if (queueLength >= queue.length) { -- queue = this.resizeDecreaseQueue(); -- } -- queue[queueLength++] = -- ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -- | ((targetLevel & 0xFL) << (6 + 6 + 16)) -- | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)) -- | flags; -+ if (targetLevel > 0) { -+ if (queueLength >= queue.length) { -+ queue = this.resizeDecreaseQueue(); - } -- continue; -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)) -+ | flags; - } -+ continue; - } - } else { - // we actually need to worry about our state here - final BlockState fromBlock = this.getBlockState(posX, posY, posZ); -- this.mutablePos2.set(posX, posY, posZ); - for (final AxisDirection propagate : checkDirections) { - final int offX = posX + propagate.x; - final int offY = posY + propagate.y; -@@ -1446,7 +1356,7 @@ public abstract class StarLightEngine { - final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; - final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8); - -- final VoxelShape fromShape = (((StarlightAbstractBlockState)fromBlock).starlight$isConditionallyFullOpaque()) ? fromBlock.getFaceOcclusionShape(world, this.mutablePos2, propagate.nms) : Shapes.empty(); -+ final VoxelShape fromShape = (((StarlightAbstractBlockState)fromBlock).starlight$isConditionallyFullOpaque()) ? fromBlock.getFaceOcclusionShape(propagate.nms) : Shapes.empty(); - - if (fromShape != Shapes.empty() && Shapes.faceShapeOccludes(Shapes.empty(), fromShape)) { - continue; -@@ -1464,104 +1374,59 @@ public abstract class StarLightEngine { - if (blockState == null) { - continue; - } -- final int opacityCached = ((StarlightAbstractBlockState)blockState).starlight$getOpacityIfCached(); -- if (opacityCached != -1) { -- final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacityCached)); -- if (lightLevel > targetLevel) { -- // it looks like another source propagated here, so re-propagate it -- if (increaseQueueLength >= increaseQueue.length) { -- increaseQueue = this.resizeIncreaseQueue(); -- } -- increaseQueue[increaseQueueLength++] = -- ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -- | ((lightLevel & 0xFL) << (6 + 6 + 16)) -- | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -- | FLAG_RECHECK_LEVEL; -- continue; -- } -- final int emittedLight = blockState.getLightEmission() & emittedMask; -- if (emittedLight != 0) { -- // re-propagate source -- // note: do not set recheck level, or else the propagation will fail -- if (increaseQueueLength >= increaseQueue.length) { -- increaseQueue = this.resizeIncreaseQueue(); -- } -- increaseQueue[increaseQueueLength++] = -- ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -- | ((emittedLight & 0xFL) << (6 + 6 + 16)) -- | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -- | (((StarlightAbstractBlockState)blockState).starlight$isConditionallyFullOpaque() ? (FLAG_WRITE_LEVEL | FLAG_HAS_SIDED_TRANSPARENT_BLOCKS) : FLAG_WRITE_LEVEL); -- } -- -- currentNibble.set(localIndex, 0); -- this.postLightUpdate(offX, offY, offZ); -+ this.lightEmissionPos.set(offX, offY, offZ); -+ long flags = 0; -+ if (((StarlightAbstractBlockState)blockState).starlight$isConditionallyFullOpaque()) { -+ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(propagate.getOpposite().nms); - -- if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour... -- if (queueLength >= queue.length) { -- queue = this.resizeDecreaseQueue(); -- } -- queue[queueLength++] = -- ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -- | ((targetLevel & 0xFL) << (6 + 6 + 16)) -- | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)); -+ if (Shapes.faceShapeOccludes(fromShape, cullingFace)) { - continue; - } -- continue; -- } else { -- this.mutablePos1.set(offX, offY, offZ); -- long flags = 0; -- if (((StarlightAbstractBlockState)blockState).starlight$isConditionallyFullOpaque()) { -- final VoxelShape cullingFace = blockState.getFaceOcclusionShape(world, this.mutablePos1, propagate.getOpposite().nms); -- -- if (Shapes.faceShapeOccludes(fromShape, cullingFace)) { -- continue; -- } -- flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; -- } -+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; -+ } - -- final int opacity = blockState.getLightBlock(world, this.mutablePos1); -- final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacity)); -- if (lightLevel > targetLevel) { -- // it looks like another source propagated here, so re-propagate it -- if (increaseQueueLength >= increaseQueue.length) { -- increaseQueue = this.resizeIncreaseQueue(); -- } -- increaseQueue[increaseQueueLength++] = -- ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -- | ((lightLevel & 0xFL) << (6 + 6 + 16)) -- | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -- | (FLAG_RECHECK_LEVEL | flags); -- continue; -+ final int opacity = blockState.getLightBlock(); -+ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacity)); -+ if (lightLevel > targetLevel) { -+ // it looks like another source propagated here, so re-propagate it -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); - } -- final int emittedLight = blockState.getLightEmission() & emittedMask; -- if (emittedLight != 0) { -- // re-propagate source -- // note: do not set recheck level, or else the propagation will fail -- if (increaseQueueLength >= increaseQueue.length) { -- increaseQueue = this.resizeIncreaseQueue(); -- } -- increaseQueue[increaseQueueLength++] = -- ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -- | ((emittedLight & 0xFL) << (6 + 6 + 16)) -- | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -- | (flags | FLAG_WRITE_LEVEL); -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((lightLevel & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (FLAG_RECHECK_LEVEL | flags); -+ continue; -+ } -+ final int emittedLight = (platformHooks.getLightEmission(blockState, world, this.lightEmissionPos)) & emittedMask; -+ if (emittedLight != 0) { -+ // re-propagate source -+ // note: do not set recheck level, or else the propagation will fail -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); - } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((emittedLight & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (flags | FLAG_WRITE_LEVEL); -+ } - -- currentNibble.set(localIndex, 0); -- this.postLightUpdate(offX, offY, offZ); -+ currentNibble.set(localIndex, 0); -+ this.postLightUpdate(offX, offY, offZ); - -- if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour... -- if (queueLength >= queue.length) { -- queue = this.resizeDecreaseQueue(); -- } -- queue[queueLength++] = -- ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -- | ((targetLevel & 0xFL) << (6 + 6 + 16)) -- | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)) -- | flags; -+ if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour... -+ if (queueLength >= queue.length) { -+ queue = this.resizeDecreaseQueue(); - } -- continue; -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)) -+ | flags; - } -+ continue; - } - } - } -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/StarLightInterface.java b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/StarLightInterface.java -index c64ab41198a5e0c7cbcbe6452af11f82f5938862..571db5f9bf94745a8afe2cd313e593fb15db5e37 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/StarLightInterface.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/StarLightInterface.java -@@ -1,8 +1,9 @@ - package ca.spottedleaf.moonrise.patches.starlight.light; - - import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; --import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor; - import ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable; -+import ca.spottedleaf.concurrentutil.util.Priority; - import ca.spottedleaf.moonrise.common.util.CoordinateUtils; - import ca.spottedleaf.moonrise.common.util.WorldUtil; - import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel; -@@ -745,34 +746,34 @@ public final class StarLightInterface { - super(lightInterface); - } - -- public void lowerPriority(final int chunkX, final int chunkZ, final PrioritisedExecutor.Priority priority) { -+ public void lowerPriority(final int chunkX, final int chunkZ, final Priority priority) { - final ServerChunkTasks task = this.chunkTasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); - if (task != null) { - task.lowerPriority(priority); - } - } - -- public void setPriority(final int chunkX, final int chunkZ, final PrioritisedExecutor.Priority priority) { -+ public void setPriority(final int chunkX, final int chunkZ, final Priority priority) { - final ServerChunkTasks task = this.chunkTasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); - if (task != null) { - task.setPriority(priority); - } - } - -- public void raisePriority(final int chunkX, final int chunkZ, final PrioritisedExecutor.Priority priority) { -+ public void raisePriority(final int chunkX, final int chunkZ, final Priority priority) { - final ServerChunkTasks task = this.chunkTasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); - if (task != null) { - task.raisePriority(priority); - } - } - -- public PrioritisedExecutor.Priority getPriority(final int chunkX, final int chunkZ) { -+ public Priority getPriority(final int chunkX, final int chunkZ) { - final ServerChunkTasks task = this.chunkTasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); - if (task != null) { - return task.getPriority(); - } - -- return PrioritisedExecutor.Priority.COMPLETING; -+ return Priority.COMPLETING; - } - - @Override -@@ -816,7 +817,7 @@ public final class StarLightInterface { - return ret; - } - -- public ServerChunkTasks queueChunkLightTask(final ChunkPos pos, final BooleanSupplier lightTask, final PrioritisedExecutor.Priority priority) { -+ public ServerChunkTasks queueChunkLightTask(final ChunkPos pos, final BooleanSupplier lightTask, final Priority priority) { - final ServerChunkTasks ret = this.chunkTasks.compute(CoordinateUtils.getChunkKey(pos), (final long keyInMap, ServerChunkTasks valueInMap) -> { - if (valueInMap == null) { - valueInMap = new ServerChunkTasks( -@@ -879,11 +880,11 @@ public final class StarLightInterface { - - public ServerChunkTasks(final long chunkCoordinate, final StarLightInterface lightEngine, - final ServerLightQueue queue) { -- this(chunkCoordinate, lightEngine, queue, PrioritisedExecutor.Priority.NORMAL); -+ this(chunkCoordinate, lightEngine, queue, Priority.NORMAL); - } - - public ServerChunkTasks(final long chunkCoordinate, final StarLightInterface lightEngine, -- final ServerLightQueue queue, final PrioritisedExecutor.Priority priority) { -+ final ServerLightQueue queue, final Priority priority) { - super(chunkCoordinate, lightEngine, queue); - this.task = ((ChunkSystemServerLevel)(ServerLevel)lightEngine.getWorld()).moonrise$getChunkTaskScheduler().radiusAwareScheduler.createTask( - CoordinateUtils.getChunkX(chunkCoordinate), CoordinateUtils.getChunkZ(chunkCoordinate), -@@ -903,19 +904,19 @@ public final class StarLightInterface { - return this.task.cancel(); - } - -- public PrioritisedExecutor.Priority getPriority() { -+ public Priority getPriority() { - return this.task.getPriority(); - } - -- public void lowerPriority(final PrioritisedExecutor.Priority priority) { -+ public void lowerPriority(final Priority priority) { - this.task.lowerPriority(priority); - } - -- public void setPriority(final PrioritisedExecutor.Priority priority) { -+ public void setPriority(final Priority priority) { - this.task.setPriority(priority); - } - -- public void raisePriority(final PrioritisedExecutor.Priority priority) { -+ public void raisePriority(final Priority priority) { - this.task.raisePriority(priority); - } - -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/storage/StarlightSectionData.java b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/storage/StarlightSectionData.java -new file mode 100644 -index 0000000000000000000000000000000000000000..40d004afdc6449530f5bb2d7c7638b8ee3e3a577 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/storage/StarlightSectionData.java -@@ -0,0 +1,13 @@ -+package ca.spottedleaf.moonrise.patches.starlight.storage; -+ -+public interface StarlightSectionData { -+ -+ public int starlight$getBlockLightState(); -+ -+ public void starlight$setBlockLightState(final int state); -+ -+ public int starlight$getSkyLightState(); -+ -+ public void starlight$setSkyLightState(final int state); -+ -+} -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/util/SaveUtil.java b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/util/SaveUtil.java -index 57692a503e147a00ac4e1586cd78e12b71a80d3f..689ce367164e79e0426eeecb81dbbc521d4bc742 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/util/SaveUtil.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/util/SaveUtil.java -@@ -14,19 +14,20 @@ import net.minecraft.world.level.chunk.ChunkAccess; - import net.minecraft.world.level.chunk.status.ChunkStatus; - import org.slf4j.Logger; - -+// note: keep in-sync with SerializableChunkDataMixin - public final class SaveUtil { - - private static final Logger LOGGER = LogUtils.getLogger(); - -- private static final int STARLIGHT_LIGHT_VERSION = 9; -+ public static final int STARLIGHT_LIGHT_VERSION = 9; - - public static int getLightVersion() { - return STARLIGHT_LIGHT_VERSION; - } - -- private static final String BLOCKLIGHT_STATE_TAG = "starlight.blocklight_state"; -- private static final String SKYLIGHT_STATE_TAG = "starlight.skylight_state"; -- private static final String STARLIGHT_VERSION_TAG = "starlight.light_version"; -+ public static final String BLOCKLIGHT_STATE_TAG = "starlight.blocklight_state"; -+ public static final String SKYLIGHT_STATE_TAG = "starlight.skylight_state"; -+ public static final String STARLIGHT_VERSION_TAG = "starlight.light_version"; - - public static void saveLightHook(final Level world, final ChunkAccess chunk, final CompoundTag nbt) { - try { -diff --git a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -index 9bd509915b391e9d382fe47798e2c345b6e59a9a..65c7e7a1b1a5472ee03149bb4ccd9f7d0229069b 100644 ---- a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -+++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -@@ -242,6 +242,7 @@ public class GlobalConfiguration extends ConfigurationPart { - - @PostProcess - private void postProcess() { -+ ca.spottedleaf.moonrise.common.util.MoonriseCommon.adjustWorkerThreads(this.workerThreads, this.ioThreads); - ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler.init(this); - } - } -diff --git a/src/main/java/net/minecraft/core/MappedRegistry.java b/src/main/java/net/minecraft/core/MappedRegistry.java -index 71e04e5c1bc0722abf8ca2e0738bd60b6d7ae21c..8e0dfaf02343d74ce786e4fc647bc4c1d73c0014 100644 ---- a/src/main/java/net/minecraft/core/MappedRegistry.java -+++ b/src/main/java/net/minecraft/core/MappedRegistry.java -@@ -50,6 +50,19 @@ public class MappedRegistry implements WritableRegistry { - return this.getTags(); - } - -+ // Paper start - fluid method optimisations -+ private void injectFluidRegister( -+ final ResourceKey resourceKey, -+ final T object -+ ) { -+ if (resourceKey.registryKey() == (Object)net.minecraft.core.registries.Registries.FLUID) { -+ for (final net.minecraft.world.level.material.FluidState possibleState : ((net.minecraft.world.level.material.Fluid)object).getStateDefinition().getPossibleStates()) { -+ ((ca.spottedleaf.moonrise.patches.fluid.FluidFluidState)(Object)possibleState).moonrise$initCaches(); -+ } -+ } -+ } -+ // Paper end - fluid method optimisations -+ - public MappedRegistry(ResourceKey> key, Lifecycle lifecycle) { - this(key, lifecycle, false); - } -@@ -114,6 +127,7 @@ public class MappedRegistry implements WritableRegistry { - this.toId.put(value, i); - this.registrationInfos.put(key, info); - this.registryLifecycle = this.registryLifecycle.add(info.lifecycle()); -+ this.injectFluidRegister(key, value); // Paper - fluid method optimisations - return reference; - } - } -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 7c388e230c4a88edf6212dd8990e8238d3265ebf..8a66012b7f2396031840c8c718f49f8aab716ee0 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1106,7 +1106,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop trackerEntities = entityLookup.trackerEntities; -@@ -921,21 +920,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - if (tracker == null) { - continue; - } -- ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity)tracker).moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition())); -- tracker.serverEntity.sendChanges(); -- } -- -- // process unloads -- final ca.spottedleaf.moonrise.common.list.ReferenceList unloadedEntities = entityLookup.trackerUnloadedEntities; -- final Entity[] unloadedEntitiesRaw = java.util.Arrays.copyOf(unloadedEntities.getRawDataUnchecked(), unloadedEntities.size()); -- unloadedEntities.clear(); -- -- for (final Entity entity : unloadedEntitiesRaw) { -- final ChunkMap.TrackedEntity tracker = ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity)entity).moonrise$getTrackedEntity(); -- if (tracker == null) { -- continue; -+ ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity)tracker).moonrise$tick(((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity)entity).moonrise$getChunkData().nearbyPlayers); -+ if (((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity)tracker).moonrise$hasPlayers() -+ || ((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity)entity).moonrise$getChunkStatus().isOrAfter(FullChunkStatus.ENTITY_TICKING)) { -+ tracker.serverEntity.sendChanges(); - } -- ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity)tracker).moonrise$clearPlayers(); - } - } - // Paper end - optimise entity tracker -@@ -1172,6 +1161,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - this.removePlayer(player); - } - } -+ -+ @Override -+ public final boolean moonrise$hasPlayers() { -+ return !this.seenBy.isEmpty(); -+ } - // Paper end - optimise entity tracker - - public TrackedEntity(final Entity entity, final int i, final int j, final boolean flag) { -@@ -1262,20 +1256,24 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - - private int getEffectiveRange() { -- int i = this.range; -- Iterator iterator = this.entity.getIndirectPassengers().iterator(); -+ // Paper start - optimise entity tracker -+ final Entity entity = this.entity; -+ int range = ca.spottedleaf.moonrise.common.PlatformHooks.get().modifyEntityTrackingRange(entity, this.range); - -- while (iterator.hasNext()) { -- Entity entity = (Entity) iterator.next(); -- int j = entity.getType().clientTrackingRange() * 16; -- j = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, j); // Paper -+ if (entity.getPassengers() == ImmutableList.of()) { -+ return this.scaledRange(range); -+ } - -- if (j > i) { -- i = j; -- } -+ // note: we change to List -+ final List passengers = (List)entity.getIndirectPassengers(); -+ for (int i = 0, len = passengers.size(); i < len; ++i) { -+ final Entity passenger = passengers.get(i); -+ // note: max should be branchless -+ range = Math.max(range, ca.spottedleaf.moonrise.common.PlatformHooks.get().modifyEntityTrackingRange(passenger, passenger.getType().clientTrackingRange() << 4)); - } - -- return this.scaledRange(i); -+ return this.scaledRange(range); -+ // Paper end - optimise entity tracker - } - - public void updatePlayers(List players) { -diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index 22157cbc4e4bb1e4010116bdf7429815d46bff2e..23a13bfd23514cde6dcf8d59ba3b43d84f266aad 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -99,7 +99,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon - final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler chunkTaskScheduler = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler(); - final CompletableFuture completable = new CompletableFuture<>(); - chunkTaskScheduler.scheduleChunkLoad( -- chunkX, chunkZ, toStatus, true, ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.BLOCKING, -+ chunkX, chunkZ, toStatus, true, ca.spottedleaf.concurrentutil.util.Priority.BLOCKING, - completable::complete - ); - -@@ -130,6 +130,15 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon - return ifPresent; - } - -+ final ca.spottedleaf.moonrise.common.PlatformHooks platformHooks = ca.spottedleaf.moonrise.common.PlatformHooks.get(); -+ -+ if (platformHooks.hasCurrentlyLoadingChunk() && currentChunk != null) { -+ final ChunkAccess loading = platformHooks.getCurrentlyLoadingChunk(currentChunk.vanillaChunkHolder); -+ if (loading != null && ca.spottedleaf.moonrise.common.util.TickThread.isTickThread()) { -+ return loading; -+ } -+ } -+ - return load ? this.syncLoad(chunkX, chunkZ, toStatus) : null; - } - // Paper end - rewrite chunk system -@@ -254,7 +263,24 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon - @Nullable - @Override - public LevelChunk getChunkNow(int chunkX, int chunkZ) { -- return this.fullChunks.get(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkX, chunkZ)); // Paper - rewrite chunk system -+ // Paper start - rewrite chunk system -+ final LevelChunk ret = this.fullChunks.get(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ if (!ca.spottedleaf.moonrise.common.PlatformHooks.get().hasCurrentlyLoadingChunk()) { -+ return ret; -+ } -+ -+ if (ret != null || !ca.spottedleaf.moonrise.common.util.TickThread.isTickThread()) { -+ return ret; -+ } -+ -+ final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder holder = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler() -+ .chunkHolderManager.getChunkHolder(chunkX, chunkZ); -+ if (holder == null) { -+ return ret; -+ } -+ -+ return ca.spottedleaf.moonrise.common.PlatformHooks.get().getCurrentlyLoadingChunk(holder.vanillaChunkHolder); -+ // Paper end - rewrite chunk system - } - - private void clearCache() { -@@ -311,7 +337,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon - - ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().scheduleChunkLoad( - chunkX, chunkZ, leastStatus, true, -- ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.HIGHER, -+ ca.spottedleaf.concurrentutil.util.Priority.HIGHER, - complete - ); - -@@ -549,7 +575,8 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon - - private void getFullChunk(long pos, Consumer chunkConsumer) { - // Paper start - rewrite chunk system -- final LevelChunk fullChunk = this.getChunkNow(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(pos), ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(pos)); -+ // note: bypass currentlyLoaded from getChunkNow -+ final LevelChunk fullChunk = this.fullChunks.get(pos); - if (fullChunk != null) { - chunkConsumer.accept(fullChunk); - } -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index af27a004f618912d6a5f1d2c82c8e7e9fb00a037..c7523387f0e9bbfe952abd237a936c8319f10200 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -775,11 +775,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - } - - // Paper start - optimise random ticking -+ private final ca.spottedleaf.moonrise.common.util.SimpleRandom simpleRandom = new ca.spottedleaf.moonrise.common.util.SimpleRandom(0L); -+ - private void optimiseRandomTick(final LevelChunk chunk, final int tickSpeed) { - final LevelChunkSection[] sections = chunk.getSections(); - final int minSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection((ServerLevel)(Object)this); -- final RandomSource random = this.random; -- final boolean tickFluids = false; // Paper - not configurable - MC-224294 -+ final ca.spottedleaf.moonrise.common.util.SimpleRandom simpleRandom = this.simpleRandom; -+ final boolean doubleTickFluids = !ca.spottedleaf.moonrise.common.PlatformHooks.get().configFixMC224294(); - - final ChunkPos cpos = chunk.getPos(); - final int offsetX = cpos.x << 4; -@@ -789,39 +791,32 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - final int offsetY = (sectionIndex + minSection) << 4; - final LevelChunkSection section = sections[sectionIndex]; - final net.minecraft.world.level.chunk.PalettedContainer states = section.states; -- if (section == null || !section.isRandomlyTickingBlocks()) { -+ if (!section.isRandomlyTickingBlocks()) { - continue; - } - -- final ca.spottedleaf.moonrise.common.list.IBlockDataList tickList = ((ca.spottedleaf.moonrise.patches.block_counting.BlockCountingChunkSection)section).moonrise$getTickingBlockList(); -- if (tickList.size() == 0) { -- continue; -- } -+ final ca.spottedleaf.moonrise.common.list.ShortList tickList = ((ca.spottedleaf.moonrise.patches.block_counting.BlockCountingChunkSection)section).moonrise$getTickingBlockList(); - - for (int i = 0; i < tickSpeed; ++i) { - final int tickingBlocks = tickList.size(); -- final int index = random.nextInt() & ((16 * 16 * 16) - 1); -+ final int index = simpleRandom.nextInt() & ((16 * 16 * 16) - 1); - - if (index >= tickingBlocks) { - // most of the time we fall here - continue; - } - -- final long raw = tickList.getRaw(index); -- final int location = ca.spottedleaf.moonrise.common.list.IBlockDataList.getLocationFromRaw(raw); -- final int randomX = (location & 15); -- final int randomY = ((location >>> (4 + 4)) & 255); -- final int randomZ = ((location >>> 4) & 15); -- final BlockState state = states.get(randomX | (randomZ << 4) | (randomY << 8)); -+ final int location = (int)tickList.getRaw(index) & 0xFFFF; -+ final BlockState state = states.get(location); - - // do not use a mutable pos, as some random tick implementations store the input without calling immutable()! -- final BlockPos pos = new BlockPos(randomX | offsetX, randomY | offsetY, randomZ | offsetZ); -+ final BlockPos pos = new BlockPos((location & 15) | offsetX, ((location >>> (4 + 4)) & 15) | offsetY, ((location >>> 4) & 15) | offsetZ); - -- state.randomTick((ServerLevel)(Object)this, pos, random); -- if (tickFluids) { -+ state.randomTick((ServerLevel)(Object)this, pos, simpleRandom); -+ if (doubleTickFluids) { - final FluidState fluidState = state.getFluidState(); - if (fluidState.isRandomlyTicking()) { -- fluidState.randomTick((ServerLevel)(Object)this, pos, random); -+ fluidState.randomTick((ServerLevel)(Object)this, pos, simpleRandom); - } - } - } -@@ -832,6 +827,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - // Paper end - optimise random ticking - - public void tickChunk(LevelChunk chunk, int randomTickSpeed) { -+ final ca.spottedleaf.moonrise.common.util.SimpleRandom simpleRandom = this.simpleRandom; // Paper - optimise random ticking - ChunkPos chunkcoordintpair = chunk.getPos(); - boolean flag = this.isRaining(); - int j = chunkcoordintpair.getMinBlockX(); -@@ -839,7 +835,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - ProfilerFiller gameprofilerfiller = Profiler.get(); - - gameprofilerfiller.push("thunder"); -- if (!this.paperConfig().environment.disableThunder && flag && this.isThundering() && this.spigotConfig.thunderChance > 0 && this.random.nextInt(this.spigotConfig.thunderChance) == 0) { // Spigot // Paper - Option to disable thunder -+ if (!this.paperConfig().environment.disableThunder && flag && this.isThundering() && this.spigotConfig.thunderChance > 0 && simpleRandom.nextInt(this.spigotConfig.thunderChance) == 0) { // Spigot // Paper - Option to disable thunder // Paper - optimise random ticking - BlockPos blockposition = this.findLightningTargetAround(this.getBlockRandomPos(j, 0, k, 15)); - - if (this.isRainingAt(blockposition)) { -@@ -871,7 +867,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - - if (!this.paperConfig().environment.disableIceAndSnow) { // Paper - Option to disable ice and snow - for (int l = 0; l < randomTickSpeed; ++l) { -- if (this.random.nextInt(48) == 0) { -+ if (simpleRandom.nextInt(48) == 0) { // Paper - optimise random ticking - this.tickPrecipitation(this.getBlockRandomPos(j, 0, k, 15)); - } - } -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 4fe3024e26b56c2d796acf703a1bc200ff309f09..7529b3d90e65036c7bf869af30475932d547b3ab 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -1407,7 +1407,7 @@ public abstract class PlayerList { - - public void setViewDistance(int viewDistance) { - this.viewDistance = viewDistance; -- this.broadcastAll(new ClientboundSetChunkCacheRadiusPacket(viewDistance)); -+ //this.broadcastAll(new ClientboundSetChunkCacheRadiusPacket(viewDistance)); // Paper - rewrite chunk system - Iterator iterator = this.server.getAllLevels().iterator(); - - while (iterator.hasNext()) { -diff --git a/src/main/java/net/minecraft/util/BitStorage.java b/src/main/java/net/minecraft/util/BitStorage.java -index 19661e106612b8e4e152085fb398db7bd06acc23..e4e153cb8899e70273aa150b8ea26907cf68b15c 100644 ---- a/src/main/java/net/minecraft/util/BitStorage.java -+++ b/src/main/java/net/minecraft/util/BitStorage.java -@@ -24,15 +24,15 @@ public interface BitStorage extends ca.spottedleaf.moonrise.patches.block_counti - // Paper start - block counting - // provide default impl in case mods implement this... - @Override -- public default it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap moonrise$countEntries() { -- final it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap ret = new it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap<>(); -+ public default it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap moonrise$countEntries() { -+ final it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap ret = new it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap<>(); - - final int size = this.getSize(); - for (int index = 0; index < size; ++index) { - final int paletteIdx = this.get(index); - ret.computeIfAbsent(paletteIdx, (final int key) -> { -- return new it.unimi.dsi.fastutil.ints.IntArrayList(); -- }).add(index); -+ return new it.unimi.dsi.fastutil.shorts.ShortArrayList(); -+ }).add((short)index); - } - - return ret; -diff --git a/src/main/java/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java b/src/main/java/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java -index 61dee55417bc802e25b9ba2f271d32d8c12844a9..a8a260a3caaa8e5004069b833ecc8b17b2fc8db5 100644 ---- a/src/main/java/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java -+++ b/src/main/java/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java -@@ -7,7 +7,7 @@ import java.util.Iterator; - import javax.annotation.Nullable; - import net.minecraft.core.IdMap; - --public class CrudeIncrementalIntIdentityHashBiMap implements IdMap { -+public class CrudeIncrementalIntIdentityHashBiMap implements IdMap, ca.spottedleaf.moonrise.patches.fast_palette.FastPalette { // Paper - optimise palette reads - private static final int NOT_FOUND = -1; - private static final Object EMPTY_SLOT = null; - private static final float LOADFACTOR = 0.8F; -@@ -17,6 +17,16 @@ public class CrudeIncrementalIntIdentityHashBiMap implements IdMap { - private int nextId; - private int size; - -+ // Paper start - optimise palette reads -+ private ca.spottedleaf.moonrise.patches.fast_palette.FastPaletteData reference; -+ -+ @Override -+ public final K[] moonrise$getRawPalette(final ca.spottedleaf.moonrise.patches.fast_palette.FastPaletteData src) { -+ this.reference = src; -+ return this.byId; -+ } -+ // Paper end - optimise palette reads -+ - private CrudeIncrementalIntIdentityHashBiMap(int size) { - this.keys = (K[])(new Object[size]); - this.values = new int[size]; -@@ -88,6 +98,12 @@ public class CrudeIncrementalIntIdentityHashBiMap implements IdMap { - this.byId = crudeIncrementalIntIdentityHashBiMap.byId; - this.nextId = crudeIncrementalIntIdentityHashBiMap.nextId; - this.size = crudeIncrementalIntIdentityHashBiMap.size; -+ // Paper start - optimise palette reads -+ final ca.spottedleaf.moonrise.patches.fast_palette.FastPaletteData ref = this.reference; -+ if (ref != null) { -+ ref.moonrise$setPalette(this.byId); -+ } -+ // Paper end - optimise palette reads - } - - public void addMapping(K value, int id) { -diff --git a/src/main/java/net/minecraft/util/SimpleBitStorage.java b/src/main/java/net/minecraft/util/SimpleBitStorage.java -index 8acf2f2491a8d9d13392c5e89b2bd5c9918285e1..d99ec470b4653beab630999a5b2c1a6428b20c38 100644 ---- a/src/main/java/net/minecraft/util/SimpleBitStorage.java -+++ b/src/main/java/net/minecraft/util/SimpleBitStorage.java -@@ -208,6 +208,20 @@ public class SimpleBitStorage implements BitStorage { - private final int divideAdd; private final long divideAddUnsigned; // Paper - Perf: Optimize SimpleBitStorage - private final int divideShift; - -+ // Paper start - optimise bitstorage read/write operations -+ private static final int[] BETTER_MAGIC = new int[33]; -+ static { -+ // 20 bits of precision -+ // since index is always [0, 4095] (i.e 12 bits), multiplication by a magic value here (20 bits) -+ // fits exactly in an int and allows us to use integer arithmetic -+ for (int bits = 1; bits < BETTER_MAGIC.length; ++bits) { -+ BETTER_MAGIC[bits] = (int)ca.spottedleaf.concurrentutil.util.IntegerUtil.getUnsignedDivisorMagic(64L / bits, 20); -+ } -+ } -+ private final int magic; -+ private final int mulBits; -+ // Paper end - optimise bitstorage read/write operations -+ - public SimpleBitStorage(int elementBits, int size, int[] data) { - this(elementBits, size); - int i = 0; -@@ -261,6 +275,13 @@ public class SimpleBitStorage implements BitStorage { - } else { - this.data = new long[j]; - } -+ // Paper start - optimise bitstorage read/write operations -+ this.magic = BETTER_MAGIC[this.bits]; -+ this.mulBits = (64 / this.bits) * this.bits; -+ if (this.size > 4096) { -+ throw new IllegalStateException("Size > 4096 not supported"); -+ } -+ // Paper end - optimise bitstorage read/write operations - } - - private int cellIndex(int index) { -@@ -273,31 +294,54 @@ public class SimpleBitStorage implements BitStorage { - public final int getAndSet(int index, int value) { // Paper - Perf: Optimize SimpleBitStorage - //Validate.inclusiveBetween(0L, (long)(this.size - 1), (long)index); // Paper - Perf: Optimize SimpleBitStorage - //Validate.inclusiveBetween(0L, this.mask, (long)value); // Paper - Perf: Optimize SimpleBitStorage -- int i = this.cellIndex(index); -- long l = this.data[i]; -- int j = (index - i * this.valuesPerLong) * this.bits; -- int k = (int)(l >> j & this.mask); -- this.data[i] = l & ~(this.mask << j) | ((long)value & this.mask) << j; -- return k; -+ // Paper start - optimise bitstorage read/write operations -+ final int full = this.magic * index; // 20 bits of magic + 12 bits of index = barely int -+ final int divQ = full >>> 20; -+ final int divR = (full & 0xFFFFF) * this.mulBits >>> 20; -+ -+ final long[] dataArray = this.data; -+ -+ final long data = dataArray[divQ]; -+ final long mask = this.mask; -+ -+ final long write = data & ~(mask << divR) | ((long)value & mask) << divR; -+ -+ dataArray[divQ] = write; -+ -+ return (int)(data >>> divR & mask); -+ // Paper end - optimise bitstorage read/write operations - } - - @Override - public final void set(int index, int value) { // Paper - Perf: Optimize SimpleBitStorage - //Validate.inclusiveBetween(0L, (long)(this.size - 1), (long)index); // Paper - Perf: Optimize SimpleBitStorage - //Validate.inclusiveBetween(0L, this.mask, (long)value); // Paper - Perf: Optimize SimpleBitStorage -- int i = this.cellIndex(index); -- long l = this.data[i]; -- int j = (index - i * this.valuesPerLong) * this.bits; -- this.data[i] = l & ~(this.mask << j) | ((long)value & this.mask) << j; -+ // Paper start - optimise bitstorage read/write operations -+ final int full = this.magic * index; // 20 bits of magic + 12 bits of index = barely int -+ final int divQ = full >>> 20; -+ final int divR = (full & 0xFFFFF) * this.mulBits >>> 20; -+ -+ final long[] dataArray = this.data; -+ -+ final long data = dataArray[divQ]; -+ final long mask = this.mask; -+ -+ final long write = data & ~(mask << divR) | ((long)value & mask) << divR; -+ -+ dataArray[divQ] = write; -+ // Paper end - optimise bitstorage read/write operations - } - - @Override - public final int get(int index) { // Paper - Perf: Optimize SimpleBitStorage - //Validate.inclusiveBetween(0L, (long)(this.size - 1), (long)index); // Paper - Perf: Optimize SimpleBitStorage -- int i = this.cellIndex(index); -- long l = this.data[i]; -- int j = (index - i * this.valuesPerLong) * this.bits; -- return (int)(l >> j & this.mask); -+ // Paper start - optimise bitstorage read/write operations -+ final int full = this.magic * index; // 20 bits of magic + 12 bits of index = barely int -+ final int divQ = full >>> 20; -+ final int divR = (full & 0xFFFFF) * this.mulBits >>> 20; -+ -+ return (int)(this.data[divQ] >>> divR & this.mask); -+ // Paper end - optimise bitstorage read/write operations - } - - @Override -@@ -364,35 +408,62 @@ public class SimpleBitStorage implements BitStorage { - - // Paper start - block counting - @Override -- public final it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap moonrise$countEntries() { -+ public final it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap moonrise$countEntries() { - final int valuesPerLong = this.valuesPerLong; - final int bits = this.bits; -- final long mask = this.mask; -+ final long mask = (1L << bits) - 1L; - final int size = this.size; - -- // we may be backed by global palette, so limit bits for init capacity -- final it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap ret = new it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap<>( -- 1 << Math.min(6, bits) -- ); -- -- int index = 0; -- -- for (long value : this.data) { -- int li = 0; -- do { -- final int paletteIdx = (int)(value & mask); -- value >>= bits; -+ if (bits <= 6) { -+ final it.unimi.dsi.fastutil.shorts.ShortArrayList[] byId = new it.unimi.dsi.fastutil.shorts.ShortArrayList[1 << bits]; -+ final it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap ret = new it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap<>(1 << bits); -+ -+ int index = 0; -+ -+ for (long value : this.data) { -+ int li = 0; -+ do { -+ final int paletteIdx = (int)(value & mask); -+ value >>= bits; -+ ++li; -+ -+ final it.unimi.dsi.fastutil.shorts.ShortArrayList coords = byId[paletteIdx]; -+ if (coords != null) { -+ coords.add((short)index++); -+ continue; -+ } else { -+ final it.unimi.dsi.fastutil.shorts.ShortArrayList newCoords = new it.unimi.dsi.fastutil.shorts.ShortArrayList(64); -+ byId[paletteIdx] = newCoords; -+ newCoords.add((short)index++); -+ ret.put(paletteIdx, newCoords); -+ continue; -+ } -+ } while (li < valuesPerLong && index < size); -+ } - -- ret.computeIfAbsent(paletteIdx, (final int key) -> { -- return new it.unimi.dsi.fastutil.ints.IntArrayList(); -- }).add(index); -+ return ret; -+ } else { -+ final it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap ret = new it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap<>( -+ 1 << 6 -+ ); -+ -+ int index = 0; -+ -+ for (long value : this.data) { -+ int li = 0; -+ do { -+ final int paletteIdx = (int)(value & mask); -+ value >>= bits; -+ ++li; -+ -+ ret.computeIfAbsent(paletteIdx, (final int key) -> { -+ return new it.unimi.dsi.fastutil.shorts.ShortArrayList(64); -+ }).add((short)index++); -+ } while (li < valuesPerLong && index < size); -+ } - -- ++li; -- ++index; -- } while (li < valuesPerLong && index < size); -+ return ret; - } -- -- return ret; - } - // Paper end - block counting - -diff --git a/src/main/java/net/minecraft/util/ZeroBitStorage.java b/src/main/java/net/minecraft/util/ZeroBitStorage.java -index 15c5164d0ef41a978c16ee317fa73e97f2480207..1f9c436a632e4f110be61cf76fcfc3b7eb80334e 100644 ---- a/src/main/java/net/minecraft/util/ZeroBitStorage.java -+++ b/src/main/java/net/minecraft/util/ZeroBitStorage.java -@@ -65,17 +65,17 @@ public class ZeroBitStorage implements BitStorage { - - // Paper start - block counting - @Override -- public final it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap moonrise$countEntries() { -+ public final it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap moonrise$countEntries() { - final int size = this.size; - -- final int[] raw = new int[size]; -+ final short[] raw = new short[size]; - for (int i = 0; i < size; ++i) { -- raw[i] = i; -+ raw[i] = (short)i; - } - -- final it.unimi.dsi.fastutil.ints.IntArrayList coordinates = it.unimi.dsi.fastutil.ints.IntArrayList.wrap(raw, size); -+ final it.unimi.dsi.fastutil.shorts.ShortArrayList coordinates = it.unimi.dsi.fastutil.shorts.ShortArrayList.wrap(raw, size); - -- final it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap ret = new it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap<>(1); -+ final it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap ret = new it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap<>(1); - ret.put(0, coordinates); - return ret; - } -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 5410a0380c44629f1c9b4f0a8e6017cfc5a31a89..a3b0363fbc207ed9edc8a4d6619b6fff9389a9c7 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -448,6 +448,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - // Paper start - rewrite chunk system - private final boolean isHardColliding = this.moonrise$isHardCollidingUncached(); - private net.minecraft.server.level.FullChunkStatus chunkStatus; -+ private ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData chunkData; - private int sectionX = Integer.MIN_VALUE; - private int sectionY = Integer.MIN_VALUE; - private int sectionZ = Integer.MIN_VALUE; -@@ -468,6 +469,16 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - this.chunkStatus = status; - } - -+ @Override -+ public final ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData moonrise$getChunkData() { -+ return this.chunkData; -+ } -+ -+ @Override -+ public final void moonrise$setChunkData(final ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData chunkData) { -+ this.chunkData = chunkData; -+ } -+ - @Override - public final int moonrise$getSectionX() { - return this.sectionX; -@@ -516,6 +527,54 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - return this.getIndirectPassengersStream().anyMatch((entity) -> entity instanceof Player); - } - // Paper end - rewrite chunk system -+ // Paper start - optimise collisions -+ private static float[] calculateStepHeights(final AABB box, final List voxels, final List aabbs, final float stepHeight, -+ final float collidedY) { -+ final FloatArraySet ret = new FloatArraySet(); -+ -+ for (int i = 0, len = voxels.size(); i < len; ++i) { -+ final VoxelShape shape = voxels.get(i); -+ -+ final double[] yCoords = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)shape).moonrise$rootCoordinatesY(); -+ final double yOffset = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)shape).moonrise$offsetY(); -+ -+ for (final double yUnoffset : yCoords) { -+ final double y = yUnoffset + yOffset; -+ -+ final float step = (float)(y - box.minY); -+ -+ if (step > stepHeight) { -+ break; -+ } -+ -+ if (step < 0.0f || !(step != collidedY)) { -+ continue; -+ } -+ -+ ret.add(step); -+ } -+ } -+ -+ for (int i = 0, len = aabbs.size(); i < len; ++i) { -+ final AABB shape = aabbs.get(i); -+ -+ final float step1 = (float)(shape.minY - box.minY); -+ final float step2 = (float)(shape.maxY - box.minY); -+ -+ if (!(step1 < 0.0f) && step1 != collidedY && !(step1 > stepHeight)) { -+ ret.add(step1); -+ } -+ -+ if (!(step2 < 0.0f) && step2 != collidedY && !(step2 > stepHeight)) { -+ ret.add(step2); -+ } -+ } -+ -+ final float[] steps = ret.toFloatArray(); -+ FloatArrays.unstableSort(steps); -+ return steps; -+ } -+ // Paper end - optimise collisions - // Paper start - optimise entity tracker - private net.minecraft.server.level.ChunkMap.TrackedEntity trackedEntity; - -@@ -1402,73 +1461,67 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - return movement; - } - -- final Level world = this.level; -- final AABB currBoundingBox = this.getBoundingBox(); -- -- if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isEmpty(currBoundingBox)) { -- return movement; -- } -+ final AABB currentBox = this.getBoundingBox(); - -- final List potentialCollisionsBB = new ArrayList<>(); - final List potentialCollisionsVoxel = new ArrayList<>(); -- final double stepHeight = (double)this.maxUpStep(); -- final AABB collisionBox; -- final boolean onGround = this.onGround; -+ final List potentialCollisionsBB = new ArrayList<>(); - -+ final AABB initialCollisionBox; - if (xZero & zZero) { -- if (movement.y > 0.0) { -- collisionBox = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.cutUpwards(currBoundingBox, movement.y); -- } else { -- collisionBox = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.cutDownwards(currBoundingBox, movement.y); -- } -+ // note: xZero & zZero -> collision on x/z == 0 -> no step height calculation -+ // this specifically optimises entities standing still -+ initialCollisionBox = movement.y < 0.0 ? -+ ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.cutDownwards(currentBox, movement.y) : ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.cutUpwards(currentBox, movement.y); - } else { -- // note: xZero == false or zZero == false -- if (stepHeight > 0.0 && (onGround || (movement.y < 0.0))) { -- // don't bother getting the collisions if we don't need them. -- if (movement.y <= 0.0) { -- collisionBox = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.expandUpwards(currBoundingBox.expandTowards(movement.x, movement.y, movement.z), stepHeight); -- } else { -- collisionBox = currBoundingBox.expandTowards(movement.x, Math.max(stepHeight, movement.y), movement.z); -- } -- } else { -- collisionBox = currBoundingBox.expandTowards(movement.x, movement.y, movement.z); -- } -+ initialCollisionBox = currentBox.expandTowards(movement); - } - -- ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.getCollisions( -- world, (Entity)(Object)this, collisionBox, potentialCollisionsVoxel, potentialCollisionsBB, -- ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_CHECK_BORDER, -- null, null -+ final List entityAABBs = new ArrayList<>(); -+ ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.getEntityHardCollisions( -+ this.level, (Entity)(Object)this, initialCollisionBox, entityAABBs, 0, null - ); - -- if (potentialCollisionsVoxel.isEmpty() && potentialCollisionsBB.isEmpty()) { -- return movement; -- } -+ ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.getCollisionsForBlocksOrWorldBorder( -+ this.level, (Entity)(Object)this, initialCollisionBox, potentialCollisionsVoxel, potentialCollisionsBB, -+ ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_CHECK_BORDER, null -+ ); -+ potentialCollisionsBB.addAll(entityAABBs); -+ final Vec3 collided = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(movement, currentBox, potentialCollisionsVoxel, potentialCollisionsBB); - -- final Vec3 limitedMoveVector = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(movement, currBoundingBox, potentialCollisionsVoxel, potentialCollisionsBB); -+ final boolean collidedX = collided.x != movement.x; -+ final boolean collidedY = collided.y != movement.y; -+ final boolean collidedZ = collided.z != movement.z; - -- if (stepHeight > 0.0 -- && (onGround || (limitedMoveVector.y != movement.y && movement.y < 0.0)) -- && (limitedMoveVector.x != movement.x || limitedMoveVector.z != movement.z)) { -- Vec3 vec3d2 = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(new Vec3(movement.x, stepHeight, movement.z), currBoundingBox, potentialCollisionsVoxel, potentialCollisionsBB); -- final Vec3 vec3d3 = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(new Vec3(0.0, stepHeight, 0.0), currBoundingBox.expandTowards(movement.x, 0.0, movement.z), potentialCollisionsVoxel, potentialCollisionsBB); -+ final boolean collidedDownwards = collidedY && movement.y < 0.0; - -- if (vec3d3.y < stepHeight) { -- final Vec3 vec3d4 = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(new Vec3(movement.x, 0.0D, movement.z), currBoundingBox.move(vec3d3), potentialCollisionsVoxel, potentialCollisionsBB).add(vec3d3); -+ final double stepHeight; - -- if (vec3d4.horizontalDistanceSqr() > vec3d2.horizontalDistanceSqr()) { -- vec3d2 = vec3d4; -- } -- } -+ if ((!collidedDownwards && !this.onGround) || (!collidedX && !collidedZ) || (stepHeight = (double)this.maxUpStep()) <= 0.0) { -+ return collided; -+ } - -- if (vec3d2.horizontalDistanceSqr() > limitedMoveVector.horizontalDistanceSqr()) { -- return vec3d2.add(ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(new Vec3(0.0D, -vec3d2.y + movement.y, 0.0D), currBoundingBox.move(vec3d2), potentialCollisionsVoxel, potentialCollisionsBB)); -- } -+ final AABB collidedYBox = collidedDownwards ? currentBox.move(0.0, collided.y, 0.0) : currentBox; -+ AABB stepRetrievalBox = collidedYBox.expandTowards(movement.x, stepHeight, movement.z); -+ if (!collidedDownwards) { -+ stepRetrievalBox = stepRetrievalBox.expandTowards(0.0, (double)-1.0E-5F, 0.0); -+ } - -- return limitedMoveVector; -- } else { -- return limitedMoveVector; -+ final List stepVoxels = new ArrayList<>(); -+ final List stepAABBs = entityAABBs; -+ -+ ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.getCollisionsForBlocksOrWorldBorder( -+ this.level, (Entity)(Object)this, stepRetrievalBox, stepVoxels, stepAABBs, -+ ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_CHECK_BORDER, null -+ ); -+ -+ for (final float step : calculateStepHeights(collidedYBox, stepVoxels, stepAABBs, (float)stepHeight, (float)collided.y)) { -+ final Vec3 stepResult = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(new Vec3(movement.x, (double)step, movement.z), collidedYBox, stepVoxels, stepAABBs); -+ if (stepResult.horizontalDistanceSqr() > collided.horizontalDistanceSqr()) { -+ return stepResult.add(0.0, collidedYBox.minY - currentBox.minY, 0.0); -+ } - } -+ -+ return collided; - // Paper end - optimise collisions - } - -@@ -2836,64 +2889,99 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - return false; - } - -- final float reducedWith = this.dimensions.width() * 0.8F; -- final AABB box = AABB.ofSize(this.getEyePosition(), reducedWith, 1.0E-6D, reducedWith); -+ final double reducedWith = (double)(this.dimensions.width() * 0.8F); -+ final AABB boundingBox = AABB.ofSize(this.getEyePosition(), reducedWith, 1.0E-6D, reducedWith); -+ final Level world = this.level; - -- if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isEmpty(box)) { -+ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isEmpty(boundingBox)) { - return false; - } - -- final BlockPos.MutableBlockPos tempPos = new BlockPos.MutableBlockPos(); -+ final int minBlockX = Mth.floor(boundingBox.minX); -+ final int minBlockY = Mth.floor(boundingBox.minY); -+ final int minBlockZ = Mth.floor(boundingBox.minZ); -+ -+ final int maxBlockX = Mth.floor(boundingBox.maxX); -+ final int maxBlockY = Mth.floor(boundingBox.maxY); -+ final int maxBlockZ = Mth.floor(boundingBox.maxZ); - -- final int minX = Mth.floor(box.minX); -- final int minY = Mth.floor(box.minY); -- final int minZ = Mth.floor(box.minZ); -- final int maxX = Mth.floor(box.maxX); -- final int maxY = Mth.floor(box.maxY); -- final int maxZ = Mth.floor(box.maxZ); -+ final int minChunkX = minBlockX >> 4; -+ final int minChunkY = minBlockY >> 4; -+ final int minChunkZ = minBlockZ >> 4; - -- final net.minecraft.world.level.chunk.ChunkSource chunkProvider = this.level.getChunkSource(); -+ final int maxChunkX = maxBlockX >> 4; -+ final int maxChunkY = maxBlockY >> 4; -+ final int maxChunkZ = maxBlockZ >> 4; - -- long lastChunkKey = ChunkPos.INVALID_CHUNK_POS; -- net.minecraft.world.level.chunk.LevelChunk lastChunk = null; -- for (int fz = minZ; fz <= maxZ; ++fz) { -- tempPos.setZ(fz); -- for (int fx = minX; fx <= maxX; ++fx) { -- final int newChunkX = fx >> 4; -- final int newChunkZ = fz >> 4; -- final net.minecraft.world.level.chunk.LevelChunk chunk = lastChunkKey == (lastChunkKey = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(newChunkX, newChunkZ)) ? -- lastChunk : (lastChunk = (net.minecraft.world.level.chunk.LevelChunk)chunkProvider.getChunk(newChunkX, newChunkZ, net.minecraft.world.level.chunk.status.ChunkStatus.FULL, true)); -- tempPos.setX(fx); -- for (int fy = minY; fy <= maxY; ++fy) { -- tempPos.setY(fy); -+ final int minSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection(world); -+ final net.minecraft.world.level.chunk.ChunkSource chunkSource = world.getChunkSource(); -+ final BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos(); - -- final BlockState state = chunk.getBlockState(tempPos); -+ for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) { -+ for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) { -+ final net.minecraft.world.level.chunk.LevelChunkSection[] sections = chunkSource.getChunk(currChunkX, currChunkZ, net.minecraft.world.level.chunk.status.ChunkStatus.FULL, true).getSections(); - -- if (((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)state).moonrise$emptyCollisionShape() || !state.isSuffocating(this.level, tempPos)) { -+ for (int currChunkY = minChunkY; currChunkY <= maxChunkY; ++currChunkY) { -+ final int sectionIdx = currChunkY - minSection; -+ if (sectionIdx < 0 || sectionIdx >= sections.length) { - continue; - } -- -- // Yes, it does not use the Entity context stuff. -- final VoxelShape collisionShape = state.getCollisionShape(this.level, tempPos); -- -- if (collisionShape.isEmpty()) { -+ final net.minecraft.world.level.chunk.LevelChunkSection section = sections[sectionIdx]; -+ if (section.hasOnlyAir()) { -+ // empty - continue; - } - -- final AABB toCollide = box.move(-(double)fx, -(double)fy, -(double)fz); -+ final net.minecraft.world.level.chunk.PalettedContainer blocks = section.states; -+ -+ final int minXIterate = currChunkX == minChunkX ? (minBlockX & 15) : 0; -+ final int maxXIterate = currChunkX == maxChunkX ? (maxBlockX & 15) : 15; -+ final int minZIterate = currChunkZ == minChunkZ ? (minBlockZ & 15) : 0; -+ final int maxZIterate = currChunkZ == maxChunkZ ? (maxBlockZ & 15) : 15; -+ final int minYIterate = currChunkY == minChunkY ? (minBlockY & 15) : 0; -+ final int maxYIterate = currChunkY == maxChunkY ? (maxBlockY & 15) : 15; -+ -+ for (int currY = minYIterate; currY <= maxYIterate; ++currY) { -+ final int blockY = currY | (currChunkY << 4); -+ mutablePos.setY(blockY); -+ for (int currZ = minZIterate; currZ <= maxZIterate; ++currZ) { -+ final int blockZ = currZ | (currChunkZ << 4); -+ mutablePos.setZ(blockZ); -+ for (int currX = minXIterate; currX <= maxXIterate; ++currX) { -+ final int blockX = currX | (currChunkX << 4); -+ mutablePos.setX(blockX); -+ -+ final BlockState blockState = blocks.get((currX) | (currZ << 4) | ((currY) << 8)); -+ -+ if (((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)blockState).moonrise$emptyCollisionShape() -+ || !blockState.isSuffocating(world, mutablePos)) { -+ continue; -+ } - -- final AABB singleAABB = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)collisionShape).moonrise$getSingleAABBRepresentation(); -- if (singleAABB != null) { -- if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.voxelShapeIntersect(singleAABB, toCollide)) { -- return true; -- } -- continue; -- } -+ // Yes, it does not use the Entity context stuff. -+ final VoxelShape collisionShape = blockState.getCollisionShape(world, mutablePos); -+ -+ if (collisionShape.isEmpty()) { -+ continue; -+ } -+ -+ final AABB toCollide = boundingBox.move(-(double)blockX, -(double)blockY, -(double)blockZ); - -- if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.voxelShapeIntersectNoEmpty(collisionShape, toCollide)) { -- return true; -+ final AABB singleAABB = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)collisionShape).moonrise$getSingleAABBRepresentation(); -+ if (singleAABB != null) { -+ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.voxelShapeIntersect(singleAABB, toCollide)) { -+ return true; -+ } -+ continue; -+ } -+ -+ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.voxelShapeIntersectNoEmpty(collisionShape, toCollide)) { -+ return true; -+ } -+ continue; -+ } -+ } - } -- continue; - } - } - } -@@ -4508,82 +4596,136 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - return Mth.lerp(delta, this.yRotO, this.yRot); - } - -- public boolean updateFluidHeightAndDoFluidPushing(TagKey tag, double speed) { -+ // Paper start - optimise collisions -+ public boolean updateFluidHeightAndDoFluidPushing(final TagKey fluid, final double flowScale) { - if (this.touchingUnloadedChunk()) { - return false; -- } else { -- AABB axisalignedbb = this.getBoundingBox().deflate(0.001D); -- int i = Mth.floor(axisalignedbb.minX); -- int j = Mth.ceil(axisalignedbb.maxX); -- int k = Mth.floor(axisalignedbb.minY); -- int l = Mth.ceil(axisalignedbb.maxY); -- int i1 = Mth.floor(axisalignedbb.minZ); -- int j1 = Mth.ceil(axisalignedbb.maxZ); -- double d1 = 0.0D; -- boolean flag = this.isPushedByFluid(); -- boolean flag1 = false; -- Vec3 vec3d = Vec3.ZERO; -- int k1 = 0; -- BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos(); -- -- for (int l1 = i; l1 < j; ++l1) { -- for (int i2 = k; i2 < l; ++i2) { -- for (int j2 = i1; j2 < j1; ++j2) { -- blockposition_mutableblockposition.set(l1, i2, j2); -- FluidState fluid = this.level().getFluidState(blockposition_mutableblockposition); -- -- if (fluid.is(tag)) { -- double d2 = (double) ((float) i2 + fluid.getHeight(this.level(), blockposition_mutableblockposition)); -- -- if (d2 >= axisalignedbb.minY) { -- flag1 = true; -- d1 = Math.max(d2 - axisalignedbb.minY, d1); -- if (flag) { -- Vec3 vec3d1 = fluid.getFlow(this.level(), blockposition_mutableblockposition); -- -- if (d1 < 0.4D) { -- vec3d1 = vec3d1.scale(d1); -- } -+ } -+ -+ final AABB boundingBox = this.getBoundingBox().deflate(1.0E-3); -+ -+ final Level world = this.level; -+ final int minSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection(world); -+ -+ final int minBlockX = Mth.floor(boundingBox.minX); -+ final int minBlockY = Math.max((minSection << 4), Mth.floor(boundingBox.minY)); -+ final int minBlockZ = Mth.floor(boundingBox.minZ); -+ -+ // note: bounds are exclusive in Vanilla, so we subtract 1 - our loop expects bounds to be inclusive -+ final int maxBlockX = Mth.ceil(boundingBox.maxX) - 1; -+ final int maxBlockY = Math.min((ca.spottedleaf.moonrise.common.util.WorldUtil.getMaxSection(world) << 4) | 15, Mth.ceil(boundingBox.maxY) - 1); -+ final int maxBlockZ = Mth.ceil(boundingBox.maxZ) - 1; -+ -+ final boolean isPushable = this.isPushedByFluid(); -+ final BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos(); -+ -+ Vec3 pushVector = Vec3.ZERO; -+ double totalPushes = 0.0; -+ double maxHeightDiff = 0.0; -+ boolean inFluid = false; -+ -+ final int minChunkX = minBlockX >> 4; -+ final int maxChunkX = maxBlockX >> 4; -+ -+ final int minChunkY = minBlockY >> 4; -+ final int maxChunkY = maxBlockY >> 4; -+ -+ final int minChunkZ = minBlockZ >> 4; -+ final int maxChunkZ = maxBlockZ >> 4; -+ -+ final net.minecraft.world.level.chunk.ChunkSource chunkSource = world.getChunkSource(); -+ -+ for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) { -+ for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) { -+ final net.minecraft.world.level.chunk.LevelChunkSection[] sections = chunkSource.getChunk(currChunkX, currChunkZ, net.minecraft.world.level.chunk.status.ChunkStatus.FULL, false).getSections(); - -- vec3d = vec3d.add(vec3d1); -- ++k1; -+ // bound y -+ for (int currChunkY = minChunkY; currChunkY <= maxChunkY; ++currChunkY) { -+ final int sectionIdx = currChunkY - minSection; -+ if (sectionIdx < 0 || sectionIdx >= sections.length) { -+ continue; -+ } -+ final net.minecraft.world.level.chunk.LevelChunkSection section = sections[sectionIdx]; -+ if (section.hasOnlyAir()) { -+ // empty -+ continue; -+ } -+ -+ final net.minecraft.world.level.chunk.PalettedContainer blocks = section.states; -+ -+ final int minXIterate = currChunkX == minChunkX ? (minBlockX & 15) : 0; -+ final int maxXIterate = currChunkX == maxChunkX ? (maxBlockX & 15) : 15; -+ final int minZIterate = currChunkZ == minChunkZ ? (minBlockZ & 15) : 0; -+ final int maxZIterate = currChunkZ == maxChunkZ ? (maxBlockZ & 15) : 15; -+ final int minYIterate = currChunkY == minChunkY ? (minBlockY & 15) : 0; -+ final int maxYIterate = currChunkY == maxChunkY ? (maxBlockY & 15) : 15; -+ -+ for (int currY = minYIterate; currY <= maxYIterate; ++currY) { -+ for (int currZ = minZIterate; currZ <= maxZIterate; ++currZ) { -+ for (int currX = minXIterate; currX <= maxXIterate; ++currX) { -+ final FluidState fluidState = blocks.get((currX) | (currZ << 4) | ((currY) << 8)).getFluidState(); -+ -+ if (fluidState.isEmpty() || !fluidState.is(fluid)) { -+ continue; -+ } -+ -+ mutablePos.set(currX | (currChunkX << 4), currY | (currChunkY << 4), currZ | (currChunkZ << 4)); -+ -+ final double height = (double)((float)mutablePos.getY() + fluidState.getHeight(world, mutablePos)); -+ final double diff = height - boundingBox.minY; -+ -+ if (diff < 0.0) { -+ continue; -+ } -+ -+ inFluid = true; -+ maxHeightDiff = Math.max(maxHeightDiff, diff); -+ -+ if (!isPushable) { -+ continue; - } -- // CraftBukkit start - store last lava contact location -- if (tag == FluidTags.LAVA) { -- this.lastLavaContact = blockposition_mutableblockposition.immutable(); -+ -+ ++totalPushes; -+ -+ final Vec3 flow = fluidState.getFlow(world, mutablePos); -+ -+ if (diff < 0.4) { -+ pushVector = pushVector.add(flow.scale(diff)); -+ } else { -+ pushVector = pushVector.add(flow); - } -- // CraftBukkit end - } - } - } - } - } -+ } - -- if (vec3d.length() > 0.0D) { -- if (k1 > 0) { -- vec3d = vec3d.scale(1.0D / (double) k1); -- } -+ this.fluidHeight.put(fluid, maxHeightDiff); - -- if (!(this instanceof Player)) { -- vec3d = vec3d.normalize(); -- } -+ if (pushVector.lengthSqr() == 0.0) { -+ return inFluid; -+ } - -- Vec3 vec3d2 = this.getDeltaMovement(); -+ // note: totalPushes != 0 as pushVector != 0 -+ pushVector = pushVector.scale(1.0 / totalPushes); -+ final Vec3 currMovement = this.getDeltaMovement(); - -- vec3d = vec3d.scale(speed); -- double d3 = 0.003D; -+ if (!((Entity)(Object)this instanceof Player)) { -+ pushVector = pushVector.normalize(); -+ } - -- if (Math.abs(vec3d2.x) < 0.003D && Math.abs(vec3d2.z) < 0.003D && vec3d.length() < 0.0045000000000000005D) { -- vec3d = vec3d.normalize().scale(0.0045000000000000005D); -- } -+ pushVector = pushVector.scale(flowScale); -+ if (Math.abs(currMovement.x) < 0.003 && Math.abs(currMovement.z) < 0.003 && pushVector.length() < 0.0045000000000000005) { -+ pushVector = pushVector.normalize().scale(0.0045000000000000005); -+ } - -- this.setDeltaMovement(this.getDeltaMovement().add(vec3d)); -- } -+ this.setDeltaMovement(currMovement.add(pushVector)); - -- this.fluidHeight.put(tag, d1); -- return flag1; -- } -+ // note: inFluid = true here as pushVector != 0 -+ return true; - } -+ // Paper end - optimise collisions - - public boolean touchingUnloadedChunk() { - AABB axisalignedbb = this.getBoundingBox().inflate(1.0D); -diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java -index 4cf6cb0abfeb7065c6d9381fb4194371c0cddc35..5930a430983061afddf20e3208ff2462ca1b78cd 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java -+++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java -@@ -73,8 +73,7 @@ public class PoiManager extends SectionStorage im - - ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Accessing poi chunk off-main"); - -- final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager manager = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.world).moonrise$getChunkTaskScheduler().chunkHolderManager; -- final ca.spottedleaf.moonrise.patches.chunk_system.level.poi.PoiChunk ret = manager.getPoiChunkIfLoaded(chunkX, chunkZ, true); -+ final ca.spottedleaf.moonrise.patches.chunk_system.level.poi.PoiChunk ret = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.world).moonrise$getChunkTaskScheduler().chunkHolderManager.getPoiChunkIfLoaded(chunkX, chunkZ, true); - - return ret == null ? Optional.empty() : ret.getSectionForVanilla(chunkY); - } -@@ -128,9 +127,13 @@ public class PoiManager extends SectionStorage im - public final void moonrise$onUnload(final long coordinate) { // Paper - rewrite chunk system - final int chunkX = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(coordinate); - final int chunkZ = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(coordinate); -+ -+ final int minY = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection(this.world); -+ final int maxY = ca.spottedleaf.moonrise.common.util.WorldUtil.getMaxSection(this.world); -+ - ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Unloading poi chunk off-main"); -- for (int section = this.levelHeightAccessor.getMinSection(); section < this.levelHeightAccessor.getMaxSection(); ++section) { -- final long sectionPos = SectionPos.asLong(chunkX, section, chunkZ); -+ for (int sectionY = minY; sectionY <= maxY; ++sectionY) { -+ final long sectionPos = SectionPos.asLong(chunkX, sectionY, chunkZ); - this.updateDistanceTracking(sectionPos); - } - } -@@ -139,8 +142,12 @@ public class PoiManager extends SectionStorage im - public final void moonrise$loadInPoiChunk(final ca.spottedleaf.moonrise.patches.chunk_system.level.poi.PoiChunk poiChunk) { - final int chunkX = poiChunk.chunkX; - final int chunkZ = poiChunk.chunkZ; -+ -+ final int minY = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection(this.world); -+ final int maxY = ca.spottedleaf.moonrise.common.util.WorldUtil.getMaxSection(this.world); -+ - ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Loading poi chunk off-main"); -- for (int sectionY = this.levelHeightAccessor.getMinSection(); sectionY < this.levelHeightAccessor.getMaxSection(); ++sectionY) { -+ for (int sectionY = minY; sectionY <= maxY; ++sectionY) { - final PoiSection section = poiChunk.getSection(sectionY); - if (section != null && !((ca.spottedleaf.moonrise.patches.chunk_system.level.poi.ChunkSystemPoiSection)section).moonrise$isEmpty()) { - this.onSectionLoad(SectionPos.asLong(chunkX, sectionY, chunkZ)); -@@ -316,8 +323,10 @@ public class PoiManager extends SectionStorage im - } - - public int sectionsToVillage(SectionPos pos) { -- this.villageDistanceTracker.propagateUpdates(); // Paper - rewrite chunk system -- return convertBetweenLevels(this.villageDistanceTracker.getLevel(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkSectionKey(pos))); // Paper - rewrite chunk system -+ // Paper start - rewrite chunk system -+ this.villageDistanceTracker.propagateUpdates(); -+ return convertBetweenLevels(this.villageDistanceTracker.getLevel(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkSectionKey(pos))); -+ // Paper end - rewrite chunk system - } - - boolean isVillageCenter(long pos) { -diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java -index f6f0d7c21ee81ff33d4af350c4d39aadfbe140df..712cbfc100e8aaf612d1d651dae64f57f892a768 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java -+++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java -@@ -31,7 +31,7 @@ public class PoiSection implements ca.spottedleaf.moonrise.patches.chunk_system. - private boolean isValid; - - // Paper start - rewrite chunk system -- private final Optional noAllocOptional = Optional.of((PoiSection)(Object)this);; -+ private final Optional noAllocOptional = Optional.of((PoiSection)(Object)this); - - @Override - public final boolean moonrise$isEmpty() { -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 332dc7e6bdfb5b3741764d4877185a2e86a982f8..40fe47c7c145587ac81f0f15c237ed72ea9c094d 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -264,26 +264,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl - } - // Paper end - rewrite chunk system - // Paper start - optimise collisions -- private final int minSection; -- private final int maxSection; -- -- @Override -- public final int moonrise$getMinSection() { -- return this.minSection; -- } -- -- @Override -- public final int moonrise$getMaxSection() { -- return this.maxSection; -- } -- - /** - * Route to faster lookup. - * See {@link EntityGetter#isUnobstructed(Entity, VoxelShape)} for expected behavior - * @author Spottedleaf - */ - @Override -- public final boolean isUnobstructed(final Entity entity) { -+ public boolean isUnobstructed(final Entity entity) { - final AABB boundingBox = entity.getBoundingBox(); - if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isEmpty(boundingBox)) { - return false; -@@ -313,7 +300,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl - final Vec3 to = clipContext.getTo(); - final Vec3 from = clipContext.getFrom(); - -- return net.minecraft.world.phys.BlockHitResult.miss(to, Direction.getNearest(from.x - to.x, from.y - to.y, from.z - to.z), BlockPos.containing(to.x, to.y, to.z)); -+ return net.minecraft.world.phys.BlockHitResult.miss(to, Direction.getApproximateNearest(from.x - to.x, from.y - to.y, from.z - to.z), BlockPos.containing(to.x, to.y, to.z)); - } - - private static final FluidState AIR_FLUIDSTATE = Fluids.EMPTY.defaultFluidState(); -@@ -367,7 +354,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl - int lastChunkY = Integer.MIN_VALUE; - int lastChunkZ = Integer.MIN_VALUE; - -- final int minSection = ((ca.spottedleaf.moonrise.patches.collisions.world.CollisionLevel)level).moonrise$getMinSection(); -+ final int minSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection(level); - - for (;;) { - currPos.set(currX, currY, currZ); -@@ -450,7 +437,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl - * @author Spottedleaf - */ - @Override -- public final net.minecraft.world.phys.BlockHitResult clip(final ClipContext clipContext) { -+ public net.minecraft.world.phys.BlockHitResult clip(final ClipContext clipContext) { - // can only do this in this class, as not everything that implements BlockGetter can retrieve chunks - return fastClip(clipContext.getFrom(), clipContext.getTo(), (Level)(Object)this, clipContext); - } -@@ -460,7 +447,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl - * @author Spottedleaf - */ - @Override -- public final boolean collidesWithSuffocatingBlock(final Entity entity, final AABB box) { -+ public boolean collidesWithSuffocatingBlock(final Entity entity, final AABB box) { - return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.getCollisionsForBlocksOrWorldBorder((Level)(Object)this, entity, box, null, null, - ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_CHECK_ONLY, - (final BlockState state, final BlockPos pos) -> { -@@ -486,8 +473,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl - * @author Spottedleaf - */ - @Override -- public final java.util.Optional findFreePosition(final Entity entity, final VoxelShape boundsShape, final Vec3 fromPosition, -- final double rangeX, final double rangeY, final double rangeZ) { -+ public java.util.Optional findFreePosition(final Entity entity, final VoxelShape boundsShape, final Vec3 fromPosition, -+ final double rangeX, final double rangeY, final double rangeZ) { - if (boundsShape.isEmpty()) { - return java.util.Optional.empty(); - } -@@ -546,103 +533,139 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl - * @author Spottedleaf - */ - @Override -- public final java.util.Optional findSupportingBlock(final Entity entity, final AABB aabb) { -+ public java.util.Optional findSupportingBlock(final Entity entity, final AABB aabb) { -+ final int minSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection((Level)(Object)this); -+ - final int minBlockX = Mth.floor(aabb.minX - ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) - 1; - final int maxBlockX = Mth.floor(aabb.maxX + ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) + 1; - -- final int minBlockY = Mth.floor(aabb.minY - ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) - 1; -- final int maxBlockY = Mth.floor(aabb.maxY + ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) + 1; -+ final int minBlockY = Math.max((minSection << 4) - 1, Mth.floor(aabb.minY - ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) - 1); -+ final int maxBlockY = Math.min((ca.spottedleaf.moonrise.common.util.WorldUtil.getMaxSection((Level)(Object)this) << 4) + 16, Mth.floor(aabb.maxY + ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) + 1); - - final int minBlockZ = Mth.floor(aabb.minZ - ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) - 1; - final int maxBlockZ = Mth.floor(aabb.maxZ + ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) + 1; - -- ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.LazyEntityCollisionContext collisionContext = null; -- -- final BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); -+ final BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos(); -+ final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.LazyEntityCollisionContext collisionShape = new ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.LazyEntityCollisionContext(entity); - BlockPos selected = null; - double selectedDistance = Double.MAX_VALUE; -- - final Vec3 entityPos = entity.position(); - -- LevelChunk lastChunk = null; -- int lastChunkX = Integer.MIN_VALUE; -- int lastChunkZ = Integer.MIN_VALUE; -+ // special cases: -+ if (minBlockY > maxBlockY) { -+ // no point in checking -+ return java.util.Optional.empty(); -+ } - -- final ChunkSource chunkSource = this.getChunkSource(); -+ final int minChunkX = minBlockX >> 4; -+ final int maxChunkX = maxBlockX >> 4; - -- for (int currZ = minBlockZ; currZ <= maxBlockZ; ++currZ) { -- pos.setZ(currZ); -- for (int currX = minBlockX; currX <= maxBlockX; ++currX) { -- pos.setX(currX); -+ final int minChunkY = minBlockY >> 4; -+ final int maxChunkY = maxBlockY >> 4; - -- final int newChunkX = currX >> 4; -- final int newChunkZ = currZ >> 4; -+ final int minChunkZ = minBlockZ >> 4; -+ final int maxChunkZ = maxBlockZ >> 4; - -- if (((newChunkX ^ lastChunkX) | (newChunkZ ^ lastChunkZ)) != 0) { -- lastChunkX = newChunkX; -- lastChunkZ = newChunkZ; -- lastChunk = (LevelChunk)chunkSource.getChunk(newChunkX, newChunkZ, ChunkStatus.FULL, false); -- } -+ final ChunkSource chunkSource = this.getChunkSource(); - -- if (lastChunk == null) { -+ for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) { -+ for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) { -+ final ChunkAccess chunk = chunkSource.getChunk(currChunkX, currChunkZ, ChunkStatus.FULL, false); -+ -+ if (chunk == null) { - continue; - } -- for (int currY = minBlockY; currY <= maxBlockY; ++currY) { -- int edgeCount = ((currX == minBlockX || currX == maxBlockX) ? 1 : 0) + -- ((currY == minBlockY || currY == maxBlockY) ? 1 : 0) + -- ((currZ == minBlockZ || currZ == maxBlockZ) ? 1 : 0); -- if (edgeCount == 3) { -- continue; -- } - -- pos.setY(currY); -+ final net.minecraft.world.level.chunk.LevelChunkSection[] sections = chunk.getSections(); - -- final double distance = pos.distToCenterSqr(entityPos); -- if (distance > selectedDistance || (distance == selectedDistance && selected.compareTo(pos) >= 0)) { -+ // bound y -+ for (int currChunkY = minChunkY; currChunkY <= maxChunkY; ++currChunkY) { -+ final int sectionIdx = currChunkY - minSection; -+ if (sectionIdx < 0 || sectionIdx >= sections.length) { - continue; - } -- -- final BlockState state = ((ca.spottedleaf.moonrise.patches.chunk_getblock.GetBlockChunk)lastChunk).moonrise$getBlock(currX, currY, currZ); -- if (((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)state).moonrise$emptyCollisionShape()) { -+ final net.minecraft.world.level.chunk.LevelChunkSection section = sections[sectionIdx]; -+ if (section.hasOnlyAir()) { -+ // empty - continue; - } - -- VoxelShape blockCollision = ((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)state).moonrise$getConstantCollisionShape(); -- -- if ((edgeCount != 1 || state.hasLargeCollisionShape()) && (edgeCount != 2 || state.getBlock() == Blocks.MOVING_PISTON)) { -- if (collisionContext == null) { -- collisionContext = new ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.LazyEntityCollisionContext(entity); -- } -- -- if (blockCollision == null) { -- blockCollision = state.getCollisionShape((Level)(Object)this, pos, collisionContext); -- } -- -- if (blockCollision.isEmpty()) { -- continue; -- } -- -- // avoid VoxelShape#move by shifting the entity collision shape instead -- final AABB shiftedAABB = aabb.move(-(double)currX, -(double)currY, -(double)currZ); -- -- final AABB singleAABB = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)blockCollision).moonrise$getSingleAABBRepresentation(); -- if (singleAABB != null) { -- if (!ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.voxelShapeIntersect(singleAABB, shiftedAABB)) { -- continue; -+ final boolean hasSpecial = ((ca.spottedleaf.moonrise.patches.block_counting.BlockCountingChunkSection)section).moonrise$hasSpecialCollidingBlocks(); -+ final int sectionAdjust = !hasSpecial ? 1 : 0; -+ -+ final net.minecraft.world.level.chunk.PalettedContainer blocks = section.states; -+ -+ final int minXIterate = currChunkX == minChunkX ? (minBlockX & 15) + sectionAdjust : 0; -+ final int maxXIterate = currChunkX == maxChunkX ? (maxBlockX & 15) - sectionAdjust : 15; -+ final int minZIterate = currChunkZ == minChunkZ ? (minBlockZ & 15) + sectionAdjust : 0; -+ final int maxZIterate = currChunkZ == maxChunkZ ? (maxBlockZ & 15) - sectionAdjust : 15; -+ final int minYIterate = currChunkY == minChunkY ? (minBlockY & 15) + sectionAdjust : 0; -+ final int maxYIterate = currChunkY == maxChunkY ? (maxBlockY & 15) - sectionAdjust : 15; -+ -+ for (int currY = minYIterate; currY <= maxYIterate; ++currY) { -+ final int blockY = currY | (currChunkY << 4); -+ mutablePos.setY(blockY); -+ for (int currZ = minZIterate; currZ <= maxZIterate; ++currZ) { -+ final int blockZ = currZ | (currChunkZ << 4); -+ mutablePos.setZ(blockZ); -+ for (int currX = minXIterate; currX <= maxXIterate; ++currX) { -+ final int localBlockIndex = (currX) | (currZ << 4) | ((currY) << 8); -+ final int blockX = currX | (currChunkX << 4); -+ mutablePos.setX(blockX); -+ -+ final int edgeCount = hasSpecial ? ((blockX == minBlockX || blockX == maxBlockX) ? 1 : 0) + -+ ((blockY == minBlockY || blockY == maxBlockY) ? 1 : 0) + -+ ((blockZ == minBlockZ || blockZ == maxBlockZ) ? 1 : 0) : 0; -+ if (edgeCount == 3) { -+ continue; -+ } -+ -+ final double distance = mutablePos.distToCenterSqr(entityPos); -+ if (distance > selectedDistance || (distance == selectedDistance && selected.compareTo(mutablePos) >= 0)) { -+ continue; -+ } -+ -+ final BlockState blockData = blocks.get(localBlockIndex); -+ -+ if (((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)blockData).moonrise$emptyContextCollisionShape()) { -+ continue; -+ } -+ -+ VoxelShape blockCollision = ((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)blockData).moonrise$getConstantContextCollisionShape(); -+ -+ if (edgeCount == 0 || ((edgeCount != 1 || blockData.hasLargeCollisionShape()) && (edgeCount != 2 || blockData.getBlock() == Blocks.MOVING_PISTON))) { -+ if (blockCollision == null) { -+ blockCollision = blockData.getCollisionShape((Level)(Object)this, mutablePos, collisionShape); -+ -+ if (blockCollision.isEmpty()) { -+ continue; -+ } -+ } -+ -+ // avoid VoxelShape#move by shifting the entity collision shape instead -+ final AABB shiftedAABB = aabb.move(-(double)blockX, -(double)blockY, -(double)blockZ); -+ -+ final AABB singleAABB = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)blockCollision).moonrise$getSingleAABBRepresentation(); -+ if (singleAABB != null) { -+ if (!ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.voxelShapeIntersect(singleAABB, shiftedAABB)) { -+ continue; -+ } -+ -+ selected = mutablePos.immutable(); -+ selectedDistance = distance; -+ continue; -+ } -+ -+ if (!ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.voxelShapeIntersectNoEmpty(blockCollision, shiftedAABB)) { -+ continue; -+ } -+ -+ selected = mutablePos.immutable(); -+ selectedDistance = distance; -+ continue; -+ } - } -- -- selected = pos.immutable(); -- selectedDistance = distance; -- continue; - } -- -- if (!ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.voxelShapeIntersectNoEmpty(blockCollision, shiftedAABB)) { -- continue; -- } -- -- selected = pos.immutable(); -- selectedDistance = distance; -- continue; - } - } - } -@@ -651,6 +674,74 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl - return java.util.Optional.ofNullable(selected); - } - // Paper end - optimise collisions -+ // Paper start - getblock optimisations - cache world height/sections -+ private final int minY; -+ private final int height; -+ private final int maxY; -+ private final int minSectionY; -+ private final int maxSectionY; -+ private final int sectionsCount; -+ -+ @Override -+ public int getMinY() { -+ return this.minY; -+ } -+ -+ @Override -+ public int getHeight() { -+ return this.height; -+ } -+ -+ @Override -+ public int getMaxY() { -+ return this.maxY; -+ } -+ -+ @Override -+ public int getSectionsCount() { -+ return this.sectionsCount; -+ } -+ -+ @Override -+ public int getMinSectionY() { -+ return this.minSectionY; -+ } -+ -+ @Override -+ public int getMaxSectionY() { -+ return this.maxSectionY; -+ } -+ -+ @Override -+ public boolean isInsideBuildHeight(final int blockY) { -+ return blockY >= this.minY && blockY <= this.maxY; -+ } -+ -+ @Override -+ public boolean isOutsideBuildHeight(final BlockPos pos) { -+ return this.isOutsideBuildHeight(pos.getY()); -+ } -+ -+ @Override -+ public boolean isOutsideBuildHeight(final int blockY) { -+ return blockY < this.minY || blockY > this.maxY; -+ } -+ -+ @Override -+ public int getSectionIndex(final int blockY) { -+ return (blockY >> 4) - this.minSectionY; -+ } -+ -+ @Override -+ public int getSectionIndexFromSectionY(final int sectionY) { -+ return sectionY - this.minSectionY; -+ } -+ -+ @Override -+ public int getSectionYFromSectionIndex(final int sectionIdx) { -+ return sectionIdx + this.minSectionY; -+ } -+ // Paper end - getblock optimisations - cache world height/sections - // Paper start - optimise random ticking - @Override - public abstract Holder getUncachedNoiseBiome(final int x, final int y, final int z); -@@ -669,6 +760,15 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl - // Paper end - optimise random ticking - - protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, RegistryAccess iregistrycustom, Holder holder, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function paperWorldConfigCreator) { // Paper - create paper world config -+ // Paper start - getblock optimisations - cache world height/sections -+ final DimensionType dimType = holder.value(); -+ this.minY = dimType.minY(); -+ this.height = dimType.height(); -+ this.maxY = this.minY + this.height - 1; -+ this.minSectionY = this.minY >> 4; -+ this.maxSectionY = this.maxY >> 4; -+ this.sectionsCount = this.maxSectionY - this.minSectionY + 1; -+ // Paper end - getblock optimisations - cache world height/sections - this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot - this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config - this.generator = gen; -@@ -1573,7 +1673,16 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl - if (slices == null) { - return new org.bukkit.entity.Entity[0]; - } -- return slices.getChunkEntities(); -+ -+ List ret = new java.util.ArrayList<>(); -+ for (Entity entity : slices.getAllEntities()) { -+ org.bukkit.entity.Entity bukkit = entity.getBukkitEntity(); -+ if (bukkit != null && bukkit.isValid()) { -+ ret.add(bukkit); -+ } -+ } -+ -+ return ret.toArray(new org.bukkit.entity.Entity[0]); - } - // Paper end - rewrite chunk system - -diff --git a/src/main/java/net/minecraft/world/level/biome/BiomeManager.java b/src/main/java/net/minecraft/world/level/biome/BiomeManager.java -index 01352cc83b25eb0e30b7e0ff521fc7c1b3d5155b..90f8360f547ce709fd13ee34f8e67d8bfa94b498 100644 ---- a/src/main/java/net/minecraft/world/level/biome/BiomeManager.java -+++ b/src/main/java/net/minecraft/world/level/biome/BiomeManager.java -@@ -98,8 +98,7 @@ public class BiomeManager { - } - - private static double getFiddle(long l) { -- double d = (double)Math.floorMod(l >> 24, 1024) / 1024.0; -- return (d - 0.5) * 0.9; -+ return (double)(((l >> 24) & (1024 - 1)) - (1024/2)) * (0.9 / 1024.0); // Paper - avoid floorMod, fp division, and fp subtraction - } - - public interface NoiseBiomeSource { -diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -index a4b4fd83d201fff005c738c84fa5c1bc55d670bd..8631655a181735df53f8a02c9eb98f0cc13f55bb 100644 ---- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -+++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -@@ -838,18 +838,12 @@ public abstract class BlockBehaviour implements FeatureElement { - private int lightBlock; - - // Paper start - rewrite chunk system -- private int opacityIfCached; - private boolean isConditionallyFullOpaque; - - @Override - public final boolean starlight$isConditionallyFullOpaque() { - return this.isConditionallyFullOpaque; - } -- -- @Override -- public final int starlight$getOpacityIfCached() { -- return this.opacityIfCached; -- } - // Paper end - rewrite chunk system - // Paper start - optimise collisions - private static final int RANDOM_OFFSET = 704237939; -@@ -859,16 +853,22 @@ public abstract class BlockBehaviour implements FeatureElement { - private final int id2 = it.unimi.dsi.fastutil.HashCommon.murmurHash3(it.unimi.dsi.fastutil.HashCommon.murmurHash3(ID_GENERATOR.getAndIncrement() + RANDOM_OFFSET) + RANDOM_OFFSET); - private boolean occludesFullBlock; - private boolean emptyCollisionShape; -+ private boolean emptyConstantCollisionShape; - private VoxelShape constantCollisionShape; -- private AABB constantAABBCollision; - -- private static void initCaches(final VoxelShape shape) { -+ private static void initCaches(final VoxelShape shape, final boolean neighbours) { - ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)shape).moonrise$isFullBlock(); - ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)shape).moonrise$occludesFullBlock(); - shape.toAabbs(); - if (!shape.isEmpty()) { - shape.bounds(); - } -+ if (neighbours) { -+ for (final Direction direction : DIRECTIONS_CACHED) { -+ initCaches(((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)shape).moonrise$getFaceShapeClamped(direction), false); -+ initCaches(shape.getFaceShape(direction), false); -+ } -+ } - } - - @Override -@@ -886,6 +886,11 @@ public abstract class BlockBehaviour implements FeatureElement { - return this.emptyCollisionShape; - } - -+ @Override -+ public final boolean moonrise$emptyContextCollisionShape() { -+ return this.emptyConstantCollisionShape; -+ } -+ - @Override - public final int moonrise$uniqueId1() { - return this.id1; -@@ -897,14 +902,9 @@ public abstract class BlockBehaviour implements FeatureElement { - } - - @Override -- public final VoxelShape moonrise$getConstantCollisionShape() { -+ public final VoxelShape moonrise$getConstantContextCollisionShape() { - return this.constantCollisionShape; - } -- -- @Override -- public final AABB moonrise$getConstantCollisionAABB() { -- return this.constantAABBCollision; -- } - // Paper end - optimise collisions - - protected BlockStateBase(Block block, Reference2ObjectArrayMap, Comparable> propertyMap, MapCodec codec) { -@@ -993,39 +993,37 @@ public abstract class BlockBehaviour implements FeatureElement { - this.lightBlock = ((Block) this.owner).getLightBlock(this.asState()); - // Paper start - rewrite chunk system - this.isConditionallyFullOpaque = this.canOcclude & this.useShapeForLightOcclusion; -- this.opacityIfCached = this.cache == null || this.isConditionallyFullOpaque ? -1 : this.cache.lightBlock; - // Paper end - rewrite chunk system - // Paper start - optimise collisions - if (this.cache != null) { - final VoxelShape collisionShape = this.cache.collisionShape; - try { - this.constantCollisionShape = this.getCollisionShape(null, null, null); -- this.constantAABBCollision = this.constantCollisionShape == null ? null : ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)this.constantCollisionShape).moonrise$getSingleAABBRepresentation(); - } catch (final Throwable throwable) { - this.constantCollisionShape = null; -- this.constantAABBCollision = null; - } - this.occludesFullBlock = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)collisionShape).moonrise$occludesFullBlock(); - this.emptyCollisionShape = collisionShape.isEmpty(); -+ this.emptyConstantCollisionShape = this.constantCollisionShape != null && this.constantCollisionShape.isEmpty(); - // init caches -- initCaches(collisionShape); -- if (collisionShape != Shapes.empty() && collisionShape != Shapes.block()) { -- for (final Direction direction : DIRECTIONS_CACHED) { -- // initialise the directional face shape cache as well -- final VoxelShape shape = Shapes.getFaceShape(collisionShape, direction); -- initCaches(shape); -- } -- } -- if (this.cache.occlusionShapes != null) { -- for (final VoxelShape shape : this.cache.occlusionShapes) { -- initCaches(shape); -- } -+ initCaches(collisionShape, true); -+ if (this.constantCollisionShape != null) { -+ initCaches(this.constantCollisionShape, true); - } - } else { - this.occludesFullBlock = false; - this.emptyCollisionShape = false; -+ this.emptyConstantCollisionShape = false; - this.constantCollisionShape = null; -- this.constantAABBCollision = null; -+ } -+ -+ if (this.occlusionShape != null) { -+ initCaches(this.occlusionShape, true); -+ } -+ if (this.occlusionShapesByFace != null) { -+ for (final VoxelShape shape : this.occlusionShapesByFace) { -+ initCaches(shape, true); -+ } - } - // Paper end - optimise collisions - } -diff --git a/src/main/java/net/minecraft/world/level/block/state/StateHolder.java b/src/main/java/net/minecraft/world/level/block/state/StateHolder.java -index 422b364764e0df16ca250b4939d7b226e69c0840..2df28ffc731bd77e0d7af3541cfd3741aa5af83b 100644 ---- a/src/main/java/net/minecraft/world/level/block/state/StateHolder.java -+++ b/src/main/java/net/minecraft/world/level/block/state/StateHolder.java -@@ -15,7 +15,7 @@ import java.util.stream.Collectors; - import javax.annotation.Nullable; - import net.minecraft.world.level.block.state.properties.Property; - --public abstract class StateHolder { -+public abstract class StateHolder implements ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.PropertyAccessStateHolder { // Paper - optimise blockstate property access - public static final String NAME_TAG = "Name"; - public static final String PROPERTIES_TAG = "Properties"; - public static final Function, Comparable>, String> PROPERTY_ENTRY_TO_STRING_FUNCTION = new Function, Comparable>, String>() { -@@ -34,14 +34,28 @@ public abstract class StateHolder { - } - }; - protected final O owner; -- private final Reference2ObjectArrayMap, Comparable> values; -+ private Reference2ObjectArrayMap, Comparable> values; // Paper - optimise blockstate property access - remove final - private Map, S[]> neighbours; - protected final MapCodec propertiesCodec; - -+ // Paper start - optimise blockstate property access -+ protected ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.util.ZeroCollidingReferenceStateTable optimisedTable; -+ protected final long tableIndex; -+ -+ @Override -+ public final long moonrise$getTableIndex() { -+ return this.tableIndex; -+ } -+ // Paper end - optimise blockstate property access -+ - protected StateHolder(O owner, Reference2ObjectArrayMap, Comparable> propertyMap, MapCodec codec) { - this.owner = owner; - this.values = propertyMap; - this.propertiesCodec = codec; -+ // Paper start - optimise blockstate property access -+ this.optimisedTable = new ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.util.ZeroCollidingReferenceStateTable<>(this.values.keySet()); -+ this.tableIndex = this.optimisedTable.getIndex((StateHolder)(Object)this); -+ // Paper end - optimise blockstate property access - } - - public > S cycle(Property property) { -@@ -67,20 +81,21 @@ public abstract class StateHolder { - } - - public Collection> getProperties() { -- return Collections.unmodifiableCollection(this.values.keySet()); -+ return this.optimisedTable.getProperties(); // Paper - optimise blockstate property access - } - - public > boolean hasProperty(Property property) { -- return this.values.containsKey(property); -+ return property != null && this.optimisedTable.hasProperty(property); // Paper - optimise blockstate property access - } - - public > T getValue(Property property) { -- Comparable comparable = this.values.get(property); -- if (comparable == null) { -- throw new IllegalArgumentException("Cannot get property " + property + " as it does not exist in " + this.owner); -- } else { -- return property.getValueClass().cast(comparable); -+ // Paper start - optimise blockstate property access -+ final T ret = this.optimisedTable.get(this.tableIndex, property); -+ if (ret != null) { -+ return ret; - } -+ throw new IllegalArgumentException("Cannot get property " + property + " as it does not exist in " + this.owner); -+ // Paper end - optimise blockstate property access - } - - public > Optional getOptionalValue(Property property) { -@@ -93,22 +108,30 @@ public abstract class StateHolder { - - @Nullable - public > T getNullableValue(Property property) { -- Comparable comparable = this.values.get(property); -- return comparable == null ? null : property.getValueClass().cast(comparable); -+ return property == null ? null : this.optimisedTable.get(this.tableIndex, property); // Paper - optimise blockstate property access - } - - public , V extends T> S setValue(Property property, V value) { -- Comparable comparable = this.values.get(property); -- if (comparable == null) { -- throw new IllegalArgumentException("Cannot set property " + property + " as it does not exist in " + this.owner); -- } else { -- return this.setValueInternal(property, value, comparable); -+ // Paper start - optimise blockstate property access -+ final S ret = this.optimisedTable.set(this.tableIndex, property, value); -+ if (ret != null) { -+ return ret; - } -+ throw new IllegalArgumentException("Cannot set property " + property + " to " + value + " on " + this.owner); -+ // Paper end - optimise blockstate property access - } - - public , V extends T> S trySetValue(Property property, V value) { -- Comparable comparable = this.values.get(property); -- return (S)(comparable == null ? this : this.setValueInternal(property, value, comparable)); -+ // Paper start - optimise blockstate property access -+ if (property == null) { -+ return (S)(StateHolder)(Object)this; -+ } -+ final S ret = this.optimisedTable.trySet(this.tableIndex, property, value, (S)(StateHolder)(Object)this); -+ if (ret != null) { -+ return ret; -+ } -+ throw new IllegalArgumentException("Cannot set property " + property + " to " + value + " on " + this.owner); -+ // Paper end - optimise blockstate property access - } - - private , V extends T> S setValueInternal(Property property, V newValue, Comparable oldValue) { -@@ -125,18 +148,27 @@ public abstract class StateHolder { - } - - public void populateNeighbours(Map, Comparable>, S> states) { -- if (this.neighbours != null) { -- throw new IllegalStateException(); -- } else { -- Map, S[]> map = new Reference2ObjectArrayMap<>(this.values.size()); -+ // Paper start - optimise blockstate property access -+ final Map, Comparable>, S> map = states; -+ if (this.optimisedTable.isLoaded()) { -+ return; -+ } -+ this.optimisedTable.loadInTable(map); - -- for (Entry, Comparable> entry : this.values.entrySet()) { -- Property property = entry.getKey(); -- map.put(property, property.getPossibleValues().stream().map(value -> states.get(this.makeNeighbourValues(property, value))).toArray()); -- } -+ // de-duplicate the tables -+ for (final Map.Entry, Comparable>, S> entry : map.entrySet()) { -+ final S value = entry.getValue(); -+ ((StateHolder)value).optimisedTable = this.optimisedTable; -+ } - -- this.neighbours = map; -+ // remove values arrays -+ for (final Map.Entry, Comparable>, S> entry : map.entrySet()) { -+ final S value = entry.getValue(); -+ ((StateHolder)value).values = null; - } -+ -+ return; -+ // Paper end optimise blockstate property access - } - - private Map, Comparable> makeNeighbourValues(Property property, Comparable value) { -@@ -146,7 +178,11 @@ public abstract class StateHolder { - } - - public Map, Comparable> getValues() { -- return this.values; -+ // Paper start - optimise blockstate property access -+ ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.util.ZeroCollidingReferenceStateTable table = this.optimisedTable; -+ // We have to use this.values until the table is loaded -+ return table.isLoaded() ? table.getMapView(this.tableIndex) : this.values; -+ // Paper end - optimise blockstate property access - } - - protected static > Codec codec(Codec codec, Function ownerToStateFunction) { -diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java -index ea76aa490358e9e1d13350ba0ea246ec2c423894..98058505d36baf74008da08339afc196713b14a7 100644 ---- a/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java -+++ b/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java -@@ -3,13 +3,23 @@ package net.minecraft.world.level.block.state.properties; - import java.util.List; - import java.util.Optional; - --public final class BooleanProperty extends Property { -+public final class BooleanProperty extends Property implements ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.PropertyAccess { // Paper - optimise blockstate property access - private static final List VALUES = List.of(true, false); - private static final int TRUE_INDEX = 0; - private static final int FALSE_INDEX = 1; - -+ // Paper start - optimise blockstate property access -+ private static final Boolean[] BY_ID = new Boolean[]{ Boolean.FALSE, Boolean.TRUE }; -+ -+ @Override -+ public final int moonrise$getIdFor(final Boolean value) { -+ return value.booleanValue() ? 1 : 0; -+ } -+ // Paper end - optimise blockstate property access -+ - private BooleanProperty(String name) { - super(name, Boolean.class); -+ this.moonrise$setById(BY_ID); // Paper - optimise blockstate property access - } - - @Override -diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java -index 85a197232be9377c0313ec00e8f935551e2c60e0..30b2fce9e47ffcc3de1542b1d0f073f5640127a7 100644 ---- a/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java -+++ b/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java -@@ -10,11 +10,39 @@ import java.util.function.Predicate; - import java.util.stream.Collectors; - import net.minecraft.util.StringRepresentable; - --public final class EnumProperty & StringRepresentable> extends Property { -+public final class EnumProperty & StringRepresentable> extends Property implements ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.PropertyAccess { // Paper - optimise blockstate property access - private final List values; - private final Map names; - private final int[] ordinalToIndex; - -+ // Paper start - optimise blockstate property access -+ private int[] idLookupTable; -+ -+ @Override -+ public final int moonrise$getIdFor(final T value) { -+ final Class target = this.getValueClass(); -+ return ((value.getClass() != target && value.getDeclaringClass() != target)) ? -1 : this.idLookupTable[value.ordinal()]; -+ } -+ -+ private void init() { -+ final java.util.Collection values = this.getPossibleValues(); -+ final Class clazz = this.getValueClass(); -+ -+ int id = 0; -+ this.idLookupTable = new int[clazz.getEnumConstants().length]; -+ Arrays.fill(this.idLookupTable, -1); -+ final T[] byId = (T[])java.lang.reflect.Array.newInstance(clazz, values.size()); -+ -+ for (final T value : values) { -+ final int valueId = id++; -+ this.idLookupTable[value.ordinal()] = valueId; -+ byId[valueId] = value; -+ } -+ -+ this.moonrise$setById(byId); -+ } -+ // Paper end - optimise blockstate property access -+ - private EnumProperty(String name, Class type, List values) { - super(name, type); - if (values.isEmpty()) { -@@ -37,6 +65,7 @@ public final class EnumProperty & StringRepresentable> extends - - this.names = builder.buildOrThrow(); - } -+ this.init(); // Paper - optimise blockstate property access - } - - @Override -diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java -index 55a87592a99105dbf57b26fb6ccba695295fce24..986365acc9983331a7982ea2e1eac2b0efe1506d 100644 ---- a/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java -+++ b/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java -@@ -5,11 +5,33 @@ import java.util.List; - import java.util.Optional; - import java.util.stream.IntStream; - --public final class IntegerProperty extends Property { -+public final class IntegerProperty extends Property implements ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.PropertyAccess { // Paper - optimise blockstate property access - private final IntImmutableList values; - public final int min; - public final int max; - -+ // Paper start - optimise blockstate property access -+ @Override -+ public final int moonrise$getIdFor(final Integer value) { -+ final int val = value.intValue(); -+ final int ret = val - this.min; -+ -+ return ret | ((this.max - ret) >> 31); -+ } -+ -+ private void init() { -+ final int min = this.min; -+ final int max = this.max; -+ -+ final Integer[] byId = new Integer[max - min + 1]; -+ for (int i = min; i <= max; ++i) { -+ byId[i - min] = Integer.valueOf(i); -+ } -+ -+ this.moonrise$setById(byId); -+ } -+ // Paper end - optimise blockstate property access -+ - private IntegerProperty(String name, int min, int max) { - super(name, Integer.class); - if (min < 0) { -@@ -21,6 +43,7 @@ public final class IntegerProperty extends Property { - this.max = max; - this.values = IntImmutableList.toList(IntStream.range(min, max + 1)); - } -+ this.init(); // Paper - optimise blockstate property access - } - - @Override -diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/Property.java b/src/main/java/net/minecraft/world/level/block/state/properties/Property.java -index fcf04c5c58ff35d38c5bf0df562ae2f8dc98a0ee..0b116160924300a9d62ad5948bfaf276f0386e4d 100644 ---- a/src/main/java/net/minecraft/world/level/block/state/properties/Property.java -+++ b/src/main/java/net/minecraft/world/level/block/state/properties/Property.java -@@ -10,7 +10,7 @@ import java.util.stream.Stream; - import javax.annotation.Nullable; - import net.minecraft.world.level.block.state.StateHolder; - --public abstract class Property> { -+public abstract class Property> implements ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.PropertyAccess { // Paper - optimise blockstate property access - private final Class clazz; - private final String name; - @Nullable -@@ -24,9 +24,38 @@ public abstract class Property> { - ); - private final Codec> valueCodec = this.codec.xmap(this::value, Property.Value::value); - -+ // Paper start - optimise blockstate property access -+ private static final java.util.concurrent.atomic.AtomicInteger ID_GENERATOR = new java.util.concurrent.atomic.AtomicInteger(); -+ private final int id; -+ private T[] byId; -+ -+ @Override -+ public final int moonrise$getId() { -+ return this.id; -+ } -+ -+ @Override -+ public final T moonrise$getById(final int id) { -+ final T[] byId = this.byId; -+ return id < 0 || id >= byId.length ? null : this.byId[id]; -+ } -+ -+ @Override -+ public final void moonrise$setById(final T[] byId) { -+ if (this.byId != null) { -+ throw new IllegalStateException(); -+ } -+ this.byId = byId; -+ } -+ -+ @Override -+ public abstract int moonrise$getIdFor(final T value); -+ // Paper end - optimise blockstate property access -+ - protected Property(String name, Class type) { - this.clazz = type; - this.name = name; -+ this.id = ID_GENERATOR.getAndIncrement(); // Paper - optimise blockstate property access - } - - public Property.Value value(T value) { -diff --git a/src/main/java/net/minecraft/world/level/chunk/HashMapPalette.java b/src/main/java/net/minecraft/world/level/chunk/HashMapPalette.java -index 98dbeaf8bde15940e5b5d5d1f13fd4bb32f0a10d..7beea075b5a7ef738a4ac0558b99f4c5708f2c4a 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/HashMapPalette.java -+++ b/src/main/java/net/minecraft/world/level/chunk/HashMapPalette.java -@@ -8,12 +8,19 @@ import net.minecraft.network.FriendlyByteBuf; - import net.minecraft.network.VarInt; - import net.minecraft.util.CrudeIncrementalIntIdentityHashBiMap; - --public class HashMapPalette implements Palette { -+public class HashMapPalette implements Palette, ca.spottedleaf.moonrise.patches.fast_palette.FastPalette { // Paper - optimise palette reads - private final IdMap registry; - private final CrudeIncrementalIntIdentityHashBiMap values; - private final PaletteResize resizeHandler; - private final int bits; - -+ // Paper start - optimise palette reads -+ @Override -+ public final T[] moonrise$getRawPalette(final ca.spottedleaf.moonrise.patches.fast_palette.FastPaletteData container) { -+ return ((ca.spottedleaf.moonrise.patches.fast_palette.FastPalette)this.values).moonrise$getRawPalette(container); -+ } -+ // Paper end - optimise palette reads -+ - public HashMapPalette(IdMap idList, int bits, PaletteResize listener, List entries) { - this(idList, bits, listener); - entries.forEach(this.values::add); -diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -index a61294befc2f855fcecb2336a2d5444ce60e0a3a..a0e51681731dc7b487d5b14ae0d44a881bd5cb09 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -@@ -54,7 +54,7 @@ import net.minecraft.world.ticks.LevelChunkTicks; - import net.minecraft.world.ticks.TickContainerAccess; - import org.slf4j.Logger; - --public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk, ca.spottedleaf.moonrise.patches.starlight.chunk.StarlightChunk, ca.spottedleaf.moonrise.patches.chunk_getblock.GetBlockChunk { // Paper - rewrite chunk system // Paper - get block chunk optimisation -+public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk, ca.spottedleaf.moonrise.patches.starlight.chunk.StarlightChunk, ca.spottedleaf.moonrise.patches.getblock.GetBlockChunk { // Paper - rewrite chunk system // Paper - get block chunk optimisation - - static final Logger LOGGER = LogUtils.getLogger(); - private static final TickingBlockEntity NULL_TICKER = new TickingBlockEntity() { -@@ -688,7 +688,7 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p - */ - org.bukkit.Chunk bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this); - server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkLoadEvent(bukkitChunk, this.needsDecoration)); -- ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(this.locX, this.locZ).getEntityChunk().callEntitiesLoadEvent(); // Paper - rewrite chunk system -+ org.bukkit.craftbukkit.event.CraftEventFactory.callEntitiesLoadEvent(this.level, this.chunkPos, ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(this.locX, this.locZ).getEntityChunk().getAllEntities()); // Paper - rewrite chunk system - - if (this.needsDecoration) { - try (co.aikar.timings.Timing ignored = this.level.timings.chunkLoadPopulate.startTiming()) { // Paper -@@ -719,7 +719,7 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p - public void unloadCallback() { - if (!this.loadedTicketLevel) { LOGGER.error("Double calling chunk unload!", new Throwable()); } // Paper - org.bukkit.Server server = this.level.getCraftServer(); -- ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(this.locX, this.locZ).getEntityChunk().callEntitiesUnloadEvent(); // Paper - rewrite chunk system -+ org.bukkit.craftbukkit.event.CraftEventFactory.callEntitiesUnloadEvent(this.level, this.chunkPos, ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(this.locX, this.locZ).getEntityChunk().getAllEntities()); // Paper - rewrite chunk system - org.bukkit.Chunk bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this); - org.bukkit.event.world.ChunkUnloadEvent unloadEvent = new org.bukkit.event.world.ChunkUnloadEvent(bukkitChunk, true); // Paper - rewrite chunk system - force save to true so that mustNotSave is correctly set below - server.getPluginManager().callEvent(unloadEvent); -diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java -index 161211124f3f8390530af7ab21f3a0f1025209c5..4167ed830382c6a76bb281e9d753919925c6bd00 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java -@@ -26,15 +26,17 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ - private PalettedContainer> biomes; // CraftBukkit - read/write - - // Paper start - block counting -- private static final it.unimi.dsi.fastutil.ints.IntArrayList FULL_LIST = new it.unimi.dsi.fastutil.ints.IntArrayList(16*16*16); -+ private static final it.unimi.dsi.fastutil.shorts.ShortArrayList FULL_LIST = new it.unimi.dsi.fastutil.shorts.ShortArrayList(16*16*16); - static { -- for (int i = 0; i < (16*16*16); ++i) { -+ for (short i = 0; i < (16*16*16); ++i) { - FULL_LIST.add(i); - } - } - -- private int specialCollidingBlocks; -- private final ca.spottedleaf.moonrise.common.list.IBlockDataList tickingBlocks = new ca.spottedleaf.moonrise.common.list.IBlockDataList(); -+ private boolean isClient; -+ private static final short CLIENT_FORCED_SPECIAL_COLLIDING_BLOCKS = (short)9999; -+ private short specialCollidingBlocks; -+ private final ca.spottedleaf.moonrise.common.list.ShortList tickingBlocks = new ca.spottedleaf.moonrise.common.list.ShortList(); - - @Override - public final int moonrise$getSpecialCollidingBlocks() { -@@ -42,7 +44,7 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ - } - - @Override -- public final ca.spottedleaf.moonrise.common.list.IBlockDataList moonrise$getTickingBlockList() { -+ public final ca.spottedleaf.moonrise.common.list.ShortList moonrise$getTickingBlockList() { - return this.tickingBlocks; - } - // Paper end - block counting -@@ -86,6 +88,45 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ - return this.setBlockState(x, y, z, state, true); - } - -+ // Paper start - block counting -+ private void updateBlockCallback(final int x, final int y, final int z, final BlockState newState, -+ final BlockState oldState) { -+ if (oldState == newState) { -+ return; -+ } -+ -+ if (this.isClient) { -+ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isSpecialCollidingBlock(newState)) { -+ this.specialCollidingBlocks = CLIENT_FORCED_SPECIAL_COLLIDING_BLOCKS; -+ } -+ return; -+ } -+ -+ final boolean isSpecialOld = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isSpecialCollidingBlock(oldState); -+ final boolean isSpecialNew = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isSpecialCollidingBlock(newState); -+ if (isSpecialOld != isSpecialNew) { -+ if (isSpecialOld) { -+ --this.specialCollidingBlocks; -+ } else { -+ ++this.specialCollidingBlocks; -+ } -+ } -+ -+ final boolean oldTicking = oldState.isRandomlyTicking(); -+ final boolean newTicking = newState.isRandomlyTicking(); -+ if (oldTicking != newTicking) { -+ final ca.spottedleaf.moonrise.common.list.ShortList tickingBlocks = this.tickingBlocks; -+ final short position = (short)(x | (z << 4) | (y << (4+4))); -+ -+ if (oldTicking) { -+ tickingBlocks.remove(position); -+ } else { -+ tickingBlocks.add(position); -+ } -+ } -+ } -+ // Paper end - block counting -+ - public BlockState setBlockState(int x, int y, int z, BlockState state, boolean lock) { - BlockState iblockdata1; - -@@ -105,7 +146,7 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ - } - } - -- if (!fluid.isEmpty()) { -+ if (!!fluid.isRandomlyTicking()) { // Paper - block counting - --this.tickingFluidCount; - } - -@@ -116,25 +157,11 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ - } - } - -- if (!fluid1.isEmpty()) { -+ if (!!fluid1.isRandomlyTicking()) { // Paper - block counting - ++this.tickingFluidCount; - } - -- // Paper start - block counting -- if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isSpecialCollidingBlock(iblockdata1)) { -- --this.specialCollidingBlocks; -- } -- if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isSpecialCollidingBlock(state)) { -- ++this.specialCollidingBlocks; -- } -- -- if (iblockdata1.isRandomlyTicking()) { -- this.tickingBlocks.remove(x, y, z); -- } -- if (state.isRandomlyTicking()) { -- this.tickingBlocks.add(x, y, z, state); -- } -- // Paper end - block counting -+ this.updateBlockCallback(x, y, z, state, iblockdata1); // Paper - block counting - - return iblockdata1; - } -@@ -170,7 +197,7 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ - final int paletteSize = palette.getSize(); - final net.minecraft.util.BitStorage storage = data.storage(); - -- final it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap counts; -+ final it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap counts; - if (paletteSize == 1) { - counts = new it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap<>(1); - counts.put(0, FULL_LIST); -@@ -178,10 +205,10 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ - counts = ((ca.spottedleaf.moonrise.patches.block_counting.BlockCountingBitStorage)storage).moonrise$countEntries(); - } - -- for (final java.util.Iterator> iterator = counts.int2ObjectEntrySet().fastIterator(); iterator.hasNext();) { -- final it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry entry = iterator.next(); -+ for (final java.util.Iterator> iterator = counts.int2ObjectEntrySet().fastIterator(); iterator.hasNext();) { -+ final it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry entry = iterator.next(); - final int paletteIdx = entry.getIntKey(); -- final it.unimi.dsi.fastutil.ints.IntArrayList coordinates = entry.getValue(); -+ final it.unimi.dsi.fastutil.shorts.ShortArrayList coordinates = entry.getValue(); - final int paletteCount = coordinates.size(); - - final BlockState state = palette.valueFor(paletteIdx); -@@ -191,25 +218,30 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ - } - - if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isSpecialCollidingBlock(state)) { -- this.specialCollidingBlocks += paletteCount; -+ this.specialCollidingBlocks += (short)paletteCount; - } -- this.nonEmptyBlockCount += paletteCount; -+ this.nonEmptyBlockCount += (short)paletteCount; - if (state.isRandomlyTicking()) { -- this.tickingBlockCount += paletteCount; -- final int[] raw = coordinates.elements(); -+ this.tickingBlockCount += (short)paletteCount; -+ final short[] raw = coordinates.elements(); -+ final int rawLen = raw.length; -+ -+ final ca.spottedleaf.moonrise.common.list.ShortList tickingBlocks = this.tickingBlocks; -+ -+ tickingBlocks.setMinCapacity(Math.min((rawLen + tickingBlocks.size()) * 3 / 2, 16*16*16)); - - java.util.Objects.checkFromToIndex(0, paletteCount, raw.length); - for (int i = 0; i < paletteCount; ++i) { -- this.tickingBlocks.add(raw[i], state); -+ tickingBlocks.add(raw[i]); - } - } - - final FluidState fluid = state.getFluidState(); - - if (!fluid.isEmpty()) { -- //this.nonEmptyBlockCount += count; // fix vanilla bug: make non empty block count correct -+ //this.nonEmptyBlockCount += count; // fix vanilla bug: make non-empty block count correct - if (fluid.isRandomlyTicking()) { -- this.tickingFluidCount += paletteCount; -+ this.tickingFluidCount += (short)paletteCount; - } - } - } -@@ -232,7 +264,11 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ - - datapaletteblock.read(buf); - this.biomes = datapaletteblock; -- this.recalcBlockCounts(); // Paper - block counting -+ // Paper start - block counting -+ this.isClient = true; -+ // force has special colliding blocks to be true -+ this.specialCollidingBlocks = this.nonEmptyBlockCount != (short)0 && this.maybeHas(ca.spottedleaf.moonrise.patches.collisions.CollisionUtil::isSpecialCollidingBlock) ? CLIENT_FORCED_SPECIAL_COLLIDING_BLOCKS : (short)0; -+ // Paper end - block counting - } - - public void readBiomes(FriendlyByteBuf buf) { -diff --git a/src/main/java/net/minecraft/world/level/chunk/LinearPalette.java b/src/main/java/net/minecraft/world/level/chunk/LinearPalette.java -index bc4d9452bbeb05a691fd285603e49491f41d3ad2..f8d9892970c9092f7cc84434d4fbf34354ce1195 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LinearPalette.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LinearPalette.java -@@ -7,13 +7,20 @@ import net.minecraft.network.FriendlyByteBuf; - import net.minecraft.network.VarInt; - import org.apache.commons.lang3.Validate; - --public class LinearPalette implements Palette { -+public class LinearPalette implements Palette, ca.spottedleaf.moonrise.patches.fast_palette.FastPalette { // Paper - optimise palette reads - private final IdMap registry; - private final T[] values; - private final PaletteResize resizeHandler; - private final int bits; - private int size; - -+ // Paper start - optimise palette reads -+ @Override -+ public final T[] moonrise$getRawPalette(final ca.spottedleaf.moonrise.patches.fast_palette.FastPaletteData container) { -+ return this.values; -+ } -+ // Paper end - optimise palette reads -+ - private LinearPalette(IdMap idList, int bits, PaletteResize listener, List list) { - this.registry = idList; - this.values = (T[])(new Object[1 << bits]); -diff --git a/src/main/java/net/minecraft/world/level/chunk/Palette.java b/src/main/java/net/minecraft/world/level/chunk/Palette.java -index b8922e4a13df535cdc5701e893a6e460b33ff90d..100807f8b8337f56f49cdb818ccc75be2f08ecd1 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/Palette.java -+++ b/src/main/java/net/minecraft/world/level/chunk/Palette.java -@@ -5,7 +5,7 @@ import java.util.function.Predicate; - import net.minecraft.core.IdMap; - import net.minecraft.network.FriendlyByteBuf; - --public interface Palette { -+public interface Palette extends ca.spottedleaf.moonrise.patches.fast_palette.FastPalette { // Paper - optimise palette reads - int idFor(T object); - - boolean maybeHas(Predicate predicate); -diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -index b46c58c952e183bd74854c3eb70d64979af70f18..533167eaa8bd39006fb1c7e193c81359973da9af 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -+++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -@@ -71,6 +71,33 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - ); - } - -+ // Paper start - optimise palette reads -+ private void updateData(final PalettedContainer.Data data) { -+ if (data != null) { -+ ((ca.spottedleaf.moonrise.patches.fast_palette.FastPaletteData)(Object)data).moonrise$setPalette( -+ ((ca.spottedleaf.moonrise.patches.fast_palette.FastPalette)data.palette).moonrise$getRawPalette((ca.spottedleaf.moonrise.patches.fast_palette.FastPaletteData)(Object)data) -+ ); -+ } -+ } -+ -+ private T readPaletteSlow(final PalettedContainer.Data data, final int paletteIdx) { -+ return data.palette.valueFor(paletteIdx); -+ } -+ -+ private T readPalette(final PalettedContainer.Data data, final int paletteIdx) { -+ final T[] palette = ((ca.spottedleaf.moonrise.patches.fast_palette.FastPaletteData)(Object)data).moonrise$getPalette(); -+ if (palette == null) { -+ return this.readPaletteSlow(data, paletteIdx); -+ } -+ -+ final T ret = palette[paletteIdx]; -+ if (ret == null) { -+ throw new IllegalArgumentException("Palette index out of bounds"); -+ } -+ return ret; -+ } -+ // Paper end - optimise palette reads -+ - public PalettedContainer( - IdMap idList, - PalettedContainer.Strategy paletteProvider, -@@ -81,12 +108,14 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - this.registry = idList; - this.strategy = paletteProvider; - this.data = new PalettedContainer.Data<>(dataProvider, storage, dataProvider.factory().create(dataProvider.bits(), idList, this, paletteEntries)); -+ this.updateData(this.data); // Paper - optimise palette reads - } - - private PalettedContainer(IdMap idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Data data) { - this.registry = idList; - this.strategy = paletteProvider; - this.data = data; -+ this.updateData(this.data); // Paper - optimise palette reads - } - - private PalettedContainer(PalettedContainer container) { -@@ -100,6 +129,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - this.registry = idList; - this.data = this.createOrReuseData(null, 0); - this.data.palette.idFor(object); -+ this.updateData(this.data); // Paper - optimise palette reads - } - - private PalettedContainer.Data createOrReuseData(@Nullable PalettedContainer.Data previousData, int bits) { -@@ -115,6 +145,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - PalettedContainer.Data data2 = this.createOrReuseData(data, newBits); - data2.copyFrom(data.palette, data.storage); - this.data = data2; -+ this.updateData(this.data); // Paper - optimise palette reads - return data2.palette.idFor(object); - } - -@@ -136,9 +167,12 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - } - - private synchronized T getAndSet(int index, T value) { // Paper - synchronize -- int i = this.data.palette.idFor(value); -- int j = this.data.storage.getAndSet(index, i); -- return this.data.palette.valueFor(j); -+ // Paper start - optimise palette reads -+ final int paletteIdx = this.data.palette.idFor(value); -+ final PalettedContainer.Data data = this.data; -+ final int prev = data.storage.getAndSet(index, paletteIdx); -+ return this.readPalette(data, prev); -+ // Paper end - optimise palette reads - } - - public void set(int x, int y, int z, T value) { -@@ -162,8 +196,10 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - } - - public T get(int index) { // Paper - public -- PalettedContainer.Data data = this.data; -- return data.palette.valueFor(data.storage.get(index)); -+ // Paper start - optimise palette reads -+ final PalettedContainer.Data data = this.data; -+ return this.readPalette(data, data.storage.get(index)); -+ // Paper end - optimise palette reads - } - - @Override -@@ -183,6 +219,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - data.palette.read(buf); - buf.readLongArray(data.storage.getRaw()); - this.data = data; -+ this.updateData(this.data); // Paper - optimise palette reads - } finally { - this.release(); - } -@@ -323,7 +360,44 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - void accept(T object, int count); - } - -- static record Data(PalettedContainer.Configuration configuration, BitStorage storage, Palette palette) { -+ // Paper start - optimise palette reads -+ public static final class Data implements ca.spottedleaf.moonrise.patches.fast_palette.FastPaletteData { -+ -+ private final PalettedContainer.Configuration configuration; -+ private final BitStorage storage; -+ private final Palette palette; -+ -+ private T[] moonrise$palette; -+ -+ public Data(final PalettedContainer.Configuration configuration, final BitStorage storage, final Palette palette) { -+ this.configuration = configuration; -+ this.storage = storage; -+ this.palette = palette; -+ } -+ -+ public PalettedContainer.Configuration configuration() { -+ return this.configuration; -+ } -+ -+ public BitStorage storage() { -+ return this.storage; -+ } -+ -+ public Palette palette() { -+ return this.palette; -+ } -+ -+ @Override -+ public final T[] moonrise$getPalette() { -+ return this.moonrise$palette; -+ } -+ -+ @Override -+ public final void moonrise$setPalette(final T[] palette) { -+ this.moonrise$palette = palette; -+ } -+ // Paper end - optimise palette reads -+ - public void copyFrom(Palette palette, BitStorage storage) { - for (int i = 0; i < storage.getSize(); i++) { - T object = palette.valueFor(storage.get(i)); -diff --git a/src/main/java/net/minecraft/world/level/chunk/SingleValuePalette.java b/src/main/java/net/minecraft/world/level/chunk/SingleValuePalette.java -index a45e6410600afc5464e5d29932c193786ce0a6fb..a1ba68c95c2cdebdc0d7782cce7895529918073c 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/SingleValuePalette.java -+++ b/src/main/java/net/minecraft/world/level/chunk/SingleValuePalette.java -@@ -8,12 +8,24 @@ import net.minecraft.network.FriendlyByteBuf; - import net.minecraft.network.VarInt; - import org.apache.commons.lang3.Validate; - --public class SingleValuePalette implements Palette { -+public class SingleValuePalette implements Palette, ca.spottedleaf.moonrise.patches.fast_palette.FastPalette { // Paper - optimise palette reads - private final IdMap registry; - @Nullable - private T value; - private final PaletteResize resizeHandler; - -+ // Paper start - optimise palette reads -+ private T[] rawPalette; -+ -+ @Override -+ public final T[] moonrise$getRawPalette(final ca.spottedleaf.moonrise.patches.fast_palette.FastPaletteData container) { -+ if (this.rawPalette != null) { -+ return this.rawPalette; -+ } -+ return this.rawPalette = (T[])new Object[] { this.value }; -+ } -+ // Paper end - optimise palette reads -+ - public SingleValuePalette(IdMap idList, PaletteResize listener, List entries) { - this.registry = idList; - this.resizeHandler = listener; -@@ -33,6 +45,11 @@ public class SingleValuePalette implements Palette { - return this.resizeHandler.onResize(1, object); - } else { - this.value = object; -+ // Paper start - optimise palette reads -+ if (this.rawPalette != null) { -+ this.rawPalette[0] = object; -+ } -+ // Paper end - optimise palette reads - return 0; - } - } -@@ -58,6 +75,11 @@ public class SingleValuePalette implements Palette { - @Override - public void read(FriendlyByteBuf buf) { - this.value = this.registry.byIdOrThrow(buf.readVarInt()); -+ // Paper start - optimise palette reads -+ if (this.rawPalette != null) { -+ this.rawPalette[0] = this.value; -+ } -+ // Paper end - optimise palette reads - } - - @Override -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java -index f1237f6fd6414900ffbad0caee31aa83310eeef4..8071ce70d66909bb4bda45792bf329a939d6f918 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java -@@ -25,7 +25,7 @@ import net.minecraft.util.profiling.jfr.JvmProfiler; - import net.minecraft.world.level.ChunkPos; - import org.slf4j.Logger; - --public class RegionFile implements AutoCloseable { -+public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemRegionFile { // Paper - rewrite chunk system - - private static final Logger LOGGER = LogUtils.getLogger(); - private static final int SECTOR_BYTES = 4096; -@@ -49,6 +49,21 @@ public class RegionFile implements AutoCloseable { - @VisibleForTesting - protected final RegionBitmap usedSectors; - -+ // Paper start - rewrite chunk system -+ @Override -+ public final ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData moonrise$startWrite(final net.minecraft.nbt.CompoundTag data, final ChunkPos pos) throws IOException { -+ final RegionFile.ChunkBuffer buffer = ((RegionFile)(Object)this).new ChunkBuffer(pos); -+ ((ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemChunkBuffer)buffer).moonrise$setWriteOnClose(false); -+ -+ final DataOutputStream out = new DataOutputStream(this.version.wrap(buffer)); -+ -+ return new ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData( -+ data, ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData.WriteResult.WRITE, -+ out, ((ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemChunkBuffer)buffer)::moonrise$write -+ ); -+ } -+ // Paper end - rewrite chunk system -+ - public RegionFile(RegionStorageInfo storageKey, Path directory, Path path, boolean dsync) throws IOException { - this(storageKey, directory, path, RegionFileVersion.getSelected(), dsync); - } -@@ -220,6 +235,16 @@ public class RegionFile implements AutoCloseable { - - @Nullable - private DataInputStream createExternalChunkInputStream(ChunkPos pos, byte flags) throws IOException { -+ // Paper start - rewrite chunk system -+ final DataInputStream is = this.createExternalChunkInputStream0(pos, flags); -+ if (is == null) { -+ return is; -+ } -+ return new ca.spottedleaf.moonrise.patches.chunk_system.util.stream.ExternalChunkStreamMarker(is); -+ } -+ @Nullable -+ private DataInputStream createExternalChunkInputStream0(ChunkPos pos, byte flags) throws IOException { -+ // Paper end - rewrite chunk system - Path path = this.getExternalChunkPath(pos); - - if (!Files.isRegularFile(path, new LinkOption[0])) { -@@ -443,10 +468,29 @@ public class RegionFile implements AutoCloseable { - } - - public static final int MAX_CHUNK_SIZE = 500 * 1024 * 1024; // Paper - don't write garbage data to disk if writing serialization fails -- private class ChunkBuffer extends ByteArrayOutputStream { -+ private class ChunkBuffer extends ByteArrayOutputStream implements ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemChunkBuffer { // Paper - rewrite chunk system - - private final ChunkPos pos; - -+ // Paper start - rewrite chunk system -+ private boolean writeOnClose = true; -+ -+ @Override -+ public final boolean moonrise$getWriteOnClose() { -+ return this.writeOnClose; -+ } -+ -+ @Override -+ public final void moonrise$setWriteOnClose(final boolean value) { -+ this.writeOnClose = value; -+ } -+ -+ @Override -+ public final void moonrise$write(final RegionFile regionFile) throws IOException { -+ regionFile.write(this.pos, ByteBuffer.wrap(this.buf, 0, this.count)); -+ } -+ // Paper end - rewrite chunk system -+ - public ChunkBuffer(final ChunkPos chunkcoordintpair) { - super(8096); - super.write(0); -@@ -480,7 +524,7 @@ public class RegionFile implements AutoCloseable { - - JvmProfiler.INSTANCE.onRegionFileWrite(RegionFile.this.info, this.pos, RegionFile.this.version, i); - bytebuffer.putInt(0, i); -- RegionFile.this.write(this.pos, bytebuffer); -+ if (this.writeOnClose) { RegionFile.this.write(this.pos, bytebuffer); } // Paper - rewrite chunk system - } - } - -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -index 18054304e08c8a6346c0135a0e6a68e77fe5c37c..9dbc9e2f9d5aab71720bb81803efe76e2f361f04 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -@@ -28,8 +28,8 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise - - // Paper start - rewrite chunk system - private static final int REGION_SHIFT = 5; -- private static final int MAX_NON_EXISTING_CACHE = 1024 * 64; -- private final it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet nonExistingRegionFiles = new it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet(MAX_NON_EXISTING_CACHE+1); -+ private static final int MAX_NON_EXISTING_CACHE = 1024 * 4; -+ private final it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet nonExistingRegionFiles = new it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet(); - private static String getRegionFileName(final int chunkX, final int chunkZ) { - return "r." + (chunkX >> REGION_SHIFT) + "." + (chunkZ >> REGION_SHIFT) + ".mca"; - } -@@ -104,6 +104,97 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise - - return ret; - } -+ -+ @Override -+ public final ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData moonrise$startWrite( -+ final int chunkX, final int chunkZ, final CompoundTag compound -+ ) throws IOException { -+ if (compound == null) { -+ return new ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData( -+ compound, ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData.WriteResult.DELETE, -+ null, null -+ ); -+ } -+ -+ final ChunkPos pos = new ChunkPos(chunkX, chunkZ); -+ final RegionFile regionFile = this.getRegionFile(pos); -+ -+ // note: not required to keep regionfile loaded after this call, as the write param takes a regionfile as input -+ // (and, the regionfile parameter is unused for writing until the write call) -+ final ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData writeData = ((ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemRegionFile)regionFile).moonrise$startWrite(compound, pos); -+ -+ try { -+ NbtIo.write(compound, writeData.output()); -+ } finally { -+ writeData.output().close(); -+ } -+ -+ return writeData; -+ } -+ -+ @Override -+ public final void moonrise$finishWrite( -+ final int chunkX, final int chunkZ, final ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData writeData -+ ) throws IOException { -+ final ChunkPos pos = new ChunkPos(chunkX, chunkZ); -+ if (writeData.result() == ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData.WriteResult.DELETE) { -+ final RegionFile regionFile = this.moonrise$getRegionFileIfExists(chunkX, chunkZ); -+ if (regionFile != null) { -+ regionFile.clear(pos); -+ } // else: didn't exist -+ -+ return; -+ } -+ -+ writeData.write().run(this.getRegionFile(pos)); -+ } -+ -+ @Override -+ public final ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData moonrise$readData( -+ final int chunkX, final int chunkZ -+ ) throws IOException { -+ final RegionFile regionFile = this.moonrise$getRegionFileIfExists(chunkX, chunkZ); -+ -+ final DataInputStream input = regionFile == null ? null : regionFile.getChunkDataInputStream(new ChunkPos(chunkX, chunkZ)); -+ -+ if (input == null) { -+ return new ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData( -+ ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData.ReadResult.NO_DATA, null, null -+ ); -+ } -+ -+ final ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData ret = new ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData( -+ ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData.ReadResult.HAS_DATA, input, null -+ ); -+ -+ if (!(input instanceof ca.spottedleaf.moonrise.patches.chunk_system.util.stream.ExternalChunkStreamMarker)) { -+ // internal stream, which is fully read -+ return ret; -+ } -+ -+ final CompoundTag syncRead = this.moonrise$finishRead(chunkX, chunkZ, ret); -+ -+ if (syncRead == null) { -+ // need to try again -+ return this.moonrise$readData(chunkX, chunkZ); -+ } -+ -+ return new ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData( -+ ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData.ReadResult.SYNC_READ, null, syncRead -+ ); -+ } -+ -+ // if the return value is null, then the caller needs to re-try with a new call to readData() -+ @Override -+ public final CompoundTag moonrise$finishRead( -+ final int chunkX, final int chunkZ, final ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData readData -+ ) throws IOException { -+ try { -+ return NbtIo.read(readData.input()); -+ } finally { -+ readData.input().close(); -+ } -+ } - // Paper end - rewrite chunk system - - protected RegionFileStorage(RegionStorageInfo storageKey, Path directory, boolean dsync) { // Paper - protected -@@ -112,6 +203,12 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise - this.info = storageKey; - } - -+ // Paper start - rewrite chunk system -+ public RegionFile getRegionFile(ChunkPos chunkcoordintpair) throws IOException { -+ return this.getRegionFile(chunkcoordintpair, false); -+ } -+ // Paper end - rewrite chunk system -+ - public RegionFile getRegionFile(ChunkPos chunkcoordintpair, boolean existingOnly) throws IOException { // CraftBukkit // Paper - public - // Paper start - rewrite chunk system - if (existingOnly) { -diff --git a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java -index 261e5994d13f8bc30490b86691c80c0a21e7640a..f4fbcbb8ff6d2677af1a02a0801a323c06dce9b1 100644 ---- a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java -+++ b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java -@@ -55,6 +55,48 @@ public abstract class FlowingFluid extends Fluid { - }); - private final Map shapes = Maps.newIdentityHashMap(); - -+ // Paper start - fluid method optimisations -+ private FluidState sourceFalling; -+ private FluidState sourceNotFalling; -+ -+ private static final int TOTAL_FLOWING_STATES = FALLING.getPossibleValues().size() * LEVEL.getPossibleValues().size(); -+ private static final int MIN_LEVEL = LEVEL.getPossibleValues().stream().sorted().findFirst().get().intValue(); -+ -+ // index = (falling ? 1 : 0) + level*2 -+ private FluidState[] flowingLookUp; -+ private volatile boolean init; -+ -+ private static final int COLLISION_OCCLUSION_CACHE_SIZE = 2048; -+ private static final ThreadLocal COLLISION_OCCLUSION_CACHE = ThreadLocal.withInitial(() -> new ca.spottedleaf.moonrise.patches.collisions.util.FluidOcclusionCacheKey[COLLISION_OCCLUSION_CACHE_SIZE]); -+ -+ -+ /** -+ * Due to init order, we need to use callbacks to initialise our state -+ */ -+ private void init() { -+ synchronized (this) { -+ if (this.init) { -+ return; -+ } -+ this.flowingLookUp = new FluidState[TOTAL_FLOWING_STATES]; -+ final FluidState defaultFlowState = this.getFlowing().defaultFluidState(); -+ for (int i = 0; i < TOTAL_FLOWING_STATES; ++i) { -+ final int falling = i & 1; -+ final int level = (i >>> 1) + MIN_LEVEL; -+ -+ this.flowingLookUp[i] = defaultFlowState.setValue(FALLING, falling == 1 ? Boolean.TRUE : Boolean.FALSE) -+ .setValue(LEVEL, Integer.valueOf(level)); -+ } -+ -+ final FluidState defaultFallState = this.getSource().defaultFluidState(); -+ this.sourceFalling = defaultFallState.setValue(FALLING, Boolean.TRUE); -+ this.sourceNotFalling = defaultFallState.setValue(FALLING, Boolean.FALSE); -+ -+ this.init = true; -+ } -+ } -+ // Paper end - fluid method optimisations -+ - public FlowingFluid() {} - - @Override -@@ -246,65 +288,70 @@ public abstract class FlowingFluid extends Fluid { - } - } - -- private static boolean canPassThroughWall(Direction face, BlockGetter world, BlockPos pos, BlockState state, BlockPos fromPos, BlockState fromState) { -- VoxelShape voxelshape = fromState.getCollisionShape(world, fromPos); -+ // Paper start - fluid method optimisations -+ private static boolean canPassThroughWall(final Direction direction, final BlockGetter level, -+ final BlockPos fromPos, final BlockState fromState, -+ final BlockPos toPos, final BlockState toState) { -+ if (((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)fromState).moonrise$emptyCollisionShape() & ((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)toState).moonrise$emptyCollisionShape()) { -+ // don't even try to cache simple cases -+ return true; -+ } - -- if (voxelshape == Shapes.block()) { -+ if (((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)fromState).moonrise$occludesFullBlock() | ((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)toState).moonrise$occludesFullBlock()) { -+ // don't even try to cache simple cases - return false; -- } else { -- VoxelShape voxelshape1 = state.getCollisionShape(world, pos); -- -- if (voxelshape1 == Shapes.block()) { -- return false; -- } else if (voxelshape1 == Shapes.empty() && voxelshape == Shapes.empty()) { -- return true; -- } else { -- Object2ByteLinkedOpenHashMap object2bytelinkedopenhashmap; -- -- if (!state.getBlock().hasDynamicShape() && !fromState.getBlock().hasDynamicShape()) { -- object2bytelinkedopenhashmap = (Object2ByteLinkedOpenHashMap) FlowingFluid.OCCLUSION_CACHE.get(); -- } else { -- object2bytelinkedopenhashmap = null; -- } -+ } - -- FlowingFluid.BlockStatePairKey fluidtypeflowing_a; -+ final ca.spottedleaf.moonrise.patches.collisions.util.FluidOcclusionCacheKey[] cache = ((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)fromState).moonrise$hasCache() & ((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)toState).moonrise$hasCache() ? -+ COLLISION_OCCLUSION_CACHE.get() : null; - -- if (object2bytelinkedopenhashmap != null) { -- fluidtypeflowing_a = new FlowingFluid.BlockStatePairKey(state, fromState, face); -- byte b0 = object2bytelinkedopenhashmap.getAndMoveToFirst(fluidtypeflowing_a); -+ final int keyIndex -+ = (((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)fromState).moonrise$uniqueId1() ^ ((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)toState).moonrise$uniqueId2() ^ ((ca.spottedleaf.moonrise.patches.collisions.util.CollisionDirection)(Object)direction).moonrise$uniqueId()) -+ & (COLLISION_OCCLUSION_CACHE_SIZE - 1); - -- if (b0 != 127) { -- return b0 != 0; -- } -- } else { -- fluidtypeflowing_a = null; -- } -- -- boolean flag = !Shapes.mergedFaceOccludes(voxelshape1, voxelshape, face); -+ if (cache != null) { -+ final ca.spottedleaf.moonrise.patches.collisions.util.FluidOcclusionCacheKey cached = cache[keyIndex]; -+ if (cached != null && cached.first() == fromState && cached.second() == toState && cached.direction() == direction) { -+ return cached.result(); -+ } -+ } - -- if (object2bytelinkedopenhashmap != null) { -- if (object2bytelinkedopenhashmap.size() == 200) { -- object2bytelinkedopenhashmap.removeLastByte(); -- } -+ final VoxelShape shape1 = fromState.getCollisionShape(level, fromPos); -+ final VoxelShape shape2 = toState.getCollisionShape(level, toPos); - -- object2bytelinkedopenhashmap.putAndMoveToFirst(fluidtypeflowing_a, (byte) (flag ? 1 : 0)); -- } -+ final boolean result = !Shapes.mergedFaceOccludes(shape1, shape2, direction); - -- return flag; -- } -+ if (cache != null) { -+ // we can afford to replace in-use keys more often due to the excessive caching the collision patch does in mergedFaceOccludes -+ cache[keyIndex] = new ca.spottedleaf.moonrise.patches.collisions.util.FluidOcclusionCacheKey(fromState, toState, direction, result); - } -+ -+ return result; - } -+ // Paper end - fluid method optimisations - - public abstract Fluid getFlowing(); - - public FluidState getFlowing(int level, boolean falling) { -- return (FluidState) ((FluidState) this.getFlowing().defaultFluidState().setValue(FlowingFluid.LEVEL, level)).setValue(FlowingFluid.FALLING, falling); -+ // Paper start - fluid method optimisations -+ final int amount = level; -+ if (!this.init) { -+ this.init(); -+ } -+ final int index = (falling ? 1 : 0) | ((amount - MIN_LEVEL) << 1); -+ return this.flowingLookUp[index]; -+ // Paper end - fluid method optimisations - } - - public abstract Fluid getSource(); - - public FluidState getSource(boolean falling) { -- return (FluidState) this.getSource().defaultFluidState().setValue(FlowingFluid.FALLING, falling); -+ // Paper start - fluid method optimisations -+ if (!this.init) { -+ this.init(); -+ } -+ return falling ? this.sourceFalling : this.sourceNotFalling; -+ // Paper end - fluid method optimisations - } - - protected abstract boolean canConvertToSource(ServerLevel world); -diff --git a/src/main/java/net/minecraft/world/level/material/FluidState.java b/src/main/java/net/minecraft/world/level/material/FluidState.java -index 87adfe152abd1b8b4d547034576883c5d1cdf134..2d50d72bf026d0cf9c546a3c6fc1859379bfd805 100644 ---- a/src/main/java/net/minecraft/world/level/material/FluidState.java -+++ b/src/main/java/net/minecraft/world/level/material/FluidState.java -@@ -22,12 +22,30 @@ import net.minecraft.world.level.block.state.properties.Property; - import net.minecraft.world.phys.Vec3; - import net.minecraft.world.phys.shapes.VoxelShape; - --public final class FluidState extends StateHolder { -+public final class FluidState extends StateHolder implements ca.spottedleaf.moonrise.patches.fluid.FluidFluidState { // Paper - fluid method optimisations - public static final Codec CODEC = codec(BuiltInRegistries.FLUID.byNameCodec(), Fluid::defaultFluidState).stable(); - public static final int AMOUNT_MAX = 9; - public static final int AMOUNT_FULL = 8; - protected final boolean isEmpty; // Paper - Perf: moved from isEmpty() - -+ // Paper start - fluid method optimisations -+ private int amount; -+ //private boolean isEmpty; -+ private boolean isSource; -+ private float ownHeight; -+ private boolean isRandomlyTicking; -+ private BlockState legacyBlock; -+ -+ @Override -+ public final void moonrise$initCaches() { -+ this.amount = this.getType().getAmount((FluidState)(Object)this); -+ //this.isEmpty = this.getType().isEmpty(); -+ this.isSource = this.getType().isSource((FluidState)(Object)this); -+ this.ownHeight = this.getType().getOwnHeight((FluidState)(Object)this); -+ this.isRandomlyTicking = this.getType().isRandomlyTicking(); -+ } -+ // Paper end - fluid method optimisations -+ - public FluidState(Fluid fluid, Reference2ObjectArrayMap, Comparable> propertyMap, MapCodec codec) { - super(fluid, propertyMap, codec); - this.isEmpty = fluid.isEmpty(); // Paper - Perf: moved from isEmpty() -@@ -38,11 +56,11 @@ public final class FluidState extends StateHolder { - } - - public boolean isSource() { -- return this.getType().isSource(this); -+ return this.isSource; // Paper - fluid method optimisations - } - - public boolean isSourceOfType(Fluid fluid) { -- return this.owner == fluid && this.owner.isSource(this); -+ return this.isSource && this.owner == fluid; // Paper - fluid method optimisations - } - - public boolean isEmpty() { -@@ -54,11 +72,11 @@ public final class FluidState extends StateHolder { - } - - public float getOwnHeight() { -- return this.getType().getOwnHeight(this); -+ return this.ownHeight; // Paper - fluid method optimisations - } - - public int getAmount() { -- return this.getType().getAmount(this); -+ return this.amount; // Paper - fluid method optimisations - } - - public boolean shouldRenderBackwardUpFace(BlockGetter world, BlockPos pos) { -@@ -84,7 +102,7 @@ public final class FluidState extends StateHolder { - } - - public boolean isRandomlyTicking() { -- return this.getType().isRandomlyTicking(); -+ return this.isRandomlyTicking; // Paper - fluid method optimisations - } - - public void randomTick(ServerLevel world, BlockPos pos, RandomSource random) { -@@ -96,7 +114,12 @@ public final class FluidState extends StateHolder { - } - - public BlockState createLegacyBlock() { -- return this.getType().createLegacyBlock(this); -+ // Paper start - fluid method optimisations -+ if (this.legacyBlock != null) { -+ return this.legacyBlock; -+ } -+ return this.legacyBlock = this.getType().createLegacyBlock((FluidState)(Object)this); -+ // Paper end - fluid method optimisations - } - - @Nullable -diff --git a/src/main/java/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java -index 1d36f8dcffd22cf844448d3d8351fb8718cf5227..fbe0c4b0fdbb992b7002f6afe1e74d63cbb420f2 100644 ---- a/src/main/java/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java -+++ b/src/main/java/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java -@@ -57,7 +57,7 @@ public abstract class DiscreteVoxelShape implements ca.spottedleaf.moonrise.patc - } - } - -- final boolean hasSingleAABB = sizeX == 1 && sizeY == 1 && sizeZ == 1 && !isEmpty && discreteVoxelShape.isFull(0, 0, 0); -+ final boolean hasSingleAABB = sizeX == 1 && sizeY == 1 && sizeZ == 1 && !isEmpty && (voxelSet[0] & 1L) != 0L; - - final int minFullX = discreteVoxelShape.firstFull(Direction.Axis.X); - final int minFullY = discreteVoxelShape.firstFull(Direction.Axis.Y); -diff --git a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java -index 672a2038c6d8b31090403766460c6149a75adf8b..513bed7f11aee667c87046db4cf912b80e8f3638 100644 ---- a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java -+++ b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java -@@ -180,13 +180,13 @@ public final class Shapes { - final VoxelShape first = tmp[i]; - final VoxelShape second = tmp[next]; - -- tmp[newSize++] = Shapes.or(first, second); -+ tmp[newSize++] = Shapes.joinUnoptimized(first, second, BooleanOp.OR); - } - } - size = newSize; - } - -- return tmp[0]; -+ return tmp[0].optimize(); - // Paper end - optimise collisions - } - -diff --git a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java -index d850a7de74150a04622da71d9614320f3d5d69e8..3f8e7e29c3e52211a29e6f0a32890f6b53bfd9a8 100644 ---- a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java -+++ b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java -@@ -162,13 +162,13 @@ public abstract class VoxelShape implements ca.spottedleaf.moonrise.patches.coll - - if (direction.getAxisDirection() == Direction.AxisDirection.POSITIVE) { - if (DoubleMath.fuzzyEquals(this.max(axis), 1.0, ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON)) { -- ret = tryForceBlock(new SliceShape((VoxelShape)(Object)this, axis, this.shape.getSize(axis) - 1)); -+ ret = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.sliceShape((VoxelShape)(Object)this, axis, this.shape.getSize(axis) - 1); - } else { - ret = Shapes.empty(); - } - } else { - if (DoubleMath.fuzzyEquals(this.min(axis), 0.0, ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON)) { -- ret = tryForceBlock(new SliceShape((VoxelShape)(Object)this, axis, 0)); -+ ret = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.sliceShape((VoxelShape)(Object)this, axis, 0); - } else { - ret = Shapes.empty(); - } -@@ -179,23 +179,6 @@ public abstract class VoxelShape implements ca.spottedleaf.moonrise.patches.coll - return ret; - } - -- private static VoxelShape tryForceBlock(final VoxelShape other) { -- if (other == Shapes.block()) { -- return other; -- } -- -- final AABB otherAABB = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)other).moonrise$getSingleAABBRepresentation(); -- if (otherAABB == null) { -- return other; -- } -- -- if (((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)Shapes.block()).moonrise$getSingleAABBRepresentation().equals(otherAABB)) { -- return Shapes.block(); -- } -- -- return other; -- } -- - private boolean computeOccludesFullBlock() { - if (this.isEmpty) { - this.occludesFullBlock = Boolean.FALSE; -@@ -293,18 +276,21 @@ public abstract class VoxelShape implements ca.spottedleaf.moonrise.patches.coll - return result; - } - -- private static DoubleList offsetList(final DoubleList src, final double by) { -- if (src instanceof OffsetDoubleList offsetDoubleList) { -- return new OffsetDoubleList(offsetDoubleList.delegate, by + offsetDoubleList.offset); -+ private static DoubleList offsetList(final double[] src, final double by) { -+ final it.unimi.dsi.fastutil.doubles.DoubleArrayList wrap = it.unimi.dsi.fastutil.doubles.DoubleArrayList.wrap(src); -+ if (by == 0.0) { -+ return wrap; - } -- return new OffsetDoubleList(src, by); -+ return new OffsetDoubleList(wrap, by); - } - - private List toAabbsUncached() { -- final List ret = new java.util.ArrayList<>(); -+ final List ret; - if (this.singleAABBRepresentation != null) { -+ ret = new java.util.ArrayList<>(1); - ret.add(this.singleAABBRepresentation); - } else { -+ ret = new java.util.ArrayList<>(); - final double[] coordsX = this.rootCoordinatesX; - final double[] coordsY = this.rootCoordinatesY; - final double[] coordsZ = this.rootCoordinatesZ; -@@ -426,6 +412,26 @@ public abstract class VoxelShape implements ca.spottedleaf.moonrise.patches.coll - final double minDistance = minDistanceArr[0]; - return new BlockHitResult(from.add(minDistance * diffX, minDistance * diffY, minDistance * diffZ), direction, offset, false); - } -+ -+ private VoxelShape calculateFaceDirect(final Direction direction, final Direction.Axis axis, final double[] coords, final double offset) { -+ if (coords.length == 2 && -+ DoubleMath.fuzzyEquals(coords[0] + offset, 0.0, ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) && -+ DoubleMath.fuzzyEquals(coords[1] + offset, 1.0, ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON)) { -+ return (VoxelShape)(Object)this; -+ } -+ -+ final boolean positiveDir = direction.getAxisDirection() == Direction.AxisDirection.POSITIVE; -+ -+ // see findIndex -+ final int index = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.findFloor( -+ coords, (positiveDir ? (1.0 - ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) : (0.0 + ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON)) - offset, -+ 0, coords.length - 1 -+ ); -+ -+ return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.sliceShape( -+ (VoxelShape)(Object)this, axis, index -+ ); -+ } - // Paper end - optimise collisions - - protected VoxelShape(DiscreteVoxelShape voxels) { -@@ -517,20 +523,32 @@ public abstract class VoxelShape implements ca.spottedleaf.moonrise.patches.coll - } - - public VoxelShape singleEncompassing() { -- return this.isEmpty() -- ? Shapes.empty() -- : Shapes.box( -- this.min(Direction.Axis.X), -- this.min(Direction.Axis.Y), -- this.min(Direction.Axis.Z), -- this.max(Direction.Axis.X), -- this.max(Direction.Axis.Y), -- this.max(Direction.Axis.Z) -- ); -+ // Paper start - optimise collisions -+ if (this.isEmpty) { -+ return Shapes.empty(); -+ } -+ return Shapes.create(this.bounds()); -+ // Paper end - optimise collisions - } - - protected double get(Direction.Axis axis, int index) { -- return this.getCoords(axis).getDouble(index); -+ // Paper start - optimise collisions -+ final int idx = index; -+ switch (axis) { -+ case X: { -+ return this.rootCoordinatesX[idx] + this.offsetX; -+ } -+ case Y: { -+ return this.rootCoordinatesY[idx] + this.offsetY; -+ } -+ case Z: { -+ return this.rootCoordinatesZ[idx] + this.offsetZ; -+ } -+ default: { -+ throw new IllegalStateException("Unknown axis: " + axis); -+ } -+ } -+ // Paper end - optimise collisions - } - - public abstract DoubleList getCoords(Direction.Axis axis); -@@ -551,9 +569,9 @@ public abstract class VoxelShape implements ca.spottedleaf.moonrise.patches.coll - - final ArrayVoxelShape ret = new ArrayVoxelShape( - this.shape, -- offsetList(this.getCoords(Direction.Axis.X), x), -- offsetList(this.getCoords(Direction.Axis.Y), y), -- offsetList(this.getCoords(Direction.Axis.Z), z) -+ offsetList(this.rootCoordinatesX, this.offsetX + x), -+ offsetList(this.rootCoordinatesY, this.offsetY + y), -+ offsetList(this.rootCoordinatesZ, this.offsetZ + z) - ); - - final ca.spottedleaf.moonrise.patches.collisions.shape.CachedToAABBs cachedToAABBs = this.cachedToAABBs; -@@ -578,6 +596,11 @@ public abstract class VoxelShape implements ca.spottedleaf.moonrise.patches.coll - - final List aabbs = this.toAabbs(); - -+ if (aabbs.isEmpty()) { -+ // We are a SliceShape, which does not properly fill isEmpty for every case -+ return Shapes.empty(); -+ } -+ - if (aabbs.size() == 1) { - final AABB singleAABB = aabbs.get(0); - final VoxelShape ret = Shapes.create(singleAABB); -@@ -704,7 +727,32 @@ public abstract class VoxelShape implements ca.spottedleaf.moonrise.patches.coll - } - - protected int findIndex(Direction.Axis axis, double coord) { -- return Mth.binarySearch(0, this.shape.getSize(axis) + 1, i -> coord < this.get(axis, i)) - 1; -+ // Paper start - optimise collisions -+ final double value = coord; -+ switch (axis) { -+ case X: { -+ final double[] values = this.rootCoordinatesX; -+ return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.findFloor( -+ values, value - this.offsetX, 0, values.length - 1 -+ ); -+ } -+ case Y: { -+ final double[] values = this.rootCoordinatesY; -+ return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.findFloor( -+ values, value - this.offsetY, 0, values.length - 1 -+ ); -+ } -+ case Z: { -+ final double[] values = this.rootCoordinatesZ; -+ return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.findFloor( -+ values, value - this.offsetZ, 0, values.length - 1 -+ ); -+ } -+ default: { -+ throw new IllegalStateException("Unknown axis: " + axis); -+ } -+ } -+ // Paper end - optimise collisions - } - - @Nullable -@@ -727,13 +775,13 @@ public abstract class VoxelShape implements ca.spottedleaf.moonrise.patches.coll - final AABB singleAABB = this.singleAABBRepresentation; - if (singleAABB != null) { - if (singleAABB.contains(fromBehindOffsetX, fromBehindOffsetY, fromBehindOffsetZ)) { -- return new BlockHitResult(fromBehind, Direction.getNearest(directionOpposite.x, directionOpposite.y, directionOpposite.z).getOpposite(), offset, true); -+ return new BlockHitResult(fromBehind, Direction.getApproximateNearest(directionOpposite.x, directionOpposite.y, directionOpposite.z).getOpposite(), offset, true); - } - return clip(singleAABB, from, to, offset); - } - - if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.strictlyContains((VoxelShape)(Object)this, fromBehindOffsetX, fromBehindOffsetY, fromBehindOffsetZ)) { -- return new BlockHitResult(fromBehind, Direction.getNearest(directionOpposite.x, directionOpposite.y, directionOpposite.z).getOpposite(), offset, true); -+ return new BlockHitResult(fromBehind, Direction.getApproximateNearest(directionOpposite.x, directionOpposite.y, directionOpposite.z).getOpposite(), offset, true); - } - - return AABB.clip(((VoxelShape)(Object)this).toAabbs(), from, to, offset); -@@ -786,20 +834,24 @@ public abstract class VoxelShape implements ca.spottedleaf.moonrise.patches.coll - } - } - -- private VoxelShape calculateFace(Direction facing) { -- Direction.Axis axis = facing.getAxis(); -- if (this.isCubeLikeAlong(axis)) { -- return this; -- } else { -- Direction.AxisDirection axisDirection = facing.getAxisDirection(); -- int i = this.findIndex(axis, axisDirection == Direction.AxisDirection.POSITIVE ? 0.9999999 : 1.0E-7); -- SliceShape sliceShape = new SliceShape(this, axis, i); -- if (sliceShape.isEmpty()) { -- return Shapes.empty(); -- } else { -- return (VoxelShape)(sliceShape.isCubeLike() ? Shapes.block() : sliceShape); -+ private VoxelShape calculateFace(Direction direction) { -+ // Paper start - optimise collisions -+ final Direction.Axis axis = direction.getAxis(); -+ switch (axis) { -+ case X: { -+ return this.calculateFaceDirect(direction, axis, this.rootCoordinatesX, this.offsetX); -+ } -+ case Y: { -+ return this.calculateFaceDirect(direction, axis, this.rootCoordinatesY, this.offsetY); -+ } -+ case Z: { -+ return this.calculateFaceDirect(direction, axis, this.rootCoordinatesZ, this.offsetZ); -+ } -+ default: { -+ throw new IllegalStateException("Unknown axis: " + axis); - } - } -+ // Paper end - optimise collisions - } - - protected boolean isCubeLike() {