From cc60cba1f211a55a569db0a6edda8a6d8b7e2d20 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sat, 4 Nov 2023 10:21:22 -0400 Subject: [PATCH] Refactor Forge dynamic resources implementation to be similar to Fabric --- .../ModelBakerImplMixin.java | 131 +++++++++++------- .../dynamic_resources/ModelBakeryMixin.java | 55 +++++--- 2 files changed, 122 insertions(+), 64 deletions(-) diff --git a/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/dynamic_resources/ModelBakerImplMixin.java b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/dynamic_resources/ModelBakerImplMixin.java index 9ca72ed5..c19e1f42 100644 --- a/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/dynamic_resources/ModelBakerImplMixin.java +++ b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/dynamic_resources/ModelBakerImplMixin.java @@ -1,9 +1,13 @@ package org.embeddedt.modernfix.forge.mixin.perf.dynamic_resources; -import net.minecraft.client.renderer.block.model.BlockModel; +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.resources.model.*; +import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraftforge.fml.util.ObfuscationReflectionHelper; import org.embeddedt.modernfix.ModernFix; import org.embeddedt.modernfix.ModernFixClient; import org.embeddedt.modernfix.api.entrypoint.ModernFixClientIntegration; @@ -17,71 +21,104 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.util.Objects; +import java.util.Optional; import java.util.function.Function; -@Mixin(ModelBakery.ModelBakerImpl.class) +@Mixin(value = ModelBakery.ModelBakerImpl.class, priority = 600) public abstract class ModelBakerImplMixin implements IModelBakerImpl { private static final boolean debugDynamicModelLoading = Boolean.getBoolean("modernfix.debugDynamicModelLoading"); @Shadow @Final private ModelBakery field_40571; - @Shadow public abstract UnbakedModel getModel(ResourceLocation arg); - private boolean mfix$ignoreCache = false; + @Shadow @Final private Function modelTextureGetter; + + private static final MethodHandle blockStateLoaderHandle; + static { + try { + blockStateLoaderHandle = MethodHandles.lookup().unreflect( + ObfuscationReflectionHelper.findMethod(ModelBakery.class, "m_119263_", BlockState.class) + ); + } catch(ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + @Override public void mfix$ignoreCache() { mfix$ignoreCache = true; } - @Inject(method = "bake(Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/client/resources/model/ModelState;Ljava/util/function/Function;)Lnet/minecraft/client/resources/model/BakedModel;", at = @At("HEAD"), cancellable = true, remap = false) - public void getOrLoadBakedModelDynamic(ResourceLocation arg, ModelState arg2, Function textureGetter, CallbackInfoReturnable cir) { - ModelBakery.BakedCacheKey key = new ModelBakery.BakedCacheKey(arg, arg2.getRotation(), arg2.isUvLocked()); - BakedModel existing = mfix$ignoreCache ? null : this.field_40571.bakedCache.get(key); - if (existing != null) { - cir.setReturnValue(existing); - } else { - synchronized (this) { - if(debugDynamicModelLoading) - ModernFix.LOGGER.info("Baking {}", arg); - UnbakedModel iunbakedmodel = this.getModel(arg); - IExtendedModelBakery extendedBakery = (IExtendedModelBakery)this.field_40571; - if(iunbakedmodel == extendedBakery.mfix$getUnbakedMissingModel() && debugDynamicModelLoading) - ModernFix.LOGGER.warn("Model {} not present", arg); - // TODO: make sure parent resolution doesn't re-run many times - iunbakedmodel.resolveParents(this::getModel); - BakedModel ibakedmodel = null; - if (iunbakedmodel instanceof BlockModel) { - BlockModel blockmodel = (BlockModel)iunbakedmodel; - if (blockmodel.getRootModel() == ModelBakery.GENERATION_MARKER) { - ibakedmodel = ModelBakery.ITEM_MODEL_GENERATOR.generateBlockModel(textureGetter, blockmodel).bake((ModelBaker)this, blockmodel, textureGetter, arg2, arg, false); - } - } - if(iunbakedmodel != extendedBakery.mfix$getUnbakedMissingModel()) { - for(ModernFixClientIntegration integration : ModernFixClient.CLIENT_INTEGRATIONS) { + private ResourceLocation capturedLocation; + private UnbakedModel capturedModel; + private ModelState capturedState; + + @Inject(method = "bake(Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/client/resources/model/ModelState;Ljava/util/function/Function;)Lnet/minecraft/client/resources/model/BakedModel;", at = @At("HEAD"), remap = false) + private void captureState(ResourceLocation arg, ModelState state, Function sprites, CallbackInfoReturnable cir) { + capturedState = state; + } + + @Inject(method = "getModel", at = @At("HEAD"), cancellable = true) + private void obtainModel(ResourceLocation arg, CallbackInfoReturnable cir) { + capturedLocation = arg; + if(debugDynamicModelLoading) + ModernFix.LOGGER.info("Baking {}", arg); + IExtendedModelBakery extendedBakery = (IExtendedModelBakery)this.field_40571; + if(arg instanceof ModelResourceLocation && arg != ModelBakery.MISSING_MODEL_LOCATION) { + // synchronized because we use topLevelModels + synchronized (this.field_40571) { + /* to emulate vanilla model loading, treat as top-level */ + Optional blockOpt = Objects.equals(((ModelResourceLocation)arg).getVariant(), "inventory") ? Optional.empty() : BuiltInRegistries.BLOCK.getOptional(new ResourceLocation(arg.getNamespace(), arg.getPath())); + if(blockOpt.isPresent()) { + /* load via lambda for mods that expect blockstate to get loaded */ + for(BlockState state : extendedBakery.getBlockStatesForMRL(blockOpt.get().getStateDefinition(), (ModelResourceLocation)arg)) { try { - iunbakedmodel = integration.onUnbakedModelPreBake(arg, iunbakedmodel, this.field_40571); - } catch(RuntimeException e) { - ModernFix.LOGGER.error("Exception encountered firing bake event for {}", arg, e); + blockStateLoaderHandle.invokeExact(this.field_40571, state); + } catch(Throwable e) { + ModernFix.LOGGER.error("Error loading model", e); } } + } else { + this.field_40571.loadTopLevel((ModelResourceLocation)arg); } - if(ibakedmodel == null) { - if(iunbakedmodel == extendedBakery.mfix$getUnbakedMissingModel()) { - // use a shared baked missing model - if(extendedBakery.getBakedMissingModel() == null) { - extendedBakery.setBakedMissingModel(iunbakedmodel.bake((ModelBaker)this, textureGetter, arg2, arg)); - ((DynamicBakedModelProvider)this.field_40571.getBakedTopLevelModels()).setMissingModel(extendedBakery.getBakedMissingModel()); - } - ibakedmodel = extendedBakery.getBakedMissingModel(); - } else - ibakedmodel = iunbakedmodel.bake((ModelBaker)this, textureGetter, arg2, arg); + cir.setReturnValue(this.field_40571.topLevelModels.getOrDefault(arg, extendedBakery.mfix$getUnbakedMissingModel())); + // avoid leaks + this.field_40571.topLevelModels.clear(); + } + } else + cir.setReturnValue(this.field_40571.getModel(arg)); + UnbakedModel toReplace = cir.getReturnValue(); + if(true) { + for(ModernFixClientIntegration integration : ModernFixClient.CLIENT_INTEGRATIONS) { + try { + toReplace = integration.onUnbakedModelPreBake(arg, toReplace, this.field_40571); + } catch(RuntimeException e) { + ModernFix.LOGGER.error("Exception firing model pre-bake event for {}", arg, e); } - for(ModernFixClientIntegration integration : ModernFixClient.CLIENT_INTEGRATIONS) { - ibakedmodel = integration.onBakedModelLoad(arg, iunbakedmodel, ibakedmodel, arg2, this.field_40571); - } - this.field_40571.bakedCache.put(key, ibakedmodel); - cir.setReturnValue(ibakedmodel); } } + cir.setReturnValue(toReplace); + cir.getReturnValue().resolveParents(this.field_40571::getModel); + capturedModel = cir.getReturnValue(); + if(cir.getReturnValue() == extendedBakery.mfix$getUnbakedMissingModel()) { + if(arg != ModelBakery.MISSING_MODEL_LOCATION && debugDynamicModelLoading) + ModernFix.LOGGER.warn("Model {} not present", arg); + } + } + + @ModifyExpressionValue(method = "bake(Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/client/resources/model/ModelState;Ljava/util/function/Function;)Lnet/minecraft/client/resources/model/BakedModel;", at = @At(value = "INVOKE", target = "Ljava/util/Map;get(Ljava/lang/Object;)Ljava/lang/Object;", ordinal = 0)) + private Object ignoreCacheIfRequested(Object o) { + return mfix$ignoreCache ? null : o; + } + + @ModifyExpressionValue(method = "bake(Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/client/resources/model/ModelState;Ljava/util/function/Function;)Lnet/minecraft/client/resources/model/BakedModel;", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/UnbakedModel;bake(Lnet/minecraft/client/resources/model/ModelBaker;Ljava/util/function/Function;Lnet/minecraft/client/resources/model/ModelState;Lnet/minecraft/resources/ResourceLocation;)Lnet/minecraft/client/resources/model/BakedModel;")) + private BakedModel unifyMissingBakedModel(BakedModel model) { + for(ModernFixClientIntegration integration : ModernFixClient.CLIENT_INTEGRATIONS) { + model = integration.onBakedModelLoad(capturedLocation, capturedModel, model, capturedState, this.field_40571); + } + return model; } } diff --git a/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/dynamic_resources/ModelBakeryMixin.java b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/dynamic_resources/ModelBakeryMixin.java index 99f1ebe5..efc88cd8 100644 --- a/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/dynamic_resources/ModelBakeryMixin.java +++ b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/dynamic_resources/ModelBakeryMixin.java @@ -28,10 +28,7 @@ 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.ModifyVariable; -import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.*; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @@ -42,8 +39,8 @@ import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import java.util.function.BiFunction; -/* high priority so that our injectors are added before other mods' */ -@Mixin(value = ModelBakery.class, priority = 600) +/* low priority so that our injectors are added after other mods' */ +@Mixin(value = ModelBakery.class, priority = 1100) @ClientOnlyMixin public abstract class ModelBakeryMixin implements IExtendedModelBakery { @@ -68,6 +65,9 @@ public abstract class ModelBakeryMixin implements IExtendedModelBakery { @Shadow @Final private static Logger LOGGER; + @Shadow + public abstract void loadTopLevel(ModelResourceLocation modelResourceLocation); + @Shadow public abstract UnbakedModel getModel(ResourceLocation resourceLocation); private Cache loadedBakedModels; @@ -76,6 +76,8 @@ public abstract class ModelBakeryMixin implements IExtendedModelBakery { private HashMap smallLoadingCache = new HashMap<>(); + private boolean ignoreModelLoad; + // disable fabric recursion @SuppressWarnings("unused") private boolean fabric_enableGetOrLoadModelGuard; @@ -116,6 +118,12 @@ public abstract class ModelBakeryMixin implements IExtendedModelBakery { this.bakedTopLevelModels = new DynamicBakedModelProvider((ModelBakery)(Object)this, bakedCache); } + @ModifyArg(method = "", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/profiling/ProfilerFiller;popPush(Ljava/lang/String;)V", ordinal = 0), index = 0) + private String ignoreFutureModelLoads(String name) { + this.ignoreModelLoad = true; + return name; + } + private void onModelRemoved(RemovalNotification notification) { if(!debugDynamicModelLoading) return; @@ -130,6 +138,9 @@ public abstract class ModelBakeryMixin implements IExtendedModelBakery { rl = ((ModelBakery.BakedCacheKey)k).id(); baked = true; } + /* can fire when a model is replaced */ + if(!baked && this.loadedModels.getIfPresent(rl) != null) + return; ModernFix.LOGGER.warn("Evicted {} model {}", baked ? "baked" : "unbaked", rl); } @@ -138,21 +149,23 @@ public abstract class ModelBakeryMixin implements IExtendedModelBakery { private Set blockStateFiles; private Set modelFiles; - @Redirect(method = "", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/ModelBakery;loadBlockModel(Lnet/minecraft/resources/ResourceLocation;)Lnet/minecraft/client/renderer/block/model/BlockModel;", ordinal = 0)) - private BlockModel captureMissingModel(ModelBakery bakery, ResourceLocation location) throws IOException { - this.missingModel = this.loadBlockModel(location); + @ModifyArg(method = "", at = @At(value = "INVOKE", target = "Ljava/util/Map;put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", ordinal = 0), index = 1) + private Object captureMissingModel(Object model) { + this.missingModel = (UnbakedModel)model; this.blockStateFiles = new HashSet<>(); this.modelFiles = new HashSet<>(); - return (BlockModel)this.missingModel; + return this.missingModel; } /** * @author embeddedt - * @reason don't actually load the model. + * @reason don't actually load most models */ - @Inject(method = "loadTopLevel", at = @At("HEAD"), cancellable = true) - private void addTopLevelFile(ModelResourceLocation location, CallbackInfo ci) { - ci.cancel(); + @Redirect(method = "*", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/ModelBakery;loadTopLevel(Lnet/minecraft/client/resources/model/ModelResourceLocation;)V")) + private void addTopLevelFile(ModelBakery bakery, ModelResourceLocation location) { + if(location == MISSING_MODEL_LOCATION || !this.ignoreModelLoad) { + loadTopLevel(location); + } } @Redirect(method = "", at = @At(value = "INVOKE", target = "Ljava/util/Map;forEach(Ljava/util/function/BiConsumer;)V", ordinal = 0)) @@ -177,13 +190,14 @@ public abstract class ModelBakeryMixin implements IExtendedModelBakery { private BiFunction textureGetter; @Inject(method = "bakeModels", at = @At("HEAD")) - private void storeTextureGetter(BiFunction getter, CallbackInfo ci) { + private void captureGetter(BiFunction getter, CallbackInfo ci) { + this.ignoreModelLoad = false; textureGetter = getter; DynamicBakedModelProvider.currentInstance = (DynamicBakedModelProvider)this.bakedTopLevelModels; } @Redirect(method = "bakeModels", at = @At(value = "INVOKE", target = "Ljava/util/Map;keySet()Ljava/util/Set;")) - private Set skipBakingModels(Map instance) { + private Set skipBake(Map instance) { Set modelSet = new HashSet<>(instance.keySet()); if(modelSet.size() > 0) ModernFix.LOGGER.info("Early baking {} models", modelSet.size()); @@ -295,9 +309,16 @@ public abstract class ModelBakeryMixin implements IExtendedModelBakery { @Override public BakedModel bakeDefault(ResourceLocation modelLocation, ModelState state) { + ModelBakery.BakedCacheKey key = new ModelBakery.BakedCacheKey(modelLocation, BlockModelRotation.X0_Y0.getRotation(), BlockModelRotation.X0_Y0.isUvLocked()); + BakedModel m = loadedBakedModels.getIfPresent(key); + if(m != null) + return m; ModelBakery self = (ModelBakery) (Object) this; ModelBaker theBaker = self.new ModelBakerImpl(textureGetter, modelLocation); - return theBaker.bake(modelLocation, state, theBaker.getModelTextureGetter()); + synchronized(this) { m = theBaker.bake(modelLocation, state, theBaker.getModelTextureGetter()); } + if(m != null) + loadedBakedModels.put(key, m); + return m; } @Override