diff --git a/build.gradle b/build.gradle index d042a0a1..a3abaf78 100644 --- a/build.gradle +++ b/build.gradle @@ -18,7 +18,7 @@ plugins { apply plugin: 'org.spongepowered.mixin' group = 'org.embeddedt' -version = '1.5.2' +version = '1.6.0-beta1' java { archivesBaseName = 'modernfix-mc' + minecraft_version @@ -137,7 +137,6 @@ dependencies { if(include_optimization_mods.toBoolean()) { runtimeOnly fg.deobf("curse.maven:roadrunner-529754:3683120") - runtimeOnly fg.deobf("curse.maven:starlight-529754:3683120") runtimeOnly fg.deobf("curse.maven:rubidium-574856:3949659") runtimeOnly fg.deobf("curse.maven:noexperimental-407174:3188120") runtimeOnly fg.deobf("curse.maven:spark-361579:3767277") diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/parallelize_model_loading/ModelBakeryMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/parallelize_model_loading/ModelBakeryMixin.java index 1ef20dae..32afa730 100644 --- a/src/main/java/org/embeddedt/modernfix/mixin/perf/parallelize_model_loading/ModelBakeryMixin.java +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/parallelize_model_loading/ModelBakeryMixin.java @@ -2,21 +2,29 @@ package org.embeddedt.modernfix.mixin.perf.parallelize_model_loading; import com.google.common.base.Stopwatch; import com.google.common.collect.Sets; +import com.mojang.datafixers.util.Pair; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.BlockModelShapes; import net.minecraft.client.renderer.model.*; import net.minecraft.client.renderer.texture.SpriteMap; import net.minecraft.profiler.IProfiler; +import net.minecraft.resources.IResource; +import net.minecraft.resources.IResourceManager; +import net.minecraft.state.StateContainer; import net.minecraft.util.ResourceLocation; import net.minecraft.util.Util; import net.minecraft.util.math.vector.TransformationMatrix; -import org.apache.commons.lang3.tuple.Pair; -import org.apache.commons.lang3.tuple.Triple; +import net.minecraft.util.registry.Registry; import org.embeddedt.modernfix.ModernFix; +import org.embeddedt.modernfix.util.AsyncStopwatch; import org.spongepowered.asm.mixin.Final; 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.ModifyVariable; import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @@ -24,6 +32,9 @@ import org.spongepowered.asm.mixin.injection.callback.LocalCapture; import javax.annotation.Nullable; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; @@ -44,7 +55,9 @@ public abstract class ModelBakeryMixin { @Shadow public abstract IUnbakedModel getModel(ResourceLocation modelLocation); @Shadow @Final private Map topLevelModels; + @Shadow @Final protected IResourceManager resourceManager; private Map deserializedModelCache = null; + private Map>> deserializedBlockstateCache = null; private boolean useModelCache = false; @Inject(method = "loadBlockModel", at = @At("HEAD"), cancellable = true) @@ -65,34 +78,74 @@ public abstract class ModelBakeryMixin { } } - @Inject(method = "processLoading", at = @At(value = "INVOKE", target = "Lnet/minecraft/profiler/IProfiler;popPush(Ljava/lang/String;)V", ordinal = 1)) + @Inject(method = "processLoading", at = @At(value = "INVOKE", target = "Lnet/minecraft/profiler/IProfiler;popPush(Ljava/lang/String;)V", ordinal = 0)) private void preloadJsonModels(IProfiler profilerIn, int maxMipmapLevel, CallbackInfo ci) { profilerIn.popPush("loadjsons"); - ModernFix.LOGGER.warn("Preloading JSONs in parallel..."); - Stopwatch stopwatch = Stopwatch.createStarted(); - useModelCache = false; - deserializedModelCache = Minecraft.getInstance().getResourceManager().listResources("models", p -> { - if(!p.endsWith(".json")) - return false; - for(int i = 0; i < p.length(); i++) { - if(!ResourceLocation.validPathChar(p.charAt(i))) - return false; - } - return true; - }) - .parallelStream() - .map(location -> new ResourceLocation(location.getNamespace(), location.getPath().substring(7, location.getPath().length() - 5))) - .map(location -> Pair.of(location, this.loadModelSafely(location))) - .filter(pair -> pair.getValue() != null) - .collect(Collectors.toConcurrentMap(Pair::getKey, Pair::getValue)); - useModelCache = true; - ModernFix.LOGGER.warn("Preloading JSONs took " + stopwatch.elapsed(TimeUnit.MILLISECONDS)/1000f + " seconds"); - stopwatch.stop(); + AsyncStopwatch.measureAndLogSerialRunningTime("Parallel JSON loading", () -> { + useModelCache = false; + deserializedModelCache = Minecraft.getInstance().getResourceManager().listResources("models", p -> { + if(!p.endsWith(".json")) + return false; + for(int i = 0; i < p.length(); i++) { + if(!ResourceLocation.validPathChar(p.charAt(i))) + return false; + } + return true; + }) + .parallelStream() + .map(location -> new ResourceLocation(location.getNamespace(), location.getPath().substring(7, location.getPath().length() - 5))) + .map(location -> Pair.of(location, this.loadModelSafely(location))) + .filter(pair -> pair.getSecond() != null) + .collect(Collectors.toConcurrentMap(Pair::getFirst, Pair::getSecond)); + useModelCache = true; + }); + AsyncStopwatch.measureAndLogSerialRunningTime("Parallel blockstate loading", () -> { + ThreadLocal containerHolder = ThreadLocal.withInitial(BlockModelDefinition.ContainerHolder::new); + this.deserializedBlockstateCache = Registry.BLOCK.keySet().parallelStream() + .flatMap(block -> { + ResourceLocation blockStateJSON = new ResourceLocation(block.getNamespace(), "blockstates/" + block.getPath() + ".json"); + List blockStates; + try { + blockStates = this.resourceManager.getResources(blockStateJSON); + } catch(IOException e) { + ModernFix.LOGGER.warn("Exception loading blockstate definition: {}: {}", block, e); + blockStates = Collections.emptyList(); + } + return blockStates.stream().map(resource -> Pair.of(block, resource)); + }) + .map((pair) -> { + ResourceLocation block = pair.getFirst(); + IResource resource = pair.getSecond(); + try (InputStream inputstream = resource.getInputStream()) { + BlockModelDefinition.ContainerHolder context = containerHolder.get(); + context.setDefinition(Registry.BLOCK.get(block).getStateDefinition()); + return Pair.of(block, Pair.of(resource.getSourceName(), BlockModelDefinition.fromStream(context, new InputStreamReader(inputstream, StandardCharsets.UTF_8)))); + } catch (Exception exception1) { + ModernFix.LOGGER.warn(String.format("Exception loading blockstate definition: '%s' in resourcepack: '%s': %s", resource.getLocation(), resource.getSourceName(), exception1.getMessage())); + return Pair.of(block, Pair.of((String)null, (BlockModelDefinition)null)); + } + }) + .filter(pair -> pair.getSecond().getSecond() != null) + .collect(Collectors.groupingBy(Pair::getFirst, Collectors.mapping(Pair::getSecond, Collectors.toList()))); + }); + AsyncStopwatch.measureAndLogSerialRunningTime("Predicate generation", () -> { + /* Pregenerate predicates */ + this.deserializedBlockstateCache.entrySet().parallelStream() + .flatMap(entry -> entry.getValue().stream() + .map(Pair::getSecond) + .map(def -> Pair.of(Registry.BLOCK.get(entry.getKey()).getStateDefinition(), def))) + .filter(pair -> pair.getSecond().isMultiPart()) + .flatMap(pair -> pair.getSecond().getMultiPart().getSelectors().stream().map(selector -> Pair.of(pair.getFirst(), selector))) + .forEach(pair -> { + pair.getSecond().getPredicate(pair.getFirst()); + }); + }); } @Inject(method = "processLoading", at = @At("RETURN"), remap = false) private void clearModelCache(IProfiler profilerIn, int maxMipmapLevel, CallbackInfo ci) { deserializedModelCache.clear(); + deserializedBlockstateCache.clear(); useModelCache = false; } @@ -111,7 +164,7 @@ public abstract class ModelBakeryMixin { } return candidate; }; - Set> set = Collections.synchronizedSet(Sets.newLinkedHashSet()); + Set> set = Collections.synchronizedSet(Sets.newLinkedHashSet()); String modelMissingString = MISSING_MODEL_LOCATION.toString(); Set materials = this.topLevelModels.values().parallelStream().flatMap((unbaked) -> { return unbaked.getMaterials(safeUnbakedGetter, set).stream(); @@ -125,4 +178,29 @@ public abstract class ModelBakeryMixin { stopwatch.stop(); return materials; } + + private List replacementList = null; + + @Redirect(method = "loadModel", at = @At(value = "INVOKE", target = "Lnet/minecraft/resources/IResourceManager;getResources(Lnet/minecraft/util/ResourceLocation;)Ljava/util/List;", ordinal = 0)) + private List getResourceList(IResourceManager instance, ResourceLocation jsonLocation, ResourceLocation originalBlockLocation) throws IOException { + replacementList = null; + if(this.deserializedBlockstateCache != null) { + if(!(originalBlockLocation instanceof ModelResourceLocation)) { + throw new AssertionError("Injector in unexpected spot?"); + } + ModelResourceLocation mrl = (ModelResourceLocation)originalBlockLocation; + ResourceLocation location = new ResourceLocation(mrl.getNamespace(), mrl.getPath()); + List theList = this.deserializedBlockstateCache.get(location); + if(theList != null && theList.size() > 0) { + replacementList = theList; + return theList; + } + } + return instance.getResources(jsonLocation); + } + + @Redirect(method = "loadModel", at = @At(value = "INVOKE", target = "Ljava/util/stream/Stream;map(Ljava/util/function/Function;)Ljava/util/stream/Stream;", ordinal = 0)) + private Stream fakeResourceList(Stream instance, Function function) { + return replacementList != null ? instance : instance.map(function); + } } diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/parallelize_model_loading/SelectorMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/parallelize_model_loading/SelectorMixin.java new file mode 100644 index 00000000..7fda2c06 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/parallelize_model_loading/SelectorMixin.java @@ -0,0 +1,30 @@ +package org.embeddedt.modernfix.mixin.perf.parallelize_model_loading; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.client.renderer.model.multipart.Selector; +import net.minecraft.state.StateContainer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Predicate; + +@Mixin(Selector.class) +public class SelectorMixin { + private ConcurrentHashMap, Predicate> predicateCache = new ConcurrentHashMap<>(); + @Inject(method = "getPredicate", at = @At("HEAD"), cancellable = true) + private void useCachedPredicate(StateContainer pState, CallbackInfoReturnable> cir) { + Predicate cached = this.predicateCache.get(pState); + if(cached != null) + cir.setReturnValue(cached); + } + + @Inject(method = "getPredicate", at = @At("RETURN")) + private void storeCachedPredicate(StateContainer pState, CallbackInfoReturnable> cir) { + this.predicateCache.put(pState, cir.getReturnValue()); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/util/AsyncStopwatch.java b/src/main/java/org/embeddedt/modernfix/util/AsyncStopwatch.java index 2cf113f6..a45db852 100644 --- a/src/main/java/org/embeddedt/modernfix/util/AsyncStopwatch.java +++ b/src/main/java/org/embeddedt/modernfix/util/AsyncStopwatch.java @@ -1,6 +1,7 @@ package org.embeddedt.modernfix.util; import com.google.common.base.Stopwatch; +import org.embeddedt.modernfix.ModernFix; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; @@ -30,4 +31,15 @@ public class AsyncStopwatch { public long getCpuTime() { return cpuTimeMs.get(); } + + public static void measureAndLogSerialRunningTime(String label, Runnable runnable) { + ModernFix.LOGGER.info(label + "..."); + Stopwatch stopwatch = Stopwatch.createStarted(); + try { + runnable.run(); + ModernFix.LOGGER.info(label + " took " + stopwatch.elapsed(TimeUnit.MILLISECONDS)/1000f + " seconds"); + } finally { + stopwatch.stop(); + } + } } diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index a4a05181..ba522272 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -2,4 +2,5 @@ public net.minecraft.client.Minecraft$WorldSelectionType public net.minecraft.client.renderer.RenderType$Type public net.minecraft.client.renderer.RenderType$Type (Ljava/lang/String;Lnet/minecraft/client/renderer/vertex/VertexFormat;IIZZLnet/minecraft/client/renderer/RenderType$State;)V public net.minecraft.block.AbstractBlock$AbstractBlockState$Cache -public net.minecraft.util.math.shapes.VoxelShape (Lnet/minecraft/util/math/shapes/VoxelShapePart;)V # \ No newline at end of file +public net.minecraft.util.math.shapes.VoxelShape (Lnet/minecraft/util/math/shapes/VoxelShapePart;)V # +public net.minecraft.client.renderer.model.ModelBakery$BlockStateDefinitionException \ No newline at end of file diff --git a/src/main/resources/modernfix.mixins.json b/src/main/resources/modernfix.mixins.json index 863318ae..364bc629 100644 --- a/src/main/resources/modernfix.mixins.json +++ b/src/main/resources/modernfix.mixins.json @@ -36,6 +36,7 @@ "perf.parallelize_model_loading.OBJLoaderMixin", "perf.parallelize_model_loading.multipart.MultipartMixin", "perf.parallelize_model_loading.multipart.VariantListMixin", + "perf.parallelize_model_loading.SelectorMixin", "perf.trim_model_caches.ModelManagerMixin", "perf.async_jei.IngredientListElementFactoryMixin", "perf.async_jei.ClientLifecycleHandlerMixin",