Dynamic model loading

This commit is contained in:
embeddedt 2023-04-08 17:01:16 -04:00
parent 3a8bc41dd4
commit 26d76de7ef
No known key found for this signature in database
GPG Key ID: A69433EC199B5613
27 changed files with 879 additions and 619 deletions

View File

@ -95,6 +95,7 @@ dependencies {
modCompileOnly("curse.maven:jepb-437558:3172880")
modCompileOnly("curse.maven:babel-436964:3196072")
modCompileOnly("curse.maven:twforest-227639:3575220")
modRuntimeOnly("curse.maven:ferritecore-429235:4074330")
}
tasks.withType(JavaCompile) {

View File

@ -0,0 +1,181 @@
package org.embeddedt.modernfix.blockstate;
import com.google.common.collect.ImmutableSet;
import net.minecraft.world.level.block.state.properties.Property;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* Fake "map" implementation used to hold the states.
*
* Intentionally throws on methods that would be inefficient so that we know
* if an incompatible mod is present.
*/
public class FakeStateMap<S> implements Map<Map<Property<?>, Comparable<?>>, S> {
private final Map<Property<?>, Comparable<?>>[] keys;
private final Object[] values;
private int usedSlots;
public FakeStateMap(int numStates) {
this.keys = new Map[numStates];
this.values = new Object[numStates];
this.usedSlots = 0;
}
@Override
public int size() {
return usedSlots;
}
@Override
public boolean isEmpty() {
return size() == 0;
}
@Override
public boolean containsKey(Object o) {
throw new UnsupportedOperationException();
}
@Override
public boolean containsValue(Object o) {
throw new UnsupportedOperationException();
}
@Override
public S get(Object o) {
throw new UnsupportedOperationException();
}
@Nullable
@Override
public S put(Map<Property<?>, Comparable<?>> propertyComparableMap, S s) {
keys[usedSlots] = propertyComparableMap;
values[usedSlots] = s;
usedSlots++;
return null;
}
@Override
public S remove(Object o) {
throw new UnsupportedOperationException();
}
@Override
public void putAll(@NotNull Map<? extends Map<Property<?>, Comparable<?>>, ? extends S> map) {
for(Entry<? extends Map<Property<?>, Comparable<?>>, ? extends S> entry : map.entrySet()) {
this.put(entry.getKey(), entry.getValue());
}
}
@Override
public void clear() {
for(int i = 0; i < this.keys.length; i++) {
this.keys[i] = null;
this.values[i] = null;
}
this.usedSlots = 0;
}
@NotNull
@Override
public Set<Map<Property<?>, Comparable<?>>> keySet() {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public Collection<S> values() {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public Set<Entry<Map<Property<?>, Comparable<?>>, S>> entrySet() {
return new Set<Entry<Map<Property<?>, Comparable<?>>, S>>() {
@Override
public int size() {
return usedSlots;
}
@Override
public boolean isEmpty() {
return FakeStateMap.this.isEmpty();
}
@Override
public boolean contains(Object o) {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public Iterator<Entry<Map<Property<?>, Comparable<?>>, S>> iterator() {
return new Iterator<Entry<Map<Property<?>, Comparable<?>>, S>>() {
int currentIdx = 0;
@Override
public boolean hasNext() {
return currentIdx < usedSlots;
}
@Override
public Entry<Map<Property<?>, Comparable<?>>, S> next() {
if(currentIdx >= usedSlots)
throw new IndexOutOfBoundsException();
Entry<Map<Property<?>, Comparable<?>>, S> entry = new AbstractMap.SimpleImmutableEntry<>(keys[currentIdx], (S)values[currentIdx]);
currentIdx++;
return entry;
}
};
}
@NotNull
@Override
public Object[] toArray() {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public <T> T[] toArray(@NotNull T[] ts) {
throw new UnsupportedOperationException();
}
@Override
public boolean add(Entry<Map<Property<?>, Comparable<?>>, S> mapSEntry) {
throw new UnsupportedOperationException();
}
@Override
public boolean remove(Object o) {
throw new UnsupportedOperationException();
}
@Override
public boolean containsAll(@NotNull Collection<?> collection) {
throw new UnsupportedOperationException();
}
@Override
public boolean addAll(@NotNull Collection<? extends Entry<Map<Property<?>, Comparable<?>>, S>> collection) {
throw new UnsupportedOperationException();
}
@Override
public boolean retainAll(@NotNull Collection<?> collection) {
throw new UnsupportedOperationException();
}
@Override
public boolean removeAll(@NotNull Collection<?> collection) {
throw new UnsupportedOperationException();
}
@Override
public void clear() {
throw new UnsupportedOperationException();
}
};
}
}

View File

@ -1,5 +1,8 @@
package org.embeddedt.modernfix.classloading;
import com.google.common.collect.Interner;
import com.google.common.collect.Interners;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
import net.minecraftforge.fml.loading.FMLLoader;
@ -14,6 +17,7 @@ import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.FileSystem;
import java.nio.file.Files;
@ -23,7 +27,7 @@ import java.util.regex.Pattern;
import java.util.stream.Stream;
public class ModernFixResourceFinder {
private static HashMap<String, List<URL>> urlsForClass = null;
private static HashMap<String, List<Pair<String, String>>> urlsForClass = null;
private static final Class<? extends IModLocator> MINECRAFT_LOCATOR;
private static Field explodedDirModsField = null;
private static final Logger LOGGER = LogManager.getLogger("ModernFixResourceFinder");
@ -35,6 +39,8 @@ public class ModernFixResourceFinder {
throw new RuntimeException(e);
}
}
private static final Interner<String> PATH_INTERNER = Interners.newStrongInterner();
public static synchronized void init() throws ReflectiveOperationException {
urlsForClass = new HashMap<>();
//LOGGER.info("Start building list of class locations...");
@ -50,22 +56,20 @@ public class ModernFixResourceFinder {
.map(root::relativize)
.forEach(path -> {
String strPath = path.toString();
URL url = (URL)LamdbaExceptionUtils.uncheck(() -> {
return new URL("modjar://" + fileInfo.getMods().get(0).getModId() + "/" + strPath);
});
List<URL> urlList = urlsForClass.get(strPath);
Pair<String, String> pathPair = Pair.of(fileInfo.getMods().get(0).getModId(), PATH_INTERNER.intern(strPath));
List<Pair<String, String>> urlList = urlsForClass.get(strPath);
if(urlList != null) {
if(urlList.size() > 1)
urlList.add(url);
urlList.add(pathPair);
else {
/* Convert singleton to real list */
ArrayList<URL> newList = new ArrayList<>(urlList);
newList.add(url);
ArrayList<Pair<String, String>> newList = new ArrayList<>(urlList);
newList.add(pathPair);
urlsForClass.put(strPath, newList);
}
} else {
/* Use a singleton list initially to keep memory usage down */
urlsForClass.put(strPath, Collections.singletonList(url));
urlsForClass.put(strPath, Collections.singletonList(pathPair));
}
});
} catch(IOException e) {
@ -73,9 +77,9 @@ public class ModernFixResourceFinder {
}
}
}
for(List<URL> list : urlsForClass.values()) {
for(List<Pair<String, String>> list : urlsForClass.values()) {
if(list instanceof ArrayList)
((ArrayList<URL>)list).trimToSize();
((ArrayList<Pair<String, String>>)list).trimToSize();
}
//LOGGER.info("Finish building");
}
@ -106,10 +110,16 @@ public class ModernFixResourceFinder {
public static Enumeration<URL> findAllURLsForResource(String input) {
input = SLASH_REPLACER.matcher(input).replaceAll("/");
List<URL> urlList = urlsForClass.get(input);
if(urlList != null)
return Collections.enumeration(urlList);
else {
List<Pair<String, String>> urlList = urlsForClass.get(input);
if(urlList != null) {
return Iterators.asEnumeration(urlList.stream().map(pair -> {
try {
return new URL("modjar://" + pair.getLeft() + "/" + pair.getRight());
} catch(MalformedURLException e) {
throw new RuntimeException(e);
}
}).iterator());
} else {
return Collections.emptyEnumeration();
}
}

View File

@ -36,8 +36,10 @@ public class ModernFixEarlyConfig {
this.addMixinRule("perf.boost_worker_count", true);
this.addMixinRule("perf.skip_first_datapack_reload", true);
this.addMixinRule("perf.reuse_datapacks", true);
this.addMixinRule("perf.parallelize_model_loading", true);
this.addMixinRule("perf.parallelize_model_loading.multipart", false);
this.addMixinRule("perf.model_optimizations", true);
this.addMixinRule("perf.dynamic_resources", false);
/* Use a simpler ArrayMap if FerriteCore is using the map intelligently anyway */
this.addMixinRule("perf.state_definition_construct", modPresent("ferritecore"));
this.addMixinRule("perf.cache_strongholds", true);
this.addMixinRule("perf.cache_upgraded_structures", true);
this.addMixinRule("bugfix.concurrency", true);
@ -57,7 +59,6 @@ public class ModernFixEarlyConfig {
this.addMixinRule("perf.flatten_model_predicates", true);
this.addMixinRule("perf.deduplicate_location", false);
this.addMixinRule("perf.cache_blockstate_cache_arrays", true);
this.addMixinRule("perf.faster_baking", true);
this.addMixinRule("perf.cache_model_materials", true);
this.addMixinRule("perf.datapack_reload_exceptions", true);
this.addMixinRule("perf.async_locator", true);

View File

@ -0,0 +1,119 @@
package org.embeddedt.modernfix.dynamicresources;
import com.mojang.math.Transformation;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.BlockModelRotation;
import net.minecraft.client.resources.model.ModelBakery;
import net.minecraft.resources.ResourceLocation;
import org.apache.commons.lang3.tuple.Triple;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
import java.util.AbstractMap;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
public class DynamicBakedModelProvider implements Map<ResourceLocation, BakedModel> {
private final ModelBakery bakery;
private final Map<Triple<ResourceLocation, Transformation, Boolean>, BakedModel> bakedCache;
private final Map<ResourceLocation, BakedModel> permanentOverrides;
public DynamicBakedModelProvider(ModelBakery bakery, Map<Triple<ResourceLocation, Transformation, Boolean>, BakedModel> cache) {
this.bakery = bakery;
this.bakedCache = cache;
this.permanentOverrides = new Object2ObjectOpenHashMap<>();
}
private static Triple<ResourceLocation, Transformation, Boolean> vanillaKey(Object o) {
return Triple.of((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.containsKey(o) || bakedCache.containsKey(vanillaKey(o));
}
@Override
public boolean containsValue(Object o) {
return permanentOverrides.containsValue(o) || bakedCache.containsValue(o);
}
@Override
public BakedModel get(Object o) {
BakedModel model = permanentOverrides.get(o);
return model != null ? model : bakery.bake((ResourceLocation)o, BlockModelRotation.X0_Y0);
}
@Nullable
@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(Triple::getLeft).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().getLeft(), entry.getValue())).collect(Collectors.toSet());
}
@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.getMiddle() != rotation || loc.getRight() != uvLock || overridenLocations.contains(loc.getLeft()))
return oldModel;
else
return function.apply(loc.getLeft(), oldModel);
});
}
}

View File

@ -0,0 +1,41 @@
package org.embeddedt.modernfix.dynamicresources;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.resources.ResourceLocation;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
* Handles loading models dynamically, rather than at startup time.
*/
public class DynamicModelProvider {
private final Map<ResourceLocation, UnbakedModel> internalModels;
private final Cache<ResourceLocation, Optional<UnbakedModel>> loadedModels =
CacheBuilder.newBuilder()
.expireAfterAccess(3, TimeUnit.MINUTES)
.maximumSize(1000)
.concurrencyLevel(8)
.softValues()
.build();
public DynamicModelProvider(Map<ResourceLocation, UnbakedModel> initialModels) {
this.internalModels = initialModels;
}
public UnbakedModel getModel(ResourceLocation location) {
try {
return loadedModels.get(location, () -> Optional.ofNullable(loadModel(location))).orElse(null);
} catch(ExecutionException e) {
throw new RuntimeException(e.getCause());
}
}
private UnbakedModel loadModel(ResourceLocation location) {
return null; /* TODO :) */
}
}

View File

@ -0,0 +1,37 @@
package org.embeddedt.modernfix.dynamicresources;
import com.mojang.datafixers.util.Pair;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.Util;
import net.minecraft.client.renderer.block.BlockModelShaper;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.core.Registry;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import java.util.ArrayList;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
public class ModelLocationCache {
private static final Map<BlockState, ModelResourceLocation> locationCache = new Object2ObjectOpenHashMap<>();
public static void rebuildLocationCache() {
locationCache.clear();
ArrayList<CompletableFuture<Pair<BlockState, ModelResourceLocation>>> futures = new ArrayList<>();
for(Block block : Registry.BLOCK) {
block.getStateDefinition().getPossibleStates().forEach((state) -> {
futures.add(CompletableFuture.supplyAsync(() -> {
return Pair.of(state, BlockModelShaper.stateToModelLocation(state));
}, Util.backgroundExecutor()));
});
}
for(CompletableFuture<Pair<BlockState, ModelResourceLocation>> future : futures) {
Pair<BlockState, ModelResourceLocation> pair = future.join();
locationCache.put(pair.getFirst(), pair.getSecond());
}
}
public static ModelResourceLocation get(BlockState state) {
return locationCache.get(state);
}
}

View File

@ -0,0 +1,49 @@
package org.embeddedt.modernfix.mixin.perf.dynamic_resources;
import com.mojang.datafixers.util.Pair;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.Util;
import net.minecraft.client.renderer.block.BlockModelShaper;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.core.Registry;
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.properties.Property;
import org.embeddedt.modernfix.dynamicresources.ModelLocationCache;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
@Mixin(BlockModelShaper.class)
public class BlockModelShaperMixin {
@Shadow @Final private ModelManager modelManager;
/**
* @author embeddedt
* @reason no need to rebuild model cache, and location cache is done elsewhere
*/
@Overwrite
public void rebuildCache() {
}
@Overwrite
public BakedModel getBlockModel(BlockState state) {
BakedModel model = modelManager.getModel(ModelLocationCache.get(state));
if (model == null) {
model = modelManager.getMissingModel();
}
return model;
}
}

View File

@ -0,0 +1,53 @@
package org.embeddedt.modernfix.mixin.perf.dynamic_resources;
import net.minecraft.client.renderer.ItemModelShaper;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.world.item.Item;
import net.minecraftforge.client.ItemModelMesherForge;
import net.minecraftforge.registries.IRegistryDelegate;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import java.util.Map;
@Mixin(ItemModelMesherForge.class)
public abstract class ItemModelShaperMixin extends ItemModelShaper {
@Shadow @Final private Map<IRegistryDelegate<Item>, ModelResourceLocation> locations;
public ItemModelShaperMixin(ModelManager arg) {
super(arg);
}
/**
* @reason Get the stored location for that item and meta, and get the model
* from that location from the model manager.
**/
@Overwrite
@Override
public BakedModel getItemModel(Item item) {
ModelResourceLocation map = locations.get(item.delegate);
return map == null ? null : getModelManager().getModel(map);
}
/**
* @reason Don't get all models during init (with dynamic loading, that would
* generate them all). Just store location instead.
**/
@Overwrite
@Override
public void register(Item item, ModelResourceLocation location) {
locations.put(item.delegate, location);
}
/**
* @reason Disable cache rebuilding (with dynamic loading, that would generate
* all models).
**/
@Overwrite
@Override
public void rebuildCache() {}
}

View File

@ -0,0 +1,297 @@
package org.embeddedt.modernfix.mixin.perf.dynamic_resources;
import com.google.common.base.Stopwatch;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.RemovalNotification;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.mojang.datafixers.util.Pair;
import com.mojang.math.Transformation;
import net.minecraft.Util;
import net.minecraft.client.color.block.BlockColors;
import net.minecraft.client.renderer.block.model.BlockModel;
import net.minecraft.client.renderer.block.model.ItemModelGenerator;
import net.minecraft.client.renderer.texture.AtlasSet;
import net.minecraft.client.renderer.texture.TextureAtlas;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.client.resources.model.*;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.Resource;
import net.minecraft.server.packs.resources.ResourceManager;
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 net.minecraftforge.client.ForgeHooksClient;
import net.minecraftforge.client.model.ModelLoaderRegistry;
import org.apache.commons.lang3.tuple.Triple;
import org.apache.logging.log4j.Logger;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.dynamicresources.DynamicBakedModelProvider;
import org.embeddedt.modernfix.dynamicresources.ModelLocationCache;
import org.spongepowered.asm.mixin.*;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
@Mixin(ModelBakery.class)
public abstract class ModelBakeryMixin {
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 protected abstract BlockModel loadBlockModel(ResourceLocation location) throws IOException;
@Shadow @Final protected static Set<Material> UNREFERENCED_TEXTURES;
@Shadow private Map<ResourceLocation, Pair<TextureAtlas, TextureAtlas.Preparations>> atlasPreparations;
@Shadow @Final protected ResourceManager resourceManager;
@Shadow @Nullable private AtlasSet atlasSet;
@Shadow @Final private Set<ResourceLocation> loadingStack;
@Shadow protected abstract void loadModel(ResourceLocation blockstateLocation) throws Exception;
@Shadow @Final private static Logger LOGGER;
@Shadow @Final @Mutable
private Map<ResourceLocation, BakedModel> bakedTopLevelModels;
@Shadow @Final @Mutable private Map<Triple<ResourceLocation, Transformation, Boolean>, BakedModel> bakedCache;
@Shadow @Final public static BlockModel GENERATION_MARKER;
@Shadow @Final private static ItemModelGenerator ITEM_MODEL_GENERATOR;
@Shadow @Final public static BlockModel BLOCK_ENTITY_MARKER;
private Cache<Triple<ResourceLocation, Transformation, Boolean>, BakedModel> loadedBakedModels;
private Cache<ResourceLocation, UnbakedModel> loadedModels;
@Inject(method = "<init>(Lnet/minecraft/server/packs/resources/ResourceManager;Lnet/minecraft/client/color/block/BlockColors;Z)V", at = @At("RETURN"))
private void replaceTopLevelBakedModels(ResourceManager manager, BlockColors colors, boolean vanillaBakery, CallbackInfo ci) {
this.loadedBakedModels = CacheBuilder.newBuilder()
.expireAfterAccess(3, TimeUnit.MINUTES)
.maximumSize(1000)
.concurrencyLevel(8)
.removalListener(this::onModelRemoved)
.softValues()
.build();
this.loadedModels = CacheBuilder.newBuilder()
.expireAfterAccess(3, TimeUnit.MINUTES)
.maximumSize(1000)
.concurrencyLevel(8)
.removalListener(this::onModelRemoved)
.softValues()
.build();
this.bakedCache = loadedBakedModels.asMap();
this.unbakedCache = loadedModels.asMap();
this.bakedTopLevelModels = new DynamicBakedModelProvider((ModelBakery)(Object)this, bakedCache);
}
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 = ((Triple<ResourceLocation, Transformation, Boolean>)k).getLeft();
baked = true;
}
ModernFix.LOGGER.warn("Evicted {} model {}", baked ? "baked" : "unbaked", rl);
}
private UnbakedModel missingModel;
private static boolean firstReload = true;
/**
* @author embeddedt
* @reason don't load any models initially, just set up initial data structures
*/
@Overwrite
protected void processLoading(ProfilerFiller arg, int maxMipLevels) {
ModelLoaderRegistry.onModelLoadingStart();
if(firstReload) {
ModelLocationCache.rebuildLocationCache();
firstReload = false;
}
try {
this.missingModel = this.loadBlockModel(MISSING_MODEL_LOCATION);
} catch (IOException var10) {
ModernFix.LOGGER.error("Error loading missing model, should never happen :(", var10);
throw new RuntimeException(var10);
}
// Gather model materials
Set<Material> initialMaterials = new HashSet<>(UNREFERENCED_TEXTURES);
gatherModelMaterials(initialMaterials);
ForgeHooksClient.gatherFluidTextures(initialMaterials);
Map<ResourceLocation, List<Material>> map = initialMaterials.stream().collect(Collectors.groupingBy(Material::atlasLocation));
this.atlasPreparations = Maps.newHashMap();
for(Map.Entry<ResourceLocation, List<Material>> entry : map.entrySet()) {
TextureAtlas atlas = new TextureAtlas(entry.getKey());
TextureAtlas.Preparations atlastexture$sheetdata = atlas.prepareToStitch(this.resourceManager, entry.getValue().stream().map(Material::texture), arg, maxMipLevels);
this.atlasPreparations.put(entry.getKey(), Pair.of(atlas, atlastexture$sheetdata));
}
}
/**
* Scan the models folder and try to load, parse, and get materials from as many models as possible.
*/
private void gatherModelMaterials(Set<Material> materialSet) {
Stopwatch stopwatch = Stopwatch.createStarted();
Collection<ResourceLocation> allModels = this.resourceManager.listResources("models", path -> path.endsWith(".json"));
List<CompletableFuture<Pair<ResourceLocation, JsonElement>>> modelBytes = new ArrayList<>();
for(ResourceLocation fileLocation : allModels) {
modelBytes.add(CompletableFuture.supplyAsync(() -> {
try(Resource resource = this.resourceManager.getResource(fileLocation)) {
JsonParser parser = new JsonParser();
// strip models/ and .json from the name
ResourceLocation model = new ResourceLocation(fileLocation.getNamespace(), fileLocation.getPath().substring(7, fileLocation.getPath().length()-5));
return Pair.of(model, parser.parse(new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8)));
} catch(IOException e) {
ModernFix.LOGGER.error("Error reading model " + fileLocation, e);
return Pair.of(fileLocation, null);
}
}, Util.backgroundExecutor()));
}
allModels.clear();
CompletableFuture.allOf(modelBytes.toArray(new CompletableFuture[0])).join();
Set<Pair<String, String>> errorSet = Sets.newLinkedHashSet();
Map<ResourceLocation, BlockModel> basicModels = new HashMap<>();
try {
basicModels.put(MISSING_MODEL_LOCATION, this.loadBlockModel(MISSING_MODEL_LOCATION));
basicModels.put(new ResourceLocation("builtin/generated"), GENERATION_MARKER);
basicModels.put(new ResourceLocation("builtin/entity"), BLOCK_ENTITY_MARKER);
} catch(IOException e) {
throw new RuntimeException("Exception when populating built-in models", e);
}
for(CompletableFuture<Pair<ResourceLocation, JsonElement>> future : modelBytes) {
Pair<ResourceLocation, JsonElement> pair = future.join();
try {
if(pair.getSecond() != null) {
BlockModel model = ModelLoaderRegistry.ExpandedBlockModelDeserializer.INSTANCE.fromJson(pair.getSecond(), BlockModel.class);
model.name = pair.getFirst().toString();
basicModels.put(pair.getFirst(), model);
}
} catch(Exception e) {
ModernFix.LOGGER.warn("Unable to load " + pair.getFirst(), e);
}
}
modelBytes.clear();
Function<ResourceLocation, UnbakedModel> modelGetter = loc -> basicModels.getOrDefault(loc, (BlockModel)this.missingModel);
for(BlockModel model : basicModels.values()) {
materialSet.addAll(model.getMaterials(modelGetter, errorSet));
}
//errorSet.stream().filter(pair -> !pair.getSecond().equals(MISSING_MODEL_LOCATION_STRING)).forEach(pair -> LOGGER.warn("Unable to resolve texture reference: {} in {}", pair.getFirst(), pair.getSecond()));
stopwatch.stop();
ModernFix.LOGGER.info("Resolving model textures took " + stopwatch);
}
@Inject(method = "uploadTextures", at = @At(value = "FIELD", target = "Lnet/minecraft/client/resources/model/ModelBakery;topLevelModels:Ljava/util/Map;", ordinal = 0), cancellable = true)
private void skipBake(TextureManager resourceManager, ProfilerFiller profiler, CallbackInfoReturnable<AtlasSet> cir) {
profiler.pop();
cir.setReturnValue(atlasSet);
}
/**
* 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);
}
/**
* @author embeddedt
* @reason synchronize
*/
@Overwrite
public synchronized UnbakedModel getModel(ResourceLocation modelLocation) {
if(modelLocation.equals(MISSING_MODEL_LOCATION))
return missingModel;
if (this.unbakedCache.containsKey(modelLocation)) {
return this.unbakedCache.get(modelLocation);
} else 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();
try {
if (!this.unbakedCache.containsKey(resourcelocation)) {
if(debugDynamicModelLoading)
LOGGER.info("Loading {}", resourcelocation);
this.loadModel(resourcelocation);
}
} catch (ModelBakery.BlockStateDefinitionException var9) {
LOGGER.warn(var9.getMessage());
this.unbakedCache.put(resourcelocation, iunbakedmodel);
} catch (Exception var10) {
LOGGER.warn("Unable to load model: '{}' referenced from: {}: {}", resourcelocation, modelLocation, var10);
this.unbakedCache.put(resourcelocation, iunbakedmodel);
} finally {
this.loadingStack.remove(resourcelocation);
}
}
return this.unbakedCache.getOrDefault(modelLocation, iunbakedmodel);
}
}
@Overwrite
public synchronized BakedModel getBakedModel(ResourceLocation arg, ModelState arg2, Function<Material, TextureAtlasSprite> textureGetter) {
Triple<ResourceLocation, Transformation, Boolean> triple = Triple.of(arg, arg2.getRotation(), arg2.isUvLocked());
if (this.bakedCache.containsKey(triple)) {
return this.bakedCache.get(triple);
} else if (this.atlasSet == null) {
throw new IllegalStateException("bake called too early");
} else {
if(debugDynamicModelLoading)
LOGGER.info("Baking {}", arg);
UnbakedModel iunbakedmodel = this.getModel(arg);
iunbakedmodel.getMaterials(this::getModel, new HashSet<>());
BakedModel ibakedmodel = null;
if (iunbakedmodel instanceof BlockModel) {
BlockModel blockmodel = (BlockModel)iunbakedmodel;
if (blockmodel.getRootModel() == GENERATION_MARKER) {
ibakedmodel = ITEM_MODEL_GENERATOR.generateBlockModel(textureGetter, blockmodel).bake((ModelBakery)(Object)this, blockmodel, this.atlasSet::getSprite, arg2, arg, false);
}
}
if(ibakedmodel == null) {
ibakedmodel = iunbakedmodel.bake((ModelBakery) (Object) this, textureGetter, arg2, arg);
}
this.bakedCache.put(triple, ibakedmodel);
return ibakedmodel;
}
}
}

