Merge remote-tracking branch 'origin/1.20' into 1.21.1
This commit is contained in:
commit
7dcaa6b641
|
|
@ -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<Either<GlyphProviderDefinition.Loader, GlyphProviderDefinition.Reference>> 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<UnihexProvider> 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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<UnihexProvider.LineData> cir, @Local(ordinal = 0) byte[] contents) {
|
||||||
|
if (contents.length == 16) {
|
||||||
|
cir.setReturnValue(new CompactUnihexContents.Bytes(contents));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<UnihexProvider.LineData> cir, @Local(ordinal = 0) short[] contents) {
|
||||||
|
if (contents.length == 16) {
|
||||||
|
cir.setReturnValue(new CompactUnihexContents.Shorts(contents));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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<Runnable> {
|
||||||
|
@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<Void> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,7 @@ import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
|
@ -24,6 +25,8 @@ import java.util.stream.Stream;
|
||||||
*/
|
*/
|
||||||
public class PackResourcesCacheEngine {
|
public class PackResourcesCacheEngine {
|
||||||
private static final Joiner SLASH_JOINER = Joiner.on('/');
|
private static final Joiner SLASH_JOINER = Joiner.on('/');
|
||||||
|
private static final ConcurrentHashMap<String, String> PATH_COMPONENT_INTERNER = new ConcurrentHashMap<>();
|
||||||
|
private static final ConcurrentHashMap<String, String[]> CACHED_SPLIT_PATHS = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
static class Node {
|
static class Node {
|
||||||
Map<String, Node> children;
|
Map<String, Node> children;
|
||||||
|
|
@ -100,7 +103,6 @@ public class PackResourcesCacheEngine {
|
||||||
// used for log message
|
// used for log message
|
||||||
this.debugPath = basePathRetriever.apply(PackType.CLIENT_RESOURCES).toAbsolutePath();
|
this.debugPath = basePathRetriever.apply(PackType.CLIENT_RESOURCES).toAbsolutePath();
|
||||||
this.root.children = new Object2ObjectOpenHashMap<>();
|
this.root.children = new Object2ObjectOpenHashMap<>();
|
||||||
ObjectOpenHashSet<String> pathKeys = new ObjectOpenHashSet<>();
|
|
||||||
for(PackType type : PackType.values()) {
|
for(PackType type : PackType.values()) {
|
||||||
var typeRoot = new Node();
|
var typeRoot = new Node();
|
||||||
this.root.children.put(type.getDirectory(), typeRoot);
|
this.root.children.put(type.getDirectory(), typeRoot);
|
||||||
|
|
@ -114,8 +116,12 @@ public class PackResourcesCacheEngine {
|
||||||
.filter(PackResourcesCacheEngine::isValidCachedResourcePath)
|
.filter(PackResourcesCacheEngine::isValidCachedResourcePath)
|
||||||
.forEach(path -> {
|
.forEach(path -> {
|
||||||
var node = typeRoot;
|
var node = typeRoot;
|
||||||
for (Path component : path) {
|
int nameCount = path.getNameCount();
|
||||||
String key = pathKeys.addOrGet(component.toString());
|
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) {
|
if (node.children == null) {
|
||||||
node.children = new Object2ObjectOpenHashMap<>();
|
node.children = new Object2ObjectOpenHashMap<>();
|
||||||
}
|
}
|
||||||
|
|
@ -147,9 +153,17 @@ public class PackResourcesCacheEngine {
|
||||||
|
|
||||||
public Set<String> getNamespaces(PackType type) {
|
public Set<String> getNamespaces(PackType type) {
|
||||||
awaitLoad();
|
awaitLoad();
|
||||||
if(PackTypeHelper.isVanillaPackType(type))
|
if(PackTypeHelper.isVanillaPackType(type)) {
|
||||||
return this.root.getChild(type.getDirectory()).children.keySet();
|
var namespaceToNodeMap = this.root.getChild(type.getDirectory()).children;
|
||||||
else
|
var results = new ObjectOpenHashSet<String>();
|
||||||
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -205,4 +219,16 @@ public class PackResourcesCacheEngine {
|
||||||
}
|
}
|
||||||
node.collectResources(resourceNamespace, this.rootPathsByType.get(type).resolve(resourceNamespace), components, 0, maxDepth, output);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.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.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.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.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.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.",
|
"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.",
|
||||||
|
|
|
||||||
|
|
@ -6,15 +6,13 @@ import com.google.common.collect.Sets;
|
||||||
import com.google.common.graph.GraphBuilder;
|
import com.google.common.graph.GraphBuilder;
|
||||||
import com.google.common.graph.MutableGraph;
|
import com.google.common.graph.MutableGraph;
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet;
|
import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet;
|
||||||
|
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
|
||||||
import net.minecraft.client.Minecraft;
|
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.BakedModel;
|
||||||
import net.minecraft.client.resources.model.ModelBakery;
|
import net.minecraft.client.resources.model.ModelBakery;
|
||||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
import net.minecraft.client.resources.model.ModelResourceLocation;
|
||||||
import net.minecraft.core.registries.BuiltInRegistries;
|
import net.minecraft.core.registries.BuiltInRegistries;
|
||||||
import net.minecraft.resources.FileToIdConverter;
|
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.ModContainer;
|
||||||
import net.neoforged.fml.ModList;
|
import net.neoforged.fml.ModList;
|
||||||
import net.neoforged.neoforgespi.language.IModInfo;
|
import net.neoforged.neoforgespi.language.IModInfo;
|
||||||
|
|
@ -23,12 +21,10 @@ import org.embeddedt.modernfix.util.ForwardingInclDefaultsMap;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.AbstractSet;
|
import java.util.AbstractSet;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
@ -57,22 +53,34 @@ public class ModelBakeEventHelper {
|
||||||
}
|
}
|
||||||
private static final Map<String, UniverseVisibility> MOD_VISIBILITY_CONFIGURATION = ImmutableMap.<String, UniverseVisibility>builder()
|
private static final Map<String, UniverseVisibility> MOD_VISIBILITY_CONFIGURATION = ImmutableMap.<String, UniverseVisibility>builder()
|
||||||
.put("eternal_starlight", UniverseVisibility.SELF_AND_DEPS) // needed as a mitigation until https://github.com/LeoMinecraftModding/eternal-starlight/pull/82 is merged
|
.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();
|
.build();
|
||||||
private final Map<ModelResourceLocation, BakedModel> modelRegistry;
|
private final Map<ModelResourceLocation, BakedModel> modelRegistry;
|
||||||
private final Set<ModelResourceLocation> topLevelModelLocations;
|
private final Set<ModelResourceLocation> topLevelModelLocations;
|
||||||
|
|
||||||
|
private final Set<String> namespacesWithModels;
|
||||||
private final MutableGraph<String> dependencyGraph;
|
private final MutableGraph<String> dependencyGraph;
|
||||||
|
|
||||||
public ModelBakeEventHelper(Map<ModelResourceLocation, BakedModel> modelRegistry) {
|
public ModelBakeEventHelper(Map<ModelResourceLocation, BakedModel> modelRegistry) {
|
||||||
this.modelRegistry = modelRegistry;
|
this.modelRegistry = modelRegistry;
|
||||||
this.topLevelModelLocations = new ObjectLinkedOpenHashSet<>(Block.BLOCK_STATE_REGISTRY.size() + BuiltInRegistries.ITEM.size());
|
int blockStateCount = 0;
|
||||||
// Skip going through ModelLocationCache because most of the accesses will be misses
|
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 -> {
|
BuiltInRegistries.BLOCK.entrySet().forEach(entry -> {
|
||||||
var location = entry.getKey().location();
|
var location = entry.getKey().location();
|
||||||
for(BlockState state : entry.getValue().getStateDefinition().getPossibleStates()) {
|
modelLocationBuilder.generateForBlock(topLevelModelLocations, entry.getValue(), location);
|
||||||
topLevelModelLocations.add(BlockModelShaper.stateToModelLocation(location, state));
|
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());
|
this.topLevelModelLocations.addAll(modelRegistry.keySet());
|
||||||
// We add all standard item model locations here so that mods like JAOPCA that assume their remapping logic
|
// 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
|
// 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");
|
var itemModelLister = FileToIdConverter.json("models/item");
|
||||||
itemModelLister.listMatchingResources(Minecraft.getInstance().getResourceManager()).keySet().forEach(itemModel -> {
|
itemModelLister.listMatchingResources(Minecraft.getInstance().getResourceManager()).keySet().forEach(itemModel -> {
|
||||||
this.topLevelModelLocations.add(ModelResourceLocation.inventory(itemModelLister.fileToId(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();
|
this.dependencyGraph = buildDependencyGraph();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static MutableGraph<String> buildDependencyGraph() {
|
private MutableGraph<String> buildDependencyGraph() {
|
||||||
MutableGraph<String> dependencyGraph = GraphBuilder.undirected().build();
|
MutableGraph<String> dependencyGraph = GraphBuilder.undirected().build();
|
||||||
ModList.get().forEachModContainer((id, mc) -> {
|
ModList.get().forEachModContainer((id, mc) -> {
|
||||||
dependencyGraph.addNode(id);
|
dependencyGraph.addNode(id);
|
||||||
|
|
@ -97,7 +109,7 @@ public class ModelBakeEventHelper {
|
||||||
if(mContainer.isPresent()) {
|
if(mContainer.isPresent()) {
|
||||||
for(IModInfo.ModVersion version : mContainer.get().getModInfo().getDependencies()) {
|
for(IModInfo.ModVersion version : mContainer.get().getModInfo().getDependencies()) {
|
||||||
// avoid self-loops
|
// 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());
|
dependencyGraph.putEdge(id, version.getModId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -152,17 +164,29 @@ public class ModelBakeEventHelper {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Set<String> computeVisibleModIds(String modId) {
|
||||||
|
Set<String> 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<String>();
|
||||||
|
set.add(modId);
|
||||||
|
set.addAll(deps);
|
||||||
|
return Set.copyOf(set);
|
||||||
|
}
|
||||||
|
|
||||||
public Map<ModelResourceLocation, BakedModel> wrapRegistry(String modId) {
|
public Map<ModelResourceLocation, BakedModel> wrapRegistry(String modId) {
|
||||||
var config = MOD_VISIBILITY_CONFIGURATION.getOrDefault(modId, UniverseVisibility.EVERYTHING);
|
var config = MOD_VISIBILITY_CONFIGURATION.getOrDefault(modId, UniverseVisibility.EVERYTHING);
|
||||||
if (config == UniverseVisibility.NONE) {
|
if (config == UniverseVisibility.NONE) {
|
||||||
return createWarningRegistry(modId);
|
return createWarningRegistry(modId);
|
||||||
}
|
}
|
||||||
final Set<String> modIdsToInclude = new HashSet<>();
|
final Set<String> modIdsToInclude = computeVisibleModIds(modId);
|
||||||
modIdsToInclude.add(modId);
|
|
||||||
try {
|
|
||||||
modIdsToInclude.addAll(this.dependencyGraph.adjacentNodes(modId));
|
|
||||||
} catch(IllegalArgumentException ignored) { /* sanity check */ }
|
|
||||||
modIdsToInclude.remove("minecraft");
|
|
||||||
Set<ModelResourceLocation> ourModelLocations;
|
Set<ModelResourceLocation> ourModelLocations;
|
||||||
if (config == UniverseVisibility.SELF_AND_DEPS) {
|
if (config == UniverseVisibility.SELF_AND_DEPS) {
|
||||||
ModernFix.LOGGER.debug("Mod {} is restricted to seeing models from mods: [{}]", modId, String.join(", ", modIdsToInclude));
|
ModernFix.LOGGER.debug("Mod {} is restricted to seeing models from mods: [{}]", modId, String.join(", ", modIdsToInclude));
|
||||||
|
|
@ -220,8 +244,7 @@ public class ModelBakeEventHelper {
|
||||||
@Override
|
@Override
|
||||||
public void replaceAll(BiFunction<? super ModelResourceLocation, ? super BakedModel, ? extends BakedModel> function) {
|
public void replaceAll(BiFunction<? super ModelResourceLocation, ? super BakedModel, ? extends BakedModel> 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);
|
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<ModelResourceLocation> locations = new ArrayList<>(ourModelLocations);
|
for(ModelResourceLocation location : ourModelLocations) {
|
||||||
for(ModelResourceLocation location : locations) {
|
|
||||||
/*
|
/*
|
||||||
* Fetching every model is insanely slow. So we call the function with a null object first, since it
|
* 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
|
* probably isn't expecting that. If we get an exception thrown, or it returns nonnull, then we know
|
||||||
|
|
|
||||||
|
|
@ -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<Property<?>, PropertyData> propertyToOptionStrings = new Object2ObjectOpenHashMap<>();
|
||||||
|
private final StringBuilder builder = new StringBuilder();
|
||||||
|
|
||||||
|
private record PropertyData(ImmutableList<String> nameValuePairs, int maxPairLength) {}
|
||||||
|
|
||||||
|
public void generateForBlock(Set<ModelResourceLocation> destinationSet, Block block, ResourceLocation baseLocation) {
|
||||||
|
var props = block.getStateDefinition().getProperties();
|
||||||
|
List<ImmutableList<String>> 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<String> 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 <T extends Comparable<T>> String getValueName(Property<T> property, Comparable<?> value) {
|
||||||
|
return property.getName((T)value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -33,14 +33,18 @@ public class ForgeHooksClientMixin {
|
||||||
if(ModLoader.hasErrors())
|
if(ModLoader.hasErrors())
|
||||||
return;
|
return;
|
||||||
ModelEvent.ModifyBakingResult bakeEvent = ((ModelEvent.ModifyBakingResult)event);
|
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 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<String, Stopwatch> times = new Object2ObjectOpenHashMap<>();
|
Map<String, Stopwatch> times = new Object2ObjectOpenHashMap<>();
|
||||||
|
times.put("modernfix", selfTimer);
|
||||||
ModList.get().forEachModContainer((id, mc) -> {
|
ModList.get().forEachModContainer((id, mc) -> {
|
||||||
Map<ModelResourceLocation, BakedModel> newRegistry = helper.wrapRegistry(id);
|
Map<ModelResourceLocation, BakedModel> newRegistry = helper.wrapRegistry(id);
|
||||||
ModelEvent.ModifyBakingResult postedEvent = new ModelEvent.ModifyBakingResult(newRegistry, bakeEvent.getTextureGetter(), bakeEvent.getModelBakery());
|
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 {
|
try {
|
||||||
acceptEv.invoke(mc, postedEvent);
|
acceptEv.invoke(mc, postedEvent);
|
||||||
} catch(ReflectiveOperationException e) {
|
} catch(ReflectiveOperationException e) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user