From 376ac76ac25ecf31de400ed18c21bd4adcad0738 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Fri, 8 Nov 2024 09:32:02 -0800 Subject: [PATCH] A whole lut of refactors - Replace monolithic lut building function with a class representing a layer of the lut - Actually need 2 classes because int[] and Object[] aren't trivial to make a type parameter, and we don't really want to be boxing ints here - No longer need sorted inputs - Should fix index out of bounds crash caused by reserving space for the wrong index layer - This will make it much easier to change the coordinate ordering scheme --- .../flywheel/backend/engine/LightLut.java | 222 ++++++++++-------- 1 file changed, 127 insertions(+), 95 deletions(-) diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/LightLut.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/LightLut.java index bb04c7760..d17ed806d 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/LightLut.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/LightLut.java @@ -1,32 +1,34 @@ package dev.engine_room.flywheel.backend.engine; -import org.jetbrains.annotations.NotNull; +import java.util.function.BiConsumer; +import java.util.function.Supplier; import it.unimi.dsi.fastutil.ints.IntArrayList; -import it.unimi.dsi.fastutil.ints.IntObjectImmutablePair; -import it.unimi.dsi.fastutil.ints.IntObjectPair; import it.unimi.dsi.fastutil.longs.Long2IntMap; -import it.unimi.dsi.fastutil.longs.LongArrayList; -import it.unimi.dsi.fastutil.longs.LongComparator; -import it.unimi.dsi.fastutil.objects.ReferenceArrayList; import net.minecraft.core.SectionPos; public final class LightLut { - private static final LongComparator SECTION_X_THEN_Y_THEN_Z = (long a, long b) -> { - final var xComp = Integer.compare(SectionPos.x(a), SectionPos.x(b)); - if (xComp != 0) { - return xComp; - } - var yComp = Integer.compare(SectionPos.y(a), SectionPos.y(b)); - if (yComp != 0) { - return yComp; - } - return Integer.compare(SectionPos.z(a), SectionPos.z(b)); - }; + private final Layer> indices = new Layer<>(); private LightLut() { } + private void add(long position, int index) { + final var x = SectionPos.x(position); + final var y = SectionPos.y(position); + final var z = SectionPos.z(position); + + indices.computeIfAbsent(x, Layer::new) + .computeIfAbsent(y, IntLayer::new) + .set(z, index + 1); + } + + private IntArrayList toLut() { + final var out = new IntArrayList(); + indices.fillLut(out, (yIndices, lut) -> yIndices.fillLut(lut, IntLayer::fillLut)); + return out; + } + // Massive kudos to RogueLogix for figuring out this LUT scheme. // TODO: switch to y x z or x z y ordering // DATA LAYOUT @@ -38,105 +40,135 @@ public static IntArrayList buildLut(Long2IntMap sectionIndicesMaps) { if (sectionIndicesMaps.isEmpty()) { return new IntArrayList(); } - final var positions = sortedKeys(sectionIndicesMaps); - final var baseX = SectionPos.x(positions.getLong(0)); - return buildLut(baseX, buildIndices(sectionIndicesMaps, positions, baseX)); + var out = new LightLut(); + + sectionIndicesMaps.forEach(out::add); + + return out.toLut(); } - private static ReferenceArrayList>> buildIndices(Long2IntMap sectionIndicesMaps, LongArrayList positions, int baseX) { - final var indices = new ReferenceArrayList>>(); - for (long position : positions) { - final var x = SectionPos.x(position); - final var y = SectionPos.y(position); - final var z = SectionPos.z(position); - - final var xIndex = x - baseX; - if (indices.size() <= xIndex) { - indices.ensureCapacity(xIndex + 1); - indices.size(xIndex + 1); + private static final class Layer { + private boolean hasBase = false; + private int base = 0; + private Object[] nextLayer = new Object[0]; + + public void fillLut(IntArrayList lut, BiConsumer inner) { + lut.add(base); + lut.add(nextLayer.length); + + int innerIndexBase = lut.size(); + + // Reserve space for the inner indices... + lut.size(innerIndexBase + nextLayer.length); + + for (int i = 0; i < nextLayer.length; i++) { + final var innerIndices = (T) nextLayer[i]; + if (innerIndices == null) { + continue; + } + + int layerPosition = lut.size(); + + // ...so we can write in their actual positions later. + lut.set(innerIndexBase + i, layerPosition); + + // Append the next layer to the lut. + inner.accept(innerIndices, lut); } - var yLookup = indices.get(xIndex); - if (yLookup == null) { - //noinspection SuspiciousNameCombination - yLookup = new IntObjectImmutablePair<>(y, new ReferenceArrayList<>()); - indices.set(xIndex, yLookup); + } + + public T computeIfAbsent(int i, Supplier ifAbsent) { + if (!hasBase) { + // We don't want to default to base 0, so we'll use the first value we get. + base = i; + hasBase = true; } - final var yIndices = yLookup.right(); - final var yIndex = y - yLookup.leftInt(); - if (yIndices.size() <= yIndex) { - yIndices.ensureCapacity(yIndex + 1); - yIndices.size(yIndex + 1); + if (i < base) { + rebase(i); } - var zLookup = yIndices.get(yIndex); - if (zLookup == null) { - zLookup = new IntArrayList(); - zLookup.add(z); - zLookup.add(0); // this value will be filled in later - yIndices.set(yIndex, zLookup); + + final var offset = i - base; + + if (offset >= nextLayer.length) { + resize(offset + 1); } - final var zIndex = z - zLookup.getInt(0); - if ((zLookup.size() - 2) <= zIndex) { - zLookup.ensureCapacity(zIndex + 3); - zLookup.size(zIndex + 3); + var out = nextLayer[offset]; + + if (out == null) { + out = ifAbsent.get(); + nextLayer[offset] = out; } - // Add 1 to the actual index so that 0 indicates a missing section. - zLookup.set(zIndex + 2, sectionIndicesMaps.get(position) + 1); + return (T) out; } - return indices; - } - private static @NotNull LongArrayList sortedKeys(Long2IntMap sectionIndicesMaps) { - final var out = new LongArrayList(sectionIndicesMaps.keySet()); - out.unstableSort(SECTION_X_THEN_Y_THEN_Z); - return out; + private void resize(int length) { + final var newIndices = new Object[length]; + System.arraycopy(nextLayer, 0, newIndices, 0, nextLayer.length); + nextLayer = newIndices; + } + + private void rebase(int newBase) { + final var growth = base - newBase; + + final var newIndices = new Object[nextLayer.length + growth]; + // Shift the existing elements to the end of the new array to maintain their offset with the new base. + System.arraycopy(nextLayer, 0, newIndices, growth, nextLayer.length); + + nextLayer = newIndices; + base = newBase; + } } - private static IntArrayList buildLut(int baseX, ReferenceArrayList>> indices) { - final var out = new IntArrayList(); - out.add(baseX); - out.add(indices.size()); - for (int i = 0; i < indices.size(); i++) { - out.add(0); + private static final class IntLayer { + private boolean hasBase = false; + private int base = 0; + private int[] indices = new int[0]; + + public void fillLut(IntArrayList lut) { + lut.add(base); + lut.add(indices.length); + + for (int index : indices) { + lut.add(index); + } } - for (int x = 0; x < indices.size(); x++) { - final var yLookup = indices.get(x); - if (yLookup == null) { - out.set(x + 2, 0); - continue; + + public void set(int i, int index) { + if (!hasBase) { + base = i; + hasBase = true; } - // ensure that the base position and size dont cross a (64 byte) cache line - if ((out.size() & 0xF) == 0xF) { - out.add(0); + + if (i < base) { + rebase(i); } - final var baseYIndex = out.size(); - out.set(x + 2, baseYIndex); + final var offset = i - base; - final var yIndices = yLookup.right(); - out.add(yLookup.leftInt()); - out.add(yIndices.size()); - for (int i = 0; i < indices.size(); i++) { - out.add(0); + if (offset >= indices.length) { + resize(offset + 1); } - for (int y = 0; y < yIndices.size(); y++) { - final var zLookup = yIndices.get(y); - if (zLookup == null) { - out.set(baseYIndex + y + 2, 0); - continue; - } - // ensure that the base position and size dont cross a (64 byte) cache line - if ((out.size() & 0xF) == 0xF) { - out.add(0); - } - out.set(baseYIndex + y + 2, out.size()); - zLookup.set(1, zLookup.size() - 2); - out.addAll(zLookup); - } + indices[offset] = index; + } + + private void resize(int length) { + final var newIndices = new int[length]; + System.arraycopy(indices, 0, newIndices, 0, indices.length); + indices = newIndices; + } + + private void rebase(int newBase) { + final var growth = base - newBase; + + final var newIndices = new int[indices.length + growth]; + System.arraycopy(indices, 0, newIndices, growth, indices.length); + + indices = newIndices; + base = newBase; } - return out; } }