Merge remote-tracking branch 'origin/main'

This commit is contained in:
coolGi
2023-06-15 18:30:26 +09:30
5 changed files with 97 additions and 11 deletions
@@ -34,6 +34,8 @@ import com.seibel.lod.common.wrappers.block.BiomeWrapper;
import com.seibel.lod.common.wrappers.worldGeneration.mimicObject.LightedWorldGenRegion;
import com.seibel.lod.core.wrapperInterfaces.world.ILevelWrapper;
import net.minecraft.client.multiplayer.ClientChunkCache;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.core.BlockPos;
#if POST_MC_1_17_1
import net.minecraft.core.QuartPos;
@@ -50,6 +52,9 @@ import net.minecraft.world.level.levelgen.Heightmap;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ChunkWrapper implements IChunkWrapper
{
@@ -62,9 +67,19 @@ public class ChunkWrapper implements IChunkWrapper
private final boolean isDhGeneratedChunk;
private final HashMap<BlockPos, BlockState> blockStateByBlockPosCache = new HashMap<>();
// Due to vanilla `isClientLightReady()` not designed to be used by non-render thread, that value may return 'true'
// just before the light engine is ticked, (right after all light changes is marked to the engine to be processed).
// To fix this, on client-only mode, we mixin-redirect the `isClientLightReady()` so that after the call, it will
// trigger a synchronous update of this flag here on all chunks that are wrapped.
//
// Note: Using a static weak hash map to store the chunks that need to be updated, as instance of chunk wrapper
// can be duplicated, with same chunk instance. And the data stored here are all temporary, and thus will not be
// visible when a chunk is re-wrapped later.
// (Also, thread safety done via a reader writer lock)
private final static WeakHashMap<ChunkAccess, Boolean> chunksToUpdateClientLightReady = new WeakHashMap<>();
private final static ReentrantReadWriteLock weakMapLock = new ReentrantReadWriteLock();
public ChunkWrapper(ChunkAccess chunk, LevelReader lightSource, @Nullable ILevelWrapper wrappedLevel)
{
this.chunk = chunk;
@@ -75,10 +90,12 @@ public class ChunkWrapper implements IChunkWrapper
this.useMcLightingEngine = (Config.Client.Advanced.WorldGenerator.lightingEngine.get() == ELightGenerationMode.MINECRAFT);
// TODO is this the best way to differentiate between when we are generating chunks and when MC gave us a chunk?
this.isDhGeneratedChunk = (this.lightSource.getClass() == LightedWorldGenRegion.class);
weakMapLock.writeLock().lock();
chunksToUpdateClientLightReady.put(chunk, false);
weakMapLock.writeLock().unlock();
}
//=========//
// methods //
//=========//
@@ -159,12 +176,21 @@ public class ChunkWrapper implements IChunkWrapper
#else
if (this.chunk instanceof LevelChunk)
{
// called when connected to a server (and sometimes when in a singleplayer world)
return ((LevelChunk) this.chunk).isClientLightReady() || this.chunk.isLightCorrect();
LevelChunk levelChunk = (LevelChunk) this.chunk;
if (levelChunk.getLevel() instanceof ClientLevel)
{
weakMapLock.readLock().lock();
boolean fixedIsClientLightReady = chunksToUpdateClientLightReady.get(this.chunk);
weakMapLock.readLock().unlock();
return fixedIsClientLightReady;
}
// called when in single player or in dedicated server, and the chunk is a level chunk (active)
return this.chunk.isLightCorrect() && levelChunk.loaded;
}
else
{
// called when in a single player world
// called when in a single player world and the chunk is a proto chunk (in world gen, and not active)
return this.chunk.isLightCorrect();
}
#endif
@@ -332,8 +358,38 @@ public class ChunkWrapper implements IChunkWrapper
@Override
public boolean isStillValid() { return this.wrappedLevel == null || this.wrappedLevel.tryGetChunk(this.chunkPos) == this; }
// Should be called after client light updates are triggered.
private static boolean updateClientLightReady(ChunkAccess chunk, boolean oldValue) {
if (chunk instanceof LevelChunk && ((LevelChunk)chunk).getLevel() instanceof ClientLevel)
{
LevelChunk levelChunk = (LevelChunk)chunk;
ClientChunkCache clientChunkCache = ((ClientLevel)levelChunk.getLevel()).getChunkSource();
return clientChunkCache.getChunkForLighting(chunk.getPos().x, chunk.getPos().z) != null && levelChunk.isClientLightReady();
}
else
{
return oldValue;
}
}
public static void syncedUpdateClientLightStatus()
{
#if PRE_MC_1_18_1
// TODO: Check what to do in 1.18.1, or in other versions
#else
weakMapLock.writeLock().lock();
try
{
chunksToUpdateClientLightReady.replaceAll(ChunkWrapper::updateClientLightReady);
}
finally {
weakMapLock.writeLock().unlock();
}
#endif
}
//================//
// helper methods //
@@ -416,6 +416,9 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv
{
ChunkAccess target = genChunks.get(offsetX, offsetY);
ChunkWrapper wrappedChunk = new ChunkWrapper(target, region, null);
if (target instanceof LevelChunk) {
((LevelChunk) target).loaded = true;
}
if (!wrappedChunk.isLightCorrect())
{
throw new RuntimeException("The generated chunk somehow has isLightCorrect() returning false");
@@ -567,6 +570,8 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv
if (chunk instanceof ProtoChunk)
{
chunk.setLightCorrect(true); // TODO why are we checking instanceof ProtoChunk?
// TODO: This is due to old times where it may return actual live chunks, which is LevelChunk.
// that though is no longer needed...
}
#if POST_MC_1_18_1
@@ -575,6 +580,7 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv
LevelChunk levelChunk = (LevelChunk) chunk;
levelChunk.setLightCorrect(true);
levelChunk.setClientLightReady(true);
levelChunk.loaded = true;
}
#endif
});
@@ -29,6 +29,7 @@ accessible field net/minecraft/world/level/biome/Biome biomeCategory Lnet/minecr
accessible method net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator doFill (Lnet/minecraft/world/level/levelgen/blending/Blender;Lnet/minecraft/world/level/StructureFeatureManager;Lnet/minecraft/world/level/chunk/ChunkAccess;II)Lnet/minecraft/world/level/chunk/ChunkAccess;
#accessible method net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator doCreateBiomes (Lnet/minecraft/core/Registry;Lnet/minecraft/world/level/levelgen/blending/Blender;Lnet/minecraft/world/level/StructureFeatureManager;Lnet/minecraft/world/level/chunk/ChunkAccess;)V
accessible method net/minecraft/world/level/lighting/LayerLightEngine queueSectionData (JLnet/minecraft/world/level/chunk/DataLayer;Z)V
accessible field net/minecraft/world/level/chunk/LevelChunk loaded Z
# lod generation from save file
accessible field net/minecraft/server/level/ChunkMap mainThreadExecutor Lnet/minecraft/util/thread/BlockableEventLoop;
@@ -21,15 +21,18 @@ package com.seibel.lod.fabric.mixins.client;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Matrix4f;
import com.seibel.lod.common.wrappers.chunk.ChunkWrapper;
import com.seibel.lod.core.config.Config;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.world.level.lighting.LevelLightEngine;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/**
@@ -112,4 +115,24 @@ public class MixinLevelRenderer
}
}
#endif
@Redirect(method =
"Lnet/minecraft/client/renderer/LevelRenderer;" +
"renderLevel(Lcom/mojang/blaze3d/vertex/PoseStack;" +
"FJZLnet/minecraft/client/Camera;" +
"Lnet/minecraft/client/renderer/GameRenderer;" +
"Lnet/minecraft/client/renderer/LightTexture;" +
"Lcom/mojang/math/Matrix4f;)V"
,
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/level/lighting/LevelLightEngine;runUpdates(IZZ)I"
))
private int callAfterRunUpdates(LevelLightEngine light, int pos, boolean isQueueEmpty, boolean updateBlockLight)
{
int r = light.runUpdates(pos, isQueueEmpty, updateBlockLight);
ChunkWrapper.syncedUpdateClientLightStatus();
return r;
}
}