Merge branch 'main' into 1.18
This commit is contained in:
commit
17e526bcfe
2
.github/workflows/gradle.yml
vendored
2
.github/workflows/gradle.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
21
build.gradle
21
build.gradle
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
113
src/main/java/org/embeddedt/modernfix/load/LoadEvents.java
Normal file
113
src/main/java/org/embeddedt/modernfix/load/LoadEvents.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() + "]";
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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)"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
5
src/main/resources/assets/modernfix/lang/zh_cn.json
Normal file
5
src/main/resources/assets/modernfix/lang/zh_cn.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"modernfix.jei_load": "正在加载JEI,这需要一点时间",
|
||||
"asynclocator.map.locating": "地图(正在定位...)",
|
||||
"asynclocator.map.none": "地图(未能找到相关地物)"
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user