Further optimizations to model loading

This commit is contained in:
embeddedt 2023-02-06 10:01:15 -05:00
parent 30ae895fa5
commit 0ffb3cc973
No known key found for this signature in database
GPG Key ID: A69433EC199B5613
7 changed files with 119 additions and 50 deletions

View File

@ -18,7 +18,7 @@ plugins {
apply plugin: 'org.spongepowered.mixin'
group = 'org.embeddedt'
version = '1.6.0-beta1'
version = '1.6.0-beta2'
java {
archivesBaseName = 'modernfix-mc' + minecraft_version

View File

@ -8,18 +8,19 @@ import net.minecraft.client.renderer.texture.SpriteMap;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.profiler.IProfiler;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.vector.TransformationMatrix;
import net.minecraftforge.fml.loading.progress.StartupMessageManager;
import org.apache.commons.lang3.tuple.Triple;
import org.apache.logging.log4j.Logger;
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.*;
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.stream.Collectors;
@Mixin(ModelBakery.class)
@ -37,10 +38,19 @@ public abstract class ModelBakeryMixin {
@Shadow @Nullable private SpriteMap atlasSet;
@Shadow @Final private Map<ResourceLocation, IUnbakedModel> unbakedCache;
@Shadow @Mutable
@Final private Map<Triple<ResourceLocation, TransformationMatrix, Boolean>, IBakedModel> bakedCache;
private Map<Boolean, List<ResourceLocation>> modelsToBakeParallel;
private boolean canBakeParallel(IUnbakedModel unbakedModel) {
return false;
if(!(unbakedModel instanceof BlockModel))
return false;
else {
BlockModel model = (BlockModel)unbakedModel;
if(model.customData.hasCustomGeometry())
return false;
return true;
}
}
@Inject(method = "processLoading", at = @At(value = "INVOKE", target = "Lnet/minecraft/profiler/IProfiler;pop()V"))
@ -56,18 +66,17 @@ public abstract class ModelBakeryMixin {
pProfiler.popPush("baking");
StartupMessageManager.mcLoaderConsumer().ifPresent(c -> c.accept("Baking models"));
this.atlasSet = new SpriteMap(this.atlasPreparations.values().stream().map(Pair::getFirst).collect(Collectors.toList()));
this.bakedCache = new ConcurrentHashMap<>();
this.modelsToBakeParallel = this.topLevelModels.keySet().stream()
.collect(Collectors.partitioningBy(location -> {
return true;
/*
IUnbakedModel unbakedModel = this.unbakedCache.get(location);
if(unbakedModel == null)
return false;
else
return this.canBakeParallel(unbakedModel);
*/
}));
List<ResourceLocation> parallelModels = this.modelsToBakeParallel.get(true);
List<CompletableFuture<Void>> futures = new ArrayList<>();
parallelModels.forEach((p_229350_1_) -> {
IBakedModel ibakedmodel = null;

View File

@ -0,0 +1,19 @@
package org.embeddedt.modernfix.mixin.perf.parallelize_model_loading;
import com.google.common.collect.ImmutableSet;
import net.minecraft.state.BooleanProperty;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(BooleanProperty.class)
public class BooleanPropertyMixin {
/**
* There is no point comparing the immutable sets in any two instances of this class, as they will always be
* the same.
*/
@Redirect(method = "equals", at = @At(value = "INVOKE", target = "Lcom/google/common/collect/ImmutableSet;equals(Ljava/lang/Object;)Z"))
private boolean skipEqualityCheck(ImmutableSet instance, Object object) {
return true;
}
}

View File

@ -8,6 +8,7 @@ import net.minecraft.block.BlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BlockModelShapes;
import net.minecraft.client.renderer.model.*;
import net.minecraft.client.renderer.model.multipart.Selector;
import net.minecraft.client.renderer.texture.SpriteMap;
import net.minecraft.profiler.IProfiler;
import net.minecraft.resources.IResource;
@ -38,9 +39,11 @@ import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -83,7 +86,8 @@ public abstract class ModelBakeryMixin {
profilerIn.popPush("loadjsons");
AsyncStopwatch.measureAndLogSerialRunningTime("Parallel JSON loading", () -> {
useModelCache = false;
deserializedModelCache = Minecraft.getInstance().getResourceManager().listResources("models", p -> {
this.deserializedModelCache = new ConcurrentHashMap<>();
Collection<ResourceLocation> modelLocations = Minecraft.getInstance().getResourceManager().listResources("models", p -> {
if(!p.endsWith(".json"))
return false;
for(int i = 0; i < p.length(); i++) {
@ -91,54 +95,51 @@ public abstract class ModelBakeryMixin {
return false;
}
return true;
})
.parallelStream()
.map(location -> new ResourceLocation(location.getNamespace(), location.getPath().substring(7, location.getPath().length() - 5)))
.map(location -> Pair.of(location, this.loadModelSafely(location)))
.filter(pair -> pair.getSecond() != null)
.collect(Collectors.toConcurrentMap(Pair::getFirst, Pair::getSecond));
});
ArrayList<CompletableFuture<Void>> futures = new ArrayList<>();
for(ResourceLocation location : modelLocations) {
futures.add(CompletableFuture.runAsync(() -> {
ResourceLocation modelPath = new ResourceLocation(location.getNamespace(), location.getPath().substring(7, location.getPath().length() - 5));
BlockModel model = this.loadModelSafely(modelPath);
if(model != null)
this.deserializedModelCache.put(modelPath, model);
}, Util.backgroundExecutor()));
}
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
useModelCache = true;
});
AsyncStopwatch.measureAndLogSerialRunningTime("Parallel blockstate loading", () -> {
ThreadLocal<BlockModelDefinition.ContainerHolder> containerHolder = ThreadLocal.withInitial(BlockModelDefinition.ContainerHolder::new);
this.deserializedBlockstateCache = Registry.BLOCK.keySet().parallelStream()
.flatMap(block -> {
ResourceLocation blockStateJSON = new ResourceLocation(block.getNamespace(), "blockstates/" + block.getPath() + ".json");
List<IResource> blockStates;
try {
blockStates = this.resourceManager.getResources(blockStateJSON);
} catch(IOException e) {
ModernFix.LOGGER.warn("Exception loading blockstate definition: {}: {}", block, e);
blockStates = Collections.emptyList();
}
return blockStates.stream().map(resource -> Pair.of(block, resource));
})
.map((pair) -> {
ResourceLocation block = pair.getFirst();
IResource resource = pair.getSecond();
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<IResource> blockStates;
try {
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<>();
StateContainer<Block, BlockState> stateContainer = block.getStateDefinition();
BlockModelDefinition.ContainerHolder context = containerHolder.get();
context.setDefinition(stateContainer);
for(IResource resource : blockStates) {
try (InputStream inputstream = resource.getInputStream()) {
BlockModelDefinition.ContainerHolder context = containerHolder.get();
context.setDefinition(Registry.BLOCK.get(block).getStateDefinition());
return Pair.of(block, Pair.of(resource.getSourceName(), BlockModelDefinition.fromStream(context, new InputStreamReader(inputstream, StandardCharsets.UTF_8))));
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 Pair.of(block, Pair.of((String)null, (BlockModelDefinition)null));
return;
}
})
.filter(pair -> pair.getSecond().getSecond() != null)
.collect(Collectors.groupingBy(Pair::getFirst, Collectors.mapping(Pair::getSecond, Collectors.toList())));
});
AsyncStopwatch.measureAndLogSerialRunningTime("Predicate generation", () -> {
/* Pregenerate predicates */
this.deserializedBlockstateCache.entrySet().parallelStream()
.flatMap(entry -> entry.getValue().stream()
.map(Pair::getSecond)
.map(def -> Pair.of(Registry.BLOCK.get(entry.getKey()).getStateDefinition(), def)))
.filter(pair -> pair.getSecond().isMultiPart())
.flatMap(pair -> pair.getSecond().getMultiPart().getSelectors().stream().map(selector -> Pair.of(pair.getFirst(), selector)))
.forEach(pair -> {
pair.getSecond().getPredicate(pair.getFirst());
});
}
this.deserializedBlockstateCache.put(blockLocation, definitions);
}, Util.backgroundExecutor()));
}
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
});
}

View File

@ -0,0 +1,31 @@
package org.embeddedt.modernfix.mixin.perf.parallelize_model_loading;
import net.minecraft.util.math.vector.Matrix4f;
import net.minecraft.util.math.vector.TransformationMatrix;
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.Objects;
@Mixin(TransformationMatrix.class)
public class TransformationMatrixMixin {
@Shadow @Final private Matrix4f matrix;
private Integer cachedHashCode = null;
/**
* @author embeddedt
* @reason use cached hashcode if exists
*/
@Overwrite
public int hashCode() {
int hash;
if(cachedHashCode != null) {
hash = cachedHashCode;
} else {
hash = Objects.hashCode(this.matrix);
cachedHashCode = hash;
}
return hash;
}
}

View File

@ -0,0 +1,7 @@
package org.embeddedt.modernfix.predicate;
/**
* Calculates the
*/
public class CachedModelPredicate {
}

View File

@ -37,6 +37,8 @@
"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.trim_model_caches.ModelManagerMixin",
"perf.async_jei.IngredientListElementFactoryMixin",
"perf.async_jei.ClientLifecycleHandlerMixin",