Build blockstate cache on-demand instead of using a background thread

Should also hide incompatibility with buggy block impls. like Dynamic Trees
This commit is contained in:
embeddedt 2023-03-19 12:36:31 -04:00
parent 38a4776626
commit 00d0885245
No known key found for this signature in database
GPG Key ID: A69433EC199B5613
4 changed files with 66 additions and 100 deletions

View File

@ -2,11 +2,13 @@ package org.embeddedt.modernfix.blockstate;
import com.google.common.base.Stopwatch;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.fml.loading.FMLLoader;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.core.config.ModernFixConfig;
import org.embeddedt.modernfix.duck.IBlockState;
import org.embeddedt.modernfix.util.BakeReason;
import java.util.ArrayList;
@ -15,8 +17,6 @@ import java.util.List;
import java.util.concurrent.TimeUnit;
public class BlockStateCacheHandler {
private static RebuildThread currentRebuildThread = null;
private static boolean needToBake() {
BakeReason reason = BakeReason.getCurrentBakeReason();
return !(reason == BakeReason.FREEZE /* startup */
@ -26,73 +26,11 @@ public class BlockStateCacheHandler {
}
public static void rebuildParallel(boolean force) {
if(currentRebuildThread != null) {
if(currentRebuildThread.isAlive())
ModernFix.LOGGER.warn("Interrupting previous blockstate cache rebuild");
currentRebuildThread.stopRebuild();
try {
currentRebuildThread.join(10000);
if(currentRebuildThread.isAlive())
throw new IllegalStateException("Blockstate cache rebuild thread has hung");
} catch(InterruptedException e) {
throw new RuntimeException("Don't interrupt Minecraft threads", e);
}
ModernFix.LOGGER.debug("Rebuild thread exited");
currentRebuildThread = null;
}
if(force || needToBake()) {
ArrayList<BlockState> stateList = new ArrayList<>(Block.BLOCK_STATE_REGISTRY.size());
ModernFix.LOGGER.warn("Clearing blockstate cache");
synchronized (BlockBehaviour.BlockStateBase.Cache.class) {
for (BlockState blockState : Block.BLOCK_STATE_REGISTRY) {
stateList.add(blockState);
((IBlockState)blockState).clearCache();
}
currentRebuildThread = new RebuildThread(stateList);
if(ModernFixConfig.REBUILD_BLOCKSTATES_ASYNC.get())
currentRebuildThread.start();
else {
currentRebuildThread.run();
currentRebuildThread = null;
}
} else {
ModernFix.LOGGER.debug("Deferred blockstate cache rebuild");
}
}
private static class RebuildThread extends Thread {
private boolean stopRebuild = false;
private final List<BlockState> blockStateList;
public RebuildThread(List<BlockState> statesToInit) {
this.setName("ModernFix blockstate cache rebuild thread");
this.setPriority(Thread.MIN_PRIORITY + 1);
this.blockStateList = statesToInit;
}
public void stopRebuild() {
this.stopRebuild = true;
}
private void rebuildCache() {
Iterator<BlockState> stateIterator = blockStateList.iterator();
while(!stopRebuild && stateIterator.hasNext()) {
BlockState state = stateIterator.next();
try {
state.initCache();
} catch(Exception e) {
ModernFix.LOGGER.warn("Exception encountered while initializing cache", e);
}
}
}
@Override
public void run() {
ModernFix.waitForWorldLoad(() -> stopRebuild);
if(stopRebuild)
return;
Stopwatch realtimeStopwatch = Stopwatch.createStarted();
rebuildCache();
realtimeStopwatch.stop();
if(!stopRebuild)
ModernFix.LOGGER.info("Blockstate cache rebuilt in " + realtimeStopwatch.elapsed(TimeUnit.MILLISECONDS)/1000f + " seconds");
}
}
}

View File

@ -0,0 +1,58 @@
package org.embeddedt.modernfix.mixin.perf.reduce_blockstate_cache_rebuilds;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockBehaviour;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.duck.IBlockState;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
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;
import java.util.concurrent.atomic.AtomicInteger;
@Mixin(BlockBehaviour.BlockStateBase.class)
public abstract class BlockStateBaseMixin implements IBlockState {
@Shadow public abstract void initCache();
@Shadow private BlockBehaviour.BlockStateBase.Cache cache;
private volatile boolean cacheInvalid = false;
private static boolean buildingCache = false;
private static final ThreadLocal<Boolean> isMakingCache = ThreadLocal.withInitial(() -> false);
@Override
public void clearCache() {
cacheInvalid = true;
}
@Redirect(method = "*", at = @At(
value = "FIELD",
opcode = Opcodes.GETFIELD,
target = "Lnet/minecraft/world/level/block/state/BlockBehaviour$BlockStateBase;cache:Lnet/minecraft/world/level/block/state/BlockBehaviour$BlockStateBase$Cache;",
ordinal = 0
))
private BlockBehaviour.BlockStateBase.Cache initCacheIfNeeded(BlockBehaviour.BlockStateBase base) {
if(cacheInvalid) {
// Ensure that only one block's cache is built at a time
synchronized (BlockBehaviour.BlockStateBase.Cache.class) {
if(cacheInvalid) {
// Ensure that if we end up in here recursively, we just use the original cache
if(!buildingCache) {
buildingCache = true;
try {
this.initCache();
cacheInvalid = false;
} finally {
buildingCache = false;
}
}
}
}
}
return this.cache;
}
}

View File

@ -1,29 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.reduce_blockstate_cache_rebuilds;
import com.refinedmods.refinedstorage.block.shape.ShapeCache;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.shapes.VoxelShape;
import org.embeddedt.modernfix.ModernFix;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Mixin(ShapeCache.class)
public class ShapeCacheMixin {
@Final
@Mutable
@Shadow(remap = false) private static Map<BlockState, VoxelShape> CACHE;
@Inject(method = "<clinit>", at = @At("TAIL"))
private static void useConcurrentMap(CallbackInfo ci) {
CACHE = new ConcurrentHashMap<>();
ModernFix.LOGGER.info("Successfully replaced ShapeCache with concurrent map");
}
}

View File

@ -14,14 +14,13 @@
"perf.resourcepacks.VanillaPackMixin",
"perf.skip_first_datapack_reload.LevelSaveMixin",
"perf.skip_first_datapack_reload.SaveFormatAccessor",
"perf.reduce_blockstate_cache_rebuilds.GameDataMixin",
"perf.reduce_blockstate_cache_rebuilds.BlockCallbacksMixin",
"perf.boost_worker_count.UtilMixin",
"perf.thread_priorities.UtilMixin",
"perf.preload_block_classes.GameDataMixin",
"perf.reduce_blockstate_cache_rebuilds.BlocksMixin",
"perf.reduce_blockstate_cache_rebuilds.GameDataMixin",
"perf.reduce_blockstate_cache_rebuilds.BlockCallbacksMixin",
"perf.reduce_blockstate_cache_rebuilds.ShapeCacheMixin",
"perf.reduce_blockstate_cache_rebuilds.BlocksMixin",
"perf.reduce_blockstate_cache_rebuilds.BlockStateBaseMixin",
"perf.deduplicate_location.MixinResourceLocation",
"perf.sync_executor_sleep.SyncExecutorMixin",
"perf.compress_biome_container.MixinBiomeContainer",