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

This commit is contained in:
embeddedt 2023-04-29 15:24:02 -04:00
commit 1ae3f9f319
No known key found for this signature in database
GPG Key ID: A69433EC199B5613
26 changed files with 1347 additions and 28 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

@ -1,11 +1,14 @@
package org.embeddedt.modernfix;
import com.mojang.blaze3d.platform.InputConstants;
import com.mojang.datafixers.util.Pair;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.minecraft.client.KeyMapping;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.components.DebugScreenOverlay;
import net.minecraft.client.gui.screens.ConnectScreen;
import net.minecraft.client.gui.screens.TitleScreen;
import net.minecraftforge.client.ConfigScreenHandler;
import net.minecraftforge.client.event.CustomizeGuiOverlayEvent;
import net.minecraft.util.MemoryReserve;
import net.minecraftforge.client.event.ScreenEvent;
@ -14,6 +17,7 @@ import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.world.entity.Entity;
import net.minecraftforge.client.gui.overlay.ForgeGui;
import net.minecraftforge.client.event.*;
import net.minecraftforge.client.settings.KeyConflictContext;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.TagsUpdatedEvent;
import net.minecraftforge.event.TickEvent;
@ -21,13 +25,18 @@ import net.minecraftforge.event.level.LevelEvent;
import net.minecraftforge.event.server.ServerStartingEvent;
import net.minecraftforge.eventbus.api.EventPriority;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.IExtensionPoint;
import net.minecraftforge.fml.ModContainer;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.fml.util.ObfuscationReflectionHelper;
import net.minecraftforge.network.NetworkEvent;
import org.embeddedt.modernfix.core.ModernFixMixinPlugin;
import org.embeddedt.modernfix.core.config.ModernFixConfig;
import org.embeddedt.modernfix.packet.EntityIDSyncPacket;
import org.embeddedt.modernfix.screen.ModernFixConfigScreen;
import org.embeddedt.modernfix.world.IntegratedWatchdog;
import java.lang.management.ManagementFactory;
@ -53,6 +62,26 @@ public class ModernFixClient {
if(mfContainer.isPresent())
brandingString = "ModernFix " + mfContainer.get().getModInfo().getVersion().toString();
}
FMLJavaModLoadingContext.get().getModEventBus().addListener(this::keyBindRegister);
ModLoadingContext.get().registerExtensionPoint(
ConfigScreenHandler.ConfigScreenFactory.class,
() -> new ConfigScreenHandler.ConfigScreenFactory((mc, screen) -> new ModernFixConfigScreen(screen))
);
}
private KeyMapping configKey;
private void keyBindRegister(RegisterKeyMappingsEvent event) {
configKey = new KeyMapping("key.modernfix.config", KeyConflictContext.UNIVERSAL, InputConstants.UNKNOWN, "key.modernfix");
event.register(configKey);
}
@SubscribeEvent
public void onConfigKey(TickEvent.ClientTickEvent event) {
if(event.phase == TickEvent.Phase.START && configKey.consumeClick()) {
Minecraft.getInstance().setScreen(new ModernFixConfigScreen(Minecraft.getInstance().screen));
}
}
public void resetWorldLoadStateMachine() {

View File

@ -17,6 +17,8 @@ public class ModernFixEarlyConfig {
public static final boolean OPTIFINE_PRESENT;
private File configFile;
static {
boolean hasOfClass = false;
try {
@ -34,7 +36,8 @@ public class ModernFixEarlyConfig {
return FMLLoader.getLoadingModList().getModFileById(modId) != null;
}
private ModernFixEarlyConfig() {
private ModernFixEarlyConfig(File file) {
this.configFile = file;
// Defines the default rules which can be configured by the user or other mods.
// You must manually add a rule for any new mixins not covered by an existing package rule.
this.addMixinRule("core", true); // TODO: Don't actually allow the user to disable this
@ -63,6 +66,7 @@ public class ModernFixEarlyConfig {
this.addMixinRule("perf.fast_forge_dummies", true);
this.addMixinRule("perf.dynamic_structure_manager", true);
this.addMixinRule("bugfix.chunk_deadlock", true);
this.addMixinRule("bugfix.paper_chunk_patches", true);
this.addMixinRule("perf.thread_priorities", true);
this.addMixinRule("perf.scan_cache", true);
this.addMixinRule("perf.kubejs", modPresent("kubejs"));
@ -185,7 +189,7 @@ public class ModernFixEarlyConfig {
* created. The file on disk will then be updated to include any new options.
*/
public static ModernFixEarlyConfig load(File file) {
ModernFixEarlyConfig config = new ModernFixEarlyConfig();
ModernFixEarlyConfig config = new ModernFixEarlyConfig(file);
Properties props = new Properties();
if(file.exists()) {
try (FileInputStream fin = new FileInputStream(file)){
@ -197,7 +201,7 @@ public class ModernFixEarlyConfig {
}
try {
config.writeConfig(file, props);
config.save();
} catch (IOException e) {
LOGGER.warn("Could not write configuration file", e);
}
@ -205,8 +209,8 @@ public class ModernFixEarlyConfig {
return config;
}
private void writeConfig(File file, Properties props) throws IOException {
File dir = file.getParentFile();
public void save() throws IOException {
File dir = configFile.getParentFile();
if (!dir.exists()) {
if (!dir.mkdirs()) {
@ -216,23 +220,24 @@ public class ModernFixEarlyConfig {
throw new IOException("The parent file is not a directory");
}
try (Writer writer = new FileWriter(file)) {
try (Writer writer = new FileWriter(configFile)) {
writer.write("# This is the configuration file for ModernFix.\n");
writer.write("#\n");
writer.write("# The following options can be enabled or disabled if there is a compatibility issue.\n");
writer.write("# Add a line mixin.example_name=true/false without the # sign to enable/disable a rule.\n");
List<String> lines = this.options.keySet().stream()
List<String> keys = this.options.keySet().stream()
.filter(key -> !key.equals("mixin.core"))
.sorted()
.map(key -> "# " + key + "\n")
.collect(Collectors.toList());
for(String line : lines) {
writer.write(line);
for(String line : keys) {
if(!line.equals("mixin.core"))
writer.write("# " + line + "\n");
}
for (Map.Entry<Object, Object> entry : props.entrySet()) {
String key = (String) entry.getKey();
String value = (String) entry.getValue();
writer.write(key + "=" + value + "\n");
for (String key : keys) {
Option option = this.options.get(key);
if(option.isUserDefined())
writer.write(key + "=" + option.isEnabled() + "\n");
}
}
}
@ -251,4 +256,8 @@ public class ModernFixEarlyConfig {
.filter(Option::isOverridden)
.count();
}
public Map<String, Option> getOptionMap() {
return Collections.unmodifiableMap(this.options);
}
}

View File

@ -19,11 +19,15 @@ public class Option {
}
public void setEnabled(boolean enabled, boolean userDefined) {
if(this.enabled == enabled)
return;
this.enabled = enabled;
this.userDefined = userDefined;
}
public void addModOverride(boolean enabled, String modId) {
if(this.enabled == enabled)
return;
this.enabled = enabled;
if (this.modDefined == null) {
@ -57,6 +61,10 @@ public class Option {
this.modDefined = null;
}
public void clearUserDefined() {
this.userDefined = false;
}
public Collection<String> getDefiningMods() {
return this.modDefined != null ? Collections.unmodifiableCollection(this.modDefined) : Collections.emptyList();
}

View File

@ -0,0 +1,5 @@
package org.embeddedt.modernfix.duck;
public interface IPaperChunkHolder {
boolean mfix$canAdvanceStatus();
}

View File

@ -34,6 +34,7 @@ public class DynamicBakedModelProvider implements Map<ResourceLocation, BakedMod
public void setMissingModel(BakedModel model) {
this.missingModel = model;
this.permanentOverrides.put(ModelBakery.MISSING_MODEL_LOCATION, this.missingModel);
}
private static Triple<ResourceLocation, Transformation, Boolean> vanillaKey(Object o) {

View File

@ -10,8 +10,10 @@ 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;
import net.minecraftforge.registries.ForgeRegistries;
import java.util.ArrayList;
import java.util.Collections;
@ -20,7 +22,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 +31,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(ForgeRegistries.ITEMS.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,61 @@
package org.embeddedt.modernfix.mixin.bugfix.paper_chunk_patches;
import com.mojang.datafixers.util.Either;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkStatus;
import org.embeddedt.modernfix.duck.IPaperChunkHolder;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Mixin(ChunkHolder.class)
public abstract class ChunkHolderMixin implements IPaperChunkHolder {
@Shadow public abstract CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getFutureIfPresentUnchecked(ChunkStatus arg);
@Shadow @Final private static List<ChunkStatus> CHUNK_STATUSES;
public ChunkStatus mfix$getChunkHolderStatus() {
for (ChunkStatus curr = ChunkStatus.FULL, next = curr.getParent(); curr != next; curr = next, next = next.getParent()) {
CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> future = this.getFutureIfPresentUnchecked(curr);
Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> either = future.getNow(null);
if (either == null || !either.left().isPresent()) {
continue;
}
return curr;
}
return null;
}
public ChunkAccess mfix$getAvailableChunkNow() {
// TODO can we just getStatusFuture(EMPTY)?
for (ChunkStatus curr = ChunkStatus.FULL, next = curr.getParent(); curr != next; curr = next, next = next.getParent()) {
CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> future = this.getFutureIfPresentUnchecked(curr);
Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> either = future.getNow(null);
if (either == null || !either.left().isPresent()) {
continue;
}
return either.left().get();
}
return null;
}
private static ChunkStatus mfix$getNextStatus(ChunkStatus status) {
if (status == ChunkStatus.FULL) {
return status;
}
return CHUNK_STATUSES.get(status.getIndex() + 1);
}
@Override
public boolean mfix$canAdvanceStatus() {
ChunkStatus status = mfix$getChunkHolderStatus();
ChunkAccess chunk = mfix$getAvailableChunkNow();
return chunk != null && (status == null || chunk.getStatus().isOrAfter(mfix$getNextStatus(status)));
}
}

View File

@ -0,0 +1,115 @@
package org.embeddedt.modernfix.mixin.bugfix.paper_chunk_patches;
import com.mojang.datafixers.util.Either;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.*;
import net.minecraft.server.level.progress.ChunkProgressListener;
import net.minecraft.util.thread.BlockableEventLoop;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
import net.minecraftforge.server.ServerLifecycleHooks;
import org.embeddedt.modernfix.duck.IPaperChunkHolder;
import org.spongepowered.asm.mixin.Final;
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.ModifyArg;
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.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Function;
@Mixin(ChunkMap.class)
public abstract class ChunkMapMixin {
@Shadow @Final private BlockableEventLoop<Runnable> mainThreadExecutor;
@Shadow @Final private ChunkMap.DistanceManager distanceManager;
@Shadow protected abstract CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> protoChunkToFullChunk(ChunkHolder arg);
@Shadow @Final private ServerLevel level;
@Shadow @Final private ThreadedLevelLightEngine lightEngine;
@Shadow @Final private ChunkProgressListener progressListener;
@Shadow protected abstract CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> scheduleChunkGeneration(ChunkHolder chunkHolder, ChunkStatus chunkStatus);
@Shadow @Final private StructureTemplateManager structureTemplateManager;
private Executor mainInvokingExecutor;
@Inject(method = "<init>", at = @At("RETURN"), cancellable = true)
private void setup(CallbackInfo ci) {
this.mainInvokingExecutor = (runnable) -> {
if(ServerLifecycleHooks.getCurrentServer().isSameThread())
runnable.run();
else
this.mainThreadExecutor.execute(runnable);
};
}
/* https://github.com/PaperMC/Paper/blob/ver/1.17.1/patches/server/0752-Fix-chunks-refusing-to-unload-at-low-TPS.patch */
@ModifyArg(method = "prepareAccessibleChunk", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;thenApplyAsync(Ljava/util/function/Function;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"), index = 1)
private Executor useMainThreadExecutor(Executor executor) {
return this.mainThreadExecutor;
}
/* https://github.com/PaperMC/Paper/blob/master/patches/removed/1.19.2-legacy-chunksystem/0482-Improve-Chunk-Status-Transition-Speed.patch */
@ModifyArg(method = "prepareEntityTickingChunk", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;thenApplyAsync(Ljava/util/function/Function;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"), index = 1)
private Executor useMainInvokingExecutor(Executor executor) {
return this.mainInvokingExecutor;
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Redirect(method = "scheduleChunkGeneration", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;thenComposeAsync(Ljava/util/function/Function;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"))
private CompletableFuture skipWorkerIfPossible(CompletableFuture inputFuture, Function function, Executor executor, ChunkHolder holder) {
Executor targetExecutor = (runnable) -> {
if(((IPaperChunkHolder)holder).mfix$canAdvanceStatus()) {
this.mainInvokingExecutor.execute(runnable);
return;
}
executor.execute(runnable);
};
return inputFuture.thenComposeAsync(function, targetExecutor);
}
/**
* @author embeddedt
* @reason revert 1.17 chunk system changes, significantly reduces time and RAM needed to load chunks
*/
@Inject(method = "schedule", at = @At("HEAD"), cancellable = true)
private void useLegacySchedulingLogic(ChunkHolder holder, ChunkStatus requiredStatus, CallbackInfoReturnable<CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>>> cir) {
if(requiredStatus != ChunkStatus.EMPTY) {
ChunkPos chunkpos = holder.getPos();
CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> future = holder.getOrScheduleFuture(requiredStatus.getParent(), (ChunkMap)(Object)this);
cir.setReturnValue(future.thenComposeAsync((either) -> {
Optional<ChunkAccess> optional = either.left();
if(!optional.isPresent())
return CompletableFuture.completedFuture(either);
if (requiredStatus == ChunkStatus.LIGHT) {
this.distanceManager.addTicket(TicketType.LIGHT, chunkpos, 33 + ChunkStatus.getDistance(ChunkStatus.LIGHT), chunkpos);
}
// from original method
if (optional.get().getStatus().isOrAfter(requiredStatus)) {
CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completablefuture = requiredStatus.load(this.level, this.structureTemplateManager, this.lightEngine, (arg2) -> {
return this.protoChunkToFullChunk(holder);
}, (ChunkAccess)optional.get());
this.progressListener.onStatusChange(chunkpos, requiredStatus);
return completablefuture;
} else {
return this.scheduleChunkGeneration(holder, requiredStatus);
}
}, this.mainThreadExecutor).thenComposeAsync(CompletableFuture::completedFuture, this.mainThreadExecutor));
}
}
}

View File

@ -0,0 +1,51 @@
package org.embeddedt.modernfix.mixin.bugfix.paper_chunk_patches;
import net.minecraft.util.SortedArraySet;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import java.util.AbstractSet;
import java.util.Arrays;
import java.util.function.Predicate;
@Mixin(SortedArraySet.class)
public abstract class SortedArraySetMixin<T> extends AbstractSet<T> {
@Shadow private int size;
@Shadow private T[] contents;
// Paper start - optimise removeIf
@Override
public boolean removeIf(Predicate<? super T> filter) {
// prev. impl used an iterator, which could be n^2 and creates garbage
int i = 0, len = this.size;
T[] backingArray = this.contents;
for (;;) {
if (i >= len) {
return false;
}
if (!filter.test(backingArray[i])) {
++i;
continue;
}
break;
}
// we only want to write back to backingArray if we really need to
int lastIndex = i; // this is where new elements are shifted to
for (; i < len; ++i) {
T curr = backingArray[i];
if (!filter.test(curr)) { // if test throws we're screwed
backingArray[lastIndex++] = curr;
}
}
// cleanup end
Arrays.fill(backingArray, lastIndex, len, null);
this.size = lastIndex;
return true;
}
}

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

@ -5,9 +5,11 @@ import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.core.Holder;
import net.minecraft.resources.ResourceLocation;
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 +25,8 @@ public abstract class ItemModelShaperMixin extends ItemModelShaper {
super(arg);
}
private static final ModelResourceLocation SENTINEL = new ModelResourceLocation(new ResourceLocation("modernfix", "sentinel"), "sentinel");
/**
* @reason Get the stored location for that item and meta, and get the model
* from that location from the model manager.
@ -30,7 +34,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

@ -12,6 +12,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;
@ -33,6 +34,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

@ -0,0 +1,30 @@
package org.embeddedt.modernfix.mixin.perf.dynamic_resources.rs;
import com.refinedmods.refinedstorage.render.BakedModelOverrideRegistry;
import com.refinedmods.refinedstorage.setup.ClientSetup;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.common.MinecraftForge;
import org.embeddedt.modernfix.dynamicresources.DynamicModelBakeEvent;
import org.spongepowered.asm.mixin.Final;
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.callback.CallbackInfo;
@Mixin(ClientSetup.class)
public class ClientSetupMixin {
@Shadow @Final private static BakedModelOverrideRegistry BAKED_MODEL_OVERRIDE_REGISTRY;
@Inject(method = "onClientSetup", at = @At("RETURN"), remap = false)
private static void addDynamicListener(CallbackInfo ci) {
MinecraftForge.EVENT_BUS.addListener(ClientSetupMixin::onDynamicModelBake);
}
private static void onDynamicModelBake(DynamicModelBakeEvent event) {
BakedModelOverrideRegistry.BakedModelOverrideFactory factory = BAKED_MODEL_OVERRIDE_REGISTRY.get(event.getLocation() instanceof ModelResourceLocation ? new ResourceLocation(event.getLocation().getNamespace(), event.getLocation().getPath()) : event.getLocation());
if(factory != null)
event.setModel(factory.create(event.getModel(), event.getModelBakery().getBakedTopLevelModels()));
}
}

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

@ -0,0 +1,40 @@
package org.embeddedt.modernfix.screen;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.CommonComponents;
import net.minecraft.network.chat.Component;
import javax.annotation.Nullable;
public class ModernFixConfigScreen extends Screen {
private OptionList optionList;
private Screen lastScreen;
public boolean madeChanges = false;
private Button doneButton;
public ModernFixConfigScreen(@Nullable Screen lastScreen) {
super(Component.translatable("modernfix.config"));
this.lastScreen = lastScreen;
}
@Override
protected void init() {
this.optionList = new OptionList(this, this.minecraft);
this.addWidget(this.optionList);
this.doneButton = new Button.Builder(CommonComponents.GUI_DONE, (arg) -> {
this.minecraft.setScreen(lastScreen);
}).pos(this.width / 2 - 100, this.height - 29).size(200, 20).build();
this.addRenderableWidget(this.doneButton);
}
@Override
public void render(PoseStack poseStack, int mouseX, int mouseY, float partialTicks) {
this.renderBackground(poseStack);
this.optionList.render(poseStack, mouseX, mouseY, partialTicks);
drawCenteredString(poseStack, this.font, this.title, this.width / 2, 8, 16777215);
this.doneButton.setMessage(madeChanges ? Component.translatable("modernfix.config.done_restart") : CommonComponents.GUI_DONE);
super.render(poseStack, mouseX, mouseY, partialTicks);
}
}

View File

@ -0,0 +1,122 @@
package org.embeddedt.modernfix.screen;
import com.google.common.collect.ImmutableList;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.ChatFormatting;
import net.minecraft.client.KeyMapping;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.components.ContainerObjectSelectionList;
import net.minecraft.client.gui.components.events.GuiEventListener;
import net.minecraft.client.gui.components.toasts.SystemToast;
import net.minecraft.client.gui.narration.NarratableEntry;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.core.ModernFixMixinPlugin;
import org.embeddedt.modernfix.core.config.Option;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
public class OptionList extends ContainerObjectSelectionList<OptionList.Entry> {
private final int maxNameWidth;
private static final Component OPTION_ON = Component.translatable("modernfix.option.on").withStyle(style -> style.withColor(ChatFormatting.GREEN));
private static final Component OPTION_OFF = Component.translatable("modernfix.option.off").withStyle(style -> style.withColor(ChatFormatting.RED));
private ModernFixConfigScreen mainScreen;
public OptionList(ModernFixConfigScreen arg, Minecraft arg2) {
super(arg2,arg.width + 45, arg.height, 43, arg.height - 32, 20);
this.mainScreen = arg;
int maxW = 0;
Map<String, Option> optionMap = ModernFixMixinPlugin.config.getOptionMap();
List<String> sortedKeys = optionMap.keySet().stream().filter(key -> !key.equals("mixin.core")).sorted().collect(Collectors.toList());
for(String key : sortedKeys) {
Option option = optionMap.get(key);
int w = this.minecraft.font.width(key);
maxW = Math.max(w, maxW);
this.addEntry(new OptionEntry(key, option));
}
this.maxNameWidth = maxW;
}
protected int getScrollbarPosition() {
return super.getScrollbarPosition() + 15 + 20;
}
public int getRowWidth() {
return super.getRowWidth() + 32;
}
class OptionEntry extends Entry {
private final String name;
private final Button toggleButton;
private final Option option;
public OptionEntry(String optionName, Option option) {
this.name = optionName;
this.option = option;
this.toggleButton = new Button.Builder(Component.literal(""), (arg) -> {
this.option.setEnabled(!this.option.isEnabled(), !this.option.isUserDefined());
try {
ModernFixMixinPlugin.config.save();
if(!OptionList.this.mainScreen.madeChanges) {
OptionList.this.mainScreen.madeChanges = true;
}
} catch(IOException e) {
// revert
this.option.setEnabled(!this.option.isEnabled(), !this.option.isUserDefined());
ModernFix.LOGGER.error("Unable to save config", e);
}
}).pos(0, 0).size(95, 20).build();
}
@Override
public void render(PoseStack matrixStack, int index, int top, int left, int width, int height, int mouseX, int mouseY, boolean isMouseOver, float partialTicks) {
MutableComponent nameComponent = Component.literal(this.name);
if(this.option.isUserDefined())
nameComponent = nameComponent.withStyle(ChatFormatting.ITALIC).append(Component.translatable("modernfix.config.not_default"));
OptionList.this.minecraft.font.draw(matrixStack, nameComponent, (float)(left + 160 - OptionList.this.maxNameWidth), (float)(top + height / 2 - 4), 16777215);
this.toggleButton.setPosition(left + 175, top);
this.toggleButton.setMessage(getOptionMessage(this.option));
this.toggleButton.render(matrixStack, mouseX, mouseY, partialTicks);
}
private Component getOptionMessage(Option option) {
return option.isEnabled() ? OPTION_ON : OPTION_OFF;
}
@Override
public List<? extends GuiEventListener> children() {
return ImmutableList.of(this.toggleButton);
}
public boolean mouseClicked(double mouseX, double mouseY, int button) {
return this.toggleButton.mouseClicked(mouseX, mouseY, button);
}
public boolean mouseReleased(double mouseX, double mouseY, int button) {
return this.toggleButton.mouseReleased(mouseX, mouseY, button);
}
@Override
public List<? extends NarratableEntry> narratables() {
return Collections.emptyList();
}
}
public abstract static class Entry extends ContainerObjectSelectionList.Entry<Entry> {
public Entry() {
}
}
}

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

@ -33,3 +33,6 @@ public net.minecraft.client.resources.model.ModelBakery$ModelBakerImpl
public net.minecraft.client.resources.model.ModelBakery$ModelBakerImpl <init>(Lnet/minecraft/client/resources/model/ModelBakery;Ljava/util/function/BiFunction;Lnet/minecraft/resources/ResourceLocation;)V # <init>
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.resources.ResourceKey <init>(Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/resources/ResourceLocation;)V # <init>

View File

@ -1,6 +1,13 @@
{
"key.modernfix": "ModernFix",
"key.modernfix.config": "Open config screen",
"modernfix.jei_load": "Loading JEI, this may take a while",
"modernfix.no_lazydfu": "ModernFix detected that DFU rules were compiled on startup. This slows down game launching. Installing LazyDFU to resolve this is highly recommended.",
"modernfix.config": "ModernFix mixin config",
"modernfix.config.done_restart": "Done (restart required)",
"modernfix.option.on": "on",
"modernfix.option.off": "off",
"modernfix.config.not_default": " (modified)",
"asynclocator.map.locating": "Map (Locating...)",
"asynclocator.map.none": "Map (No nearby feature found)"
}

View File

@ -8,6 +8,9 @@
"mixins": [
"core.BootstrapMixin",
"bugfix.edge_chunk_not_saved.ChunkManagerMixin",
"bugfix.paper_chunk_patches.ChunkMapMixin",
"bugfix.paper_chunk_patches.ChunkHolderMixin",
"bugfix.paper_chunk_patches.SortedArraySetMixin",
"perf.dynamic_structure_manager.StructureManagerMixin",
"bugfix.chunk_deadlock.ServerChunkCacheMixin",
"perf.dedicated_reload_executor.MinecraftServerMixin",
@ -29,10 +32,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",
@ -41,7 +46,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",
@ -55,9 +61,11 @@
"perf.dynamic_resources.BlockModelShaperMixin",
"perf.dynamic_resources.ItemModelShaperMixin",
"perf.dynamic_resources.ModelBakerImplMixin",
"perf.dynamic_resources.ItemRendererMixin",
"perf.dynamic_resources.ModelBakeryMixin",
"perf.dynamic_resources.ModelManagerMixin",
"perf.dynamic_resources.ae2.RegistrationMixin",
"perf.dynamic_resources.rs.ClientSetupMixin",
"perf.dynamic_resources.ctm.TextureMetadataHandlerMixin",
"perf.dynamic_resources.ctm.CTMPackReloadListenerMixin",
"perf.dynamic_resources.supermartijncore.ClientRegistrationHandlerMixin",