Replace ticking block coordinate set with ShortList

We can store a position (ranging from 0-4095) into a short,
so we can use ShortList to cut the memory usage in half.
This commit is contained in:
Spottedleaf
2024-09-17 15:51:31 -07:00
parent fef4872a3a
commit 1374ea34ee
9 changed files with 147 additions and 48 deletions

View File

@@ -19,6 +19,13 @@ public final class IntList {
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];
}

View File

@@ -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();
}
}

View File

@@ -3,6 +3,7 @@ package ca.spottedleaf.moonrise.mixin.block_counting;
import ca.spottedleaf.moonrise.patches.block_counting.BlockCountingBitStorage;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.shorts.ShortArrayList;
import net.minecraft.util.BitStorage;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@@ -18,15 +19,15 @@ interface BitStorageMixin extends BlockCountingBitStorage {
// provide default impl in case mods implement this...
@Override
public default Int2ObjectOpenHashMap<IntArrayList> moonrise$countEntries() {
final Int2ObjectOpenHashMap<IntArrayList> ret = new Int2ObjectOpenHashMap<>();
public default Int2ObjectOpenHashMap<ShortArrayList> moonrise$countEntries() {
final Int2ObjectOpenHashMap<ShortArrayList> ret = new 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 IntArrayList();
}).add(index);
return new ShortArrayList(64);
}).add((short)index);
}
return ret;

View File

