Dynamically load client item info & properties
This commit is contained in:
parent
18734563d6
commit
40e8f7ccec
|
|
@ -0,0 +1,42 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.minecraft.client.multiplayer.ClientRegistryLayer;
|
||||
import net.minecraft.client.resources.model.ClientItemInfoLoader;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.server.packs.resources.Resource;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.embeddedt.modernfix.dynresources.DynamicModelSystem;
|
||||
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.ModifyArg;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.function.Function;
|
||||
|
||||
@Mixin(ClientItemInfoLoader.class)
|
||||
@ClientOnlyMixin
|
||||
public abstract class MixinClientItemInfoLoader {
|
||||
@Shadow
|
||||
private static ClientItemInfoLoader.PendingLoad lambda$scheduleLoad$3(Identifier resourceId, Resource resource, RegistryAccess.Frozen registries) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason Load client item infos dynamically instead of all at once
|
||||
*/
|
||||
@ModifyArg(method = "scheduleLoad", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;thenCompose(Ljava/util/function/Function;)Ljava/util/concurrent/CompletableFuture;"))
|
||||
private static Function<Map<Identifier, Resource>, ? extends CompletionStage<ClientItemInfoLoader.LoadedClientInfos>> skipAOTClientItemLoad(
|
||||
Function<Map<Identifier, Resource>, ? extends CompletionStage<ClientItemInfoLoader.LoadedClientInfos>> original,
|
||||
@Local(ordinal = 0) RegistryAccess.Frozen staticRegistries) {
|
||||
return resourceMap -> CompletableFuture.completedFuture(DynamicModelSystem.createDynamicClientInfos(resourceMap, (resourceId, resource) -> {
|
||||
ClientItemInfoLoader.PendingLoad load = lambda$scheduleLoad$3(resourceId, resource, staticRegistries);
|
||||
return load.clientItemInfo();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
|
||||
|
||||
import net.minecraft.client.renderer.item.ItemModel;
|
||||
import net.minecraft.client.resources.model.ModelManager;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.neoforged.neoforge.client.ClientNeoForgeMod;
|
||||
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.Redirect;
|
||||
|
||||
@Mixin(ClientNeoForgeMod.class)
|
||||
@ClientOnlyMixin
|
||||
public class MixinClientNeoForgeMod {
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason avoid triggering eager load of every item model
|
||||
*/
|
||||
@Redirect(method = "lambda$new$7", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/ModelManager;getItemModel(Lnet/minecraft/resources/Identifier;)Lnet/minecraft/client/renderer/item/ItemModel;"))
|
||||
private static ItemModel checkExistenceWithoutLoadingModel(ModelManager instance, Identifier id) {
|
||||
if (!((ModelManagerAccessor)instance).mfix$getBakedItemModels().containsKey(id)) {
|
||||
ModernFix.LOGGER.warn("Missing item model '{}'", id);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +1,25 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.llamalad7.mixinextras.sugar.ref.LocalRef;
|
||||
import net.minecraft.client.renderer.item.ClientItem;
|
||||
import net.minecraft.client.resources.model.ModelBakery;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.embeddedt.modernfix.dynresources.DynamicModelSystem;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Constant;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyConstant;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.Slice;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
@Mixin(ModelBakery.class)
|
||||
|
|
@ -26,6 +34,21 @@ public class MixinModelBakery {
|
|||
return CompletableFuture.completedFuture(DynamicModelSystem.createDynamicBakedRegistry(input, baker));
|
||||
}
|
||||
|
||||
@Redirect(method = "bakeModels",
|
||||
slice = @Slice(from = @At(value = "FIELD", target = "Lnet/minecraft/client/resources/model/ModelBakery;clientInfos:Ljava/util/Map;", opcode = Opcodes.GETFIELD, ordinal = 2)),
|
||||
at = @At(value = "INVOKE", target = "Ljava/util/Map;forEach(Ljava/util/function/BiConsumer;)V", ordinal = 0))
|
||||
private void dynamicItemProperties(Map<Identifier, ClientItem> clientItems, BiConsumer<? super Identifier, ? super ClientItem> action,
|
||||
@Local(name = "itemStackModelProperties") LocalRef<Map<Identifier, ClientItem.Properties>> modelProperties) {
|
||||
modelProperties.set(Maps.asMap(clientItems.keySet(), id -> {
|
||||
var item = clientItems.get(id);
|
||||
var props = ClientItem.Properties.DEFAULT;
|
||||
if (item != null && !props.equals(item.properties())) {
|
||||
props = item.properties();
|
||||
}
|
||||
return props;
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason We want log4j to print the stacktrace and not just the exception message
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
|
||||
|
||||
import net.minecraft.client.renderer.item.ItemModel;
|
||||
import net.minecraft.client.resources.model.ModelManager;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Mixin(ModelManager.class)
|
||||
@ClientOnlyMixin
|
||||
public interface ModelManagerAccessor {
|
||||
@Accessor("bakedItemStackModels")
|
||||
Map<Identifier, ItemModel> mfix$getBakedItemModels();
|
||||
}
|
||||
|
|
@ -4,16 +4,16 @@ import com.google.common.cache.CacheBuilder;
|
|||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import it.unimi.dsi.fastutil.objects.AbstractObject2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectSet;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectSets;
|
||||
import it.unimi.dsi.fastutil.objects.ReferenceSets;
|
||||
import net.minecraft.client.color.block.BlockColors;
|
||||
import net.minecraft.client.resources.model.BlockStateModelLoader;
|
||||
import net.minecraft.client.renderer.item.ClientItem;
|
||||
import net.minecraft.client.resources.model.ClientItemInfoLoader;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import net.minecraft.client.resources.model.ModelDiscovery;
|
||||
import net.minecraft.client.resources.model.ModelManager;
|
||||
import net.minecraft.client.resources.model.ResolvedModel;
|
||||
|
|
@ -38,35 +38,48 @@ import java.util.AbstractSet;
|
|||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class DynamicModelSystem {
|
||||
private static final FileToIdConverter MODEL_LISTER = FileToIdConverter.json("models");
|
||||
private static final FileToIdConverter BLOCKSTATE_LISTER = FileToIdConverter.json("blockstates");
|
||||
private static final FileToIdConverter ITEM_LISTER = FileToIdConverter.json("items");
|
||||
|
||||
public static final boolean DEBUG_DYNAMIC_MODEL_LOADING = Boolean.getBoolean("modernfix.debugDynamicModelLoading");
|
||||
|
||||
public static Map<Identifier, UnbakedModel> createDynamicUnbakedModelMap(Map<Identifier, Resource> resourceMap) {
|
||||
LoadingCache<Identifier, UnbakedModel> unbakedModelCache = CacheBuilder.newBuilder().softValues().maximumSize(1000).build(new CacheLoader<>() {
|
||||
|
||||
private interface ResultLoader<RESOURCE, RESULT> {
|
||||
RESULT load(Identifier file, @Nullable RESOURCE resource) throws Exception;
|
||||
}
|
||||
|
||||
private static <RESOURCE, RESULT> Map<Identifier, RESULT> createCachedResourceBackedMap(Map<Identifier, RESOURCE> resourceMap,
|
||||
FileToIdConverter converter,
|
||||
String debugName,
|
||||
ResultLoader<RESOURCE, RESULT> loader) {
|
||||
LoadingCache<Identifier, RESULT> resultCache = CacheBuilder.newBuilder().softValues().maximumSize(1000).build(new CacheLoader<>() {
|
||||
@Override
|
||||
public UnbakedModel load(Identifier key) throws Exception {
|
||||
var resource = resourceMap.get(MODEL_LISTER.idToFile(key));
|
||||
if (resource == null) {
|
||||
throw new IllegalArgumentException("Model " + key + " does not exist in map");
|
||||
}
|
||||
public RESULT load(Identifier id) throws Exception {
|
||||
var file = converter.idToFile(id);
|
||||
var resource = resourceMap.get(file);
|
||||
if (DEBUG_DYNAMIC_MODEL_LOADING) {
|
||||
ModernFix.LOGGER.info("Loading unbaked model {}", key);
|
||||
}
|
||||
try (Reader reader = resource.openAsReader()) {
|
||||
return UnbakedModelParser.parse(reader);
|
||||
ModernFix.LOGGER.info("Loading {} {}", debugName, id);
|
||||
}
|
||||
return loader.load(file, resource);
|
||||
}
|
||||
});
|
||||
Set<Identifier> idSet = resourceMap.keySet().stream().map(converter::fileToId).collect(Collectors.toUnmodifiableSet());
|
||||
return Maps.asMap(idSet, key -> key != null ? resultCache.getUnchecked(key) : null);
|
||||
}
|
||||
|
||||
public static Map<Identifier, UnbakedModel> createDynamicUnbakedModelMap(Map<Identifier, Resource> resourceMap) {
|
||||
return createCachedResourceBackedMap(resourceMap, MODEL_LISTER, "unbaked model", (id, resource) -> {
|
||||
Objects.requireNonNull(resource, "unbaked model not present");
|
||||
try (Reader reader = resource.openAsReader()) {
|
||||
return UnbakedModelParser.parse(reader);
|
||||
}
|
||||
});
|
||||
Set<Identifier> unbakedIdSet = resourceMap.keySet().stream().map(MODEL_LISTER::fileToId).collect(Collectors.toUnmodifiableSet());
|
||||
return Maps.asMap(unbakedIdSet, key -> key != null ? unbakedModelCache.getUnchecked(key) : null);
|
||||
}
|
||||
|
||||
public interface SingleBlockStateEntryLoader {
|
||||
|
|
@ -96,17 +109,7 @@ public class DynamicModelSystem {
|
|||
}
|
||||
|
||||
public static BlockStateModelLoader.LoadedModels createDynamicBlockStateLoadedModels(Map<Identifier, List<Resource>> resourceMap, SingleBlockStateEntryLoader entryLoader) {
|
||||
LoadingCache<Identifier, BlockStateModelLoader.LoadedModels> definitionCache = CacheBuilder.newBuilder().softValues().maximumSize(1000).build(new CacheLoader<>() {
|
||||
@Override
|
||||
public BlockStateModelLoader.LoadedModels load(Identifier key) throws Exception {
|
||||
if (DEBUG_DYNAMIC_MODEL_LOADING) {
|
||||
ModernFix.LOGGER.info("Loading blockstate definition for {}", key);
|
||||
}
|
||||
var file = BLOCKSTATE_LISTER.idToFile(key);
|
||||
var resources = resourceMap.getOrDefault(file, List.of());
|
||||
return entryLoader.loadEntry(file, resources);
|
||||
}
|
||||
});
|
||||
var blockStateDefinitions = createCachedResourceBackedMap(resourceMap, BLOCKSTATE_LISTER, "blockstate definition", entryLoader::loadEntry);
|
||||
var staticDefinitions = BlockStateDefinitionsAccessor.getStaticDefinitions();
|
||||
var staticIdentifiers = staticDefinitions.entrySet()
|
||||
.stream()
|
||||
|
|
@ -118,11 +121,20 @@ public class DynamicModelSystem {
|
|||
if (identifier == null) {
|
||||
identifier = state.getBlock().builtInRegistryHolder().getKey().identifier();
|
||||
}
|
||||
var loadedModels = definitionCache.getUnchecked(identifier);
|
||||
var loadedModels = blockStateDefinitions.get(identifier);
|
||||
return loadedModels.models().get(state);
|
||||
}));
|
||||
}
|
||||
|
||||
public interface SingleClientItemEntryLoader {
|
||||
@Nullable ClientItem loadEntry(Identifier resourceId, Resource resource);
|
||||
}
|
||||
|
||||
public static ClientItemInfoLoader.LoadedClientInfos createDynamicClientInfos(Map<Identifier, Resource> resourceMap, SingleClientItemEntryLoader entryLoader) {
|
||||
var clientItems = createCachedResourceBackedMap(resourceMap, ITEM_LISTER, "client item info", entryLoader::loadEntry);
|
||||
return new ClientItemInfoLoader.LoadedClientInfos(clientItems);
|
||||
}
|
||||
|
||||
public record DynamicResolver(Map<Identifier, UnbakedModel> inputModels,
|
||||
BlockStateModelLoader.LoadedModels loadedModels,
|
||||
ClientItemInfoLoader.LoadedClientInfos loadedClientInfos,
|
||||
|
|
|
|||
|
|
@ -73,3 +73,5 @@ public net.minecraft.client.resources.model.ModelManager$ResolvedModels <init>(L
|
|||
public net.minecraft.client.resources.model.ModelDiscovery$ModelWrapper
|
||||
public net.minecraft.client.resources.model.ModelDiscovery$ModelWrapper ModelWrapper(Lnet/minecraft/resources/Identifier;Lnet/minecraft/client/resources/model/UnbakedModel;Z)V
|
||||
public net.minecraft.client.resources.model.ModelDiscovery createAndQueueWrapper(Lnet/minecraft/resources/Identifier;Lnet/minecraft/client/resources/model/UnbakedModel;)Lnet/minecraft/client/resources/model/ModelDiscovery$ModelWrapper;
|
||||
public net.minecraft.client.resources.model.ClientItemInfoLoader$PendingLoad
|
||||
public net.minecraft.client.resources.model.ClientItemInfoLoader$PendingLoad <init>(Lnet/minecraft/resources/Identifier;Lnet/minecraft/client/renderer/item/ClientItem;)V
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user