Get dynamic model loading working with ModifyBakingResult

This commit is contained in:
embeddedt 2024-12-12 21:31:21 -05:00
parent e82f316216
commit 19d2d8cfc0
No known key found for this signature in database
GPG Key ID: A69433EC199B5613
5 changed files with 308 additions and 334 deletions

View File

@ -1,10 +1,13 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
import com.google.common.collect.Maps;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.client.Minecraft;
import net.minecraft.client.model.geom.EntityModelSet;
import net.minecraft.client.renderer.block.model.BlockModel;
import net.minecraft.client.renderer.item.ClientItem;
import net.minecraft.client.renderer.item.ItemModel;
import net.minecraft.client.resources.model.AtlasSet;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.BlockStateModelLoader;
import net.minecraft.client.resources.model.ModelManager;
@ -12,8 +15,8 @@ import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.world.level.block.state.BlockState;
import org.apache.commons.lang3.ArrayUtils;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.dynamicresources.DynamicModelProvider;
import org.spongepowered.asm.mixin.Mixin;
@ -21,20 +24,18 @@ import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.lang.ref.WeakReference;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
@Mixin(ModelManager.class)
@ClientOnlyMixin
public class ModelManagerMixin {
public class ModelManagerMixin implements DynamicModelProvider.ModelManagerExtension {
@Shadow private BakedModel missingModel;
@Shadow private ItemModel missingItemModel;
@Shadow private EntityModelSet entityModelSet;
@Unique
private DynamicModelProvider mfix$modelProvider;
@ -57,16 +58,19 @@ public class ModelManagerMixin {
return Map.of();
}
@Inject(method = "apply", at = @At("RETURN"))
private void createModelProvider(ModelManager.ReloadState reloadState, ProfilerFiller profiler, CallbackInfo ci) {
this.mfix$modelProvider = new DynamicModelProvider(
null, // TODO
this.missingModel,
this.missingItemModel,
Minecraft.getInstance().getResourceManager(),
this.entityModelSet,
reloadState.atlasPreparations()
);
@ModifyArg(method = "reload", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;allOf([Ljava/util/concurrent/CompletableFuture;)Ljava/util/concurrent/CompletableFuture;", ordinal = 1))
private CompletableFuture<?>[] createModelProvider(CompletableFuture<?>[] cfs, @Local(ordinal = 0) CompletableFuture<EntityModelSet> entityModelFuture, @Local(ordinal = 0, argsOnly = true) Executor executor, @Local(ordinal = 0) Map<ResourceLocation, CompletableFuture<AtlasSet.StitchResult>> atlasPreparations) {
CompletableFuture<Void> makeModelProviderFuture = CompletableFuture.supplyAsync(() -> {
return Map.copyOf(Maps.transformValues(atlasPreparations, CompletableFuture::join));
}, executor).thenAcceptBoth(entityModelFuture, (stitchResults, entityModelSet) -> {
this.mfix$modelProvider = new DynamicModelProvider(
Minecraft.getInstance().getResourceManager(),
entityModelSet,
stitchResults
);
DynamicModelProvider.currentReloadingModelProvider = new WeakReference<>(this.mfix$modelProvider);
});
return ArrayUtils.add(cfs, makeModelProviderFuture);
}
/**
@ -99,4 +103,9 @@ public class ModelManagerMixin {
public ClientItem.Properties getItemProperties(ResourceLocation resourceLocation) {
return this.mfix$modelProvider.getClientItemProperties(resourceLocation);
}
@Override
public DynamicModelProvider mfix$getModelProvider() {
return this.mfix$modelProvider;
}
}

View File

@ -3,17 +3,21 @@ package org.embeddedt.modernfix.dynamicresources;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Iterators;
import com.google.common.collect.Maps;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.mojang.serialization.JsonOps;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import net.minecraft.client.model.geom.EntityModelSet;
import net.minecraft.client.renderer.block.BlockModelShaper;
import net.minecraft.client.renderer.block.model.BlockModel;
import net.minecraft.client.renderer.block.model.BlockModelDefinition;
import net.minecraft.client.renderer.block.model.ItemModelGenerator;
import net.minecraft.client.renderer.block.model.UnbakedBlockStateModel;
import net.minecraft.client.renderer.item.ClientItem;
import net.minecraft.client.renderer.item.ItemModel;
import net.minecraft.client.renderer.item.MissingItemModel;
import net.minecraft.client.renderer.texture.TextureAtlas;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.model.AtlasSet;
@ -29,6 +33,7 @@ import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.client.resources.model.ModelState;
import net.minecraft.client.resources.model.SpriteGetter;
import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.Resource;
import net.minecraft.server.packs.resources.ResourceManager;
@ -37,14 +42,23 @@ import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import org.embeddedt.modernfix.ModernFix;
import org.jetbrains.annotations.NotNull;
import java.io.Reader;
import java.lang.ref.WeakReference;
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.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
@ -53,69 +67,22 @@ import java.util.stream.Collectors;
*/
public class DynamicModelProvider {
private final LoadingCache<ResourceLocation, Optional<BlockStateModelLoader.LoadedModels>> loadedStateDefinitions =
CacheBuilder.newBuilder()
.expireAfterAccess(3, TimeUnit.MINUTES)
.maximumSize(1000)
.concurrencyLevel(8)
.softValues()
.build(new CacheLoader<>() {
@Override
public Optional<BlockStateModelLoader.LoadedModels> load(ResourceLocation key) {
return loadBlockStateDefinition(key);
}
});
this.makeLoadingCache(this::loadBlockStateDefinition);
private final LoadingCache<ResourceLocation, Optional<UnbakedModel>> loadedBlockModels =
CacheBuilder.newBuilder()
.expireAfterAccess(3, TimeUnit.MINUTES)
.maximumSize(1000)
.concurrencyLevel(8)
.softValues()
.build(new CacheLoader<>() {
@Override
public Optional<UnbakedModel> load(ResourceLocation key) {
return loadBlockModel(key);
}
});
this.makeLoadingCache(this::loadBlockModel);
private final LoadingCache<ModelResourceLocation, Optional<BakedModel>> loadedBakedModels =
CacheBuilder.newBuilder()
.expireAfterAccess(3, TimeUnit.MINUTES)
.maximumSize(1000)
.concurrencyLevel(8)
.softValues()
.build(new CacheLoader<>() {
@Override
public Optional<BakedModel> load(ModelResourceLocation key) {
return loadBakedModel(key);
}
});
this.makeLoadingCache(this::loadBakedModel);
private final LoadingCache<ResourceLocation, Optional<ClientItem>> loadedClientItemProperties =
CacheBuilder.newBuilder()
.expireAfterAccess(3, TimeUnit.MINUTES)
.maximumSize(1000)
.concurrencyLevel(8)
.softValues()
.build(new CacheLoader<>() {
@Override
public Optional<ClientItem> load(ResourceLocation key) {
return loadClientItemProperties(key);
}
});
this.makeLoadingCache(this::loadClientItemProperties);
private final LoadingCache<ResourceLocation, Optional<ItemModel>> loadedItemModels =
CacheBuilder.newBuilder()
.expireAfterAccess(3, TimeUnit.MINUTES)
.maximumSize(1000)
.concurrencyLevel(8)
.softValues()
.build(new CacheLoader<>() {
@Override
public Optional<ItemModel> load(ResourceLocation key) {
return loadItemModel(key);
}
});
this.makeLoadingCache(this::loadItemModel);
private final LoadingCache<ResourceLocation, Optional<BakedModel>> loadedStandaloneModels =
this.makeLoadingCache(this::loadStandaloneModel);
private final BakedModel missingModel;
private final ItemModel missingItemModel;
@ -127,11 +94,13 @@ public class DynamicModelProvider {
private final EntityModelSet entityModelSet;
private final ItemModelGenerator itemModelGenerator;
public DynamicModelProvider(Map<ModelResourceLocation, BakedModel> initialBakedRegistry, BakedModel missingModel,
ItemModel missingItemModel, ResourceManager resourceManager, EntityModelSet entityModelSet,
private final Map<ModelResourceLocation, BakedModel> mrlModelOverrides = new ConcurrentHashMap<>();
private final Map<ResourceLocation, ItemModel> itemStackModelOverrides = new ConcurrentHashMap<>();
private final Map<ResourceLocation, BakedModel> standaloneModelOverrides = new ConcurrentHashMap<>();
public DynamicModelProvider(ResourceManager resourceManager, EntityModelSet entityModelSet,
Map<ResourceLocation, AtlasSet.StitchResult> atlasMap) {
this.missingModel = missingModel;
this.missingItemModel = missingItemModel;
this.unbakedMissingModel = MissingBlockModel.missingModel();
this.entityModelSet = entityModelSet;
var missing = atlasMap.get(TextureAtlas.LOCATION_BLOCKS).missing();
this.textureGetter = new ModelBakery.TextureGetter() {
@ -154,9 +123,189 @@ public class DynamicModelProvider {
};
this.stateMapper = BlockStateModelLoader.definitionLocationToBlockMapper();
this.resourceManager = resourceManager;
this.unbakedMissingModel = MissingBlockModel.missingModel();
this.resolver = new DynamicResolver();
this.itemModelGenerator = new ItemModelGenerator();
this.missingModel = this.bakeModel(this.unbakedMissingModel, () -> "missing");
this.missingItemModel = new MissingItemModel(this.missingModel);
}
public BakedModel getMissingBakedModel() {
return this.missingModel;
}
public ItemModel getMissingItemModel() {
return this.missingItemModel;
}
public Map<ModelResourceLocation, BakedModel> getTopLevelEmulatedRegistry() {
Set<ModelResourceLocation> topLevelModelLocations = new HashSet<>();
// Skip going through ModelLocationCache because most of the accesses will be misses
BuiltInRegistries.BLOCK.entrySet().forEach(entry -> {
var location = entry.getKey().location();
for(BlockState state : entry.getValue().getStateDefinition().getPossibleStates()) {
topLevelModelLocations.add(BlockModelShaper.stateToModelLocation(location, state));
}
});
return new EmulatedRegistry<>(ModelResourceLocation.class, this.loadedBakedModels, topLevelModelLocations, this.mrlModelOverrides);
}
public Map<ResourceLocation, BakedModel> getStandaloneEmulatedRegistry() {
return new EmulatedRegistry<>(ResourceLocation.class, this.loadedStandaloneModels, Set.of(), this.standaloneModelOverrides);
}
public Map<ResourceLocation, ItemModel> getItemModelEmulatedRegistry() {
return new EmulatedRegistry<>(ResourceLocation.class, this.loadedItemModels, BuiltInRegistries.ITEM.keySet(), this.itemStackModelOverrides);
}
public Map<ResourceLocation, ClientItem.Properties> getItemPropertiesEmulatedRegistry() {
return Maps.transformValues(new EmulatedRegistry<>(ResourceLocation.class, this.loadedClientItemProperties, BuiltInRegistries.ITEM.keySet(), Map.of()), ClientItem::properties);
}
private <K, V> LoadingCache<K, Optional<V>> makeLoadingCache(Function<K, Optional<V>> loadingFunction) {
return CacheBuilder.newBuilder()
.expireAfterAccess(3, TimeUnit.MINUTES)
.maximumSize(1000)
.concurrencyLevel(8)
.softValues()
.build(new CacheLoader<>() {
@Override
public Optional<V> load(K key) {
return loadingFunction.apply(key);
}
});
}
private static class EmulatedRegistry<K, V> implements Map<K, V> {
private final LoadingCache<K, Optional<V>> realCache;
private final Set<K> keys;
private final Map<K, V> overrides;
private final Class<K> keyClass;
public EmulatedRegistry(Class<K> keyClass, LoadingCache<K, Optional<V>> realCache, Set<K> keys, Map<K, V> overrides) {
this.keyClass = keyClass;
this.realCache = realCache;
this.keys = Collections.unmodifiableSet(keys);
this.overrides = overrides;
}
@Override
public V get(Object key) {
if (this.keyClass.isAssignableFrom(key.getClass())) {
return this.realCache.getUnchecked((K)key).orElse(null);
} else {
return null;
}
}
@Override
public V put(K key, V value) {
V oldValue = this.realCache.getUnchecked(key).orElse(null);
this.overrides.put(key, value);
this.realCache.invalidate(key);
return oldValue;
}
@Override
public V remove(Object key) {
this.overrides.remove(key);
this.realCache.invalidate(key);
return null;
}
@Override
public void putAll(@NotNull Map<? extends K, ? extends V> m) {
m.forEach(this::put);
}
@Override
public void clear() {
this.overrides.clear();
this.realCache.invalidateAll();
}
@Override
public @NotNull Set<K> keySet() {
return keys;
}
@Override
public @NotNull Collection<V> values() {
return List.of();
}
@Override
public int size() {
return keys.size();
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public boolean containsKey(Object key) {
return keys.contains(key);
}
@Override
public boolean containsValue(Object value) {
return false;
}
@Override
public @NotNull Set<Entry<K, V>> entrySet() {
return new AbstractSet<>() {
@Override
public Iterator<Entry<K, V>> iterator() {
return Iterators.transform(keys.iterator(), key -> new Entry<>() {
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
return get(key);
}
@Override
public V setValue(V value) {
return put(key, value);
}
});
}
@Override
public int size() {
return keys.size();
}
};
}
@Override
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
for(K location : keys) {
/*
* 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) {
V existing = get(location);
V replacement = function.apply(location, existing);
if(replacement != existing) {
put(location, replacement);
}
}
}
}
}
private Optional<BlockStateModelLoader.LoadedModels> loadBlockStateDefinition(ResourceLocation location) {
@ -178,29 +327,31 @@ public class DynamicModelProvider {
return Optional.of(BlockStateModelLoader.loadBlockStateDefinitionStack(location, stateDefinition, loadedDefinitions, this.unbakedMissingModel));
}
private BakedModel bakeModel(UnbakedModel model, ModelResourceLocation location) {
private BakedModel bakeModel(UnbakedModel model, ModelDebugName name) {
synchronized (this) {
this.resolver.clearResolver();
model.resolveDependencies(this.resolver);
var modelBaker = new DynamicBaker(location::toString);
var modelBaker = new DynamicBaker(name);
return UnbakedModel.bakeWithTopModelValues(model, modelBaker, BlockModelRotation.X0_Y0);
}
}
private BakedModel bakeModel(UnbakedBlockStateModel model, ModelResourceLocation location) {
private BakedModel bakeModel(UnbakedBlockStateModel model, ModelDebugName name) {
synchronized (this) {
this.resolver.clearResolver();
model.resolveDependencies(this.resolver);
var modelBaker = new DynamicBaker(location::toString);
var modelBaker = new DynamicBaker(name);
return model.bake(modelBaker);
}
}
private Optional<BakedModel> loadBakedModel(ModelResourceLocation location) {
var override = this.mrlModelOverrides.get(location);
if (override != null) {
return Optional.of(override);
}
if (location.variant().equals("standalone") || location.variant().equals("fabric_resource")) {
return this.loadedBlockModels.getUnchecked(location.id()).map(unbakedModel -> {
return this.bakeModel(unbakedModel, location);
});
return this.loadStandaloneModel(location.id());
} else {
var optLoadedModels = this.loadedStateDefinitions.getUnchecked(location.id());
Optional<UnbakedBlockStateModel> unbakedModelOpt = optLoadedModels.map(loadedModels -> {
@ -212,11 +363,21 @@ public class DynamicModelProvider {
}
});
return unbakedModelOpt.map(unbakedModel -> {
return this.bakeModel(unbakedModel, location);
return this.bakeModel(unbakedModel, location::toString);
});
}
}
private Optional<BakedModel> loadStandaloneModel(ResourceLocation location) {
var override = this.standaloneModelOverrides.get(location);
if (override != null) {
return Optional.of(override);
}
return this.loadedBlockModels.getUnchecked(location).map(unbakedModel -> {
return this.bakeModel(unbakedModel, location::toString);
});
}
private Optional<UnbakedModel> loadBlockModel(ResourceLocation location) {
if (location.equals(ItemModelGenerator.GENERATED_ITEM_MODEL_ID)) {
return Optional.of(this.itemModelGenerator);
@ -243,7 +404,7 @@ public class DynamicModelProvider {
ClientItem clientItem = ClientItem.CODEC.parse(JsonOps.INSTANCE, JsonParser.parseReader(reader)).getOrThrow();
return Optional.of(clientItem);
} catch(Exception e) {
ModernFix.LOGGER.error("Failed to load block model {} from '{}'", location, resource.get().sourcePackId(), e);
ModernFix.LOGGER.error("Failed to load client item {} from '{}'", location, resource.get().sourcePackId(), e);
return Optional.empty();
}
} else {
@ -253,6 +414,10 @@ public class DynamicModelProvider {
}
private Optional<ItemModel> loadItemModel(ResourceLocation location) {
var override = this.itemStackModelOverrides.get(location);
if (override != null) {
return Optional.of(override);
}
return this.loadedClientItemProperties.getUnchecked(location).map(clientItem -> {
var bakingContext = new ItemModel.BakingContext(new DynamicBaker(location::toString), this.entityModelSet, this.missingItemModel);
return clientItem.model().bake(bakingContext);
@ -271,6 +436,10 @@ public class DynamicModelProvider {
return this.loadedItemModels.getUnchecked(location).orElse(this.missingItemModel);
}
public BakedModel getStandaloneModel(ResourceLocation location) {
return this.loadedStandaloneModels.getUnchecked(location).orElse(this.missingModel);
}
private class DynamicBaker implements ModelBaker {
private final ModelDebugName modelDebugName;
@ -331,4 +500,10 @@ public class DynamicModelProvider {
this.resolvedModels.clear();
}
}
public static WeakReference<DynamicModelProvider> currentReloadingModelProvider = new WeakReference<>(null);
public interface ModelManagerExtension {
DynamicModelProvider mfix$getModelProvider();
}
}

View File

@ -1,55 +1,24 @@
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.Sets;
import com.google.common.graph.GraphBuilder;
import com.google.common.graph.MutableGraph;
import net.minecraft.client.renderer.block.BlockModelShaper;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.MissingBlockModel;
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.minecraft.client.resources.model.ModelBakery;
import net.neoforged.fml.ModContainer;
import net.neoforged.fml.ModList;
import net.neoforged.neoforgespi.language.IModInfo;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.util.ForwardingInclDefaultsMap;
import org.jetbrains.annotations.Nullable;
import org.embeddedt.modernfix.dynamicresources.DynamicModelProvider;
import java.util.*;
import java.util.function.BiFunction;
/**
* Stores a list of all known default block/item models in the game, and provides a namespaced version
* 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",
"embers");
private final Map<ModelResourceLocation, BakedModel> modelRegistry;
private final Set<ModelResourceLocation> topLevelModelLocations;
private final DynamicModelProvider modelRegistry;
private final MutableGraph<String> dependencyGraph;
public ModelBakeEventHelper(Map<ModelResourceLocation, BakedModel> modelRegistry) {
public ModelBakeEventHelper(DynamicModelProvider modelRegistry) {
this.modelRegistry = modelRegistry;
this.topLevelModelLocations = new HashSet<>(modelRegistry.keySet());
// Skip going through ModelLocationCache because most of the accesses will be misses
BuiltInRegistries.BLOCK.entrySet().forEach(entry -> {
var location = entry.getKey().location();
for(BlockState state : entry.getValue().getStateDefinition().getPossibleStates()) {
topLevelModelLocations.add(BlockModelShaper.stateToModelLocation(location, state));
}
});
BuiltInRegistries.ITEM.keySet().forEach(location -> topLevelModelLocations.add(new ModelResourceLocation(location, "inventory")));
this.dependencyGraph = GraphBuilder.undirected().build();
ModList.get().forEachModContainer((id, mc) -> {
this.dependencyGraph.addNode(id);
@ -69,177 +38,14 @@ public class ModelBakeEventHelper {
}
}
private static final Set<String> WARNED_MOD_IDS = new HashSet<>();
/**
* Create a model registry that warns if keySet, entrySet, values are accessed.
* @param modId the mod that the event is being fired for
* @return a wrapper around the model registry
*/
private Map<ModelResourceLocation, BakedModel> createWarningRegistry(String modId) {
return new ForwardingInclDefaultsMap<ModelResourceLocation, BakedModel>() {
@Override
protected Map<ModelResourceLocation, BakedModel> delegate() {
return modelRegistry;
}
private void logWarning() {
if(!WARNED_MOD_IDS.add(modId))
return;
ModernFix.LOGGER.warn("Mod '{}' is accessing Map#keySet/entrySet/values/replaceAll on the model registry map inside its event handler." +
" This probably won't work as expected with dynamic resources on. Prefer using Map#get/put and constructing ModelResourceLocations another way.", modId);
}
@Override
public Set<ModelResourceLocation> keySet() {
logWarning();
return super.keySet();
}
@Override
public Set<Entry<ModelResourceLocation, BakedModel>> entrySet() {
logWarning();
return super.entrySet();
}
@Override
public Collection<BakedModel> values() {
logWarning();
return super.values();
}
@Override
public void replaceAll(BiFunction<? super ModelResourceLocation, ? super BakedModel, ? extends BakedModel> function) {
logWarning();
super.replaceAll(function);
}
};
}
public Map<ModelResourceLocation, BakedModel> wrapRegistry(String 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(MissingBlockModel.VARIANT);
return new ForwardingMap<ModelResourceLocation, BakedModel>() {
@Override
protected Map<ModelResourceLocation, BakedModel> 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<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);
}
}
}
}
};
}
private static class DynamicModelEntrySet extends AbstractSet<Map.Entry<ModelResourceLocation, BakedModel>> {
private final Map<ModelResourceLocation, BakedModel> modelRegistry;
private final Set<ModelResourceLocation> modelLocations;
private DynamicModelEntrySet(Map<ModelResourceLocation, BakedModel> modelRegistry, Set<ModelResourceLocation> modelLocations) {
this.modelRegistry = modelRegistry;
this.modelLocations = modelLocations;
}
@Override
public Iterator<Map.Entry<ModelResourceLocation, BakedModel>> iterator() {
return Iterators.transform(Iterators.unmodifiableIterator(this.modelLocations.iterator()), DynamicModelEntry::new);
}
@Override
public boolean contains(Object o) {
if(o instanceof Map.Entry entry) {
return modelRegistry.containsKey(entry.getKey());
} else {
return false;
}
}
@Override
public int size() {
return modelRegistry.size();
}
@Override
public boolean removeAll(Collection<?> c) {
throw new UnsupportedOperationException();
}
private class DynamicModelEntry implements Map.Entry<ModelResourceLocation, BakedModel> {
private final ModelResourceLocation location;
private DynamicModelEntry(ModelResourceLocation location) {
this.location = location;
}
@Override
public ModelResourceLocation getKey() {
return this.location;
}
@Override
public BakedModel getValue() {
return modelRegistry.get(this.location);
}
@Override
public BakedModel setValue(BakedModel value) {
return modelRegistry.put(this.location, value);
}
}
public ModelBakery.BakingResult createDynamicResult() {
return new ModelBakery.BakingResult(
this.modelRegistry.getMissingBakedModel(),
this.modelRegistry.getTopLevelEmulatedRegistry(),
this.modelRegistry.getMissingItemModel(),
this.modelRegistry.getItemModelEmulatedRegistry(),
this.modelRegistry.getItemPropertiesEmulatedRegistry(),
this.modelRegistry.getStandaloneEmulatedRegistry()
);
}
}

View File

@ -1,42 +0,0 @@
package org.embeddedt.modernfix.neoforge.mixin.perf.dynamic_resources;
import net.neoforged.bus.api.Event;
import net.neoforged.fml.ModLoader;
import net.neoforged.neoforge.client.ClientHooks;
import net.neoforged.neoforge.client.event.ModelEvent;
import org.embeddedt.modernfix.ModernFix;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(ClientHooks.class)
public class ForgeHooksClientMixin {
/**
* Generate a more realistic keySet that contains every item and block model location, to help with mod compat.
*/
@Redirect(method = "onModifyBakingResult", at = @At(value = "INVOKE", target = "Lnet/neoforged/fml/ModLoader;postEvent(Lnet/neoforged/bus/api/Event;)V"), remap = false)
private static void postNamespacedKeySetEvent(Event event) {
if(ModLoader.hasErrors())
return;
ModelEvent.ModifyBakingResult bakeEvent = ((ModelEvent.ModifyBakingResult)event);
/*
ModelBakeEventHelper helper = new ModelBakeEventHelper(bakeEvent.getModels());
Method acceptEv = ObfuscationReflectionHelper.findMethod(ModContainer.class, "acceptEvent", Event.class);
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();
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);
}
});
*/
ModernFix.LOGGER.warn("ModifyBakingResult support not reimplemented yet");
}
}

View File

@ -0,0 +1,26 @@
package org.embeddedt.modernfix.neoforge.mixin.perf.dynamic_resources;
import net.minecraft.client.resources.model.ModelBakery;
import net.minecraft.client.resources.model.ModelManager;
import net.neoforged.fml.ModLoader;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.dynamicresources.DynamicModelProvider;
import org.embeddedt.modernfix.neoforge.dynresources.ModelBakeEventHelper;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
@Mixin(ModelManager.class)
@ClientOnlyMixin
public class ModelManagerMixinNeo {
@ModifyArg(method = "loadModels", at = @At(value = "INVOKE", target = "Lnet/neoforged/neoforge/client/ClientHooks;onModifyBakingResult(Lnet/minecraft/client/resources/model/ModelBakery$BakingResult;Ljava/util/Map;Lnet/minecraft/client/resources/model/ModelBakery;)V"), remap = false, index = 0)
private static ModelBakery.BakingResult useDynamicBakingResult(ModelBakery.BakingResult bakingResult) {
var currentReloadingProvider = DynamicModelProvider.currentReloadingModelProvider.get();
if(ModLoader.hasErrors() || currentReloadingProvider == null) {
ModernFix.LOGGER.error("Errors encountered - not using dynamic model BakingResult");
return bakingResult;
}
return new ModelBakeEventHelper(currentReloadingProvider).createDynamicResult();
}
}