diff --git a/common/src/main/java/org/embeddedt/modernfix/common/mixin/feature/disable_unihex_font/UnihexProviderDefinitionMixin.java b/common/src/main/java/org/embeddedt/modernfix/common/mixin/feature/disable_unihex_font/UnihexProviderDefinitionMixin.java deleted file mode 100644 index 114577c1..00000000 --- a/common/src/main/java/org/embeddedt/modernfix/common/mixin/feature/disable_unihex_font/UnihexProviderDefinitionMixin.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.embeddedt.modernfix.common.mixin.feature.disable_unihex_font; - -import com.mojang.blaze3d.font.GlyphProvider; -import com.mojang.datafixers.util.Either; -import net.minecraft.client.gui.font.CodepointMap; -import net.minecraft.client.gui.font.providers.GlyphProviderDefinition; -import net.minecraft.client.gui.font.providers.UnihexProvider; -import net.minecraft.server.packs.resources.ResourceManager; -import org.embeddedt.modernfix.ModernFix; -import org.embeddedt.modernfix.annotation.ClientOnlyMixin; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -import java.io.IOException; -import java.lang.reflect.Constructor; - -@Mixin(UnihexProvider.Definition.class) -@ClientOnlyMixin -public class UnihexProviderDefinitionMixin { - @Inject(method = "unpack", at = @At("HEAD"), cancellable = true) - private void disableProvider(CallbackInfoReturnable> cir) { - cir.setReturnValue(Either.left(this::mfix$loadEmpty)); - } - - private GlyphProvider mfix$loadEmpty(ResourceManager resourceManager) throws IOException { - try { - ModernFix.LOGGER.warn("Unihex provider is disabled, a number of Unicode characters will likely not render"); - Constructor constructor = UnihexProvider.class.getDeclaredConstructor(CodepointMap.class); - constructor.setAccessible(true); - return constructor.newInstance(new CodepointMap<>(Object[]::new, Object[][]::new)); - } catch(ReflectiveOperationException e) { - throw new IOException("Failed to create empty loader", e); - } - } -} diff --git a/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/compress_unihex_font/UnihexProviderByteContentsMixin.java b/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/compress_unihex_font/UnihexProviderByteContentsMixin.java new file mode 100644 index 00000000..f358dad2 --- /dev/null +++ b/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/compress_unihex_font/UnihexProviderByteContentsMixin.java @@ -0,0 +1,22 @@ +package org.embeddedt.modernfix.common.mixin.perf.compress_unihex_font; + +import com.llamalad7.mixinextras.sugar.Local; +import it.unimi.dsi.fastutil.bytes.ByteList; +import net.minecraft.client.gui.font.providers.UnihexProvider; +import org.embeddedt.modernfix.annotation.ClientOnlyMixin; +import org.embeddedt.modernfix.render.font.CompactUnihexContents; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(targets = {"net/minecraft/client/gui/font/providers/UnihexProvider$ByteContents"}) +@ClientOnlyMixin +public class UnihexProviderByteContentsMixin { + @Inject(method = "read", at = @At(value = "NEW", target = "([B)Lnet/minecraft/client/gui/font/providers/UnihexProvider$ByteContents;"), cancellable = true) + private static void useCompactIfPossible(int index, ByteList byteList, CallbackInfoReturnable cir, @Local(ordinal = 0) byte[] contents) { + if (contents.length == 16) { + cir.setReturnValue(new CompactUnihexContents.Bytes(contents)); + } + } +} diff --git a/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/compress_unihex_font/UnihexProviderShortContentsMixin.java b/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/compress_unihex_font/UnihexProviderShortContentsMixin.java new file mode 100644 index 00000000..1f9abf47 --- /dev/null +++ b/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/compress_unihex_font/UnihexProviderShortContentsMixin.java @@ -0,0 +1,22 @@ +package org.embeddedt.modernfix.common.mixin.perf.compress_unihex_font; + +import com.llamalad7.mixinextras.sugar.Local; +import it.unimi.dsi.fastutil.bytes.ByteList; +import net.minecraft.client.gui.font.providers.UnihexProvider; +import org.embeddedt.modernfix.annotation.ClientOnlyMixin; +import org.embeddedt.modernfix.render.font.CompactUnihexContents; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(targets = {"net/minecraft/client/gui/font/providers/UnihexProvider$ShortContents"}) +@ClientOnlyMixin +public class UnihexProviderShortContentsMixin { + @Inject(method = "read", at = @At(value = "NEW", target = "([S)Lnet/minecraft/client/gui/font/providers/UnihexProvider$ShortContents;"), cancellable = true) + private static void useCompactIfPossible(int index, ByteList byteList, CallbackInfoReturnable cir, @Local(ordinal = 0) short[] contents) { + if (contents.length == 16) { + cir.setReturnValue(new CompactUnihexContents.Shorts(contents)); + } + } +} diff --git a/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/fix_loop_spin_waiting/BlockableEventLoopMixin.java b/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/fix_loop_spin_waiting/BlockableEventLoopMixin.java deleted file mode 100644 index 1f67f53a..00000000 --- a/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/fix_loop_spin_waiting/BlockableEventLoopMixin.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.embeddedt.modernfix.common.mixin.perf.fix_loop_spin_waiting; - -import net.minecraft.util.thread.BlockableEventLoop; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Overwrite; - -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.LockSupport; - -// This should fix https://bugs.mojang.com/browse/MC-183518 -@Mixin(value = BlockableEventLoop.class, priority = 500) -public class BlockableEventLoopMixin { - private static final long MFIX$TICK_WAIT_TIME = TimeUnit.MILLISECONDS.toNanos(2); - - /** - * @author embeddedt - * @reason yielding the thread is pretty pointless if we're about to park anyway - */ - @Overwrite - public void waitForTasks() { - LockSupport.parkNanos("waiting for tasks", MFIX$TICK_WAIT_TIME); - } -} diff --git a/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/fix_loop_spin_waiting/MinecraftServerMixin.java b/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/fix_loop_spin_waiting/MinecraftServerMixin.java new file mode 100644 index 00000000..20030314 --- /dev/null +++ b/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/fix_loop_spin_waiting/MinecraftServerMixin.java @@ -0,0 +1,48 @@ +package org.embeddedt.modernfix.common.mixin.perf.fix_loop_spin_waiting; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import net.minecraft.Util; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.thread.BlockableEventLoop; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; + +import java.util.concurrent.locks.LockSupport; +import java.util.function.BooleanSupplier; + +@Mixin(value = MinecraftServer.class, priority = 500) +public abstract class MinecraftServerMixin extends BlockableEventLoop { + @Shadow private long nextTickTimeNanos; + + protected MinecraftServerMixin(String name) { + super(name); + } + + @Unique + private boolean mfix$isWaitingForNextTick = false; + + @WrapOperation( + method = "waitUntilNextTick", + at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;managedBlock(Ljava/util/function/BooleanSupplier;)V") + ) + private void managedBlock(MinecraftServer instance, BooleanSupplier isDone, Operation original) { + try { + this.mfix$isWaitingForNextTick = true; + original.call(instance, isDone); + } finally { + this.mfix$isWaitingForNextTick = false; + } + } + + @Override + public void waitForTasks() { + if (this.mfix$isWaitingForNextTick) { + LockSupport.parkNanos("waiting for tasks", this.nextTickTimeNanos - Util.getNanos()); + } else { + super.waitForTasks(); + } + } +} diff --git a/common/src/main/java/org/embeddedt/modernfix/render/font/CompactUnihexContents.java b/common/src/main/java/org/embeddedt/modernfix/render/font/CompactUnihexContents.java new file mode 100644 index 00000000..2b2e199a --- /dev/null +++ b/common/src/main/java/org/embeddedt/modernfix/render/font/CompactUnihexContents.java @@ -0,0 +1,96 @@ +package org.embeddedt.modernfix.render.font; + +import net.minecraft.client.gui.font.providers.UnihexProvider; + +/** + * Implements more compact storage for LineData contents. + * + * Credit for the idea of using flattened fields rather than a backing array goes to @AnAwesomGuy. + */ +public class CompactUnihexContents { + private static long extract8Bytes(byte[] arr, int off) { + long l = 0; + for (int i = 0; i < 8; i++) { + l |= ((long)arr[off + i] << (i * 8)); + } + return l; + } + + private static byte extractByte(long compressed, int off) { + return (byte)((compressed >> (off * 8)) & 0xFF); + } + + private static long extract4Shorts(short[] arr, int off) { + long l = 0; + for (int i = 0; i < 4; i++) { + l |= ((long)arr[off + i] << (i * 16)); + } + return l; + } + + private static short extractShort(long compressed, int off) { + return (short)((compressed >> (off * 16)) & 0xFFFF); + } + + public static class Bytes implements UnihexProvider.LineData { + private final long b0; + private final long b8; + + public Bytes(byte[] contents) { + this.b0 = extract8Bytes(contents, 0); + this.b8 = extract8Bytes(contents, 8); + } + + @Override + public int line(int index) { + if (index < 0 || index >= 16) { + throw new ArrayIndexOutOfBoundsException(); + } + if (index < 8) { + return extractByte(b0, index) << 24; + } else { + return extractByte(b8, index - 8) << 24; + } + } + + @Override + public int bitWidth() { + return 8; + } + } + + public static class Shorts implements UnihexProvider.LineData { + private final long b0; + private final long b4; + private final long b8; + private final long b12; + + public Shorts(short[] contents) { + this.b0 = extract4Shorts(contents, 0); + this.b4 = extract4Shorts(contents, 4); + this.b8 = extract4Shorts(contents, 8); + this.b12 = extract4Shorts(contents, 12); + } + + @Override + public int line(int index) { + if (index < 0 || index >= 16) { + throw new ArrayIndexOutOfBoundsException(); + } + if (index < 4) { + return extractShort(b0, index) << 16; + } else if (index < 8) { + return extractShort(b4, index - 4) << 16; + } else if (index < 12) { + return extractShort(b8, index - 8) << 16; + } else { + return extractShort(b12, index - 12) << 16; + } + } + + @Override + public int bitWidth() { + return 16; + } + } +} diff --git a/common/src/main/java/org/embeddedt/modernfix/resources/PackResourcesCacheEngine.java b/common/src/main/java/org/embeddedt/modernfix/resources/PackResourcesCacheEngine.java index 41be7011..64319278 100644 --- a/common/src/main/java/org/embeddedt/modernfix/resources/PackResourcesCacheEngine.java +++ b/common/src/main/java/org/embeddedt/modernfix/resources/PackResourcesCacheEngine.java @@ -16,6 +16,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import java.util.stream.Stream; @@ -24,6 +25,8 @@ import java.util.stream.Stream; */ public class PackResourcesCacheEngine { private static final Joiner SLASH_JOINER = Joiner.on('/'); + private static final ConcurrentHashMap PATH_COMPONENT_INTERNER = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap CACHED_SPLIT_PATHS = new ConcurrentHashMap<>(); static class Node { Map children; @@ -100,7 +103,6 @@ public class PackResourcesCacheEngine { // used for log message this.debugPath = basePathRetriever.apply(PackType.CLIENT_RESOURCES).toAbsolutePath(); this.root.children = new Object2ObjectOpenHashMap<>(); - ObjectOpenHashSet pathKeys = new ObjectOpenHashSet<>(); for(PackType type : PackType.values()) { var typeRoot = new Node(); this.root.children.put(type.getDirectory(), typeRoot); @@ -114,8 +116,12 @@ public class PackResourcesCacheEngine { .filter(PackResourcesCacheEngine::isValidCachedResourcePath) .forEach(path -> { var node = typeRoot; - for (Path component : path) { - String key = pathKeys.addOrGet(component.toString()); + int nameCount = path.getNameCount(); + for (int i = 0; i < nameCount; i++) { + String key = path.getName(i).toString(); + if (i < (nameCount - 1)) { + key = PATH_COMPONENT_INTERNER.computeIfAbsent(key, Function.identity()); + } if (node.children == null) { node.children = new Object2ObjectOpenHashMap<>(); } @@ -147,9 +153,17 @@ public class PackResourcesCacheEngine { public Set getNamespaces(PackType type) { awaitLoad(); - if(PackTypeHelper.isVanillaPackType(type)) - return this.root.getChild(type.getDirectory()).children.keySet(); - else + if(PackTypeHelper.isVanillaPackType(type)) { + var namespaceToNodeMap = this.root.getChild(type.getDirectory()).children; + var results = new ObjectOpenHashSet(); + for (var entry : namespaceToNodeMap.entrySet()) { + // Entries without children are files, not folders + if (!entry.getValue().children.isEmpty()) { + results.add(entry.getKey()); + } + } + return results; + } else return null; } @@ -205,4 +219,16 @@ public class PackResourcesCacheEngine { } node.collectResources(resourceNamespace, this.rootPathsByType.get(type).resolve(resourceNamespace), components, 0, maxDepth, output); } + + private static String[] decompose(String path) { + String[] components = path.split("/"); + for (int i = 0; i < components.length; i++) { + components[i] = PATH_COMPONENT_INTERNER.computeIfAbsent(components[i], Function.identity()); + } + return components; + } + + public static String[] decomposeCached(String path) { + return CACHED_SPLIT_PATHS.computeIfAbsent(path, PackResourcesCacheEngine::decompose); + } } diff --git a/common/src/main/resources/assets/modernfix/lang/en_us.json b/common/src/main/resources/assets/modernfix/lang/en_us.json index c7e317c4..918d98ed 100644 --- a/common/src/main/resources/assets/modernfix/lang/en_us.json +++ b/common/src/main/resources/assets/modernfix/lang/en_us.json @@ -117,7 +117,7 @@ "modernfix.option.mixin.perf.twilightforest.structure_spawn_fix": "Fixes lag caused by Twilight Forest worldgen checking structures very inefficiently", "modernfix.option.mixin.perf.fast_forge_dummies": "Speeds up Forge registry freezing during launch by using a faster code path", "modernfix.option.mixin.perf.tag_id_caching": "Speeds up uses of tag entries by caching the location object instead of recreating it every time", - "modernfix.option.mixin.feature.disable_unihex_font": "Remove the Unicode font, saves 10MB but causes special characters to no longer render", + "modernfix.option.mixin.perf.compress_unihex_font": "Stores the glyphs for the Unicode font more efficiently. Kudos to @AnAwesomGuy for the trick.", "modernfix.option.mixin.bugfix.world_leaks": "Reduces the memory usage of old client-side worlds that aren't needed after switching dimensions. These are normally garbage collected in vanilla, but mods sometimes retain references to them.", "modernfix.option.mixin.perf.compact_mojang_registries": "(Fabric) Experimental option that reduces the memory usage of registries by roughly 50%. Not useful in most modpacks unless they contain millions of blocks and items.", "modernfix.option.mixin.perf.dynamic_block_codecs": "Avoids storing a codec for every block(state) and instead generates and caches it on the fly when needed. Generally not worth enabling unless you have a million blocks/items.", diff --git a/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/dynresources/ModelBakeEventHelper.java b/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/dynresources/ModelBakeEventHelper.java index 1bdee60a..c2175e10 100644 --- a/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/dynresources/ModelBakeEventHelper.java +++ b/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/dynresources/ModelBakeEventHelper.java @@ -6,15 +6,13 @@ import com.google.common.collect.Sets; import com.google.common.graph.GraphBuilder; import com.google.common.graph.MutableGraph; import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; 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.ModelResourceLocation; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.FileToIdConverter; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.state.BlockState; import net.neoforged.fml.ModContainer; import net.neoforged.fml.ModList; import net.neoforged.neoforgespi.language.IModInfo; @@ -23,12 +21,10 @@ import org.embeddedt.modernfix.util.ForwardingInclDefaultsMap; import org.jetbrains.annotations.Nullable; import java.util.AbstractSet; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; -import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -57,22 +53,34 @@ public class ModelBakeEventHelper { } private static final Map MOD_VISIBILITY_CONFIGURATION = ImmutableMap.builder() .put("eternal_starlight", UniverseVisibility.SELF_AND_DEPS) // needed as a mitigation until https://github.com/LeoMinecraftModding/eternal-starlight/pull/82 is merged + .put("alexscaves", UniverseVisibility.SELF_AND_DEPS) + .put("refinedstorage", UniverseVisibility.SELF_AND_DEPS) + .put("cabletiers", UniverseVisibility.SELF_AND_DEPS) .build(); private final Map modelRegistry; private final Set topLevelModelLocations; + + private final Set namespacesWithModels; private final MutableGraph dependencyGraph; public ModelBakeEventHelper(Map modelRegistry) { this.modelRegistry = modelRegistry; - this.topLevelModelLocations = new ObjectLinkedOpenHashSet<>(Block.BLOCK_STATE_REGISTRY.size() + BuiltInRegistries.ITEM.size()); - // Skip going through ModelLocationCache because most of the accesses will be misses + int blockStateCount = 0; + for (var b : BuiltInRegistries.BLOCK) { + blockStateCount += b.getStateDefinition().getPossibleStates().size(); + } + this.topLevelModelLocations = new ObjectLinkedOpenHashSet<>(blockStateCount + BuiltInRegistries.ITEM.size()); + this.namespacesWithModels = new ObjectOpenHashSet<>(ModList.get().size()); + var modelLocationBuilder = new ModelLocationBuilder(); BuiltInRegistries.BLOCK.entrySet().forEach(entry -> { var location = entry.getKey().location(); - for(BlockState state : entry.getValue().getStateDefinition().getPossibleStates()) { - topLevelModelLocations.add(BlockModelShaper.stateToModelLocation(location, state)); - } + modelLocationBuilder.generateForBlock(topLevelModelLocations, entry.getValue(), location); + namespacesWithModels.add(location.getNamespace()); + }); + BuiltInRegistries.ITEM.keySet().forEach(key -> { + topLevelModelLocations.add(new ModelResourceLocation(key, "inventory")); + namespacesWithModels.add(key.getNamespace()); }); - BuiltInRegistries.ITEM.keySet().forEach(key -> topLevelModelLocations.add(new ModelResourceLocation(key, "inventory"))); this.topLevelModelLocations.addAll(modelRegistry.keySet()); // We add all standard item model locations here so that mods like JAOPCA that assume their remapping logic // triggers loading of them (which it doesn't with dynamic resources on), will still detect the presence @@ -80,11 +88,15 @@ public class ModelBakeEventHelper { var itemModelLister = FileToIdConverter.json("models/item"); itemModelLister.listMatchingResources(Minecraft.getInstance().getResourceManager()).keySet().forEach(itemModel -> { this.topLevelModelLocations.add(ModelResourceLocation.inventory(itemModelLister.fileToId(itemModel))); + this.namespacesWithModels.add(itemModel.getNamespace()); }); + for (var loc : modelRegistry.keySet()) { + this.namespacesWithModels.add(loc.id().getNamespace()); + } this.dependencyGraph = buildDependencyGraph(); } - private static MutableGraph buildDependencyGraph() { + private MutableGraph buildDependencyGraph() { MutableGraph dependencyGraph = GraphBuilder.undirected().build(); ModList.get().forEachModContainer((id, mc) -> { dependencyGraph.addNode(id); @@ -97,7 +109,7 @@ public class ModelBakeEventHelper { if(mContainer.isPresent()) { for(IModInfo.ModVersion version : mContainer.get().getModInfo().getDependencies()) { // avoid self-loops - if(!Objects.equals(id, version.getModId())) + if(!Objects.equals(id, version.getModId()) && !version.getModId().equals("minecraft") && namespacesWithModels.contains(version.getModId())) dependencyGraph.putEdge(id, version.getModId()); } } @@ -152,17 +164,29 @@ public class ModelBakeEventHelper { }; } + private Set computeVisibleModIds(String modId) { + Set deps; + try { + deps = this.dependencyGraph.adjacentNodes(modId); + } catch (IllegalArgumentException e) { + deps = Set.of(); + } + if (deps.isEmpty()) { + // avoid extra work below + return Set.of(modId); + } + var set = new ObjectOpenHashSet(); + set.add(modId); + set.addAll(deps); + return Set.copyOf(set); + } + public Map wrapRegistry(String modId) { var config = MOD_VISIBILITY_CONFIGURATION.getOrDefault(modId, UniverseVisibility.EVERYTHING); if (config == UniverseVisibility.NONE) { return createWarningRegistry(modId); } - final Set modIdsToInclude = new HashSet<>(); - modIdsToInclude.add(modId); - try { - modIdsToInclude.addAll(this.dependencyGraph.adjacentNodes(modId)); - } catch(IllegalArgumentException ignored) { /* sanity check */ } - modIdsToInclude.remove("minecraft"); + final Set modIdsToInclude = computeVisibleModIds(modId); Set ourModelLocations; if (config == UniverseVisibility.SELF_AND_DEPS) { ModernFix.LOGGER.debug("Mod {} is restricted to seeing models from mods: [{}]", modId, String.join(", ", modIdsToInclude)); @@ -220,8 +244,7 @@ public class ModelBakeEventHelper { @Override public void replaceAll(BiFunction function) { ModernFix.LOGGER.warn("Mod '{}' is calling replaceAll on the model registry. Some hacks will be used to keep this fast, but they may not be 100% compatible.", modId); - List locations = new ArrayList<>(ourModelLocations); - for(ModelResourceLocation location : locations) { + for(ModelResourceLocation location : ourModelLocations) { /* * Fetching every model is insanely slow. So we call the function with a null object first, since it * probably isn't expecting that. If we get an exception thrown, or it returns nonnull, then we know diff --git a/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/dynresources/ModelLocationBuilder.java b/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/dynresources/ModelLocationBuilder.java new file mode 100644 index 00000000..6dbf84d3 --- /dev/null +++ b/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/dynresources/ModelLocationBuilder.java @@ -0,0 +1,64 @@ +package org.embeddedt.modernfix.neoforge.dynresources; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import net.minecraft.client.resources.model.ModelResourceLocation; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.properties.Property; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +public class ModelLocationBuilder { + private final Map, PropertyData> propertyToOptionStrings = new Object2ObjectOpenHashMap<>(); + private final StringBuilder builder = new StringBuilder(); + + private record PropertyData(ImmutableList nameValuePairs, int maxPairLength) {} + + public void generateForBlock(Set destinationSet, Block block, ResourceLocation baseLocation) { + var props = block.getStateDefinition().getProperties(); + List> optionsList = new ArrayList<>(props.size()); + int requiredBuilderSize = Math.max(0, props.size() - 1); // commas + for (var prop : props) { + var data = propertyToOptionStrings.computeIfAbsent(prop, ModelLocationBuilder::computePropertyOptions); + optionsList.add(data.nameValuePairs); + requiredBuilderSize += data.maxPairLength; + } + var product = Lists.cartesianProduct(optionsList); + int count = product.size(); + int tupleEntryCount = optionsList.size(); + StringBuilder stringbuilder = this.builder; + stringbuilder.ensureCapacity(requiredBuilderSize); + for (int i = 0; i < count; i++) { + stringbuilder.setLength(0); + var result = product.get(i); + for (int j = 0; j < tupleEntryCount; j++) { + if (j != 0) { + stringbuilder.append(','); + } + stringbuilder.append(result.get(j)); + } + destinationSet.add(new ModelResourceLocation(baseLocation, stringbuilder.toString())); + } + } + + private static PropertyData computePropertyOptions(Property prop) { + ImmutableList.Builder valuesList = ImmutableList.builderWithExpectedSize(prop.getPossibleValues().size()); + int maxLength = 0; + for (var val : prop.getPossibleValues()) { + String pair = prop.getName() + "=" + getValueName(prop, val); + valuesList.add(pair.toLowerCase(Locale.ROOT)); + maxLength = Math.max(pair.length(), maxLength); + } + return new PropertyData(valuesList.build(), maxLength); + } + + private static > String getValueName(Property property, Comparable value) { + return property.getName((T)value); + } +} diff --git a/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/mixin/perf/dynamic_resources/ForgeHooksClientMixin.java b/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/mixin/perf/dynamic_resources/ForgeHooksClientMixin.java index 325e19bf..f66b34d0 100644 --- a/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/mixin/perf/dynamic_resources/ForgeHooksClientMixin.java +++ b/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/mixin/perf/dynamic_resources/ForgeHooksClientMixin.java @@ -33,14 +33,18 @@ public class ForgeHooksClientMixin { if(ModLoader.hasErrors()) return; ModelEvent.ModifyBakingResult bakeEvent = ((ModelEvent.ModifyBakingResult)event); - ModelBakeEventHelper helper = new ModelBakeEventHelper(bakeEvent.getModels()); - Method acceptEv = ObfuscationReflectionHelper.findMethod(ModContainer.class, "acceptEvent", Event.class); Stopwatch globalTimer = Stopwatch.createStarted(); + Stopwatch selfTimer = Stopwatch.createStarted(); + ModelBakeEventHelper helper = new ModelBakeEventHelper(bakeEvent.getModels()); + selfTimer.stop(); + Method acceptEv = ObfuscationReflectionHelper.findMethod(ModContainer.class, "acceptEvent", Event.class); Map times = new Object2ObjectOpenHashMap<>(); + times.put("modernfix", selfTimer); ModList.get().forEachModContainer((id, mc) -> { Map newRegistry = helper.wrapRegistry(id); ModelEvent.ModifyBakingResult postedEvent = new ModelEvent.ModifyBakingResult(newRegistry, bakeEvent.getTextureGetter(), bakeEvent.getModelBakery()); - Stopwatch timer = times.computeIfAbsent(id, $ -> Stopwatch.createStarted()); + Stopwatch timer = times.computeIfAbsent(id, $ -> Stopwatch.createUnstarted()); + timer.start(); try { acceptEv.invoke(mc, postedEvent); } catch(ReflectiveOperationException e) {