Skip to content

Commit 59e3b83

Browse files
committed
Default to showing the whole model registry to mods
This should fix many silent incompatibilities that existed with the original opt-in approach. To my knowledge, there are no remaining popular mods on 1.20 that forcefully load all models in this event (by iterating over entrySet and calling getValue unconditionally) so doing this should be safe. Additional logging is also added to provide quick insight into what mod(s) have the slowest handling of this event.
1 parent 4bdddf1 commit 59e3b83

File tree

2 files changed

+131
-78
lines changed

2 files changed

+131
-78
lines changed

forge/src/main/java/org/embeddedt/modernfix/forge/dynresources/ModelBakeEventHelper.java

Lines changed: 115 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
package org.embeddedt.modernfix.forge.dynresources;
22

33
import com.google.common.collect.ForwardingMap;
4-
import com.google.common.collect.ImmutableSet;
5-
import com.google.common.collect.Iterators;
4+
import com.google.common.collect.ImmutableMap;
65
import com.google.common.collect.Sets;
76
import com.google.common.graph.GraphBuilder;
87
import com.google.common.graph.MutableGraph;
@@ -11,19 +10,20 @@
1110
import net.minecraft.client.resources.model.BakedModel;
1211
import net.minecraft.client.resources.model.ModelBakery;
1312
import net.minecraft.client.resources.model.ModelResourceLocation;
13+
import net.minecraft.core.registries.BuiltInRegistries;
1414
import net.minecraft.resources.ResourceLocation;
1515
import net.minecraft.world.level.block.state.BlockState;
1616
import net.minecraftforge.fml.ModContainer;
1717
import net.minecraftforge.fml.ModList;
1818
import net.minecraftforge.forgespi.language.IModInfo;
19-
import net.minecraftforge.registries.ForgeRegistries;
2019
import org.embeddedt.modernfix.ModernFix;
2120
import org.embeddedt.modernfix.util.ForwardingInclDefaultsMap;
2221
import org.jetbrains.annotations.Nullable;
2322

