Merge remote-tracking branch 'origin/main'
This commit is contained in:
@@ -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 //
|
||||
|
||||
+6
@@ -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;
|
||||
|
||||
+1
-1
Submodule coreSubProjects updated: 6832d21a3b...9f81194d21
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user