Merge remote-tracking branch 'origin/1.18' into 1.19.2

This commit is contained in:
embeddedt 2023-04-29 10:34:53 -04:00
commit 6768662f1d
No known key found for this signature in database
GPG Key ID: A69433EC199B5613
15 changed files with 950 additions and 19 deletions

View File

@ -2,9 +2,11 @@ package org.embeddedt.modernfix;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import net.minecraft.Util;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.item.Item;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.server.ServerStartedEvent;
@ -21,6 +23,8 @@ import net.minecraftforge.fml.loading.FMLConfig;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.fml.util.ObfuscationReflectionHelper;
import net.minecraftforge.network.NetworkConstants;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegisterEvent;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -99,6 +103,7 @@ public class ModernFix {
MinecraftForge.EVENT_BUS.register(this);
FMLJavaModLoadingContext.get().getModEventBus().addListener(this::commonSetup);
FMLJavaModLoadingContext.get().getModEventBus().addListener(this::onLoadComplete);
FMLJavaModLoadingContext.get().getModEventBus().addListener(this::registerItems);
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> MinecraftForge.EVENT_BUS.register(new ModernFixClient()));
ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class, () -> new IExtensionPoint.DisplayTest(() -> NetworkConstants.IGNORESERVERONLY, (a, b) -> true));
ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, ModernFixConfig.COMMON_CONFIG);
@ -108,6 +113,17 @@ public class ModernFix {
ModFileScanDataDeduplicator.deduplicate();
}
private void registerItems(RegisterEvent event) {
if(Boolean.getBoolean("modernfix.largeRegistryTest")) {
event.register(ForgeRegistries.Keys.ITEMS, helper -> {
Item.Properties props = new Item.Properties();
for(int i = 0; i < 1000000; i++) {
helper.register(new ResourceLocation("modernfix", "item_" + i), new Item(props));
}
});
}
}
private static boolean dfuModPresent() {
if(FMLConfig.isOptimizedDFUDisabled())
return true;

View File

@ -10,6 +10,7 @@ import net.minecraft.Util;
import net.minecraft.client.renderer.block.BlockModelShaper;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.core.Registry;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
@ -20,7 +21,7 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class ModelLocationCache {
private static final LoadingCache<BlockState, ModelResourceLocation> locationCache = CacheBuilder.newBuilder()
private static final LoadingCache<BlockState, ModelResourceLocation> blockLocationCache = CacheBuilder.newBuilder()
.maximumSize(10000)
.build(new CacheLoader<BlockState, ModelResourceLocation>() {
@Override
@ -29,9 +30,26 @@ public class ModelLocationCache {
}
});
private static final LoadingCache<Item, ModelResourceLocation> itemLocationCache = CacheBuilder.newBuilder()
.maximumSize(10000)
.build(new CacheLoader<Item, ModelResourceLocation>() {
@Override
public ModelResourceLocation load(Item key) throws Exception {
return new ModelResourceLocation(Registry.ITEM.getKey(key), "inventory");
}
});
public static ModelResourceLocation get(BlockState state) {
try {
return locationCache.get(state);
return blockLocationCache.get(state);
} catch(ExecutionException e) {
throw new RuntimeException(e.getCause());
}
}
public static ModelResourceLocation get(Item item) {
try {
return itemLocationCache.get(item);
} catch(ExecutionException e) {
throw new RuntimeException(e.getCause());
}

View File

@ -0,0 +1,16 @@
package org.embeddedt.modernfix.mixin.devenv;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.registries.ForgeRegistry;
import net.minecraftforge.registries.GameData;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(GameData.class)
public class GameDataMixin {
@Redirect(method = "*", at = @At(value = "INVOKE", target = "Lnet/minecraftforge/registries/ForgeRegistry;dump(Lnet/minecraft/resources/ResourceLocation;)V"))
private static void noDump(ForgeRegistry<?> reg, ResourceLocation id) {
}
}

View File

@ -8,6 +8,7 @@ import net.minecraft.core.Holder;
import net.minecraft.world.item.Item;
import net.minecraftforge.client.model.ForgeItemModelShaper;
import net.minecraftforge.registries.ForgeRegistries;
import org.embeddedt.modernfix.dynamicresources.ModelLocationCache;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
@ -23,6 +24,8 @@ public abstract class ItemModelShaperMixin extends ItemModelShaper {
super(arg);
}
private static final ModelResourceLocation SENTINEL = new ModelResourceLocation("modernfix", "sentinel");
/**
* @reason Get the stored location for that item and meta, and get the model
* from that location from the model manager.
@ -30,7 +33,11 @@ public abstract class ItemModelShaperMixin extends ItemModelShaper {
@Overwrite
@Override
public BakedModel getItemModel(Item item) {
ModelResourceLocation map = locations.get(ForgeRegistries.ITEMS.getDelegateOrThrow(item));
ModelResourceLocation map = locations.getOrDefault(ForgeRegistries.ITEMS.getDelegateOrThrow(item), SENTINEL);
if(map == SENTINEL) {
/* generate the appropriate location from our cache */
map = ModelLocationCache.get(item);
}
return map == null ? null : getModelManager().getModel(map);
}

View File

@ -0,0 +1,20 @@
package org.embeddedt.modernfix.mixin.perf.dynamic_resources;
import net.minecraft.client.renderer.ItemModelShaper;
import net.minecraft.client.renderer.entity.ItemRenderer;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.world.item.Item;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(ItemRenderer.class)
public class ItemRendererMixin {
/**
* Don't waste space putting all these locations into the cache, compute them on demand later.
*/
@Redirect(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/ItemModelShaper;register(Lnet/minecraft/world/item/Item;Lnet/minecraft/client/resources/model/ModelResourceLocation;)V"))
private void skipDefaultRegistration(ItemModelShaper shaper, Item item, ModelResourceLocation mrl) {
}
}

View File

@ -11,6 +11,8 @@ import com.google.common.collect.Sets;
import com.google.gson.*;
import com.mojang.datafixers.util.Pair;
import com.mojang.math.Transformation;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import net.minecraft.Util;
import net.minecraft.client.color.block.BlockColors;
import net.minecraft.client.renderer.block.model.BlockModel;
@ -19,8 +21,11 @@ import net.minecraft.client.renderer.texture.AtlasSet;
import net.minecraft.client.renderer.texture.TextureAtlas;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.client.resources.ClientPackSource;
import net.minecraft.client.resources.model.*;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.*;
import net.minecraft.server.packs.resources.FallbackResourceManager;
import net.minecraft.server.packs.resources.Resource;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.util.profiling.ProfilerFiller;
@ -35,6 +40,8 @@ import net.minecraftforge.client.model.geometry.GeometryLoaderManager;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.ModLoader;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.resource.DelegatingPackResources;
import net.minecraftforge.resource.PathPackResources;
import org.apache.commons.lang3.tuple.Triple;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.duck.IExtendedModelBakery;
@ -57,6 +64,7 @@ import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -196,12 +204,84 @@ public abstract class ModelBakeryMixin implements IExtendedModelBakery {
return ImmutableList.of();
}
private static final int ERROR_THRESHOLD = 200;
private void logOrSuppressError(Object2IntOpenHashMap<String> suppressionMap, String type, ResourceLocation location, Throwable e) {
int numErrors;
synchronized (suppressionMap) {
numErrors = suppressionMap.computeInt(location.getNamespace(), (k, oldVal) -> (oldVal == null ? 1 : oldVal + 1));
}
if(numErrors <= ERROR_THRESHOLD)
ModernFix.LOGGER.error("Error reading {} {}: {}", type, location, e);
}
private boolean trustedResourcePack(PackResources pack) {
return pack instanceof VanillaPackResources ||
pack instanceof PathPackResources ||
pack instanceof ClientPackSource ||
pack instanceof DelegatingPackResources ||
pack instanceof FolderPackResources ||
pack instanceof FilePackResources;
}
private void gatherAdditionalViaManualScan(List<PackResources> untrustedPacks, Set<ResourceLocation> knownLocations,
Collection<ResourceLocation> uncertainLocations, String filePrefix) {
if(untrustedPacks.size() > 0) {
/* Now make a fallback resource manager and use it on the remaining packs to see if they actually contain these files */
FallbackResourceManager frm = new FallbackResourceManager(PackType.CLIENT_RESOURCES, "dummy");
for (int i = untrustedPacks.size() - 1; i >= 0; i--) {
frm.push(untrustedPacks.get(i));
}
for (ResourceLocation blockstate : uncertainLocations) {
if (knownLocations.contains(blockstate))
continue; // don't check ones we know exist
ResourceLocation fileLocation = new ResourceLocation(blockstate.getNamespace(), filePrefix + blockstate.getPath() + ".json");
Optional<Resource> resource = frm.getResource(fileLocation);
if(resource.isPresent())
knownLocations.add(blockstate);
}
}
}
/**
* Load all blockstate JSONs and model files, collect textures.
*/
private void gatherModelMaterials(Set<Material> materialSet) {
Stopwatch stopwatch = Stopwatch.createStarted();
List<CompletableFuture<Pair<ResourceLocation, JsonElement>>> blockStateData = new ArrayList<>();
final Object2IntOpenHashMap<String> blockstateErrors = new Object2IntOpenHashMap<>();
/*
* First, gather all vanilla packs, and use listResources on them. This will allow us to (hopefully) avoid
* scanning most packs a lot.
*/
List<PackResources> allPackResources = new ArrayList<>(this.resourceManager.listPacks().collect(Collectors.toList()));
Collections.reverse(allPackResources);
ObjectOpenHashSet<ResourceLocation> allAvailableModels = new ObjectOpenHashSet<>(), allAvailableStates = new ObjectOpenHashSet<>();
allPackResources.removeIf(pack -> {
if(trustedResourcePack(pack)) {
for(String namespace : pack.getNamespaces(PackType.CLIENT_RESOURCES)) {
Collection<ResourceLocation> allBlockstates = pack.getResources(PackType.CLIENT_RESOURCES, namespace, "blockstates", p -> p.getPath().endsWith(".json"));
for(ResourceLocation blockstate : allBlockstates) {
allAvailableStates.add(new ResourceLocation(blockstate.getNamespace(), blockstate.getPath().replace("blockstates/", "").replace(".json", "")));
}
Collection<ResourceLocation> allModels = pack.getResources(PackType.CLIENT_RESOURCES, namespace, "models", p -> p.getPath().endsWith(".json"));
for(ResourceLocation blockstate : allModels) {
allAvailableModels.add(new ResourceLocation(blockstate.getNamespace(), blockstate.getPath().replace("models/", "").replace(".json", "")));
}
}
return true;
}
ModernFix.LOGGER.debug("Pack with class {} needs manual scan", pack.getClass().getName());
return false;
});
gatherAdditionalViaManualScan(allPackResources, allAvailableStates, blockStateFiles, "blockstates/");
// We now have a list of all blockstates known to exist. Delete anything that we don't have
blockStateFiles.retainAll(allAvailableStates);
allAvailableStates.clear();
allAvailableStates.trim();
for(ResourceLocation blockstate : blockStateFiles) {
blockStateData.add(CompletableFuture.supplyAsync(() -> {
ResourceLocation fileLocation = new ResourceLocation(blockstate.getNamespace(), "blockstates/" + blockstate.getPath() + ".json");
@ -211,7 +291,7 @@ public abstract class ModelBakeryMixin implements IExtendedModelBakery {
JsonParser parser = new JsonParser();
return Pair.of(blockstate, parser.parse(new InputStreamReader(stream, StandardCharsets.UTF_8)));
} catch(IOException | JsonParseException e) {
ModernFix.LOGGER.error("Error reading blockstate {}: {}", blockstate, e);
logOrSuppressError(blockstateErrors, "blockstate", blockstate, e);
}
}
return Pair.of(blockstate, null);
@ -261,12 +341,25 @@ public abstract class ModelBakeryMixin implements IExtendedModelBakery {
}
} catch(RuntimeException e) {
ModernFix.LOGGER.error("Error with blockstate {}: {}", pair.getFirst(), e);
logOrSuppressError(blockstateErrors, "blockstate", pair.getFirst(), e);
}
}
}
blockstateErrors.object2IntEntrySet().forEach(entry -> {
if(entry.getIntValue() > ERROR_THRESHOLD) {
ModernFix.LOGGER.error("Suppressed additional {} blockstate errors for domain {}", entry.getIntValue(), entry.getKey());
}
});
blockstateErrors.clear();
blockStateData = null;
/* figure out which models we should actually load */
gatherAdditionalViaManualScan(allPackResources, allAvailableModels, modelFiles, "models/");
modelFiles.retainAll(allAvailableModels);
allAvailableModels.clear();
allAvailableModels.trim();
Map<ResourceLocation, BlockModel> basicModels = new HashMap<>();
basicModels.put(MISSING_MODEL_LOCATION, (BlockModel)missingModel);
basicModels.put(new ResourceLocation("builtin/generated"), GENERATION_MARKER);
@ -285,7 +378,7 @@ public abstract class ModelBakeryMixin implements IExtendedModelBakery {
JsonParser parser = new JsonParser();
return Pair.of(model, parser.parse(new InputStreamReader(stream, StandardCharsets.UTF_8)));
} catch(IOException | JsonParseException e) {
ModernFix.LOGGER.error("Error reading model {}: {}", fileLocation, e);
logOrSuppressError(blockstateErrors, "model", fileLocation, e);
}
}
return Pair.of(fileLocation, null);
@ -306,12 +399,17 @@ public abstract class ModelBakeryMixin implements IExtendedModelBakery {
continue;
}
} catch(Throwable e) {
ModernFix.LOGGER.warn("Unable to parse {}: {}", pair.getFirst(), e);
logOrSuppressError(blockstateErrors, "model", pair.getFirst(), e);
}
basicModels.put(pair.getFirst(), (BlockModel)missingModel);
}
UVController.useDummyUv.set(Boolean.FALSE);
}
blockstateErrors.object2IntEntrySet().forEach(entry -> {
if(entry.getIntValue() > ERROR_THRESHOLD) {
ModernFix.LOGGER.error("Suppressed additional {} model errors for domain {}", entry.getIntValue(), entry.getKey());
}
});
modelFiles = null;
Function<ResourceLocation, UnbakedModel> modelGetter = loc -> {
UnbakedModel m = basicModels.get(loc);

View File

@ -15,6 +15,7 @@ import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import team.chisel.ctm.CTM;
import team.chisel.ctm.client.model.AbstractCTMBakedModel;
@ -36,6 +37,11 @@ public abstract class TextureMetadataHandlerMixin {
MinecraftForge.EVENT_BUS.addListener(this::onDynamicModelBake);
}
@Redirect(method = "onModelBake", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/BakedModel;isCustomRenderer()Z"))
private boolean checkModelValid(BakedModel model) {
return model == null || model.isCustomRenderer();
}
public void onDynamicModelBake(DynamicModelBakeEvent event) {
UnbakedModel rootModel = event.getUnbakedModel();
BakedModel baked = event.getModel();

View File

@ -1,15 +1,29 @@
package org.embeddedt.modernfix.mixin.perf.fast_registry_validation;
import net.minecraftforge.fml.util.ObfuscationReflectionHelper;
import com.google.common.collect.BiMap;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.registries.ForgeRegistry;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.Marker;
import org.embeddedt.modernfix.registry.FastForgeRegistry;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.lang.reflect.Method;
import java.util.*;
@Mixin(value = ForgeRegistry.class, remap = false)
public class ForgeRegistryMixin {
public class ForgeRegistryMixin<V> {
private static Method bitSetTrimMethod = null;
private static boolean bitSetTrimMethodRetrieved = false;
@ -25,4 +39,71 @@ public class ForgeRegistryMixin {
}
return bitSetTrimMethod;
}
private int expectedNextBit = -1;
/**
* Avoid calling nextClearBit and scanning the whole registry for every block registration.
*/
@Redirect(method = "add(ILnet/minecraft/resources/ResourceLocation;Ljava/lang/Object;Ljava/lang/String;)I", at = @At(value = "INVOKE", target = "Ljava/util/BitSet;nextClearBit(I)I"))
private int useCachedBit(BitSet availabilityMap, int minimum) {
int bit = availabilityMap.nextClearBit(expectedNextBit != -1 ? expectedNextBit : minimum);
expectedNextBit = bit + 1;
return bit;
}
@Inject(method = { "sync", "clear", "block" }, at = @At("HEAD"))
private void clearBitCache(CallbackInfo ci) {
expectedNextBit = -1;
}
@Inject(method = "createAndAddDummy", at = @At(value = "INVOKE", target = "Ljava/util/BitSet;clear(I)V"))
private void clearBitCache2(CallbackInfo ci) {
expectedNextBit = -1;
}
@Redirect(method = "add(ILnet/minecraft/resources/ResourceLocation;Ljava/lang/Object;Ljava/lang/String;)I", at = @At(value = "INVOKE", target = "Lorg/apache/logging/log4j/Logger;trace(Lorg/apache/logging/log4j/Marker;Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V"))
private void skipTrace(Logger logger, Marker marker, String s, Object o, Object o1, Object o2, Object o3, Object o4) {
}
@Shadow @Final @Mutable private BiMap<Integer, V> ids;
@Shadow @Final @Mutable private BiMap<ResourceKey<V>, V> keys;
@Shadow @Final private ResourceKey<Registry<V>> key;
@Shadow @Final @Mutable private BiMap<ResourceLocation, V> names;
@Shadow @Final @Mutable private BiMap owners;
private FastForgeRegistry<V> fastRegistry;
/**
* The following code replaces the Forge HashBiMaps with a more efficient data structure based around
* an array list for IDs and one HashMap going from value -> information.
*/
@Inject(method = "<init>", at = @At("RETURN"))
private void replaceBackingMaps(CallbackInfo ci) {
this.fastRegistry = new FastForgeRegistry<>(this.key);
this.ids = fastRegistry.getIds();
this.keys = fastRegistry.getKeys();
this.names = fastRegistry.getNames();
this.owners = fastRegistry.getOwners();
}
@Inject(method = "freeze", at = @At("RETURN"))
private void optimizeRegistry(CallbackInfo ci) {
this.fastRegistry.optimize();
}
@Redirect(method = "sync", at = @At(value = "INVOKE", target = "Lcom/google/common/collect/BiMap;clear()V"))
private void clearBiMap(BiMap map) {
if(map == this.owners) {
this.fastRegistry.clear();
} else if(map == this.keys || map == this.names || map == this.ids) {
// do nothing, the registry is faster at clearing everything at once
} else
map.clear();
}
}

View File

@ -0,0 +1,33 @@
package org.embeddedt.modernfix.mixin.perf.fast_registry_validation;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.registries.ForgeRegistry;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Map;
import java.util.Set;
@Mixin(ForgeRegistry.Snapshot.class)
public class ForgeRegistrySnapshotMixin {
@Shadow @Final @Mutable public Map<ResourceLocation, Integer> ids;
@Shadow @Final @Mutable public Set<ResourceLocation> dummied;
/**
* The only good reason to use tree maps here is to keep the order the same. But we are tracking IDs
* anyway so order shouldn't matter. We replace the maps that will be most used.
*/
@Inject(method = "<init>", at = @At("RETURN"))
private void replaceSnapshotMaps(CallbackInfo ci) {
this.ids = new Object2ObjectOpenHashMap<>();
this.dummied = new ObjectOpenHashSet<>();
}
}

View File

@ -0,0 +1,28 @@
package org.embeddedt.modernfix.mixin.perf.fast_registry_validation;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Map;
@Mixin(ResourceKey.class)
public class ResourceKeyMixin<T> {
private static Map<ResourceLocation, Map<ResourceLocation, ResourceKey<?>>> INTERNING_MAP = new Object2ObjectOpenHashMap<>();
@Inject(method = "create(Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/resources/ResourceLocation;)Lnet/minecraft/resources/ResourceKey;", at = @At("HEAD"), cancellable = true)
private static <T> void createEfficient(ResourceLocation parent, ResourceLocation location, CallbackInfoReturnable<ResourceKey<T>> cir) {
synchronized (ResourceKey.class) {
Map<ResourceLocation, ResourceKey<?>> keys = INTERNING_MAP.computeIfAbsent(parent, k -> new Object2ObjectOpenHashMap<>());
ResourceKey<?> key = keys.get(location);
if(key == null) {
key = new ResourceKey<>(parent, location);
keys.put(location, key);
}
cir.setReturnValue((ResourceKey<T>)key);
}
}
}

View File

@ -0,0 +1,602 @@
package org.embeddedt.modernfix.registry;
import com.google.common.collect.BiMap;
import com.google.common.collect.Iterators;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
public class FastForgeRegistry<V> {
private final BiMap<Integer, V> ids;
private final DataFieldBiMap<ResourceLocation> names;
private final DataFieldBiMap<ResourceKey<V>> keys;
private final DataFieldBiMap<?> owners;
private final ResourceKey<Registry<V>> registryKey;
private final ObjectArrayList<V> valuesById;
private final Object2ObjectOpenHashMap<V, RegistryValueData> infoByValue;
private void storeId(V value, int id) {
RegistryValueData pair = infoByValue.computeIfAbsent(value, k -> new RegistryValueData());
pair.id = id;
}
private void updateInfoPairAndClearIfNull(V v, Consumer<RegistryValueData> consumer) {
infoByValue.compute(v, (oldValue, oldPair) -> {
if(oldPair == null)
oldPair = new RegistryValueData();
consumer.accept(oldPair);
if(oldPair.isEmpty())
return null;
else
return oldPair;
});
}
private void ensureArrayCanFitId(int id) {
int desiredSize = id + 1;
while(valuesById.size() < desiredSize) {
valuesById.add(null);
}
}
public void clear() {
this.infoByValue.clear();
for(int i = 0; i < this.valuesById.size(); i++) {
this.valuesById.set(i, null);
}
this.names.clearUnsafe();
this.keys.clearUnsafe();
this.owners.clearUnsafe();
}
public FastForgeRegistry(ResourceKey<Registry<V>> registryKey) {
this.registryKey = registryKey;
this.valuesById = new ObjectArrayList<>();
this.infoByValue = new Object2ObjectOpenHashMap<>();
this.keys = new DataFieldBiMap<>(p -> (ResourceKey<V>) p.key, (p, k) -> p.key = k);
this.owners = new DataFieldBiMap<>(p -> p.overrideOwner, (p, k) -> p.overrideOwner = k);
this.names = new DataFieldBiMap<>(p -> p.location, (p, l) -> p.location = l);
// IDs require a specialized implementation, as we back the K->V direction with an array
this.ids = new BiMap<Integer, V>() {
@Nullable
@Override
public V put(@Nullable Integer key, @Nullable V value) {
RegistryValueData data = infoByValue.get(value);
int unboxedKey = key;
if(data != null && data.id != -1 && data.id != unboxedKey)
throw new IllegalArgumentException("Existing mapping for ID " + data.id + " value " + value + " when new ID " + unboxedKey + " was requested");
ensureArrayCanFitId(unboxedKey);
V oldValue = valuesById.set(unboxedKey, value);
storeId(value, unboxedKey);
return oldValue;
}
@Nullable
@Override
public V forcePut(@Nullable Integer key, @Nullable V value) {
ensureArrayCanFitId(key);
V oldValue = valuesById.set(key, value);
if(oldValue != null) {
updateInfoPairAndClearIfNull(oldValue, pair -> pair.id = -1);
}
storeId(value, key);
return oldValue;
}
@Override
public void putAll(Map<? extends Integer, ? extends V> map) {
map.forEach(this::put);
}
@Override
public Set<V> values() {
return Collections.unmodifiableSet(infoByValue.keySet());
}
@Override
public BiMap<V, Integer> inverse() {
return new BiMap<V, Integer>() {
@Nullable
@Override
public Integer put(@Nullable V key, @Nullable Integer value) {
throw new UnsupportedOperationException();
}
@Nullable
@Override
public Integer forcePut(@Nullable V key, @Nullable Integer value) {
throw new UnsupportedOperationException();
}
@Override
public void putAll(Map<? extends V, ? extends Integer> map) {
throw new UnsupportedOperationException();
}
@Override
public Set<Integer> values() {
throw new UnsupportedOperationException();
}
@Override
public BiMap<Integer, V> inverse() {
throw new UnsupportedOperationException();
}
@Override
public int size() {
throw new UnsupportedOperationException();
}
@Override
public boolean isEmpty() {
throw new UnsupportedOperationException();
}
@Override
public boolean containsKey(Object key) {
throw new UnsupportedOperationException();
}
@Override
public boolean containsValue(Object value) {
throw new UnsupportedOperationException();
}
@Override
public Integer get(Object key) {
RegistryValueData pair = infoByValue.get(key);
if(pair == null)
return null;
return pair.id == -1 ? null : pair.id;
}
@Override
public Integer remove(Object key) {
RegistryValueData pair = infoByValue.get(key);
if(pair == null)
return null;
int id = pair.id;
if(id != -1)
valuesById.set(id, null);
updateInfoPairAndClearIfNull((V)key, p -> p.id = -1);
return id == -1 ? null : id;
}
@Override
public void clear() {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public Set<V> keySet() {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public Set<Entry<V, Integer>> entrySet() {
throw new UnsupportedOperationException();
}
};
}
@Override
public int size() {
return infoByValue.size();
}
@Override
public boolean isEmpty() {
throw new UnsupportedOperationException();
}
@Override
public boolean containsKey(Object key) {
throw new UnsupportedOperationException();
}
@Override
public boolean containsValue(Object value) {
throw new UnsupportedOperationException();
}
@Override
public V get(Object key) {
int id = (Integer)key;
if(id < 0 || id >= valuesById.size())
return null;
else
return valuesById.get(id);
}
@Override
public V remove(Object key) {
throw new UnsupportedOperationException();
}
@Override
public void clear() {
valuesById.clear();
infoByValue.values().removeIf(pair -> {
pair.id = -1;
return pair.isEmpty();
});
}
@NotNull
@Override
public Set<Integer> keySet() {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public Set<Entry<Integer, V>> entrySet() {
throw new UnsupportedOperationException();
}
@Override
public void forEach(BiConsumer<? super Integer, ? super V> action) {
for(int i = 0 ; i < valuesById.size(); i++) {
V val = valuesById.get(i);
if(val != null)
action.accept(i, val);
}
}
};
}
public void optimize() {
this.keys.optimize();
this.owners.optimize();
this.names.optimize();
this.infoByValue.trim();
}
public BiMap<Integer, V> getIds() {
return ids;
}
public BiMap<ResourceKey<V>, V> getKeys() {
return keys;
}
public BiMap<ResourceLocation, V> getNames() {
return names;
}
public DataFieldBiMap<?> getOwners() {
return owners;
}
/**
* Custom BiMap implementation that uses one internal hash map for the K->V direction, and the shared global
* information hash map for the V->K direction.
*/
class DataFieldBiMap<K> implements BiMap<K, V> {
public final Object2ObjectOpenHashMap<K, V> valuesByKey = new Object2ObjectOpenHashMap<>();
private final Function<RegistryValueData, K> getter;
private final BiConsumer<RegistryValueData, K> setter;
public DataFieldBiMap(Function<RegistryValueData, K> getter, BiConsumer<RegistryValueData, K> setter) {
this.getter = getter;
this.setter = setter;
}
public void optimize() {
this.valuesByKey.trim();
}
public void clearUnsafe() {
this.valuesByKey.clear();
}
private V forcePut(@Nullable K key, @Nullable V value, boolean throwOnExisting) {
if(throwOnExisting) {
RegistryValueData dataForValue = infoByValue.get(value);
// null check could be wrong if null is a valid value but doesn't matter in Forge's use
if(dataForValue != null && getter.apply(dataForValue) != null && !Objects.equals(getter.apply(dataForValue), key)) {
throw new IllegalArgumentException("Existing mapping for key " + key + " value " + value);
}
}
V oldValue = valuesByKey.put(key, value);
if(oldValue != null) {
updateInfoPairAndClearIfNull(oldValue, p -> setter.accept(p, null));
}
updateInfoPairAndClearIfNull(value, p -> setter.accept(p, key));
return oldValue;
}
@Nullable
@Override
public V put(@Nullable K key, @Nullable V value) {
return forcePut(key, value, true);
}
@Nullable
@Override
public V forcePut(@Nullable K key, @Nullable V value) {
return forcePut(key, value, false);
}
@Override
public void putAll(Map<? extends K, ? extends V> map) {
map.forEach(this::put);
}
@Override
public Set<V> values() {
return Collections.unmodifiableSet(infoByValue.keySet());
}
private DataFieldBiMapInverse<K> inverse = null;
@Override
public BiMap<V, K> inverse() {
if(inverse == null)
inverse = new DataFieldBiMapInverse<>(this);
return inverse;
}
@Override
public int size() {
return valuesByKey.size();
}
@Override
public boolean isEmpty() {
return valuesByKey.isEmpty();
}
@Override
public boolean containsKey(Object key) {
return valuesByKey.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
return infoByValue.containsKey(value);
}
@Override
public V get(Object key) {
return valuesByKey.get(key);
}
@Override
public V remove(Object key) {
V value = get(key);
if(value == null)
return null;
inverse().remove(value);
return value;
}
@Override
public void clear() {
valuesByKey.values().forEach(v -> updateInfoPairAndClearIfNull(v, p -> p.key = null));
valuesByKey.clear();
}
@NotNull
@Override
public Set<K> keySet() {
return Collections.unmodifiableSet(valuesByKey.keySet());
}
@NotNull
@Override
public Set<Map.Entry<K, V>> entrySet() {
return Collections.unmodifiableSet(valuesByKey.entrySet());
}
}
class DataFieldBiMapInverse<K> implements BiMap<V, K> {
private final DataFieldBiMap<K> forward;
public DataFieldBiMapInverse(DataFieldBiMap<K> forward) {
this.forward = forward;
}
@Nullable
@Override
public K put(@Nullable V key, @Nullable K value) {
throw new UnsupportedOperationException();
}
@Nullable
@Override
public K forcePut(@Nullable V key, @Nullable K value) {
throw new UnsupportedOperationException();
}
@Override
public void putAll(Map<? extends V, ? extends K> map) {
throw new UnsupportedOperationException();
}
@Override
public Set<K> values() {
throw new UnsupportedOperationException();
}
@Override
public BiMap<K, V> inverse() {
return forward;
}
@Override
public int size() {
throw new UnsupportedOperationException();
}
@Override
public boolean isEmpty() {
throw new UnsupportedOperationException();
}
@Override
public boolean containsKey(Object key) {
return infoByValue.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
return forward.valuesByKey.containsKey(value);
}
@Override
public K get(Object key) {
RegistryValueData pair = infoByValue.get(key);
if(pair == null)
return null;
else
return forward.getter.apply(pair);
}
@Override
public K remove(Object key) {
RegistryValueData pair = infoByValue.get(key);
if(pair == null)
return null;
else {
K rk = forward.getter.apply(pair);
forward.valuesByKey.remove(rk);
updateInfoPairAndClearIfNull((V)key, p -> forward.setter.accept(p, null));
return rk;
}
}
@Override
public void clear() {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public Set<V> keySet() {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public Set<Entry<V, K>> entrySet() {
throw new UnsupportedOperationException();
}
}
class FastForgeRegistryLocationSet implements Set<ResourceLocation> {
private final Set<ResourceKey<V>> backingSet;
public FastForgeRegistryLocationSet(Set<ResourceKey<V>> backingSet) {
this.backingSet = backingSet;
}
@Override
public int size() {
return backingSet.size();
}
@Override
public boolean isEmpty() {
return backingSet.isEmpty();
}
@Override
public boolean contains(Object o) {
return backingSet.contains(ResourceKey.create(FastForgeRegistry.this.registryKey, (ResourceLocation)o));
}
@NotNull
@Override
public Iterator<ResourceLocation> iterator() {
return Iterators.transform(backingSet.iterator(), ResourceKey::location);
}
@NotNull
@Override
public Object[] toArray() {
Object[] keyArray = backingSet.toArray();
for(int i = 0; i < keyArray.length; i++) {
keyArray[i] = ((ResourceKey<V>)keyArray[i]).location();
}
return keyArray;
}
@NotNull
@Override
public <T> T[] toArray(@NotNull T[] a) {
Object[] keyArray = backingSet.toArray();
T[] finalArray = Arrays.copyOf(a, keyArray.length);
for(int i = 0; i < keyArray.length; i++) {
finalArray[i] = (T)((ResourceKey<V>)keyArray[i]).location();
}
return finalArray;
}
@Override
public boolean add(ResourceLocation resourceLocation) {
throw new UnsupportedOperationException();
}
@Override
public boolean remove(Object o) {
throw new UnsupportedOperationException();
}
@Override
public boolean containsAll(@NotNull Collection<?> c) {
for(Object o : c) {
if(!contains(o))
return false;
}
return true;
}
@Override
public boolean addAll(@NotNull Collection<? extends ResourceLocation> c) {
throw new UnsupportedOperationException();
}
@Override
public boolean retainAll(@NotNull Collection<?> c) {
throw new UnsupportedOperationException();
}
@Override
public boolean removeAll(@NotNull Collection<?> c) {
throw new UnsupportedOperationException();
}
@Override
public void clear() {
throw new UnsupportedOperationException();
}
}
static class RegistryValueData {
public ResourceKey<?> key;
public ResourceLocation location;
public int id = -1;
public Object overrideOwner;
boolean isEmpty() {
return key == null && location == null && id == -1 && overrideOwner == null;
}
}
}

View File

@ -25,30 +25,30 @@ public class CachedResourcePath {
private static final String[] NO_PREFIX = new String[0];
public CachedResourcePath(Path path) {
this(NO_PREFIX, path, path.getNameCount());
this(NO_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)));
this(NO_PREFIX, SLASH_SPLITTER.splitToList(FileUtil.normalize(s)), false);
}
public <T> CachedResourcePath(String[] prefixElements, Collection<T> collection) {
this(prefixElements, collection, collection.size());
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) {
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] = PATH_COMPONENT_INTERNER.intern(prefixElements[i]);
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] = PATH_COMPONENT_INTERNER.intern(s);
components[i] = intern ? PATH_COMPONENT_INTERNER.intern(s) : s;
i++;
}
pathComponents = components;

View File

@ -98,7 +98,8 @@ public class CanonizingStringMap<T> implements Map<String, T> {
@NotNull
@Override
public Set<String> keySet() {
return Collections.unmodifiableSet(this.backingMap.keySet());
// has to be modifiable because mods (cough, Tinkers) use it to clear the tag
return this.backingMap.keySet();
}
@NotNull

View File

@ -28,4 +28,5 @@ public net.minecraft.server.level.ServerChunkCache$MainThreadExecutor
public net.minecraft.nbt.CompoundTag <init>(Ljava/util/Map;)V # <init>
public net.minecraft.client.renderer.texture.TextureAtlasSprite <init>(Lnet/minecraft/client/renderer/texture/TextureAtlas;Lnet/minecraft/client/renderer/texture/TextureAtlasSprite$Info;IIIIILcom/mojang/blaze3d/platform/NativeImage;)V # <init>
public net.minecraft.server.level.DistanceManager m_140792_(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V # addTicket
public net.minecraft.server.level.ChunkMap$DistanceManager
public net.minecraft.server.level.ChunkMap$DistanceManager
public net.minecraft.resources.ResourceKey <init>(Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/resources/ResourceLocation;)V # <init>

View File

@ -35,10 +35,12 @@
"feature.branding.BrandingControlMixin",
"feature.direct_stack_trace.CrashReportMixin",
"perf.nbt_memory_usage.CompoundTagMixin",
"perf.fast_registry_validation.ForgeRegistryMixin",
"perf.kubejs.RecipeEventJSMixin",
"perf.tag_id_caching.TagEntryMixin",
"perf.tag_id_caching.TagOrElementLocationMixin",
"perf.fast_registry_validation.ForgeRegistryMixin",
"perf.fast_registry_validation.ForgeRegistrySnapshotMixin",
"perf.fast_registry_validation.ResourceKeyMixin",
"perf.cache_strongholds.ChunkGeneratorMixin",
"perf.cache_upgraded_structures.StructureManagerMixin",
"perf.cache_strongholds.ServerLevelMixin",
@ -47,7 +49,8 @@
"perf.compress_blockstate.BlockBehaviourMixin",
"perf.dedup_blockstate_flattening_map.BlockStateDataMixin",
"perf.dedup_blockstate_flattening_map.ChunkPalettedStorageFixMixin",
"devenv.MinecraftServerMixin"
"devenv.MinecraftServerMixin",
"devenv.GameDataMixin"
],
"client": [
"core.MinecraftMixin",
@ -61,6 +64,7 @@
"perf.dynamic_resources.BlockElementFaceDeserializerMixin",
"perf.dynamic_resources.BlockModelShaperMixin",
"perf.dynamic_resources.ItemModelShaperMixin",
"perf.dynamic_resources.ItemRendererMixin",
"perf.dynamic_resources.ModelBakeryMixin",
"perf.dynamic_resources.ae2.RegistrationMixin",
"perf.dynamic_resources.ctm.TextureMetadataHandlerMixin",