diff --git a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/RenderSectionManager.java b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/RenderSectionManager.java index 767c9d0138..d67e1c33b7 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/RenderSectionManager.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/RenderSectionManager.java @@ -360,8 +360,8 @@ public boolean isSectionVisible(int x, int y, int z) { } public void updateChunks(boolean updateImmediately) { + this.sectionCache.cleanup(); this.regions.update(); - this.sectionCache.update(); this.submitRebuildTasks(ChunkUpdateType.IMPORTANT_REBUILD, false); this.submitRebuildTasks(ChunkUpdateType.REBUILD, !updateImmediately); @@ -732,7 +732,6 @@ public Collection getDebugStrings() { this.rebuildQueues.get(ChunkUpdateType.REBUILD).size(), this.rebuildQueues.get(ChunkUpdateType.INITIAL_BUILD).size()) ); - list.add("Chunk cache: " + this.sectionCache.getDebugString()); return list; } diff --git a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/compile/executor/ChunkJobTyped.java b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/compile/executor/ChunkJobTyped.java index d990c5de55..87646c2df2 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/compile/executor/ChunkJobTyped.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/compile/executor/ChunkJobTyped.java @@ -1,6 +1,5 @@ package me.jellysquid.mods.sodium.client.render.chunk.compile.executor; -import me.jellysquid.mods.sodium.client.SodiumClientMod; import me.jellysquid.mods.sodium.client.render.chunk.compile.ChunkBuildContext; import me.jellysquid.mods.sodium.client.render.chunk.compile.tasks.ChunkBuilderTask; @@ -50,8 +49,6 @@ public void execute(ChunkBuildContext context) { } catch (Throwable throwable) { result = ChunkJobResult.exceptionally(throwable); ChunkBuilder.LOGGER.error("Chunk build failed", throwable); - } finally { - this.task.releaseResources(); } try { diff --git a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java index 28a8692c2b..baba0eee78 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java @@ -168,9 +168,4 @@ private CrashException fillCrashInfo(CrashReport report, WorldSlice slice, Block return new CrashException(report); } - - @Override - public void releaseResources() { - this.renderContext.releaseResources(); - } } diff --git a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderTask.java b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderTask.java index 4deb975ac3..a2994ed32e 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderTask.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderTask.java @@ -24,6 +24,4 @@ public abstract class ChunkBuilderTask { * if the task was cancelled. */ public abstract OUTPUT execute(ChunkBuildContext context, CancellationToken cancellationToken); - - public abstract void releaseResources(); } diff --git a/src/main/java/me/jellysquid/mods/sodium/client/world/cloned/ChunkRenderContext.java b/src/main/java/me/jellysquid/mods/sodium/client/world/cloned/ChunkRenderContext.java index c2c051650b..501d1667ec 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/world/cloned/ChunkRenderContext.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/world/cloned/ChunkRenderContext.java @@ -26,12 +26,4 @@ public ChunkSectionPos getOrigin() { public BlockBox getVolume() { return this.volume; } - - public void releaseResources() { - for (var section : this.sections) { - if (section != null) { - section.releaseReference(); - } - } - } } diff --git a/src/main/java/me/jellysquid/mods/sodium/client/world/cloned/ClonedChunkSection.java b/src/main/java/me/jellysquid/mods/sodium/client/world/cloned/ClonedChunkSection.java index 6b01d34bcd..6215ad22bd 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/world/cloned/ClonedChunkSection.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/world/cloned/ClonedChunkSection.java @@ -1,6 +1,8 @@ package me.jellysquid.mods.sodium.client.world.cloned; -import it.unimi.dsi.fastutil.shorts.Short2ShortOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; +import it.unimi.dsi.fastutil.ints.Int2ReferenceMaps; +import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; import me.jellysquid.mods.sodium.client.world.cloned.palette.ClonedPalette; import me.jellysquid.mods.sodium.client.world.cloned.palette.ClonedPaletteFallback; import me.jellysquid.mods.sodium.client.world.cloned.palette.ClonedPalleteArray; @@ -18,40 +20,28 @@ import net.minecraft.world.World; import net.minecraft.world.biome.Biome; import net.minecraft.world.chunk.*; -import org.apache.commons.lang3.Validate; -import java.util.Arrays; +import java.util.EnumMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; public class ClonedChunkSection { private static final LightType[] LIGHT_TYPES = LightType.values(); - private final BlockEntity[] blockEntity = new BlockEntity[16 * 16 * 16]; - private final Object[] blockEntityAttachments = new Object[16 * 16 * 16]; + private Int2ReferenceMap blockEntities; + private Int2ReferenceMap blockEntityAttachments; - private final Short2ShortOpenHashMap blockEntityIds; - private final ChunkNibbleArray[] lightDataArrays; + private final ChunkNibbleArray[] lightDataArrays = new ChunkNibbleArray[LIGHT_TYPES.length]; - private ChunkSectionPos pos; + private final ChunkSectionPos pos; private PackedIntegerArray blockStateData; private ClonedPalette blockStatePalette; private ReadableContainer> biomeData; - private boolean empty = true; - - ClonedChunkSection() { - this.blockEntityIds = new Short2ShortOpenHashMap(); - this.blockEntityIds.defaultReturnValue((short) -1); - - this.lightDataArrays = new ChunkNibbleArray[LIGHT_TYPES.length]; - } - - public void copy(World world, WorldChunk chunk, ChunkSection section, ChunkSectionPos pos) { - Validate.isTrue(this.empty); + private long lastUsedTimestamp = Long.MAX_VALUE; + public ClonedChunkSection(World world, WorldChunk chunk, ChunkSection section, ChunkSectionPos pos) { this.pos = pos; this.copyBlockData(section); @@ -60,22 +50,6 @@ public void copy(World world, WorldChunk chunk, ChunkSection section, ChunkSecti this.copyBlockEntities(chunk, pos); } - void clear() { - Arrays.fill(this.blockEntity, 0, this.blockEntityIds.size(), null); - Arrays.fill(this.blockEntityAttachments, 0, this.blockEntityIds.size(), null); - - this.blockEntityIds.clear(); - - this.blockStateData = null; - this.blockStatePalette = null; - - this.biomeData = null; - - Arrays.fill(this.lightDataArrays, null); - - this.empty = true; - } - private void copyBlockData(ChunkSection section) { PalettedContainer.Data container = ((PalettedContainerAccessor) section.getBlockStateContainer()).getData(); @@ -103,32 +77,41 @@ private void copyBlockEntities(WorldChunk chunk, ChunkSectionPos chunkCoord) { BlockBox box = new BlockBox(chunkCoord.getMinX(), chunkCoord.getMinY(), chunkCoord.getMinZ(), chunkCoord.getMaxX(), chunkCoord.getMaxY(), chunkCoord.getMaxZ()); + Int2ReferenceOpenHashMap blockEntities = null; + // Copy the block entities from the chunk into our cloned section for (Map.Entry entry : chunk.getBlockEntities().entrySet()) { BlockPos pos = entry.getKey(); BlockEntity entity = entry.getValue(); if (box.contains(pos)) { - var id = (short) this.blockEntityIds.size(); - var prev = this.blockEntityIds.put(ChunkSectionPos.packLocal(pos), id); - - if (prev != this.blockEntityIds.defaultReturnValue()) { - throw new IllegalStateException("Already inserted block entity at " + pos); + if (blockEntities == null) { + blockEntities = new Int2ReferenceOpenHashMap<>(); } - this.blockEntity[id] = entity; + blockEntities.put(packLocal(pos.getX() & 15, pos.getY() & 15, pos.getZ() & 15), entity); } } + this.blockEntities = blockEntities != null ? blockEntities : Int2ReferenceMaps.emptyMap(); + + Int2ReferenceOpenHashMap blockEntityAttachments = null; + // Retrieve any render attachments after we have copied all block entities, as this will call into the code of // other mods. This could potentially result in the chunk being modified, which would cause problems if we // were iterating over any data in that chunk. // See https://github.com/CaffeineMC/sodium-fabric/issues/942 for more info. - for (int i = 0; i < this.blockEntityIds.size(); i++) { - if (this.blockEntity[i] instanceof RenderAttachmentBlockEntity holder) { - this.blockEntityAttachments[i] = holder.getRenderAttachmentData(); + for (var entry : Int2ReferenceMaps.fastIterable(this.blockEntities)) { + if (entry.getValue() instanceof RenderAttachmentBlockEntity holder) { + if (blockEntityAttachments == null) { + blockEntityAttachments = new Int2ReferenceOpenHashMap<>(); + } + + blockEntityAttachments.put(entry.getIntKey(), holder.getRenderAttachmentData()); } } + + this.blockEntityAttachments = blockEntityAttachments != null ? blockEntityAttachments : Int2ReferenceMaps.emptyMap(); } public RegistryEntry getBiome(int x, int y, int z) { @@ -136,23 +119,11 @@ public RegistryEntry getBiome(int x, int y, int z) { } public BlockEntity getBlockEntity(int x, int y, int z) { - var id = this.blockEntityIds.get(packLocal(x, y, z)); - - if (id < 0) { - return null; - } - - return this.blockEntity[id]; + return this.blockEntities.get(packLocal(x, y, z)); } public Object getBlockEntityRenderAttachment(int x, int y, int z) { - var id = this.blockEntityIds.get(packLocal(x, y, z)); - - if (id < 0) { - return null; - } - - return this.blockEntityAttachments[id]; + return this.blockEntityAttachments.get(packLocal(x, y, z)); } public PackedIntegerArray getBlockData() { @@ -207,7 +178,7 @@ private static short packLocal(int x, int y, int z) { } public int getLightLevel(LightType type, int x, int y, int z) { - var array = this.lightDataArrays[type.ordinal()]; + var array = this.getLightArray(type); // The sky-light array may not exist in certain dimensions. if (array == null) { @@ -217,14 +188,6 @@ public int getLightLevel(LightType type, int x, int y, int z) { return array.get(x, y, z); } - private final AtomicInteger referenceCount = new AtomicInteger(0); - - private long lastUsedTimestamp; - - public int getReferenceCount() { - return this.referenceCount.get(); - } - public long getLastUsedTimestamp() { return this.lastUsedTimestamp; } @@ -232,12 +195,4 @@ public long getLastUsedTimestamp() { public void setLastUsedTimestamp(long timestamp) { this.lastUsedTimestamp = timestamp; } - - public void acquireReference() { - this.referenceCount.getAndIncrement(); - } - - public void releaseReference() { - this.referenceCount.getAndDecrement(); - } } diff --git a/src/main/java/me/jellysquid/mods/sodium/client/world/cloned/ClonedChunkSectionCache.java b/src/main/java/me/jellysquid/mods/sodium/client/world/cloned/ClonedChunkSectionCache.java index e2c21f5233..3b93f3bb5c 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/world/cloned/ClonedChunkSectionCache.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/world/cloned/ClonedChunkSectionCache.java @@ -1,119 +1,59 @@ package me.jellysquid.mods.sodium.client.world.cloned; import it.unimi.dsi.fastutil.longs.Long2ReferenceLinkedOpenHashMap; -import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet; import net.minecraft.util.math.ChunkSectionPos; import net.minecraft.world.World; import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.ChunkSection; import net.minecraft.world.chunk.WorldChunk; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.ArrayDeque; import java.util.concurrent.TimeUnit; public class ClonedChunkSectionCache { private static final int MAX_CACHE_SIZE = 512; /* number of entries */ - private static final long MAX_CACHE_DURATION = TimeUnit.SECONDS.toNanos(5); /* number of seconds */ + private static final long MAX_CACHE_DURATION = TimeUnit.SECONDS.toNanos(5); /* number of nanoseconds */ private final World world; private final Long2ReferenceLinkedOpenHashMap positionToEntry = new Long2ReferenceLinkedOpenHashMap<>(); - private final ReferenceLinkedOpenHashSet inactiveEntries = new ReferenceLinkedOpenHashSet<>(); - private final ReferenceLinkedOpenHashSet invalidatedEntries = new ReferenceLinkedOpenHashSet<>(); - - private final ArrayDeque freeEntries = new ArrayDeque<>(); - - private long currentTime = System.nanoTime(); + private long time; // updated once per frame to be the elapsed time since application start public ClonedChunkSectionCache(World world) { this.world = world; + this.time = getMonotonicTimeSource(); } - public void update() { - this.currentTime = System.nanoTime(); - - this.invalidateExcessiveEntries(); - this.invalidateOutdatedEntries(); - - this.tryReclaimInvalidated(); - - this.markInactiveEntries(); - } - - private void markInactiveEntries() { - this.inactiveEntries.clear(); - - for (var entry : this.positionToEntry.values()) { - if (entry.getReferenceCount() == 0) { - this.inactiveEntries.add(entry); - } - } - } - - private void tryReclaimInvalidated() { - var it = this.invalidatedEntries.iterator(); - - while (it.hasNext()) { - var entry = it.next(); - - if (entry.getReferenceCount() == 0) { - entry.clear(); - - if (this.freeEntries.size() < 256) { - this.freeEntries.add(entry); - } - - it.remove(); - } - } - } - - private void invalidateOutdatedEntries() { - var it = this.positionToEntry.values() - .iterator(); - - while (it.hasNext()) { - var entry = it.next(); - - if (this.currentTime > (entry.getLastUsedTimestamp() + MAX_CACHE_DURATION)) { - it.remove(); - - this.invalidatedEntries.add(entry); - } - } - } - - private void invalidateExcessiveEntries() { - // Ensure the cache does not get too large, by removing the oldest entries within it - while (this.positionToEntry.size() > MAX_CACHE_SIZE) { - var entry = this.positionToEntry.removeFirst(); - - this.invalidatedEntries.add(entry); - } + public void cleanup() { + this.time = getMonotonicTimeSource(); + this.positionToEntry.values() + .removeIf(entry -> this.time > (entry.getLastUsedTimestamp() + MAX_CACHE_DURATION)); } @Nullable public ClonedChunkSection acquire(int x, int y, int z) { - var cloned = this.positionToEntry.getAndMoveToLast(ChunkSectionPos.asLong(x, y, z)); + var pos = ChunkSectionPos.asLong(x, y, z); + var section = this.positionToEntry.getAndMoveToLast(pos); - if (cloned != null) { - this.inactiveEntries.remove(cloned); - } else { - cloned = this.clone(x, y, z); + if (section == null) { + section = this.clone(x, y, z); // There was nothing to clone, because that section is empty - if (cloned == null) { + if (section == null) { return null; } + + while (this.positionToEntry.size() >= MAX_CACHE_SIZE) { + this.positionToEntry.removeFirst(); + } + + this.positionToEntry.putAndMoveToLast(pos, section); } - cloned.acquireReference(); - cloned.setLastUsedTimestamp(this.currentTime); + section.setLastUsedTimestamp(this.time); - return cloned; + return section; } @Nullable @@ -130,67 +70,13 @@ private ClonedChunkSection clone(int x, int y, int z) { return null; } - ChunkSectionPos sectionCoord = ChunkSectionPos.from(x, y, z); - - ClonedChunkSection clonedSection = this.allocate(); - - clonedSection.setLastUsedTimestamp(this.currentTime); - clonedSection.copy(this.world, chunk, section, sectionCoord); - - this.positionToEntry.putAndMoveToLast(sectionCoord.asLong(), clonedSection); - - return clonedSection; - } - - @NotNull - private ClonedChunkSection allocate() { - if (!this.freeEntries.isEmpty()) { - return this.takeFreeEntry(); - } - - if (!this.inactiveEntries.isEmpty()) { - return this.takeInactiveEntry(); - } - - return new ClonedChunkSection(); - } - - @NotNull - private ClonedChunkSection takeFreeEntry() { - return this.freeEntries.removeFirst(); - } - - @NotNull - private ClonedChunkSection takeInactiveEntry() { - var section = this.inactiveEntries.removeFirst(); - var position = section.getPosition(); - - this.positionToEntry.remove(position.asLong()); + ChunkSectionPos pos = ChunkSectionPos.from(x, y, z); - section.clear(); - - return section; + return new ClonedChunkSection(this.world, chunk, section, pos); } public void invalidate(int x, int y, int z) { - // When invalidating, the section is immediately removed from the cache, which prevents - // it from being taken by future build tasks. - var section = this.positionToEntry.remove(ChunkSectionPos.asLong(x, y, z)); - - if (section != null) { - this.invalidate(section); - } - } - - private void invalidate(ClonedChunkSection section) { - this.invalidatedEntries.add(section); - } - - public String getDebugString() { - int total = this.positionToEntry.size() + this.invalidatedEntries.size() + this.freeEntries.size(); - - return String.format("T=%03d (IA=%03d | IV=%03d | F=%03d)", - total, this.inactiveEntries.size(), this.invalidatedEntries.size(), this.freeEntries.size()); + this.positionToEntry.remove(ChunkSectionPos.asLong(x, y, z)); } @Nullable @@ -203,4 +89,9 @@ private static ChunkSection getChunkSection(World world, Chunk chunk, int y) { return section; } + + private static long getMonotonicTimeSource() { + // Should be monotonic in JDK 17 on sane platforms... + return System.nanoTime(); + } }