View File

@ -1,46 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.faster_baking;
import com.mojang.datafixers.util.Pair;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.client.renderer.block.BlockModelShaper;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.Util;
import net.minecraft.core.Registry;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import java.util.ArrayList;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
@Mixin(BlockModelShaper.class)
public abstract class BlockModelShapesMixin {
@Shadow @Final private ModelManager modelManager;
@Shadow @Final private Map<BlockState, BakedModel> modelByStateCache;
/**
* @author embeddedt
* @reason parallelize cache rebuild
*/
@Overwrite
public void rebuildCache() {
this.modelByStateCache.clear();
ArrayList<CompletableFuture<Pair<BlockState, BakedModel>>> futures = new ArrayList<>();
for(Block block : Registry.BLOCK) {
block.getStateDefinition().getPossibleStates().forEach((state) -> {
futures.add(CompletableFuture.supplyAsync(() -> {
return Pair.of(state, this.modelManager.getModel(BlockModelShaper.stateToModelLocation(state)));
}, Util.backgroundExecutor()));
});
}
for(CompletableFuture<Pair<BlockState, BakedModel>> future : futures) {
Pair<BlockState, BakedModel> pair = future.join();
this.modelByStateCache.put(pair.getFirst(), pair.getSecond());
}
}
}

View File

