Skip to content

Commit cc60cba

Browse files
committed
Refactor Forge dynamic resources implementation to be similar to Fabric
1 parent fb84fda commit cc60cba

File tree

2 files changed

+122
-64
lines changed

2 files changed

+122
-64
lines changed
Lines changed: 84 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
package org.embeddedt.modernfix.forge.mixin.perf.dynamic_resources;
22

3-
import net.minecraft.client.renderer.block.model.BlockModel;
3+
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
44
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
55
import net.minecraft.client.resources.model.*;
6+
import net.minecraft.core.registries.BuiltInRegistries;
67
import net.minecraft.resources.ResourceLocation;
8+
import net.minecraft.world.level.block.Block;
9+
import net.minecraft.world.level.block.state.BlockState;
10+
import net.minecraftforge.fml.util.ObfuscationReflectionHelper;
711
import org.embeddedt.modernfix.ModernFix;
812
import org.embeddedt.modernfix.ModernFixClient;
913
import org.embeddedt.modernfix.api.entrypoint.ModernFixClientIntegration;
@@ -17,71 +21,104 @@
1721
import org.spongepowered.asm.mixin.injection.Inject;
1822
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
1923

24+
import java.lang.invoke.MethodHandle;
25+
import java.lang.invoke.MethodHandles;
26+
import java.util.Objects;
27+
import java.util.Optional;
2028
import java.util.function.Function;
2129