2423
import java.util.AbstractSet;
2524
import java.util.ArrayList;
2625
import java.util.Collection;
26+
import java.util.Collections;
2727
import java.util.HashSet;
2828
import java.util.Iterator;
2929
import java.util.List;
@@ -38,50 +38,59 @@
3838
* of the model registry that emulates vanilla keySet behavior.
3939
*/
4040
public class ModelBakeEventHelper {
41-
// TODO: make into config option
42-
private static final Set<String> INCOMPATIBLE_MODS = ImmutableSet.of(
43-
"industrialforegoing",
44-
"mekanism",
45-
"vampirism",
46-
"elevatorid",
47-
"cfm",
48-
"refinedstorage",
49-
"embers",
50-
"buildcraftsilicon",
51-
"buildcrafttransport",
52-
"buildcraftfactory");
41+
private enum UniverseVisibility {
42+
/**
43+
* Mod cannot see any view of the universe of model locations.
44+
*/
45+
NONE,
46+
/**
47+
* Mod can see its own model locations and those of dependencies/dependents.
48+
*/
49+
SELF_AND_DEPS,
50+
/**
51+
* Mod can see every model location.
52+
*/
53+
EVERYTHING
54+
}
55+
private static final Map<String, UniverseVisibility> MOD_VISIBILITY_CONFIGURATION = ImmutableMap.<String, UniverseVisibility>builder()
56+
.build();
5357
private final Map<ResourceLocation, BakedModel> modelRegistry;
5458
private final Set<ResourceLocation> topLevelModelLocations;
5559
private final MutableGraph<String> dependencyGraph;
5660
public ModelBakeEventHelper(Map<ResourceLocation, BakedModel> modelRegistry) {
5761
this.modelRegistry = modelRegistry;
5862
this.topLevelModelLocations = new ObjectLinkedOpenHashSet<>();
5963
// Skip going through ModelLocationCache because most of the accesses will be misses
60-
ForgeRegistries.BLOCKS.getEntries().forEach(entry -> {
64+
BuiltInRegistries.BLOCK.entrySet().forEach(entry -> {
6165
var location = entry.getKey().location();
6266
for(BlockState state : entry.getValue().getStateDefinition().getPossibleStates()) {
6367
topLevelModelLocations.add(BlockModelShaper.stateToModelLocation(location, state));
6468
}
6569
});
66-
ForgeRegistries.ITEMS.getKeys().forEach(key -> topLevelModelLocations.add(new ModelResourceLocation(key, "inventory")));
70+
BuiltInRegistries.ITEM.keySet().forEach(key -> topLevelModelLocations.add(new ModelResourceLocation(key, "inventory")));
6771
this.topLevelModelLocations.addAll(modelRegistry.keySet());
68-
this.dependencyGraph = GraphBuilder.undirected().build();
72+
this.dependencyGraph = buildDependencyGraph();
73+
}
74+
75+
private static MutableGraph<String> buildDependencyGraph() {
76+
MutableGraph<String> dependencyGraph = GraphBuilder.undirected().build();
6977
ModList.get().forEachModContainer((id, mc) -> {
70-
this.dependencyGraph.addNode(id);
78+
dependencyGraph.addNode(id);
7179
for(IModInfo.ModVersion version : mc.getModInfo().getDependencies()) {
72-
this.dependencyGraph.addNode(version.getModId());
80+
dependencyGraph.addNode(version.getModId());
7381
}
7482
});
75-
for(String id : this.dependencyGraph.nodes()) {
83+
for(String id : dependencyGraph.nodes()) {
7684
Optional<? extends ModContainer> mContainer = ModList.get().getModContainerById(id);
7785
if(mContainer.isPresent()) {
7886
for(IModInfo.ModVersion version : mContainer.get().getModInfo().getDependencies()) {
7987
// avoid self-loops
8088
if(!Objects.equals(id, version.getModId()))
81-
this.dependencyGraph.putEdge(id, version.getModId());
89+
dependencyGraph.putEdge(id, version.getModId());
8290
}
8391
}
8492
}
93+
return dependencyGraph;
8594
}
8695

8796
private static final Set<String> WARNED_MOD_IDS = new HashSet<>();
@@ -132,73 +141,94 @@ public void replaceAll(BiFunction<? super ResourceLocation, ? super BakedModel,
132141
}
133142

134143
public Map<ResourceLocation, BakedModel> wrapRegistry(String modId) {
144+
var config = MOD_VISIBILITY_CONFIGURATION.getOrDefault(modId, UniverseVisibility.EVERYTHING);
145+
if (config == UniverseVisibility.NONE) {
146+
return createWarningRegistry(modId);
147+
}
135148
final Set<String> modIdsToInclude = new HashSet<>();
136149
modIdsToInclude.add(modId);
137150
try {
138151
modIdsToInclude.addAll(this.dependencyGraph.adjacentNodes(modId));
139152
} catch(IllegalArgumentException ignored) { /* sanity check */ }
140153
modIdsToInclude.remove("minecraft");
141-
if(modIdsToInclude.stream().noneMatch(INCOMPATIBLE_MODS::contains))
142-
return createWarningRegistry(modId);
143-
Set<ResourceLocation> ourModelLocations = Sets.filter(this.topLevelModelLocations, loc -> modIdsToInclude.contains(loc.getNamespace()));
154+
Set<ResourceLocation> ourModelLocations;
155+
if (config == UniverseVisibility.SELF_AND_DEPS) {
156+
ourModelLocations = Sets.filter(this.topLevelModelLocations, loc -> modIdsToInclude.contains(loc.getNamespace()));
157+
} else {
158+
ourModelLocations = this.topLevelModelLocations;
159+
}
144160
BakedModel missingModel = modelRegistry.get(ModelBakery.MISSING_MODEL_LOCATION);
145-
return new ForwardingMap<ResourceLocation, BakedModel>() {
146-
@Override
147-
protected Map<ResourceLocation, BakedModel> delegate() {
148-
return modelRegistry;
149-
}
161+
return new EmulatedModelRegistry(modId, modIdsToInclude, missingModel, ourModelLocations);
162+
}
150163

151-
@Override
152-
public BakedModel get(@Nullable Object key) {
153-
BakedModel model = super.get(key);
154-
if(model == null && key != null && modIdsToInclude.contains(((ResourceLocation)key).getNamespace())) {
155-
ModernFix.LOGGER.warn("Model {} is missing, but was requested in model bake event. Returning missing model", key);
156-
return missingModel;
157-
}
158-
return model;
159-
}
164+
public class EmulatedModelRegistry extends ForwardingMap<ResourceLocation, BakedModel> {
165+
private final Set<String> modIdsToInclude;
166+
private final BakedModel missingModel;
167+
private final Set<ResourceLocation> ourModelLocations;
168+
private final String modId;
160169

161-
@Override
162-
public Set<ResourceLocation> keySet() {
163-
return ourModelLocations;
164-
}
170+
private EmulatedModelRegistry(String modId, Set<String> modIdsToInclude, BakedModel missingModel, Set<ResourceLocation> ourModelLocations) {
171+
this.modId = modId;
172+
this.modIdsToInclude = modIdsToInclude;
173+
this.missingModel = missingModel;
174+
this.ourModelLocations = ourModelLocations;
175+
}
165176

166-
@Override
167-
public boolean containsKey(@Nullable Object key) {
168-
return ourModelLocations.contains(key) || super.containsKey(key);
169-
}
177+
@Override
178+
protected Map<ResourceLocation, BakedModel> delegate() {
179+
return modelRegistry;
180+
}
170181

171-
@Override
172-
public Set<Entry<ResourceLocation, BakedModel>> entrySet() {
173-
return new DynamicModelEntrySet(this, ourModelLocations);
182+
@Override
183+
public BakedModel get(@Nullable Object key) {
184+
BakedModel model = super.get(key);
185+
if(model == null && key != null && modIdsToInclude.contains(((ResourceLocation)key).getNamespace())) {
186+
ModernFix.LOGGER.warn("Model {} is missing, but was requested in model bake event. Returning missing model", key);
187+
return missingModel;
174188
}
189+
return model;
190+
}
175191

176-
@Override
177-
public void replaceAll(BiFunction<? super ResourceLocation, ? super BakedModel, ? extends BakedModel> function) {
178-
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);
179-
List<ResourceLocation> locations = new ArrayList<>(keySet());
180-
for(ResourceLocation location : locations) {
181-
/*
182-
* Fetching every model is insanely slow. So we call the function with a null object first, since it
183-
* probably isn't expecting that. If we get an exception thrown, or it returns nonnull, then we know
184-
* it actually cares about the given model.
185-
*/
186-
boolean needsReplacement;
187-
try {
188-
needsReplacement = function.apply(location, null) != null;
189-
} catch(Throwable e) {
190-
needsReplacement = true;
191-
}
192-
if(needsReplacement) {
193-
BakedModel existing = get(location);
194-
BakedModel replacement = function.apply(location, existing);
195-
if(replacement != existing) {
196-
put(location, replacement);
197-
}
192+
@Override
193+
public Set<ResourceLocation> keySet() {
194+
return Collections.unmodifiableSet(ourModelLocations);
195+
}
196+
197+
@Override
198+
public boolean containsKey(@Nullable Object key) {
199+
return ourModelLocations.contains(key) || super.containsKey(key);
200+
}
201+
202+
@Override
203+
public Set<Entry<ResourceLocation, BakedModel>> entrySet() {
204+
return new DynamicModelEntrySet(this, ourModelLocations);
205+
}
206+
207+
@Override
208+
public void replaceAll(BiFunction<? super ResourceLocation, ? super BakedModel, ? extends BakedModel> function) {
209+
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);
210+
List<ResourceLocation> locations = new ArrayList<>(ourModelLocations);
211+
for(ResourceLocation location : locations) {
212+
/*
213+
* Fetching every model is insanely slow. So we call the function with a null object first, since it
214+
* probably isn't expecting that. If we get an exception thrown, or it returns nonnull, then we know
215+
* it actually cares about the given model.
216+
*/
217+
boolean needsReplacement;
218+
try {
219+
needsReplacement = function.apply(location, null) != null;
220+
} catch(Throwable e) {
221+
needsReplacement = true;
222+
}
223+
if(needsReplacement) {
224+
BakedModel existing = get(location);
225+
BakedModel replacement = function.apply(location, existing);
226+
if(replacement != existing) {
227+
put(location, replacement);
198228
}
199229
}
200230
}
201-
};
231+
}
202232
}
203233

204234
private static class DynamicModelEntrySet extends AbstractSet<Map.Entry<ResourceLocation, BakedModel>> {
@@ -212,7 +242,18 @@ private DynamicModelEntrySet(Map<ResourceLocation, BakedModel> modelRegistry, Se
212242

213243
@Override
214244
public Iterator<Map.Entry<ResourceLocation, BakedModel>> iterator() {
215-
return Iterators.transform(Iterators.unmodifiableIterator(this.modelLocations.iterator()), DynamicModelEntry::new);
245+
var iter = this.modelLocations.iterator();
246+
return new Iterator<>() {
247+
@Override
248+
public boolean hasNext() {
249+
return iter.hasNext();
250+
}
251+
252+
@Override
253+
public Map.Entry<ResourceLocation, BakedModel> next() {
254+
return new DynamicModelEntry(iter.next());
255+
}
256+
};
216257
}
217258

218259
@Override

forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/dynamic_resources/ForgeHooksClientMixin.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.embeddedt.modernfix.forge.mixin.perf.dynamic_resources;
22

33
import com.google.common.base.Stopwatch;
4+
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
45
import net.minecraft.client.resources.model.BakedModel;
56
import net.minecraft.resources.ResourceLocation;
67
import net.minecraftforge.client.ForgeHooksClient;
@@ -17,6 +18,8 @@
1718
import org.spongepowered.asm.mixin.injection.Redirect;
1819

1920
import java.lang.reflect.Method;
21+
import java.time.Duration;
22+
import java.util.Comparator;
2023
import java.util.Map;
2124
import java.util.concurrent.TimeUnit;
2225

@@ -32,19 +35,28 @@ private static void postNamespacedKeySetEvent(ModLoader loader, Event event) {
3235
ModelEvent.ModifyBakingResult bakeEvent = ((ModelEvent.ModifyBakingResult)event);
3336
ModelBakeEventHelper helper = new ModelBakeEventHelper(bakeEvent.getModels());
3437
Method acceptEv = ObfuscationReflectionHelper.findMethod(ModContainer.class, "acceptEvent", Event.class);
38+
Stopwatch globalTimer = Stopwatch.createStarted();
39+
Map<String, Stopwatch> times = new Object2ObjectOpenHashMap<>();
3540
ModList.get().forEachModContainer((id, mc) -> {
3641
Map<ResourceLocation, BakedModel> newRegistry = helper.wrapRegistry(id);
3742
ModelEvent.ModifyBakingResult postedEvent = new ModelEvent.ModifyBakingResult(newRegistry, bakeEvent.getModelBakery());
38-
Stopwatch timer = Stopwatch.createStarted();
43+
Stopwatch timer = times.computeIfAbsent(id, $ -> Stopwatch.createStarted());
3944
try {
4045
acceptEv.invoke(mc, postedEvent);
4146
} catch(ReflectiveOperationException e) {
4247
e.printStackTrace();
4348
}
4449
timer.stop();
45-
if(timer.elapsed(TimeUnit.SECONDS) >= 1) {
46-
ModernFix.LOGGER.warn("Mod '{}' took {} in the model bake event", id, timer);
47-
}
4850
});
51+
globalTimer.stop();
52+
if (globalTimer.elapsed(TimeUnit.SECONDS) >= 1) {
53+
ModernFix.LOGGER.warn("Posting dynamic ModelEvent.ModifyBakingResult to mods took {}, breakdown below:", globalTimer);
54+
times.entrySet().stream()
55+
.sorted(Comparator.<Map.Entry<String, Stopwatch>, Duration>comparing(e -> e.getValue().elapsed()).reversed())
56+
.filter(e -> e.getValue().elapsed(TimeUnit.MILLISECONDS) > 50)
57+
.forEach(entry -> {
58+
ModernFix.LOGGER.warn(" {}: {}", entry.getKey(), entry.getValue().toString());
59+
});
60+
}
4961
}
5062
}

0 commit comments

Comments
 (0)