Merge remote-tracking branch 'origin/1.20' into 1.21.1

This commit is contained in:
embeddedt 2025-04-28 19:02:46 -04:00
commit 60d3026ea6
No known key found for this signature in database
GPG Key ID: A69433EC199B5613
4 changed files with 146 additions and 209 deletions

View File

@ -1,108 +0,0 @@
package org.embeddedt.modernfix.resources;
import com.google.common.base.Splitter;
import com.google.common.collect.Interner;
import com.google.common.collect.Interners;
import org.embeddedt.modernfix.util.FileUtil;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
public class CachedResourcePath {
private final String[] pathComponents;
public static final Interner<String> PATH_COMPONENT_INTERNER = Interners.newStrongInterner();
private static final Splitter SLASH_SPLITTER = Splitter.on('/');
public static final String[] NO_PREFIX = new String[0];
public CachedResourcePath(String[] prefix, Path path) {
this(prefix, path, path.getNameCount(), true);
}
public CachedResourcePath(String s) {
// normalize so we can guarantee there are no empty sections
this(NO_PREFIX, SLASH_SPLITTER.splitToList(FileUtil.normalize(s)), false);
}
public <T> CachedResourcePath(String[] prefixElements, Collection<T> collection, boolean intern) {
this(prefixElements, collection, collection.size(), intern);
}
public <T> CachedResourcePath(String[] prefixElements, Iterable<T> path, int count, boolean intern) {
String[] components = new String[prefixElements.length + count];
int i = 0;
while(i < prefixElements.length) {
components[i] = intern ? PATH_COMPONENT_INTERNER.intern(prefixElements[i]) : prefixElements[i];
i++;
}
for(Object component : path) {
String s = component.toString();
if(s.length() == 0)
continue;
components[i] = intern ? PATH_COMPONENT_INTERNER.intern(s) : s;
i++;
}
pathComponents = components;
}
public CachedResourcePath(String[] prefixElements, CachedResourcePath other) {
String[] components = new String[prefixElements.length + other.pathComponents.length];
int i = 0;
while(i < prefixElements.length) {
components[i] = PATH_COMPONENT_INTERNER.intern(prefixElements[i]);
i++;
}
System.arraycopy(other.pathComponents, 0, components, i, other.pathComponents.length);
pathComponents = components;
}
/**
* DOES NOT INTERN!
*/
public CachedResourcePath(String[] pathComponents) {
for(String s : pathComponents) {
if(s.length() == 0) {
// reconstruct the whole array skipping blanks. inefficient, but should not be the common case
pathComponents = Arrays.stream(pathComponents).filter(comp -> comp.length() > 0).toArray(String[]::new);
break;
}
}
this.pathComponents = pathComponents;
}
@Override
public int hashCode() {
return Arrays.hashCode(pathComponents);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CachedResourcePath that = (CachedResourcePath) o;
return Arrays.equals(pathComponents, that.pathComponents);
}
public String getFileName() {
return pathComponents[pathComponents.length - 1];
}
public int getNameCount() {
return pathComponents.length;
}
public String getNameAt(int i) {
return pathComponents[i];
}
public String getFullPath(int startIndex) {
StringBuilder sb = new StringBuilder();
for(int i = startIndex; i < pathComponents.length; i++) {
sb.append(pathComponents[i]);
if(i != (pathComponents.length - 1))
sb.append('/');
}
return sb.toString();
}
}

View File

@ -1,17 +0,0 @@
package org.embeddedt.modernfix.resources;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.PackResources;
import net.minecraft.server.packs.resources.IoSupplier;
import java.io.InputStream;
import java.util.Collection;
import java.util.function.Function;
public class NewResourcePackAdapter {
public static void sendToOutput(Function<ResourceLocation, IoSupplier<InputStream>> streamCreator, PackResources.ResourceOutput output, Collection<ResourceLocation> locations) {
for(ResourceLocation rl : locations) {
output.accept(rl, streamCreator.apply(rl));
}
}
}

View File

