diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index e14c3237..561195c2 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -16,6 +16,7 @@ jobs: with: distribution: 'temurin' java-version: '17' + cache: 'gradle' - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Build the mod diff --git a/common/src/main/java/org/embeddedt/modernfix/ModernFix.java b/common/src/main/java/org/embeddedt/modernfix/ModernFix.java index 4644a728..b571b718 100644 --- a/common/src/main/java/org/embeddedt/modernfix/ModernFix.java +++ b/common/src/main/java/org/embeddedt/modernfix/ModernFix.java @@ -9,10 +9,11 @@ import org.apache.logging.log4j.Logger; import org.embeddedt.modernfix.command.ModernFixCommands; import org.embeddedt.modernfix.core.ModernFixMixinPlugin; import org.embeddedt.modernfix.platform.ModernFixPlatformHooks; +import org.embeddedt.modernfix.resources.ReloadExecutor; import org.embeddedt.modernfix.util.ClassInfoManager; import java.lang.management.ManagementFactory; -import java.util.concurrent.*; +import java.util.concurrent.ExecutorService; // The value here should match an entry in the META-INF/mods.toml file public class ModernFix { @@ -31,12 +32,7 @@ public class ModernFix { static { if(ModernFixMixinPlugin.instance.isOptionEnabled("perf.dedicated_reload_executor.ReloadExecutor")) { - try { - resourceReloadService = Util.makeExecutor("ResourceReload"); - } catch(Throwable e) { - LOGGER.error("Error creating resource reload service, using fallback", e); - resourceReloadService = Util.backgroundExecutor(); - } + resourceReloadService = ReloadExecutor.createCustomResourceReloadExecutor(); } else { resourceReloadService = Util.backgroundExecutor(); } diff --git a/common/src/main/java/org/embeddedt/modernfix/ModernFixClient.java b/common/src/main/java/org/embeddedt/modernfix/ModernFixClient.java index 93020fa2..c65a5ff8 100644 --- a/common/src/main/java/org/embeddedt/modernfix/ModernFixClient.java +++ b/common/src/main/java/org/embeddedt/modernfix/ModernFixClient.java @@ -174,6 +174,8 @@ public class ModernFixClient { } public void onServerStarted(MinecraftServer server) { + if(!ModernFixMixinPlugin.instance.isOptionEnabled("feature.integrated_server_watchdog.IntegratedWatchdog")) + return; IntegratedWatchdog watchdog = new IntegratedWatchdog(server); watchdog.start(); } diff --git a/common/src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/chunk_deadlock/ServerChunkCacheMixin.java b/common/src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/chunk_deadlock/ServerChunkCacheMixin.java index 980f46c4..fe4ea9d2 100644 --- a/common/src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/chunk_deadlock/ServerChunkCacheMixin.java +++ b/common/src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/chunk_deadlock/ServerChunkCacheMixin.java @@ -33,7 +33,7 @@ public abstract class ServerChunkCacheMixin { @Inject(method = "getChunk", at = @At("HEAD"), cancellable = true) private void bailIfServerDead(int chunkX, int chunkZ, ChunkStatus requiredStatus, boolean load, CallbackInfoReturnable cir) { - if(!this.mainThread.isAlive()) { + if(!this.level.getServer().isRunning() && !this.mainThread.isAlive()) { ModernFix.LOGGER.fatal("A mod is accessing chunks from a stopped server (this will also cause memory leaks)"); if(debugDeadServerAccess) { new Exception().printStackTrace(); diff --git a/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/faster_item_rendering/GameRendererMixin.java b/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/faster_item_rendering/GameRendererMixin.java new file mode 100644 index 00000000..ee3a0f9d --- /dev/null +++ b/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/faster_item_rendering/GameRendererMixin.java @@ -0,0 +1,21 @@ +package org.embeddedt.modernfix.common.mixin.perf.faster_item_rendering; + +import net.minecraft.client.renderer.GameRenderer; +import org.embeddedt.modernfix.render.RenderState; +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; + +@Mixin(GameRenderer.class) +public class GameRendererMixin { + @Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GameRenderer;renderLevel(FJLcom/mojang/blaze3d/vertex/PoseStack;)V", shift = At.Shift.BEFORE)) + private void markRenderingLevel(CallbackInfo ci) { + RenderState.IS_RENDERING_LEVEL = true; + } + + @Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GameRenderer;renderLevel(FJLcom/mojang/blaze3d/vertex/PoseStack;)V", shift = At.Shift.AFTER)) + private void markNotRenderingLevel(CallbackInfo ci) { + RenderState.IS_RENDERING_LEVEL = false; + } +} diff --git a/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/faster_item_rendering/ItemRendererMixin.java b/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/faster_item_rendering/ItemRendererMixin.java index d28fadad..b601c805 100644 --- a/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/faster_item_rendering/ItemRendererMixin.java +++ b/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/faster_item_rendering/ItemRendererMixin.java @@ -13,7 +13,10 @@ import net.minecraft.client.resources.model.SimpleBakedModel; import net.minecraft.core.Direction; import net.minecraft.util.RandomSource; import net.minecraft.world.item.ItemDisplayContext; +import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.ItemStack; +import org.embeddedt.modernfix.render.FastItemRenderType; +import org.embeddedt.modernfix.render.RenderState; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -38,6 +41,17 @@ public abstract class ItemRendererMixin { this.transformType = transformType; } + private static final Direction[] ITEM_DIRECTIONS = new Direction[] { Direction.SOUTH }; + private static final Direction[] BLOCK_DIRECTIONS = new Direction[] { Direction.UP, Direction.EAST, Direction.NORTH }; + + private boolean isCorrectDirectionForType(FastItemRenderType type, Direction direction) { + if(type == FastItemRenderType.SIMPLE_ITEM) + return direction == Direction.SOUTH; + else { + return direction == Direction.UP || direction == Direction.EAST || direction == Direction.NORTH; + } + } + /** * If a model * - is a vanilla item model (SimpleBakedModel), @@ -48,25 +62,42 @@ public abstract class ItemRendererMixin { */ @Inject(method = "renderModelLists", at = @At("HEAD"), cancellable = true) private void fasterItemRender(BakedModel model, ItemStack stack, int combinedLight, int combinedOverlay, PoseStack matrixStack, VertexConsumer buffer, CallbackInfo ci) { - if(!stack.isEmpty() && model.getClass() == SimpleBakedModel.class && transformType == ItemDisplayContext.GUI && model.getTransforms().gui == ItemTransform.NO_TRANSFORM) { + if(!RenderState.IS_RENDERING_LEVEL && !stack.isEmpty() && model.getClass() == SimpleBakedModel.class && transformType == ItemDisplayContext.GUI) { + FastItemRenderType type; + ItemTransform transform = model.getTransforms().gui; + if(transform == ItemTransform.NO_TRANSFORM) + type = FastItemRenderType.SIMPLE_ITEM; + else if(stack.getItem() instanceof BlockItem && isBlockTransforms(transform)) + type = FastItemRenderType.SIMPLE_BLOCK; + else + return; ci.cancel(); PoseStack.Pose pose = matrixStack.last(); int[] combinedLights = new int[] {combinedLight, combinedLight, combinedLight, combinedLight}; - List culledFaces = model.getQuads(null, Direction.SOUTH, dummyRandom); - List unculledFaces = model.getQuads(null, null, dummyRandom); - /* check size to avoid instantiating iterator when the list is empty */ - if(culledFaces.size() > 0) { - for(BakedQuad quad : culledFaces) { - render2dItemFace(quad, stack, buffer, pose, combinedLights, combinedOverlay); + Direction[] directions = type == FastItemRenderType.SIMPLE_ITEM ? ITEM_DIRECTIONS : BLOCK_DIRECTIONS; + for(Direction direction : directions) { + List culledFaces = model.getQuads(null, direction, dummyRandom); + /* check size to avoid instantiating iterator when the list is empty */ + if(culledFaces.size() > 0) { + for(BakedQuad quad : culledFaces) { + render2dItemFace(quad, stack, buffer, pose, combinedLights, combinedOverlay); + } } } + List unculledFaces = model.getQuads(null, null, dummyRandom); for(BakedQuad quad : unculledFaces) { - if(quad.getDirection() == Direction.SOUTH) + if(isCorrectDirectionForType(type, quad.getDirection())) render2dItemFace(quad, stack, buffer, pose, combinedLights, combinedOverlay); } } } + private boolean isBlockTransforms(ItemTransform transform) { + return transform.rotation.x() == 30f + && transform.rotation.y() == 225f + && transform.rotation.z() == 0f; + } + private void render2dItemFace(BakedQuad quad, ItemStack stack, VertexConsumer buffer, PoseStack.Pose pose, int[] combinedLights, int combinedOverlay) { int i = -1; if (quad.isTinted()) { diff --git a/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/resourcepacks/ReloadableResourceManagerMixin.java b/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/resourcepacks/ReloadableResourceManagerMixin.java new file mode 100644 index 00000000..3fd155cf --- /dev/null +++ b/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/resourcepacks/ReloadableResourceManagerMixin.java @@ -0,0 +1,18 @@ +package org.embeddedt.modernfix.common.mixin.perf.resourcepacks; + +import net.minecraft.server.packs.resources.ReloadableResourceManager; +import org.embeddedt.modernfix.ModernFix; +import org.embeddedt.modernfix.resources.PackResourcesCacheEngine; +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; + +@Mixin(ReloadableResourceManager.class) +public class ReloadableResourceManagerMixin { + @Inject(method = "createReload", at = @At("HEAD")) + private void invalidateResourceCaches(CallbackInfoReturnable cir) { + ModernFix.LOGGER.info("Invalidating pack caches"); + PackResourcesCacheEngine.invalidate(); + } +} diff --git a/common/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java b/common/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java index 28733531..1e49dcff 100644 --- a/common/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java +++ b/common/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java @@ -139,15 +139,15 @@ public class ModernFixEarlyConfig { private static final ImmutableMap DEFAULT_SETTING_OVERRIDES = ImmutableMap.builder() .put("mixin.perf.dynamic_resources", false) - .put("mixin.feature.reduce_loading_screen_freezes", false) .put("mixin.feature.direct_stack_trace", false) .put("mixin.perf.rewrite_registry", false) .put("mixin.perf.clear_mixin_classinfo", false) .put("mixin.perf.compress_blockstate", false) .put("mixin.bugfix.packet_leak", false) .put("mixin.perf.deduplicate_location", false) - .put("mixin.perf.preload_block_classes", false) - .put("mixin.perf.faster_singleplayer_load", false) + .put("mixin.perf.dynamic_entity_renderers", false) + .put("mixin.feature.integrated_server_watchdog", true) + .put("mixin.perf.faster_item_rendering", false) .put("mixin.perf.blast_search_trees", shouldReplaceSearchTrees) .put("mixin.devenv", isDevEnv) .put("mixin.perf.remove_spawn_chunks", isDevEnv) @@ -185,8 +185,12 @@ public class ModernFixEarlyConfig { disableIfModPresent("mixin.perf.async_jei", "modernui"); disableIfModPresent("mixin.perf.compress_biome_container", "chocolate", "betterendforge"); disableIfModPresent("mixin.bugfix.mc218112", "performant"); + disableIfModPresent("mixin.bugfix.remove_block_chunkloading", "performant"); + disableIfModPresent("mixin.bugfix.paper_chunk_patches", "c2me"); disableIfModPresent("mixin.perf.reuse_datapacks", "tac"); disableIfModPresent("mixin.launch.class_search_cache", "optifine"); + disableIfModPresent("mixin.perf.datapack_reload_exceptions", "cyanide"); + disableIfModPresent("mixin.perf.faster_texture_loading", "stitch"); } private void disableIfModPresent(String configName, String... ids) { @@ -195,8 +199,6 @@ public class ModernFixEarlyConfig { Option option = this.options.get(configName); if(option != null) option.addModOverride(false, id); - else - LOGGER.warn("Can't disable missing option {}", configName); } } } diff --git a/common/src/main/java/org/embeddedt/modernfix/dynamicresources/ModelBakeryHelpers.java b/common/src/main/java/org/embeddedt/modernfix/dynamicresources/ModelBakeryHelpers.java index bb50402a..c12859c1 100644 --- a/common/src/main/java/org/embeddedt/modernfix/dynamicresources/ModelBakeryHelpers.java +++ b/common/src/main/java/org/embeddedt/modernfix/dynamicresources/ModelBakeryHelpers.java @@ -58,6 +58,10 @@ public class ModelBakeryHelpers { } } } + // check if there is only one possible state + if(fixedProperties.size() == stateDefinition.getProperties().size()) { + return ImmutableList.of(fixedState); + } // generate all possible blockstates from the remaining properties ArrayList> anyProperties = new ArrayList<>(stateDefinition.getProperties()); anyProperties.removeAll(fixedProperties); diff --git a/common/src/main/java/org/embeddedt/modernfix/render/FastItemRenderType.java b/common/src/main/java/org/embeddedt/modernfix/render/FastItemRenderType.java new file mode 100644 index 00000000..31b02d27 --- /dev/null +++ b/common/src/main/java/org/embeddedt/modernfix/render/FastItemRenderType.java @@ -0,0 +1,6 @@ +package org.embeddedt.modernfix.render; + +public enum FastItemRenderType { + SIMPLE_ITEM, + SIMPLE_BLOCK +} diff --git a/common/src/main/java/org/embeddedt/modernfix/render/RenderState.java b/common/src/main/java/org/embeddedt/modernfix/render/RenderState.java new file mode 100644 index 00000000..e869e383 --- /dev/null +++ b/common/src/main/java/org/embeddedt/modernfix/render/RenderState.java @@ -0,0 +1,5 @@ +package org.embeddedt.modernfix.render; + +public class RenderState { + public static boolean IS_RENDERING_LEVEL = false; +} diff --git a/common/src/main/java/org/embeddedt/modernfix/resources/ICachingResourcePack.java b/common/src/main/java/org/embeddedt/modernfix/resources/ICachingResourcePack.java new file mode 100644 index 00000000..a9e42bde --- /dev/null +++ b/common/src/main/java/org/embeddedt/modernfix/resources/ICachingResourcePack.java @@ -0,0 +1,5 @@ +package org.embeddedt.modernfix.resources; + +public interface ICachingResourcePack { + void invalidateCache(); +} diff --git a/common/src/main/java/org/embeddedt/modernfix/resources/PackResourcesCacheEngine.java b/common/src/main/java/org/embeddedt/modernfix/resources/PackResourcesCacheEngine.java index dae520c2..25cf6267 100644 --- a/common/src/main/java/org/embeddedt/modernfix/resources/PackResourcesCacheEngine.java +++ b/common/src/main/java/org/embeddedt/modernfix/resources/PackResourcesCacheEngine.java @@ -1,10 +1,13 @@ package org.embeddedt.modernfix.resources; +import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.mojang.datafixers.util.Pair; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.PackType; +import org.embeddedt.modernfix.ModernFix; import org.embeddedt.modernfix.util.PackTypeHelper; import java.io.IOException; @@ -14,6 +17,7 @@ import java.util.*; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Predicate; +import java.util.stream.Collectors; import java.util.stream.Stream; /** @@ -26,6 +30,9 @@ public class PackResourcesCacheEngine { private final Map> namespacesByType; private final Set containedPaths; private final EnumMap>> resourceListings; + private volatile boolean cacheGenerationFlag = false; + private List cacheGenerationTasks = new ArrayList<>(); + private Path debugPath; public PackResourcesCacheEngine(Function> namespacesRetriever, BiFunction basePathRetriever) { this.namespacesByType = new EnumMap<>(PackType.class); @@ -36,32 +43,44 @@ public class PackResourcesCacheEngine { } this.containedPaths = new ObjectOpenHashSet<>(); this.resourceListings = new EnumMap<>(PackType.class); + // used for log message + this.debugPath = basePathRetriever.apply(PackType.CLIENT_RESOURCES, "minecraft").toAbsolutePath(); for(PackType type : PackType.values()) { Collection namespaces = PackTypeHelper.isVanillaPackType(type) ? this.namespacesByType.get(type) : namespacesRetriever.apply(type); - ImmutableMap.Builder> packTypedMap = ImmutableMap.builder(); - for(String namespace : namespaces) { - try { - ImmutableList.Builder namespacedList = ImmutableList.builder(); - Path root = basePathRetriever.apply(type, namespace).toAbsolutePath(); - String[] prefix = new String[] { type.getDirectory(), namespace }; - try (Stream stream = Files.walk(root)) { - stream - .map(path -> root.relativize(path.toAbsolutePath())) - .filter(PackResourcesCacheEngine::isValidCachedResourcePath) - .forEach(path -> { - CachedResourcePath cachedPath = new CachedResourcePath(prefix, path); - this.containedPaths.add(cachedPath); - if(!cachedPath.getFileName().endsWith(".mcmeta")) - namespacedList.add(cachedPath); - }); + Collection> namespacedRoots = namespaces.stream().map(s -> Pair.of(s, basePathRetriever.apply(type, s).toAbsolutePath())).collect(Collectors.toList()); + cacheGenerationTasks.add(() -> { + ImmutableMap.Builder> packTypedMap = ImmutableMap.builder(); + for(Pair pair : namespacedRoots) { + try { + ImmutableList.Builder namespacedList = ImmutableList.builder(); + String namespace = pair.getFirst(); + Path root = pair.getSecond(); + String[] prefix = new String[] { type.getDirectory(), namespace }; + try (Stream stream = Files.walk(root)) { + stream + .map(path -> root.relativize(path.toAbsolutePath())) + .filter(PackResourcesCacheEngine::isValidCachedResourcePath) + .forEach(path -> { + CachedResourcePath cachedPath = new CachedResourcePath(prefix, path); + synchronized (this.containedPaths) { + this.containedPaths.add(cachedPath); + } + if(!cachedPath.getFileName().endsWith(".mcmeta")) + namespacedList.add(cachedPath); + }); + } + packTypedMap.put(namespace, namespacedList.build()); + } catch(IOException ignored) { } - packTypedMap.put(namespace, namespacedList.build()); - } catch(IOException ignored) { } - } - this.resourceListings.put(type, packTypedMap.build()); + synchronized (this.resourceListings) { + this.resourceListings.put(type, packTypedMap.build()); + } + }); } - ((ObjectOpenHashSet)this.containedPaths).trim(); + cacheGenerationTasks.add(() -> { + ((ObjectOpenHashSet)this.containedPaths).trim(); + }); } private static boolean isValidCachedResourcePath(Path path) { @@ -86,13 +105,37 @@ public class PackResourcesCacheEngine { return null; } + private void doGenerateCache() { + Stopwatch watch = Stopwatch.createStarted(); + for(Runnable r : this.cacheGenerationTasks) { + r.run(); + } + watch.stop(); + ModernFix.LOGGER.debug("Generated cache for {} in {}", debugPath, watch); + debugPath = null; + cacheGenerationTasks = ImmutableList.of(); + } + + private void awaitLoad() { + if(!this.cacheGenerationFlag) { + synchronized (this) { + if(!this.cacheGenerationFlag) { + this.doGenerateCache(); + this.cacheGenerationFlag = true; + } + } + } + } + public boolean hasResource(String path) { + awaitLoad(); return this.containedPaths.contains(new CachedResourcePath(path)); } public Collection getResources(PackType type, String resourceNamespace, String pathIn, int maxDepth, Predicate filter) { if(!PackTypeHelper.isVanillaPackType(type)) throw new IllegalArgumentException("Only vanilla PackTypes are supported"); + awaitLoad(); List paths = resourceListings.get(type).getOrDefault(resourceNamespace, Collections.emptyList()); if(paths.isEmpty()) return Collections.emptyList(); @@ -111,4 +154,20 @@ public class PackResourcesCacheEngine { } return resources; } + + private static final WeakHashMap cachingPacks = new WeakHashMap<>(); + public static void track(ICachingResourcePack pack) { + synchronized (cachingPacks) { + cachingPacks.put(pack, Boolean.TRUE); + } + } + + public static void invalidate() { + synchronized (cachingPacks) { + cachingPacks.keySet().forEach(pack -> { + if(pack != null) + pack.invalidateCache(); + }); + } + } } diff --git a/common/src/main/java/org/embeddedt/modernfix/resources/ReloadExecutor.java b/common/src/main/java/org/embeddedt/modernfix/resources/ReloadExecutor.java new file mode 100644 index 00000000..ec35c84a --- /dev/null +++ b/common/src/main/java/org/embeddedt/modernfix/resources/ReloadExecutor.java @@ -0,0 +1,48 @@ +package org.embeddedt.modernfix.resources; + +import net.minecraft.ReportedException; +import net.minecraft.server.Bootstrap; +import org.embeddedt.modernfix.ModernFix; + +import java.util.concurrent.CompletionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinWorkerThread; +import java.util.concurrent.atomic.AtomicInteger; + +public class ReloadExecutor { + public static ExecutorService createCustomResourceReloadExecutor() { + ClassLoader loader = ReloadExecutor.class.getClassLoader(); + AtomicInteger workerCount = new AtomicInteger(0); + return new ForkJoinPool(ForkJoinPool.getCommonPoolParallelism(), (forkJoinPool) -> { + ForkJoinWorkerThread forkJoinWorkerThread = new ForkJoinWorkerThread(forkJoinPool) { + protected void onTermination(Throwable throwOnTermination) { + if (throwOnTermination != null) { + ModernFix.LOGGER.warn("{} died", this.getName(), throwOnTermination); + } else { + ModernFix.LOGGER.debug("{} shutdown", this.getName()); + } + + super.onTermination(throwOnTermination); + } + }; + // needed to prevent weirdness on some systems + forkJoinWorkerThread.setContextClassLoader(loader); + forkJoinWorkerThread.setName("Worker-ResourceReload-" + workerCount.getAndIncrement()); + return forkJoinWorkerThread; + }, ReloadExecutor::handleException, true); + } + + private static void handleException(Thread thread, Throwable throwable) { + if (throwable instanceof CompletionException) { + throwable = throwable.getCause(); + } + + if (throwable instanceof ReportedException) { + Bootstrap.realStdoutPrintln(((ReportedException)throwable).getReport().getFriendlyReport()); + System.exit(-1); + } + + ModernFix.LOGGER.error(String.format("Caught exception in thread %s", thread), throwable); + } +} diff --git a/common/src/main/java/org/embeddedt/modernfix/structure/CachingStructureManager.java b/common/src/main/java/org/embeddedt/modernfix/structure/CachingStructureManager.java index d14f2777..6bf07666 100644 --- a/common/src/main/java/org/embeddedt/modernfix/structure/CachingStructureManager.java +++ b/common/src/main/java/org/embeddedt/modernfix/structure/CachingStructureManager.java @@ -50,6 +50,12 @@ public class CachingStructureManager { private static final Set laggyStructureMods = new ObjectOpenHashSet<>(); + private static final int MAX_HASH_LENGTH = 9; + + private static String truncateHash(String hash) { + return hash.substring(0, MAX_HASH_LENGTH + 1); + } + public static CompoundTag readStructureTag(ResourceLocation location, DataFixer datafixer, InputStream stream) throws IOException { byte[] structureBytes = toBytes(stream); CompoundTag currentTag = NbtIo.readCompressed(new ByteArrayInputStream(structureBytes)); @@ -58,20 +64,22 @@ public class CachingStructureManager { } int currentDataVersion = currentTag.getInt("DataVersion"); if(currentDataVersion < SharedConstants.getCurrentVersion().getDataVersion().getVersion()) { - synchronized (laggyStructureMods) { - if(laggyStructureMods.add(location.getNamespace())) { - ModernFix.LOGGER.warn("Mod {} is shipping outdated structure files, which can cause worldgen lag; please report this to them.", location.getNamespace()); - } - } /* Needs upgrade, try looking up from cache */ MessageDigest hasher = digestThreadLocal.get(); hasher.reset(); String hash = encodeHex(hasher.digest(structureBytes)); - CompoundTag cachedUpgraded = getCachedUpgraded(location, hash); + CompoundTag cachedUpgraded = getCachedUpgraded(location, truncateHash(hash)); + if(cachedUpgraded == null) + cachedUpgraded = getCachedUpgraded(location, hash); /* pick up old cache */ if(cachedUpgraded != null && cachedUpgraded.getInt("DataVersion") == SharedConstants.getCurrentVersion().getDataVersion().getVersion()) { ModernFix.LOGGER.debug("Using cached upgraded version of {}", location); currentTag = cachedUpgraded; } else { + synchronized (laggyStructureMods) { + if(laggyStructureMods.add(location.getNamespace())) { + ModernFix.LOGGER.warn("Mod {} is shipping outdated structure files, which can cause worldgen lag; please report this to them.", location.getNamespace()); + } + } ModernFix.LOGGER.debug("Structure {} is being run through DFU (hash {}), this will cause launch time delays", location, hash); currentTag = DataFixTypes.STRUCTURE.update(datafixer, currentTag, currentDataVersion, SharedConstants.getCurrentVersion().getDataVersion().getVersion()); @@ -100,7 +108,7 @@ public class CachingStructureManager { } private static synchronized void saveCachedUpgraded(ResourceLocation location, String hash, CompoundTag tagToSave) { - File theFile = getCachePath(location, hash); + File theFile = getCachePath(location, truncateHash(hash)); try { NbtIo.writeCompressed(tagToSave, theFile); } catch(IOException e) { diff --git a/common/src/main/resources/modernfix.accesswidener b/common/src/main/resources/modernfix.accesswidener index 4bba23eb..7480e262 100644 --- a/common/src/main/resources/modernfix.accesswidener +++ b/common/src/main/resources/modernfix.accesswidener @@ -43,4 +43,5 @@ accessible method net/minecraft/client/resources/model/ModelBakery$ModelBakerImp accessible method net/minecraft/client/resources/model/ModelBakery$BakedCacheKey (Lnet/minecraft/resources/ResourceLocation;Lcom/mojang/math/Transformation;Z)V accessible class net/minecraft/world/level/chunk/PalettedContainer$Data accessible field net/minecraft/server/MinecraftServer resources Lnet/minecraft/server/MinecraftServer$ReloadableResources; -accessible class net/minecraft/server/MinecraftServer$ReloadableResources \ No newline at end of file +accessible class net/minecraft/server/MinecraftServer$ReloadableResources +accessible method net/minecraft/client/gui/screens/Screen addRenderableWidget (Lnet/minecraft/client/gui/components/events/GuiEventListener;)Lnet/minecraft/client/gui/components/events/GuiEventListener; diff --git a/fabric/build.gradle b/fabric/build.gradle index 34557ed3..47220caf 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -33,6 +33,7 @@ dependencies { modIncludeImplementation(fabricApi.module("fabric-lifecycle-events-v1", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' } modIncludeImplementation(fabricApi.module("fabric-screen-api-v1", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' } modIncludeImplementation(fabricApi.module("fabric-command-api-v2", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' } + modIncludeImplementation(fabricApi.module("fabric-models-v0", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' } modImplementation(fabricApi.module("fabric-resource-loader-v0", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' } modCompileOnly("com.terraformersmc:modmenu:${rootProject.modmenu_version}") { transitive false } // Remove the next line if you don't want to depend on the API diff --git a/fabric/src/main/java/org/embeddedt/modernfix/fabric/mixin/perf/dynamic_resources/LoaderInstanceMixin.java b/fabric/src/main/java/org/embeddedt/modernfix/fabric/mixin/perf/dynamic_resources/LoaderInstanceMixin.java new file mode 100644 index 00000000..5ab99015 --- /dev/null +++ b/fabric/src/main/java/org/embeddedt/modernfix/fabric/mixin/perf/dynamic_resources/LoaderInstanceMixin.java @@ -0,0 +1,19 @@ +package org.embeddedt.modernfix.fabric.mixin.perf.dynamic_resources; + +import net.fabricmc.fabric.impl.client.model.ModelLoadingRegistryImpl; +import net.minecraft.client.resources.model.ModelBakery; +import org.embeddedt.modernfix.annotation.ClientOnlyMixin; +import org.embeddedt.modernfix.annotation.RequiresMod; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(ModelLoadingRegistryImpl.LoaderInstance.class) +@RequiresMod("fabric-models-v0") +@ClientOnlyMixin +public class LoaderInstanceMixin { + @Redirect(method = "finish", at = @At(value = "FIELD", target = "Lnet/fabricmc/fabric/impl/client/model/ModelLoadingRegistryImpl$LoaderInstance;loader:Lnet/minecraft/client/resources/model/ModelBakery;")) + private void keepLoader(ModelLoadingRegistryImpl.LoaderInstance instance, ModelBakery value) { + /* allow loading models to happen later */ + } +} \ No newline at end of file diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index 0c33028e..7b3901e5 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -32,6 +32,7 @@ "fabric-lifecycle-events-v1": "*", "fabric-screen-api-v1": "*", "fabric-command-api-v2": "*", + "fabric-models-v0": "*", "minecraft": ">=1.16.5" }, "breaks": { diff --git a/forge/src/main/java/org/embeddedt/modernfix/forge/datagen/RuntimeDatagen.java b/forge/src/main/java/org/embeddedt/modernfix/forge/datagen/RuntimeDatagen.java new file mode 100644 index 00000000..c396c68a --- /dev/null +++ b/forge/src/main/java/org/embeddedt/modernfix/forge/datagen/RuntimeDatagen.java @@ -0,0 +1,80 @@ +package org.embeddedt.modernfix.forge.datagen; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.screens.TitleScreen; +import net.minecraft.network.chat.Component; +import net.minecraft.server.packs.PackResources; +import net.minecraft.server.packs.PackType; +import net.minecraft.server.packs.resources.MultiPackResourceManager; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.client.event.ScreenEvent; +import net.minecraftforge.common.data.ExistingFileHelper; +import net.minecraftforge.data.event.GatherDataEvent; +import net.minecraftforge.data.loading.DatagenModLoader; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.ModLoader; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.util.ObfuscationReflectionHelper; +import org.embeddedt.modernfix.ModernFix; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; +import java.util.stream.Collectors; + +@Mod.EventBusSubscriber(value = Dist.CLIENT) +public class RuntimeDatagen { + private static final String RESOURCES_OUT_DIR = getPropertyOrBlank("modernfix.datagen.output"); + private static final String RESOURCES_IN_DIR = getPropertyOrBlank("modernfix.datagen.existing"); + private static final String MODS_LIST = getPropertyOrBlank("modernfix.datagen.mods"); + private static final String EXISTING_MODS_LIST = getPropertyOrBlank("modernfix.datagen.existing_mods"); + private static final boolean IS_FLAT = Boolean.getBoolean("modernfix.datagen.flat"); + + private static String getPropertyOrBlank(String name) { + String val = System.getProperty(name); + if(val == null || val.length() == 0) + return ""; + else + return val; + } + + public static boolean isDatagenAvailable() { + return RESOURCES_OUT_DIR.length() > 0; + } + + public static void runRuntimeDatagen() { + ObfuscationReflectionHelper.setPrivateValue(DatagenModLoader.class, null, true, "runningDataGen"); + Set mods = new HashSet<>(Arrays.stream(MODS_LIST.split(",")).collect(Collectors.toSet())); + ModernFix.LOGGER.info("Beginning runtime datagen for " + mods.size() + " mods..."); + Set existingMods = new HashSet<>(Arrays.stream(EXISTING_MODS_LIST.split(",")).collect(Collectors.toSet())); + Set existingPacks = new HashSet<>(Arrays.stream(RESOURCES_IN_DIR.split(",")).map(Paths::get).collect(Collectors.toSet())); + Path path = Paths.get(RESOURCES_OUT_DIR); + GatherDataEvent.DataGeneratorConfig dataGeneratorConfig = new GatherDataEvent.DataGeneratorConfig(mods, path, Collections.emptyList(), + true, true, true, true, true, mods.isEmpty() || IS_FLAT); + if (!mods.contains("forge")) { + //If we aren't generating data for forge, automatically add forge as an existing so mods can access forge's data + existingMods.add("forge"); + } + ExistingFileHelper existingFileHelper = new ExistingFileHelper(existingPacks, existingMods, true, null, null); + /* Inject the client pack resources from us */ + MultiPackResourceManager manager = ObfuscationReflectionHelper.getPrivateValue(ExistingFileHelper.class, existingFileHelper, "clientResources"); + List oldPacks = new ArrayList<>(manager.listPacks().collect(Collectors.toList())); + oldPacks.add(Minecraft.getInstance().getClientPackSource().getVanillaPack()); + ObfuscationReflectionHelper.setPrivateValue(ExistingFileHelper.class, existingFileHelper, new MultiPackResourceManager(PackType.CLIENT_RESOURCES, oldPacks), "clientResources"); + ModLoader.get().runEventGenerator(mc->new GatherDataEvent(mc, dataGeneratorConfig.makeGenerator(p->dataGeneratorConfig.isFlat() ? p : p.resolve(mc.getModId()), dataGeneratorConfig.getMods().contains(mc.getModId())), dataGeneratorConfig, existingFileHelper)); + dataGeneratorConfig.runAll(); + ObfuscationReflectionHelper.setPrivateValue(DatagenModLoader.class, null, false, "runningDataGen"); + ModernFix.LOGGER.info("Finished runtime datagen."); + } + + @SubscribeEvent + public static void onInitTitleScreen(ScreenEvent.Init.Post event) { + if(isDatagenAvailable() && event.getScreen() instanceof TitleScreen) { + TitleScreen screen = (TitleScreen)event.getScreen(); + screen.addRenderableWidget(Button.builder(Component.literal("DG"), (arg) -> { + runRuntimeDatagen(); + }).pos(screen.width / 2 - 100 - 50, screen.height / 4 + 48).size(50, 20).build()); + } + } +}