Skip to content

Commit 40e8f7c

Browse files
committed
Dynamically load client item info & properties
1 parent 1873456 commit 40e8f7c

6 files changed

Lines changed: 152 additions & 29 deletions

File tree

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
2+
3+
import com.llamalad7.mixinextras.sugar.Local;
4+
import net.minecraft.client.multiplayer.ClientRegistryLayer;
5+
import net.minecraft.client.resources.model.ClientItemInfoLoader;
6+
import net.minecraft.core.RegistryAccess;
7+
import net.minecraft.resources.Identifier;
8+
import net.minecraft.server.packs.resources.Resource;
9+
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
10+
import org.embeddedt.modernfix.dynresources.DynamicModelSystem;
11+
import org.spongepowered.asm.mixin.Mixin;
12+
import org.spongepowered.asm.mixin.Shadow;
13+
import org.spongepowered.asm.mixin.injection.At;
14+
import org.spongepowered.asm.mixin.injection.ModifyArg;
15+
16+
import java.util.Map;
17+
import java.util.concurrent.CompletableFuture;
18+
import java.util.concurrent.CompletionStage;
19+
import java.util.function.Function;
20+
21+
@Mixin(ClientItemInfoLoader.class)
22+
@ClientOnlyMixin
23+
public abstract class MixinClientItemInfoLoader {
24+
@Shadow
25+
private static ClientItemInfoLoader.PendingLoad lambda$scheduleLoad$3(Identifier resourceId, Resource resource, RegistryAccess.Frozen registries) {
26+
throw new AssertionError();
27+
}
28+
29+
/**
30+
* @author embeddedt
31+
* @reason Load client item infos dynamically instead of all at once
32+
*/
33+
@ModifyArg(method = "scheduleLoad", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;thenCompose(Ljava/util/function/Function;)Ljava/util/concurrent/CompletableFuture;"))
34+
private static Function<Map<Identifier, Resource>, ? extends CompletionStage<ClientItemInfoLoader.LoadedClientInfos>> skipAOTClientItemLoad(
35+
Function<Map<Identifier, Resource>, ? extends CompletionStage<ClientItemInfoLoader.LoadedClientInfos>> original,
36+
@Local(ordinal = 0) RegistryAccess.Frozen staticRegistries) {
37+
return resourceMap -> CompletableFuture.completedFuture(DynamicModelSystem.createDynamicClientInfos(resourceMap, (resourceId, resource) -> {
38+
ClientItemInfoLoader.PendingLoad load = lambda$scheduleLoad$3(resourceId, resource, staticRegistries);
39+
return load.clientItemInfo();
40+
}));
41+
}
42+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
2+
3+
import net.minecraft.client.renderer.item.ItemModel;
4+
import net.minecraft.client.resources.model.ModelManager;
5+
import net.minecraft.resources.Identifier;
6+
import net.neoforged.neoforge.client.ClientNeoForgeMod;
7+
import org.embeddedt.modernfix.ModernFix;
8+
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
9+
import org.spongepowered.asm.mixin.Mixin;
10+
import org.spongepowered.asm.mixin.injection.At;
11+
import org.spongepowered.asm.mixin.injection.Redirect;
12+
13+
@Mixin(ClientNeoForgeMod.class)
14+
@ClientOnlyMixin
15+
public class MixinClientNeoForgeMod {
16+
/**
17+
* @author embeddedt
18+
* @reason avoid triggering eager load of every item model
19+
*/
20+
@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;"))
21+
private static ItemModel checkExistenceWithoutLoadingModel(ModelManager instance, Identifier id) {
22+
if (!((ModelManagerAccessor)instance).mfix$getBakedItemModels().containsKey(id)) {
23+
ModernFix.LOGGER.warn("Missing item model '{}'", id);
24+
}
25+
return null;
26+
}
27+
}

src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/MixinModelBakery.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,25 @@
11
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
22

3+
import com.google.common.collect.Maps;
4+
import com.llamalad7.mixinextras.sugar.Local;
5+
import com.llamalad7.mixinextras.sugar.ref.LocalRef;
6+
import net.minecraft.client.renderer.item.ClientItem;
37
import net.minecraft.client.resources.model.ModelBakery;
8+
import net.minecraft.resources.Identifier;
49
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
510
import org.embeddedt.modernfix.dynresources.DynamicModelSystem;
11+
import org.objectweb.asm.Opcodes;
612
import org.spongepowered.asm.mixin.Mixin;
713
import org.spongepowered.asm.mixin.injection.At;
814
import org.spongepowered.asm.mixin.injection.Constant;
915
import org.spongepowered.asm.mixin.injection.ModifyConstant;
1016
import org.spongepowered.asm.mixin.injection.Redirect;
17+
import org.spongepowered.asm.mixin.injection.Slice;
1118

1219
import java.util.Map;
1320
import java.util.concurrent.CompletableFuture;
1421
import java.util.concurrent.Executor;
22+
import java.util.function.BiConsumer;
1523
import java.util.function.BiFunction;
1624

1725
@Mixin(ModelBakery.class)
@@ -26,6 +34,21 @@ private <K, U, V> CompletableFuture<Map<K, V>> dynamicallyBake(Map<K, U> input,
2634
return CompletableFuture.completedFuture(DynamicModelSystem.createDynamicBakedRegistry(input, baker));
2735
}
2836

37+
@Redirect(method = "bakeModels",
38+
slice = @Slice(from = @At(value = "FIELD", target = "Lnet/minecraft/client/resources/model/ModelBakery;clientInfos:Ljava/util/Map;", opcode = Opcodes.GETFIELD, ordinal = 2)),
39+
at = @At(value = "INVOKE", target = "Ljava/util/Map;forEach(Ljava/util/function/BiConsumer;)V", ordinal = 0))
40+
private void dynamicItemProperties(Map<Identifier, ClientItem> clientItems, BiConsumer<? super Identifier, ? super ClientItem> action,
41+
@Local(name = "itemStackModelProperties") LocalRef<Map<Identifier, ClientItem.Properties>> modelProperties) {
42+
modelProperties.set(Maps.asMap(clientItems.keySet(), id -> {
43+
var item = clientItems.get(id);
44+
var props = ClientItem.Properties.DEFAULT;
45+
if (item != null && !props.equals(item.properties())) {
46+
props = item.properties();
47+
}
48+
return props;
49+
}));
50+
}
51+
2952
/**
3053
* @author embeddedt
3154
* @reason We want log4j to print the stacktrace and not just the exception message
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
2+
3+
import net.minecraft.client.renderer.item.ItemModel;
4+
import net.minecraft.client.resources.model.ModelManager;
5+
import net.minecraft.resources.Identifier;
6+
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
7+
import org.spongepowered.asm.mixin.Mixin;
8+
import org.spongepowered.asm.mixin.gen.Accessor;
9+
10+
import java.util.Map;
11+
12+
@Mixin(ModelManager.class)
13+
@ClientOnlyMixin
14+
public interface ModelManagerAccessor {
15+
@Accessor("bakedItemStackModels")
16+
Map<Identifier, ItemModel> mfix$getBakedItemModels();
17+
}

src/main/java/org/embeddedt/modernfix/dynresources/DynamicModelSystem.java

Lines changed: 41 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@
44
import com.google.common.cache.CacheLoader;
55
import com.google.common.cache.LoadingCache;
66
import com.google.common.collect.Maps;
7-
import com.google.common.collect.Sets;
87
import it.unimi.dsi.fastutil.objects.AbstractObject2IntMap;
98
import it.unimi.dsi.fastutil.objects.Object2IntMap;
109
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
1110
import it.unimi.dsi.fastutil.objects.ObjectSet;
1211
import it.unimi.dsi.fastutil.objects.ObjectSets;
13-
import it.unimi.dsi.fastutil.objects.ReferenceSets;
1412
import net.minecraft.client.color.block.BlockColors;
1513
import net.minecraft.client.resources.model.BlockStateModelLoader;
14+
import net.minecraft.client.renderer.item.ClientItem;
1615
import net.minecraft.client.resources.model.ClientItemInfoLoader;
16+
import org.jetbrains.annotations.Nullable;
1717
import net.minecraft.client.resources.model.ModelDiscovery;
1818
import net.minecraft.client.resources.model.ModelManager;
1919
import net.minecraft.client.resources.model.ResolvedModel;
@@ -38,35 +38,48 @@
3838
import java.util.Iterator;
3939
import java.util.List;
4040
import java.util.Map;
41+
import java.util.Objects;
4142
import java.util.Set;
4243
import java.util.function.BiFunction;
43-
import java.util.function.Function;
4444
import java.util.stream.Collectors;
4545

4646
public class DynamicModelSystem {
4747
private static final FileToIdConverter MODEL_LISTER = FileToIdConverter.json("models");
4848
private static final FileToIdConverter BLOCKSTATE_LISTER = FileToIdConverter.json("blockstates");
49+
private static final FileToIdConverter ITEM_LISTER = FileToIdConverter.json("items");
4950

5051
public static final boolean DEBUG_DYNAMIC_MODEL_LOADING = Boolean.getBoolean("modernfix.debugDynamicModelLoading");
51-
52-
public static Map<Identifier, UnbakedModel> createDynamicUnbakedModelMap(Map<Identifier, Resource> resourceMap) {
53-
LoadingCache<Identifier, UnbakedModel> unbakedModelCache = CacheBuilder.newBuilder().softValues().maximumSize(1000).build(new CacheLoader<>() {
52+
53+
private interface ResultLoader<RESOURCE, RESULT> {
54+
RESULT load(Identifier file, @Nullable RESOURCE resource) throws Exception;
55+
}
56+
57+
private static <RESOURCE, RESULT> Map<Identifier, RESULT> createCachedResourceBackedMap(Map<Identifier, RESOURCE> resourceMap,
58+
FileToIdConverter converter,
59+
String debugName,
60+
ResultLoader<RESOURCE, RESULT> loader) {
61+
LoadingCache<Identifier, RESULT> resultCache = CacheBuilder.newBuilder().softValues().maximumSize(1000).build(new CacheLoader<>() {
5462
@Override
55-
public UnbakedModel load(Identifier key) throws Exception {
56-
var resource = resourceMap.get(MODEL_LISTER.idToFile(key));
57-
if (resource == null) {
58-
throw new IllegalArgumentException("Model " + key + " does not exist in map");
59-
}
63+
public RESULT load(Identifier id) throws Exception {
64+
var file = converter.idToFile(id);
65+
var resource = resourceMap.get(file);
6066
if (DEBUG_DYNAMIC_MODEL_LOADING) {
61-
ModernFix.LOGGER.info("Loading unbaked model {}", key);
62-
}
63-
try (Reader reader = resource.openAsReader()) {
64-
return UnbakedModelParser.parse(reader);
67+
ModernFix.LOGGER.info("Loading {} {}", debugName, id);
6568
}
69+
return loader.load(file, resource);
70+
}
71+
});
72+
Set<Identifier> idSet = resourceMap.keySet().stream().map(converter::fileToId).collect(Collectors.toUnmodifiableSet());
73+
return Maps.asMap(idSet, key -> key != null ? resultCache.getUnchecked(key) : null);
74+
}
75+
76+
public static Map<Identifier, UnbakedModel> createDynamicUnbakedModelMap(Map<Identifier, Resource> resourceMap) {
77+
return createCachedResourceBackedMap(resourceMap, MODEL_LISTER, "unbaked model", (id, resource) -> {
78+
Objects.requireNonNull(resource, "unbaked model not present");
79+
try (Reader reader = resource.openAsReader()) {
80+
return UnbakedModelParser.parse(reader);
6681
}
6782
});
68-
Set<Identifier> unbakedIdSet = resourceMap.keySet().stream().map(MODEL_LISTER::fileToId).collect(Collectors.toUnmodifiableSet());
69-
return Maps.asMap(unbakedIdSet, key -> key != null ? unbakedModelCache.getUnchecked(key) : null);
7083
}
7184

7285
public interface SingleBlockStateEntryLoader {
@@ -96,17 +109,7 @@ public int size() {
96109
}
97110

98111
public static BlockStateModelLoader.LoadedModels createDynamicBlockStateLoadedModels(Map<Identifier, List<Resource>> resourceMap, SingleBlockStateEntryLoader entryLoader) {
99-
LoadingCache<Identifier, BlockStateModelLoader.LoadedModels> definitionCache = CacheBuilder.newBuilder().softValues().maximumSize(1000).build(new CacheLoader<>() {
100-
@Override
101-
public BlockStateModelLoader.LoadedModels load(Identifier key) throws Exception {
102-
if (DEBUG_DYNAMIC_MODEL_LOADING) {
103-
ModernFix.LOGGER.info("Loading blockstate definition for {}", key);
104-
}
105-
var file = BLOCKSTATE_LISTER.idToFile(key);
106-
var resources = resourceMap.getOrDefault(file, List.of());
107-
return entryLoader.loadEntry(file, resources);
108-
}
109-
});
112+
var blockStateDefinitions = createCachedResourceBackedMap(resourceMap, BLOCKSTATE_LISTER, "blockstate definition", entryLoader::loadEntry);
110113
var staticDefinitions = BlockStateDefinitionsAccessor.getStaticDefinitions();
111114
var staticIdentifiers = staticDefinitions.entrySet()
112115
.stream()
@@ -118,11 +121,20 @@ public BlockStateModelLoader.LoadedModels load(Identifier key) throws Exception
118121
if (identifier == null) {
119122
identifier = state.getBlock().builtInRegistryHolder().getKey().identifier();
120123
}
121-
var loadedModels = definitionCache.getUnchecked(identifier);
124+
var loadedModels = blockStateDefinitions.get(identifier);
122125
return loadedModels.models().get(state);
123126
}));
124127
}
125128

129+
public interface SingleClientItemEntryLoader {
130+
@Nullable ClientItem loadEntry(Identifier resourceId, Resource resource);
131+
}
132+
133+
public static ClientItemInfoLoader.LoadedClientInfos createDynamicClientInfos(Map<Identifier, Resource> resourceMap, SingleClientItemEntryLoader entryLoader) {
134+
var clientItems = createCachedResourceBackedMap(resourceMap, ITEM_LISTER, "client item info", entryLoader::loadEntry);
135+
return new ClientItemInfoLoader.LoadedClientInfos(clientItems);
136+
}
137+
126138
public record DynamicResolver(Map<Identifier, UnbakedModel> inputModels,
127139
BlockStateModelLoader.LoadedModels loadedModels,
128140
ClientItemInfoLoader.LoadedClientInfos loadedClientInfos,

src/main/resources/META-INF/accesstransformer.cfg

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,5 @@ public net.minecraft.client.resources.model.ModelManager$ResolvedModels <init>(L
7373
public net.minecraft.client.resources.model.ModelDiscovery$ModelWrapper
7474
public net.minecraft.client.resources.model.ModelDiscovery$ModelWrapper ModelWrapper(Lnet/minecraft/resources/Identifier;Lnet/minecraft/client/resources/model/UnbakedModel;Z)V
7575
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;
76+
public net.minecraft.client.resources.model.ClientItemInfoLoader$PendingLoad
77+
public net.minecraft.client.resources.model.ClientItemInfoLoader$PendingLoad <init>(Lnet/minecraft/resources/Identifier;Lnet/minecraft/client/renderer/item/ClientItem;)V

0 commit comments

Comments
 (0)