@ -1,8 +1,7 @@
package org.embeddedt.modernfix.neoforge.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;
@ -12,7 +11,6 @@ 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.neoforged.fml.ModContainer;
import net.neoforged.fml.ModList;
@ -21,7 +19,17 @@ import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.util.ForwardingInclDefaultsMap;
import org.jetbrains.annotations.Nullable;
import java.util.*;
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;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiFunction;
/**
@ -29,22 +37,27 @@ 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<String> 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<String, UniverseVisibility> MOD_VISIBILITY_CONFIGURATION = ImmutableMap.<String, UniverseVisibility>builder()
.build();
private final Map<ModelResourceLocation, BakedModel> modelRegistry;
private final Set<ModelResourceLocation> topLevelModelLocations;
private final MutableGraph<String> dependencyGraph;
public ModelBakeEventHelper(Map<ModelResourceLocation, BakedModel> modelRegistry) {
this.modelRegistry = modelRegistry;
this.topLevelModelLocations = new ObjectLinkedOpenHashSet<>();
@ -55,25 +68,30 @@ public class ModelBakeEventHelper {
topLevelModelLocations.add(BlockModelShaper.stateToModelLocation(location, state));
}
});
BuiltInRegistries.ITEM.keySet().forEach(location -> topLevelModelLocations.add(new ModelResourceLocation(location, "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<String> buildDependencyGraph() {
MutableGraph<String> 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<? extends ModContainer> 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<String> WARNED_MOD_IDS = new HashSet<>();
@ -124,73 +142,94 @@ public class ModelBakeEventHelper {
}
public Map<ModelResourceLocation, BakedModel> wrapRegistry(String modId) {
var config = MOD_VISIBILITY_CONFIGURATION.getOrDefault(modId, UniverseVisibility.EVERYTHING);
if (config == UniverseVisibility.NONE) {
return createWarningRegistry(modId);
}
final Set<String> 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<ModelResourceLocation> ourModelLocations = Sets.filter(this.topLevelModelLocations, loc -> modIdsToInclude.contains(loc.id().getNamespace()));
BakedModel missingModel = modelRegistry.get(ModelBakery.MISSING_MODEL_VARIANT);
return new ForwardingMap<ModelResourceLocation, BakedModel>() {
@Override
protected Map<ModelResourceLocation, BakedModel> delegate() {
return modelRegistry;
}
Set<ModelResourceLocation> ourModelLocations;
if (config == UniverseVisibility.SELF_AND_DEPS) {
ourModelLocations = Sets.filter(this.topLevelModelLocations, loc -> modIdsToInclude.contains(loc.id().getNamespace()));
} else {
ourModelLocations = this.topLevelModelLocations;
}
BakedModel missingModel = modelRegistry.get(ModelBakery.MISSING_MODEL_LOCATION);
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<ModelResourceLocation, BakedModel> {
private final Set<String> modIdsToInclude;
private final BakedModel missingModel;
private final Set<ModelResourceLocation> ourModelLocations;
private final String modId;
private EmulatedModelRegistry(String modId, Set<String> modIdsToInclude, BakedModel missingModel, Set<ModelResourceLocation> ourModelLocations) {
this.modId = modId;
this.modIdsToInclude = modIdsToInclude;
this.missingModel = missingModel;
this.ourModelLocations = ourModelLocations;
}
@Override
protected Map<ModelResourceLocation, BakedModel> delegate() {
return modelRegistry;
}
@Override
public BakedModel get(@Nullable Object key) {
BakedModel model = super.get(key);
if(model == null && key instanceof ModelResourceLocation mrl && modIdsToInclude.contains(mrl.id().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<ModelResourceLocation> keySet() {
return Collections.unmodifiableSet(ourModelLocations);
}
@Override
public boolean containsKey(@Nullable Object key) {
return ourModelLocations.contains(key) || super.containsKey(key);
}
@Override
public Set<Entry<ModelResourceLocation, BakedModel>> entrySet() {
return new DynamicModelEntrySet(this, ourModelLocations);
}
@Override
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);
List<ModelResourceLocation> locations = new ArrayList<>(ourModelLocations);
for(ModelResourceLocation 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<ModelResourceLocation> keySet() {
return ourModelLocations;
}
@Override
public boolean containsKey(@Nullable Object key) {
return ourModelLocations.contains(key) || super.containsKey(key);
}
@Override
public Set<Entry<ModelResourceLocation, BakedModel>> entrySet() {
return new DynamicModelEntrySet(this, ourModelLocations);
}
@Override
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);
List<ModelResourceLocation> locations = new ArrayList<>(keySet());
for(ModelResourceLocation 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<Map.Entry<ModelResourceLocation, BakedModel>> {
@ -204,7 +243,18 @@ public class ModelBakeEventHelper {
@Override
public Iterator<Map.Entry<ModelResourceLocation, BakedModel>> 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<ModelResourceLocation, BakedModel> next() {
return new DynamicModelEntry(iter.next());
}
};
}
@Override

View File

@ -1,6 +1,7 @@
package org.embeddedt.modernfix.neoforge.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.client.resources.model.ModelResourceLocation;
import net.neoforged.bus.api.Event;
@ -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<String, Stopwatch> times = new Object2ObjectOpenHashMap<>();
ModList.get().forEachModContainer((id, mc) -> {
Map<ModelResourceLocation, BakedModel> newRegistry = helper.wrapRegistry(id);
ModelEvent.ModifyBakingResult postedEvent = new ModelEvent.ModifyBakingResult(newRegistry, bakeEvent.getTextureGetter(), 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.<Map.Entry<String, Stopwatch>, 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());
});
}
}
}