Port random ticking patch from Paper

This commit is contained in:
Spottedleaf
2024-07-14 12:55:46 -07:00
parent e0aac6916b
commit 1f0ca522c7
22 changed files with 582 additions and 55 deletions

View File

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

View File

@@ -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 "";
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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

View File

@@ -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

View File

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

View File

@@ -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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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;

View File

@@ -1,7 +0,0 @@
package ca.spottedleaf.moonrise.patches.collisions.world;
public interface CollisionLevelChunkSection {
public int moonrise$getSpecialCollidingBlocks();
}

View File

@@ -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",