Port random ticking patch from Paper
This commit is contained in:
@@ -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
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 "";
|
||||
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ import java.util.TreeMap;
|
||||
|
||||
public final class YamlConfig<T> {
|
||||
|
||||
public final TypeAdapterRegistry typeAdapters = new TypeAdapterRegistry();
|
||||
public final TypeAdapterRegistry typeAdapters;
|
||||
|
||||
private final Class<? extends T> clazz;
|
||||
|
||||
@@ -40,8 +40,13 @@ public final class YamlConfig<T> {
|
||||
private final Yaml yaml;
|
||||
|
||||
public YamlConfig(final Class<? extends T> clazz, final T dfl) throws Exception {
|
||||
this(clazz, dfl, new TypeAdapterRegistry());
|
||||
}
|
||||
|
||||
public YamlConfig(final Class<? extends T> 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();
|
||||
|
||||
@@ -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<Boolean> fixMC224294 = new DefaultedValue<>();
|
||||
}
|
||||
}
|
||||
@@ -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<DefaultedValue<?>, 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package ca.spottedleaf.moonrise.common.config.moonrise.type;
|
||||
|
||||
public final class DefaultedValue<T> {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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<MoonriseConfig> 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);
|
||||
}
|
||||
|
||||
@@ -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<IntArrayList> moonrise$countEntries() {
|
||||
final Int2ObjectOpenHashMap<IntArrayList> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<BlockState> 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<BlockState> 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<BlockState> 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<BlockState> 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<IntArrayList> 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<Int2IntMap.Entry> iterator = counts.int2IntEntrySet().fastIterator(); iterator.hasNext();) {
|
||||
final Int2IntMap.Entry entry = iterator.next();
|
||||
for (final Iterator<Int2ObjectMap.Entry<IntArrayList>> iterator = counts.int2ObjectEntrySet().fastIterator(); iterator.hasNext();) {
|
||||
final Int2ObjectMap.Entry<IntArrayList> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<IntArrayList> 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<>(
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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<IntArrayList> 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<IntArrayList> ret = new Int2ObjectOpenHashMap<>(1);
|
||||
ret.put(0, coordinates);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -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<Entity> entityStorage;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private ClientChunkCache chunkSource;
|
||||
|
||||
protected ClientLevelMixin(WritableLevelData writableLevelData, ResourceKey<Level> resourceKey, RegistryAccess registryAccess, Holder<DimensionType> holder, Supplier<ProfilerFiller> 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<Level> resourceKey, RegistryAccess registryAccess, Holder<DimensionType> holder, Supplier<ProfilerFiller> 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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<Level> resourceKey, RegistryAccess registryAccess, Holder<DimensionType> holder, Supplier<ProfilerFiller> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<IntArrayList> moonrise$countEntries();
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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<BlockState> blocks = section.states;
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
package ca.spottedleaf.moonrise.patches.collisions.world;
|
||||
|
||||
public interface CollisionLevelChunkSection {
|
||||
|
||||
public int moonrise$getSpecialCollidingBlocks();
|
||||
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user