22-
@Mixin(ModelBakery.ModelBakerImpl.class)
30+
@Mixin(value = ModelBakery.ModelBakerImpl.class, priority = 600)
2331
public abstract class ModelBakerImplMixin implements IModelBakerImpl {
2432
private static final boolean debugDynamicModelLoading = Boolean.getBoolean("modernfix.debugDynamicModelLoading");
2533
@Shadow @Final private ModelBakery field_40571;
2634

27-
@Shadow public abstract UnbakedModel getModel(ResourceLocation arg);
28-
2935
private boolean mfix$ignoreCache = false;
3036

37+
@Shadow @Final private Function<Material, TextureAtlasSprite> modelTextureGetter;
38+
39+
private static final MethodHandle blockStateLoaderHandle;
40+
static {
41+
try {
42+
blockStateLoaderHandle = MethodHandles.lookup().unreflect(
43+
ObfuscationReflectionHelper.findMethod(ModelBakery.class, "m_119263_", BlockState.class)
44+
);
45+
} catch(ReflectiveOperationException e) {
46+
throw new RuntimeException(e);
47+
}
48+
}
49+
3150
@Override
3251
public void mfix$ignoreCache() {
3352
mfix$ignoreCache = true;
3453
}
3554

36-
@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)
37-
public void getOrLoadBakedModelDynamic(ResourceLocation arg, ModelState arg2, Function<Material, TextureAtlasSprite> textureGetter, CallbackInfoReturnable<BakedModel> cir) {
38-
ModelBakery.BakedCacheKey key = new ModelBakery.BakedCacheKey(arg, arg2.getRotation(), arg2.isUvLocked());
39-
BakedModel existing = mfix$ignoreCache ? null : this.field_40571.bakedCache.get(key);
40-
if (existing != null) {
41-
cir.setReturnValue(existing);
42-
} else {
43-
synchronized (this) {
44-
if(debugDynamicModelLoading)
45-
ModernFix.LOGGER.info("Baking {}", arg);
46-
UnbakedModel iunbakedmodel = this.getModel(arg);
47-
IExtendedModelBakery extendedBakery = (IExtendedModelBakery)this.field_40571;
48-
if(iunbakedmodel == extendedBakery.mfix$getUnbakedMissingModel() && debugDynamicModelLoading)
49-
ModernFix.LOGGER.warn("Model {} not present", arg);
50-
// TODO: make sure parent resolution doesn't re-run many times
51-
iunbakedmodel.resolveParents(this::getModel);
52-
BakedModel ibakedmodel = null;
53-
if (iunbakedmodel instanceof BlockModel) {
54-
BlockModel blockmodel = (BlockModel)iunbakedmodel;
55-
if (blockmodel.getRootModel() == ModelBakery.GENERATION_MARKER) {
56-
ibakedmodel = ModelBakery.ITEM_MODEL_GENERATOR.generateBlockModel(textureGetter, blockmodel).bake((ModelBaker)this, blockmodel, textureGetter, arg2, arg, false);
57-
}
58-
}
59-
if(iunbakedmodel != extendedBakery.mfix$getUnbakedMissingModel()) {
60-
for(ModernFixClientIntegration integration : ModernFixClient.CLIENT_INTEGRATIONS) {
55+
private ResourceLocation capturedLocation;
56+
private UnbakedModel capturedModel;
57+
private ModelState capturedState;
58+
59+
@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)
60+
private void captureState(ResourceLocation arg, ModelState state, Function<Material, TextureAtlasSprite> sprites, CallbackInfoReturnable<BakedModel> cir) {
61+
capturedState = state;
62+
}
63+
64+
@Inject(method = "getModel", at = @At("HEAD"), cancellable = true)
65+
private void obtainModel(ResourceLocation arg, CallbackInfoReturnable<UnbakedModel> cir) {
66+
capturedLocation = arg;
67+
if(debugDynamicModelLoading)
68+
ModernFix.LOGGER.info("Baking {}", arg);
69+
IExtendedModelBakery extendedBakery = (IExtendedModelBakery)this.field_40571;
70+
if(arg instanceof ModelResourceLocation && arg != ModelBakery.MISSING_MODEL_LOCATION) {
71+
// synchronized because we use topLevelModels
72+
synchronized (this.field_40571) {
73+
/* to emulate vanilla model loading, treat as top-level */
74+
Optional<Block> blockOpt = Objects.equals(((ModelResourceLocation)arg).getVariant(), "inventory") ? Optional.empty() : BuiltInRegistries.BLOCK.getOptional(new ResourceLocation(arg.getNamespace(), arg.getPath()));
75+
if(blockOpt.isPresent()) {
76+
/* load via lambda for mods that expect blockstate to get loaded */
77+
for(BlockState state : extendedBakery.getBlockStatesForMRL(blockOpt.get().getStateDefinition(), (ModelResourceLocation)arg)) {
6178
try {
62-
iunbakedmodel = integration.onUnbakedModelPreBake(arg, iunbakedmodel, this.field_40571);
63-
} catch(RuntimeException e) {
64-
ModernFix.LOGGER.error("Exception encountered firing bake event for {}", arg, e);
79+
blockStateLoaderHandle.invokeExact(this.field_40571, state);
80+
} catch(Throwable e) {
81+
ModernFix.LOGGER.error("Error loading model", e);
6582
}
6683
}
84+
} else {
85+
this.field_40571.loadTopLevel((ModelResourceLocation)arg);
6786
}
68-
if(ibakedmodel == null) {
69-
if(iunbakedmodel == extendedBakery.mfix$getUnbakedMissingModel()) {
70-
// use a shared baked missing model
71-
if(extendedBakery.getBakedMissingModel() == null) {
72-
extendedBakery.setBakedMissingModel(iunbakedmodel.bake((ModelBaker)this, textureGetter, arg2, arg));
73-
((DynamicBakedModelProvider)this.field_40571.getBakedTopLevelModels()).setMissingModel(extendedBakery.getBakedMissingModel());
74-
}
75-
ibakedmodel = extendedBakery.getBakedMissingModel();
76-
} else
77-
ibakedmodel = iunbakedmodel.bake((ModelBaker)this, textureGetter, arg2, arg);
78-
}
79-
for(ModernFixClientIntegration integration : ModernFixClient.CLIENT_INTEGRATIONS) {
80-
ibakedmodel = integration.onBakedModelLoad(arg, iunbakedmodel, ibakedmodel, arg2, this.field_40571);
87+
cir.setReturnValue(this.field_40571.topLevelModels.getOrDefault(arg, extendedBakery.mfix$getUnbakedMissingModel()));
88+
// avoid leaks
89+
this.field_40571.topLevelModels.clear();
90+
}
91+
} else
92+
cir.setReturnValue(this.field_40571.getModel(arg));
93+
UnbakedModel toReplace = cir.getReturnValue();
94+
if(true) {
95+
for(ModernFixClientIntegration integration : ModernFixClient.CLIENT_INTEGRATIONS) {
96+
try {
97+
toReplace = integration.onUnbakedModelPreBake(arg, toReplace, this.field_40571);
98+
} catch(RuntimeException e) {
99+
ModernFix.LOGGER.error("Exception firing model pre-bake event for {}", arg, e);
81100
}
82-
this.field_40571.bakedCache.put(key, ibakedmodel);
83-
cir.setReturnValue(ibakedmodel);
84101
}
85102
}
103+
cir.setReturnValue(toReplace);
104+
cir.getReturnValue().resolveParents(this.field_40571::getModel);
105+
capturedModel = cir.getReturnValue();
106+
if(cir.getReturnValue() == extendedBakery.mfix$getUnbakedMissingModel()) {
107+
if(arg != ModelBakery.MISSING_MODEL_LOCATION && debugDynamicModelLoading)
108+
ModernFix.LOGGER.warn("Model {} not present", arg);
109+
}
110+
}
111+
112+
@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))
113+
private Object ignoreCacheIfRequested(Object o) {
114+
return mfix$ignoreCache ? null : o;
115+
}
116+
117+
@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;"))
118+
private BakedModel unifyMissingBakedModel(BakedModel model) {
119+
for(ModernFixClientIntegration integration : ModernFixClient.CLIENT_INTEGRATIONS) {
120+
model = integration.onBakedModelLoad(capturedLocation, capturedModel, model, capturedState, this.field_40571);
121+
}
122+
return model;
86123
}
87124
}

forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/dynamic_resources/ModelBakeryMixin.java

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,7 @@
2828
import org.spongepowered.asm.mixin.Mixin;
2929
import org.spongepowered.asm.mixin.Mutable;
3030
import org.spongepowered.asm.mixin.Shadow;
31-
import org.spongepowered.asm.mixin.injection.At;
32-
import org.spongepowered.asm.mixin.injection.Inject;
33-
import org.spongepowered.asm.mixin.injection.ModifyVariable;
34-
import org.spongepowered.asm.mixin.injection.Redirect;
31+
import org.spongepowered.asm.mixin.injection.*;
3532
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
3633
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
3734

@@ -42,8 +39,8 @@
4239
import java.util.function.BiConsumer;
4340
import java.util.function.BiFunction;
4441

45-
/* high priority so that our injectors are added before other mods' */
46-
@Mixin(value = ModelBakery.class, priority = 600)
42+
/* low priority so that our injectors are added after other mods' */
43+
@Mixin(value = ModelBakery.class, priority = 1100)
4744
@ClientOnlyMixin
4845
public abstract class ModelBakeryMixin implements IExtendedModelBakery {
4946

@@ -68,6 +65,9 @@ public abstract class ModelBakeryMixin implements IExtendedModelBakery {
6865

6966
@Shadow @Final private static Logger LOGGER;
7067

68+
@Shadow
69+
public abstract void loadTopLevel(ModelResourceLocation modelResourceLocation);
70+
7171
@Shadow public abstract UnbakedModel getModel(ResourceLocation resourceLocation);
7272

7373
private Cache<ModelBakery.BakedCacheKey, BakedModel> loadedBakedModels;
@@ -76,6 +76,8 @@ public abstract class ModelBakeryMixin implements IExtendedModelBakery {
7676

7777
private HashMap<ResourceLocation, UnbakedModel> smallLoadingCache = new HashMap<>();
7878

79+
private boolean ignoreModelLoad;
80+
7981
// disable fabric recursion
8082
@SuppressWarnings("unused")
8183
private boolean fabric_enableGetOrLoadModelGuard;
@@ -116,6 +118,12 @@ public UnbakedModel put(ResourceLocation key, UnbakedModel value) {
116118
this.bakedTopLevelModels = new DynamicBakedModelProvider((ModelBakery)(Object)this, bakedCache);
117119
}
118120

121+
@ModifyArg(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/profiling/ProfilerFiller;popPush(Ljava/lang/String;)V", ordinal = 0), index = 0)
122+
private String ignoreFutureModelLoads(String name) {
123+
this.ignoreModelLoad = true;
124+
return name;
125+
}
126+
119127
private <K, V> void onModelRemoved(RemovalNotification<K, V> notification) {
120128
if(!debugDynamicModelLoading)
121129
return;
@@ -130,6 +138,9 @@ private <K, V> void onModelRemoved(RemovalNotification<K, V> notification) {
130138
rl = ((ModelBakery.BakedCacheKey)k).id();
131139
baked = true;
132140
}
141+
/* can fire when a model is replaced */
142+
if(!baked && this.loadedModels.getIfPresent(rl) != null)
143+
return;
133144
ModernFix.LOGGER.warn("Evicted {} model {}", baked ? "baked" : "unbaked", rl);
134145
}
135146

@@ -138,21 +149,23 @@ private <K, V> void onModelRemoved(RemovalNotification<K, V> notification) {
138149
private Set<ResourceLocation> blockStateFiles;
139150
private Set<ResourceLocation> modelFiles;
140151

141-
@Redirect(method = "<init>", 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))
142-
private BlockModel captureMissingModel(ModelBakery bakery, ResourceLocation location) throws IOException {
143-
this.missingModel = this.loadBlockModel(location);
152+
@ModifyArg(method = "<init>", at = @At(value = "INVOKE", target = "Ljava/util/Map;put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", ordinal = 0), index = 1)
153+
private Object captureMissingModel(Object model) {
154+
this.missingModel = (UnbakedModel)model;
144155
this.blockStateFiles = new HashSet<>();
145156
this.modelFiles = new HashSet<>();
146-
return (BlockModel)this.missingModel;
157+
return this.missingModel;
147158
}
148159

149160
/**
150161
* @author embeddedt
151-
* @reason don't actually load the model.
162+
* @reason don't actually load most models
152163
*/
153-
@Inject(method = "loadTopLevel", at = @At("HEAD"), cancellable = true)
154-
private void addTopLevelFile(ModelResourceLocation location, CallbackInfo ci) {
155-
ci.cancel();
164+
@Redirect(method = "*", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/ModelBakery;loadTopLevel(Lnet/minecraft/client/resources/model/ModelResourceLocation;)V"))
165+
private void addTopLevelFile(ModelBakery bakery, ModelResourceLocation location) {
166+
if(location == MISSING_MODEL_LOCATION || !this.ignoreModelLoad) {
167+
loadTopLevel(location);
168+
}
156169
}
157170

158171
@Redirect(method = "<init>", at = @At(value = "INVOKE", target = "Ljava/util/Map;forEach(Ljava/util/function/BiConsumer;)V", ordinal = 0))
@@ -177,13 +190,14 @@ private Collection<?> copyTopLevelModelList(Map<?, ?> map) {
177190
private BiFunction<ResourceLocation, Material, TextureAtlasSprite> textureGetter;
178191

179192
@Inject(method = "bakeModels", at = @At("HEAD"))
180-
private void storeTextureGetter(BiFunction<ResourceLocation, Material, TextureAtlasSprite> getter, CallbackInfo ci) {
193+
private void captureGetter(BiFunction<ResourceLocation, Material, TextureAtlasSprite> getter, CallbackInfo ci) {
194+
this.ignoreModelLoad = false;
181195
textureGetter = getter;
182196
DynamicBakedModelProvider.currentInstance = (DynamicBakedModelProvider)this.bakedTopLevelModels;
183197
}
184198

185199
@Redirect(method = "bakeModels", at = @At(value = "INVOKE", target = "Ljava/util/Map;keySet()Ljava/util/Set;"))
186-
private Set skipBakingModels(Map instance) {
200+
private Set<ResourceLocation> skipBake(Map<ResourceLocation, UnbakedModel> instance) {
187201
Set<ResourceLocation> modelSet = new HashSet<>(instance.keySet());
188202
if(modelSet.size() > 0)
189203
ModernFix.LOGGER.info("Early baking {} models", modelSet.size());
@@ -295,9 +309,16 @@ private ImmutableList<BlockState> loadOnlyRelevantBlockState(StateDefinition<Blo
295309

296310
@Override
297311
public BakedModel bakeDefault(ResourceLocation modelLocation, ModelState state) {
312+
ModelBakery.BakedCacheKey key = new ModelBakery.BakedCacheKey(modelLocation, BlockModelRotation.X0_Y0.getRotation(), BlockModelRotation.X0_Y0.isUvLocked());
313+
BakedModel m = loadedBakedModels.getIfPresent(key);
314+
if(m != null)
315+
return m;
298316
ModelBakery self = (ModelBakery) (Object) this;
299317
ModelBaker theBaker = self.new ModelBakerImpl(textureGetter, modelLocation);
300-
return theBaker.bake(modelLocation, state, theBaker.getModelTextureGetter());
318+
synchronized(this) { m = theBaker.bake(modelLocation, state, theBaker.getModelTextureGetter()); }
319+
if(m != null)
320+
loadedBakedModels.put(key, m);
321+
return m;
301322
}
302323

303324
@Override

0 commit comments

Comments
 (0)