@@ -1,13 +1,13 @@
package ca.spottedleaf.moonrise.mixin.block_counting;
import ca.spottedleaf.moonrise.common.list.IntList;
import ca.spottedleaf.moonrise.common.list.ShortList;
import ca.spottedleaf.moonrise.patches.block_counting.BlockCountingBitStorage;
import ca.spottedleaf.moonrise.patches.collisions.CollisionUtil;
import ca.spottedleaf.moonrise.patches.block_counting.BlockCountingChunkSection;
import com.llamalad7.mixinextras.sugar.Local;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.shorts.ShortArrayList;
import net.minecraft.util.BitStorage;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.LevelChunkSection;
@@ -48,9 +48,9 @@ abstract class LevelChunkSectionMixin implements BlockCountingChunkSection {
public abstract boolean maybeHas(Predicate<BlockState> predicate);
@Unique
private static final IntArrayList FULL_LIST = new IntArrayList(16*16*16);
private static final ShortArrayList FULL_LIST = new 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);
}
}
@@ -65,7 +65,7 @@ abstract class LevelChunkSectionMixin implements BlockCountingChunkSection {
private short specialCollidingBlocks;
@Unique
private final IntList tickingBlocks = new IntList();
private final ShortList tickingBlocks = new ShortList();
@Override
public final boolean moonrise$hasSpecialCollidingBlocks() {
@@ -73,7 +73,7 @@ abstract class LevelChunkSectionMixin implements BlockCountingChunkSection {
}
@Override
public final IntList moonrise$getTickingBlockList() {
public final ShortList moonrise$getTickingBlockList() {
return this.tickingBlocks;
}
@@ -100,20 +100,27 @@ abstract class LevelChunkSectionMixin implements BlockCountingChunkSection {
return;
}
if (CollisionUtil.isSpecialCollidingBlock(oldState)) {
--this.specialCollidingBlocks;
}
if (CollisionUtil.isSpecialCollidingBlock(newState)) {
++this.specialCollidingBlocks;
final boolean isSpecialOld = CollisionUtil.isSpecialCollidingBlock(oldState);
final boolean isSpecialNew = CollisionUtil.isSpecialCollidingBlock(newState);
if (isSpecialOld != isSpecialNew) {
if (isSpecialOld) {
--this.specialCollidingBlocks;
} else {
++this.specialCollidingBlocks;
}
}
final int position = x | (z << 4) | (y << (4+4));
final boolean oldTicking = oldState.isRandomlyTicking();
final boolean newTicking = newState.isRandomlyTicking();
if (oldTicking != newTicking) {
final ShortList tickingBlocks = this.tickingBlocks;
final short position = (short)(x | (z << 4) | (y << (4+4)));
if (oldState.isRandomlyTicking()) {
this.tickingBlocks.remove(position);
}
if (newState.isRandomlyTicking()) {
this.tickingBlocks.add(position);
if (oldTicking) {
tickingBlocks.remove(position);
} else {
tickingBlocks.add(position);
}
}
}
@@ -151,7 +158,7 @@ abstract class LevelChunkSectionMixin implements BlockCountingChunkSection {
final int paletteSize = palette.getSize();
final BitStorage storage = data.storage;
final Int2ObjectOpenHashMap<IntArrayList> counts;
final Int2ObjectOpenHashMap<ShortArrayList> counts;
if (paletteSize == 1) {
counts = new Int2ObjectOpenHashMap<>(1);
counts.put(0, FULL_LIST);
@@ -159,10 +166,10 @@ abstract class LevelChunkSectionMixin implements BlockCountingChunkSection {
counts = ((BlockCountingBitStorage)storage).moonrise$countEntries();
}
for (final Iterator<Int2ObjectMap.Entry<IntArrayList>> iterator = counts.int2ObjectEntrySet().fastIterator(); iterator.hasNext();) {
final Int2ObjectMap.Entry<IntArrayList> entry = iterator.next();
for (final Iterator<Int2ObjectMap.Entry<ShortArrayList>> iterator = counts.int2ObjectEntrySet().fastIterator(); iterator.hasNext();) {
final Int2ObjectMap.Entry<ShortArrayList> entry = iterator.next();
final int paletteIdx = entry.getIntKey();
final IntArrayList coordinates = entry.getValue();
final ShortArrayList coordinates = entry.getValue();
final int paletteCount = coordinates.size();
final BlockState state = palette.valueFor(paletteIdx);
@@ -172,16 +179,21 @@ abstract class LevelChunkSectionMixin implements BlockCountingChunkSection {
}
if (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;
Objects.checkFromToIndex(0, paletteCount, raw.length);
final ShortList tickingBlocks = this.tickingBlocks;
tickingBlocks.setMinCapacity(Math.min((rawLen + tickingBlocks.size()) * 3 / 2, 16*16*16));
Objects.checkFromToIndex(0, paletteCount, rawLen);
for (int i = 0; i < paletteCount; ++i) {
this.tickingBlocks.add(raw[i]);
tickingBlocks.add(raw[i]);
}
}
@@ -190,7 +202,7 @@ abstract class LevelChunkSectionMixin implements BlockCountingChunkSection {
if (!fluid.isEmpty()) {
//this.nonEmptyBlockCount += count; // fix vanilla bug: make non-empty block count correct
if (fluid.isRandomlyTicking()) {
this.tickingFluidCount += paletteCount;
this.tickingFluidCount += (short)paletteCount;
}
}
}

View File

@@ -3,6 +3,7 @@ package ca.spottedleaf.moonrise.mixin.block_counting;
import ca.spottedleaf.moonrise.patches.block_counting.BlockCountingBitStorage;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.shorts.ShortArrayList;
import net.minecraft.util.BitStorage;
import net.minecraft.util.SimpleBitStorage;
import org.spongepowered.asm.mixin.Final;
@@ -33,14 +34,14 @@ abstract class SimpleBitStorageMixin implements BitStorage, BlockCountingBitStor
private int size;
@Override
public final Int2ObjectOpenHashMap<IntArrayList> moonrise$countEntries() {
public final Int2ObjectOpenHashMap<ShortArrayList> moonrise$countEntries() {
final int valuesPerLong = this.valuesPerLong;
final int bits = this.bits;
final long mask = this.mask;
final int size = this.size;
// we may be backed by global palette, so limit bits for init capacity
final Int2ObjectOpenHashMap<IntArrayList> ret = new Int2ObjectOpenHashMap<>(
final Int2ObjectOpenHashMap<ShortArrayList> ret = new Int2ObjectOpenHashMap<>(
1 << Math.min(6, bits)
);
@@ -53,8 +54,8 @@ abstract class SimpleBitStorageMixin implements BitStorage, BlockCountingBitStor
value >>= bits;
ret.computeIfAbsent(paletteIdx, (final int key) -> {
return new IntArrayList();
}).add(index);
return new ShortArrayList(64);
}).add((short)index);
++li;
++index;

View File

@@ -3,6 +3,7 @@ package ca.spottedleaf.moonrise.mixin.block_counting;
import ca.spottedleaf.moonrise.patches.block_counting.BlockCountingBitStorage;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.shorts.ShortArrayList;
import net.minecraft.util.BitStorage;
import net.minecraft.util.ZeroBitStorage;
import org.spongepowered.asm.mixin.Final;
@@ -17,17 +18,17 @@ abstract class ZeroBitStorageMixin implements BitStorage, BlockCountingBitStorag
private int size;
@Override
public final Int2ObjectOpenHashMap<IntArrayList> moonrise$countEntries() {
public final Int2ObjectOpenHashMap<ShortArrayList> 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 IntArrayList coordinates = IntArrayList.wrap(raw, size);
final ShortArrayList coordinates = ShortArrayList.wrap(raw, size);
final Int2ObjectOpenHashMap<IntArrayList> ret = new Int2ObjectOpenHashMap<>(1);
final Int2ObjectOpenHashMap<ShortArrayList> ret = new Int2ObjectOpenHashMap<>(1);
ret.put(0, coordinates);
return ret;
}

View File

@@ -1,7 +1,7 @@
package ca.spottedleaf.moonrise.mixin.random_ticking;
import ca.spottedleaf.moonrise.common.PlatformHooks;
import ca.spottedleaf.moonrise.common.list.IntList;
import ca.spottedleaf.moonrise.common.list.ShortList;
import ca.spottedleaf.moonrise.common.util.SimpleRandom;
import ca.spottedleaf.moonrise.common.util.WorldUtil;
import ca.spottedleaf.moonrise.patches.block_counting.BlockCountingChunkSection;
@@ -89,7 +89,7 @@ abstract class ServerLevelMixin extends Level implements WorldGenLevel {
continue;
}
final IntList tickList = ((BlockCountingChunkSection)section).moonrise$getTickingBlockList();
final ShortList tickList = ((BlockCountingChunkSection)section).moonrise$getTickingBlockList();
for (int i = 0; i < tickSpeed; ++i) {
final int tickingBlocks = tickList.size();
@@ -100,7 +100,7 @@ abstract class ServerLevelMixin extends Level implements WorldGenLevel {
continue;
}
final int location = tickList.getRaw(index);
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()!

View File

@@ -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<IntArrayList> moonrise$countEntries();
public Int2ObjectOpenHashMap<ShortArrayList> moonrise$countEntries();
}

View File

@@ -1,11 +1,11 @@
package ca.spottedleaf.moonrise.patches.block_counting;
import ca.spottedleaf.moonrise.common.list.IntList;
import ca.spottedleaf.moonrise.common.list.ShortList;
public interface BlockCountingChunkSection {
public boolean moonrise$hasSpecialCollidingBlocks();
public IntList moonrise$getTickingBlockList();
public ShortList moonrise$getTickingBlockList();
}