From 9180c79854e5a98b372a563cda494887cbcf38a5 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Mon, 2 Jan 2023 13:56:51 -0500 Subject: [PATCH] Parallelize parts of model loading/baking --- .../modernfix/mixin/ModelBakeryMixin.java | 125 ++++++++++++++++++ src/main/resources/modernfix.mixins.json | 3 +- 2 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/embeddedt/modernfix/mixin/ModelBakeryMixin.java diff --git a/src/main/java/org/embeddedt/modernfix/mixin/ModelBakeryMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/ModelBakeryMixin.java new file mode 100644 index 00000000..eea57fc9 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/ModelBakeryMixin.java @@ -0,0 +1,125 @@ +package org.embeddedt.modernfix.mixin; + +import com.google.common.base.Stopwatch; +import com.google.common.cache.LoadingCache; +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.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 org.embeddedt.modernfix.ModernFix; +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.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import javax.annotation.Nullable; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Mixin(ModelBakery.class) +public abstract class ModelBakeryMixin { + @Shadow protected abstract BlockModel loadModel(ResourceLocation location) throws IOException; + + @Shadow @Final private Map unbakedModels; + @Shadow @Final public static ModelResourceLocation MODEL_MISSING; + + @Shadow public abstract IUnbakedModel getUnbakedModel(ResourceLocation modelLocation); + + @Shadow @Final public static BlockModel MODEL_GENERATED; + @Shadow @Final private static ItemModelGenerator ITEM_MODEL_GENERATOR; + @Shadow @Nullable private SpriteMap spriteMap; + @Shadow @Final private Map, IBakedModel> bakedModels; + @Shadow @Final private Map topBakedModels; + private Map deserializedModelCache = null; + private boolean useModelCache = false; + + @Inject(method = "loadModel", at = @At("HEAD"), cancellable = true) + private void useCachedModel(ResourceLocation location, CallbackInfoReturnable cir) { + if(useModelCache && deserializedModelCache != null) { + BlockModel model = deserializedModelCache.get(location); + if(model != null) + cir.setReturnValue(model); + } + } + + private BlockModel loadModelSafely(ResourceLocation location) { + try { + return this.loadModel(location); + } catch(Throwable e) { + ModernFix.LOGGER.warn("Model " + location + " will not be preloaded", e); + return null; + } + } + + @Inject(method = "processLoading", at = @At(value = "INVOKE", target = "Lnet/minecraft/profiler/IProfiler;endStartSection(Ljava/lang/String;)V", ordinal = 1)) + private void preloadJsonModels(IProfiler profilerIn, int maxMipmapLevel, CallbackInfo ci) { + profilerIn.endStartSection("loadjsons"); + ModernFix.LOGGER.warn("Preloading JSONs in parallel..."); + Stopwatch stopwatch = Stopwatch.createStarted(); + useModelCache = false; + deserializedModelCache = Minecraft.getInstance().getResourceManager().getAllResourceLocations("models", p -> p.endsWith(".json")) + .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.SECONDS) + " seconds"); + stopwatch.stop(); + } + + @Inject(method = "processLoading", at = @At("RETURN"), remap = false) + private void clearModelCache(IProfiler profilerIn, int maxMipmapLevel, CallbackInfo ci) { + deserializedModelCache.clear(); + useModelCache = false; + } + + @Redirect(method = "uploadTextures", at = @At(value = "INVOKE", target = "Ljava/util/Set;forEach(Ljava/util/function/Consumer;)V", ordinal = 0)) + private void parallelBake(Set locationSet, Consumer consumer) { + final IModelTransform transform = ModelRotation.X0_Y0; + if(this.spriteMap == null) + throw new IllegalStateException("no sprite map"); + ModernFix.LOGGER.warn("Baking models in parallel..."); + Stopwatch stopwatch = Stopwatch.createStarted(); + locationSet.forEach(this::getUnbakedModel); /* make sure every unbaked model is loaded, should be fast */ + List> models = CompletableFuture.supplyAsync(() -> { + return locationSet.parallelStream().map(location -> { + IUnbakedModel iunbakedmodel = this.unbakedModels.get(location); + if (iunbakedmodel instanceof BlockModel) { + BlockModel blockmodel = (BlockModel)iunbakedmodel; + if (blockmodel.getRootModel() == MODEL_GENERATED) { + return Pair.of(location, ITEM_MODEL_GENERATOR.makeItemModel(this.spriteMap::getSprite, blockmodel).bakeModel((ModelBakery)(Object)this, blockmodel, this.spriteMap::getSprite, transform, location, false)); + } + } + + IBakedModel ibakedmodel = iunbakedmodel.bakeModel((ModelBakery)(Object)this, this.spriteMap::getSprite, transform, location); + return Pair.of(location, ibakedmodel); + }).collect(Collectors.toList()); + }, Util.getServerExecutor()).join(); + models.forEach(pair -> { + Triple triple = Triple.of(pair.getKey(), transform.getRotation(), transform.isUvLock()); + this.bakedModels.put(triple, pair.getValue()); + if(pair.getValue() != null) + this.topBakedModels.put(pair.getKey(), pair.getValue()); + }); + ModernFix.LOGGER.warn("Baking in parallel took " + stopwatch.elapsed(TimeUnit.SECONDS) + " seconds"); + stopwatch.stop(); + } +} diff --git a/src/main/resources/modernfix.mixins.json b/src/main/resources/modernfix.mixins.json index b251b2d2..fa87bb38 100644 --- a/src/main/resources/modernfix.mixins.json +++ b/src/main/resources/modernfix.mixins.json @@ -13,7 +13,8 @@ ], "client": [ "MinecraftMixin", - "RenderTypeMixin" + "RenderTypeMixin", + "ModelBakeryMixin" ], "injectors": { "defaultRequire": 1