Merge remote-tracking branch 'origin/1.20' into 1.21.1
This commit is contained in:
commit
60d3026ea6
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user