diff --git a/src/main/java/ca/spottedleaf/moonrise/common/config/adapter/TypeAdapterRegistry.java b/src/main/java/ca/spottedleaf/moonrise/common/config/adapter/TypeAdapterRegistry.java index 21dc5ae..fb89e11 100644 --- a/src/main/java/ca/spottedleaf/moonrise/common/config/adapter/TypeAdapterRegistry.java +++ b/src/main/java/ca/spottedleaf/moonrise/common/config/adapter/TypeAdapterRegistry.java @@ -186,9 +186,14 @@ public final class TypeAdapterRegistry { adapter = findOrMakeAdapter(registry, field.getType()); } + String serializedKey = serializable.serializedKey(); + if (serializedKey.isEmpty()) { + serializedKey = makeSerializedKey(field.getName()); + } + ret.add(new SerializableField( field, serializable.required(), serializable.comment(), adapter, - serializable.serialize(), makeSerializedKey(field.getName()) + serializable.serialize(), serializedKey )); } } diff --git a/src/main/java/ca/spottedleaf/moonrise/common/config/annotation/Serializable.java b/src/main/java/ca/spottedleaf/moonrise/common/config/annotation/Serializable.java index 8ca9f50..77c25c6 100644 --- a/src/main/java/ca/spottedleaf/moonrise/common/config/annotation/Serializable.java +++ b/src/main/java/ca/spottedleaf/moonrise/common/config/annotation/Serializable.java @@ -37,4 +37,9 @@ public @interface Serializable { */ public boolean serialize() default true; + /** + * When not empty, this value overrides the auto generated serialized key in the config. + */ + public String serializedKey() default ""; + } diff --git a/src/main/java/ca/spottedleaf/moonrise/common/config/config/YamlConfig.java b/src/main/java/ca/spottedleaf/moonrise/common/config/config/YamlConfig.java index fbfd4c8..f0fdfeb 100644 --- a/src/main/java/ca/spottedleaf/moonrise/common/config/config/YamlConfig.java +++ b/src/main/java/ca/spottedleaf/moonrise/common/config/config/YamlConfig.java @@ -31,7 +31,7 @@ import java.util.TreeMap; public final class YamlConfig { - public final TypeAdapterRegistry typeAdapters = new TypeAdapterRegistry(); + public final TypeAdapterRegistry typeAdapters; private final Class clazz; @@ -40,8 +40,13 @@ public final class YamlConfig { private final Yaml yaml; public YamlConfig(final Class clazz, final T dfl) throws Exception { + this(clazz, dfl, new TypeAdapterRegistry()); + } + + public YamlConfig(final Class clazz, final T dfl, final TypeAdapterRegistry registry) throws Exception { this.clazz = clazz; this.config = dfl; + this.typeAdapters = registry; this.typeAdapters.makeAdapter(clazz); final LoaderOptions loaderOptions = new LoaderOptions(); diff --git a/src/main/java/ca/spottedleaf/moonrise/common/config/MoonriseConfig.java b/src/main/java/ca/spottedleaf/moonrise/common/config/moonrise/MoonriseConfig.java similarity index 85% rename from src/main/java/ca/spottedleaf/moonrise/common/config/MoonriseConfig.java rename to src/main/java/ca/spottedleaf/moonrise/common/config/moonrise/MoonriseConfig.java index ff4ee2c..97dd334 100644 --- a/src/main/java/ca/spottedleaf/moonrise/common/config/MoonriseConfig.java +++ b/src/main/java/ca/spottedleaf/moonrise/common/config/moonrise/MoonriseConfig.java @@ -1,10 +1,9 @@ -package ca.spottedleaf.moonrise.common.config; +package ca.spottedleaf.moonrise.common.config.moonrise; -import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry; import ca.spottedleaf.moonrise.common.config.annotation.Adaptable; import ca.spottedleaf.moonrise.common.config.annotation.Serializable; +import ca.spottedleaf.moonrise.common.config.moonrise.type.DefaultedValue; import ca.spottedleaf.moonrise.common.config.type.Duration; -import java.util.concurrent.TimeUnit; @Adaptable public final class MoonriseConfig { @@ -159,4 +158,26 @@ public final class MoonriseConfig { ) public boolean populationGenParallelism = false; } + + @Serializable + public BugFixes bugFixes = new BugFixes(); + + @Adaptable + public static final class BugFixes { + + public static final Boolean FIX_MC224294_DEFAULT = Boolean.TRUE; + + @Serializable( + serializedKey = "fix-MC-224294", + comment = """ + Fixes https://bugs.mojang.com/browse/MC-224294. By avoiding double ticking lava blocks during + chunk random ticking, the cost of world random ticking is significantly reduced. + This configuration has three options: + default -> Current default is "true". + true -> Does not double tick lava. This is different from Vanilla behavior. + false -> Does double tick lava. This is the same behavior as Vanilla. + """ + ) + public DefaultedValue fixMC224294 = new DefaultedValue<>(); + } } diff --git a/src/main/java/ca/spottedleaf/moonrise/common/config/moonrise/adapter/DefaultedTypeAdapter.java b/src/main/java/ca/spottedleaf/moonrise/common/config/moonrise/adapter/DefaultedTypeAdapter.java new file mode 100644 index 0000000..6dee481 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/common/config/moonrise/adapter/DefaultedTypeAdapter.java @@ -0,0 +1,38 @@ +package ca.spottedleaf.moonrise.common.config.moonrise.adapter; + +import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapter; +import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry; +import ca.spottedleaf.moonrise.common.config.moonrise.type.DefaultedValue; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +public final class DefaultedTypeAdapter extends TypeAdapter, Object> { + + private static final String DEFAULT_STRING = "default"; + + @Override + public DefaultedValue deserialize(final TypeAdapterRegistry registry, final Object input, final Type type) { + if (input instanceof String string && string.equalsIgnoreCase(DEFAULT_STRING)) { + return new DefaultedValue<>(); + } + + if (!(type instanceof ParameterizedType parameterizedType)) { + throw new IllegalArgumentException("DefaultedValue field must specify generic type"); + } + final Type valueType = parameterizedType.getActualTypeArguments()[0]; + + return new DefaultedValue<>(registry.deserialize(input, valueType)); + } + + @Override + public Object serialize(final TypeAdapterRegistry registry, final DefaultedValue value, final Type type) { + final Object raw = value.getValueRaw(); + if (raw == null) { + return DEFAULT_STRING; + } + + final Type valueType = type instanceof ParameterizedType parameterizedType ? parameterizedType.getActualTypeArguments()[0] : null; + + return registry.serialize(raw, valueType); + } +} diff --git a/src/main/java/ca/spottedleaf/moonrise/common/config/moonrise/type/DefaultedValue.java b/src/main/java/ca/spottedleaf/moonrise/common/config/moonrise/type/DefaultedValue.java new file mode 100644 index 0000000..45c6e53 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/common/config/moonrise/type/DefaultedValue.java @@ -0,0 +1,22 @@ +package ca.spottedleaf.moonrise.common.config.moonrise.type; + +public final class DefaultedValue { + + private final T value; + + public DefaultedValue() { + this(null); + } + + public DefaultedValue(final T value) { + this.value = value; + } + + public T getValueRaw() { + return value; + } + + public T getOrDefault(final T dfl) { + return this.value != null ? this.value : dfl; + } +} 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 31b8fd5..6d69c50 100644 --- a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java +++ b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java @@ -1,10 +1,11 @@ package ca.spottedleaf.moonrise.common.util; import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadPool; -import ca.spottedleaf.moonrise.common.config.MoonriseConfig; import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry; +import ca.spottedleaf.moonrise.common.config.moonrise.MoonriseConfig; import ca.spottedleaf.moonrise.common.config.config.YamlConfig; -import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler; +import ca.spottedleaf.moonrise.common.config.moonrise.adapter.DefaultedTypeAdapter; +import ca.spottedleaf.moonrise.common.config.moonrise.type.DefaultedValue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; @@ -14,10 +15,13 @@ public final class MoonriseCommon { private static final Logger LOGGER = LoggerFactory.getLogger(MoonriseCommon.class); private static final File CONFIG_FILE = new File(System.getProperty("Moonrise.ConfigFile", "moonrise.yml")); + private static final TypeAdapterRegistry CONFIG_ADAPTERS = new TypeAdapterRegistry(); private static final YamlConfig CONFIG; static { + CONFIG_ADAPTERS.putAdapter(DefaultedValue.class, new DefaultedTypeAdapter()); + try { - CONFIG = new YamlConfig<>(MoonriseConfig.class, new MoonriseConfig()); + CONFIG = new YamlConfig<>(MoonriseConfig.class, new MoonriseConfig(), CONFIG_ADAPTERS); } catch (final Exception ex) { throw new RuntimeException(ex); } diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/block_counting/BitStorageMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/block_counting/BitStorageMixin.java new file mode 100644 index 0000000..2aeaccf --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/block_counting/BitStorageMixin.java @@ -0,0 +1,37 @@ +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 net.minecraft.util.BitStorage; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(BitStorage.class) +public interface BitStorageMixin extends BlockCountingBitStorage { + + @Shadow + int getBits(); + + @Shadow + int getSize(); + + @Shadow + int get(int i); + + // provide default impl in case mods implement this... + @Override + public default Int2ObjectOpenHashMap moonrise$countEntries() { + final Int2ObjectOpenHashMap 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 ret; + } +} diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/collisions/LevelChunkSectionMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/block_counting/LevelChunkSectionMixin.java similarity index 58% rename from src/main/java/ca/spottedleaf/moonrise/mixin/collisions/LevelChunkSectionMixin.java rename to src/main/java/ca/spottedleaf/moonrise/mixin/block_counting/LevelChunkSectionMixin.java index 5d2d204..3b1f484 100644 --- a/src/main/java/ca/spottedleaf/moonrise/mixin/collisions/LevelChunkSectionMixin.java +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/block_counting/LevelChunkSectionMixin.java @@ -1,9 +1,15 @@ -package ca.spottedleaf.moonrise.mixin.collisions; +package ca.spottedleaf.moonrise.mixin.block_counting; +import ca.spottedleaf.moonrise.common.list.IBlockDataList; +import ca.spottedleaf.moonrise.patches.block_counting.BlockCountingBitStorage; import ca.spottedleaf.moonrise.patches.collisions.CollisionUtil; -import ca.spottedleaf.moonrise.patches.collisions.world.CollisionLevelChunkSection; +import ca.spottedleaf.moonrise.patches.block_counting.BlockCountingChunkSection; +import com.llamalad7.mixinextras.sugar.Local; import it.unimi.dsi.fastutil.ints.Int2IntMap; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntArrayList; import net.minecraft.util.BitStorage; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.LevelChunkSection; @@ -20,10 +26,11 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import java.util.Iterator; +import java.util.Objects; import java.util.function.Predicate; @Mixin(LevelChunkSection.class) -public abstract class LevelChunkSectionMixin implements CollisionLevelChunkSection { +public abstract class LevelChunkSectionMixin implements BlockCountingChunkSection { @Shadow @Final @@ -41,30 +48,62 @@ public abstract class LevelChunkSectionMixin implements CollisionLevelChunkSecti @Shadow public abstract boolean maybeHas(Predicate predicate); + @Unique + private static final IntArrayList FULL_LIST = new IntArrayList(16*16*16); + static { + for (int i = 0; i < (16*16*16); ++i) { + FULL_LIST.add(i); + } + } @Unique private int specialCollidingBlocks; + @Unique + private final IBlockDataList tickingBlocks = new IBlockDataList(); + + @Override + public final int moonrise$getSpecialCollidingBlocks() { + return this.specialCollidingBlocks; + } + + @Override + public final IBlockDataList moonrise$getTickingBlockList() { + return this.tickingBlocks; + } + /** - * @reason Callback used to update the known collision data on block update. + * @reason Callback used to update block counts on block change. * @author Spottedleaf */ @Inject( method = "setBlockState(IIILnet/minecraft/world/level/block/state/BlockState;Z)Lnet/minecraft/world/level/block/state/BlockState;", - at = @At("RETURN") + at = @At( + "RETURN" + ) ) - private void updateBlockCallback(final int x, final int y, final int z, final BlockState state, final boolean lock, - final CallbackInfoReturnable cir) { - if (CollisionUtil.isSpecialCollidingBlock(state)) { + private void updateBlockCallback(final int x, final int y, final int z, final BlockState newState, final boolean lock, + final CallbackInfoReturnable cir, @Local(ordinal = 1) final BlockState oldState) { + if (oldState == newState) { + return; + } + if (CollisionUtil.isSpecialCollidingBlock(oldState)) { + --this.specialCollidingBlocks; + } + if (CollisionUtil.isSpecialCollidingBlock(newState)) { ++this.specialCollidingBlocks; } - if (CollisionUtil.isSpecialCollidingBlock(cir.getReturnValue())) { - --this.specialCollidingBlocks; + + if (oldState.isRandomlyTicking()) { + this.tickingBlocks.remove(x, y, z); + } + if (newState.isRandomlyTicking()) { + this.tickingBlocks.add(x, y, z, newState); } } /** - * @reason Insert known collision data counting + * @reason Calculate block counts after deserialization. * @author Spottedleaf */ @Overwrite @@ -74,6 +113,7 @@ public abstract class LevelChunkSectionMixin implements CollisionLevelChunkSecti this.tickingBlockCount = (short)0; this.tickingFluidCount = (short)0; this.specialCollidingBlocks = (short)0; + this.tickingBlocks.clear(); if (this.maybeHas((final BlockState state) -> !state.isAir())) { final PalettedContainer.Data data = this.states.data; @@ -81,19 +121,19 @@ public abstract class LevelChunkSectionMixin implements CollisionLevelChunkSecti final int paletteSize = palette.getSize(); final BitStorage storage = data.storage; - final Int2IntOpenHashMap counts = new Int2IntOpenHashMap(paletteSize); + final Int2ObjectOpenHashMap counts; if (paletteSize == 1) { - counts.addTo(0, storage.getSize()); + counts = new Int2ObjectOpenHashMap<>(1); + counts.put(0, FULL_LIST); } else { - storage.getAll((final int paletteIdx) -> { - counts.addTo(paletteIdx, 1); - }); + counts = ((BlockCountingBitStorage)storage).moonrise$countEntries(); } - for (final Iterator iterator = counts.int2IntEntrySet().fastIterator(); iterator.hasNext();) { - final Int2IntMap.Entry entry = iterator.next(); + for (final Iterator> iterator = counts.int2ObjectEntrySet().fastIterator(); iterator.hasNext();) { + final Int2ObjectMap.Entry entry = iterator.next(); final int paletteIdx = entry.getIntKey(); - final int paletteCount = entry.getIntValue(); + final IntArrayList coordinates = entry.getValue(); + final int paletteCount = coordinates.size(); final BlockState state = palette.valueFor(paletteIdx); @@ -107,6 +147,12 @@ public abstract class LevelChunkSectionMixin implements CollisionLevelChunkSecti this.nonEmptyBlockCount += paletteCount; if (state.isRandomlyTicking()) { this.tickingBlockCount += paletteCount; + final int[] raw = coordinates.elements(); + + Objects.checkFromToIndex(0, paletteCount, raw.length); + for (int i = 0; i < paletteCount; ++i) { + this.tickingBlocks.add(raw[i], state); + } } final FluidState fluid = state.getFluidState(); @@ -122,8 +168,7 @@ public abstract class LevelChunkSectionMixin implements CollisionLevelChunkSecti } /** - * @reason recalcBlockCounts is not called on the client, so we need to insert the call somewhere for our collision - * state caches + * @reason Call recalcBlockCounts on the client, as the client does not invoke it when deserializing chunk sections. * @author Spottedleaf */ @Inject( @@ -135,9 +180,4 @@ public abstract class LevelChunkSectionMixin implements CollisionLevelChunkSecti private void callRecalcBlocksClient(final CallbackInfo ci) { this.recalcBlockCounts(); } - - @Override - public final int moonrise$getSpecialCollidingBlocks() { - return this.specialCollidingBlocks; - } } diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/block_counting/SimpleBitStorageMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/block_counting/SimpleBitStorageMixin.java new file mode 100644 index 0000000..7d19e70 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/block_counting/SimpleBitStorageMixin.java @@ -0,0 +1,67 @@ +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 net.minecraft.util.BitStorage; +import net.minecraft.util.SimpleBitStorage; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import java.util.Objects; + +@Mixin(SimpleBitStorage.class) +public abstract class SimpleBitStorageMixin implements BitStorage, BlockCountingBitStorage { + + @Shadow + @Final + private long[] data; + + @Shadow + @Final + private int valuesPerLong; + + @Shadow + @Final + private int bits; + + @Shadow + @Final + private long mask; + + @Shadow + @Final + private int size; + + @Override + public final Int2ObjectOpenHashMap 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 ret = new 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; + + ret.computeIfAbsent(paletteIdx, (final int key) -> { + return new IntArrayList(); + }).add(index); + + ++li; + ++index; + } while (li < valuesPerLong && index < size); + } + + return ret; + } +} diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/block_counting/ZeroBitStorageMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/block_counting/ZeroBitStorageMixin.java new file mode 100644 index 0000000..3ba844a --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/block_counting/ZeroBitStorageMixin.java @@ -0,0 +1,35 @@ +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 net.minecraft.util.BitStorage; +import net.minecraft.util.ZeroBitStorage; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import java.util.Objects; + +@Mixin(ZeroBitStorage.class) +public abstract class ZeroBitStorageMixin implements BitStorage, BlockCountingBitStorage { + + @Shadow + @Final + private int size; + + @Override + public final Int2ObjectOpenHashMap moonrise$countEntries() { + final int size = this.size; + + final int[] raw = new int[size]; + for (int i = 0; i < size; ++i) { + raw[i] = i; + } + + final IntArrayList coordinates = IntArrayList.wrap(raw, size); + + final Int2ObjectOpenHashMap ret = new Int2ObjectOpenHashMap<>(1); + ret.put(0, coordinates); + return ret; + } +} diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/ClientLevelMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/ClientLevelMixin.java index b8aba16..a6cb463 100644 --- a/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/ClientLevelMixin.java +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/ClientLevelMixin.java @@ -2,6 +2,7 @@ package ca.spottedleaf.moonrise.mixin.chunk_system; import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel; import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.client.ClientEntityLookup; +import net.minecraft.client.multiplayer.ClientChunkCache; import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.multiplayer.ClientPacketListener; import net.minecraft.client.renderer.LevelRenderer; @@ -12,6 +13,9 @@ import net.minecraft.util.profiling.ProfilerFiller; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.Level; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.dimension.DimensionType; import net.minecraft.world.level.entity.EntityAccess; import net.minecraft.world.level.entity.LevelEntityGetter; @@ -34,6 +38,10 @@ public abstract class ClientLevelMixin extends Level implements ChunkSystemLevel @Shadow private TransientEntitySectionManager entityStorage; + @Shadow + @Final + private ClientChunkCache chunkSource; + protected ClientLevelMixin(WritableLevelData writableLevelData, ResourceKey resourceKey, RegistryAccess registryAccess, Holder holder, Supplier supplier, boolean bl, boolean bl2, long l, int i) { super(writableLevelData, resourceKey, registryAccess, holder, supplier, bl, bl2, l, i); } @@ -65,6 +73,30 @@ public abstract class ClientLevelMixin extends Level implements ChunkSystemLevel return this.moonrise$getEntityLookup().getEntityCount(); } + /** + * @reason The implementation in Level will perform two virtual invokes: + * 1. When retrieving the ChunkSource + * 2. When calling getChunk on the ChunkSource + * We can reduce the virtual invokes to 1 by instead directly implementing getChunk + * in both ServerLevel and ClientLevel. Additionally, for dedicated servers, the virtual invoke + * may be elided entirely as ClientLevel is not used. For code only used by the server, it may + * also be elided. + * @author Spottedleaf + */ + @Override + public ChunkAccess getChunk(final int x, final int z, final ChunkStatus status, final boolean load) { + // client never returns null for load=true, so do not null check the result + return this.chunkSource.getChunk(x, z, status, load); + } + + /** + * @author Spottedleaf + */ + @Override + public LevelChunk getChunk(final int x, final int z) { + return (LevelChunk)this.chunkSource.getChunk(x, z, ChunkStatus.FULL, true); + } + /** * @reason Redirect to new entity manager * @author Spottedleaf diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/LevelMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/LevelMixin.java index 64d2ca4..36e6511 100644 --- a/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/LevelMixin.java +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/LevelMixin.java @@ -4,6 +4,7 @@ import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel; import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.EntityLookup; import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.dfl.DefaultEntityLookup; import ca.spottedleaf.moonrise.patches.chunk_system.world.ChunkSystemEntityGetter; +import net.minecraft.core.BlockPos; import net.minecraft.server.level.FullChunkStatus; import net.minecraft.util.profiling.ProfilerFiller; import net.minecraft.world.entity.Entity; @@ -14,6 +15,7 @@ import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.entity.EntityTypeTest; +import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.phys.AABB; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Overwrite; @@ -33,6 +35,12 @@ public abstract class LevelMixin implements ChunkSystemLevel, ChunkSystemEntityG @Shadow public abstract ProfilerFiller getProfiler(); + @Shadow + public abstract LevelChunk getChunk(int i, int j); + + @Shadow + public abstract int getHeight(Heightmap.Types types, int i, int j); + @Unique private EntityLookup entityLookup; @@ -194,6 +202,32 @@ public abstract class LevelMixin implements ChunkSystemLevel, ChunkSystemEntityG // no-op on ClientLevel } + /** + * @reason Declare method in this class so that any invocations are virtual, and not interface. + * @author Spottedleaf + */ + @Override + public boolean hasChunk(final int x, final int z) { + return this.getChunkSource().hasChunk(x, z); + } + + /** + * @reason Turn all getChunk(x, z, status) calls into virtual invokes, instead of interface invokes: + * 1. The interface invoke is expensive + * 2. The method makes other interface invokes (again, expensive) + * Instead, we just directly call getChunk(x, z, status, true) which avoids the interface invokes entirely. + * @author Spottedleaf + */ + @Override + public ChunkAccess getChunk(final int x, final int z, final ChunkStatus status) { + return ((Level)(Object)this).getChunk(x, z, status, true); + } + + @Override + public BlockPos getHeightmapPos(Heightmap.Types types, BlockPos blockPos) { + return new BlockPos(blockPos.getX(), this.getHeight(types, blockPos.getX(), blockPos.getZ()), blockPos.getZ()); + } + /** * @reason Allow block updates in non-ticking chunks, as new chunk system sends non-ticking chunks to clients * @author Spottedleaf diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/ServerChunkCacheMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/ServerChunkCacheMixin.java index c47e1b9..5447222 100644 --- a/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/ServerChunkCacheMixin.java +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/ServerChunkCacheMixin.java @@ -135,7 +135,7 @@ public abstract class ServerChunkCacheMixin extends ChunkSource implements Chunk @Override @Overwrite public LevelChunk getChunkNow(final int chunkX, final int chunkZ) { - return ((ChunkSystemServerLevel)this.level).moonrise$getFullChunkIfLoaded(chunkX, chunkZ); + return this.fullChunks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); } /** diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/ServerLevelMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/ServerLevelMixin.java index fbcfee4..5302431 100644 --- a/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/ServerLevelMixin.java +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/ServerLevelMixin.java @@ -24,6 +24,7 @@ import net.minecraft.resources.ResourceKey; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ChunkMap; import net.minecraft.server.level.DistanceManager; +import net.minecraft.server.level.ServerChunkCache; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.progress.ChunkProgressListener; import net.minecraft.util.profiling.ProfilerFiller; @@ -32,6 +33,7 @@ import net.minecraft.world.entity.Entity; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.CustomSpawner; import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.WorldGenLevel; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.LevelChunk; @@ -74,6 +76,10 @@ public abstract class ServerLevelMixin extends Level implements ChunkSystemServe @Final private MinecraftServer server; + @Shadow + @Final + private ServerChunkCache chunkSource; + protected ServerLevelMixin(WritableLevelData writableLevelData, ResourceKey resourceKey, RegistryAccess registryAccess, Holder holder, Supplier supplier, boolean bl, boolean bl2, long l, int i) { super(writableLevelData, resourceKey, registryAccess, holder, supplier, bl, bl2, l, i); } @@ -138,16 +144,7 @@ public abstract class ServerLevelMixin extends Level implements ChunkSystemServe @Override public final LevelChunk moonrise$getFullChunkIfLoaded(final int chunkX, final int chunkZ) { - final NewChunkHolder newChunkHolder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(CoordinateUtils.getChunkKey(chunkX, chunkZ)); - if (newChunkHolder == null || !newChunkHolder.isFullChunkReady()) { - return null; - } - - if (newChunkHolder.getCurrentChunk() instanceof LevelChunk levelChunk) { - return levelChunk; - } - // race condition: chunk unloaded, only happens off-main - return null; + return this.chunkSource.getChunkNow(chunkX, chunkZ); } @Override @@ -315,6 +312,39 @@ public abstract class ServerLevelMixin extends Level implements ChunkSystemServe return this.nearbyPlayers; } + /** + * @reason Declare method in this class so that any invocations are virtual, and not interface. + * @author Spottedleaf + */ + @Override + public boolean hasChunk(final int x, final int z) { + return this.moonrise$getFullChunkIfLoaded(x, z) != null; + } + + /** + * @reason The implementation in Level will perform two virtual invokes: + * 1. When retrieving the ChunkSource + * 2. When calling getChunk on the ChunkSource + * We can reduce the virtual invokes to 1 by instead directly implementing getChunk + * in both ServerLevel and ClientLevel. Additionally, for dedicated servers, the virtual invoke + * may be elided entirely as ClientLevel is not used. For code only used by the server/client, + * it may also be elided. + * @author Spottedleaf + */ + @Override + public ChunkAccess getChunk(final int x, final int z, final ChunkStatus status, final boolean load) { + // ServerChunkCache performs the null check for us, so don't duplicate it + return this.chunkSource.getChunk(x, z, status, load); + } + + /** + * @author Spottedleaf + */ + @Override + public LevelChunk getChunk(final int x, final int z) { + return (LevelChunk)this.chunkSource.getChunk(x, z, ChunkStatus.FULL, true); + } + /** * @reason Entities are guaranteed to be ticking in the new chunk system * @author Spottedleaf diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/random_ticking/BiomeMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/random_ticking/BiomeMixin.java new file mode 100644 index 0000000..59ab265 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/random_ticking/BiomeMixin.java @@ -0,0 +1,23 @@ +package ca.spottedleaf.moonrise.mixin.random_ticking; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.biome.Biome; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(Biome.class) +public abstract class BiomeMixin { + + @Shadow + protected abstract float getHeightAdjustedTemperature(BlockPos blockPos); + + /** + * @reason Cache appears ineffective + * @author Spottedleaf + */ + @Overwrite + public float getTemperature(final BlockPos pos) { + return this.getHeightAdjustedTemperature(pos); + } +} diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/random_ticking/ServerLevelMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/random_ticking/ServerLevelMixin.java new file mode 100644 index 0000000..ede3530 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/random_ticking/ServerLevelMixin.java @@ -0,0 +1,110 @@ +package ca.spottedleaf.moonrise.mixin.random_ticking; + + +import ca.spottedleaf.moonrise.common.config.moonrise.MoonriseConfig; +import ca.spottedleaf.moonrise.common.list.IBlockDataList; +import ca.spottedleaf.moonrise.common.util.MoonriseCommon; +import ca.spottedleaf.moonrise.common.util.WorldUtil; +import ca.spottedleaf.moonrise.patches.block_counting.BlockCountingChunkSection; +import com.llamalad7.mixinextras.sugar.Local; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.RegistryAccess; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.RandomSource; +import net.minecraft.util.profiling.ProfilerFiller; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.storage.WritableLevelData; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; +import java.util.function.Supplier; + +@Mixin(ServerLevel.class) +public abstract class ServerLevelMixin extends Level implements WorldGenLevel { + + protected ServerLevelMixin(WritableLevelData writableLevelData, ResourceKey resourceKey, RegistryAccess registryAccess, Holder holder, Supplier supplier, boolean bl, boolean bl2, long l, int i) { + super(writableLevelData, resourceKey, registryAccess, holder, supplier, bl, bl2, l, i); + } + + @Unique + private static final LevelChunkSection[] EMPTY_SECTION_ARRAY = new LevelChunkSection[0]; + + /** + * @reason Optimise random ticking so that it will not retrieve BlockStates unnecessarily, as well as + * optionally avoiding double ticking fluid blocks. + * @author Spottedleaf + */ + @Redirect( + method = "tickChunk", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/world/level/chunk/LevelChunk;getSections()[Lnet/minecraft/world/level/chunk/LevelChunkSection;", + ordinal = 0 + ) + ) + private LevelChunkSection[] optimiseRandomTick(final LevelChunk chunk, + @Local(ordinal = 0, argsOnly = true) final int tickSpeed) { + final LevelChunkSection[] sections = chunk.getSections(); + final int minSection = WorldUtil.getMinSection((ServerLevel)(Object)this); + final RandomSource random = this.random; + final boolean tickFluids = !MoonriseCommon.getConfig().bugFixes.fixMC224294 + .getOrDefault(MoonriseConfig.BugFixes.FIX_MC224294_DEFAULT).booleanValue(); + + final ChunkPos cpos = chunk.getPos(); + final int offsetX = cpos.x << 4; + final int offsetZ = cpos.z << 4; + + for (int sectionIndex = 0, sectionsLen = sections.length; sectionIndex < sectionsLen; sectionIndex++) { + final int offsetY = (sectionIndex + minSection) << 4; + final LevelChunkSection section = sections[sectionIndex]; + if (section == null || !section.isRandomlyTickingBlocks()) { + continue; + } + + final IBlockDataList tickList = ((BlockCountingChunkSection)section).moonrise$getTickingBlockList(); + if (tickList.size() == 0) { + continue; + } + + for (int i = 0; i < tickSpeed; ++i) { + final int tickingBlocks = tickList.size(); + final int index = random.nextInt() & ((16 * 16 * 16) - 1); + + if (index >= tickingBlocks) { + // most of the time we fall here + continue; + } + + final long raw = tickList.getRaw(index); + final BlockState state = IBlockDataList.getBlockDataFromRaw(raw); + final int location = IBlockDataList.getLocationFromRaw(raw); + final int randomX = (location & 15) | offsetX; + final int randomY = ((location >>> (4 + 4)) & 255) | offsetY; + final int randomZ = ((location >>> 4) & 15) | offsetZ; + + // do not use a mutable pos, as some random tick implementations store the input without calling immutable()! + final BlockPos pos = new BlockPos(randomX, randomY, randomZ); + + state.randomTick((ServerLevel)(Object)this, pos, random); + if (tickFluids) { + final FluidState fluidState = state.getFluidState(); + if (fluidState.isRandomlyTicking()) { + fluidState.randomTick((ServerLevel)(Object)this, pos, random); + } + } + } + } + + return EMPTY_SECTION_ARRAY; + } +} 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 0000000..aef4fc0 --- /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; + +public interface BlockCountingBitStorage { + + 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 0000000..a08ddb0 --- /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; + +public interface BlockCountingChunkSection { + + public int moonrise$getSpecialCollidingBlocks(); + + public IBlockDataList moonrise$getTickingBlockList(); + +} 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 8d2a2d5..085919f 100644 --- a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java +++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java @@ -7,7 +7,7 @@ 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.collisions.world.CollisionLevel; -import ca.spottedleaf.moonrise.patches.collisions.world.CollisionLevelChunkSection; +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; @@ -1697,7 +1697,7 @@ public final class CollisionUtil { continue; } - final boolean hasSpecial = ((CollisionLevelChunkSection)section).moonrise$getSpecialCollidingBlocks() != 0; + final boolean hasSpecial = ((BlockCountingChunkSection)section).moonrise$getSpecialCollidingBlocks() != 0; final int sectionAdjust = !hasSpecial ? 1 : 0; final PalettedContainer blocks = section.states; diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/world/CollisionLevelChunkSection.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/world/CollisionLevelChunkSection.java deleted file mode 100644 index 2f0b433..0000000 --- a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/world/CollisionLevelChunkSection.java +++ /dev/null @@ -1,7 +0,0 @@ -package ca.spottedleaf.moonrise.patches.collisions.world; - -public interface CollisionLevelChunkSection { - - public int moonrise$getSpecialCollidingBlocks(); - -} diff --git a/src/main/resources/moonrise.mixins.json b/src/main/resources/moonrise.mixins.json index 3dd30b8..51729a5 100644 --- a/src/main/resources/moonrise.mixins.json +++ b/src/main/resources/moonrise.mixins.json @@ -6,6 +6,10 @@ "mixins": [ "bitstorage.SimpleBitStorageMixin", "bitstorage.ZeroBitStorageMixin", + "block_counting.BitStorageMixin", + "block_counting.LevelChunkSectionMixin", + "block_counting.SimpleBitStorageMixin", + "block_counting.ZeroBitStorageMixin", "block_entity_remove.LevelMixin", "blockstate_propertyaccess.BooleanPropertyMixin", "blockstate_propertyaccess.EnumPropertyMixin", @@ -60,7 +64,6 @@ "collisions.EntityGetterMixin", "collisions.EntityMixin", "collisions.ExplosionMixin", - "collisions.LevelChunkSectionMixin", "collisions.LevelMixin", "collisions.LivingEntityMixin", "collisions.ServerEntityMixin", @@ -85,6 +88,8 @@ "poi_lookup.AcquirePoiMixin", "poi_lookup.PoiManagerMixin", "poi_lookup.PortalForcerMixin", + "random_ticking.BiomeMixin", + "random_ticking.ServerLevelMixin", "serverlist.ConnectionMixin", "starlight.blockstate.BlockStateBaseMixin", "starlight.chunk.ChunkAccessMixin",