1.21-pre1 - rewrite dynamic resources
This commit is contained in:
parent
2ef7d5fc85
commit
ab8810b7fe
|
|
@ -7,10 +7,8 @@ import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.world.level.block.Block;
|
import net.minecraft.world.level.block.Block;
|
||||||
import net.minecraft.world.level.block.state.BlockState;
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
import net.minecraft.world.level.block.state.StateDefinition;
|
import net.minecraft.world.level.block.state.StateDefinition;
|
||||||
import org.embeddedt.modernfix.duck.IExtendedModelBakery;
|
|
||||||
import org.embeddedt.modernfix.dynamicresources.ModelBakeryHelpers;
|
import org.embeddedt.modernfix.dynamicresources.ModelBakeryHelpers;
|
||||||
import org.embeddedt.modernfix.util.DynamicMap;
|
import org.embeddedt.modernfix.util.DynamicMap;
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
@ -25,7 +23,7 @@ public final class ModelHelpers {
|
||||||
* @return a list of all blockstates related to the model
|
* @return a list of all blockstates related to the model
|
||||||
*/
|
*/
|
||||||
public static ImmutableList<BlockState> getBlockStateForLocation(ModelResourceLocation location) {
|
public static ImmutableList<BlockState> getBlockStateForLocation(ModelResourceLocation location) {
|
||||||
Optional<Block> blockOpt = BuiltInRegistries.BLOCK.getOptional(ResourceLocation.fromNamespaceAndPath(location.getNamespace(), location.getPath()));
|
Optional<Block> blockOpt = BuiltInRegistries.BLOCK.getOptional(location.id());
|
||||||
if(blockOpt.isPresent())
|
if(blockOpt.isPresent())
|
||||||
return ModelBakeryHelpers.getBlockStatesForMRL(blockOpt.get().getStateDefinition(), location);
|
return ModelBakeryHelpers.getBlockStatesForMRL(blockOpt.get().getStateDefinition(), location);
|
||||||
else
|
else
|
||||||
|
|
@ -58,6 +56,8 @@ public final class ModelHelpers {
|
||||||
* @return an appropriate ModelBaker
|
* @return an appropriate ModelBaker
|
||||||
*/
|
*/
|
||||||
public static ModelBaker adaptBakery(ModelBakery bakery) {
|
public static ModelBaker adaptBakery(ModelBakery bakery) {
|
||||||
|
throw new UnsupportedOperationException("TODO");
|
||||||
|
/*
|
||||||
return new ModelBaker() {
|
return new ModelBaker() {
|
||||||
@Override
|
@Override
|
||||||
public UnbakedModel getModel(ResourceLocation resourceLocation) {
|
public UnbakedModel getModel(ResourceLocation resourceLocation) {
|
||||||
|
|
@ -70,5 +70,7 @@ public final class ModelHelpers {
|
||||||
return ((IExtendedModelBakery)bakery).bakeDefault(resourceLocation, modelState);
|
return ((IExtendedModelBakery)bakery).bakeDefault(resourceLocation, modelState);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
|
|
||||||
|
|
||||||
import com.google.gson.JsonDeserializationContext;
|
|
||||||
import com.google.gson.JsonElement;
|
|
||||||
import net.minecraft.client.renderer.block.model.BlockElementFace;
|
|
||||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
|
||||||
import org.embeddedt.modernfix.dynamicresources.UVController;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
|
||||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
|
||||||
|
|
||||||
import java.lang.reflect.Type;
|
|
||||||
|
|
||||||
@Mixin(BlockElementFace.Deserializer.class)
|
|
||||||
@ClientOnlyMixin
|
|
||||||
public class BlockElementFaceDeserializerMixin {
|
|
||||||
|
|
||||||
@Redirect(method = "deserialize(Lcom/google/gson/JsonElement;Ljava/lang/reflect/Type;Lcom/google/gson/JsonDeserializationContext;)Lnet/minecraft/client/renderer/block/model/BlockElementFace;",
|
|
||||||
at = @At(value = "INVOKE", target = "Lcom/google/gson/JsonDeserializationContext;deserialize(Lcom/google/gson/JsonElement;Ljava/lang/reflect/Type;)Ljava/lang/Object;", ordinal = 0, remap = false))
|
|
||||||
private Object skipUvsForInitialLoad(JsonDeserializationContext context, JsonElement element, Type type) {
|
|
||||||
return UVController.useDummyUv.get() ? UVController.dummyUv : context.deserialize(element, type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2IntMaps;
|
||||||
|
import net.minecraft.client.color.block.BlockColors;
|
||||||
|
import net.minecraft.client.resources.model.BlockStateModelLoader;
|
||||||
|
import net.minecraft.client.resources.model.UnbakedModel;
|
||||||
|
import net.minecraft.core.DefaultedRegistry;
|
||||||
|
import net.minecraft.core.registries.BuiltInRegistries;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.util.profiling.ProfilerFiller;
|
||||||
|
import net.minecraft.world.level.block.Block;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import net.minecraft.world.level.block.state.StateDefinition;
|
||||||
|
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||||
|
import org.embeddedt.modernfix.duck.IBlockStateModelLoader;
|
||||||
|
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.Redirect;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
@Mixin(BlockStateModelLoader.class)
|
||||||
|
@ClientOnlyMixin
|
||||||
|
public abstract class BlockStateModelLoaderMixin implements IBlockStateModelLoader {
|
||||||
|
@Shadow protected abstract void loadBlockStateDefinitions(ResourceLocation resourceLocation, StateDefinition<Block, BlockState> stateDefinition);
|
||||||
|
|
||||||
|
@Shadow @Mutable @Final private Object2IntMap<BlockState> modelGroups;
|
||||||
|
|
||||||
|
@Inject(method = "<init>", at = @At("RETURN"))
|
||||||
|
private void makeModelGroupsSynchronized(Map map, ProfilerFiller profilerFiller, UnbakedModel unbakedModel, BlockColors blockColors, BiConsumer biConsumer, CallbackInfo ci) {
|
||||||
|
this.modelGroups = Object2IntMaps.synchronize(this.modelGroups);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadSpecificBlock(ResourceLocation location) {
|
||||||
|
var optionalBlock = BuiltInRegistries.BLOCK.getOptional(location);
|
||||||
|
if(optionalBlock.isPresent()) {
|
||||||
|
this.loadBlockStateDefinitions(location, optionalBlock.get().getStateDefinition());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Redirect(method = "loadAllBlockStates", at = @At(value = "INVOKE", target = "Lnet/minecraft/core/DefaultedRegistry;iterator()Ljava/util/Iterator;"))
|
||||||
|
private Iterator<?> skipIteratingBlocks(DefaultedRegistry instance) {
|
||||||
|
return Collections.emptyIterator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
|
|
||||||
|
|
||||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
|
||||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
|
||||||
import net.minecraft.client.renderer.block.model.ItemOverrides;
|
|
||||||
import net.minecraft.client.resources.model.ModelBaker;
|
|
||||||
import net.minecraft.client.resources.model.UnbakedModel;
|
|
||||||
import net.minecraft.resources.ResourceLocation;
|
|
||||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
|
||||||
import org.embeddedt.modernfix.duck.IExtendedModelBaker;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
|
||||||
|
|
||||||
@Mixin(ItemOverrides.class)
|
|
||||||
@ClientOnlyMixin
|
|
||||||
public class ItemOverridesMixin {
|
|
||||||
@WrapOperation(method = "*", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/ModelBaker;getModel(Lnet/minecraft/resources/ResourceLocation;)Lnet/minecraft/client/resources/model/UnbakedModel;"))
|
|
||||||
private UnbakedModel preventThrowForMissing(ModelBaker instance, ResourceLocation resourceLocation, Operation<UnbakedModel> original) {
|
|
||||||
boolean prevState = ((IExtendedModelBaker)instance).throwOnMissingModel(false);
|
|
||||||
try {
|
|
||||||
return original.call(instance, resourceLocation);
|
|
||||||
} finally {
|
|
||||||
((IExtendedModelBaker)instance).throwOnMissingModel(prevState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
|
||||||
|
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.resources.model.ModelManager;
|
||||||
|
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||||
|
import org.embeddedt.modernfix.duck.IExtendedModelManager;
|
||||||
|
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.callback.CallbackInfo;
|
||||||
|
|
||||||
|
@Mixin(Minecraft.class)
|
||||||
|
@ClientOnlyMixin
|
||||||
|
public abstract class MinecraftMixin_ModelTicking {
|
||||||
|
@Shadow public abstract ModelManager getModelManager();
|
||||||
|
|
||||||
|
@Inject(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/tutorial/Tutorial;tick()V"))
|
||||||
|
private void tickModels(CallbackInfo ci) {
|
||||||
|
((IExtendedModelManager)this.getModelManager()).mfix$tick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,180 @@
|
||||||
|
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
|
||||||
|
|
||||||
|
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||||
|
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
|
||||||
|
import net.minecraft.client.color.block.BlockColors;
|
||||||
|
import net.minecraft.client.resources.model.BakedModel;
|
||||||
|
import net.minecraft.client.resources.model.BlockStateModelLoader;
|
||||||
|
import net.minecraft.client.resources.model.ModelBakery;
|
||||||
|
import net.minecraft.client.resources.model.ModelResourceLocation;
|
||||||
|
import net.minecraft.client.resources.model.UnbakedModel;
|
||||||
|
import net.minecraft.core.DefaultedRegistry;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.util.profiling.ProfilerFiller;
|
||||||
|
import org.embeddedt.modernfix.ModernFix;
|
||||||
|
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||||
|
import org.embeddedt.modernfix.duck.IBlockStateModelLoader;
|
||||||
|
import org.embeddedt.modernfix.duck.IExtendedModelBakery;
|
||||||
|
import org.embeddedt.modernfix.util.DynamicOverridableMap;
|
||||||
|
import org.embeddedt.modernfix.util.LRUMap;
|
||||||
|
import org.spongepowered.asm.mixin.Final;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Mutable;
|
||||||
|
import org.spongepowered.asm.mixin.Overwrite;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
import org.spongepowered.asm.mixin.Unique;
|
||||||
|
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.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
|
@Mixin(ModelBakery.class)
|
||||||
|
@ClientOnlyMixin
|
||||||
|
public abstract class ModelBakeryMixin implements IExtendedModelBakery {
|
||||||
|
@Unique
|
||||||
|
private BlockStateModelLoader dynamicLoader;
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private final ReentrantLock modelBakeryLock = new ReentrantLock();
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private ModelBakery.TextureGetter textureGetter;
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private BakedModel bakedMissingModel;
|
||||||
|
|
||||||
|
@Shadow abstract UnbakedModel getModel(ResourceLocation resourceLocation);
|
||||||
|
|
||||||
|
@Shadow @Final private UnbakedModel missingModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bake a model using the provided texture getter and location. The model is stored in {@link ModelBakeryMixin#bakedTopLevelModels}.
|
||||||
|
*/
|
||||||
|
@Shadow protected abstract void method_61072(ModelBakery.TextureGetter getter, ModelResourceLocation location, UnbakedModel model);
|
||||||
|
|
||||||
|
@Shadow @Mutable @Final private Map<ModelResourceLocation, BakedModel> bakedTopLevelModels;
|
||||||
|
@Shadow @Mutable @Final public Map<ModelResourceLocation, UnbakedModel> topLevelModels;
|
||||||
|
@Shadow @Mutable @Final private Map<ResourceLocation, UnbakedModel> unbakedCache;
|
||||||
|
@Shadow @Mutable @Final public Map<ModelBakery.BakedCacheKey, BakedModel> bakedCache;
|
||||||
|
|
||||||
|
@Shadow protected abstract void loadItemModelAndDependencies(ResourceLocation resourceLocation);
|
||||||
|
|
||||||
|
|
||||||
|
@Shadow @Final public static ModelResourceLocation MISSING_MODEL_VARIANT;
|
||||||
|
|
||||||
|
private final Map<ModelResourceLocation, BakedModel> mfix$emulatedBakedRegistry = new DynamicOverridableMap<>(this::loadBakedModelDynamic);
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private UnbakedModel loadUnbakedModelDynamic(ModelResourceLocation location) {
|
||||||
|
if(location.equals(MISSING_MODEL_VARIANT)) {
|
||||||
|
return missingModel;
|
||||||
|
} else if(location.variant().equals("inventory")) {
|
||||||
|
this.loadItemModelAndDependencies(location.id());
|
||||||
|
} else {
|
||||||
|
((IBlockStateModelLoader)dynamicLoader).loadSpecificBlock(location.id());
|
||||||
|
}
|
||||||
|
return this.topLevelModels.getOrDefault(location, this.missingModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private BakedModel loadBakedModelDynamic(ModelResourceLocation location) {
|
||||||
|
if(location.equals(MISSING_MODEL_VARIANT)) {
|
||||||
|
return bakedMissingModel;
|
||||||
|
}
|
||||||
|
BakedModel model;
|
||||||
|
modelBakeryLock.lock();
|
||||||
|
try {
|
||||||
|
model = bakedTopLevelModels.get(location);
|
||||||
|
if(model == null) {
|
||||||
|
UnbakedModel prototype = loadUnbakedModelDynamic(location);
|
||||||
|
prototype.resolveParents(this::getModel);
|
||||||
|
this.method_61072(this.textureGetter, location, prototype);
|
||||||
|
model = bakedTopLevelModels.remove(location);
|
||||||
|
if(model == null) {
|
||||||
|
ModernFix.LOGGER.error("Failed to load model " + location);
|
||||||
|
model = bakedMissingModel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
modelBakeryLock.unlock();
|
||||||
|
}
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ModifyExpressionValue(method = "<init>", at = @At(value = "CONSTANT", args = "stringValue=missing_model"))
|
||||||
|
private String replaceBackingMaps(String original) {
|
||||||
|
this.unbakedCache = new LRUMap<>(this.unbakedCache);
|
||||||
|
this.bakedCache = new LRUMap<>(this.bakedCache);
|
||||||
|
this.topLevelModels = new LRUMap<>(this.topLevelModels);
|
||||||
|
this.bakedTopLevelModels = new LRUMap<>(this.bakedTopLevelModels);
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Redirect(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/BlockStateModelLoader;loadAllBlockStates()V"))
|
||||||
|
private void noInitialBlockStateLoad(BlockStateModelLoader instance) {
|
||||||
|
dynamicLoader = instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Redirect(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/core/DefaultedRegistry;keySet()Ljava/util/Set;"))
|
||||||
|
private Set<?> skipLoadingItems(DefaultedRegistry instance) {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Inject(method = "bakeModels", at = @At("HEAD"))
|
||||||
|
private void storeTextureGetterAndBakeMissing(ModelBakery.TextureGetter textureGetter, CallbackInfo ci) {
|
||||||
|
this.textureGetter = textureGetter;
|
||||||
|
this.method_61072(textureGetter, MISSING_MODEL_VARIANT, Objects.requireNonNull(this.topLevelModels.get(MISSING_MODEL_VARIANT)));
|
||||||
|
this.bakedMissingModel = this.bakedTopLevelModels.get(MISSING_MODEL_VARIANT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "<init>", at = @At("RETURN"))
|
||||||
|
private void onInitialLoadFinish(BlockColors blockColors, ProfilerFiller profilerFiller, Map map, Map map2, CallbackInfo ci) {
|
||||||
|
var permanentMRLs = new ObjectOpenHashSet<>(this.topLevelModels.keySet());
|
||||||
|
((LRUMap<ModelResourceLocation, UnbakedModel>)this.topLevelModels).setPermanentEntries(permanentMRLs);
|
||||||
|
((LRUMap<ModelResourceLocation, BakedModel>)this.bakedTopLevelModels).setPermanentEntries(permanentMRLs);
|
||||||
|
ModernFix.LOGGER.info("Dynamic model bakery initialized, with {} permanent top level models", this.topLevelModels.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private int tickCount;
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private static final int MAXIMUM_CACHE_SIZE = 1000;
|
||||||
|
|
||||||
|
private void runCleanup() {
|
||||||
|
((LRUMap<?, ?>)this.unbakedCache).dropEntriesToMeetSize(MAXIMUM_CACHE_SIZE);
|
||||||
|
((LRUMap<?, ?>)this.bakedCache).dropEntriesToMeetSize(MAXIMUM_CACHE_SIZE);
|
||||||
|
((LRUMap<?, ?>)this.topLevelModels).dropEntriesToMeetSize(MAXIMUM_CACHE_SIZE);
|
||||||
|
((LRUMap<?, ?>)this.bakedTopLevelModels).dropEntriesToMeetSize(MAXIMUM_CACHE_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mfix$tick() {
|
||||||
|
tickCount++;
|
||||||
|
if((tickCount % 200) == 0) {
|
||||||
|
if(modelBakeryLock.tryLock()) {
|
||||||
|
try {
|
||||||
|
runCleanup();
|
||||||
|
} finally {
|
||||||
|
modelBakeryLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author embeddedt
|
||||||
|
* @reason We provide a fake baked registry to the rest of Minecraft, that dynamically loads models.
|
||||||
|
*/
|
||||||
|
@Overwrite
|
||||||
|
public Map<ModelResourceLocation, BakedModel> getBakedTopLevelModels() {
|
||||||
|
return this.mfix$emulatedBakedRegistry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,24 +2,31 @@ package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import net.minecraft.client.renderer.block.model.BlockModel;
|
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||||
|
import net.minecraft.client.resources.model.AtlasSet;
|
||||||
import net.minecraft.client.resources.model.BakedModel;
|
import net.minecraft.client.resources.model.BakedModel;
|
||||||
|
import net.minecraft.client.resources.model.BlockStateModelLoader;
|
||||||
import net.minecraft.client.resources.model.ModelBakery;
|
import net.minecraft.client.resources.model.ModelBakery;
|
||||||
import net.minecraft.client.resources.model.ModelManager;
|
import net.minecraft.client.resources.model.ModelManager;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.server.packs.resources.ResourceManager;
|
import net.minecraft.server.packs.resources.ResourceManager;
|
||||||
import net.minecraft.util.GsonHelper;
|
import net.minecraft.util.GsonHelper;
|
||||||
|
import net.minecraft.util.profiling.ProfilerFiller;
|
||||||
import net.minecraft.world.level.block.Block;
|
import net.minecraft.world.level.block.Block;
|
||||||
import net.minecraft.world.level.block.state.BlockState;
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
import net.minecraft.world.level.block.state.StateDefinition;
|
import net.minecraft.world.level.block.state.StateDefinition;
|
||||||
import org.embeddedt.modernfix.ModernFix;
|
import org.embeddedt.modernfix.ModernFix;
|
||||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||||
|
import org.embeddedt.modernfix.duck.IExtendedModelBakery;
|
||||||
|
import org.embeddedt.modernfix.duck.IExtendedModelManager;
|
||||||
import org.embeddedt.modernfix.util.LambdaMap;
|
import org.embeddedt.modernfix.util.LambdaMap;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.Shadow;
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
import org.spongepowered.asm.mixin.Unique;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
@ -33,9 +40,12 @@ import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Mixin(ModelManager.class)
|
@Mixin(ModelManager.class)
|
||||||
@ClientOnlyMixin
|
@ClientOnlyMixin
|
||||||
public class ModelManagerMixin {
|
public class ModelManagerMixin implements IExtendedModelManager {
|
||||||
@Shadow private Map<ResourceLocation, BakedModel> bakedRegistry;
|
@Shadow private Map<ResourceLocation, BakedModel> bakedRegistry;
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private Runnable tickHandler = () -> {};
|
||||||
|
|
||||||
@Inject(method = "<init>", at = @At("RETURN"))
|
@Inject(method = "<init>", at = @At("RETURN"))
|
||||||
private void injectDummyBakedRegistry(CallbackInfo ci) {
|
private void injectDummyBakedRegistry(CallbackInfo ci) {
|
||||||
if(this.bakedRegistry == null) {
|
if(this.bakedRegistry == null) {
|
||||||
|
|
@ -49,7 +59,7 @@ public class ModelManagerMixin {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Redirect(method = "reload", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/ModelManager;loadBlockStates(Lnet/minecraft/server/packs/resources/ResourceManager;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"))
|
@Redirect(method = "reload", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/ModelManager;loadBlockStates(Lnet/minecraft/server/packs/resources/ResourceManager;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"))
|
||||||
private CompletableFuture<Map<ResourceLocation, List<ModelBakery.LoadedJson>>> deferBlockStateLoad(ResourceManager manager, Executor executor) {
|
private CompletableFuture<Map<ResourceLocation, List<BlockStateModelLoader.LoadedJson>>> deferBlockStateLoad(ResourceManager manager, Executor executor) {
|
||||||
return CompletableFuture.completedFuture(new LambdaMap<>(location -> loadSingleBlockState(manager, location)));
|
return CompletableFuture.completedFuture(new LambdaMap<>(location -> loadSingleBlockState(manager, location)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,14 +79,24 @@ public class ModelManagerMixin {
|
||||||
}).orElse(null);
|
}).orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<ModelBakery.LoadedJson> loadSingleBlockState(ResourceManager manager, ResourceLocation location) {
|
private List<BlockStateModelLoader.LoadedJson> loadSingleBlockState(ResourceManager manager, ResourceLocation location) {
|
||||||
return manager.getResourceStack(location).stream().map(resource -> {
|
return manager.getResourceStack(location).stream().map(resource -> {
|
||||||
try (BufferedReader reader = resource.openAsReader()) {
|
try (BufferedReader reader = resource.openAsReader()) {
|
||||||
return new ModelBakery.LoadedJson(resource.sourcePackId(), GsonHelper.parse(reader));
|
return new BlockStateModelLoader.LoadedJson(resource.sourcePackId(), GsonHelper.parse(reader));
|
||||||
} catch(IOException e) {
|
} catch(IOException e) {
|
||||||
ModernFix.LOGGER.error("Couldn't load blockstate", e);
|
ModernFix.LOGGER.error("Couldn't load blockstate", e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}).filter(Objects::nonNull).collect(Collectors.toList());
|
}).filter(Objects::nonNull).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Inject(method = "loadModels", at = @At("RETURN"))
|
||||||
|
private void storeTicker(ProfilerFiller profilerFiller, Map<ResourceLocation, AtlasSet.StitchResult> map, ModelBakery modelBakery, CallbackInfoReturnable<?> cir) {
|
||||||
|
tickHandler = ((IExtendedModelBakery)modelBakery)::mfix$tick;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mfix$tick() {
|
||||||
|
tickHandler.run();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package org.embeddedt.modernfix.duck;
|
||||||
|
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
|
||||||
|
public interface IBlockStateModelLoader {
|
||||||
|
void loadSpecificBlock(ResourceLocation location);
|
||||||
|
}
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
package org.embeddedt.modernfix.duck;
|
|
||||||
|
|
||||||
public interface IExtendedModelBaker {
|
|
||||||
/**
|
|
||||||
* Causes the ModelBaker to throw when it finds a missing model instead of proceeding with the bake.
|
|
||||||
* @return the previous value of this flag
|
|
||||||
*/
|
|
||||||
boolean throwOnMissingModel(boolean flag);
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +1,5 @@
|
||||||
package org.embeddedt.modernfix.duck;
|
package org.embeddedt.modernfix.duck;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import net.minecraft.client.resources.model.BakedModel;
|
|
||||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
|
||||||
import net.minecraft.client.resources.model.ModelState;
|
|
||||||
import net.minecraft.client.resources.model.UnbakedModel;
|
|
||||||
import net.minecraft.resources.ResourceLocation;
|
|
||||||
import net.minecraft.world.level.block.Block;
|
|
||||||
import net.minecraft.world.level.block.state.BlockState;
|
|
||||||
import net.minecraft.world.level.block.state.StateDefinition;
|
|
||||||
|
|
||||||
public interface IExtendedModelBakery {
|
public interface IExtendedModelBakery {
|
||||||
ImmutableList<BlockState> getBlockStatesForMRL(StateDefinition<Block, BlockState> stateDefinition, ModelResourceLocation location);
|
void mfix$tick();
|
||||||
BakedModel bakeDefault(ResourceLocation modelLocation, ModelState state);
|
|
||||||
UnbakedModel mfix$getUnbakedMissingModel();
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package org.embeddedt.modernfix.duck;
|
||||||
|
|
||||||
|
public interface IExtendedModelManager {
|
||||||
|
void mfix$tick();
|
||||||
|
}
|
||||||
|
|
@ -1,240 +0,0 @@
|
||||||
package org.embeddedt.modernfix.dynamicresources;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
|
||||||
import com.mojang.math.Transformation;
|
|
||||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
|
||||||
import net.minecraft.client.renderer.block.model.BakedQuad;
|
|
||||||
import net.minecraft.client.renderer.block.model.ItemOverrides;
|
|
||||||
import net.minecraft.client.renderer.block.model.ItemTransforms;
|
|
||||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
|
||||||
import net.minecraft.client.resources.model.BakedModel;
|
|
||||||
import net.minecraft.client.resources.model.BlockModelRotation;
|
|
||||||
import net.minecraft.client.resources.model.ModelBakery;
|
|
||||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
|
||||||
import net.minecraft.core.Direction;
|
|
||||||
import net.minecraft.core.registries.BuiltInRegistries;
|
|
||||||
import net.minecraft.resources.ResourceLocation;
|
|
||||||
import net.minecraft.util.RandomSource;
|
|
||||||
import net.minecraft.world.level.block.Block;
|
|
||||||
import net.minecraft.world.level.block.state.BlockState;
|
|
||||||
import org.embeddedt.modernfix.duck.IExtendedModelBakery;
|
|
||||||
import org.embeddedt.modernfix.ModernFix;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.function.BiFunction;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
public class DynamicBakedModelProvider implements Map<ResourceLocation, BakedModel> {
|
|
||||||
/**
|
|
||||||
* The list of blacklisted resource locations that are never baked as top-level models.
|
|
||||||
*
|
|
||||||
* This is a hack to get around the fact that we don't really know exactly what models were supposed to end up
|
|
||||||
* in the baked registry ahead of time.
|
|
||||||
*/
|
|
||||||
private static final ImmutableSet<ResourceLocation> BAKE_SKIPPED_TOPLEVEL = ImmutableSet.<ResourceLocation>builder()
|
|
||||||
.add(ResourceLocation.fromNamespaceAndPath("custommachinery", "block/custom_machine_block"))
|
|
||||||
.build();
|
|
||||||
public static DynamicBakedModelProvider currentInstance = null;
|
|
||||||
private final ModelBakery bakery;
|
|
||||||
private final Map<ModelBakery.BakedCacheKey, BakedModel> bakedCache;
|
|
||||||
private final Map<ResourceLocation, BakedModel> permanentOverrides;
|
|
||||||
private BakedModel missingModel;
|
|
||||||
private static final BakedModel SENTINEL = new BakedModel() {
|
|
||||||
@Override
|
|
||||||
public List<BakedQuad> getQuads(@Nullable BlockState state, @Nullable Direction direction, RandomSource random) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean useAmbientOcclusion() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isGui3d() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean usesBlockLight() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isCustomRenderer() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TextureAtlasSprite getParticleIcon() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ItemTransforms getTransforms() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ItemOverrides getOverrides() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public DynamicBakedModelProvider(ModelBakery bakery, Map<ModelBakery.BakedCacheKey, BakedModel> cache) {
|
|
||||||
this.bakery = bakery;
|
|
||||||
this.bakedCache = cache;
|
|
||||||
this.permanentOverrides = Collections.synchronizedMap(new Object2ObjectOpenHashMap<>());
|
|
||||||
if(currentInstance == null)
|
|
||||||
currentInstance = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ModelBakery.BakedCacheKey vanillaKey(Object o) {
|
|
||||||
return new ModelBakery.BakedCacheKey((ResourceLocation)o, BlockModelRotation.X0_Y0.getRotation(), false);
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public int size() {
|
|
||||||
return bakedCache.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEmpty() {
|
|
||||||
return bakedCache.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean containsKey(Object o) {
|
|
||||||
return permanentOverrides.getOrDefault(o, SENTINEL) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean containsValue(Object o) {
|
|
||||||
return permanentOverrides.containsValue(o) || bakedCache.containsValue(o);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isVanillaTopLevelModel(ResourceLocation location) {
|
|
||||||
if(location instanceof ModelResourceLocation) {
|
|
||||||
try {
|
|
||||||
ModelResourceLocation mrl = (ModelResourceLocation)location;
|
|
||||||
ResourceLocation registryKey = ResourceLocation.fromNamespaceAndPath(mrl.getNamespace(), mrl.getPath());
|
|
||||||
// check for standard inventory model
|
|
||||||
if(mrl.getVariant().equals("inventory") && BuiltInRegistries.ITEM.containsKey(registryKey))
|
|
||||||
return true;
|
|
||||||
Optional<Block> blockOpt = BuiltInRegistries.BLOCK.getOptional(registryKey);
|
|
||||||
if(blockOpt.isPresent()) {
|
|
||||||
return ModelBakeryHelpers.getBlockStatesForMRL(blockOpt.get().getStateDefinition(), mrl).size() > 0;
|
|
||||||
}
|
|
||||||
} catch(RuntimeException ignored) {
|
|
||||||
// can occur if the MRL is not valid for that blockstate, ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(location.getNamespace().equals("minecraft") && location.getPath().equals("builtin/missing"))
|
|
||||||
return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private BakedModel getMissingModel() {
|
|
||||||
BakedModel m = missingModel;
|
|
||||||
if(m == null) {
|
|
||||||
m = missingModel = ((IExtendedModelBakery)bakery).bakeDefault(ModelBakery.MISSING_MODEL_LOCATION, BlockModelRotation.X0_Y0);
|
|
||||||
}
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BakedModel get(Object o) {
|
|
||||||
BakedModel model = permanentOverrides.getOrDefault(o, SENTINEL);
|
|
||||||
if(model != SENTINEL)
|
|
||||||
return model;
|
|
||||||
else {
|
|
||||||
try {
|
|
||||||
if(BAKE_SKIPPED_TOPLEVEL.contains((ResourceLocation)o))
|
|
||||||
model = getMissingModel();
|
|
||||||
else
|
|
||||||
model = ((IExtendedModelBakery)bakery).bakeDefault((ResourceLocation)o, BlockModelRotation.X0_Y0);
|
|
||||||
} catch(RuntimeException e) {
|
|
||||||
ModernFix.LOGGER.error("Exception baking {}: {}", o, e);
|
|
||||||
model = getMissingModel();
|
|
||||||
}
|
|
||||||
if(model == getMissingModel()) {
|
|
||||||
// to correctly emulate the original map, we return null for missing models, unless they are top-level
|
|
||||||
model = isVanillaTopLevelModel((ResourceLocation)o) ? model : null;
|
|
||||||
permanentOverrides.put((ResourceLocation) o, model);
|
|
||||||
}
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BakedModel put(ResourceLocation resourceLocation, BakedModel bakedModel) {
|
|
||||||
BakedModel m = permanentOverrides.put(resourceLocation, bakedModel);
|
|
||||||
if(m != null)
|
|
||||||
return m;
|
|
||||||
else
|
|
||||||
return bakedCache.get(vanillaKey(resourceLocation));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BakedModel remove(Object o) {
|
|
||||||
BakedModel m = permanentOverrides.remove(o);
|
|
||||||
if(m != null)
|
|
||||||
return m;
|
|
||||||
return bakedCache.remove(vanillaKey(o));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void putAll(@NotNull Map<? extends ResourceLocation, ? extends BakedModel> map) {
|
|
||||||
permanentOverrides.putAll(map);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clear() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
public Set<ResourceLocation> keySet() {
|
|
||||||
return bakedCache.keySet().stream().map(ModelBakery.BakedCacheKey::id).collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
public Collection<BakedModel> values() {
|
|
||||||
return bakedCache.values();
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
public Set<Entry<ResourceLocation, BakedModel>> entrySet() {
|
|
||||||
return bakedCache.entrySet().stream().map(entry -> new AbstractMap.SimpleImmutableEntry<>(entry.getKey().id(), entry.getValue())).collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public BakedModel replace(ResourceLocation key, BakedModel value) {
|
|
||||||
BakedModel existingOverride = permanentOverrides.get(key);
|
|
||||||
// as long as no valid override was put in (null can mean unable to load model, so we treat as invalid), replace
|
|
||||||
// the model
|
|
||||||
if(existingOverride == null)
|
|
||||||
return this.put(key, value);
|
|
||||||
else
|
|
||||||
return existingOverride;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void replaceAll(BiFunction<? super ResourceLocation, ? super BakedModel, ? extends BakedModel> function) {
|
|
||||||
Set<ResourceLocation> overridenLocations = permanentOverrides.keySet();
|
|
||||||
permanentOverrides.replaceAll(function);
|
|
||||||
boolean uvLock = BlockModelRotation.X0_Y0.isUvLocked();
|
|
||||||
Transformation rotation = BlockModelRotation.X0_Y0.getRotation();
|
|
||||||
bakedCache.replaceAll((loc, oldModel) -> {
|
|
||||||
if(loc.transformation() != rotation || loc.isUvLocked() != uvLock || overridenLocations.contains(loc.id()))
|
|
||||||
return oldModel;
|
|
||||||
else
|
|
||||||
return function.apply(loc.id(), oldModel);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
package org.embeddedt.modernfix.util;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
|
||||||
|
import org.embeddedt.modernfix.ModernFix;
|
||||||
|
import org.embeddedt.modernfix.platform.ModernFixPlatformHooks;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class LRUMap<K, V> extends Object2ObjectLinkedOpenHashMap<K, V> {
|
||||||
|
private Set<K> permanentEntries = Set.of();
|
||||||
|
public LRUMap(Map<K, V> map) {
|
||||||
|
super(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V put(K k, V v) {
|
||||||
|
return putAndMoveToLast(k, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V get(Object k) {
|
||||||
|
return getAndMoveToLast((K)k);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPermanentEntries(Set<K> permanentEntries) {
|
||||||
|
this.permanentEntries = permanentEntries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dropEntriesToMeetSize(int size) {
|
||||||
|
int prevSize = size();
|
||||||
|
if(size() > size) {
|
||||||
|
var iterator = entrySet().iterator();
|
||||||
|
while(size() > size && iterator.hasNext()) {
|
||||||
|
var entry = iterator.next();
|
||||||
|
if(!this.permanentEntries.contains(entry.getKey())) {
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(ModernFixPlatformHooks.INSTANCE.isDevEnv()) {
|
||||||
|
ModernFix.LOGGER.warn("Trimmed map from {} to {} entries", prevSize, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
package org.embeddedt.modernfix.fabric.mixin.perf.dynamic_resources;
|
|
||||||
|
|
||||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
|
||||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
|
||||||
import net.minecraft.client.renderer.block.model.ItemOverrides;
|
|
||||||
import net.minecraft.client.resources.model.BakedModel;
|
|
||||||
import net.minecraft.client.resources.model.ModelBaker;
|
|
||||||
import net.minecraft.client.resources.model.ModelState;
|
|
||||||
import net.minecraft.resources.ResourceLocation;
|
|
||||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
|
||||||
import org.embeddedt.modernfix.duck.IExtendedModelBaker;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
|
||||||
|
|
||||||
@Mixin(ItemOverrides.class)
|
|
||||||
@ClientOnlyMixin
|
|
||||||
public class ItemOverridesFabricMixin {
|
|
||||||
/**
|
|
||||||
* @author embeddedt
|
|
||||||
* @reason servers insist on generating invalid item overrides that have missing models
|
|
||||||
*/
|
|
||||||
@WrapOperation(method = "bakeModel", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/ModelBaker;bake(Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/client/resources/model/ModelState;)Lnet/minecraft/client/resources/model/BakedModel;"))
|
|
||||||
private BakedModel bake(ModelBaker instance, ResourceLocation resourceLocation, ModelState modelState, Operation<BakedModel> original) {
|
|
||||||
boolean prevState = ((IExtendedModelBaker)instance).throwOnMissingModel(false);
|
|
||||||
try {
|
|
||||||
return original.call(instance, resourceLocation, modelState);
|
|
||||||
} finally {
|
|
||||||
((IExtendedModelBaker)instance).throwOnMissingModel(prevState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
package org.embeddedt.modernfix.fabric.mixin.perf.dynamic_resources;
|
|
||||||
|
|
||||||
import net.fabricmc.fabric.impl.client.model.ModelLoadingRegistryImpl;
|
|
||||||
import net.minecraft.client.resources.model.ModelBakery;
|
|
||||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
|
||||||
import org.embeddedt.modernfix.annotation.RequiresMod;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
|
||||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
|
||||||
|
|
||||||
@Mixin(ModelLoadingRegistryImpl.LoaderInstance.class)
|
|
||||||
@RequiresMod("fabric-models-v0")
|
|
||||||
@ClientOnlyMixin
|
|
||||||
public class LoaderInstanceMixin {
|
|
||||||
@Redirect(method = "finish", at = @At(value = "FIELD", target = "Lnet/fabricmc/fabric/impl/client/model/ModelLoadingRegistryImpl$LoaderInstance;loader:Lnet/minecraft/client/resources/model/ModelBakery;"), require = 0)
|
|
||||||
private void keepLoader(ModelLoadingRegistryImpl.LoaderInstance instance, ModelBakery value) {
|
|
||||||
/* allow loading models to happen later */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,185 +0,0 @@
|
||||||
package org.embeddedt.modernfix.fabric.mixin.perf.dynamic_resources;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
|
||||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
|
||||||
import net.fabricmc.loader.api.FabricLoader;
|
|
||||||
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 org.embeddedt.modernfix.ModernFix;
|
|
||||||
import org.embeddedt.modernfix.ModernFixClient;
|
|
||||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
|
||||||
import org.embeddedt.modernfix.api.entrypoint.ModernFixClientIntegration;
|
|
||||||
import org.embeddedt.modernfix.duck.IExtendedModelBaker;
|
|
||||||
import org.embeddedt.modernfix.duck.IExtendedModelBakery;
|
|
||||||
import org.embeddedt.modernfix.dynamicresources.ModelMissingException;
|
|
||||||
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.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(value = ModelBakery.ModelBakerImpl.class, priority = 600)
|
|
||||||
@ClientOnlyMixin
|
|
||||||
public abstract class ModelBakerImplMixin implements IExtendedModelBaker {
|
|
||||||
private static final boolean debugDynamicModelLoading = Boolean.getBoolean("modernfix.debugDynamicModelLoading");
|
|
||||||
@Shadow @Final private ModelBakery field_40571;
|
|
||||||
|
|
||||||
@Shadow public abstract UnbakedModel getModel(ResourceLocation arg);
|
|
||||||
|
|
||||||
@Shadow @Final private Function<Material, TextureAtlasSprite> modelTextureGetter;
|
|
||||||
|
|
||||||
private static final MethodHandle blockStateLoaderHandle;
|
|
||||||
static {
|
|
||||||
try {
|
|
||||||
blockStateLoaderHandle = MethodHandles.lookup().unreflect(ModelBakery.class.getDeclaredMethod(
|
|
||||||
FabricLoader.getInstance().getMappingResolver().mapMethodName(
|
|
||||||
"intermediary",
|
|
||||||
"net.minecraft.client.resources.model.ModelBakery",
|
|
||||||
"method_4716",
|
|
||||||
"(Lnet/minecraft/world/level/block/state/BlockState;)V"
|
|
||||||
),
|
|
||||||
BlockState.class
|
|
||||||
));
|
|
||||||
} catch(ReflectiveOperationException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean throwIfMissing;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean throwOnMissingModel(boolean flag) {
|
|
||||||
boolean old = throwIfMissing;
|
|
||||||
throwIfMissing = flag;
|
|
||||||
return old;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Inject(method = "getModel", at = @At("HEAD"), cancellable = true)
|
|
||||||
private void obtainModel(ResourceLocation arg, CallbackInfoReturnable<UnbakedModel> cir) {
|
|
||||||
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<Block> blockOpt = Objects.equals(((ModelResourceLocation)arg).getVariant(), "inventory") ? Optional.empty() : BuiltInRegistries.BLOCK.getOptional(ResourceLocation.fromNamespaceAndPath(arg.getNamespace(), arg.getPath()));
|
|
||||||
boolean invalidMRL = false;
|
|
||||||
if(blockOpt.isPresent()) {
|
|
||||||
/* load via lambda for mods that expect blockstate to get loaded */
|
|
||||||
ImmutableList<BlockState> states;
|
|
||||||
try {
|
|
||||||
states = extendedBakery.getBlockStatesForMRL(blockOpt.get().getStateDefinition(), (ModelResourceLocation)arg);
|
|
||||||
} catch(RuntimeException e) {
|
|
||||||
states = ImmutableList.of();
|
|
||||||
invalidMRL = true;
|
|
||||||
// Fall back to getModel
|
|
||||||
cir.setReturnValue(this.field_40571.getModel(arg));
|
|
||||||
}
|
|
||||||
for(BlockState state : states) {
|
|
||||||
try {
|
|
||||||
blockStateLoaderHandle.invokeExact(this.field_40571, state);
|
|
||||||
} catch(Throwable e) {
|
|
||||||
ModernFix.LOGGER.error("Error loading model", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.field_40571.loadTopLevel((ModelResourceLocation)arg);
|
|
||||||
}
|
|
||||||
if(!invalidMRL) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cir.setReturnValue(toReplace);
|
|
||||||
cir.getReturnValue().resolveParents(this.field_40571::getModel);
|
|
||||||
if(cir.getReturnValue() == extendedBakery.mfix$getUnbakedMissingModel()) {
|
|
||||||
if(arg != ModelBakery.MISSING_MODEL_LOCATION) {
|
|
||||||
if(debugDynamicModelLoading)
|
|
||||||
ModernFix.LOGGER.warn("Model {} not present", arg);
|
|
||||||
if(throwIfMissing)
|
|
||||||
throw new ModelMissingException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@WrapOperation(method = "bake", 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 callBakedModelIntegration(UnbakedModel unbakedModel, ModelBaker baker, Function<Material, TextureAtlasSprite> spriteGetter, ModelState state, ResourceLocation location, Operation<BakedModel> operation) {
|
|
||||||
BakedModel model = operation.call(unbakedModel, baker, spriteGetter, state, location);
|
|
||||||
|
|
||||||
for(ModernFixClientIntegration integration : ModernFixClient.CLIENT_INTEGRATIONS) {
|
|
||||||
model = integration.onBakedModelLoad(location, unbakedModel, model, state, this.field_40571, spriteGetter);
|
|
||||||
}
|
|
||||||
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author embeddedt
|
|
||||||
* @reason emulate old function, to allow injectors to work
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
@Overwrite
|
|
||||||
public BakedModel bake(ResourceLocation arg, ModelState arg2) {
|
|
||||||
ModelBakery.BakedCacheKey key = new ModelBakery.BakedCacheKey(arg, arg2.getRotation(), arg2.isUvLocked());
|
|
||||||
BakedModel existing = this.field_40571.bakedCache.get(key);
|
|
||||||
if (existing != null) {
|
|
||||||
return existing;
|
|
||||||
} else {
|
|
||||||
synchronized (this.field_40571) {
|
|
||||||
if(debugDynamicModelLoading)
|
|
||||||
ModernFix.LOGGER.info("Baking {}", arg);
|
|
||||||
UnbakedModel iunbakedmodel = this.getModel(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(this.modelTextureGetter, blockmodel).bake((ModelBaker)this, blockmodel, this.modelTextureGetter, arg2, arg, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(ibakedmodel == null) {
|
|
||||||
// leave the original assignment in the same spot so wrapping injectors work
|
|
||||||
// this means two bakes might happen for missing models, but not much we can do
|
|
||||||
ibakedmodel = iunbakedmodel.bake((ModelBaker)this, this.modelTextureGetter, arg2, arg);
|
|
||||||
IExtendedModelBakery extendedBakery = (IExtendedModelBakery)this.field_40571;
|
|
||||||
if(iunbakedmodel == extendedBakery.mfix$getUnbakedMissingModel()) {
|
|
||||||
// use a shared baked missing model
|
|
||||||
createBakedMissingModelIfNeeded(extendedBakery, iunbakedmodel, arg2, arg);
|
|
||||||
ibakedmodel = extendedBakery.getBakedMissingModel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.field_40571.bakedCache.put(key, ibakedmodel);
|
|
||||||
return ibakedmodel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
@ -1,328 +0,0 @@
|
||||||
package org.embeddedt.modernfix.fabric.mixin.perf.dynamic_resources;
|
|
||||||
|
|
||||||
import com.google.common.cache.Cache;
|
|
||||||
import com.google.common.cache.CacheBuilder;
|
|
||||||
import com.google.common.cache.RemovalNotification;
|
|
||||||
import com.google.common.collect.ForwardingMap;
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import net.minecraft.client.Minecraft;
|
|
||||||
import net.minecraft.client.color.block.BlockColors;
|
|
||||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
|
||||||
import net.minecraft.client.resources.model.*;
|
|
||||||
import net.minecraft.resources.ResourceLocation;
|
|
||||||
import net.minecraft.world.level.block.Block;
|
|
||||||
import net.minecraft.world.level.block.state.BlockState;
|
|
||||||
import net.minecraft.world.level.block.state.StateDefinition;
|
|
||||||
import net.minecraft.world.level.block.state.properties.Property;
|
|
||||||
import org.embeddedt.modernfix.ModernFix;
|
|
||||||
import org.embeddedt.modernfix.ModernFixClient;
|
|
||||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
|
||||||
import org.embeddedt.modernfix.api.entrypoint.ModernFixClientIntegration;
|
|
||||||
import org.embeddedt.modernfix.duck.IExtendedModelBaker;
|
|
||||||
import org.embeddedt.modernfix.duck.IExtendedModelBakery;
|
|
||||||
import org.embeddedt.modernfix.dynamicresources.DynamicBakedModelProvider;
|
|
||||||
import org.embeddedt.modernfix.dynamicresources.ModelBakeryHelpers;
|
|
||||||
import org.objectweb.asm.Opcodes;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
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.ModifyArg;
|
|
||||||
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;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.ConcurrentMap;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.function.BiConsumer;
|
|
||||||
import java.util.function.BiFunction;
|
|
||||||
|
|
||||||
/* low priority so that our injectors are added after other mods' */
|
|
||||||
@Mixin(value = ModelBakery.class, priority = 1100)
|
|
||||||
@ClientOnlyMixin
|
|
||||||
public abstract class ModelBakeryMixin implements IExtendedModelBakery {
|
|
||||||
|
|
||||||
private static final boolean debugDynamicModelLoading = Boolean.getBoolean("modernfix.debugDynamicModelLoading");
|
|
||||||
|
|
||||||
@Shadow @Final @Mutable public Map<ResourceLocation, UnbakedModel> unbakedCache;
|
|
||||||
|
|
||||||
@Shadow @Final public static ModelResourceLocation MISSING_MODEL_LOCATION;
|
|
||||||
|
|
||||||
@Shadow @Final private Set<ResourceLocation> loadingStack;
|
|
||||||
|
|
||||||
@Shadow protected abstract void loadModel(ResourceLocation blockstateLocation) throws Exception;
|
|
||||||
|
|
||||||
@Shadow @Final @Mutable
|
|
||||||
private Map<ResourceLocation, BakedModel> bakedTopLevelModels;
|
|
||||||
|
|
||||||
@Shadow @Final @Mutable private Map<ModelBakery.BakedCacheKey, BakedModel> bakedCache;
|
|
||||||
|
|
||||||
@Shadow @Final @Mutable private BlockColors blockColors;
|
|
||||||
@Shadow @Final private static Logger LOGGER;
|
|
||||||
|
|
||||||
@Shadow
|
|
||||||
public abstract void loadTopLevel(ModelResourceLocation modelResourceLocation);
|
|
||||||
|
|
||||||
@Shadow public abstract UnbakedModel getModel(ResourceLocation resourceLocation);
|
|
||||||
|
|
||||||
private Cache<ModelBakery.BakedCacheKey, BakedModel> loadedBakedModels;
|
|
||||||
|
|
||||||
private Cache<ResourceLocation, UnbakedModel> loadedModels;
|
|
||||||
|
|
||||||
private HashMap<ResourceLocation, UnbakedModel> smallLoadingCache = new HashMap<>();
|
|
||||||
|
|
||||||
private boolean ignoreModelLoad;
|
|
||||||
|
|
||||||
// disable fabric recursion
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
private boolean fabric_enableGetOrLoadModelGuard;
|
|
||||||
|
|
||||||
|
|
||||||
@Redirect(method = "<init>", at = @At(value = "FIELD", opcode = Opcodes.PUTFIELD, target = "Lnet/minecraft/client/resources/model/ModelBakery;blockColors:Lnet/minecraft/client/color/block/BlockColors;"))
|
|
||||||
private void replaceTopLevelBakedModels(ModelBakery bakery, BlockColors val) {
|
|
||||||
// we can handle recursion in getModel without issues
|
|
||||||
fabric_enableGetOrLoadModelGuard = false;
|
|
||||||
this.blockColors = val;
|
|
||||||
this.loadedBakedModels = CacheBuilder.newBuilder()
|
|
||||||
.expireAfterAccess(ModelBakeryHelpers.MAX_MODEL_LIFETIME_SECS, TimeUnit.SECONDS)
|
|
||||||
.maximumSize(ModelBakeryHelpers.MAX_BAKED_MODEL_COUNT)
|
|
||||||
.concurrencyLevel(8)
|
|
||||||
.removalListener(this::onModelRemoved)
|
|
||||||
.softValues()
|
|
||||||
.build();
|
|
||||||
this.loadedModels = CacheBuilder.newBuilder()
|
|
||||||
.expireAfterAccess(ModelBakeryHelpers.MAX_MODEL_LIFETIME_SECS, TimeUnit.SECONDS)
|
|
||||||
.maximumSize(ModelBakeryHelpers.MAX_UNBAKED_MODEL_COUNT)
|
|
||||||
.concurrencyLevel(8)
|
|
||||||
.removalListener(this::onModelRemoved)
|
|
||||||
//.softValues()
|
|
||||||
.build();
|
|
||||||
this.bakedCache = loadedBakedModels.asMap();
|
|
||||||
ConcurrentMap<ResourceLocation, UnbakedModel> unbakedCacheBackingMap = loadedModels.asMap();
|
|
||||||
this.unbakedCache = new ForwardingMap<ResourceLocation, UnbakedModel>() {
|
|
||||||
@Override
|
|
||||||
protected Map<ResourceLocation, UnbakedModel> delegate() {
|
|
||||||
return unbakedCacheBackingMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UnbakedModel put(ResourceLocation key, UnbakedModel value) {
|
|
||||||
smallLoadingCache.put(key, value);
|
|
||||||
return super.put(key, value);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
this.bakedTopLevelModels = new DynamicBakedModelProvider((ModelBakery)(Object)this, bakedCache);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Inject(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/profiling/ProfilerFiller;popPush(Ljava/lang/String;)V", ordinal = 0))
|
|
||||||
private void ignoreFutureModelLoads(CallbackInfo ci) {
|
|
||||||
this.ignoreModelLoad = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private <K, V> void onModelRemoved(RemovalNotification<K, V> notification) {
|
|
||||||
if(!debugDynamicModelLoading)
|
|
||||||
return;
|
|
||||||
Object k = notification.getKey();
|
|
||||||
if(k == null)
|
|
||||||
return;
|
|
||||||
ResourceLocation rl;
|
|
||||||
boolean baked = false;
|
|
||||||
if(k instanceof ResourceLocation) {
|
|
||||||
rl = (ResourceLocation)k;
|
|
||||||
} else {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
private UnbakedModel missingModel;
|
|
||||||
|
|
||||||
private Set<ResourceLocation> blockStateFiles;
|
|
||||||
private Set<ResourceLocation> modelFiles;
|
|
||||||
|
|
||||||
@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)
|
|
||||||
private Object captureMissingModel(Object model) {
|
|
||||||
this.missingModel = (UnbakedModel)model;
|
|
||||||
this.blockStateFiles = new HashSet<>();
|
|
||||||
this.modelFiles = new HashSet<>();
|
|
||||||
return this.missingModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author embeddedt
|
|
||||||
* @reason don't actually load most models
|
|
||||||
*/
|
|
||||||
@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 = "<init>", at = @At(value = "INVOKE", target = "Ljava/util/Map;forEach(Ljava/util/function/BiConsumer;)V", ordinal = 0))
|
|
||||||
private void fetchStaticDefinitions(Map<ResourceLocation, StateDefinition<Block, BlockState>> map, BiConsumer<ResourceLocation, StateDefinition<Block, BlockState>> func) {
|
|
||||||
/* no-op */
|
|
||||||
}
|
|
||||||
|
|
||||||
@Redirect(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/state/StateDefinition;getPossibleStates()Lcom/google/common/collect/ImmutableList;", ordinal = 0))
|
|
||||||
private ImmutableList<BlockState> fetchBlocks(StateDefinition<Block, BlockState> def) {
|
|
||||||
/* no-op */
|
|
||||||
return ImmutableList.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make a copy of the top-level model list to avoid CME if more models get loaded here.
|
|
||||||
*/
|
|
||||||
@Redirect(method = "<init>", at = @At(value = "INVOKE", target = "Ljava/util/Map;values()Ljava/util/Collection;", ordinal = 0))
|
|
||||||
private Collection<?> copyTopLevelModelList(Map<?, ?> map) {
|
|
||||||
return new ArrayList<>(map.values());
|
|
||||||
}
|
|
||||||
|
|
||||||
private BiFunction<ResourceLocation, Material, TextureAtlasSprite> textureGetter;
|
|
||||||
|
|
||||||
@Inject(method = "bakeModels", at = @At("HEAD"))
|
|
||||||
private void captureGetter(BiFunction<ResourceLocation, Material, TextureAtlasSprite> 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<ResourceLocation> skipBake(Map<ResourceLocation, UnbakedModel> instance) {
|
|
||||||
Set<ResourceLocation> modelSet = new HashSet<>(instance.keySet());
|
|
||||||
if(modelSet.size() > 0)
|
|
||||||
ModernFix.LOGGER.info("Early baking {} models", modelSet.size());
|
|
||||||
return modelSet;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use the already loaded missing model instead of the cache entry (which will probably get evicted).
|
|
||||||
*/
|
|
||||||
@Redirect(method = "loadModel", at = @At(value = "INVOKE", target = "Ljava/util/Map;get(Ljava/lang/Object;)Ljava/lang/Object;", ordinal = 1))
|
|
||||||
private Object getMissingModel(Map map, Object rl) {
|
|
||||||
if(rl == MISSING_MODEL_LOCATION && map == unbakedCache)
|
|
||||||
return missingModel;
|
|
||||||
return unbakedCache.get(rl);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ModifyVariable(method = "cacheAndQueueDependencies", at = @At("HEAD"), argsOnly = true)
|
|
||||||
private UnbakedModel fireUnbakedEvent(UnbakedModel model, ResourceLocation location) {
|
|
||||||
for(ModernFixClientIntegration integration : ModernFixClient.CLIENT_INTEGRATIONS) {
|
|
||||||
try {
|
|
||||||
model = integration.onUnbakedModelLoad(location, model, (ModelBakery)(Object)this);
|
|
||||||
} catch(RuntimeException e) {
|
|
||||||
ModernFix.LOGGER.error("Exception firing model load event for {}", location, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int mfix$nestedLoads = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author embeddedt
|
|
||||||
* @reason synchronize
|
|
||||||
*/
|
|
||||||
@Inject(method = "getModel", at = @At("HEAD"), cancellable = true)
|
|
||||||
public void getOrLoadModelDynamic(ResourceLocation modelLocation, CallbackInfoReturnable<UnbakedModel> cir) {
|
|
||||||
if(modelLocation.equals(MISSING_MODEL_LOCATION)) {
|
|
||||||
cir.setReturnValue(missingModel);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
UnbakedModel existing = this.unbakedCache.get(modelLocation);
|
|
||||||
if (existing != null) {
|
|
||||||
cir.setReturnValue(existing);
|
|
||||||
} else {
|
|
||||||
synchronized(this) {
|
|
||||||
if (this.loadingStack.contains(modelLocation)) {
|
|
||||||
throw new IllegalStateException("Circular reference while loading " + modelLocation);
|
|
||||||
} else {
|
|
||||||
this.loadingStack.add(modelLocation);
|
|
||||||
UnbakedModel iunbakedmodel = missingModel;
|
|
||||||
|
|
||||||
while(!this.loadingStack.isEmpty()) {
|
|
||||||
ResourceLocation resourcelocation = this.loadingStack.iterator().next();
|
|
||||||
|
|
||||||
mfix$nestedLoads++;
|
|
||||||
try {
|
|
||||||
existing = this.unbakedCache.get(resourcelocation);
|
|
||||||
if (existing == null) {
|
|
||||||
if(debugDynamicModelLoading)
|
|
||||||
LOGGER.info("Loading {}", resourcelocation);
|
|
||||||
this.loadModel(resourcelocation);
|
|
||||||
} else
|
|
||||||
smallLoadingCache.put(resourcelocation, existing);
|
|
||||||
} catch (ModelBakery.BlockStateDefinitionException var9) {
|
|
||||||
LOGGER.warn(var9.getMessage());
|
|
||||||
this.unbakedCache.put(resourcelocation, iunbakedmodel);
|
|
||||||
smallLoadingCache.put(resourcelocation, iunbakedmodel);
|
|
||||||
} catch (Exception var10) {
|
|
||||||
LOGGER.warn("Unable to load model: '{}' referenced from: {}: {}", resourcelocation, modelLocation, var10);
|
|
||||||
this.unbakedCache.put(resourcelocation, iunbakedmodel);
|
|
||||||
smallLoadingCache.put(resourcelocation, iunbakedmodel);
|
|
||||||
} finally {
|
|
||||||
mfix$nestedLoads--;
|
|
||||||
this.loadingStack.remove(resourcelocation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We have to get the result from the temporary cache used for a model load
|
|
||||||
// As in pathological cases (e.g. Pedestals on 1.19) unbakedCache can lose
|
|
||||||
// the model immediately
|
|
||||||
UnbakedModel result = smallLoadingCache.getOrDefault(modelLocation, iunbakedmodel);
|
|
||||||
try {
|
|
||||||
// required as some mods (e.g. EBE) call bake directly on the returned model, without resolving parents themselves
|
|
||||||
result.resolveParents(this::getModel);
|
|
||||||
} catch(RuntimeException ignored) {}
|
|
||||||
// We are done with loading, so clear this cache to allow GC of any unneeded models
|
|
||||||
if(mfix$nestedLoads == 0)
|
|
||||||
smallLoadingCache.clear();
|
|
||||||
cir.setReturnValue(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private <T extends Comparable<T>, V extends T> BlockState setPropertyGeneric(BlockState state, Property<T> prop, Object o) {
|
|
||||||
return state.setValue(prop, (V)o);
|
|
||||||
}
|
|
||||||
@Redirect(method = "loadModel", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/state/StateDefinition;getPossibleStates()Lcom/google/common/collect/ImmutableList;"))
|
|
||||||
private ImmutableList<BlockState> loadOnlyRelevantBlockState(StateDefinition<Block, BlockState> stateDefinition, ResourceLocation location) {
|
|
||||||
if(!(location instanceof ModelResourceLocation) || Minecraft.getInstance().level == null)
|
|
||||||
return stateDefinition.getPossibleStates();
|
|
||||||
return ModelBakeryHelpers.getBlockStatesForMRL(stateDefinition, (ModelResourceLocation)location);
|
|
||||||
}
|
|
||||||
|
|
||||||
@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);
|
|
||||||
((IExtendedModelBaker)theBaker).throwOnMissingModel(true);
|
|
||||||
synchronized(this) { m = theBaker.bake(modelLocation, state); }
|
|
||||||
if(m != null)
|
|
||||||
loadedBakedModels.put(key, m);
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ImmutableList<BlockState> getBlockStatesForMRL(StateDefinition<Block, BlockState> stateDefinition, ModelResourceLocation location) {
|
|
||||||
return loadOnlyRelevantBlockState(stateDefinition, location);
|
|
||||||
}
|
|
||||||
|
|
||||||
public UnbakedModel mfix$getUnbakedMissingModel() {
|
|
||||||
return missingModel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -5,7 +5,7 @@ junit_version=5.10.0-M1
|
||||||
mixinextras_version=0.3.2
|
mixinextras_version=0.3.2
|
||||||
|
|
||||||
mod_id=modernfix
|
mod_id=modernfix
|
||||||
minecraft_version=24w21a
|
minecraft_version=1.21-pre1
|
||||||
enabled_platforms=fabric
|
enabled_platforms=fabric
|
||||||
forge_version=20.6.42-beta
|
forge_version=20.6.42-beta
|
||||||
# parchment_version=2023.07.09
|
# parchment_version=2023.07.09
|
||||||
|
|
@ -15,7 +15,7 @@ rei_version=13.0.678
|
||||||
ctm_version=1.20.1-1.1.8+4
|
ctm_version=1.20.1-1.1.8+4
|
||||||
kubejs_version=1902.6.0-build.142
|
kubejs_version=1902.6.0-build.142
|
||||||
rhino_version=1902.2.2-build.268
|
rhino_version=1902.2.2-build.268
|
||||||
supported_minecraft_versions=24w21a
|
supported_minecraft_versions=1.21-pre1
|
||||||
|
|
||||||
fabric_loader_version=0.15.11
|
fabric_loader_version=0.15.11
|
||||||
fabric_api_version=0.98.2+1.21
|
fabric_api_version=0.98.2+1.21
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user