From 78cf3134a770bd0d095e8e1ca54ffdf42a60fa41 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Fri, 29 Dec 2023 16:16:51 -0500 Subject: [PATCH] Minor optimizations for chunk retrieval --- .../vintagefix/chunk/VintageChunkMap.java | 65 +++++++++++++++++++ .../mixin/chunk_access/ChunkMixin.java | 42 ++++++++++++ .../ChunkProviderClientMixin.java | 14 ++++ .../ChunkProviderServerMixin.java | 16 +++++ 4 files changed, 137 insertions(+) create mode 100644 src/main/java/org/embeddedt/vintagefix/chunk/VintageChunkMap.java create mode 100644 src/main/java/org/embeddedt/vintagefix/mixin/chunk_access/ChunkMixin.java create mode 100644 src/main/java/org/embeddedt/vintagefix/mixin/chunk_access/ChunkProviderClientMixin.java create mode 100644 src/main/java/org/embeddedt/vintagefix/mixin/chunk_access/ChunkProviderServerMixin.java diff --git a/src/main/java/org/embeddedt/vintagefix/chunk/VintageChunkMap.java b/src/main/java/org/embeddedt/vintagefix/chunk/VintageChunkMap.java new file mode 100644 index 0000000..ffe9299 --- /dev/null +++ b/src/main/java/org/embeddedt/vintagefix/chunk/VintageChunkMap.java @@ -0,0 +1,65 @@ +package org.embeddedt.vintagefix.chunk; + +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.chunk.Chunk; + +/** + * This class implements a small LRU cache on top of Long2ObjectOpenHashMap to enable + * faster chunk lookups if the same chunk is retrieved many times. + */ +public class VintageChunkMap extends Long2ObjectOpenHashMap { + private static final int CACHE_SIZE = 4; + private final long[] cachedChunkPositions = new long[CACHE_SIZE]; + private final Chunk[] cachedChunks = new Chunk[CACHE_SIZE]; + + public VintageChunkMap() { + super(8192); + } + + @Override + public Chunk get(long key) { + for (int i = 0; i < 4; ++i) { + // Consolidate the scan into one comparison, allowing the JVM to better optimize the function + // This is considerably faster than scanning two arrays side-by-side + if (key == cachedChunkPositions[i]) { + Chunk chunk = this.cachedChunks[i]; + + // If the chunk exists for the key, return the result + // We also check that the position matches, in case mods try to access + // chunks on the wrong thread + if (chunk != null && ChunkPos.asLong(chunk.x, chunk.z) == key) { + return chunk; + } + } + } + + Chunk chunk = super.get(key); + + if (chunk != null) { + this.addToCache(key, chunk); + } + + return chunk; + } + + @Override + public Chunk remove(long k) { + for (int i = 0; i < 4; ++i) { + if (k == cachedChunkPositions[i]) { + cachedChunks[i] = null; + } + } + return super.remove(k); + } + + private void addToCache(long key, Chunk chunk) { + for (int i = CACHE_SIZE - 1; i > 0; --i) { + this.cachedChunkPositions[i] = this.cachedChunkPositions[i - 1]; + this.cachedChunks[i] = this.cachedChunks[i - 1]; + } + + this.cachedChunkPositions[0] = key; + this.cachedChunks[0] = chunk; + } +} diff --git a/src/main/java/org/embeddedt/vintagefix/mixin/chunk_access/ChunkMixin.java b/src/main/java/org/embeddedt/vintagefix/mixin/chunk_access/ChunkMixin.java new file mode 100644 index 0000000..b741720 --- /dev/null +++ b/src/main/java/org/embeddedt/vintagefix/mixin/chunk_access/ChunkMixin.java @@ -0,0 +1,42 @@ +package org.embeddedt.vintagefix.mixin.chunk_access; + +import net.minecraft.block.state.IBlockState; +import net.minecraft.init.Blocks; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.storage.ExtendedBlockStorage; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(Chunk.class) +public class ChunkMixin { + @Shadow + @Final + private ExtendedBlockStorage[] storageArrays; + + @Shadow + @Final + public static ExtendedBlockStorage NULL_BLOCK_STORAGE; + + private static final IBlockState DEFAULT_BLOCK_STATE = Blocks.AIR.getDefaultState(); + + /** + * @reason Reduce method size to help the JVM inline + * @author JellySquid + */ + @Overwrite + public IBlockState getBlockState(int x, int y, int z) { + if (y >= 0 && (y >> 4) < this.storageArrays.length) { + ExtendedBlockStorage section = this.storageArrays[y >> 4]; + + if (section != NULL_BLOCK_STORAGE) { + return section.get(x & 15, y & 15, z & 15); + } + } + + return DEFAULT_BLOCK_STATE; + } +} diff --git a/src/main/java/org/embeddedt/vintagefix/mixin/chunk_access/ChunkProviderClientMixin.java b/src/main/java/org/embeddedt/vintagefix/mixin/chunk_access/ChunkProviderClientMixin.java new file mode 100644 index 0000000..3e5bb23 --- /dev/null +++ b/src/main/java/org/embeddedt/vintagefix/mixin/chunk_access/ChunkProviderClientMixin.java @@ -0,0 +1,14 @@ +package org.embeddedt.vintagefix.mixin.chunk_access; + +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import net.minecraft.client.multiplayer.ChunkProviderClient; +import net.minecraft.world.chunk.Chunk; +import org.embeddedt.vintagefix.chunk.VintageChunkMap; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(ChunkProviderClient.class) +public class ChunkProviderClientMixin { + @Shadow @Final private final Long2ObjectMap loadedChunks = new VintageChunkMap(); +} diff --git a/src/main/java/org/embeddedt/vintagefix/mixin/chunk_access/ChunkProviderServerMixin.java b/src/main/java/org/embeddedt/vintagefix/mixin/chunk_access/ChunkProviderServerMixin.java new file mode 100644 index 0000000..b2192d5 --- /dev/null +++ b/src/main/java/org/embeddedt/vintagefix/mixin/chunk_access/ChunkProviderServerMixin.java @@ -0,0 +1,16 @@ +package org.embeddedt.vintagefix.mixin.chunk_access; + +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.gen.ChunkProviderServer; +import org.embeddedt.vintagefix.chunk.VintageChunkMap; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(ChunkProviderServer.class) +public class ChunkProviderServerMixin { + @Shadow + @Final + private final Long2ObjectMap loadedChunks = new VintageChunkMap(); +}