Merge branch 'main' into 1.18

This commit is contained in:
embeddedt 2023-02-22 19:38:48 -05:00
commit 17e526bcfe
No known key found for this signature in database
GPG Key ID: A69433EC199B5613
24 changed files with 556 additions and 47 deletions

View File

@ -15,7 +15,7 @@ jobs:
- uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '8'
java-version: '17'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build the mod

View File

@ -7,7 +7,7 @@ plugins {
sourceCompatibility = targetCompatibility = JavaVersion.VERSION_17
group = 'org.embeddedt'
version = '1.6.0-beta3'
version = '1.7.0'
java {
archivesBaseName = 'modernfix-mc' + minecraft_version
@ -43,6 +43,24 @@ repositories {
name = 'ParchmentMC'
url = 'https://maven.parchmentmc.org'
}
maven {
// Shedaniel's maven (Architectury API)
url = "https://maven.architectury.dev"
content {
includeGroup "me.shedaniel"
}
}
maven {
// saps.dev Maven (KubeJS and Rhino)
url = "https://maven.saps.dev/minecraft"
content {
includeGroup "dev.latvian.mods"
}
}
maven { // CTM
url "https://maven.tterrag.com/"
}
}
dependencies {
@ -141,6 +159,7 @@ curseforge {
releaseType = "release"
addGameVersion "Forge"
addGameVersion minecraft_version
mainArtifact remapJar
}
}
}

View File