@ -1,161 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.faster_baking;
import com.google.common.collect.ImmutableSet;
import com.mojang.datafixers.util.Pair;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.block.model.MultiVariant;
import net.minecraft.client.renderer.block.model.multipart.MultiPart;
import net.minecraft.client.renderer.texture.TextureAtlas;
import net.minecraft.client.renderer.texture.AtlasSet;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.resources.ResourceLocation;
import com.mojang.math.Transformation;
import net.minecraftforge.client.event.ModelBakeEvent;
import net.minecraftforge.fml.loading.progress.StartupMessageManager;
import org.apache.commons.lang3.tuple.Triple;
import org.embeddedt.modernfix.core.config.ModernFixConfig;
import org.embeddedt.modernfix.duck.IExtendedModelBakery;
import org.embeddedt.modernfix.models.LazyBakedModel;
import org.embeddedt.modernfix.util.ModUtil;
import org.spongepowered.asm.mixin.*;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import javax.annotation.Nullable;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;
import static org.embeddedt.modernfix.ModernFix.LOGGER;
import net.minecraft.client.renderer.block.model.BlockModel;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.BlockModelRotation;
import net.minecraft.client.resources.model.Material;
import net.minecraft.client.resources.model.ModelBakery;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.client.resources.model.ModelState;
import net.minecraft.client.resources.model.UnbakedModel;
@Mixin(ModelBakery.class)
public abstract class ModelBakeryMixin implements IExtendedModelBakery {
@Shadow @Final private Map<ResourceLocation, UnbakedModel> topLevelModels;
@Shadow @Final private Map<ResourceLocation, BakedModel> bakedTopLevelModels;
@Shadow @Deprecated @Nullable public abstract BakedModel bake(ResourceLocation pLocation, ModelState pTransform);
@Shadow private Map<ResourceLocation, Pair<TextureAtlas, TextureAtlas.Preparations>> atlasPreparations;
@Shadow @Nullable private AtlasSet atlasSet;
@Shadow @Nullable public abstract BakedModel getBakedModel(ResourceLocation pLocation, ModelState pTransform, Function<Material, TextureAtlasSprite> textureGetter);
@Shadow @Final public static ModelResourceLocation MISSING_MODEL_LOCATION;
@Shadow @Final private Map<Triple<ResourceLocation, Transformation, Boolean>, BakedModel> bakedCache;
@Shadow @Final private Map<ResourceLocation, UnbakedModel> unbakedCache;
private BakedModel bakeIfPossible(ResourceLocation p_229350_1_) {
BakedModel ibakedmodel = null;
try {
ibakedmodel = this.bake(p_229350_1_, BlockModelRotation.X0_Y0);
} catch (Exception exception) {
exception.printStackTrace();
LOGGER.warn("Unable to bake model: '{}': {}", p_229350_1_, exception);
}
return ibakedmodel;
}
private boolean requiresBake(UnbakedModel model) {
if(model instanceof BlockModel && ((BlockModel)model).customData.hasCustomGeometry())
return true;
else
return false;
}
@Inject(method = "processLoading", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/profiling/ProfilerFiller;pop()V"))
private void bakeModels(ProfilerFiller pProfiler, int p_i226056_4_, CallbackInfo ci) {
pProfiler.popPush("atlas");
Minecraft.getInstance().executeBlocking(() -> {
for(Pair<TextureAtlas, TextureAtlas.Preparations> pair : this.atlasPreparations.values()) {
TextureAtlas atlastexture = pair.getFirst();
TextureAtlas.Preparations atlastexture$sheetdata = pair.getSecond();
atlastexture.reload(atlastexture$sheetdata);
}
});
pProfiler.popPush("baking");
StartupMessageManager.mcLoaderConsumer().ifPresent(c -> c.accept("Baking models"));
this.atlasSet = new AtlasSet(this.atlasPreparations.values().stream().map(Pair::getFirst).collect(Collectors.toList()));
BakedModel missingModel = this.bake(MISSING_MODEL_LOCATION, BlockModelRotation.X0_Y0);
this.bakedTopLevelModels.put(MISSING_MODEL_LOCATION, missingModel);
Collection<String> modsListening = ModUtil.findAllModsListeningToEvent(ModelBakeEvent.class);
LOGGER.debug("Found ModelBakeEvent listeners: [" + String.join(", ", modsListening) + "]");
Set<String> incompatibleLazyBakedModels = ImmutableSet.<String>builder()
.addAll(modsListening)
.build();
/* First, bake any incompatible models ahead of time (for mods that have custom models) */
new ArrayList<>(this.unbakedCache.keySet()).forEach(location -> {
if(incompatibleLazyBakedModels.contains(location.getNamespace())) {
this.bakeIfPossible(location);
}
});
List<ResourceLocation> multiparts = new ArrayList<>();
/* Then store them as top-level models if needed, and set up the lazy models */
this.topLevelModels.forEach((location, value) -> {
if (requiresBake(value) || incompatibleLazyBakedModels.contains(location.getNamespace())) {
BakedModel model = this.bakeIfPossible(location);
if (model != null)
this.bakedTopLevelModels.put(location, model);
} else {
if(value instanceof MultiPart || value instanceof MultiVariant) {
multiparts.add(location);
} else {
this.bakedTopLevelModels.put(location, new LazyBakedModel(() -> {
synchronized (this.bakedCache) {
BakedModel ibakedmodel = this.bakeIfPossible(location);
return ibakedmodel != null ? ibakedmodel : missingModel;
}
}));
}
}
});
multiparts.forEach(location -> {
BakedModel model = this.bakeIfPossible(location);
if (model != null)
this.bakedTopLevelModels.put(location, model);
});
}
/**
* @author embeddedt
* @reason texture loading and baking are moved earlier in the launch process, only render thread stuff is done here
*/
@Overwrite
public AtlasSet uploadTextures(TextureManager pResourceManager, ProfilerFiller pProfiler) {
pProfiler.push("atlas_upload");
for(Pair<TextureAtlas, TextureAtlas.Preparations> pair : this.atlasPreparations.values()) {
TextureAtlas atlastexture = pair.getFirst();
TextureAtlas.Preparations atlastexture$sheetdata = pair.getSecond();
pResourceManager.register(atlastexture.location(), atlastexture);
pResourceManager.bind(atlastexture.location());
atlastexture.updateFilter(atlastexture$sheetdata);
}
pProfiler.pop();
return this.atlasSet;
}
@Override
public AtlasSet getUnfinishedAtlasSet() {
return this.atlasSet;
}
}

View File

@ -1,40 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.faster_baking;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.block.BlockModelShaper;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelBakery;
import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.client.renderer.texture.AtlasSet;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.client.model.ModelLoader;
import net.minecraftforge.fml.common.ObfuscationReflectionHelper;
import org.embeddedt.modernfix.duck.IExtendedModelBakery;
import org.embeddedt.modernfix.models.LazyBakedModel;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
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;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import javax.annotation.Nullable;
import java.util.Map;
@Mixin(ModelManager.class)
public class ModelManagerMixin {
@Inject(method = "apply(Lnet/minecraft/client/resources/model/ModelBakery;Lnet/minecraft/server/packs/resources/ResourceManager;Lnet/minecraft/util/profiling/ProfilerFiller;)V",
at = @At(value = "RETURN"))
private void allowBake(ModelBakery pObject, ResourceManager pResourceManager, ProfilerFiller pProfiler, CallbackInfo ci) {
LazyBakedModel.allowBakeForFlags = true;
}
}

View File

@ -1,4 +1,4 @@
package org.embeddedt.modernfix.mixin.perf.parallelize_model_loading;
package org.embeddedt.modernfix.mixin.perf.model_optimizations;
import com.google.common.collect.ImmutableSet;
import net.minecraft.world.level.block.state.properties.BooleanProperty;

View File

@ -1,4 +1,4 @@
package org.embeddedt.modernfix.mixin.perf.parallelize_model_loading;
package org.embeddedt.modernfix.mixin.perf.model_optimizations;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.client.model.obj.MaterialLibrary;

View File

@ -1,4 +1,4 @@
package org.embeddedt.modernfix.mixin.perf.parallelize_model_loading;
package org.embeddedt.modernfix.mixin.perf.model_optimizations;
import net.minecraft.world.level.block.state.properties.Property;
import org.embeddedt.modernfix.dedup.IdentifierCaches;

View File

@ -1,6 +1,5 @@
package org.embeddedt.modernfix.mixin.perf.parallelize_model_loading;
package org.embeddedt.modernfix.mixin.perf.model_optimizations;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.client.renderer.block.model.multipart.Selector;

View File

@ -1,4 +1,4 @@
package org.embeddedt.modernfix.mixin.perf.parallelize_model_loading;
package org.embeddedt.modernfix.mixin.perf.model_optimizations;
import com.mojang.math.Matrix4f;
import com.mojang.math.Transformation;

View File

@ -1,26 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.parallelize_model_loading;
import com.google.common.collect.Interner;
import com.google.common.collect.Interners;
import net.minecraft.client.renderer.block.BlockModelShaper;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.Property;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Mixin(BlockModelShaper.class)
public class BlockModelShaperMixin {
private static Map<BlockState, String> stateToPropertiesCache = new ConcurrentHashMap<>();
@Redirect(method = "stateToModelLocation(Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/world/level/block/state/BlockState;)Lnet/minecraft/client/resources/model/ModelResourceLocation;",
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/block/BlockModelShaper;statePropertiesToString(Ljava/util/Map;)Ljava/lang/String;"))
private static String getCachedProperty(Map<Property<?>, Comparable<?>> values, ResourceLocation location, BlockState state) {
/* We intentionally don't use the values parameter inside the lambda to avoid an allocation */
return stateToPropertiesCache.computeIfAbsent(state, s -> BlockModelShaper.statePropertiesToString(s.getValues()));
}
}

View File

@ -1,114 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.parallelize_model_loading;
import com.mojang.datafixers.util.Pair;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.server.packs.resources.Resource;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.Util;
import net.minecraft.core.Registry;
import net.minecraftforge.fml.loading.progress.StartupMessageManager;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.util.AsyncStopwatch;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Stream;
import net.minecraft.client.renderer.block.model.BlockModelDefinition;
import net.minecraft.client.resources.model.ModelBakery;
import net.minecraft.client.resources.model.ModelResourceLocation;
@Mixin(ModelBakery.class)
public abstract class ModelBakeryMixin {
@Shadow @Final protected ResourceManager resourceManager;
private Map<ResourceLocation, List<Pair<String, BlockModelDefinition>>> deserializedBlockstateCache = null;
@Inject(method = "processLoading", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/profiling/ProfilerFiller;popPush(Ljava/lang/String;)V", ordinal = 0))
private void preloadJsonModels(ProfilerFiller profilerIn, int maxMipmapLevel, CallbackInfo ci) {
StartupMessageManager.mcLoaderConsumer().ifPresent(c -> c.accept("Loading models"));
profilerIn.popPush("loadblockstates");
AsyncStopwatch.measureAndLogSerialRunningTime("Parallel blockstate loading", () -> {
ThreadLocal<BlockModelDefinition.Context> containerHolder = ThreadLocal.withInitial(BlockModelDefinition.Context::new);
this.deserializedBlockstateCache = new ConcurrentHashMap<>();
ArrayList<CompletableFuture<Void>> futures = new ArrayList<>();
for(Block block : Registry.BLOCK) {
ResourceLocation blockLocation = Registry.BLOCK.getKey(block);
futures.add(CompletableFuture.runAsync(() -> {
ResourceLocation blockStateJSON = new ResourceLocation(blockLocation.getNamespace(), "blockstates/" + blockLocation.getPath() + ".json");
List<Resource> blockStates;
try {
/* Some mods' custom resource pack implementations don't seem to like concurrency here */
synchronized(this.resourceManager) {
blockStates = this.resourceManager.getResources(blockStateJSON);
}
} catch(IOException e) {
ModernFix.LOGGER.warn("Exception loading blockstate definition: {}: {}", blockLocation, e);
return;
}
List<Pair<String, BlockModelDefinition>> definitions = new ArrayList<>();
StateDefinition<Block, BlockState> stateContainer = block.getStateDefinition();
BlockModelDefinition.Context context = containerHolder.get();
context.setDefinition(stateContainer);
for(Resource resource : blockStates) {
try (InputStream inputstream = resource.getInputStream()) {
BlockModelDefinition definition = BlockModelDefinition.fromStream(context, new InputStreamReader(inputstream, StandardCharsets.UTF_8));
definitions.add(Pair.of(resource.getSourceName(), definition));
} catch (Exception exception1) {
ModernFix.LOGGER.warn(String.format("Exception loading blockstate definition: '%s' in resourcepack: '%s': %s", resource.getLocation(), resource.getSourceName(), exception1.getMessage()));
return;
}
}
this.deserializedBlockstateCache.put(blockLocation, definitions);
}, Util.backgroundExecutor()));
}
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
});
}
@Inject(method = "processLoading", at = @At("RETURN"), remap = false)
private void clearModelCache(ProfilerFiller profilerIn, int maxMipmapLevel, CallbackInfo ci) {
deserializedBlockstateCache.clear();
}
private List<?> replacementList = null;
@Redirect(method = "loadModel", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/packs/resources/ResourceManager;getResources(Lnet/minecraft/resources/ResourceLocation;)Ljava/util/List;", ordinal = 0))
private List<?> getResourceList(ResourceManager instance, ResourceLocation jsonLocation, ResourceLocation originalBlockLocation) throws IOException {
replacementList = null;
if(this.deserializedBlockstateCache != null) {
if(!(originalBlockLocation instanceof ModelResourceLocation)) {
throw new AssertionError("Injector in unexpected spot?");
}
ModelResourceLocation mrl = (ModelResourceLocation)originalBlockLocation;
ResourceLocation location = new ResourceLocation(mrl.getNamespace(), mrl.getPath());
List<?> theList = this.deserializedBlockstateCache.get(location);
if(theList != null && theList.size() > 0) {
replacementList = theList;
return theList;
}
}
return instance.getResources(jsonLocation);
}
@Redirect(method = "loadModel", at = @At(value = "INVOKE", target = "Ljava/util/stream/Stream;map(Ljava/util/function/Function;)Ljava/util/stream/Stream;", ordinal = 0))
private Stream<?> fakeResourceList(Stream<?> instance, Function function) {
return replacementList != null ? instance : instance.map(function);
}
}

View File

@ -1,17 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.parallelize_model_loading.multipart;
import net.minecraft.client.renderer.block.model.multipart.MultiPart;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import java.util.List;
import java.util.stream.Stream;
@Mixin(MultiPart.class)
public class MultipartMixin {
@Redirect(method = "getMaterials", at = @At(value = "INVOKE", target = "Ljava/util/List;stream()Ljava/util/stream/Stream;", ordinal = 0))
private Stream makeStreamParallel(List instance) {
return instance.parallelStream();
}
}

View File

@ -1,32 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.parallelize_model_loading.multipart;
import com.mojang.datafixers.util.Pair;
import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.client.resources.model.Material;
import net.minecraft.client.renderer.block.model.Variant;
import net.minecraft.client.renderer.block.model.MultiVariant;
import net.minecraft.resources.ResourceLocation;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
@Mixin(MultiVariant.class)
public abstract class VariantListMixin {
@Shadow public abstract List<Variant> getVariants();
/**
* @author embeddedt
* @reason Parallelize calls to getMaterials
*/
@Overwrite
public Collection<Material> getMaterials(Function<ResourceLocation, UnbakedModel> pModelGetter, Set<Pair<String, String>> pMissingTextureErrors) {
List<UnbakedModel> models = this.getVariants().stream().map(Variant::getModelLocation).distinct().map(pModelGetter).collect(Collectors.toList());
return models.parallelStream().flatMap(model -> model.getMaterials(pModelGetter, pMissingTextureErrors).stream()).collect(Collectors.toSet());
}
}

View File

@ -0,0 +1,29 @@
package org.embeddedt.modernfix.mixin.perf.state_definition_construct;
import com.google.common.collect.ImmutableSortedMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.StateHolder;
import net.minecraft.world.level.block.state.properties.Property;
import org.embeddedt.modernfix.blockstate.FakeStateMap;
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.ModifyVariable;
import java.util.Map;
@Mixin(StateDefinition.class)
public class StateDefinitionMixin<O, S extends StateHolder<O, S>> {
@Shadow @Final private ImmutableSortedMap<String, Property<?>> propertiesByName;
@ModifyVariable(method = "<init>", at = @At(value = "STORE", ordinal = 0), ordinal = 1, index = 8)
private Map<Map<Property<?>, Comparable<?>>, S> useArrayMap(Map<Map<Property<?>, Comparable<?>>, S> in) {
int numStates = 1;
for(Property<?> prop : this.propertiesByName.values()) {
numStates *= prop.getPossibleValues().size();
}
return new FakeStateMap<>(numStates);
}
}

View File

@ -1,138 +0,0 @@
package org.embeddedt.modernfix.models;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.datafixers.util.Pair;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.renderer.block.model.ItemTransforms;
import net.minecraft.client.renderer.block.model.ItemOverrides;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.world.item.ItemStack;
import net.minecraft.core.Direction;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraftforge.client.model.data.EmptyModelData;
import net.minecraftforge.client.model.data.IModelData;
import org.embeddedt.modernfix.ModernFix;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Random;
import java.util.function.Supplier;
public class LazyBakedModel implements BakedModel {
private BakedModel delegate = null;
private Supplier<BakedModel> delegateSupplier;
/**
* This flag is changed to true when we should bake instead of returning reasonable defaults for certain
* method calls.
*/
public static boolean allowBakeForFlags = false;
public LazyBakedModel(Supplier<BakedModel> delegateSupplier) {
this.delegateSupplier = delegateSupplier;
}
public BakedModel computeDelegate() {
if(this.delegate == null) {
this.delegate = this.delegateSupplier.get();
this.delegateSupplier = null;
}
return this.delegate;
}
@Override
public List<BakedQuad> getQuads(@Nullable BlockState pState, @Nullable Direction pSide, Random pRand) {
return computeDelegate().getQuads(pState, pSide, pRand);
}
@Override
public boolean useAmbientOcclusion() {
return computeDelegate().useAmbientOcclusion();
}
@Override
public boolean isGui3d() {
return computeDelegate().isGui3d();
}
@Override
public boolean usesBlockLight() {
return computeDelegate().usesBlockLight();
}
@Override
public boolean isCustomRenderer() {
if(this.delegate != null)
return this.delegate.isCustomRenderer();
if(!LazyBakedModel.allowBakeForFlags)
return false;
return computeDelegate().isCustomRenderer();
}
@Override
public TextureAtlasSprite getParticleIcon() {
return computeDelegate().getParticleIcon();
}
@Override
public ItemTransforms getTransforms() {
return computeDelegate().getTransforms();
}
@Override
public ItemOverrides getOverrides() {
return computeDelegate().getOverrides();
}
@Override
public BakedModel getBakedModel() {
return computeDelegate().getBakedModel();
}
@Nonnull
@Override
public List<BakedQuad> getQuads(@Nullable BlockState state, @Nullable Direction side, @Nonnull Random rand, @Nonnull IModelData extraData) {
return computeDelegate().getQuads(state, side, rand, extraData);
}
@Override
public boolean isAmbientOcclusion(BlockState state) {
return computeDelegate().isAmbientOcclusion(state);
}
@Override
public boolean doesHandlePerspectives() {
return computeDelegate().doesHandlePerspectives();
}
@Override
public BakedModel handlePerspective(ItemTransforms.TransformType cameraTransformType, PoseStack mat) {
return computeDelegate().handlePerspective(cameraTransformType, mat);
}
@Nonnull
@Override
public IModelData getModelData(@Nonnull BlockAndTintGetter world, @Nonnull BlockPos pos, @Nonnull BlockState state, @Nonnull IModelData tileData) {
return computeDelegate().getModelData(world, pos, state, tileData);
}
@Override
public TextureAtlasSprite getParticleTexture(@Nonnull IModelData data) {
return computeDelegate().getParticleTexture(data);
}
@Override
public boolean isLayered() {
return computeDelegate().isLayered();
}
@Override
public List<Pair<BakedModel, RenderType>> getLayerModels(ItemStack itemStack, boolean fabulous) {
return computeDelegate().getLayerModels(itemStack, fabulous);
}
}

View File

@ -92,18 +92,18 @@ public class StbStitcher {
}
}
private static STBRPRect setWrapper(STBRPRect rect, int id, int width, int height, int x, int y, boolean was_packed) {
public static STBRPRect setWrapper(STBRPRect rect, int id, int width, int height, int x, int y, boolean was_packed) {
try {
if(MH_rect_shortSet != null)
return (STBRPRect)MH_rect_shortSet.invokeExact(rect, id, (short)width, (short)height, (short)0, (short)0, false);
return (STBRPRect)MH_rect_shortSet.invokeExact(rect, id, (short)width, (short)height, (short)x, (short)y, was_packed);
else
return (STBRPRect)MH_rect_intSet.invokeExact(rect, id, width, height, 0, 0, false);
return (STBRPRect)MH_rect_intSet.invokeExact(rect, id, width, height, x, y, was_packed);
} catch(Throwable e) {
throw new AssertionError(e);
}
}
private static int getX(STBRPRect rect) {
public static int getX(STBRPRect rect) {
try {
if(MH_rect_shortX != null)
return (short)MH_rect_shortX.invokeExact(rect);
@ -114,7 +114,7 @@ public class StbStitcher {
}
}
private static int getY(STBRPRect rect) {
public static int getY(STBRPRect rect) {
try {
if(MH_rect_shortX != null)
return (short)MH_rect_shortY.invokeExact(rect);

View File

@ -9,14 +9,34 @@ public class FileUtil {
return file;
}
private static final Pattern SLASH_PATTERN = Pattern.compile("(?:\\\\+|\\/+)");
/**
* Normalize a path by removing double slashes, etc.
* <p></p>
* This implementation avoids creating a new string unless there are actually double slashes present
* in the input path.
* @param path input path
* @return a normalized version of the path
*/
public static String normalize(String path) {
return SLASH_PATTERN.matcher(path).replaceAll("/");
char prevChar = 0;
StringBuilder sb = null;
for(int i = 0; i < path.length(); i++) {
char thisChar = path.charAt(i);
if(prevChar != '/' || thisChar != prevChar) {
/* This character should end up in the final string. If we are using the builder, add it there. */
if(sb != null)
sb.append(thisChar);
} else {
/* This character should not end up in the final string. We need to make a buidler if we haven't
* done so yet.
*/
if(sb == null) {
sb = new StringBuilder(path.length());
sb.append(path, 0, i);
}
}
prevChar = thisChar;
}
return sb == null ? path : sb.toString();
}
}

View File

@ -60,7 +60,8 @@
"perf.cache_strongholds.ChunkGeneratorMixin",
"perf.cache_upgraded_structures.StructureManagerMixin",
"perf.cache_strongholds.ServerLevelMixin",
"devenv.MinecraftServerMixin",
"perf.state_definition_construct.StateDefinitionMixin",
"devenv.MinecraftServerMixin"
],
"client": [
"core.MinecraftMixin",
@ -71,15 +72,14 @@
"bugfix.concurrency.RenderTypeMixin",
"bugfix.concurrency.MinecraftMixin",
"bugfix.concurrency.StaticTagHelperMixin",
"perf.parallelize_model_loading.BlockModelShaperMixin",
"perf.parallelize_model_loading.ModelBakeryMixin",
"perf.parallelize_model_loading.OBJLoaderMixin",
"perf.parallelize_model_loading.multipart.MultipartMixin",
"perf.parallelize_model_loading.multipart.VariantListMixin",
"perf.parallelize_model_loading.SelectorMixin",
"perf.parallelize_model_loading.TransformationMatrixMixin",
"perf.parallelize_model_loading.BooleanPropertyMixin",
"perf.parallelize_model_loading.PropertyMixin",
"perf.dynamic_resources.BlockModelShaperMixin",
"perf.dynamic_resources.ItemModelShaperMixin",
"perf.dynamic_resources.ModelBakeryMixin",
"perf.model_optimizations.OBJLoaderMixin",
"perf.model_optimizations.SelectorMixin",
"perf.model_optimizations.TransformationMatrixMixin",
"perf.model_optimizations.BooleanPropertyMixin",
"perf.model_optimizations.PropertyMixin",
"perf.async_jei.InputConstantsMixin",
"perf.async_jei.IngredientListElementFactoryMixin",
"perf.async_jei.ClientLifecycleHandlerMixin",
@ -93,9 +93,6 @@
"perf.flatten_model_predicates.PropertyValueConditionMixin",
"perf.blast_search_trees.MinecraftMixin",
"perf.blast_search_trees.IngredientFilterInvoker",
"perf.faster_baking.ModelBakeryMixin",
"perf.faster_baking.BlockModelShapesMixin",
"perf.faster_baking.ModelManagerMixin",
"perf.cache_model_materials.VanillaModelMixin",
"perf.cache_model_materials.MultipartMixin",
"perf.faster_texture_stitching.StitcherMixin",