diff --git a/forge/src/main/java/org/embeddedt/modernfix/forge/dynresources/ModelBakeEventHelper.java b/forge/src/main/java/org/embeddedt/modernfix/forge/dynresources/ModelBakeEventHelper.java index c1a36d27..9a15f31d 100644 --- a/forge/src/main/java/org/embeddedt/modernfix/forge/dynresources/ModelBakeEventHelper.java +++ b/forge/src/main/java/org/embeddedt/modernfix/forge/dynresources/ModelBakeEventHelper.java @@ -1,8 +1,7 @@ package org.embeddedt.modernfix.forge.dynresources; import com.google.common.collect.ForwardingMap; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterators; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets; import com.google.common.graph.GraphBuilder; import com.google.common.graph.MutableGraph; @@ -11,12 +10,12 @@ 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.ResourceLocation; import net.minecraft.world.level.block.state.BlockState; import net.minecraftforge.fml.ModContainer; import net.minecraftforge.fml.ModList; import net.minecraftforge.forgespi.language.IModInfo; -import net.minecraftforge.registries.ForgeRegistries; import org.embeddedt.modernfix.ModernFix; import org.embeddedt.modernfix.util.ForwardingInclDefaultsMap; import org.jetbrains.annotations.Nullable; @@ -24,6 +23,7 @@ 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; @@ -38,18 +38,22 @@ import java.util.function.BiFunction; * of the model registry that emulates vanilla keySet behavior. */ public class ModelBakeEventHelper { - // TODO: make into config option - private static final Set INCOMPATIBLE_MODS = ImmutableSet.of( - "industrialforegoing", - "mekanism", - "vampirism", - "elevatorid", - "cfm", - "refinedstorage", - "embers", - "buildcraftsilicon", - "buildcrafttransport", - "buildcraftfactory"); + private enum UniverseVisibility { + /** + * Mod cannot see any view of the universe of model locations. + */ + NONE, + /** + * Mod can see its own model locations and those of dependencies/dependents. + */ + SELF_AND_DEPS, + /** + * Mod can see every model location. + */ + EVERYTHING + } + private static final Map MOD_VISIBILITY_CONFIGURATION = ImmutableMap.builder() + .build(); private final Map modelRegistry; private final Set topLevelModelLocations; private final MutableGraph dependencyGraph; @@ -57,31 +61,36 @@ public class ModelBakeEventHelper { this.modelRegistry = modelRegistry; this.topLevelModelLocations = new ObjectLinkedOpenHashSet<>(); // Skip going through ModelLocationCache because most of the accesses will be misses - ForgeRegistries.BLOCKS.getEntries().forEach(entry -> { + BuiltInRegistries.BLOCK.entrySet().forEach(entry -> { var location = entry.getKey().location(); for(BlockState state : entry.getValue().getStateDefinition().getPossibleStates()) { topLevelModelLocations.add(BlockModelShaper.stateToModelLocation(location, state)); } }); - ForgeRegistries.ITEMS.getKeys().forEach(key -> topLevelModelLocations.add(new ModelResourceLocation(key, "inventory"))); + BuiltInRegistries.ITEM.keySet().forEach(key -> topLevelModelLocations.add(new ModelResourceLocation(key, "inventory"))); this.topLevelModelLocations.addAll(modelRegistry.keySet()); - this.dependencyGraph = GraphBuilder.undirected().build(); + this.dependencyGraph = buildDependencyGraph(); + } + + private static MutableGraph buildDependencyGraph() { + MutableGraph dependencyGraph = GraphBuilder.undirected().build(); ModList.get().forEachModContainer((id, mc) -> { - this.dependencyGraph.addNode(id); + dependencyGraph.addNode(id); for(IModInfo.ModVersion version : mc.getModInfo().getDependencies()) { - this.dependencyGraph.addNode(version.getModId()); + dependencyGraph.addNode(version.getModId()); } }); - for(String id : this.dependencyGraph.nodes()) { + for(String id : dependencyGraph.nodes()) { Optional mContainer = ModList.get().getModContainerById(id); if(mContainer.isPresent()) { for(IModInfo.ModVersion version : mContainer.get().getModInfo().getDependencies()) { // avoid self-loops if(!Objects.equals(id, version.getModId())) - this.dependencyGraph.putEdge(id, version.getModId()); + dependencyGraph.putEdge(id, version.getModId()); } } } + return dependencyGraph; } private static final Set WARNED_MOD_IDS = new HashSet<>(); @@ -132,73 +141,94 @@ public class ModelBakeEventHelper { } 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"); - if(modIdsToInclude.stream().noneMatch(INCOMPATIBLE_MODS::contains)) - return createWarningRegistry(modId); - Set ourModelLocations = Sets.filter(this.topLevelModelLocations, loc -> modIdsToInclude.contains(loc.getNamespace())); + Set ourModelLocations; + if (config == UniverseVisibility.SELF_AND_DEPS) { + ourModelLocations = Sets.filter(this.topLevelModelLocations, loc -> modIdsToInclude.contains(loc.getNamespace())); + } else { + ourModelLocations = this.topLevelModelLocations; + } BakedModel missingModel = modelRegistry.get(ModelBakery.MISSING_MODEL_LOCATION); - return new ForwardingMap() { - @Override - protected Map delegate() { - return modelRegistry; - } + return new EmulatedModelRegistry(modId, modIdsToInclude, missingModel, ourModelLocations); + } - @Override - public BakedModel get(@Nullable Object key) { - BakedModel model = super.get(key); - if(model == null && key != null && modIdsToInclude.contains(((ResourceLocation)key).getNamespace())) { - ModernFix.LOGGER.warn("Model {} is missing, but was requested in model bake event. Returning missing model", key); - return missingModel; + public class EmulatedModelRegistry extends ForwardingMap { + private final Set modIdsToInclude; + private final BakedModel missingModel; + private final Set ourModelLocations; + private final String modId; + + private EmulatedModelRegistry(String modId, Set modIdsToInclude, BakedModel missingModel, Set ourModelLocations) { + this.modId = modId; + this.modIdsToInclude = modIdsToInclude; + this.missingModel = missingModel; + this.ourModelLocations = ourModelLocations; + } + + @Override + protected Map delegate() { + return modelRegistry; + } + + @Override + public BakedModel get(@Nullable Object key) { + BakedModel model = super.get(key); + if(model == null && key != null && modIdsToInclude.contains(((ResourceLocation)key).getNamespace())) { + ModernFix.LOGGER.warn("Model {} is missing, but was requested in model bake event. Returning missing model", key); + return missingModel; + } + return model; + } + + @Override + public Set keySet() { + return Collections.unmodifiableSet(ourModelLocations); + } + + @Override + public boolean containsKey(@Nullable Object key) { + return ourModelLocations.contains(key) || super.containsKey(key); + } + + @Override + public Set> entrySet() { + return new DynamicModelEntrySet(this, ourModelLocations); + } + + @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(ResourceLocation location : locations) { + /* + * 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 + * it actually cares about the given model. + */ + boolean needsReplacement; + try { + needsReplacement = function.apply(location, null) != null; + } catch(Throwable e) { + needsReplacement = true; } - return model; - } - - @Override - public Set keySet() { - return ourModelLocations; - } - - @Override - public boolean containsKey(@Nullable Object key) { - return ourModelLocations.contains(key) || super.containsKey(key); - } - - @Override - public Set> entrySet() { - return new DynamicModelEntrySet(this, ourModelLocations); - } - - @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<>(keySet()); - for(ResourceLocation location : locations) { - /* - * 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 - * it actually cares about the given model. - */ - boolean needsReplacement; - try { - needsReplacement = function.apply(location, null) != null; - } catch(Throwable e) { - needsReplacement = true; - } - if(needsReplacement) { - BakedModel existing = get(location); - BakedModel replacement = function.apply(location, existing); - if(replacement != existing) { - put(location, replacement); - } + if(needsReplacement) { + BakedModel existing = get(location); + BakedModel replacement = function.apply(location, existing); + if(replacement != existing) { + put(location, replacement); } } } - }; + } } private static class DynamicModelEntrySet extends AbstractSet> { @@ -212,7 +242,18 @@ public class ModelBakeEventHelper { @Override public Iterator> iterator() { - return Iterators.transform(Iterators.unmodifiableIterator(this.modelLocations.iterator()), DynamicModelEntry::new); + var iter = this.modelLocations.iterator(); + return new Iterator<>() { + @Override + public boolean hasNext() { + return iter.hasNext(); + } + + @Override + public Map.Entry next() { + return new DynamicModelEntry(iter.next()); + } + }; } @Override diff --git a/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/dynamic_resources/ForgeHooksClientMixin.java b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/dynamic_resources/ForgeHooksClientMixin.java index 076ce3c6..198420e7 100644 --- a/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/dynamic_resources/ForgeHooksClientMixin.java +++ b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/dynamic_resources/ForgeHooksClientMixin.java @@ -1,6 +1,7 @@ package org.embeddedt.modernfix.forge.mixin.perf.dynamic_resources; import com.google.common.base.Stopwatch; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.minecraft.client.resources.model.BakedModel; import net.minecraft.resources.ResourceLocation; import net.minecraftforge.client.ForgeHooksClient; @@ -17,6 +18,8 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Redirect; import java.lang.reflect.Method; +import java.time.Duration; +import java.util.Comparator; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -32,19 +35,28 @@ public class ForgeHooksClientMixin { 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(); + Map times = new Object2ObjectOpenHashMap<>(); ModList.get().forEachModContainer((id, mc) -> { Map newRegistry = helper.wrapRegistry(id); ModelEvent.ModifyBakingResult postedEvent = new ModelEvent.ModifyBakingResult(newRegistry, bakeEvent.getModelBakery()); - Stopwatch timer = Stopwatch.createStarted(); + Stopwatch timer = times.computeIfAbsent(id, $ -> Stopwatch.createStarted()); try { acceptEv.invoke(mc, postedEvent); } catch(ReflectiveOperationException e) { e.printStackTrace(); } timer.stop(); - if(timer.elapsed(TimeUnit.SECONDS) >= 1) { - ModernFix.LOGGER.warn("Mod '{}' took {} in the model bake event", id, timer); - } }); + globalTimer.stop(); + if (globalTimer.elapsed(TimeUnit.SECONDS) >= 1) { + ModernFix.LOGGER.warn("Posting dynamic ModelEvent.ModifyBakingResult to mods took {}, breakdown below:", globalTimer); + times.entrySet().stream() + .sorted(Comparator., Duration>comparing(e -> e.getValue().elapsed()).reversed()) + .filter(e -> e.getValue().elapsed(TimeUnit.MILLISECONDS) > 50) + .forEach(entry -> { + ModernFix.LOGGER.warn(" {}: {}", entry.getKey(), entry.getValue().toString()); + }); + } } }