@ -6,6 +6,7 @@ import net.minecraftforge.event.server.ServerStartedEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.DistExecutor;
import net.minecraftforge.fml.IExtensionPoint;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.config.ModConfig;
@ -17,6 +18,10 @@ import org.apache.logging.log4j.Logger;
import org.embeddedt.modernfix.core.config.ModernFixConfig;
import java.lang.management.ManagementFactory;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.function.BooleanSupplier;
// The value here should match an entry in the META-INF/mods.toml file
@Mod(ModernFix.MODID)
@ -32,6 +37,26 @@ public class ModernFix {
// Used to skip computing the blockstate caches twice
public static boolean runningFirstInjection = false;
public static CountDownLatch worldLoadSemaphore = null;
/**
* Simple mechanism used to delay some background processes until the client is actually in-game, to reduce
* launch time.
*/
public static void waitForWorldLoad(BooleanSupplier exitEarly) {
CountDownLatch latch = worldLoadSemaphore;
if(latch != null) {
try {
while(!latch.await(100, TimeUnit.MILLISECONDS)) {
if(exitEarly.getAsBoolean())
return;
}
} catch(InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public ModernFix() {
INSTANCE = this;

View File

@ -4,19 +4,28 @@ import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.ConnectScreen;
import net.minecraft.client.gui.screens.TitleScreen;
import net.minecraftforge.client.event.ScreenOpenEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.eventbus.api.EventPriority;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import org.embeddedt.modernfix.core.ModernFixMixinPlugin;
import org.embeddedt.modernfix.load.LoadEvents;
import org.embeddedt.modernfix.screen.DeferredLevelLoadingScreen;
import java.lang.management.ManagementFactory;
public class ModernFixClient {
public static long worldLoadStartTime;
private static int numRenderTicks;
public static float gameStartTimeSeconds = -1;
public ModernFixClient() {
if(ModernFixMixinPlugin.instance.isOptionEnabled("perf.faster_singleplayer_load.ClientEvents")) {
MinecraftForge.EVENT_BUS.register(new LoadEvents());
}
}
public void resetWorldLoadStateMachine() {
numRenderTicks = 0;
worldLoadStartTime = -1;
@ -34,12 +43,13 @@ public class ModernFixClient {
@SubscribeEvent
public void onRenderTickEnd(TickEvent.RenderTickEvent event) {
if(event.phase == TickEvent.Phase.END && worldLoadStartTime != -1 && Minecraft.getInstance().player != null && numRenderTicks++ >= 10) {
if(event.phase == TickEvent.Phase.END && !(Minecraft.getInstance().screen instanceof DeferredLevelLoadingScreen) && worldLoadStartTime != -1 && Minecraft.getInstance().player != null && numRenderTicks++ >= 10) {
float timeSpentLoading = ((float)(System.nanoTime() - worldLoadStartTime) / 1000000000f);
ModernFix.LOGGER.warn("Time from main menu to in-game was " + timeSpentLoading + " seconds");
ModernFix.LOGGER.warn("Total time to load game and open world was " + (timeSpentLoading + gameStartTimeSeconds) + " seconds");
resetWorldLoadStateMachine();
if(ModernFix.worldLoadSemaphore != null)
ModernFix.worldLoadSemaphore.countDown();
}
}
}

View File

@ -1,13 +1,8 @@
package org.embeddedt.modernfix.blockstate;
import com.google.common.base.Stopwatch;
import com.google.common.collect.ImmutableSet;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.Util;
import net.minecraft.core.BlockPos;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.level.EmptyBlockGetter;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.fml.loading.FMLLoader;
import org.embeddedt.modernfix.ModernFix;
@ -17,18 +12,9 @@ import org.embeddedt.modernfix.util.BakeReason;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
public class BlockStateCacheHandler {
private static final Set<String> PRECACHED_COLLISION_SHAPES = ImmutableSet.<String>builder()
.add("refinedstorage")
.add("cabletiers")
.add("extrastorage")
.build();
private static RebuildThread currentRebuildThread = null;
private static boolean needToBake() {
@ -88,25 +74,21 @@ public class BlockStateCacheHandler {
private void rebuildCache() {
Iterator<BlockState> stateIterator = blockStateList.iterator();
while(!stopRebuild && stateIterator.hasNext()) {
stateIterator.next().initCache();
BlockState state = stateIterator.next();
try {
state.initCache();
} catch(Exception e) {
ModernFix.LOGGER.warn("Exception encountered while initializing cache", e);
}
}
}
@Override
@SuppressWarnings("deprecation")
public void run() {
ModernFix.waitForWorldLoad(() -> stopRebuild);
if(stopRebuild)
return;
Stopwatch realtimeStopwatch = Stopwatch.createStarted();
/* Run some special sauce for Refined Storage since it has very slow collision shapes */
List<BlockState> specialStates = blockStateList.stream()
.filter(state -> PRECACHED_COLLISION_SHAPES.contains(state.getBlock().getRegistryName().getNamespace())).collect(Collectors.toList());
CompletableFuture.runAsync(() -> {
specialStates.parallelStream()
.forEach(state -> {
/* Force these blocks to compute their shapes ahead of time on worker threads */
state.getBlock().getCollisionShape(state, EmptyBlockGetter.INSTANCE, BlockPos.ZERO, CollisionContext.empty());
state.getBlock().getOcclusionShape(state, EmptyBlockGetter.INSTANCE, BlockPos.ZERO);
});
}, Util.backgroundExecutor()).join();
rebuildCache();
realtimeStopwatch.stop();
if(!stopRebuild)

View File

@ -4,7 +4,7 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.embeddedt.modernfix.core.config.ModernFixEarlyConfig;
import org.embeddedt.modernfix.core.config.Option;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.*;
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
@ -16,8 +16,10 @@ public class ModernFixMixinPlugin implements IMixinConfigPlugin {
private final Logger logger = LogManager.getLogger("ModernFix");
public static ModernFixEarlyConfig config = null;
public static ModernFixMixinPlugin instance;
public ModernFixMixinPlugin() {
instance = this;
try {
config = ModernFixEarlyConfig.load(new File("./config/modernfix-mixins.properties"));
} catch (Exception e) {

View File

@ -24,7 +24,7 @@ public class ModernFixConfig {
public static ForgeConfigSpec.ConfigValue<List<? extends String>> BLACKLIST_ASYNC_JEI_PLUGINS;
public static ForgeConfigSpec.IntValue INTEGRATED_SERVER_PRIORITY;
public static ForgeConfigSpec.IntValue BACKGROUND_WORKER_PRIORITY;
public static ForgeConfigSpec.BooleanValue ENABLE_DEBUG_RELOADER;
public static ForgeConfigSpec.BooleanValue REBUILD_BLOCKSTATES_ASYNC;
@ -40,6 +40,9 @@ public class ModernFixConfig {
"jepb:jei_plugin"
), locationValidator);
INTEGRATED_SERVER_PRIORITY = COMMON_BUILDER.comment("Thread priority to use for the integrated server. By default this is one less than the client thread, to help prevent the server from lowering FPS.").defineInRange("integratedServerPriority", 4, 1, 10);
ENABLE_DEBUG_RELOADER = COMMON_BUILDER
.comment("Whether Minecraft's built-in profiling logic should be enabled for resource reloading. Can help with diagnosing world load times.")
.define("enable_debug_reloader", false);
REBUILD_BLOCKSTATES_ASYNC = COMMON_BUILDER
.comment("Rebuild blockstate cache asynchronously. Should work with most mods, but can be disabled.")
.define("rebuild_blockstate_cache_async", true);

View File

@ -34,6 +34,9 @@ public class ModernFixEarlyConfig {
this.addMixinRule("perf.faster_baking", true);
this.addMixinRule("perf.cache_model_materials", true);
this.addMixinRule("perf.datapack_reload_exceptions", true);
this.addMixinRule("perf.faster_texture_stitching", true);
/* off by default in 1.18 because it doesn't work as well */
this.addMixinRule("perf.faster_singleplayer_load", false);
/* Keep this off if JEI isn't installed to prevent breaking vanilla gameplay */
this.addMixinRule("perf.blast_search_trees", FMLLoader.getLoadingModList().getModFileById("jei") != null);
this.addMixinRule("safety", true);

View File

@ -0,0 +1,113 @@
package org.embeddedt.modernfix.load;
import it.unimi.dsi.fastutil.longs.LongIterator;
import net.minecraft.Util;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.LevelLoadingScreen;
import net.minecraft.client.gui.screens.ProgressScreen;
import net.minecraft.client.server.IntegratedServer;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.TicketType;
import net.minecraft.server.level.progress.ChunkProgressListener;
import net.minecraft.util.Unit;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.ForcedChunksSavedData;
import net.minecraftforge.client.event.ScreenOpenEvent;
import net.minecraftforge.common.world.ForgeChunkManager;
import net.minecraftforge.event.entity.player.PlayerEvent;
import net.minecraftforge.event.server.ServerAboutToStartEvent;
import net.minecraftforge.eventbus.api.EventPriority;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.server.ServerLifecycleHooks;
import org.embeddedt.modernfix.core.ModernFixMixinPlugin;
import org.embeddedt.modernfix.screen.DeferredLevelLoadingScreen;
import java.util.concurrent.locks.LockSupport;
import java.util.function.BooleanSupplier;
/**
* Handles deferring the world load screen.
* <p></p>
* TODO: The vanilla check that at least 441 chunks have been loaded does not check whether they are spawn chunks
* or chunks loaded by the player. Consequently it is possible for loading to finish before every spawn chunk has
* been loaded. However the chunk system has at least been warmed up by this point so the remaining chunks load
* reasonably quickly.
*/
public class LoadEvents {
private boolean hasFirstPlayerJoined = false;
@SubscribeEvent
public void serverWillStart(ServerAboutToStartEvent event) {
hasFirstPlayerJoined = false;
}
@SubscribeEvent(priority = EventPriority.LOWEST)
public void onPlayerLogin(PlayerEvent.PlayerLoggedInEvent event) {
if(!hasFirstPlayerJoined && ModernFixMixinPlugin.instance.isOptionEnabled("perf.faster_singleplayer_load.ClientEvents")) {
hasFirstPlayerJoined = true;
MinecraftServer server = ServerLifecycleHooks.getCurrentServer();
if(server instanceof IntegratedServer) {
handleInitialChunkLoad();
}
}
}
@SubscribeEvent(priority = EventPriority.LOWEST)
public void onWorldShow(ScreenOpenEvent event) {
if(ServerLifecycleHooks.getCurrentServer() instanceof IntegratedServer) {
if(event.getScreen() == null && Minecraft.getInstance().level != null && integratedWorldLoadListener != null) {
/* this means the world is about to be displayed, check if 441 initialized */
ServerChunkCache provider = ServerLifecycleHooks.getCurrentServer().overworld().getChunkSource();
BooleanSupplier worldLoadDone = () -> provider.getTickingGenerated() >= 441;
if(!worldLoadDone.getAsBoolean()) {
DeferredLevelLoadingScreen newScreen = new DeferredLevelLoadingScreen(Minecraft.getInstance().progressListener.get(), worldLoadDone);
event.setScreen(newScreen);
}
} else if(event.getScreen() instanceof LevelLoadingScreen && Minecraft.getInstance().level == null && ModernFixMixinPlugin.instance.isOptionEnabled("perf.faster_singleplayer_load.ClientEvents")) {
ProgressScreen loadscreen = new ProgressScreen(false);
loadscreen.progressStartNoAbort(new TranslatableComponent("connect.joining"));
event.setScreen(loadscreen);
}
}
}
public static ChunkProgressListener integratedWorldLoadListener;
private void handleInitialChunkLoad() {
MinecraftServer server = ServerLifecycleHooks.getCurrentServer();
ServerLevel overworld = server.overworld();
ServerChunkCache provider = overworld.getChunkSource();
provider.getLightEngine().setTaskPerBatch(500);
provider.addRegionTicket(TicketType.START, new ChunkPos(overworld.getSharedSpawnPos()), 11, Unit.INSTANCE);
while(provider.getTickingGenerated() < 441) {
server.runAllTasks();
Thread.yield();
LockSupport.parkNanos("waiting for world load", 100000L);
server.nextTickTime = Util.getMillis() + 10;
}
for(ServerLevel serverworld1 : server.getAllLevels()) {
ForcedChunksSavedData forcedchunkssavedata = serverworld1.getDataStorage().get(ForcedChunksSavedData::load, "chunks");
if (forcedchunkssavedata != null) {
LongIterator longiterator = forcedchunkssavedata.getChunks().iterator();
while(longiterator.hasNext()) {
long i = longiterator.nextLong();
ChunkPos chunkpos = new ChunkPos(i);
serverworld1.getChunkSource().updateChunkForced(chunkpos, true);
}
ForgeChunkManager.reinstatePersistentChunks(serverworld1, forcedchunkssavedata);
}
}
server.runAllTasks();
server.nextTickTime = Util.getMillis() + 10;
provider.getLightEngine().setTaskPerBatch(5);
if(integratedWorldLoadListener != null) {
integratedWorldLoadListener.stop();
integratedWorldLoadListener = null;
}
}
}

View File

@ -0,0 +1,21 @@
package org.embeddedt.modernfix.mixin.core;
import net.minecraft.client.Minecraft;
import net.minecraft.server.WorldStem;
import net.minecraft.world.level.storage.LevelStorageSource;
import org.embeddedt.modernfix.ModernFix;
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.CallbackInfo;
import java.util.concurrent.CountDownLatch;
import java.util.function.Function;
@Mixin(Minecraft.class)
public class MinecraftMixin {
@Inject(method = "doLoadLevel", at = @At("HEAD"), remap = false)
private void setLatch(String string, Function<LevelStorageSource.LevelStorageAccess, WorldStem.DataPackConfigSupplier> function, Function<LevelStorageSource.LevelStorageAccess, WorldStem.WorldDataSupplier> function2, boolean bl, Minecraft.ExperimentalDialogType arg, boolean creating, CallbackInfo ci) {
ModernFix.worldLoadSemaphore = new CountDownLatch(1);
}
}

View File

@ -0,0 +1,24 @@
package org.embeddedt.modernfix.mixin.feature.measure_time;
import net.minecraft.server.packs.resources.PreparableReloadListener;
import net.minecraft.server.packs.resources.ProfiledReloadInstance;
import org.embeddedt.modernfix.util.NamedPreparableResourceListener;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import java.util.ArrayList;
import java.util.List;
@Mixin(ProfiledReloadInstance.class)
public class ProfiledReloadInstanceMixin {
@ModifyVariable(method = "<init>", at = @At("HEAD"), argsOnly = true, ordinal = 0)
private static List<PreparableReloadListener> getWrappedListeners(List<PreparableReloadListener> listeners) {
List<PreparableReloadListener> newList = new ArrayList<>(listeners.size());
for(PreparableReloadListener listener : listeners) {
newList.add(new NamedPreparableResourceListener(listener));
}
return newList;
}
}

View File

@ -0,0 +1,22 @@
package org.embeddedt.modernfix.mixin.feature.measure_time;
import net.minecraft.server.packs.resources.ReloadableResourceManager;
import org.embeddedt.modernfix.core.config.ModernFixConfig;
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.ModifyArg;
@Mixin(ReloadableResourceManager.class)
public class SimpleReloadableResourceManagerMixin {
/**
* @author embeddedt
* @reason add ability to use this feature in modpacks
*/
@ModifyArg(method = "createReload", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/packs/resources/SimpleReloadInstance;create(Lnet/minecraft/server/packs/resources/ResourceManager;Ljava/util/List;Ljava/util/concurrent/Executor;Ljava/util/concurrent/Executor;Ljava/util/concurrent/CompletableFuture;Z)Lnet/minecraft/server/packs/resources/ReloadInstance;"), index = 5)
private boolean enableDebugReloader(boolean bl) {
return bl || ModernFixConfig.ENABLE_DEBUG_RELOADER.get();
}
}

View File

@ -3,6 +3,8 @@ package org.embeddedt.modernfix.mixin.perf.faster_baking;
import com.google.common.collect.ImmutableSet;
import com.mojang.datafixers.util.Pair;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.block.model.MultiVariant;
import net.minecraft.client.renderer.block.model.multipart.MultiPart;
import net.minecraft.client.renderer.texture.TextureAtlas;
import net.minecraft.client.renderer.texture.AtlasSet;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
@ -105,22 +107,32 @@ public abstract class ModelBakeryMixin implements IExtendedModelBakery {
this.bakeIfPossible(location);
}
});
List<ResourceLocation> multiparts = new ArrayList<>();
/* Then store them as top-level models if needed, and set up the lazy models */
this.topLevelModels.forEach((location, value) -> {
if (incompatibleLazyBakedModels.contains(location.getNamespace()) || requiresBake(value)) {
if (requiresBake(value) || incompatibleLazyBakedModels.contains(location.getNamespace())) {
BakedModel model = this.bakeIfPossible(location);
if (model != null)
this.bakedTopLevelModels.put(location, model);
} else {
this.bakedTopLevelModels.put(location, new LazyBakedModel(() -> {
synchronized (this.bakedCache) {
BakedModel ibakedmodel = this.bakeIfPossible(location);
if(value instanceof MultiPart || value instanceof MultiVariant) {
multiparts.add(location);
} else {
this.bakedTopLevelModels.put(location, new LazyBakedModel(() -> {
synchronized (this.bakedCache) {
BakedModel ibakedmodel = this.bakeIfPossible(location);
return ibakedmodel != null ? ibakedmodel : missingModel;
}
}));
return ibakedmodel != null ? ibakedmodel : missingModel;
}
}));
}
}
});
multiparts.forEach(location -> {
BakedModel model = this.bakeIfPossible(location);
if (model != null)
this.bakedTopLevelModels.put(location, model);
});
}
/**

View File

@ -0,0 +1,41 @@
package org.embeddedt.modernfix.mixin.perf.faster_singleplayer_load;
import net.minecraft.Util;
import net.minecraft.client.server.IntegratedServer;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.progress.ChunkProgressListener;
import org.embeddedt.modernfix.ModernFixClient;
import org.embeddedt.modernfix.load.LoadEvents;
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;
@Mixin(MinecraftServer.class)
public abstract class MinecraftServerMixin {
@Shadow protected long nextTickTime;
@Shadow public abstract ServerLevel overworld();
@Shadow protected abstract void updateMobSpawningFlags();
/**
* @author embeddedt
* @reason defer the 441 chunk load until *after* join game packets are sent to the client, in order to allow
* mods that process advancements, etc. to work on that at the same time
*/
@Inject(method = "prepareLevels", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerLevel;getChunkSource()Lnet/minecraft/server/level/ServerChunkCache;", ordinal = 0), cancellable = true)
private void skipInitialChunkLoad(ChunkProgressListener arg, CallbackInfo ci) {
if(((Object)this) instanceof IntegratedServer) {
ci.cancel();
LoadEvents.integratedWorldLoadListener = arg;
this.nextTickTime = Util.getMillis();
this.overworld().getChunkSource().getLightEngine().setTaskPerBatch(5);
this.updateMobSpawningFlags();
}
}
}

View File

@ -0,0 +1,54 @@
package org.embeddedt.modernfix.mixin.perf.faster_texture_stitching;
import com.mojang.datafixers.util.Pair;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.minecraft.client.renderer.texture.Stitcher;
import org.embeddedt.modernfix.textures.StbStitcher;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
@Mixin(Stitcher.class)
public class StitcherMixin {
@Shadow @Final private Set<Stitcher.Holder> texturesToBeStitched;
@Shadow private int storageX;
@Shadow private int storageY;
@Shadow @Final private static Comparator<Stitcher.Holder> HOLDER_COMPARATOR;
private List<StbStitcher.LoadableSpriteInfo> loadableSpriteInfos;
/**
* @author embeddedt, SuperCoder79
* @reason Use improved STB stitcher instead of the vanilla implementation, for performance
*/
@Overwrite
public void stitch() {
ObjectArrayList<Stitcher.Holder> holderList = new ObjectArrayList<>(this.texturesToBeStitched);
holderList.sort(HOLDER_COMPARATOR);
Stitcher.Holder[] aholder = holderList.toArray(new Stitcher.Holder[0]);
Pair<Pair<Integer, Integer>, List<StbStitcher.LoadableSpriteInfo>> packingInfo = StbStitcher.packRects(aholder);
this.storageX = packingInfo.getFirst().getFirst();
this.storageY = packingInfo.getFirst().getSecond();
this.loadableSpriteInfos = packingInfo.getSecond();
}
/**
* @author embeddedt, SuperCoder79
* @reason We setup the image ourselves in the StbStitcher, so we just feed this information back into the vanilla code
*/
@Overwrite
public void gatherSprites(Stitcher.SpriteLoader spriteLoader) {
for(StbStitcher.LoadableSpriteInfo info : loadableSpriteInfos) {
spriteLoader.load(info.info, info.width, info.height, info.x, info.y);
}
}
}

View File

@ -54,7 +54,10 @@ public abstract class ModelBakeryMixin {
ResourceLocation blockStateJSON = new ResourceLocation(blockLocation.getNamespace(), "blockstates/" + blockLocation.getPath() + ".json");
List<Resource> blockStates;
try {
blockStates = this.resourceManager.getResources(blockStateJSON);
/* Some mods' custom resource pack implementations don't seem to like concurrency here */
synchronized(this.resourceManager) {
blockStates = this.resourceManager.getResources(blockStateJSON);
}
} catch(IOException e) {
ModernFix.LOGGER.warn("Exception loading blockstate definition: {}: {}", blockLocation, e);
return;

View File

@ -0,0 +1,27 @@
package org.embeddedt.modernfix.screen;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.gui.screens.LevelLoadingScreen;
import net.minecraft.server.level.progress.StoringChunkProgressListener;
import java.util.function.BooleanSupplier;
public class DeferredLevelLoadingScreen extends LevelLoadingScreen {
private final BooleanSupplier worldLoadFinished;
public DeferredLevelLoadingScreen(StoringChunkProgressListener arg, BooleanSupplier worldLoadFinished) {
super(arg);
this.worldLoadFinished = worldLoadFinished;
}
@Override
public void tick() {
super.tick();
if(this.worldLoadFinished.getAsBoolean())
this.onClose();
}
@Override
public void renderBackground(PoseStack matrixStack, int vOffset) {
renderDirtBackground(vOffset);
}
}

View File

@ -0,0 +1,90 @@
package org.embeddedt.modernfix.textures;
import com.google.common.collect.ImmutableList;
import com.mojang.datafixers.util.Pair;
import net.minecraft.client.renderer.texture.Stitcher;
import net.minecraft.client.renderer.texture.StitcherException;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.util.Mth;
import org.lwjgl.stb.STBRPContext;
import org.lwjgl.stb.STBRPNode;
import org.lwjgl.stb.STBRPRect;
import org.lwjgl.stb.STBRectPack;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
/* Source: https://github.com/GTNewHorizons/lwjgl3ify/blob/f21364cd3d178aef863458a2faa1f5718a4e350d/src/main/java/me/eigenraven/lwjgl3ify/textures/StbStitcher.java */
public class StbStitcher {
public static Pair<Pair<Integer, Integer>, List<LoadableSpriteInfo>> packRects(Stitcher.Holder[] holders) {
int holderSize = holders.length;
List<LoadableSpriteInfo> infoList = new ArrayList<>();
// Allocate memory for the rectangles and the context
try (STBRPRect.Buffer rectBuf = STBRPRect.malloc(holderSize);
STBRPContext ctx = STBRPContext.malloc(); ) {
// Initialize the rectangles that we'll be using in the calculation
// While that's happening, sum up the area needed to fit all of the images
int sqSize = 0;
for (int j = 0; j < holderSize; ++j) {
Stitcher.Holder holder = holders[j];
int width = holder.width;
int height = holder.height;
// The ID here is just the array index, for easy lookup later
rectBuf.get(j).set(j, (short)width, (short)height, (short)0, (short)0, false);
sqSize += (width * height);
}
int size = Mth.smallestEncompassingPowerOfTwo((int) Math.sqrt(sqSize));
int width = size * 2; // needed to fix weirdness in 1.16
int height = size;
// Internal node structure needed for STB
try (STBRPNode.Buffer nodes = STBRPNode.malloc(width + 10)) {
// Initialize the rect packer
STBRectPack.stbrp_init_target(ctx, width, height, nodes);
// Perform rectangle packing
STBRectPack.stbrp_pack_rects(ctx, rectBuf);
for (STBRPRect rect : rectBuf) {
Stitcher.Holder holder = holders[rect.id()];
// Ensure that everything is properly packed!
if (!rect.was_packed()) {
throw new StitcherException(holder.spriteInfo,
Stream.of(holders).map(arg -> arg.spriteInfo).collect(ImmutableList.toImmutableList()));
}
// Initialize the sprite now with the position and size that we've calculated so far
infoList.add(new LoadableSpriteInfo(holder.spriteInfo, width, height, rect.x(), rect.y()));
//holder.spriteInfo.initSprite(size, size, rect.x(), rect.y(), false);
}
return Pair.of(Pair.of(width, height), infoList);
}
}
}
public static class LoadableSpriteInfo {
public final TextureAtlasSprite.Info info;
public final int width;
public final int height;
public final int x;
public final int y;
LoadableSpriteInfo(TextureAtlasSprite.Info info, int width, int height, int x, int y) {
this.info = info;
this.width = width;
this.height = height;
this.x = x;
this.y = y;
}
}
}

View File

@ -9,6 +9,8 @@ import net.minecraftforge.fml.util.ObfuscationReflectionHelper;
import org.embeddedt.modernfix.ModernFix;
import java.util.*;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinWorkerThread;
import java.util.function.Supplier;
public class ModUtil {
@ -44,4 +46,21 @@ public class ModUtil {
});
return modsListening;
}
private static final ClassLoader targetClassLoader = Thread.currentThread().getContextClassLoader();
private static class ModernFixForkJoinWorkerThread extends ForkJoinWorkerThread {
ModernFixForkJoinWorkerThread(ForkJoinPool pool) {
super(pool);
/* Ensure that the context class loader is set correctly */
this.setContextClassLoader(targetClassLoader);
}
}
public static ForkJoinPool commonPool = new ForkJoinPool(
ForkJoinPool.getCommonPoolParallelism(),
ModernFixForkJoinWorkerThread::new,
null,
false
);
}

View File

@ -0,0 +1,25 @@
package org.embeddedt.modernfix.util;
import net.minecraft.server.packs.resources.PreparableReloadListener;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.util.profiling.ProfilerFiller;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
public class NamedPreparableResourceListener implements PreparableReloadListener {
private final PreparableReloadListener delegate;
public NamedPreparableResourceListener(PreparableReloadListener delegate) {
this.delegate = delegate;
}
@Override
public CompletableFuture<Void> reload(PreparationBarrier stage, ResourceManager resourceManager, ProfilerFiller preparationsProfiler, ProfilerFiller reloadProfiler, Executor backgroundExecutor, Executor gameExecutor) {
return this.delegate.reload(stage, resourceManager, preparationsProfiler, reloadProfiler, backgroundExecutor, gameExecutor);
}
@Override
public String getName() {
return this.delegate.getName() + " [" + this.delegate.getClass().getName() + "]";
}
}

View File

@ -4,4 +4,8 @@ public net.minecraft.client.renderer.RenderType$CompositeRenderType <init>(Ljava
public net.minecraft.world.level.block.state.BlockBehaviour$BlockStateBase$Cache
public net.minecraft.world.phys.shapes.VoxelShape <init>(Lnet/minecraft/util/math/shapes/VoxelShapePart;)V # <init>
public net.minecraft.client.resources.model.ModelBakery$BlockStateDefinitionException
public net.minecraft.client.renderer.model.ModelBakery field_217849_F # unbakedCache
public net.minecraft.client.renderer.model.ModelBakery field_217849_F # unbakedCache
public net.minecraft.client.renderer.texture.Stitcher$Holder
public net.minecraft.util.thread.BlockableEventLoop m_18699_()V # runAllTasks
public net.minecraft.server.MinecraftServer f_129726_ # nextTickTime
public net.minecraft.client.Minecraft f_90999_ # progressListener

View File

@ -1,5 +1,5 @@
{
"modernfix.jei_load": "Loading JEI...",
"modernfix.jei_load": "Loading JEI, this may take a while",
"asynclocator.map.locating": "Map (Locating...)",
"asynclocator.map.none": "Map (No nearby feature found)"
}
}

View File

@ -0,0 +1,5 @@
{
"modernfix.jei_load": "正在加载JEI这需要一点时间",
"asynclocator.map.locating": "地图(正在定位...",
"asynclocator.map.none": "地图(未能找到相关地物)"
}

View File

@ -19,9 +19,12 @@
"perf.cache_blockstate_cache_arrays.AbstractBlockStateCacheMixin",
"perf.datapack_reload_exceptions.LootTableManagerMixin",
"perf.datapack_reload_exceptions.RecipeManagerMixin",
"feature.measure_time.BootstrapMixin"
"feature.measure_time.BootstrapMixin",
"feature.measure_time.SimpleReloadableResourceManagerMixin",
"feature.measure_time.ProfiledReloadInstanceMixin"
],
"client": [
"core.MinecraftMixin",
"feature.measure_time.MinecraftMixin",
"feature.reduce_loading_screen_freezes.ModelBakeryMixin",
"bugfix.concurrency.MinecraftMixin",
@ -44,7 +47,9 @@
"perf.faster_baking.BlockModelShapesMixin",
"perf.faster_baking.ModelManagerMixin",
"perf.cache_model_materials.VanillaModelMixin",
"perf.cache_model_materials.MultipartMixin"
"perf.cache_model_materials.MultipartMixin",
"perf.faster_texture_stitching.StitcherMixin",
"perf.faster_singleplayer_load.MinecraftServerMixin"
],
"injectors": {
"defaultRequire": 1