Begin the next era, start update to 21.11

Co-authored-by: DerCommander323 <volcarlos323@googlemail.com>
Co-authored-by: coredex-source <samdhi2323@gmail.com>
This commit is contained in:
embeddedt 2025-12-27 11:31:32 -05:00
parent c63b9de971
commit 23a5f2985e
No known key found for this signature in database
GPG Key ID: A69433EC199B5613
101 changed files with 131 additions and 4187 deletions

7
TODO.txt Normal file
View File

@ -0,0 +1,7 @@
- Reimplement mixin.bugfix.entity_pose_stack
- Reimplement dynamic_resources
- Investigate if cache_strongholds is still needed in 26.1
- Check if BlockStateData patch is still needed in compact_mojang_registries
- Check if faster_texture_stitching is still worthwhile with 21.x changes to the stitcher
- Sculk deadlock fix looks unnecessary since Mojang is careful about when they send the event
- Rewrite deduplicate_wall_shapes

View File

@ -61,6 +61,9 @@ neoForge {
}
runs {
configureEach {
systemProperty("modernfix.auditMixinsAtStart", "true")
}
create("client") {
client()
}
@ -133,7 +136,6 @@ val embed by configurations.creating {
dependencies {
implementation(project(":annotations"))
embed(project(":annotations"))
"additionalRuntimeClasspath"(project(":annotations"))
annotationProcessor(project(path = ":annotation-processor", configuration = "shadow"))
val jei_version = rootProject.properties["jei_version"].toString()
@ -142,7 +144,6 @@ dependencies {
compileOnly("curse.maven:ctm-267602:${rootProject.properties["ctm_version"].toString()}")
compileOnly("curse.maven:ldlib-626676:${rootProject.properties["ldlib_version"].toString()}")
compileOnly("curse.maven:supermartijncore-454372:4455391")
compileOnly("curse.maven:patchouli-306770:6164575")
compileOnly("curse.maven:cofhcore-69162:5374122")
compileOnly("curse.maven:resourcefullib-570073:5659871")
compileOnly("curse.maven:kubejs-238086:5853326")
@ -162,6 +163,8 @@ tasks.withType<JavaCompile>().configureEach {
)
)
}
// Show more errors when porting
options.compilerArgs.addAll(listOf("-Xmaxerrs", "1000"))
}
sourceSets {

View File

@ -5,13 +5,13 @@ junit_version=5.10.0-M1
mixinextras_version=0.4.1
mod_id=modernfix
minecraft_version=1.21.1
minecraft_version=1.21.11
enabled_platforms=neoforge
forge_version=21.1.111
parchment_version=2024.11.17
parchment_mc_version=1.21.1
forge_version=21.11.14-beta
parchment_version=2025.12.20
parchment_mc_version=1.21.11
refined_storage_version=4392788
jei_version=19.21.2.313
jei_version=27.3.0.12
rei_version=13.0.678
ctm_version=5587515
ldlib_version=5782845

View File

@ -1,7 +1,8 @@
package org.embeddedt.modernfix;
import net.minecraft.SharedConstants;
import net.minecraft.Util;
import net.minecraft.TracingExecutor;
import net.minecraft.util.Util;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ServerLevel;
@ -14,7 +15,6 @@ import org.embeddedt.modernfix.resources.ReloadExecutor;
import org.embeddedt.modernfix.util.ClassInfoManager;
import java.lang.management.ManagementFactory;
import java.util.concurrent.ExecutorService;
// The value here should match an entry in the META-INF/mods.toml file
public class ModernFix {
@ -31,24 +31,24 @@ public class ModernFix {
// Used to skip computing the blockstate caches twice
public static boolean runningFirstInjection = false;
private static ExecutorService resourceReloadService = null;
private static TracingExecutor resourceReloadService = null;
static {
if(ModernFixMixinPlugin.instance.isOptionEnabled("perf.dedicated_reload_executor.ReloadExecutor")) {
resourceReloadService = ReloadExecutor.createCustomResourceReloadExecutor();
resourceReloadService = new TracingExecutor(ReloadExecutor.createCustomResourceReloadExecutor());
} else {
resourceReloadService = Util.backgroundExecutor();
}
}
public static ExecutorService resourceReloadExecutor() {
public static TracingExecutor resourceReloadExecutor() {
return resourceReloadService;
}
public ModernFix() {
INSTANCE = this;
if(ModernFixMixinPlugin.instance.isOptionEnabled("feature.snapshot_easter_egg.NameChange") && !SharedConstants.getCurrentVersion().isStable())
if(ModernFixMixinPlugin.instance.isOptionEnabled("feature.snapshot_easter_egg.NameChange") && !SharedConstants.getCurrentVersion().stable())
NAME = "PreemptiveFix";
ModernFixPlatformHooks.INSTANCE.onServerCommandRegister(ModernFixCommands::register);
}

View File

@ -1,11 +1,5 @@
package org.embeddedt.modernfix.api.entrypoint;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelBakery;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.client.resources.model.ModelState;
import net.minecraft.client.resources.model.UnbakedModel;
/**
* Implement this interface in a mod class and add it to "modernfix:integration_v1" in your mod metadata file
* to integrate with ModernFix's features.
@ -19,19 +13,4 @@ public interface ModernFixClientIntegration {
*/
default void onDynamicResourcesStatusChange(boolean enabled) {
}
/**
* Called to allow mods to observe the loading of a baked model and either make changes to it or wrap it with their
* own instance.
*
* @param location the ResourceLocation of the model (this may be a ModelResourceLocation)
* @param originalModel the original model
* @param bakery the model bakery - do not touch internal fields as they probably don't behave the way you expect
* with dynamic resources on
* @param textureGetter function to retrieve textures for this model
* @return the model which should actually be loaded for this resource location
*/
default BakedModel onBakedModelLoad(ModelResourceLocation location, UnbakedModel baseModel, BakedModel originalModel, ModelState state, ModelBakery bakery, ModelBakery.TextureGetter textureGetter) {
return originalModel;
}
}

View File

@ -1,76 +0,0 @@
package org.embeddedt.modernfix.api.helpers;
import com.google.common.collect.ImmutableList;
import net.minecraft.client.resources.model.*;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import org.embeddedt.modernfix.dynamicresources.ModelBakeryHelpers;
import org.embeddedt.modernfix.util.DynamicMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiFunction;
@SuppressWarnings("unused")
public final class ModelHelpers {
/**
* Allows converting a ModelResourceLocation back into the corresponding BlockState(s). Try to avoid calling this
* multiple times if possible.
* @param location the location of the model
* @return a list of all blockstates related to the model
*/
public static ImmutableList<BlockState> getBlockStateForLocation(ModelResourceLocation location) {
Optional<Block> blockOpt = BuiltInRegistries.BLOCK.getOptional(location.id());
if(blockOpt.isPresent())
return ModelBakeryHelpers.getBlockStatesForMRL(blockOpt.get().getStateDefinition(), location);
else
return ImmutableList.of();
}
/**
* Allows converting a ModelResourceLocation back into the corresponding BlockState(s). Faster version of its
* companion function if and only if you know the corresponding Block already for some reason.
* @param definition the state definition for the Block
* @param location the location of the model
* @return a list of all blockstates related to the model
*/
public static ImmutableList<BlockState> getBlockStateForLocation(StateDefinition<Block, BlockState> definition, ModelResourceLocation location) {
return ModelBakeryHelpers.getBlockStatesForMRL(definition, location);
}
/**
* Compatibility helper for mods to use to get a map-like view of the model bakery.
* @param modelGetter the model getter function supplied by the integration class
* @return a fake map of the top-level models
*/
public static Map<ResourceLocation, BakedModel> createFakeTopLevelMap(BiFunction<ResourceLocation, ModelState, BakedModel> modelGetter) {
return new DynamicMap<>(ResourceLocation.class, location -> modelGetter.apply(location, BlockModelRotation.X0_Y0));
}
/**
* Provides a ModelBaker for mods to use.
* @param bakery the ModelBakery supplied to your integration
* @return an appropriate ModelBaker
*/
public static ModelBaker adaptBakery(ModelBakery bakery) {
throw new UnsupportedOperationException("TODO");
/*
return new ModelBaker() {
@Override
public UnbakedModel getModel(ResourceLocation resourceLocation) {
return bakery.getModel(resourceLocation);
}
@Nullable
@Override
public BakedModel bake(ResourceLocation resourceLocation, ModelState modelState) {
return ((IExtendedModelBakery)bakery).bakeDefault(resourceLocation, modelState);
}
};
*/
}
}

View File

@ -4,6 +4,7 @@ import com.mojang.brigadier.CommandDispatcher;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.permissions.Permissions;
import org.embeddedt.modernfix.duck.IProfilingServerFunctionManager;
import static net.minecraft.commands.Commands.literal;
@ -11,7 +12,7 @@ import static net.minecraft.commands.Commands.literal;
public class ModernFixCommands {
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
dispatcher.register(literal("modernfix")
.then(literal("mcfunctions").requires(source -> source.hasPermission(3))
.then(literal("mcfunctions").requires(source -> source.permissions().hasPermission(Permissions.COMMANDS_ADMIN))
.executes(context -> {
ServerLevel level = context.getSource().getLevel();
if(level == null) {

View File

@ -1,31 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.chunk_deadlock;
import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
import net.minecraft.core.Holder;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.gameevent.GameEvent;
import org.embeddedt.modernfix.ModernFix;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@Mixin(Entity.class)
public class EntityMixin {
/**
* @author embeddedt
* @reason When an entity is added to the world via the worldgen load path (ChunkMap#postLoadProtoChunk calling
* ServerLevel#addWorldGenChunkEntities), attempts to add a passenger result in a deadlock when the sculk event
* tries to raytrace blocks. To fix this, we skip firing the sculk event if the chunk the entity is within is not
* loaded.
*/
@WrapWithCondition(method = "addPassenger", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;gameEvent(Lnet/minecraft/core/Holder;Lnet/minecraft/world/entity/Entity;)V"))
private boolean onlyAddIfSelfChunkLoaded(Entity instance, Holder<GameEvent> gameEvent, Entity entity) {
var chunkPos = instance.chunkPosition();
if (instance.level() instanceof ServerLevel serverLevel && serverLevel.getChunkSource().getChunkNow(chunkPos.x, chunkPos.z) == null) {
ModernFix.LOGGER.warn("Skipped emitting ENTITY_MOUNT game event for entity {} as it would cause deadlock", instance.toString());
return false;
} else {
return true;
}
}
}

View File

@ -1,24 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.ender_dragon_leak;
import net.minecraft.client.renderer.entity.EnderDragonRenderer;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(EnderDragonRenderer.class)
@ClientOnlyMixin
public abstract class EnderDragonRendererMixin {
@Shadow @Final private EnderDragonRenderer.DragonModel model;
/**
* Prevent leaking the client world through the entity reference.
*/
@Inject(method = "render(Lnet/minecraft/world/entity/boss/enderdragon/EnderDragon;FFLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;I)V", at = @At("RETURN"))
private void clearDragonEntityReference(CallbackInfo ci) {
this.model.entity = null;
}
}

View File

@ -1,29 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.entity_pose_stack;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
import net.neoforged.neoforge.client.event.RenderLivingEvent;
import net.neoforged.bus.api.Event;
import net.neoforged.bus.api.IEventBus;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(LivingEntityRenderer.class)
@ClientOnlyMixin
public class LivingEntityRendererMixin {
@Redirect(method = "render(Lnet/minecraft/world/entity/LivingEntity;FFLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;I)V", at = @At(value = "INVOKE", target = "Lnet/neoforged/bus/api/IEventBus;post(Lnet/neoforged/bus/api/Event;)Lnet/neoforged/bus/api/Event;", ordinal = 0))
private Event fireCheckingPoseStack(IEventBus instance, Event event) {
PoseStack stack = ((RenderLivingEvent)event).getPoseStack();
int size = ((PoseStackAccessor)stack).getPoseStack().size();
instance.post(event);
if (((RenderLivingEvent.Pre)event).isCanceled()) {
// Pop the stack if someone pushed it in the event
while (((PoseStackAccessor)stack).getPoseStack().size() > size) {
stack.popPose();
}
}
return event;
}
}

View File

@ -1,29 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.entity_pose_stack;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.entity.player.PlayerRenderer;
import net.neoforged.bus.api.Event;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.neoforge.client.event.RenderPlayerEvent;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(PlayerRenderer.class)
@ClientOnlyMixin
public class PlayerRendererMixin {
@Redirect(method = "render(Lnet/minecraft/client/player/AbstractClientPlayer;FFLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;I)V", at = @At(value = "INVOKE", target = "Lnet/neoforged/bus/api/IEventBus;post(Lnet/neoforged/bus/api/Event;)Lnet/neoforged/bus/api/Event;", ordinal = 0))
private Event fireCheckingPoseStack(IEventBus instance, Event event) {
PoseStack stack = ((RenderPlayerEvent)event).getPoseStack();
int size = ((PoseStackAccessor)stack).getPoseStack().size();
instance.post(event);
if (((RenderPlayerEvent.Pre)event).isCanceled()) {
// Pop the stack if someone pushed it in the event
while (((PoseStackAccessor)stack).getPoseStack().size() > size) {
stack.popPose();
}
}
return event;
}
}

View File

@ -1,15 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.entity_pose_stack;
import com.mojang.blaze3d.vertex.PoseStack;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import java.util.Deque;
@Mixin(PoseStack.class)
@ClientOnlyMixin
public interface PoseStackAccessor {
@Accessor
Deque<PoseStack.Pose> getPoseStack();
}

View File

@ -2,17 +2,16 @@ package org.embeddedt.modernfix.common.mixin.bugfix.missing_block_entities;
import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Registry;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.PalettedContainerFactory;
import net.minecraft.world.level.chunk.UpgradeData;
import net.minecraft.world.level.levelgen.blending.BlendingData;
import org.embeddedt.modernfix.ModernFix;
@ -38,14 +37,14 @@ public abstract class LevelChunkMixin extends ChunkAccess {
@Shadow @Nullable public abstract BlockEntity getBlockEntity(BlockPos pos, LevelChunk.EntityCreationType creationType);
public LevelChunkMixin(ChunkPos chunkPos, UpgradeData upgradeData, LevelHeightAccessor levelHeightAccessor, Registry<Biome> biomeRegistry, long inhabitedTime, @Nullable LevelChunkSection[] sections, @Nullable BlendingData blendingData) {
super(chunkPos, upgradeData, levelHeightAccessor, biomeRegistry, inhabitedTime, sections, blendingData);
public LevelChunkMixin(ChunkPos chunkPos, UpgradeData upgradeData, LevelHeightAccessor levelHeightAccessor, PalettedContainerFactory palettedContainerFactory, long inhabitedTime, LevelChunkSection @org.jspecify.annotations.Nullable [] sections, @org.jspecify.annotations.Nullable BlendingData blendingData) {
super(chunkPos, upgradeData, levelHeightAccessor, palettedContainerFactory, inhabitedTime, sections, blendingData);
}
@Inject(method = "replaceWithPacketData", at = @At("RETURN"))
private void validateBlockEntitiesInChunk(CallbackInfo ci) {
// No reason to check in singleplayer or on the integrated server
if (this.level.isClientSide && !Minecraft.getInstance().isLocalServer()) {
if (this.level.isClientSide() && !Minecraft.getInstance().isLocalServer()) {
for (int i = 0; i < this.sections.length; i++) {
var section = this.sections[i];
try {

View File

@ -24,7 +24,7 @@ public class MinecraftMixin {
/**
* To mitigate the effect of leaked client worlds, clear most of the data structures that waste memory.
*/
@Inject(method = "disconnect(Lnet/minecraft/client/gui/screens/Screen;Z)V", at = @At(value = "FIELD", opcode = Opcodes.PUTFIELD, target = "Lnet/minecraft/client/Minecraft;level:Lnet/minecraft/client/multiplayer/ClientLevel;"))
@Inject(method = "disconnect(Lnet/minecraft/client/gui/screens/Screen;ZZ)V", at = @At(value = "FIELD", opcode = Opcodes.PUTFIELD, target = "Lnet/minecraft/client/Minecraft;level:Lnet/minecraft/client/multiplayer/ClientLevel;"))
private void clearLevelDataForLeaks(CallbackInfo ci) {
if(this.level != null) {
try {

View File

@ -1,22 +0,0 @@
package org.embeddedt.modernfix.common.mixin.core;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.server.Bootstrap;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
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(Bootstrap.class)
@ClientOnlyMixin
public class BootstrapClientMixin {
/**
* Hack to workaround RenderStateShard deadlock (by loading it early on a single thread). We use validate
* here to ensure Forge registries are initialized.
*/
@Inject(method = "validate", at = @At("HEAD"))
private static void loadClientClasses(CallbackInfo ci) {
RenderType.solid();
}
}

View File

@ -5,6 +5,7 @@ import org.embeddedt.modernfix.util.TimeFormatter;
import org.slf4j.Logger;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.MixinEnvironment;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
@ -22,6 +23,9 @@ public class BootstrapMixin {
private static void doModernFixBootstrap(CallbackInfo ci) {
if(!isBootstrapped) {
LOGGER.info("ModernFix reached bootstrap stage ({} after launch)", TimeFormatter.formatNanos(ManagementFactory.getRuntimeMXBean().getUptime() * 1000L * 1000L));
if (Boolean.getBoolean("modernfix.auditMixinsAtStart")) {
MixinEnvironment.getCurrentEnvironment().audit();
}
}
}
}

View File

@ -1,7 +1,7 @@
package org.embeddedt.modernfix.common.mixin.core;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import net.minecraft.Util;
import net.minecraft.util.Util;
import net.minecraft.server.MinecraftServer;
import org.embeddedt.modernfix.duck.ITimeTrackingServer;
import org.embeddedt.modernfix.neoforge.load.MinecraftServerReloadTracker;
@ -36,7 +36,7 @@ public class MinecraftServerMixin implements ITimeTrackingServer {
return mfix$lastTickStartTime;
}
@Inject(method = "runServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;tickServer(Ljava/util/function/BooleanSupplier;)V"))
@Inject(method = "runServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;processPacketsAndTick(Z)V"))
private void trackTickTime(CallbackInfo ci) {
mfix$lastTickStartTime = Util.getMillis();
}

View File

@ -1,16 +1,15 @@
package org.embeddedt.modernfix.common.mixin.feature.cause_lag_by_disabling_threads;
import net.minecraft.Util;
import net.minecraft.TracingExecutor;
import net.minecraft.util.Util;
import org.embeddedt.modernfix.util.DirectExecutorService;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Shadow;
import java.util.concurrent.ExecutorService;
@Mixin(Util.class)
public class UtilMixin {
@Shadow @Final @Mutable
private static final ExecutorService BACKGROUND_EXECUTOR = new DirectExecutorService();
private static final TracingExecutor BACKGROUND_EXECUTOR = new TracingExecutor(new DirectExecutorService());
}

View File

@ -7,7 +7,7 @@ import com.llamalad7.mixinextras.sugar.ref.LocalRef;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.functions.CommandFunction;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.resources.Identifier;
import net.minecraft.server.ServerFunctionManager;
import org.embeddedt.modernfix.duck.IProfilingServerFunctionManager;
import org.spongepowered.asm.mixin.Final;
@ -25,17 +25,17 @@ import java.util.Map;
@Mixin(ServerFunctionManager.class)
public class ServerFunctionManagerMixin implements IProfilingServerFunctionManager {
@Shadow @Final private static ResourceLocation TICK_FUNCTION_TAG;
@Shadow @Final private static Identifier TICK_FUNCTION_TAG;
private final Map<ResourceLocation, Stopwatch> mfix$functionWatches = new Object2ObjectOpenHashMap<>();
private final Map<Identifier, Stopwatch> mfix$functionWatches = new Object2ObjectOpenHashMap<>();
@Inject(method = "executeTagFunctions", at = @At("HEAD"))
private void resetWatches(Collection<CommandFunction<CommandSourceStack>> functionObjects, ResourceLocation identifier, CallbackInfo ci) {
private void resetWatches(Collection<CommandFunction<CommandSourceStack>> functionObjects, Identifier identifier, CallbackInfo ci) {
mfix$functionWatches.values().forEach(Stopwatch::reset);
}
@Inject(method = "executeTagFunctions", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/ServerFunctionManager;execute(Lnet/minecraft/commands/functions/CommandFunction;Lnet/minecraft/commands/CommandSourceStack;)V"))
private void startWatch(Collection<CommandFunction<CommandSourceStack>> functionObjects, ResourceLocation identifier, CallbackInfo ci, @Local(ordinal = 0) CommandFunction<CommandSourceStack> function, @Share("stopwatch") LocalRef<Stopwatch> watchRef) {
private void startWatch(Collection<CommandFunction<CommandSourceStack>> functionObjects, Identifier identifier, CallbackInfo ci, @Local(ordinal = 0) CommandFunction<CommandSourceStack> function, @Share("stopwatch") LocalRef<Stopwatch> watchRef) {
watchRef.set(null);
if (identifier == TICK_FUNCTION_TAG) {
var watch = mfix$functionWatches.computeIfAbsent(function.id(), i -> Stopwatch.createUnstarted());
@ -45,7 +45,7 @@ public class ServerFunctionManagerMixin implements IProfilingServerFunctionManag
}
@Inject(method = "executeTagFunctions", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/ServerFunctionManager;execute(Lnet/minecraft/commands/functions/CommandFunction;Lnet/minecraft/commands/CommandSourceStack;)V", shift = At.Shift.AFTER))
private void stopWatch(Collection<CommandFunction<CommandSourceStack>> functionObjects, ResourceLocation identifier, CallbackInfo ci, @Share("stopwatch") LocalRef<Stopwatch> watchRef) {
private void stopWatch(Collection<CommandFunction<CommandSourceStack>> functionObjects, Identifier identifier, CallbackInfo ci, @Share("stopwatch") LocalRef<Stopwatch> watchRef) {
var watch = watchRef.get();
if (watch != null && watch.isRunning()) {
watch.stop();
@ -53,14 +53,14 @@ public class ServerFunctionManagerMixin implements IProfilingServerFunctionManag
}
@Inject(method = "executeTagFunctions", at = @At("RETURN"))
private void pruneUnusedWatches(Collection<CommandFunction<CommandSourceStack>> functionObjects, ResourceLocation identifier, CallbackInfo ci) {
private void pruneUnusedWatches(Collection<CommandFunction<CommandSourceStack>> functionObjects, Identifier identifier, CallbackInfo ci) {
mfix$functionWatches.values().removeIf(watch -> watch.elapsed().isZero());
}
@Override
public String mfix$getProfilingResults() {
var list = new ArrayList<>(mfix$functionWatches.entrySet());
list.sort(Comparator.<Map.Entry<ResourceLocation, Stopwatch>, Duration>comparing(e -> e.getValue().elapsed()).reversed());
list.sort(Comparator.<Map.Entry<Identifier, Stopwatch>, Duration>comparing(e -> e.getValue().elapsed()).reversed());
StringBuilder sb = new StringBuilder();
for (var entry : list) {
sb.append(entry.getKey().toString());

View File

@ -39,7 +39,7 @@ public class ProfiledReloadInstanceMixin {
@ModifyVariable(method = "finish", ordinal = 0, argsOnly = true, at = @At("HEAD"))
private List<ProfiledReloadInstance.State> sortStates(List<ProfiledReloadInstance.State> datapoints) {
datapoints = new ArrayList<>(datapoints);
datapoints.sort(Comparator.<ProfiledReloadInstance.State>comparingLong(s -> s.preparationNanos.get() + s.reloadNanos.get()).reversed());
datapoints.sort(Comparator.<ProfiledReloadInstance.State>comparingLong(s -> s.preparationNanos().get() + s.reloadNanos().get()).reversed());
return datapoints;
}
}

View File

@ -25,7 +25,7 @@ public class GameDataMixin {
}
RegisterEvent registryEvent = (RegisterEvent)event;
// We control phases ourselves so we can make a separate progress bar for each phase.
String registryName = registryEvent.getRegistryKey().location().toString();
String registryName = registryEvent.getRegistryKey().identifier().toString();
for(EventPriority phase : EventPriority.values()) {
// FIXME need to use prepend rather than append for it to be visible for now
var pb = StartupNotificationManager.prependProgressBar(registryName, ModList.get().size());

View File

@ -1,68 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.cache_strongholds;
import net.minecraft.Util;
import net.minecraft.core.Holder;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
import net.minecraft.world.level.levelgen.structure.StructureSet;
import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.duck.IChunkGenerator;
import org.embeddedt.modernfix.duck.IServerLevel;
import org.embeddedt.modernfix.world.StrongholdLocationCache;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Mixin(ChunkGeneratorStructureState.class)
public class ChunkGeneratorMixin implements IChunkGenerator {
private WeakReference<ServerLevel> mfix$serverLevel;
@Override
public void mfix$setAssociatedServerLevel(ServerLevel level) {
mfix$serverLevel = new WeakReference<>(level);
}
@Inject(method = "generateRingPositions", at = @At("HEAD"), cancellable = true)
private void useCachedDataIfAvailable(Holder<StructureSet> structureSet, ConcentricRingsStructurePlacement placement, CallbackInfoReturnable<CompletableFuture<List<ChunkPos>>> cir) {
if(placement.count() == 0)
return;
ServerLevel level = searchLevel();
if(level == null)
return;
StrongholdLocationCache cache = ((IServerLevel)level).mfix$getStrongholdCache();
List<ChunkPos> positions = cache.getChunkPosList();
if(positions.isEmpty())
return;
ModernFix.LOGGER.debug("Loaded stronghold cache for dimension {} with {} positions", level.dimension().location(), positions.size());
cir.setReturnValue(CompletableFuture.completedFuture(positions));
}
private ServerLevel searchLevel() {
if(mfix$serverLevel != null)
return mfix$serverLevel.get();
else
return null;
}
@Inject(method = "generateRingPositions", at = @At("RETURN"), cancellable = true)
private void saveCachedData(Holder<StructureSet> structureSet, ConcentricRingsStructurePlacement placement, CallbackInfoReturnable<CompletableFuture<List<ChunkPos>>> cir) {
cir.setReturnValue(cir.getReturnValue().thenApplyAsync(list -> {
if(list.size() == 0)
return list;
ServerLevel level = searchLevel();
if(level != null) {
StrongholdLocationCache cache = ((IServerLevel)level).mfix$getStrongholdCache();
cache.setChunkPosList(list);
ModernFix.LOGGER.debug("Saved stronghold cache for dimension {}", level.dimension().location());
}
return list;
}, Util.backgroundExecutor()));
}
}

View File

@ -1,61 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.cache_strongholds;
import net.minecraft.core.Holder;
import net.minecraft.core.RegistryAccess;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.storage.DimensionDataStorage;
import net.minecraft.world.level.storage.WritableLevelData;
import org.embeddedt.modernfix.duck.IChunkGenerator;
import org.embeddedt.modernfix.duck.IServerLevel;
import org.embeddedt.modernfix.world.StrongholdLocationCache;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.function.Supplier;
@Mixin(ServerLevel.class)
public abstract class ServerLevelMixin extends Level implements IServerLevel {
protected ServerLevelMixin(WritableLevelData arg, ResourceKey<Level> arg2, RegistryAccess arg3, Holder<DimensionType> arg4, Supplier<ProfilerFiller> supplier, boolean bl, boolean bl2, long l, int i) {
super(arg, arg2, arg3, arg4, supplier, bl, bl2, l, i);
}
@Shadow public abstract DimensionDataStorage getDataStorage();
@Shadow @Final private ServerChunkCache chunkSource;
private StrongholdLocationCache mfix$strongholdCache;
/**
* Initialize the stronghold cache but don't force any structure generation yet.
*/
@Redirect(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/chunk/ChunkGeneratorStructureState;ensureStructuresGenerated()V"))
private void hookStrongholdCache(ChunkGeneratorStructureState generator) {
((IChunkGenerator)generator).mfix$setAssociatedServerLevel((ServerLevel)(Object)this);
}
/**
* Now start the stronghold generation process.
*/
@Inject(method = "<init>", at = @At("TAIL"))
private void ensureGeneration(CallbackInfo ci) {
mfix$strongholdCache = this.getDataStorage().computeIfAbsent(
StrongholdLocationCache.factory((ServerLevel)(Object)this),
StrongholdLocationCache.getFileId(this.dimensionTypeRegistration()));
this.chunkSource.getGeneratorState().ensureStructuresGenerated();
}
@Override
public StrongholdLocationCache mfix$getStrongholdCache() {
return mfix$strongholdCache;
}
}

View File

@ -1,6 +1,7 @@
package org.embeddedt.modernfix.common.mixin.perf.compact_bit_storage;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.level.chunk.PaletteResize;
import net.minecraft.world.level.chunk.PalettedContainer;
import org.jetbrains.annotations.Nullable;
import org.objectweb.asm.Opcodes;
@ -40,7 +41,7 @@ public abstract class PalettedContainerMixin<T> {
return;
}
this.data = this.createOrReuseData(null, 0);
this.data.palette().idFor(value);
this.data.palette().idFor(value, (PaletteResize<T>)this);
}
}
}

View File

@ -22,6 +22,7 @@ public class BlockStateDataMixin {
* @author embeddedt
* @reason Reduce memory use of these constant CompoundTags via aggressive interning.
*/
/*
@ModifyExpressionValue(method = "parse", at = @At(value = "INVOKE", target = "Lnet/minecraft/nbt/TagParser;parseTag(Ljava/lang/String;)Lnet/minecraft/nbt/CompoundTag;"))
private static CompoundTag compactTag(CompoundTag tag) {
if (TAG_INTERNER == null) {
@ -40,6 +41,8 @@ public class BlockStateDataMixin {
return new CompoundTag(Map.ofEntries(entries));
}
*/
@Inject(method = "<clinit>", at = @At("RETURN"))
private static void clearInterner(CallbackInfo ci) {
if (TAG_INTERNER != null) {

View File

@ -17,7 +17,7 @@ public class CreateWorldScreenMixin {
return ModernFix.resourceReloadExecutor();
}
@ModifyArg(method = "openFresh", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/WorldLoader;load(Lnet/minecraft/server/WorldLoader$InitConfig;Lnet/minecraft/server/WorldLoader$WorldDataSupplier;Lnet/minecraft/server/WorldLoader$ResultFactory;Ljava/util/concurrent/Executor;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"), index = 3)
@ModifyArg(method = "openCreateWorldScreen", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/WorldLoader;load(Lnet/minecraft/server/WorldLoader$InitConfig;Lnet/minecraft/server/WorldLoader$WorldDataSupplier;Lnet/minecraft/server/WorldLoader$ResultFactory;Ljava/util/concurrent/Executor;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"), index = 3)
private static Executor getCreationExecutorService(Executor e) {
return ModernFix.resourceReloadExecutor();
}

View File

@ -1,5 +1,6 @@
package org.embeddedt.modernfix.common.mixin.perf.dedicated_reload_executor;
import net.minecraft.TracingExecutor;
import net.minecraft.client.Minecraft;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
@ -7,13 +8,11 @@ import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import java.util.concurrent.ExecutorService;
@Mixin(Minecraft.class)
@ClientOnlyMixin
public class MinecraftMixin {
@Redirect(method = { "<init>", "reloadResourcePacks(ZLnet/minecraft/client/Minecraft$GameLoadCookie;)Ljava/util/concurrent/CompletableFuture;" }, at = @At(value = "INVOKE", target = "Lnet/minecraft/Util;backgroundExecutor()Ljava/util/concurrent/ExecutorService;", ordinal = 0))
private ExecutorService getResourceReloadExecutor() {
@Redirect(method = { "<init>", "reloadResourcePacks(ZLnet/minecraft/client/Minecraft$GameLoadCookie;)Ljava/util/concurrent/CompletableFuture;" }, at = @At(value = "INVOKE", target = "Lnet/minecraft/util/Util;backgroundExecutor()Lnet/minecraft/TracingExecutor;", ordinal = 0))
private TracingExecutor getResourceReloadExecutor() {
return ModernFix.resourceReloadExecutor();
}
}

View File

@ -10,7 +10,7 @@ import java.util.concurrent.Executor;
@Mixin(MinecraftServer.class)
public class MinecraftServerMixin {
@ModifyArg(method = "*", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/ReloadableServerResources;loadResources(Lnet/minecraft/server/packs/resources/ResourceManager;Lnet/minecraft/core/LayeredRegistryAccess;Lnet/minecraft/world/flag/FeatureFlagSet;Lnet/minecraft/commands/Commands$CommandSelection;ILjava/util/concurrent/Executor;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"), index = 5)
@ModifyArg(method = "*", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/ReloadableServerResources;loadResources(Lnet/minecraft/server/packs/resources/ResourceManager;Lnet/minecraft/core/LayeredRegistryAccess;Ljava/util/List;Lnet/minecraft/world/flag/FeatureFlagSet;Lnet/minecraft/commands/Commands$CommandSelection;Lnet/minecraft/server/permissions/PermissionSet;Ljava/util/concurrent/Executor;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"), index = 6)
private Executor getReloadExecutor(Executor asyncExecutor) {
return ModernFix.resourceReloadExecutor();
}

View File

@ -1,66 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.deduplicate_wall_shapes;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.mojang.datafixers.util.Pair;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.WallBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.Property;
import net.minecraft.world.phys.shapes.VoxelShape;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.HashMap;
import java.util.Map;
/**
* Most wall blocks use the default set of vanilla properties, and the default sizes for their shapes. This means
* there is no need to reconstruct a separate VoxelShape instance for each wall, we can just repurpose the
* same shape instances. To do this we can cache a mapping between a state (represented only as its prop->value map)
* and the desired shape, and generate the BlockState->VoxelShape map from this for each block.
*/
@Mixin(WallBlock.class)
public abstract class WallBlockMixin extends Block {
private static Map<ImmutableList<Float>, Pair<Map<Map<Property<?>, Comparable<?>>, VoxelShape>, StateDefinition<Block, BlockState>>> CACHE_BY_SHAPE_VALS = new HashMap<>();
public WallBlockMixin(Properties properties) {
super(properties);
}
@Inject(method = "makeShapes", at = @At("HEAD"), cancellable = true)
private synchronized void useCachedShapeMap(float f1, float f2, float f3, float f4, float f5, float f6, CallbackInfoReturnable<Map<BlockState, VoxelShape>> cir) {
ImmutableList<Float> key = ImmutableList.of(f1, f2, f3, f4, f5, f6);
Pair<Map<Map<Property<?>, Comparable<?>>, VoxelShape>, StateDefinition<Block, BlockState>> cache = CACHE_BY_SHAPE_VALS.get(key);
// require the properties to be identical
if(cache == null || !cache.getSecond().getProperties().equals(this.stateDefinition.getProperties()))
return;
ImmutableMap.Builder<BlockState, VoxelShape> builder = ImmutableMap.builder();
for(BlockState state : this.stateDefinition.getPossibleStates()) {
VoxelShape shape = cache.getFirst().get(state.getValues());
if(shape == null)
return; // fallback to vanilla logic
builder.put(state, shape);
}
cir.setReturnValue(builder.build());
}
@Inject(method = "makeShapes", at = @At("RETURN"))
private synchronized void storeCachedShapesByProperty(float f1, float f2, float f3, float f4, float f5, float f6, CallbackInfoReturnable<Map<BlockState, VoxelShape>> cir) {
// never populate cache as a non-vanilla block
if((Class<?>)this.getClass() != WallBlock.class)
return;
ImmutableList<Float> key = ImmutableList.of(f1, f2, f3, f4, f5, f6);
if(!CACHE_BY_SHAPE_VALS.containsKey(key)) {
Map<Map<Property<?>, Comparable<?>>, VoxelShape> cacheByProperties = new HashMap<>();
Map<BlockState, VoxelShape> shapeMap = cir.getReturnValue();
for(Map.Entry<BlockState, VoxelShape> entry : shapeMap.entrySet()) {
cacheByProperties.put(entry.getKey().getValues(), entry.getValue());
}
CACHE_BY_SHAPE_VALS.put(key, Pair.of(cacheByProperties, this.stateDefinition));
}
}
}

View File

@ -1,74 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
import net.minecraft.client.renderer.block.BlockModelShaper;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.duck.IModelHoldingBlockState;
import org.embeddedt.modernfix.dynamicresources.ModelLocationCache;
import org.embeddedt.modernfix.util.DynamicOverridableMap;
import org.spongepowered.asm.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.Map;
@Mixin(BlockModelShaper.class)
@ClientOnlyMixin
public class BlockModelShaperMixin {
@Shadow @Final private ModelManager modelManager;
@Shadow
private Map<BlockState, BakedModel> modelByStateCache;
@Inject(method = { "<init>", "replaceCache" }, at = @At("RETURN"))
private void replaceModelMap(CallbackInfo ci) {
// replace the backing map for mods which will access it
this.modelByStateCache = new DynamicOverridableMap<>(BlockState.class, state -> modelManager.getModel(ModelLocationCache.get(state)));
// Clear the cached models on blockstate objects
for(Block block : BuiltInRegistries.BLOCK) {
for(BlockState state : block.getStateDefinition().getPossibleStates()) {
if(state instanceof IModelHoldingBlockState modelHolder) {
modelHolder.mfix$setModel(null);
}
}
}
}
private BakedModel cacheBlockModel(BlockState state) {
// Do all model system accesses in the unlocked path
ModelResourceLocation mrl = ModelLocationCache.get(state);
BakedModel model = mrl == null ? null : modelManager.getModel(mrl);
if (model == null) {
model = modelManager.getMissingModel();
}
return model;
}
/**
* @author embeddedt
* @reason get the model from the dynamic model provider
*/
@Overwrite
public BakedModel getBlockModel(BlockState state) {
if(state instanceof IModelHoldingBlockState modelHolder) {
BakedModel model = modelHolder.mfix$getModel();
if(model != null) {
return model;
}
model = this.cacheBlockModel(state);
modelHolder.mfix$setModel(model);
return model;
} else {
return this.cacheBlockModel(state);
}
}
}

View File

@ -1,26 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.world.level.block.state.BlockBehaviour;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.duck.IModelHoldingBlockState;
import org.spongepowered.asm.mixin.Mixin;
import java.lang.ref.SoftReference;
@Mixin(BlockBehaviour.BlockStateBase.class)
@ClientOnlyMixin
public class BlockStateBaseMixin implements IModelHoldingBlockState {
private volatile SoftReference<BakedModel> mfix$model;
@Override
public BakedModel mfix$getModel() {
var ref = mfix$model;
return ref != null ? ref.get() : null;
}
@Override
public void mfix$setModel(BakedModel model) {
mfix$model = model != null ? new SoftReference<>(model) : null;
}
}

View File

@ -1,117 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableList;
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import it.unimi.dsi.fastutil.Pair;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntMaps;
import it.unimi.dsi.fastutil.objects.ReferenceObjectImmutablePair;
import net.minecraft.client.color.block.BlockColors;
import net.minecraft.client.renderer.block.model.BlockModelDefinition;
import net.minecraft.client.resources.model.BlockStateModelLoader;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.core.DefaultedRegistry;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.duck.IBlockStateModelLoader;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
@Mixin(BlockStateModelLoader.class)
@ClientOnlyMixin
public abstract class BlockStateModelLoaderMixin implements IBlockStateModelLoader {
@Shadow protected abstract void loadBlockStateDefinitions(ResourceLocation resourceLocation, StateDefinition<Block, BlockState> stateDefinition);
@Shadow @Mutable @Final private Object2IntMap<BlockState> modelGroups;
private ImmutableList<BlockState> filteredStates;
@Inject(method = "<init>", at = @At("RETURN"))
private void makeModelGroupsSynchronized(Map map, ProfilerFiller profilerFiller, UnbakedModel unbakedModel, BlockColors blockColors, BiConsumer biConsumer, CallbackInfo ci) {
this.modelGroups = Object2IntMaps.synchronize(this.modelGroups);
}
@Override
public void loadSpecificBlock(ModelResourceLocation location) {
var optionalBlock = BuiltInRegistries.BLOCK.getOptional(location.id());
if(optionalBlock.isPresent()) {
// embeddedt note - filtering is currently disabled as it's quite inefficient to do vs. just loading
// the extra models and letting LRU deal with it
/*
try {
// Only filter states if we are in a world and not in the loading overlay
filteredStates = (Minecraft.getInstance().getOverlay() == null && Minecraft.getInstance().level != null) ? ModelBakeryHelpers.getBlockStatesForMRL(optionalBlock.get().getStateDefinition(), location) : null;
} catch(RuntimeException e) {
ModernFix.LOGGER.error("Exception filtering states on {}", location, e);
filteredStates = null;
}
*/
try {
this.loadBlockStateDefinitions(location.id(), optionalBlock.get().getStateDefinition());
} finally {
filteredStates = null;
}
}
}
@Redirect(method = "loadAllBlockStates", at = @At(value = "INVOKE", target = "Lnet/minecraft/core/DefaultedRegistry;iterator()Ljava/util/Iterator;"))
private Iterator<?> skipIteratingBlocks(DefaultedRegistry instance) {
return Collections.emptyIterator();
}
@Redirect(method = "loadBlockStateDefinitions", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/state/StateDefinition;getPossibleStates()Lcom/google/common/collect/ImmutableList;"))
private ImmutableList<BlockState> getFilteredStates(StateDefinition<Block, BlockState> instance) {
return this.filteredStates != null ? this.filteredStates : instance.getPossibleStates();
}
// Add some caching around key hot paths
private final Cache<ReferenceObjectImmutablePair<BlockStateModelLoader.LoadedJson, ResourceLocation>, BlockModelDefinition> cachedBlockModelDefs = CacheBuilder.newBuilder()
.maximumSize(100)
.build();
private static final Cache<Pair<StateDefinition<Block, BlockState>, String>, Predicate<BlockState>> cachedBlockStatePredicates = CacheBuilder.newBuilder()
.maximumSize(100)
.build();
@WrapOperation(method = "loadBlockStateDefinitions", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/BlockStateModelLoader$LoadedJson;parse(Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/client/renderer/block/model/BlockModelDefinition$Context;)Lnet/minecraft/client/renderer/block/model/BlockModelDefinition;"))
private BlockModelDefinition avoidMultipleParses(BlockStateModelLoader.LoadedJson instance, ResourceLocation blockStateId, BlockModelDefinition.Context context, Operation<BlockModelDefinition> original) {
try {
return cachedBlockModelDefs.get(ReferenceObjectImmutablePair.of(instance, blockStateId), () -> original.call(instance, blockStateId, context));
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
@WrapMethod(method = "predicate")
private static Predicate<BlockState> memoizePredicate(StateDefinition<Block, BlockState> stateDefentition, String properties, Operation<Predicate<BlockState>> original) {
try {
return cachedBlockStatePredicates.get(Pair.of(stateDefentition, properties), () -> original.call(stateDefentition, properties));
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -1,66 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
import com.google.common.base.Stopwatch;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.neoforged.bus.api.Event;
import net.neoforged.fml.ModContainer;
import net.neoforged.fml.ModList;
import net.neoforged.fml.ModLoader;
import net.neoforged.fml.util.ObfuscationReflectionHelper;
import net.neoforged.neoforge.client.ClientHooks;
import net.neoforged.neoforge.client.event.ModelEvent;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.neoforge.dynresources.ModelBakeEventHelper;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.Comparator;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Mixin(ClientHooks.class)
public class ForgeHooksClientMixin {
/**
* Generate a more realistic keySet that contains every item and block model location, to help with mod compat.
*/
@Redirect(method = "onModifyBakingResult", at = @At(value = "INVOKE", target = "Lnet/neoforged/fml/ModLoader;postEvent(Lnet/neoforged/bus/api/Event;)V"), remap = false)
private static void postNamespacedKeySetEvent(Event event) {
if(ModLoader.hasErrors())
return;
ModelEvent.ModifyBakingResult bakeEvent = ((ModelEvent.ModifyBakingResult)event);
Stopwatch globalTimer = Stopwatch.createStarted();
Stopwatch selfTimer = Stopwatch.createStarted();
ModelBakeEventHelper helper = new ModelBakeEventHelper(bakeEvent.getModels());
selfTimer.stop();
Method acceptEv = ObfuscationReflectionHelper.findMethod(ModContainer.class, "acceptEvent", Event.class);
Map<String, Stopwatch> times = new Object2ObjectOpenHashMap<>();
times.put("modernfix", selfTimer);
ModList.get().forEachModContainer((id, mc) -> {
Map<ModelResourceLocation, BakedModel> newRegistry = helper.wrapRegistry(id);
ModelEvent.ModifyBakingResult postedEvent = new ModelEvent.ModifyBakingResult(newRegistry, bakeEvent.getTextureGetter(), bakeEvent.getModelBakery());
Stopwatch timer = times.computeIfAbsent(id, $ -> Stopwatch.createUnstarted());
timer.start();
try {
acceptEv.invoke(mc, postedEvent);
} catch(ReflectiveOperationException e) {
e.printStackTrace();
}
timer.stop();
});
globalTimer.stop();
if (globalTimer.elapsed(TimeUnit.SECONDS) >= 1) {
ModernFix.LOGGER.warn("Posting dynamic ModelEvent.ModifyBakingResult to mods took {}, breakdown below:", globalTimer);
times.entrySet().stream()
.sorted(Comparator.<Map.Entry<String, Stopwatch>, Duration>comparing(e -> e.getValue().elapsed()).reversed())
.filter(e -> e.getValue().elapsed(TimeUnit.MILLISECONDS) > 50)
.forEach(entry -> {
ModernFix.LOGGER.warn(" {}: {}", entry.getKey(), entry.getValue().toString());
});
}
}
}

View File

@ -1,91 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
import net.minecraft.client.renderer.ItemModelShaper;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import net.neoforged.neoforge.client.model.RegistryAwareItemModelShaper;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.dynamicresources.DynamicModelCache;
import org.embeddedt.modernfix.dynamicresources.ModelLocationCache;
import org.embeddedt.modernfix.util.ItemMesherMap;
import org.spongepowered.asm.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.HashMap;
import java.util.Map;
@Mixin(RegistryAwareItemModelShaper.class)
@ClientOnlyMixin
public abstract class ItemModelMesherForgeMixin extends ItemModelShaper {
@Shadow(remap = false) @Final @Mutable private Map<Item, ModelResourceLocation> locations;
private Map<Item, ModelResourceLocation> overrideLocations;
private final DynamicModelCache<Item> mfix$modelCache = new DynamicModelCache<>(k -> this.mfix$getModelSlow((Item)k), true);
public ItemModelMesherForgeMixin(ModelManager arg) {
super(arg);
}
private static final ModelResourceLocation SENTINEL = new ModelResourceLocation(ResourceLocation.fromNamespaceAndPath("modernfix", "sentinel"), "sentinel");
@Inject(method = "<init>", at = @At("RETURN"))
private void replaceLocationMap(CallbackInfo ci) {
overrideLocations = new HashMap<>();
// need to replace this map because mods query locations through it
locations = new ItemMesherMap<>(this::mfix$getLocationForge);
}
@Unique
private ModelResourceLocation mfix$getLocationForge(Item item) {
ModelResourceLocation map = overrideLocations.getOrDefault(item, SENTINEL);
if(map == SENTINEL) {
/* generate the appropriate location from our cache */
map = ModelLocationCache.get(item);
}
return map;
}
private BakedModel mfix$getModelSlow(Item key) {
ModelResourceLocation map = mfix$getLocationForge(key);
return map == null ? null : getModelManager().getModel(map);
}
/**
* @author embeddedt
* @reason Get the stored location for that item and meta, and get the model
* from that location from the model manager.
**/
@Overwrite
@Override
public BakedModel getItemModel(Item item) {
return this.mfix$modelCache.get(item);
}
/**
* @author embeddedt
* @reason Don't get all models during init (with dynamic loading, that would
* generate them all). Just store location instead.
**/
@Overwrite
@Override
public void register(Item item, ModelResourceLocation location) {
overrideLocations.put(item, location);
}
/**
* @author embeddedt
* @reason Disable cache rebuilding (with dynamic loading, that would generate
* all models).
**/
@Overwrite
@Override
public void rebuildCache() {
this.mfix$modelCache.clear();
}
}

View File

@ -1,91 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import net.minecraft.client.renderer.ItemModelShaper;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.dynamicresources.DynamicModelCache;
import org.embeddedt.modernfix.dynamicresources.ModelLocationCache;
import org.embeddedt.modernfix.util.DynamicInt2ObjectMap;
import org.spongepowered.asm.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.HashMap;
import java.util.Map;
@Mixin(ItemModelShaper.class)
@ClientOnlyMixin
public abstract class ItemModelShaperMixin {
@Shadow public abstract ModelManager getModelManager();
@Shadow @Final @Mutable private Int2ObjectMap<BakedModel> shapesCache;
private Map<Item, ModelResourceLocation> overrideLocationsVanilla;
public ItemModelShaperMixin() {
super();
}
private static final ModelResourceLocation SENTINEL_VANILLA = new ModelResourceLocation(ResourceLocation.fromNamespaceAndPath("modernfix", "sentinel"), "sentinel");
private final DynamicModelCache<Item> mfix$itemModelCache = new DynamicModelCache<>(k -> this.mfix$getModelForItem((Item)k), true);
@Inject(method = "<init>", at = @At("RETURN"))
private void replaceLocationMap(CallbackInfo ci) {
overrideLocationsVanilla = new HashMap<>();
this.shapesCache = new DynamicInt2ObjectMap<>(index -> getModelManager().getModel(ModelLocationCache.get(Item.byId(index))));
}
@Unique
private ModelResourceLocation mfix$getLocation(Item item) {
ModelResourceLocation map = overrideLocationsVanilla.getOrDefault(item, SENTINEL_VANILLA);
if(map == SENTINEL_VANILLA) {
/* generate the appropriate location from our cache */
map = ModelLocationCache.get(item);
}
return map;
}
private BakedModel mfix$getModelForItem(Item item) {
ModelResourceLocation map = mfix$getLocation(item);
return map == null ? null : getModelManager().getModel(map);
}
/**
* @author embeddedt
* @reason Get the stored location for that item and meta, and get the model
* from that location from the model manager.
**/
@Overwrite
public BakedModel getItemModel(Item item) {
return this.mfix$itemModelCache.get(item);
}
/**
* @author embeddedt
* @reason Don't get all models during init (with dynamic loading, that would
* generate them all). Just store location instead.
**/
@Overwrite
public void register(Item item, ModelResourceLocation location) {
overrideLocationsVanilla.put(item, location);
}
/**
* @author embeddedt
* @reason Disable cache rebuilding (with dynamic loading, that would generate
* all models).
**/
@Overwrite
public void rebuildCache() {
this.mfix$itemModelCache.clear();
}
}

View File

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

View File

@ -1,22 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.model.ModelManager;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.duck.IExtendedModelManager;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Minecraft.class)
@ClientOnlyMixin
public abstract class MinecraftMixin_ModelTicking {
@Shadow public abstract ModelManager getModelManager();
@Inject(method = "tick", at = @At(value = "RETURN"))
private void tickModels(CallbackInfo ci) {
((IExtendedModelManager)this.getModelManager()).mfix$tick();
}
}

View File

@ -1,85 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.Material;
import net.minecraft.client.resources.model.ModelBakery;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.client.resources.model.ModelState;
import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.resources.ResourceLocation;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.duck.IExtendedModelBakery;
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 org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import java.util.function.Function;
@Mixin(ModelBakery.ModelBakerImpl.class)
@ClientOnlyMixin
public abstract class ModelBakerImplMixin {
@Shadow public abstract UnbakedModel getModel(ResourceLocation location);
@Shadow(aliases = {"this$0"}) @Final private ModelBakery field_40571;
@Unique
private int mfix$getDepth = 0;
/**
* @author embeddedt
* @reason force parent resolution to happen before model gets baked
*/
@ModifyReturnValue(method = "getModel", at = @At("RETURN"))
private UnbakedModel resolveParents(UnbakedModel model) {
mfix$getDepth++;
if(mfix$getDepth == 1) {
try {
model.resolveParents(this::getModel);
} catch(Exception e) {
ModernFix.LOGGER.warn("Exception encountered resolving parents", e);
}
}
mfix$getDepth--;
return model;
}
@WrapMethod(method = "bake(Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/client/resources/model/ModelState;)Lnet/minecraft/client/resources/model/BakedModel;")
private BakedModel mfix$lockWhenBaking(ResourceLocation location, ModelState transform, Operation<BakedModel> original) {
var lock = ((IExtendedModelBakery)this.field_40571).mfix$getLock();
lock.lock();
try {
return original.call(location, transform);
} finally {
lock.unlock();
}
}
/**
* @author embeddedt
* @reason Handle dynamic model loading
*/
@Overwrite(remap = false)
public UnbakedModel getTopLevelModel(ModelResourceLocation location) {
IExtendedModelBakery bakery = (IExtendedModelBakery)this.field_40571;
UnbakedModel model = bakery.mfix$loadUnbakedModelDynamic(location);
return model == bakery.mfix$getMissingModel() ? null : model;
}
@WrapMethod(method = "bake(Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/client/resources/model/ModelState;Ljava/util/function/Function;)Lnet/minecraft/client/resources/model/BakedModel;", remap = false)
private BakedModel mfix$lockWhenBaking(ResourceLocation location, ModelState transform, Function<Material, TextureAtlasSprite> textureGetter, Operation<BakedModel> original) {
var lock = ((IExtendedModelBakery)this.field_40571).mfix$getLock();
lock.lock();
try {
return original.call(location, transform, textureGetter);
} finally {
lock.unlock();
}
}
}

View File

@ -1,274 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import net.minecraft.client.color.block.BlockColors;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.BlockModelRotation;
import net.minecraft.client.resources.model.BlockStateModelLoader;
import net.minecraft.client.resources.model.ModelBakery;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.core.DefaultedRegistry;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.profiling.ProfilerFiller;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.ModernFixClient;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.api.entrypoint.ModernFixClientIntegration;
import org.embeddedt.modernfix.duck.IBlockStateModelLoader;
import org.embeddedt.modernfix.duck.IExtendedModelBakery;
import org.embeddedt.modernfix.util.DynamicOverridableMap;
import org.embeddedt.modernfix.util.LRUMap;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
@Mixin(ModelBakery.class)
@ClientOnlyMixin
public abstract class ModelBakeryMixin implements IExtendedModelBakery {
@Unique
private BlockStateModelLoader dynamicLoader;
@Unique
private final ReentrantLock modelBakeryLock = new ReentrantLock();
@Unique
private ModelBakery.TextureGetter textureGetter;
@Unique
private BakedModel bakedMissingModel;
@Shadow abstract UnbakedModel getModel(ResourceLocation resourceLocation);
@Shadow @Final private UnbakedModel missingModel;
@Unique
private static final boolean DEBUG_MODEL_LOADS = Boolean.getBoolean("modernfix.debugDynamicModelLoading");
/**
* Bake a model using the provided texture getter and location. The model is stored in {@link ModelBakeryMixin#bakedTopLevelModels}.
*/
@Shadow(aliases = "lambda$bakeModels$6") protected abstract void method_61072(ModelBakery.TextureGetter getter, ModelResourceLocation location, UnbakedModel model);
@Shadow @Mutable @Final private Map<ModelResourceLocation, BakedModel> bakedTopLevelModels;
@Shadow @Mutable @Final public Map<ModelResourceLocation, UnbakedModel> topLevelModels;
@Shadow @Mutable @Final private Map<ResourceLocation, UnbakedModel> unbakedCache;
@Shadow @Mutable @Final public Map<ModelBakery.BakedCacheKey, BakedModel> bakedCache;
@Shadow protected abstract void loadItemModelAndDependencies(ResourceLocation resourceLocation);
@Shadow @Final public static ModelResourceLocation MISSING_MODEL_VARIANT;
@Shadow protected abstract void registerModelAndLoadDependencies(ModelResourceLocation modelLocation, UnbakedModel model);
private final Map<ModelResourceLocation, BakedModel> mfix$emulatedBakedRegistry = new DynamicOverridableMap<>(ModelResourceLocation.class, this::loadBakedModelDynamic);
@Override
public UnbakedModel mfix$loadUnbakedModelDynamic(ModelResourceLocation location) {
if(location.equals(MISSING_MODEL_VARIANT)) {
return missingModel;
}
modelBakeryLock.lock();
try {
UnbakedModel existing = this.topLevelModels.get(location);
if (existing != null) {
return existing;
}
if(DEBUG_MODEL_LOADS) {
ModernFix.LOGGER.info("Loading model {}", location);
}
if(location.variant().equals("inventory")) {
this.loadItemModelAndDependencies(location.id());
} else if (location.variant().equals("fabric_resource") || location.variant().equals("standalone")) {
UnbakedModel unbakedModel = this.getModel(location.id());
this.registerModelAndLoadDependencies(location, unbakedModel);
} else {
((IBlockStateModelLoader)dynamicLoader).loadSpecificBlock(location);
}
return this.topLevelModels.getOrDefault(location, this.missingModel);
} finally {
modelBakeryLock.unlock();
}
}
@WrapMethod(method = "getModel")
private UnbakedModel mfix$lockWhenGettingModel(ResourceLocation modelLocation, Operation<UnbakedModel> original) {
modelBakeryLock.lock();
try {
return original.call(modelLocation);
} finally {
modelBakeryLock.unlock();
}
}
@Override
public UnbakedModel mfix$getMissingModel() {
return missingModel;
}
@Unique
private BakedModel loadBakedModelDynamic(ModelResourceLocation location) {
if(location.equals(MISSING_MODEL_VARIANT)) {
return bakedMissingModel;
}
BakedModel model;
modelBakeryLock.lock();
try {
model = bakedTopLevelModels.get(location);
if(model == null) {
UnbakedModel prototype = mfix$loadUnbakedModelDynamic(location);
if(prototype == missingModel) {
model = bakedMissingModel;
} else {
prototype.resolveParents(this::getModel);
if(DEBUG_MODEL_LOADS) {
ModernFix.LOGGER.info("Baking model {}", location);
}
this.method_61072(this.textureGetter, location, prototype);
model = bakedTopLevelModels.remove(location);
if(model == null) {
ModernFix.LOGGER.error("Failed to load model " + location);
model = bakedMissingModel;
}
for(ModernFixClientIntegration integration : ModernFixClient.CLIENT_INTEGRATIONS) {
try {
model = integration.onBakedModelLoad(location, prototype, model, BlockModelRotation.X0_Y0, (ModelBakery)(Object)this, this.textureGetter);
} catch (RuntimeException e) {
ModernFix.LOGGER.error("Exception encountered running dynamic resources integration", e);
}
}
}
}
} finally {
modelBakeryLock.unlock();
}
return model;
}
@ModifyExpressionValue(method = "<init>", at = @At(value = "CONSTANT", args = "stringValue=missing_model"))
private String replaceBackingMaps(String original) {
this.unbakedCache = new LRUMap<>(this.unbakedCache);
this.bakedCache = new LRUMap<>(this.bakedCache);
this.topLevelModels = new LRUMap<>(this.topLevelModels);
this.bakedTopLevelModels = new LRUMap<>(this.bakedTopLevelModels);
return original;
}
@WrapOperation(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/BlockStateModelLoader;loadAllBlockStates()V"))
private void noInitialBlockStateLoad(BlockStateModelLoader instance, Operation<Void> original) {
dynamicLoader = instance;
original.call(instance);
}
@Redirect(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/core/DefaultedRegistry;keySet()Ljava/util/Set;"))
private Set<?> skipLoadingItems(DefaultedRegistry instance) {
return Collections.emptySet();
}
@Inject(method = "bakeModels", at = @At("HEAD"))
private void storeTextureGetterAndBakeMissing(ModelBakery.TextureGetter textureGetter, CallbackInfo ci) {
this.textureGetter = textureGetter;
this.method_61072(textureGetter, MISSING_MODEL_VARIANT, Objects.requireNonNull(this.topLevelModels.get(MISSING_MODEL_VARIANT)));
this.bakedMissingModel = this.bakedTopLevelModels.get(MISSING_MODEL_VARIANT);
}
private boolean inInitialLoad = true;
@Inject(method = "bakeModels", at = @At("RETURN"))
private void onInitialBakeFinish(ModelBakery.TextureGetter textureGetter, CallbackInfo ci) {
var permanentMRLs = new ObjectOpenHashSet<>(this.bakedTopLevelModels.keySet());
((LRUMap<ModelResourceLocation, BakedModel>)this.bakedTopLevelModels).setPermanentEntries(permanentMRLs);
ModernFix.LOGGER.info("Dynamic model bakery initial baking finished, with {} permanent top level baked models", this.bakedTopLevelModels.size());
}
@Inject(method = "<init>", at = @At("RETURN"))
private void onInitialLoadFinish(BlockColors blockColors, ProfilerFiller profilerFiller, Map map, Map map2, CallbackInfo ci) {
var permanentMRLs = new ObjectOpenHashSet<>(this.topLevelModels.keySet());
((LRUMap<ModelResourceLocation, UnbakedModel>)this.topLevelModels).setPermanentEntries(permanentMRLs);
ModernFix.LOGGER.info("Dynamic model bakery loading finished, with {} permanent top level models", this.topLevelModels.size());
}
@Unique
private int tickCount;
@Unique
private static final int MAXIMUM_CACHE_SIZE = 1000;
private void runCleanup() {
try {
((LRUMap<?, ?>)this.unbakedCache).dropEntriesToMeetSize(MAXIMUM_CACHE_SIZE);
} catch(RuntimeException e) {
throw new IllegalStateException("Exception dropping entries in unbaked cache", e);
}
try {
((LRUMap<?, ?>)this.bakedCache).dropEntriesToMeetSize(MAXIMUM_CACHE_SIZE);
} catch(RuntimeException e) {
throw new IllegalStateException("Exception dropping entries in baked cache", e);
}
try {
((LRUMap<?, ?>)this.topLevelModels).dropEntriesToMeetSize(MAXIMUM_CACHE_SIZE);
} catch(RuntimeException e) {
throw new IllegalStateException("Exception dropping entries in top level models", e);
}
try {
((LRUMap<?, ?>)this.bakedTopLevelModels).dropEntriesToMeetSize(MAXIMUM_CACHE_SIZE);
} catch(RuntimeException e) {
throw new IllegalStateException("Exception dropping entries in baked top level models", e);
}
}
@Override
public void mfix$finishLoading() {
inInitialLoad = false;
}
@Override
public void mfix$tick() {
if(inInitialLoad) {
return;
}
tickCount++;
if((tickCount % 200) == 0) {
if(modelBakeryLock.tryLock()) {
try {
runCleanup();
} finally {
modelBakeryLock.unlock();
}
}
}
}
/**
* @author embeddedt
* @reason We provide a fake baked registry to the rest of Minecraft, that dynamically loads models.
*/
@Overwrite
public Map<ModelResourceLocation, BakedModel> getBakedTopLevelModels() {
return this.mfix$emulatedBakedRegistry;
}
@Override
public ReentrantLock mfix$getLock() {
return this.modelBakeryLock;
}
}

View File

@ -1,120 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.client.renderer.block.model.BlockModel;
import net.minecraft.client.resources.model.AtlasSet;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.BlockStateModelLoader;
import net.minecraft.client.resources.model.ModelBakery;
import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.Resource;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.util.GsonHelper;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.duck.IExtendedModelBakery;
import org.embeddedt.modernfix.duck.IExtendedModelManager;
import org.embeddedt.modernfix.util.CacheUtil;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Coerce;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.Executor;
import java.util.function.Function;
import java.util.stream.Collectors;
@Mixin(ModelManager.class)
@ClientOnlyMixin
public class ModelManagerMixin implements IExtendedModelManager {
@Shadow private Map<ResourceLocation, BakedModel> bakedRegistry;
@Unique
private Runnable tickHandler = () -> {};
@Inject(method = "<init>", at = @At("RETURN"))
private void injectDummyBakedRegistry(CallbackInfo ci) {
if(this.bakedRegistry == null) {
this.bakedRegistry = new HashMap<>();
}
}
@ModifyArg(method = "loadBlockModels", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;thenCompose(Ljava/util/function/Function;)Ljava/util/concurrent/CompletableFuture;", ordinal = 0), index = 0)
private static Function<Map<ResourceLocation, Resource>, ? extends CompletionStage<Map<ResourceLocation, BlockModel>>> deferBlockModelLoad(Function<Map<ResourceLocation, Resource>, ? extends CompletionStage<Map<ResourceLocation, BlockModel>>> fn, @Local(ordinal = 0, argsOnly = true) ResourceManager manager) {
return resourceMap -> {
var cache = CacheUtil.<ResourceLocation, BlockModel>simpleCacheForLambda(location -> loadSingleBlockModel(manager, location), 100L);
return CompletableFuture.completedFuture(Maps.asMap(Set.copyOf(resourceMap.keySet()), location -> cache.getUnchecked(location)));
};
}
@Redirect(method = "reload", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/ModelManager;loadBlockStates(Lnet/minecraft/server/packs/resources/ResourceManager;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"))
private CompletableFuture<Map<ResourceLocation, List<BlockStateModelLoader.LoadedJson>>> deferBlockStateLoad(ResourceManager manager, Executor executor) {
var cache = CacheUtil.<ResourceLocation, List<BlockStateModelLoader.LoadedJson>>simpleCacheForLambda(location -> loadSingleBlockState(manager, location), 100L);
var blockStateKeys = Set.copyOf(BlockStateModelLoader.BLOCKSTATE_LISTER.listMatchingResourceStacks(manager).keySet());
return CompletableFuture.completedFuture(Maps.asMap(blockStateKeys, location -> cache.getUnchecked(location)));
}
@Redirect(method = "loadModels", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/state/StateDefinition;getPossibleStates()Lcom/google/common/collect/ImmutableList;"))
private ImmutableList<BlockState> skipCollection(StateDefinition<Block, BlockState> definition) {
return ImmutableList.of();
}
private static BlockModel loadSingleBlockModel(ResourceManager manager, ResourceLocation location) {
return manager.getResource(location).map(resource -> {
try (BufferedReader reader = resource.openAsReader()) {
return BlockModel.fromStream(reader);
} catch(IOException e) {
ModernFix.LOGGER.error("Couldn't load model", e);
return null;
}
}).orElse(null);
}
private List<BlockStateModelLoader.LoadedJson> loadSingleBlockState(ResourceManager manager, ResourceLocation location) {
return manager.getResourceStack(location).stream().map(resource -> {
try (BufferedReader reader = resource.openAsReader()) {
return new BlockStateModelLoader.LoadedJson(resource.sourcePackId(), GsonHelper.parse(reader));
} catch(IOException e) {
ModernFix.LOGGER.error("Couldn't load blockstate", e);
return null;
}
}).filter(Objects::nonNull).collect(Collectors.toList());
}
@Inject(method = "loadModels", at = @At("RETURN"))
private void storeTicker(ProfilerFiller profilerFiller, Map<ResourceLocation, AtlasSet.StitchResult> map, ModelBakery modelBakery, CallbackInfoReturnable<?> cir) {
tickHandler = ((IExtendedModelBakery)modelBakery)::mfix$tick;
}
@Inject(method = "apply", at = @At("RETURN"))
private void freezeBakery(@Coerce Object reloadState, ProfilerFiller profilerFiller, CallbackInfo ci, @Local(ordinal = 0) ModelBakery bakery) {
((IExtendedModelBakery)bakery).mfix$finishLoading();
}
@Override
public void mfix$tick() {
tickHandler.run();
}
}

View File

@ -1,16 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources.ctm;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelBakery;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import java.util.Map;
@Mixin(ModelBakery.class)
@ClientOnlyMixin
public interface CTMModelBakeryAccessor {
@Accessor("bakedCache")
Map<ModelBakery.BakedCacheKey, BakedModel> mfix$getBakedCache();
}

View File

@ -1,114 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources.ctm;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.mojang.datafixers.util.Pair;
import net.minecraft.client.resources.model.*;
import net.minecraft.resources.ResourceLocation;
import org.embeddedt.modernfix.ModernFixClient;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.annotation.RequiresMod;
import org.embeddedt.modernfix.api.entrypoint.ModernFixClientIntegration;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import team.chisel.ctm.CTM;
import team.chisel.ctm.api.model.IModelCTM;
import team.chisel.ctm.client.model.AbstractCTMBakedModel;
import team.chisel.ctm.client.model.ModelCTM;
import team.chisel.ctm.client.texture.IMetadataSectionCTM;
import team.chisel.ctm.client.util.ResourceUtil;
import team.chisel.ctm.client.util.TextureMetadataHandler;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.util.*;
@Mixin(TextureMetadataHandler.class)
@RequiresMod("ctm")
@ClientOnlyMixin
public abstract class TextureMetadataHandlerMixin implements ModernFixClientIntegration {
@Shadow(remap = false) @Nonnull protected abstract BakedModel wrap(UnbakedModel model, BakedModel object) throws IOException;
@Shadow(remap = false) @Final private Multimap<ResourceLocation, Material> scrapedTextures;
@Inject(method = "<init>", at = @At("RETURN"))
private void subscribeDynamic(CallbackInfo ci) {
ModernFixClient.CLIENT_INTEGRATIONS.add(this);
}
@Inject(method = { "onModelBake(Lnet/neoforged/neoforge/client/event/ModelEvent$BakingCompleted;)V", "onModelBake(Lnet/neoforged/neoforge/client/event/ModelEvent$ModifyBakingResult;)V" }, at = @At("HEAD"), cancellable = true, remap = false)
private void noIteration(CallbackInfo ci) {
ci.cancel();
}
@Override
public BakedModel onBakedModelLoad(ModelResourceLocation mrl, UnbakedModel rootModel, BakedModel baked, ModelState state, ModelBakery bakery, ModelBakery.TextureGetter getter) {
if (!(baked instanceof AbstractCTMBakedModel) && !baked.isCustomRenderer()) {
Deque<ResourceLocation> dependencies = new ArrayDeque<>();
Set<ResourceLocation> seenModels = new HashSet<>();
dependencies.push(mrl.id());
seenModels.add(mrl.id());
boolean shouldWrap = false;
Set<Pair<String, String>> errors = new HashSet<>();
// Breadth-first loop through dependencies, exiting as soon as a CTM texture is found, and skipping duplicates/cycles
while (!shouldWrap && !dependencies.isEmpty()) {
ResourceLocation dep = dependencies.pop();
UnbakedModel model;
try {
model = dep == mrl.id() ? rootModel : bakery.getModel(dep);
} catch (Exception e) {
continue;
}
Collection<Material> textures = Sets.newHashSet(scrapedTextures.get(dep));
Collection<ResourceLocation> newDependencies = model.getDependencies();
for (Material tex : textures) {
IMetadataSectionCTM meta = null;
// Cache all dependent texture metadata
try {
meta = ResourceUtil.getMetadata(ResourceUtil.spriteToAbsolute(tex.texture())).orElse(null); // TODO, lazy
} catch (IOException e) {} // Fallthrough
if (meta != null) {
// At least one texture has CTM metadata, so we should wrap this model
shouldWrap = true;
}
}
for (ResourceLocation newDep : newDependencies) {
if (seenModels.add(newDep)) {
dependencies.push(newDep);
}
}
}
if (shouldWrap) {
try {
baked = wrap(rootModel, baked);
handleInit(mrl, baked, bakery, getter);
dependencies.clear();
} catch (IOException e) {
CTM.logger.error("Could not wrap model " + mrl + ". Aborting...", e);
}
}
}
return baked;
}
private void handleInit(ModelResourceLocation key, BakedModel wrappedModel, ModelBakery bakery, ModelBakery.TextureGetter spriteGetter) {
if(wrappedModel instanceof AbstractCTMBakedModel baked) {
IModelCTM var10 = baked.getModel();
if (var10 instanceof ModelCTM ctmModel) {
if (!ctmModel.isInitialized()) {
// Clear the baked cache as upstream CTM does
((CTMModelBakeryAccessor)bakery).mfix$getBakedCache().clear();
ModelBakery.ModelBakerImpl baker = bakery.new ModelBakerImpl(spriteGetter, key);
ctmModel.bake(baker, m -> spriteGetter.get(key, m), BlockModelRotation.X0_Y0);
}
}
}
}
}

View File

@ -1,100 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources.ldlib;
import com.lowdragmc.lowdraglib.LDLib;
import com.lowdragmc.lowdraglib.client.ClientProxy;
import com.lowdragmc.lowdraglib.client.model.custommodel.CustomBakedModel;
import com.lowdragmc.lowdraglib.client.model.custommodel.LDLMetadataSection;
import com.lowdragmc.lowdraglib.client.model.forge.LDLRendererModel;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.Material;
import net.minecraft.client.resources.model.ModelBakery;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.client.resources.model.ModelState;
import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.resources.ResourceLocation;
import org.embeddedt.modernfix.ModernFixClient;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.annotation.RequiresMod;
import org.embeddedt.modernfix.api.entrypoint.ModernFixClientIntegration;
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.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@Mixin(ClientProxy.class)
@ClientOnlyMixin
@RequiresMod("ldlib")
public abstract class ClientProxyImplMixin implements ModernFixClientIntegration {
@Inject(method = "<init>", at = @At("RETURN"))
private void registerIntegration(CallbackInfo ci) {
ModernFixClient.CLIENT_INTEGRATIONS.add(this);
}
@Redirect(method = "modelBake", at = @At(value = "INVOKE", target = "Ljava/util/Map;entrySet()Ljava/util/Set;", ordinal = 0), remap = false)
private Set<?> disableLoop(Map<?, ?> map) {
return Set.of();
}
@Override
public BakedModel onBakedModelLoad(ModelResourceLocation mrl, UnbakedModel rootModel, BakedModel baked, ModelState state, ModelBakery bakery, ModelBakery.TextureGetter textureGetter) {
if (baked == null) {
return null;
}
if (rootModel != null) {
if (baked instanceof LDLRendererModel) {
return baked;
}
if (baked.isCustomRenderer()) { // Nothing we can add to builtin models
return baked;
}
Deque<ResourceLocation> dependencies = new ArrayDeque<>();
Set<ResourceLocation> seenModels = new HashSet<>();
ResourceLocation rl = mrl.id();
dependencies.push(rl);
seenModels.add(rl);
boolean shouldWrap = ClientProxy.WRAPPED_MODELS.getOrDefault(mrl, false);
// Breadth-first loop through dependencies, exiting as soon as a CTM texture is found, and skipping duplicates/cycles
while (!shouldWrap && !dependencies.isEmpty()) {
ResourceLocation dep = dependencies.pop();
UnbakedModel model;
try {
model = dep == rl ? rootModel : bakery.getModel(dep);
} catch (Exception e) {
continue;
}
try {
Set<Material> textures = new HashSet<>(ClientProxy.SCRAPED_TEXTURES.get(dep));
for (Material tex : textures) {
// Cache all dependent texture metadata
// At least one texture has CTM metadata, so we should wrap this baked
if (!LDLMetadataSection.getMetadata(LDLMetadataSection.spriteToAbsolute(tex.texture())).isMissing()) { // TODO lazy
shouldWrap = true;
break;
}
}
if (!shouldWrap) {
for (ResourceLocation newDep : model.getDependencies()) {
if (seenModels.add(newDep)) {
dependencies.push(newDep);
}
}
}
} catch (Exception e) {
LDLib.LOGGER.error("Error loading baked dependency {} for baked {}. Skipping...", dep, rl, e);
}
}
ClientProxy.WRAPPED_MODELS.put(mrl, shouldWrap);
if (shouldWrap) {
return new CustomBakedModel<>(baked);
}
}
return baked;
}
}

View File

@ -4,7 +4,7 @@ import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.mojang.datafixers.DataFixer;
import net.minecraft.core.HolderGetter;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.resources.Identifier;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
@ -24,12 +24,12 @@ import java.util.Optional;
@Mixin(StructureTemplateManager.class)
public class StructureManagerMixin {
@Shadow @Final @Mutable
private Map<ResourceLocation, Optional<StructureTemplate>> structureRepository;
private Map<Identifier, Optional<StructureTemplate>> structureRepository;
@Inject(method = "<init>", at = @At("RETURN"))
private void makeStructuresSafe(ResourceManager arg, LevelStorageSource.LevelStorageAccess arg2, DataFixer dataFixer, HolderGetter<Block> arg3, CallbackInfo ci) {
/* Structures needing to be reloaded is not a huge issue since we optimize loading them already */
Cache<ResourceLocation, Optional<StructureTemplate>> structureCache = CacheBuilder.newBuilder()
Cache<Identifier, Optional<StructureTemplate>> structureCache = CacheBuilder.newBuilder()
.softValues()
.build();
this.structureRepository = structureCache.asMap();

View File

@ -1,192 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.faster_ingredients;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntComparators;
import it.unimi.dsi.fastutil.ints.IntList;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.ItemStackLinkedSet;
import net.minecraft.world.item.crafting.Ingredient;
import net.neoforged.neoforge.common.crafting.ICustomIngredient;
import org.embeddedt.modernfix.neoforge.load.MinecraftServerReloadTracker;
import org.embeddedt.modernfix.neoforge.recipe.ExtendedIngredient;
import org.embeddedt.modernfix.neoforge.recipe.IngredientItemStacksSoftReference;
import org.jetbrains.annotations.Nullable;
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 org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.ArrayList;
import java.util.stream.Collectors;
@Mixin(value = Ingredient.class, priority = 700)
public abstract class IngredientMixin implements ExtendedIngredient {
@Shadow @Final
private Ingredient.Value[] values;
@Shadow private @Nullable IntList stackingIds;
@Shadow @Nullable private ItemStack[] itemStacks;
@Shadow public abstract boolean isCustom();
@Shadow private ICustomIngredient customIngredient;
@Unique
private boolean isVanilla() {
return !this.isCustom();
}
private volatile IngredientItemStacksSoftReference mfix$cachedItemStacks;
/**
* Minecraft's server resource loading process has a design flaw in that tags are loaded, recipes are loaded,
* then tags are bound to the server registries. This results in recipe modification mods like KubeJS/CraftTweaker
* not being able to use Ingredient.test reliably as the TagValue will not find any contents for the given tag.
* To work around this issue these mods track tag context themselves and then patch TagValue.getItems to use it
* during the resource reload process. We often bypass Value.getItems, so we must disable that bypass
* whenever a server reload is in progress.
* <p>
* An alternative fix would be to bind tags ourselves when the recipe manager reload begins, before control is
* handed to these mods. However, it's unclear if there would be any negative side effects from binding tags early
* like this. Moreover, this fix would only work if the mod-provided patches to getItems read exactly what the
* registry would normally contain, rather than a modified version.
* <p>
* Note: this is a separate problem from the issue where clients may receive recipes before tags in 1.21.
*/
private boolean mfix$areTagsAvailable() {
return !MinecraftServerReloadTracker.isReloadActive();
}
/**
* @author embeddedt
* @reason tag ingredients can be tested without iterating over all items
*/
@Inject(method = "test(Lnet/minecraft/world/item/ItemStack;)Z", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/crafting/Ingredient;getItems()[Lnet/minecraft/world/item/ItemStack;"), cancellable = true)
private void modernfix$fasterTagIngredientTest(ItemStack stack, CallbackInfoReturnable<Boolean> cir) {
if (this.isVanilla() && this.values.length == 1 && this.values[0] instanceof Ingredient.TagValue tagValue && mfix$areTagsAvailable()) {
cir.setReturnValue(stack.getItemHolder().is(tagValue.tag()));
}
}
/**
* @author embeddedt
* @reason exploding the stack list is unnecessary
*/
@Inject(method = "hasNoItems", at = @At("HEAD"), cancellable = true, remap = false)
public void hasNoItems(CallbackInfoReturnable<Boolean> cir) {
if (this.isVanilla()) {
cir.setReturnValue(!this.containsItems());
}
}
@Unique
private boolean isEmptyTagStack(ItemStack item) {
return item.getItem() == net.minecraft.world.item.Items.BARRIER && item.getHoverName() instanceof net.minecraft.network.chat.MutableComponent hoverName && hoverName.getString().startsWith("Empty Tag: ");
}
@Unique
private boolean containsItems() {
for (Ingredient.Value value : this.values) {
if (value instanceof Ingredient.ItemValue) {
return true;
} else if (value instanceof Ingredient.TagValue tagValue && mfix$areTagsAvailable()) {
var holderSetOpt = BuiltInRegistries.ITEM.getTag(tagValue.tag());
if (holderSetOpt.isPresent() && holderSetOpt.get().size() > 0) {
return true;
}
} else {
var items = value.getItems();
if (items.isEmpty() || isEmptyTagStack(items.iterator().next())) {
// Doesn't have items
continue;
}
return true;
}
}
return false;
}
/**
* @author embeddedt
* @reason tag ingredients can be converted to stacking IDs without expanding into stacks, since stacking only
* goes by item ID
*/
@Inject(method = "getStackingIds", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/crafting/Ingredient;getItems()[Lnet/minecraft/world/item/ItemStack;"), cancellable = true)
private void modernfix$fasterTagIngredientStacking(CallbackInfoReturnable<IntList> cir) {
if (this.isVanilla() && this.values.length == 1 && this.values[0] instanceof Ingredient.TagValue tagValue && mfix$areTagsAvailable()) {
var tag = BuiltInRegistries.ITEM.getTag(tagValue.tag());
if (!tag.isPresent() || tag.get().size() == 0) {
return;
}
var list = new IntArrayList(tag.get().stream().mapToInt(h -> BuiltInRegistries.ITEM.getId(h.value())).toArray());
list.sort(IntComparators.NATURAL_COMPARATOR);
this.stackingIds = list;
cir.setReturnValue(list);
}
}
/**
* @author embeddedt
* @reason Change caching of item stacks to use a soft reference, which allows the GC to evict the array under
* memory pressure/when it hasn't been used.
*/
@Overwrite
public ItemStack[] getItems() {
// For compatibility if mods explicitly force a set of item stacks to be used
if (this.itemStacks != null) {
return this.itemStacks;
}
if (this.customIngredient != null) {
// We probably have to cache this as mods won't make it fast if they expect Neo to cache it
this.itemStacks = this.customIngredient.getItems().collect(Collectors.toCollection(ItemStackLinkedSet::createTypeAndComponentsSet)).toArray(ItemStack[]::new);
return this.itemStacks;
}
var cache = this.mfix$cachedItemStacks;
if (cache != null) {
var stacks = cache.get();
if (stacks != null) {
return stacks;
}
}
ItemStack[] result = computeItemsArray();
this.mfix$cachedItemStacks = new IngredientItemStacksSoftReference((Ingredient)(Object)this, result);
return result;
}
private ItemStack[] computeItemsArray() {
// Fast path for case with one item
if (this.values.length == 1) {
if (this.values[0] instanceof Ingredient.TagValue tagValue && mfix$areTagsAvailable()) {
var tag = BuiltInRegistries.ITEM.getTag(tagValue.tag());
if (tag.isPresent() && tag.get().size() > 0) {
var holderSet = tag.get();
ItemStack[] result = new ItemStack[holderSet.size()];
for (int i = 0; i < result.length; i++) {
result[i] = new ItemStack(holderSet.get(i));
}
return result;
}
}
}
ArrayList<ItemStack> itemList = new ArrayList<>(2);
for (var value : this.values) {
var collection = value.getItems();
itemList.ensureCapacity(collection.size() + itemList.size());
for (var item : collection) {
itemList.add(item);
}
}
return itemList.toArray(ItemStack[]::new);
}
@Override
public void mfix$clearReference() {
this.mfix$cachedItemStacks = null;
}
}

View File

@ -1,23 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.faster_item_rendering;
import net.minecraft.client.renderer.GameRenderer;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
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)
@ClientOnlyMixin
public class GameRendererMixin {
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GameRenderer;renderLevel(Lnet/minecraft/client/DeltaTracker;)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(Lnet/minecraft/client/DeltaTracker;)V", shift = At.Shift.AFTER))
private void markNotRenderingLevel(CallbackInfo ci) {
RenderState.IS_RENDERING_LEVEL = false;
}
}

View File

@ -1,72 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.faster_item_rendering;
import com.llamalad7.mixinextras.sugar.Local;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.block.model.ItemTransform;
import net.minecraft.client.renderer.entity.ItemRenderer;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.SimpleBakedModel;
import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.ItemStack;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.render.FastItemRenderType;
import org.embeddedt.modernfix.render.RenderState;
import org.embeddedt.modernfix.render.SimpleItemModelView;
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.ModifyArg;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(value = ItemRenderer.class, priority = 600)
@ClientOnlyMixin
public abstract class ItemRendererMixin {
private ItemDisplayContext transformType;
private final SimpleItemModelView modelView = new SimpleItemModelView();
@Inject(method = "render", at = @At("HEAD"))
private void markRenderingType(ItemStack itemStack, ItemDisplayContext transformType, boolean leftHand, PoseStack matrixStack, MultiBufferSource buffer, int combinedLight, int combinedOverlay, BakedModel model, CallbackInfo ci) {
this.transformType = transformType;
}
/**
* If a model
* - is a vanilla item model (SimpleBakedModel),
* - has no custom GUI transforms, and
* - is being rendered in 2D on a GUI
* we do not need to go through the process of rendering every quad. Just render the south ones (the ones facing the
* camera).
*/
@ModifyArg(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/ItemRenderer;renderModelLists(Lnet/minecraft/client/resources/model/BakedModel;Lnet/minecraft/world/item/ItemStack;IILcom/mojang/blaze3d/vertex/PoseStack;Lcom/mojang/blaze3d/vertex/VertexConsumer;)V"), index = 0)
private BakedModel useSimpleWrappedItemModel(BakedModel model, ItemStack stack, int combinedLight, int combinedOverlay, PoseStack matrixStack, VertexConsumer buffer, @Local(ordinal = 0) BakedModel originalModel) {
// Forge composite models split themselves into a smaller simple model, we need to detect that the parent
// was not simple
if(originalModel != null && originalModel.getClass() != SimpleBakedModel.class) {
return model;
}
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 model;
modelView.setItem(model);
modelView.setType(type);
return modelView;
} else
return model;
}
private boolean isBlockTransforms(ItemTransform transform) {
return transform.rotation.x() == 30f
&& transform.rotation.y() == 225f
&& transform.rotation.z() == 0f;
}
}

View File

@ -1,85 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.faster_texture_stitching;
import com.google.common.collect.ImmutableList;
import com.mojang.datafixers.util.Pair;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.minecraft.client.renderer.texture.Stitcher;
import net.minecraft.client.renderer.texture.StitcherException;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.platform.ModernFixPlatformHooks;
import org.embeddedt.modernfix.textures.StbStitcher;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Stream;
@Mixin(Stitcher.class)
@ClientOnlyMixin
public class StitcherMixin<T extends Stitcher.Entry> {
@Shadow @Final private List<Stitcher.Holder<T>> texturesToBeStitched;
@Shadow private int storageX;
@Shadow private int storageY;
@Shadow @Final private int maxWidth;
@Shadow @Final private int maxHeight;
@Shadow @Final private static Comparator<Stitcher.Holder<?>> HOLDER_COMPARATOR;
private List<StbStitcher.LoadableSpriteInfo<T>> loadableSpriteInfos;
/**
* @author embeddedt, SuperCoder79
* @reason Use improved STB stitcher instead of the vanilla implementation, for performance
*/
@Inject(method = "stitch", at = @At("HEAD"), cancellable = true)
private void stitchFast(CallbackInfo ci) {
this.loadableSpriteInfos = null;
if(!ModernFixPlatformHooks.INSTANCE.isLoadingNormally()) {
ModernFix.LOGGER.error("Using vanilla stitcher implementation due to invalid loading state");
return;
}
if(this.texturesToBeStitched.size() < 100) {
// The vanilla implementation is fine for small atlases, and using it allows mods like JEI that depend on
// precise texture alignments to avoid bugs.
return;
}
ci.cancel();
ObjectArrayList<Stitcher.Holder<T>> holderList = new ObjectArrayList<>(this.texturesToBeStitched);
holderList.sort(HOLDER_COMPARATOR);
Stitcher.Holder<T>[] aholder = holderList.toArray(new Stitcher.Holder[0]);
Pair<Pair<Integer, Integer>, List<StbStitcher.LoadableSpriteInfo<T>>> packingInfo = StbStitcher.packRects(aholder);
this.storageX = packingInfo.getFirst().getFirst();
this.storageY = packingInfo.getFirst().getSecond();
// Detect an oversized atlas generated in the previous step.
if(this.storageX > this.maxWidth || this.storageY > this.maxHeight) {
ModernFix.LOGGER.error("Requested atlas size {}x{} exceeds maximum of {}x{}", this.storageX, this.storageY, this.maxWidth, this.maxHeight);
throw new StitcherException(aholder[0].entry(), Stream.of(aholder).map(arg -> arg.entry()).collect(ImmutableList.toImmutableList()));
}
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
*/
@Inject(method = "gatherSprites", at = @At("HEAD"), cancellable = true)
private void gatherSpritesFast(Stitcher.SpriteLoader<T> spriteLoader, CallbackInfo ci) {
if(this.loadableSpriteInfos == null)
return;
ci.cancel();
for(StbStitcher.LoadableSpriteInfo<T> info : loadableSpriteInfos) {
spriteLoader.load(info.info, info.x, info.y);
}
}
}

View File

@ -1,48 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.fix_loop_spin_waiting;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.minecraft.Util;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.thread.BlockableEventLoop;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import java.util.concurrent.locks.LockSupport;
import java.util.function.BooleanSupplier;
@Mixin(value = MinecraftServer.class, priority = 500)
public abstract class MinecraftServerMixin extends BlockableEventLoop<Runnable> {
@Shadow private long nextTickTimeNanos;
protected MinecraftServerMixin(String name) {
super(name);
}
@Unique
private boolean mfix$isWaitingForNextTick = false;
@WrapOperation(
method = "waitUntilNextTick",
at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;managedBlock(Ljava/util/function/BooleanSupplier;)V")
)
private void managedBlock(MinecraftServer instance, BooleanSupplier isDone, Operation<Void> original) {
try {
this.mfix$isWaitingForNextTick = true;
original.call(instance, isDone);
} finally {
this.mfix$isWaitingForNextTick = false;
}
}
@WrapOperation(method = "waitForTasks", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/thread/ReentrantBlockableEventLoop;waitForTasks()V"))
private void waitLongerForTasks(MinecraftServer instance, Operation<Void> original) {
if (this.mfix$isWaitingForNextTick) {
LockSupport.parkNanos("waiting for tasks", this.nextTickTimeNanos - Util.getNanos());
} else {
original.call(instance);
}
}
}

View File

@ -1,20 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.ingredient_item_deduplication;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@Mixin(Ingredient.ItemValue.class)
public class IngredientItemValueMixin {
/**
* @author embeddedt
* @reason Defensively copy the item so that the deduplication is not visible to most mods (unless they introspect
* the item held within this object directly). This is necessary since some mods edit the returned stack.
*/
@ModifyExpressionValue(method = "getItems", at = @At(value = "FIELD", target = "Lnet/minecraft/world/item/crafting/Ingredient$ItemValue;item:Lnet/minecraft/world/item/ItemStack;"))
private ItemStack mfix$defensiveCopy(ItemStack original) {
return original.copy();
}
}

View File

@ -1,29 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.ingredient_item_deduplication;
import net.minecraft.world.item.crafting.Ingredient;
import org.embeddedt.modernfix.neoforge.recipe.IngredientValueDeduplicator;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import java.util.stream.Stream;
@Mixin(Ingredient.class)
public class IngredientMixin {
@ModifyVariable(method = "<init>(Ljava/util/stream/Stream;)V", at = @At("HEAD"), argsOnly = true, ordinal = 0)
private static Stream<? extends Ingredient.Value> injectDeduplicationPass(Stream<? extends Ingredient.Value> stream) {
return stream.map(IngredientValueDeduplicator::deduplicate);
}
@ModifyVariable(method = "<init>([Lnet/minecraft/world/item/crafting/Ingredient$Value;)V", at = @At("HEAD"), argsOnly = true, ordinal = 0)
private static Ingredient.Value[] injectDeduplicationPassArray(Ingredient.Value[] values) {
if (values.length == 0) {
return values;
}
Ingredient.Value[] newValues = new Ingredient.Value[values.length];
for (int i = 0; i < values.length; i++) {
newValues[i] = IngredientValueDeduplicator.deduplicate(values[i]);
}
return newValues;
}
}

View File

@ -1,18 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.ingredient_item_deduplication;
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
import net.minecraft.core.component.DataComponentMap;
import net.minecraft.core.component.DataComponentType;
import net.minecraft.core.component.PatchedDataComponentMap;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import java.util.Optional;
@Mixin(PatchedDataComponentMap.class)
public interface PatchedDataComponentMapAccessor {
@Accessor("prototype")
DataComponentMap mfix$getPrototype();
@Accessor("patch")
Reference2ObjectMap<DataComponentType<?>, Optional<?>> mfix$getPatch();
}

View File

@ -22,7 +22,7 @@ public class SessionSearchTreesMixin {
@Shadow private CompletableFuture<SearchTree<RecipeCollection>> recipeSearch;
private Supplier<SearchTree<RecipeCollection>> mfix$deferredSearchTreeSupplier;
@ModifyArg(method = { "method_60367", "lambda$updateRecipes$8" }, at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;supplyAsync(Ljava/util/function/Supplier;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"))
@ModifyArg(method = { "lambda$updateRecipes$9" }, at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;supplyAsync(Ljava/util/function/Supplier;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"))
private Supplier<SearchTree<RecipeCollection>> mfix$deferProcessing(Supplier<SearchTree<RecipeCollection>> supplier) {
this.mfix$deferredSearchTreeSupplier = supplier;
return SearchTree::empty;

View File

@ -1,19 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.model_optimizations;
import com.google.common.collect.ImmutableSet;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(BooleanProperty.class)
public class BooleanPropertyMixin {
/**
* There is no point comparing the immutable sets in any two instances of this class, as they will always be
* the same.
*/
@Redirect(method = "equals", at = @At(value = "INVOKE", target = "Lcom/google/common/collect/ImmutableSet;equals(Ljava/lang/Object;)Z", remap = false), remap = false)
private boolean skipEqualityCheck(ImmutableSet instance, Object object) {
return true;
}
}

View File

@ -1,41 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.model_optimizations;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import net.minecraft.client.renderer.block.model.MultiVariant;
import net.minecraft.client.renderer.block.model.Variant;
import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.resources.ResourceLocation;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import java.util.List;
import java.util.function.Function;
@Mixin(value = MultiVariant.class, priority = 700)
@ClientOnlyMixin
public abstract class MultiVariantMixin {
@Shadow public abstract List<Variant> getVariants();
/**
* @author embeddedt
* @reason avoid streams, try to optimize for common case
*/
@Overwrite
public void resolveParents(Function<ResourceLocation, UnbakedModel> modelGetter) {
var variants = this.getVariants();
// There is usually only a single variant
if (variants.size() == 1) {
modelGetter.apply(variants.get(0).getModelLocation()).resolveParents(modelGetter);
} else if(variants.size() > 1) {
ObjectOpenHashSet<ResourceLocation> seenLocations = new ObjectOpenHashSet<>(variants.size());
for (var variant : variants) {
var location = variant.getModelLocation();
if (seenLocations.add(location)) {
modelGetter.apply(location).resolveParents(modelGetter);
}
}
}
}
}

View File

@ -1,31 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.model_optimizations;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.client.renderer.block.model.multipart.Selector;
import net.minecraft.world.level.block.state.StateDefinition;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
@Mixin(Selector.class)
@ClientOnlyMixin
public class SelectorMixin {
private ConcurrentHashMap<StateDefinition<Block, BlockState>, Predicate<BlockState>> predicateCache = new ConcurrentHashMap<>();
@Inject(method = "getPredicate", at = @At("HEAD"), cancellable = true)
private void useCachedPredicate(StateDefinition<Block, BlockState> pState, CallbackInfoReturnable<Predicate<BlockState>> cir) {
Predicate<BlockState> cached = this.predicateCache.get(pState);
if(cached != null)
cir.setReturnValue(cached);
}
@Inject(method = "getPredicate", at = @At("RETURN"))
private void storeCachedPredicate(StateDefinition<Block, BlockState> pState, CallbackInfoReturnable<Predicate<BlockState>> cir) {
this.predicateCache.put(pState, cir.getReturnValue());
}
}

View File

@ -1,33 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.model_optimizations;
import com.mojang.math.Transformation;
import org.joml.Matrix4f;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
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.Objects;
@Mixin(Transformation.class)
@ClientOnlyMixin
public class TransformationMatrixMixin {
@Shadow @Final private Matrix4f matrix;
private Integer cachedHashCode = null;
/**
* @author embeddedt
* @reason use cached hashcode if exists
*/
@Overwrite(remap = false)
public int hashCode() {
int hash;
if(cachedHashCode != null) {
hash = cachedHashCode;
} else {
hash = Objects.hashCode(this.matrix);
cachedHashCode = hash;
}
return hash;
}
}

View File

@ -1,31 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.mojang_registry_size;
import com.google.common.collect.ArrayTable;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ImmutableTable;
import com.google.common.collect.Table;
import net.minecraft.world.level.block.state.StateHolder;
import net.minecraft.world.level.block.state.properties.Property;
import org.embeddedt.modernfix.annotation.RequiresMod;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/**
* Minor mixin to avoid duplicate empty neighbor tables, used when FerriteCore is not present. Won't be enabled in 99% of
* modded environments but is useful for testing in dev without dragging in Fabric API.
*/
@Mixin(StateHolder.class)
@RequiresMod("!ferritecore")
public class StateHolderMixin {
@Shadow private Table<Property<?>, Comparable<?>, ?> neighbours;
/* optimize the case where block has no properties */
@Inject(method = "populateNeighbours", at = @At("RETURN"), require = 0)
private void replaceEmptyTable(CallbackInfo ci) {
if((this.neighbours instanceof ArrayTable || this.neighbours instanceof HashBasedTable) && this.neighbours.isEmpty())
this.neighbours = ImmutableTable.of();
}
}

View File

@ -1,74 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.patchouli_deduplicate_books;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.neoforged.fml.util.ObfuscationReflectionHelper;
import org.embeddedt.modernfix.ModernFix;
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.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import vazkii.patchouli.client.book.BookContents;
import vazkii.patchouli.client.book.BookEntry;
import vazkii.patchouli.client.book.BookPage;
import vazkii.patchouli.client.book.ClientBookRegistry;
import vazkii.patchouli.client.book.page.PageTemplate;
import vazkii.patchouli.client.book.template.BookTemplate;
import vazkii.patchouli.client.book.template.TemplateComponent;
import vazkii.patchouli.client.book.template.component.ComponentItemStack;
import vazkii.patchouli.common.book.Book;
import vazkii.patchouli.common.book.BookRegistry;
import java.lang.reflect.Field;
import java.util.List;
@Mixin(ClientBookRegistry.class)
@RequiresMod("patchouli")
@ClientOnlyMixin
public class ClientBookRegistryMixin {
@Inject(method = "reload", at = @At("RETURN"), remap = false)
private void performDeduplication(CallbackInfo ci) {
Field templateField = ObfuscationReflectionHelper.findField(PageTemplate.class, "template");
Field contentsField = ObfuscationReflectionHelper.findField(Book.class, "contents");
Field componentsField = ObfuscationReflectionHelper.findField(BookTemplate.class, "components");
Field itemsField = ObfuscationReflectionHelper.findField(ComponentItemStack.class, "items");
int numItemsCleared = 0;
for(Book book : BookRegistry.INSTANCE.books.values()) {
try {
BookContents contents = (BookContents)contentsField.get(book);
if(contents == null || contents.entries == null)
continue;
for(BookEntry entry : contents.entries.values()) {
for(BookPage page : entry.getPages()) {
if(page instanceof PageTemplate) {
List<TemplateComponent> components;
BookTemplate template = (BookTemplate) templateField.get(page);
if(template == null)
continue;
components = (List<TemplateComponent>) componentsField.get(template);
if(components == null)
continue;
for (TemplateComponent component : components) {
if (component instanceof ComponentItemStack) {
ItemStack[] items = (ItemStack[]) itemsField.get(component);
if(items == null)
continue;
for (int i = 0; i < items.length; i++) {
if (items[i] != null && items[i].getItem() == Items.AIR) {
numItemsCleared++;
items[i] = ItemStack.EMPTY;
}
}
}
}
}
}
}
} catch(ReflectiveOperationException ignored) {
}
}
ModernFix.LOGGER.info("Cleared {} unneeded book NBT tags", numItemsCleared);
}
}

View File

@ -1,15 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.reduce_blockstate_cache_rebuilds;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.FluidState;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
@Mixin(BlockBehaviour.class)
public interface BlockBehaviourInvoker {
@Invoker
FluidState invokeGetFluidState(BlockState blockState);
@Invoker
boolean invokeIsRandomlyTicking(BlockState blockState);
}

View File

@ -1,27 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.reduce_blockstate_cache_rebuilds;
import com.google.common.collect.ImmutableList;
import net.minecraft.core.Registry;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import org.embeddedt.modernfix.blockstate.BlockStateCacheHandler;
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.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(targets = { "net/neoforged/neoforge/registries/NeoForgeRegistryCallbacks$BlockCallbacks" })
public class BlockCallbacksMixin {
@Redirect(method = "onBake", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/state/StateDefinition;getPossibleStates()Lcom/google/common/collect/ImmutableList;", ordinal = 0))
private ImmutableList<BlockState> skipCache(StateDefinition<Block, BlockState> definition) {
// prevent initCache from being called on these blockstates
return ImmutableList.of();
}
@Inject(method = "onBake", at = @At(value = "TAIL"), remap = false)
private void computeCaches(Registry<Block> registry, CallbackInfo ci) {
BlockStateCacheHandler.invalidateCache();
}
}

View File

@ -1,130 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.reduce_blockstate_cache_rebuilds;
import com.mojang.serialization.MapCodec;
import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateHolder;
import net.minecraft.world.level.block.state.properties.Property;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.Fluids;
import org.embeddedt.modernfix.duck.IBlockState;
import org.objectweb.asm.Opcodes;
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.Redirect;
@Mixin(BlockBehaviour.BlockStateBase.class)
public abstract class BlockStateBaseMixin extends StateHolder<Block, BlockState> implements IBlockState {
protected BlockStateBaseMixin(Block object, Reference2ObjectArrayMap<Property<?>, Comparable<?>> immutableMap, MapCodec<BlockState> mapCodec) {
super(object, immutableMap, mapCodec);
}
private static final FluidState MFIX$VANILLA_DEFAULT_FLUID = Fluids.EMPTY.defaultFluidState();
@Shadow public abstract void initCache();
@Shadow private BlockBehaviour.BlockStateBase.Cache cache;
@Shadow private FluidState fluidState;
@Shadow private boolean isRandomlyTicking;
@Shadow @Deprecated private boolean legacySolid;
@Shadow protected abstract BlockState asState();
private volatile boolean cacheInvalid = false;
private static boolean buildingCache = false;
@Override
public void clearCache() {
cacheInvalid = true;
}
@Override
public boolean isCacheInvalid() {
return cacheInvalid;
}
private void mfix$generateCache() {
if(cacheInvalid) {
// Ensure that only one block's cache is built at a time
synchronized (BlockBehaviour.BlockStateBase.class) {
if(cacheInvalid) {
// Ensure that if we end up in here recursively, we just use the original cache
if(!buildingCache) {
buildingCache = true;
try {
this.initCache();
cacheInvalid = false;
} finally {
buildingCache = false;
}
}
}
}
}
}
@Redirect(method = "*", at = @At(
value = "FIELD",
opcode = Opcodes.GETFIELD,
target = "Lnet/minecraft/world/level/block/state/BlockBehaviour$BlockStateBase;cache:Lnet/minecraft/world/level/block/state/BlockBehaviour$BlockStateBase$Cache;",
ordinal = 0
))
private BlockBehaviour.BlockStateBase.Cache dynamicCacheGen(BlockBehaviour.BlockStateBase base) {
mfix$generateCache();
return this.cache;
}
@Redirect(method = "*", at = @At(
value = "FIELD",
opcode = Opcodes.GETFIELD,
target = "Lnet/minecraft/world/level/block/state/BlockBehaviour$BlockStateBase;fluidState:Lnet/minecraft/world/level/material/FluidState;",
ordinal = 0
), require = 0)
private FluidState genCacheBeforeGettingFluid(BlockBehaviour.BlockStateBase base) {
// don't generate the full cache here as mods will iterate for the fluid state a lot
// assume blockstates will not change their contained fluidstate at runtime more than once
// this is how Lithium's implementation used to work, so it should be fine
if(this.cacheInvalid && this.fluidState == MFIX$VANILLA_DEFAULT_FLUID) {
synchronized (BlockBehaviour.BlockStateBase.class) {
if(!buildingCache) {
buildingCache = true;
try {
this.fluidState = ((BlockBehaviourInvoker)this.owner).invokeGetFluidState(this.asState());
} finally {
buildingCache = false;
}
}
}
}
return this.fluidState;
}
@Redirect(method = "*", at = @At(
value = "FIELD",
opcode = Opcodes.GETFIELD,
target = "Lnet/minecraft/world/level/block/state/BlockBehaviour$BlockStateBase;isRandomlyTicking:Z",
ordinal = 0
))
private boolean genCacheBeforeGettingTicking(BlockBehaviour.BlockStateBase base) {
if(this.cacheInvalid)
return ((BlockBehaviourInvoker)this.owner).invokeIsRandomlyTicking(this.asState());
return this.isRandomlyTicking;
}
@Redirect(method = "*", at = @At(
value = "FIELD",
opcode = Opcodes.GETFIELD,
target = "Lnet/minecraft/world/level/block/state/BlockBehaviour$BlockStateBase;legacySolid:Z",
ordinal = 0
))
private boolean genCacheBeforeCheckingSolid(BlockBehaviour.BlockStateBase base) {
mfix$generateCache();
return this.legacySolid;
}
}

View File

@ -1,31 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.reduce_blockstate_cache_rebuilds;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import org.embeddedt.modernfix.blockstate.BlockStateCacheHandler;
import org.embeddedt.modernfix.duck.IBlockState;
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.Redirect;
import java.util.function.Consumer;
@Mixin(value = Blocks.class, priority = 1100)
public class BlocksMixin {
@ModifyArg(method = "rebuildCache", at = @At(value = "INVOKE", target = "Lnet/minecraft/core/IdMapper;forEach(Ljava/util/function/Consumer;)V"), index = 0)
private static Consumer getEmptyConsumer(Consumer original) {
BlockStateCacheHandler.invalidateCache();
return o -> {};
}
// require = 0 due to Forge removing the BLOCK_STATE_REGISTRY init here
@Redirect(method = "<clinit>", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/state/BlockState;initCache()V"), require = 0)
private static void skipCacheInit(BlockState state) {
// Trigger classloading of Items in case a mod expects it.
Items.AIR.asItem();
// Mark the cache as invalid
((IBlockState)state).clearCache();
}
}

View File

@ -1,24 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.remove_biome_temperature_cache;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.biome.Biome;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
/* Idea from Lithium for 1.19.3 */
@Mixin(Biome.class)
public abstract class BiomeMixin {
@Shadow protected abstract float getHeightAdjustedTemperature(BlockPos pos);
/**
* @author 2No2Name
* @reason Remove caching, it's not effective
* @param pos
* @return
*/
@Overwrite
private float getTemperature(BlockPos pos) {
return this.getHeightAdjustedTemperature(pos);
}
}

View File

@ -1,33 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.smart_ingredient_sync;
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import net.minecraft.network.Connection;
import net.minecraft.network.ConnectionProtocol;
import net.minecraft.network.PacketSendListener;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientboundUpdateRecipesPacket;
import net.neoforged.neoforge.network.registration.NetworkRegistry;
import org.embeddedt.modernfix.neoforge.packet.SmartIngredientSyncPayload;
import org.spongepowered.asm.mixin.Mixin;
@Mixin(Connection.class)
public class ConnectionMixin {
/**
* @author embeddedt
* @reason Provide context to the ingredient serializer about whether the enhanced sync protocol is supported.
*/
@WrapMethod(method = "doSendPacket")
private void modernfix$checkClientPresence(Packet<?> packet, PacketSendListener sendListener, boolean flush, Operation<Void> original) {
if (packet instanceof ClientboundUpdateRecipesPacket && NetworkRegistry.hasChannel((Connection)(Object)this, ConnectionProtocol.PLAY, SmartIngredientSyncPayload.TYPE.id())) {
SmartIngredientSyncPayload.CLIENT_HAS_SMART_INGREDIENT_SYNC.set(true);
try {
original.call(packet, sendListener, flush);
} finally {
SmartIngredientSyncPayload.CLIENT_HAS_SMART_INGREDIENT_SYNC.set(false);
}
} else {
original.call(packet, sendListener, flush);
}
}
}

View File

@ -1,54 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.smart_ingredient_sync;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.crafting.Ingredient;
import org.embeddedt.modernfix.neoforge.packet.SmartIngredientSyncPayload;
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 org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(targets = {"net/minecraft/world/item/crafting/Ingredient$1"})
public abstract class IngredientMixin {
@Inject(method = "encode(Lnet/minecraft/network/RegistryFriendlyByteBuf;Lnet/minecraft/world/item/crafting/Ingredient;)V",
at = @At(value = "FIELD", target = "Lnet/minecraft/world/item/ItemStack;LIST_STREAM_CODEC:Lnet/minecraft/network/codec/StreamCodec;"),
cancellable = true)
private void checkForVanillaTagIngredient(RegistryFriendlyByteBuf buf, Ingredient ingredient, CallbackInfo ci) {
if (!SmartIngredientSyncPayload.CLIENT_HAS_SMART_INGREDIENT_SYNC.get() || ingredient.isCustom()) {
return;
}
Ingredient.Value[] values = ingredient.getValues();
if (values.length == 1 && values[0] instanceof Ingredient.TagValue tagValue) {
var optionalHolderSet = BuiltInRegistries.ITEM.getTag(tagValue.tag());
if (optionalHolderSet.isEmpty()) {
// Use default serialization logic for tags that do not exist
return;
}
// Encode this as our tag ingredient type instead of using vanilla's flattening logic.
ci.cancel();
buf.writeVarInt(-2);
buf.writeResourceLocation(tagValue.tag().location());
}
}
@Inject(method = "decode(Lnet/minecraft/network/RegistryFriendlyByteBuf;)Lnet/minecraft/world/item/crafting/Ingredient;",
at = @At(value = "HEAD"),
cancellable = true, remap = false)
private void decodeSmartIngredient(RegistryFriendlyByteBuf buf, CallbackInfoReturnable<Ingredient> cir) {
int readerIndex = buf.readerIndex();
var sizeId = buf.readVarInt();
if (sizeId == -2) {
// Probably our ingredient
var tagKey = TagKey.create(Registries.ITEM, buf.readResourceLocation());
cir.setReturnValue(Ingredient.of(tagKey));
} else {
buf.readerIndex(readerIndex);
}
}
}

View File

@ -1,6 +1,6 @@
package org.embeddedt.modernfix.common.mixin.perf.tag_id_caching;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.resources.Identifier;
import net.minecraft.tags.TagEntry;
import net.minecraft.util.ExtraCodecs;
import org.spongepowered.asm.mixin.Final;
@ -11,7 +11,7 @@ import org.spongepowered.asm.mixin.Shadow;
@Mixin(TagEntry.class)
public class TagEntryMixin {
@Shadow @Final private boolean tag;
@Shadow @Final private ResourceLocation id;
@Shadow @Final private Identifier id;
private ExtraCodecs.TagOrElementLocation cachedLoc;
/**

View File

@ -1,6 +1,6 @@
package org.embeddedt.modernfix.common.mixin.perf.tag_id_caching;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.resources.Identifier;
import net.minecraft.util.ExtraCodecs;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
@ -10,7 +10,7 @@ import org.spongepowered.asm.mixin.Shadow;
@Mixin(ExtraCodecs.TagOrElementLocation.class)
public class TagOrElementLocationMixin {
@Shadow @Final private boolean tag;
@Shadow @Final private ResourceLocation id;
@Shadow @Final private Identifier id;
private String cachedDecoratedId;
/**

View File

@ -1,12 +1,7 @@
package org.embeddedt.modernfix.common.mixin.perf.thread_priorities;
import net.minecraft.client.Minecraft;
import net.minecraft.server.Services;
import net.minecraft.server.WorldStem;
import net.minecraft.server.packs.repository.PackRepository;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.client.server.IntegratedServer;
import net.minecraft.server.level.progress.ChunkProgressListenerFactory;
import net.minecraft.world.level.storage.LevelStorageSource;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@ -17,7 +12,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@ClientOnlyMixin
public class IntegratedServerMixin {
@Inject(method = "<init>", at = @At("RETURN"))
private void adjustServerPriority(Thread thread, Minecraft arg, LevelStorageSource.LevelStorageAccess arg2, PackRepository arg3, WorldStem arg4, Services arg5, ChunkProgressListenerFactory arg6, CallbackInfo ci) {
private void adjustServerPriority(CallbackInfo ci, @Local(ordinal = 0, argsOnly = true) Thread thread) {
int pri = 4;
thread.setPriority(pri);
}

View File

@ -1,6 +1,6 @@
package org.embeddedt.modernfix.common.mixin.perf.thread_priorities;
import net.minecraft.Util;
import net.minecraft.util.Util;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;

View File

@ -1,34 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.worldgen_allocation;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.DensityFunction;
import net.minecraft.world.level.levelgen.NoiseChunk;
import net.minecraft.world.level.levelgen.material.MaterialRuleList;
import org.jetbrains.annotations.Nullable;
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.List;
@Mixin(value = MaterialRuleList.class, priority = 100)
public class MaterialRuleListMixin {
@Shadow @Final private List<NoiseChunk.BlockStateFiller> materialRuleList;
/**
* @author embeddedt
* @reason Avoid iterator allocation
*/
@Overwrite
@Nullable
public BlockState calculate(DensityFunction.FunctionContext arg) {
BlockState state = null;
int s = this.materialRuleList.size();
for(int i = 0; state == null && i < s; i++) {
NoiseChunk.BlockStateFiller blockStateFiller = this.materialRuleList.get(i);
state = blockStateFiller.calculate(arg);
}
return state;
}
}

View File

@ -1,25 +0,0 @@
package org.embeddedt.modernfix.common.mixin.safety;
import net.minecraft.client.color.item.ItemColors;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
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.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Mixin(value = ItemColors.class, priority = 700)
@ClientOnlyMixin
public class ItemColorsMixin {
private Lock mapLock = new ReentrantLock();
@Inject(method = "register", at = @At("HEAD"))
private void lockMapBeforeAccess(CallbackInfo ci) {
mapLock.lock();
}
@Inject(method = "register", at = @At("TAIL"))
private void unlockMap(CallbackInfo ci) {
mapLock.unlock();
}
}

View File

@ -1,30 +0,0 @@
package org.embeddedt.modernfix.common.mixin.safety;
import net.minecraft.client.renderer.item.ItemProperties;
import net.minecraft.client.renderer.item.ItemPropertyFunction;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Collections;
import java.util.Map;
@Mixin(value = ItemProperties.class, priority = 700)
@ClientOnlyMixin
public class ItemPropertiesMixin {
@Shadow @Final @Mutable private static Map<ResourceLocation, ItemPropertyFunction> GENERIC_PROPERTIES;
@Shadow @Final @Mutable private static Map<Item, Map<ResourceLocation, ItemPropertyFunction>> PROPERTIES;
@Inject(method = "<clinit>", at = @At("RETURN"))
private static void useConcurrentMaps(CallbackInfo ci) {
GENERIC_PROPERTIES = Collections.synchronizedMap(GENERIC_PROPERTIES);
PROPERTIES = Collections.synchronizedMap(PROPERTIES);
}
}

View File

@ -1,7 +0,0 @@
package org.embeddedt.modernfix.duck;
import net.minecraft.client.resources.model.ModelResourceLocation;
public interface IBlockStateModelLoader {
void loadSpecificBlock(ModelResourceLocation location);
}

View File

@ -1,14 +0,0 @@
package org.embeddedt.modernfix.duck;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.client.resources.model.UnbakedModel;
import java.util.concurrent.locks.ReentrantLock;
public interface IExtendedModelBakery {
void mfix$tick();
void mfix$finishLoading();
UnbakedModel mfix$loadUnbakedModelDynamic(ModelResourceLocation location);
UnbakedModel mfix$getMissingModel();
ReentrantLock mfix$getLock();
}

View File

@ -1,8 +1,8 @@
package org.embeddedt.modernfix.duck;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.renderer.block.model.BlockStateModel;
public interface IModelHoldingBlockState {
BakedModel mfix$getModel();
void mfix$setModel(BakedModel model);
BlockStateModel mfix$getModel();
void mfix$setModel(BlockStateModel model);
}

View File

@ -1,7 +0,0 @@
package org.embeddedt.modernfix.duck;
import org.embeddedt.modernfix.world.StrongholdLocationCache;
public interface IServerLevel {
StrongholdLocationCache mfix$getStrongholdCache();
}

View File

@ -1,79 +0,0 @@
package org.embeddedt.modernfix.dynamicresources;
import it.unimi.dsi.fastutil.Function;
import it.unimi.dsi.fastutil.objects.Reference2ReferenceLinkedOpenHashMap;
import net.minecraft.client.resources.model.BakedModel;
import java.util.concurrent.locks.StampedLock;
/**
* The Mojang Triple-based baked cache system is too slow to be hitting on every model retrieval, so
* we need a fast, concurrency-safe wrapper on top.
*/
public class DynamicModelCache<K> {
private final Reference2ReferenceLinkedOpenHashMap<K, BakedModel> cache = new Reference2ReferenceLinkedOpenHashMap<>();
private final StampedLock lock = new StampedLock();
private final Function<K, BakedModel> modelRetriever;
private final boolean allowNulls;
public DynamicModelCache(Function<K, BakedModel> modelRetriever, boolean allowNulls) {
this.modelRetriever = modelRetriever;
this.allowNulls = allowNulls;
}
public void clear() {
long stamp = lock.writeLock();
try {
cache.clear();
} finally {
lock.unlock(stamp);
}
}
private boolean needToPopulate(K state) {
long stamp = lock.readLock();
try {
return !cache.containsKey(state);
} finally {
lock.unlock(stamp);
}
}
private BakedModel getModelFromCache(K state) {
long stamp = lock.readLock();
try {
return cache.get(state);
} finally {
lock.unlock(stamp);
}
}
private BakedModel cacheModel(K state) {
BakedModel model = modelRetriever.apply(state);
// Lock and modify our local, faster cache
long stamp = lock.writeLock();
try {
cache.putAndMoveToFirst(state, model);
// TODO: choose less arbitrary number
if(cache.size() >= 1000) {
cache.removeLast();
}
} finally {
lock.unlock(stamp);
}
return model;
}
public BakedModel get(K key) {
BakedModel model = getModelFromCache(key);
if(model == null && (!allowNulls || needToPopulate(key))) {
model = cacheModel(key);
}
return model;
}
}

View File

@ -1,41 +0,0 @@
package org.embeddedt.modernfix.dynamicresources;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.resources.ResourceLocation;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
* Handles loading models dynamically, rather than at startup time.
*/
public class DynamicModelProvider {
private final Map<ResourceLocation, UnbakedModel> internalModels;
private final Cache<ResourceLocation, Optional<UnbakedModel>> loadedModels =
CacheBuilder.newBuilder()
.expireAfterAccess(3, TimeUnit.MINUTES)
.maximumSize(1000)
.concurrencyLevel(8)
.softValues()
.build();
public DynamicModelProvider(Map<ResourceLocation, UnbakedModel> initialModels) {
this.internalModels = initialModels;
}
public UnbakedModel getModel(ResourceLocation location) {
try {
return loadedModels.get(location, () -> Optional.ofNullable(loadModel(location))).orElse(null);
} catch(ExecutionException e) {
throw new RuntimeException(e.getCause());
}
}
private UnbakedModel loadModel(ResourceLocation location) {
return null; /* TODO :) */
}
}

View File

@ -1,142 +0,0 @@
package org.embeddedt.modernfix.dynamicresources;
import com.mojang.blaze3d.audio.SoundBuffer;
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
import net.minecraft.resources.ResourceLocation;
import org.embeddedt.modernfix.ModernFix;
import org.jetbrains.annotations.NotNull;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class DynamicSoundHelpers {
private static final long SOUND_EVICTION_DELAY = TimeUnit.SECONDS.toNanos(30);
private static final boolean debugDynamicSoundLoading = Boolean.getBoolean("modernfix.debugDynamicSoundLoading");
public interface SoundBufAccess {
long mfix$getDurationNanos();
}
public static final class Cache extends AbstractMap<ResourceLocation, CompletableFuture<SoundBuffer>> {
private static class Entry {
private final CompletableFuture<SoundBuffer> buffer;
private long lastAccessTime;
private Entry(CompletableFuture<SoundBuffer> buffer) {
this.buffer = buffer;
this.lastAccessTime = System.nanoTime();
}
public CompletableFuture<SoundBuffer> getBuffer() {
this.lastAccessTime = System.nanoTime();
return this.buffer;
}
public long getDuration() {
var buf = this.buffer.getNow(null);
if (buf == null) {
return 0;
}
return ((SoundBufAccess)buf).mfix$getDurationNanos();
}
public boolean isExpired(long currentTs) {
long duration = getDuration();
return duration > 0 && (currentTs - this.lastAccessTime) >= (duration + SOUND_EVICTION_DELAY);
}
public void discard() {
this.buffer.thenAccept(SoundBuffer::discardAlBuffer);
}
@Override
public String toString() {
return super.toString();
}
}
private final Object2ObjectLinkedOpenHashMap<ResourceLocation, Entry> store = new Object2ObjectLinkedOpenHashMap<>();
public Cache(Map<ResourceLocation, CompletableFuture<SoundBuffer>> otherMap) {
this.putAll(otherMap);
}
private void checkExpired() {
long ts = System.nanoTime();
var iter = this.store.object2ObjectEntrySet().fastIterator();
while (iter.hasNext()) {
var entry = iter.next();
if (entry.getValue().isExpired(ts)) {
if (debugDynamicSoundLoading) {
ModernFix.LOGGER.warn("Evicted sound {} with duration {} ms", entry.getKey(), entry.getValue().getDuration() / 1000000);
}
entry.getValue().discard();
iter.remove();
} else {
break;
}
}
}
@Override
public CompletableFuture<SoundBuffer> get(Object key) {
if (key instanceof ResourceLocation rl) {
var entry = this.store.getAndMoveToLast(rl);
CompletableFuture<SoundBuffer> result = entry != null ? entry.getBuffer() : null;
this.checkExpired();
return result;
} else {
return null;
}
}
@Override
public CompletableFuture<SoundBuffer> put(ResourceLocation key, CompletableFuture<SoundBuffer> value) {
var entry = new Entry(value);
if (debugDynamicSoundLoading) {
ModernFix.LOGGER.info("Loaded sound {}", key);
}
var previousEntry = this.store.putAndMoveToLast(key, entry);
return previousEntry != null ? previousEntry.getBuffer() : null;
}
@Override
public @NotNull Set<Map.Entry<ResourceLocation, CompletableFuture<SoundBuffer>>> entrySet() {
return new EntrySet();
}
private class EntrySet extends AbstractSet<Map.Entry<ResourceLocation, CompletableFuture<SoundBuffer>>> {
@Override
public Iterator<Map.Entry<ResourceLocation, CompletableFuture<SoundBuffer>>> iterator() {
var storeIter = store.entrySet().iterator();
return new Iterator<>() {
@Override
public boolean hasNext() {
return storeIter.hasNext();
}
@Override
public Map.Entry<ResourceLocation, CompletableFuture<SoundBuffer>> next() {
var entry = storeIter.next();
return new AbstractMap.SimpleImmutableEntry<>(entry.getKey(), entry.getValue().buffer);
}
};
}
@Override
public int size() {
return store.size();
}
@Override
public void clear() {
store.clear();
}
}
}
}

View File

@ -1,107 +0,0 @@
package org.embeddedt.modernfix.dynamicresources;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import net.minecraft.client.resources.model.*;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.Property;
import java.util.*;
public class ModelBakeryHelpers {
/**
* The maximum number of baked models kept in memory at once.
*/
public static final int MAX_BAKED_MODEL_COUNT = 10000;
/**
* The maximum number of unbaked models kept in memory at once.
*/
public static final int MAX_UNBAKED_MODEL_COUNT = 10000;
/**
* The time in seconds after which a model becomes eligible for eviction if not used.
*/
public static final int MAX_MODEL_LIFETIME_SECS = 300;
/**
* These folders will have all textures stitched onto the atlas when dynamic resources is enabled.
*/
public static String[] getExtraTextureFolders() {
return new String[] {
"attachment",
"bettergrass",
"block",
"blocks",
"cape",
"entity/bed",
"entity/chest",
"item",
"items",
"model",
"models",
"part",
"pipe",
"ropebridge",
"runes",
"solid_block",
"spell_effect",
"spell_projectile"
};
}
private static <T extends Comparable<T>, V extends T> BlockState setPropertyGeneric(BlockState state, Property<T> prop, Object o) {
return state.setValue(prop, (V)o);
}
private static <T extends Comparable<T>> T getValueHelper(Property<T> property, String value) {
return property.getValue(value).orElse(null);
}
private static final Splitter COMMA_SPLITTER = Splitter.on(',');
private static final Splitter EQUAL_SPLITTER = Splitter.on('=').limit(2);
public static ImmutableList<BlockState> getBlockStatesForMRL(StateDefinition<Block, BlockState> stateDefinition, ModelResourceLocation location) {
if(Objects.equals(location.getVariant(), "inventory"))
return ImmutableList.of();
Set<Property<?>> fixedProperties = new HashSet<>();
BlockState fixedState = stateDefinition.any();
for(String s : COMMA_SPLITTER.split(location.getVariant())) {
Iterator<String> iterator = EQUAL_SPLITTER.split(s).iterator();
if (iterator.hasNext()) {
String s1 = iterator.next();
Property<?> property = stateDefinition.getProperty(s1);
if (property != null && iterator.hasNext()) {
String s2 = iterator.next();
Object value = getValueHelper(property, s2);
if (value == null) {
throw new RuntimeException("Unknown value: '" + s2 + "' for blockstate property: '" + s1 + "' " + property.getPossibleValues());
}
fixedState = setPropertyGeneric(fixedState, property, value);
fixedProperties.add(property);
} else if (!s1.isEmpty()) {
throw new RuntimeException("Unknown blockstate property: '" + s1 + "'");
}
}
}
// 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<Property<?>> anyProperties = new ArrayList<>(stateDefinition.getProperties());
anyProperties.removeAll(fixedProperties);
ArrayList<BlockState> finalList = new ArrayList<>();
finalList.add(fixedState);
for(Property<?> property : anyProperties) {
ArrayList<BlockState> newPermutations = new ArrayList<>();
for(BlockState state : finalList) {
for(Comparable<?> value : property.getPossibleValues()) {
newPermutations.add(setPropertyGeneric(state, property, value));
}
}
finalList = newPermutations;
}
return ImmutableList.copyOf(finalList);
}
}

View File

@ -1,52 +0,0 @@
package org.embeddedt.modernfix.dynamicresources;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import net.minecraft.client.renderer.block.BlockModelShaper;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.state.BlockState;
import java.util.concurrent.ExecutionException;
public class ModelLocationCache {
private static final LoadingCache<BlockState, ModelResourceLocation> blockLocationCache = CacheBuilder.newBuilder()
.maximumSize(10000)
.build(new CacheLoader<BlockState, ModelResourceLocation>() {
@Override
public ModelResourceLocation load(BlockState key) throws Exception {
return BlockModelShaper.stateToModelLocation(key);
}
});
private static final LoadingCache<Item, ModelResourceLocation> itemLocationCache = CacheBuilder.newBuilder()
.maximumSize(10000)
.build(new CacheLoader<Item, ModelResourceLocation>() {
@Override
public ModelResourceLocation load(Item key) throws Exception {
return new ModelResourceLocation(BuiltInRegistries.ITEM.getKey(key), "inventory");
}
});
public static ModelResourceLocation get(BlockState state) {
if(state == null)
return null;
try {
return blockLocationCache.get(state);
} catch(ExecutionException e) {
throw new RuntimeException(e.getCause());
}
}
public static ModelResourceLocation get(Item item) {
if(item == null)
return null;
try {
return itemLocationCache.get(item);
} catch(ExecutionException e) {
throw new RuntimeException(e.getCause());
}
}
}

View File

@ -1,5 +0,0 @@
package org.embeddedt.modernfix.dynamicresources;
public class ModelMissingException extends RuntimeException {
}

View File

@ -1,39 +0,0 @@
package org.embeddedt.modernfix.neoforge;
import com.google.common.collect.ImmutableList;
import net.minecraft.resources.ResourceLocation;
import net.neoforged.neoforge.common.ModConfigSpec;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class ModernFixConfig {
private static final ModConfigSpec.Builder COMMON_BUILDER = new ModConfigSpec.Builder();
public static ModConfigSpec COMMON_CONFIG;
public static ModConfigSpec.ConfigValue<List<? extends String>> BLACKLIST_ASYNC_JEI_PLUGINS;
private static Set<ResourceLocation> jeiPluginBlacklist;
static {
Predicate<Object> locationValidator = o -> o instanceof String && ((String)o).contains(":");
BLACKLIST_ASYNC_JEI_PLUGINS = COMMON_BUILDER
.comment("These JEI plugins will be loaded on the main thread")
.defineList("blacklist_async_jei_plugins", ImmutableList.of(
"jepb:jei_plugin"
), locationValidator);
}
static {
COMMON_CONFIG = COMMON_BUILDER.build();
}
public static Set<ResourceLocation> getJeiPluginBlacklist() {
if(jeiPluginBlacklist == null) {
jeiPluginBlacklist = BLACKLIST_ASYNC_JEI_PLUGINS.get().stream().map(ResourceLocation::parse).collect(Collectors.toSet());
}
return jeiPluginBlacklist;
}
}

View File

@ -1,5 +0,0 @@
package org.embeddedt.modernfix.neoforge.dynresources;
public interface IModelBakerImpl {
void mfix$ignoreCache();
}

View File

@ -1,337 +0,0 @@
package org.embeddedt.modernfix.neoforge.dynresources;
import com.google.common.collect.ForwardingMap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
import com.google.common.graph.GraphBuilder;
import com.google.common.graph.MutableGraph;
import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelBakery;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.FileToIdConverter;
import net.neoforged.fml.ModContainer;
import net.neoforged.fml.ModList;
import net.neoforged.neoforgespi.language.IModInfo;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.util.ForwardingInclDefaultsMap;
import org.jetbrains.annotations.Nullable;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiFunction;
/**
* Stores a list of all known default block/item models in the game, and provides a namespaced version
* of the model registry that emulates vanilla keySet behavior.
*/
public class ModelBakeEventHelper {
private enum UniverseVisibility {
/**
* Mod cannot see any view of the universe of model locations.
*/
NONE,
/**
* Mod can see its own model locations and those of dependencies/dependents.
*/
SELF_AND_DEPS,
/**
* Mod can see every model location.
*/
EVERYTHING
}
private static final Map<String, UniverseVisibility> MOD_VISIBILITY_CONFIGURATION = ImmutableMap.<String, UniverseVisibility>builder()
.put("eternal_starlight", UniverseVisibility.SELF_AND_DEPS) // needed as a mitigation until https://github.com/LeoMinecraftModding/eternal-starlight/pull/82 is merged
.put("alexscaves", UniverseVisibility.SELF_AND_DEPS)
.put("refinedstorage", UniverseVisibility.SELF_AND_DEPS)
.put("cabletiers", UniverseVisibility.SELF_AND_DEPS)
.build();
private final Map<ModelResourceLocation, BakedModel> modelRegistry;
private final Set<ModelResourceLocation> topLevelModelLocations;
private final Set<String> namespacesWithModels;
private final MutableGraph<String> dependencyGraph;
public ModelBakeEventHelper(Map<ModelResourceLocation, BakedModel> modelRegistry) {
this.modelRegistry = modelRegistry;
int blockStateCount = 0;
for (var b : BuiltInRegistries.BLOCK) {
blockStateCount += b.getStateDefinition().getPossibleStates().size();
}
this.topLevelModelLocations = new ObjectLinkedOpenHashSet<>(blockStateCount + BuiltInRegistries.ITEM.size());
this.namespacesWithModels = new ObjectOpenHashSet<>(ModList.get().size());
var modelLocationBuilder = new ModelLocationBuilder();
BuiltInRegistries.BLOCK.entrySet().forEach(entry -> {
var location = entry.getKey().location();
modelLocationBuilder.generateForBlock(topLevelModelLocations, entry.getValue(), location);
namespacesWithModels.add(location.getNamespace());
});
BuiltInRegistries.ITEM.keySet().forEach(key -> {
topLevelModelLocations.add(new ModelResourceLocation(key, "inventory"));
namespacesWithModels.add(key.getNamespace());
});
this.topLevelModelLocations.addAll(modelRegistry.keySet());
// We add all standard item model locations here so that mods like JAOPCA that assume their remapping logic
// triggers loading of them (which it doesn't with dynamic resources on), will still detect the presence
// of their extra models in the emulated registry.
var itemModelLister = FileToIdConverter.json("models/item");
itemModelLister.listMatchingResources(Minecraft.getInstance().getResourceManager()).keySet().forEach(itemModel -> {
this.topLevelModelLocations.add(ModelResourceLocation.inventory(itemModelLister.fileToId(itemModel)));
this.namespacesWithModels.add(itemModel.getNamespace());
});
for (var loc : modelRegistry.keySet()) {
this.namespacesWithModels.add(loc.id().getNamespace());
}
this.dependencyGraph = buildDependencyGraph();
}
private MutableGraph<String> buildDependencyGraph() {
MutableGraph<String> dependencyGraph = GraphBuilder.undirected().build();
ModList.get().forEachModContainer((id, mc) -> {
dependencyGraph.addNode(id);
for(IModInfo.ModVersion version : mc.getModInfo().getDependencies()) {
dependencyGraph.addNode(version.getModId());
}
});
for(String id : dependencyGraph.nodes()) {
Optional<? extends ModContainer> mContainer = ModList.get().getModContainerById(id);
if(mContainer.isPresent()) {
for(IModInfo.ModVersion version : mContainer.get().getModInfo().getDependencies()) {
// avoid self-loops
if(!Objects.equals(id, version.getModId()) && !version.getModId().equals("minecraft") && namespacesWithModels.contains(version.getModId()))
dependencyGraph.putEdge(id, version.getModId());
}
}
}
return dependencyGraph;
}
private static final Set<String> WARNED_MOD_IDS = new HashSet<>();
/**
* Create a model registry that warns if keySet, entrySet, values are accessed.
* @param modId the mod that the event is being fired for
* @return a wrapper around the model registry
*/
private Map<ModelResourceLocation, BakedModel> createWarningRegistry(String modId) {
return new ForwardingInclDefaultsMap<ModelResourceLocation, BakedModel>() {
@Override
protected Map<ModelResourceLocation, BakedModel> delegate() {
return modelRegistry;
}
private void logWarning() {
if(!WARNED_MOD_IDS.add(modId))
return;
ModernFix.LOGGER.warn("Mod '{}' is accessing Map#keySet/entrySet/values/replaceAll on the model registry map inside its event handler." +
" This probably won't work as expected with dynamic resources on. Prefer using Map#get/put and constructing ModelResourceLocations another way.", modId);
}
@Override
public Set<ModelResourceLocation> keySet() {
logWarning();
return super.keySet();
}
@Override
public Set<Entry<ModelResourceLocation, BakedModel>> entrySet() {
logWarning();
return super.entrySet();
}
@Override
public Collection<BakedModel> values() {
logWarning();
return super.values();
}
@Override
public void replaceAll(BiFunction<? super ModelResourceLocation, ? super BakedModel, ? extends BakedModel> function) {
logWarning();
super.replaceAll(function);
}
};
}
private Set<String> computeVisibleModIds(String modId) {
Set<String> deps;
try {
deps = this.dependencyGraph.adjacentNodes(modId);
} catch (IllegalArgumentException e) {
deps = Set.of();
}
if (deps.isEmpty()) {
// avoid extra work below
return Set.of(modId);
}
var set = new ObjectOpenHashSet<String>();
set.add(modId);
set.addAll(deps);
return Set.copyOf(set);
}
public Map<ModelResourceLocation, BakedModel> wrapRegistry(String modId) {
var config = MOD_VISIBILITY_CONFIGURATION.getOrDefault(modId, UniverseVisibility.EVERYTHING);
if (config == UniverseVisibility.NONE) {
return createWarningRegistry(modId);
}
final Set<String> modIdsToInclude = computeVisibleModIds(modId);
Set<ModelResourceLocation> ourModelLocations;
if (config == UniverseVisibility.SELF_AND_DEPS) {
ModernFix.LOGGER.debug("Mod {} is restricted to seeing models from mods: [{}]", modId, String.join(", ", modIdsToInclude));
ourModelLocations = Sets.filter(this.topLevelModelLocations, loc -> modIdsToInclude.contains(loc.id().getNamespace()));
} else {
ourModelLocations = this.topLevelModelLocations;
}
BakedModel missingModel = modelRegistry.get(ModelBakery.MISSING_MODEL_LOCATION);
return new EmulatedModelRegistry(modId, modIdsToInclude, missingModel, ourModelLocations);
}
public class EmulatedModelRegistry extends ForwardingMap<ModelResourceLocation, BakedModel> {
private final Set<String> modIdsToInclude;
private final BakedModel missingModel;
private final Set<ModelResourceLocation> ourModelLocations;
private final String modId;
private EmulatedModelRegistry(String modId, Set<String> modIdsToInclude, BakedModel missingModel, Set<ModelResourceLocation> ourModelLocations) {
this.modId = modId;
this.modIdsToInclude = modIdsToInclude;
this.missingModel = missingModel;
this.ourModelLocations = ourModelLocations;
}
@Override
protected Map<ModelResourceLocation, BakedModel> delegate() {
return modelRegistry;
}
@Override
public BakedModel get(@Nullable Object key) {
BakedModel model = super.get(key);
if(model == null && key instanceof ModelResourceLocation mrl && modIdsToInclude.contains(mrl.id().getNamespace())) {
ModernFix.LOGGER.warn("Model {} is missing, but was requested in model bake event. Returning missing model", key);
return missingModel;
}
return model;
}
@Override
public Set<ModelResourceLocation> keySet() {
return Collections.unmodifiableSet(ourModelLocations);
}
@Override
public boolean containsKey(@Nullable Object key) {
return ourModelLocations.contains(key) || super.containsKey(key);
}
@Override
public Set<Entry<ModelResourceLocation, BakedModel>> entrySet() {
return new DynamicModelEntrySet(this, ourModelLocations);
}
@Override
public void replaceAll(BiFunction<? super ModelResourceLocation, ? super BakedModel, ? extends BakedModel> function) {
ModernFix.LOGGER.warn("Mod '{}' is calling replaceAll on the model registry. Some hacks will be used to keep this fast, but they may not be 100% compatible.", modId);
for(ModelResourceLocation location : ourModelLocations) {
/*
* Fetching every model is insanely slow. So we call the function with a null object first, since it
* probably isn't expecting that. If we get an exception thrown, or it returns nonnull, then we know
* it actually cares about the given model.
*/
boolean needsReplacement;
try {
needsReplacement = function.apply(location, null) != null;
} catch(Throwable e) {
needsReplacement = true;
}
if(needsReplacement) {
BakedModel existing = get(location);
BakedModel replacement = function.apply(location, existing);
if(replacement != existing) {
put(location, replacement);
}
}
}
}
}
private static class DynamicModelEntrySet extends AbstractSet<Map.Entry<ModelResourceLocation, BakedModel>> {
private final Map<ModelResourceLocation, BakedModel> modelRegistry;
private final Set<ModelResourceLocation> modelLocations;
private DynamicModelEntrySet(Map<ModelResourceLocation, BakedModel> modelRegistry, Set<ModelResourceLocation> modelLocations) {
this.modelRegistry = modelRegistry;
this.modelLocations = modelLocations;
}
@Override
public Iterator<Map.Entry<ModelResourceLocation, BakedModel>> iterator() {
var iter = this.modelLocations.iterator();
return new Iterator<>() {
@Override
public boolean hasNext() {
return iter.hasNext();
}
@Override
public Map.Entry<ModelResourceLocation, BakedModel> next() {
return new DynamicModelEntry(iter.next());
}
};
}
@Override
public boolean contains(Object o) {
if(o instanceof Map.Entry entry) {
return modelRegistry.containsKey(entry.getKey());
} else {
return false;
}
}
@Override
public int size() {
return modelRegistry.size();
}
@Override
public boolean removeAll(Collection<?> c) {
throw new UnsupportedOperationException();
}
private class DynamicModelEntry implements Map.Entry<ModelResourceLocation, BakedModel> {
private final ModelResourceLocation location;
private DynamicModelEntry(ModelResourceLocation location) {
this.location = location;
}
@Override
public ModelResourceLocation getKey() {
return this.location;
}
@Override
public BakedModel getValue() {
return modelRegistry.get(this.location);
}
@Override
public BakedModel setValue(BakedModel value) {
return modelRegistry.put(this.location, value);
}
}
}
}

View File

@ -1,64 +0,0 @@
package org.embeddedt.modernfix.neoforge.dynresources;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.properties.Property;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
public class ModelLocationBuilder {
private final Map<Property<?>, PropertyData> propertyToOptionStrings = new Object2ObjectOpenHashMap<>();
private final StringBuilder builder = new StringBuilder();
private record PropertyData(ImmutableList<String> nameValuePairs, int maxPairLength) {}
public void generateForBlock(Set<ModelResourceLocation> destinationSet, Block block, ResourceLocation baseLocation) {
var props = block.getStateDefinition().getProperties();
List<ImmutableList<String>> optionsList = new ArrayList<>(props.size());
int requiredBuilderSize = Math.max(0, props.size() - 1); // commas
for (var prop : props) {
var data = propertyToOptionStrings.computeIfAbsent(prop, ModelLocationBuilder::computePropertyOptions);
optionsList.add(data.nameValuePairs);
requiredBuilderSize += data.maxPairLength;
}
var product = Lists.cartesianProduct(optionsList);
int count = product.size();
int tupleEntryCount = optionsList.size();
StringBuilder stringbuilder = this.builder;
stringbuilder.ensureCapacity(requiredBuilderSize);
for (int i = 0; i < count; i++) {
stringbuilder.setLength(0);
var result = product.get(i);
for (int j = 0; j < tupleEntryCount; j++) {
if (j != 0) {
stringbuilder.append(',');
}
stringbuilder.append(result.get(j));
}
destinationSet.add(new ModelResourceLocation(baseLocation, stringbuilder.toString()));
}
}
private static PropertyData computePropertyOptions(Property<?> prop) {
ImmutableList.Builder<String> valuesList = ImmutableList.builderWithExpectedSize(prop.getPossibleValues().size());
int maxLength = 0;
for (var val : prop.getPossibleValues()) {
String pair = prop.getName() + "=" + getValueName(prop, val);
valuesList.add(pair.toLowerCase(Locale.ROOT));
maxLength = Math.max(pair.length(), maxLength);
}
return new PropertyData(valuesList.build(), maxLength);
}
private static <T extends Comparable<T>> String getValueName(Property<T> property, Comparable<?> value) {
return property.getName((T)value);
}
}

View File

@ -1,9 +1,14 @@
package org.embeddedt.modernfix.neoforge.init;
import com.mojang.blaze3d.platform.InputConstants;
import net.minecraft.client.KeyMapping;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.components.DebugScreenOverlay;
import net.minecraft.client.gui.components.debug.DebugScreenDisplayer;
import net.minecraft.client.gui.components.debug.DebugScreenEntry;
import net.minecraft.client.gui.components.debug.DebugScreenEntryStatus;
import net.minecraft.client.gui.components.debug.DebugScreenProfile;
import net.minecraft.resources.Identifier;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.chunk.LevelChunk;
import net.neoforged.bus.api.EventPriority;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.bus.api.SubscribeEvent;
@ -11,39 +16,28 @@ import net.neoforged.fml.ModContainer;
import net.neoforged.fml.ModLoader;
import net.neoforged.fml.ModLoadingIssue;
import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent;
import net.neoforged.neoforge.client.event.ClientTickEvent;
import net.neoforged.neoforge.client.event.CustomizeGuiOverlayEvent;
import net.neoforged.neoforge.client.event.RecipesUpdatedEvent;
import net.neoforged.neoforge.client.event.RegisterKeyMappingsEvent;
import net.neoforged.neoforge.client.event.RecipesReceivedEvent;
import net.neoforged.neoforge.client.event.RegisterDebugEntriesEvent;
import net.neoforged.neoforge.client.event.RenderFrameEvent;
import net.neoforged.neoforge.client.gui.IConfigScreenFactory;
import net.neoforged.neoforge.client.settings.KeyConflictContext;
import net.neoforged.neoforge.event.TagsUpdatedEvent;
import net.neoforged.neoforge.event.level.LevelEvent;
import net.neoforged.neoforge.event.server.ServerStartedEvent;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.ModernFixClient;
import org.embeddedt.modernfix.screen.ModernFixConfigScreen;
import java.util.ArrayList;
import java.util.List;
import org.jspecify.annotations.Nullable;
public class ModernFixClientForge {
private static ModernFixClient commonMod;
public ModernFixClientForge(ModContainer modContainer, IEventBus modBus) {
commonMod = new ModernFixClient();
modBus.addListener(this::keyBindRegister);
modBus.addListener(this::onClientSetup);
modBus.addListener(this::onRenderOverlay);
modContainer.registerExtensionPoint(IConfigScreenFactory.class, (mc, screen) -> new ModernFixConfigScreen(screen));
}
private KeyMapping configKey;
private void keyBindRegister(RegisterKeyMappingsEvent event) {
configKey = new KeyMapping("key.modernfix.config", KeyConflictContext.UNIVERSAL, InputConstants.UNKNOWN, "key.modernfix");
event.register(configKey);
}
private void onClientSetup(FMLClientSetupEvent event) {
if(false) {
event.enqueueWork(() -> {
@ -52,42 +46,25 @@ public class ModernFixClientForge {
}
}
@SubscribeEvent
public void onConfigKey(ClientTickEvent.Pre event) {
if(configKey != null && configKey.consumeClick()) {
Minecraft.getInstance().setScreen(new ModernFixConfigScreen(Minecraft.getInstance().screen));
}
}
private static final Identifier MODERNFIX_VERSION = Identifier.fromNamespaceAndPath(ModernFix.MODID, "version");
private static final List<String> brandingList = new ArrayList<>();
@SubscribeEvent(priority = EventPriority.HIGHEST)
public void onRenderOverlay(CustomizeGuiOverlayEvent.DebugText event) {
if(commonMod.brandingString != null && Minecraft.getInstance().getDebugOverlay().showDebugScreen()) {
if(brandingList.size() == 0) {
brandingList.add("");
brandingList.add(commonMod.brandingString);
}
int targetIdx = 0, numSeenBlanks = 0;
List<String> right = event.getRight();
while(targetIdx < right.size()) {
String s = right.get(targetIdx);
if(s == null || s.length() == 0) {
numSeenBlanks++;
private void onRenderOverlay(RegisterDebugEntriesEvent event) {
event.register(MODERNFIX_VERSION, new DebugScreenEntry() {
@Override
public void display(DebugScreenDisplayer displayer, @Nullable Level level, @Nullable LevelChunk clientChunk, @Nullable LevelChunk serverChunk) {
if (commonMod.brandingString != null) {
displayer.addToGroup(MODERNFIX_VERSION, commonMod.brandingString);
}
if(numSeenBlanks == 3)
break;
targetIdx++;
}
right.addAll(targetIdx, brandingList);
}
});
event.includeInProfile(MODERNFIX_VERSION, DebugScreenProfile.DEFAULT, DebugScreenEntryStatus.IN_OVERLAY);
}
@SubscribeEvent
public void onDisconnect(LevelEvent.Unload event) {
if(event.getLevel().isClientSide()) {
DebugScreenOverlay overlay = Minecraft.getInstance().getDebugOverlay();
Minecraft.getInstance().tell(overlay::clearChunkCache);
Minecraft.getInstance().schedule(overlay::clearChunkCache);
}
}
@ -102,7 +79,7 @@ public class ModernFixClientForge {
}
@SubscribeEvent(priority = EventPriority.LOWEST)
public void onRecipes(RecipesUpdatedEvent e) {
public void onRecipes(RecipesReceivedEvent e) {
commonMod.onRecipesUpdated();
}

View File

@ -2,7 +2,7 @@ package org.embeddedt.modernfix.neoforge.init;
import com.google.common.collect.ImmutableList;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.resources.Identifier;
import net.minecraft.world.item.Item;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.EventPriority;
@ -10,7 +10,6 @@ import net.neoforged.bus.api.IEventBus;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.*;
import net.neoforged.fml.common.Mod;
import net.neoforged.fml.config.ModConfig;
import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent;
import net.neoforged.fml.loading.FMLEnvironment;
import net.neoforged.fml.loading.FMLLoader;
@ -18,13 +17,10 @@ import net.neoforged.neoforge.common.NeoForge;
import net.neoforged.neoforge.event.server.ServerStartedEvent;
import net.neoforged.neoforge.event.server.ServerStoppedEvent;
import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent;
import net.neoforged.neoforge.network.registration.PayloadRegistrar;
import net.neoforged.neoforge.registries.RegisterEvent;
import org.apache.commons.lang3.tuple.Pair;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.core.ModernFixMixinPlugin;
import org.embeddedt.modernfix.neoforge.ModernFixConfig;
import org.embeddedt.modernfix.neoforge.packet.SmartIngredientSyncPayload;
import java.util.List;
@ -40,10 +36,9 @@ public class ModernFixForge {
modBus.addListener(this::commonSetup);
modBus.addListener(this::registerItems);
modBus.addListener(this::registerNetworkChannel);
if(FMLEnvironment.dist == Dist.CLIENT) {
if(FMLEnvironment.getDist() == Dist.CLIENT) {
NeoForge.EVENT_BUS.register(new ModernFixClientForge(modContainer, modBus));
}
modContainer.registerConfig(ModConfig.Type.COMMON, ModernFixConfig.COMMON_CONFIG);
}
private void registerItems(RegisterEvent event) {
@ -51,7 +46,7 @@ public class ModernFixForge {
event.register(Registries.ITEM, helper -> {
Item.Properties props = new Item.Properties();
for(int i = 0; i < 1000000; i++) {
helper.register(ResourceLocation.fromNamespaceAndPath("modernfix", "item_" + i), new Item(props));
helper.register(Identifier.fromNamespaceAndPath("modernfix", "item_" + i), new Item(props));
}
});
}
@ -66,7 +61,7 @@ public class ModernFixForge {
event.enqueueWork(() -> {
boolean atLeastOneWarning = false;
for(Pair<List<String>, String> warning : MOD_WARNINGS) {
boolean isPresent = !FMLLoader.isProduction() || warning.getLeft().stream().anyMatch(name -> ModList.get().isLoaded(name));
boolean isPresent = !FMLLoader.getCurrent().isProduction() || warning.getLeft().stream().anyMatch(name -> ModList.get().isLoaded(name));
if(!isPresent) {
atLeastOneWarning = true;
ModLoader.addLoadingIssue(ModLoadingIssue.warning(warning.getRight()));
@ -79,15 +74,7 @@ public class ModernFixForge {
}
private void registerNetworkChannel(final RegisterPayloadHandlersEvent event) {
if (ModernFixMixinPlugin.instance.isOptionEnabled("perf.smart_ingredient_sync.Channel")) {
// Sets the current network version
final PayloadRegistrar registrar = event.registrar("1").optional();
registrar.playToClient(
SmartIngredientSyncPayload.TYPE,
SmartIngredientSyncPayload.STREAM_CODEC,
(payload, ctx) -> {}
);
}
}
@SubscribeEvent(priority = EventPriority.LOWEST)

View File

@ -1,21 +0,0 @@
package org.embeddedt.modernfix.neoforge.packet;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import org.embeddedt.modernfix.ModernFix;
public enum SmartIngredientSyncPayload implements CustomPacketPayload {
INSTANCE;
public static final ThreadLocal<Boolean> CLIENT_HAS_SMART_INGREDIENT_SYNC = ThreadLocal.withInitial(() -> false);
public static final CustomPacketPayload.Type<SmartIngredientSyncPayload> TYPE = new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(ModernFix.MODID, "ingredient_sync"));
public static final StreamCodec<RegistryFriendlyByteBuf, SmartIngredientSyncPayload> STREAM_CODEC = StreamCodec.unit(INSTANCE);
@Override
public Type<? extends CustomPacketPayload> type() {
return TYPE;
}
}

View File

@ -1,97 +0,0 @@
package org.embeddedt.modernfix.neoforge.recipe;
import com.google.common.math.IntMath;
import it.unimi.dsi.fastutil.Hash;
import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet;
import it.unimi.dsi.fastutil.objects.Reference2ObjectMaps;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
import org.embeddedt.modernfix.common.mixin.perf.ingredient_item_deduplication.PatchedDataComponentMapAccessor;
/**
* @author embeddedt (original inspiration from Uncandango's AllTheLeaks mod)
*/
public class IngredientValueDeduplicator {
private static final ObjectOpenCustomHashSet<Ingredient.ItemValue> VALUES = new ObjectOpenCustomHashSet<>(new Hash.Strategy<>() {
@Override
public int hashCode(Ingredient.ItemValue o) {
if (o == null) {
return 0;
}
var stack = o.item();
int hash = 31 * stack.getItem().hashCode();
if (stack.getComponents() instanceof PatchedDataComponentMapAccessor comps) {
var patch = comps.mfix$getPatch();
for (var entry : Reference2ObjectMaps.fastIterable(patch)) {
int keyHash = System.identityHashCode(entry.getKey()) & 0xff;
var value = entry.getValue();
int valueHash = value.isPresent() ? System.identityHashCode(value.get()) : 0;
hash += IntMath.pow(31, keyHash) * valueHash;
}
} else {
hash += System.identityHashCode(stack.getComponents());
}
return hash;
}
private boolean areComponentsSame(ItemStack a, ItemStack b) {
// Compare using stricter logic than vanilla: require the prototype maps to be equal, and require
// the values in the patch to also be identity-equal. This works around Neo allowing Holder.Reference objects
// made with the server & client registries to be considered equal.
if (a.getComponents() instanceof PatchedDataComponentMapAccessor aComps && b.getComponents() instanceof PatchedDataComponentMapAccessor bComps) {
if (!aComps.mfix$getPrototype().equals(bComps.mfix$getPrototype())) {
return false;
}
var aPatch = aComps.mfix$getPatch();
var bPatch = bComps.mfix$getPatch();
if (aPatch != bPatch) {
if (aPatch.size() != bPatch.size()) {
return false;
}
for (var entry : Reference2ObjectMaps.fastIterable(aPatch)) {
var value = bPatch.get(entry.getKey());
if (value == null) {
return false;
}
if (value.isPresent() != entry.getValue().isPresent()) {
return false;
} else if (value.isPresent() && value.get() != entry.getValue().get()) {
return false;
}
}
}
return true;
} else {
return a.getComponents() == b.getComponents();
}
}
private boolean areStacksSame(ItemStack a, ItemStack b) {
if (!a.is(b.getItem())) {
return false;
}
if (a.isEmpty() != b.isEmpty()) {
return false;
}
if (!areComponentsSame(a, b)) {
return false;
}
return a.getCount() == b.getCount();
}
@Override
public boolean equals(Ingredient.ItemValue a, Ingredient.ItemValue b) {
return a == b || a != null && b != null && areStacksSame(a.item(), b.item());
}
});
public static Ingredient.Value deduplicate(Ingredient.Value value) {
if (value != null && value.getClass() == Ingredient.ItemValue.class) {
synchronized (VALUES) {
return VALUES.addOrGet((Ingredient.ItemValue)value);
}
} else {
return value;
}
}
}

View File

@ -35,11 +35,11 @@ import java.util.function.Consumer;
public class ModernFixPlatformHooksImpl implements ModernFixPlatformHooks {
public boolean isClient() {
return FMLLoader.getDist() == Dist.CLIENT;
return FMLLoader.getCurrent().getDist() == Dist.CLIENT;
}
public boolean isDedicatedServer() {
return FMLLoader.getDist().isDedicatedServer();
return FMLLoader.getCurrent().getDist().isDedicatedServer();
}
private static final String verString = Optional.ofNullable(
@ -51,11 +51,11 @@ public class ModernFixPlatformHooksImpl implements ModernFixPlatformHooks {
}
public boolean modPresent(String modId) {
return FMLLoader.getLoadingModList().getModFileById(modId) != null;
return FMLLoader.getCurrent().getLoadingModList().getModFileById(modId) != null;
}
public boolean isDevEnv() {
return !FMLLoader.isProduction();
return !FMLLoader.getCurrent().isProduction();
}
public MinecraftServer getCurrentServer() {

View File

@ -1,94 +0,0 @@
package org.embeddedt.modernfix.render;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.block.model.ItemOverrides;
import net.minecraft.client.renderer.block.model.ItemTransforms;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.core.Direction;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.block.state.BlockState;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* Wrapper class that presents a fake view of item models (only showing the simple front-facing quads), rather
* than every quad.
*/
public class SimpleItemModelView implements BakedModel {
private BakedModel wrappedItem;
private FastItemRenderType type;
public void setItem(BakedModel model) {
this.wrappedItem = model;
}
public void setType(FastItemRenderType type) {
this.type = type;
}
private boolean isCorrectDirectionForType(Direction direction) {
if(type == FastItemRenderType.SIMPLE_ITEM)
return direction == Direction.SOUTH;
else {
return direction == Direction.UP || direction == Direction.EAST || direction == Direction.NORTH;
}
}
private final List<BakedQuad> nullQuadList = new ObjectArrayList<>();
@Override
public List<BakedQuad> getQuads(@Nullable BlockState state, @Nullable Direction side, RandomSource rand) {
boolean isWholeListValid = isCorrectDirectionForType(side);
List<BakedQuad> realList = wrappedItem.getQuads(state, side, rand);
if (isWholeListValid) {
return realList;
}
nullQuadList.clear();
//noinspection ForLoopReplaceableByForEach
for(int i = 0; i < realList.size(); i++) {
BakedQuad quad = realList.get(i);
if(isCorrectDirectionForType(quad.getDirection())) {
nullQuadList.add(quad);
}
}
return nullQuadList;
}
@Override
public boolean useAmbientOcclusion() {
return wrappedItem.useAmbientOcclusion();
}
@Override
public boolean isGui3d() {
return wrappedItem.isGui3d();
}
@Override
public boolean usesBlockLight() {
return wrappedItem.usesBlockLight();
}
@Override
public boolean isCustomRenderer() {
return wrappedItem.isCustomRenderer();
}
@Override
public TextureAtlasSprite getParticleIcon() {
return wrappedItem.getParticleIcon();
}
@Override
public ItemTransforms getTransforms() {
return wrappedItem.getTransforms();
}
@Override
public ItemOverrides getOverrides() {
return wrappedItem.getOverrides();
}
}

View File

@ -5,7 +5,7 @@ import com.google.common.base.Stopwatch;
import com.google.common.collect.ImmutableList;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.resources.Identifier;
import net.minecraft.server.packs.PackResources;
import net.minecraft.server.packs.PackType;
import org.embeddedt.modernfix.ModernFix;
@ -75,7 +75,7 @@ public class PackResourcesCacheEngine {
void outputResources(String namespace, Path baseNioPath, String path, PackResources.ResourceOutput output) {
if (children.isEmpty()) {
// This is a terminal node.
ResourceLocation location = ResourceLocation.fromNamespaceAndPath(namespace, path);
Identifier location = Identifier.fromNamespaceAndPath(namespace, path);
output.accept(location, () -> Files.newInputStream(baseNioPath.resolve(path)));
} else {
for (var entry : children.entrySet()) {
@ -144,7 +144,7 @@ public class PackResourcesCacheEngine {
if(str.length() == 0)
return false;
for(int i = 0; i < str.length(); i++) {
if(!ResourceLocation.validPathChar(str.charAt(i))) {
if(!Identifier.validPathChar(str.charAt(i))) {
return false;
}
}

View File

@ -5,7 +5,7 @@ import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.CommonComponents;
import net.minecraft.network.chat.Component;
import net.minecraft.Util;
import net.minecraft.util.Util;
public class ModernFixConfigScreen extends Screen {
private OptionList optionList;

View File

@ -11,6 +11,7 @@ import net.minecraft.client.gui.components.ContainerObjectSelectionList;
import net.minecraft.client.gui.components.Tooltip;
import net.minecraft.client.gui.components.events.GuiEventListener;
import net.minecraft.client.gui.narration.NarratableEntry;
import net.minecraft.client.input.MouseButtonEvent;
import net.minecraft.client.resources.language.I18n;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.HoverEvent;
@ -41,7 +42,7 @@ public class OptionList extends ContainerObjectSelectionList<OptionList.Entry> {
String friendlyKey = "modernfix.option.name." + option.getName();
MutableComponent baseComponent = Component.literal(option.getSelfName());
if(I18n.exists(friendlyKey))
return Component.translatable(friendlyKey).withStyle(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, baseComponent)));
return Component.translatable(friendlyKey).withStyle(style -> style.withHoverEvent(new HoverEvent.ShowText(baseComponent)));
else
return baseComponent;
}
@ -78,7 +79,7 @@ public class OptionList extends ContainerObjectSelectionList<OptionList.Entry> {
for(String category : theCategories) {
String categoryTranslationKey = "modernfix.option.category." + category;
this.addEntry(new CategoryEntry(Component.translatable(categoryTranslationKey)
.withStyle(Style.EMPTY.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.translatable(categoryTranslationKey + ".description"))))
.withStyle(Style.EMPTY.withHoverEvent(new HoverEvent.ShowText(Component.translatable(categoryTranslationKey + ".description"))))
));
optionsByCategory.get(category).stream().filter(key -> {
int dotCount = 0;
@ -91,10 +92,6 @@ public class OptionList extends ContainerObjectSelectionList<OptionList.Entry> {
}
}
protected int getScrollbarPosition() {
return super.getScrollbarPosition() + 15 + 20;
}
public int getRowWidth() {
return super.getRowWidth() + 32;
}
@ -108,10 +105,10 @@ public class OptionList extends ContainerObjectSelectionList<OptionList.Entry> {
this.width = OptionList.this.minecraft.font.width(this.name);
}
public void render(GuiGraphics guiGraphics, int index, int top, int left, int width, int height, int mouseX, int mouseY, boolean isMouseOver, float partialTicks) {
public void renderContent(GuiGraphics guiGraphics, int mouseX, int mouseY, boolean isMouseOver, float partialTicks) {
Font var10000 = OptionList.this.minecraft.font;
float x = (float)(OptionList.this.minecraft.screen.width / 2 - this.width / 2);
int y = top + height - 10;
int y = 0 + height - 10;
guiGraphics.drawString(var10000, this.name, (int)x, y, 16777215);
/*
if(mouseX >= x && mouseY >= y && mouseX <= (x + this.width) && mouseY <= (y + OptionList.this.minecraft.font.lineHeight))
@ -166,7 +163,7 @@ public class OptionList extends ContainerObjectSelectionList<OptionList.Entry> {
}).tooltip(toggleTooltip).pos(0, 0).size(55, 20).build();
updateStatus();
this.helpButton = new Button.Builder(Component.literal("?"), (arg) -> {
mainScreen.setLastScrollAmount(getScrollAmount());
mainScreen.setLastScrollAmount(scrollAmount());
Minecraft.getInstance().setScreen(new ModernFixOptionInfoScreen(mainScreen, optionName));
}).pos(75, 0).size(20, 20).build();
if(!I18n.exists("modernfix.option." + optionName)) {
@ -181,7 +178,8 @@ public class OptionList extends ContainerObjectSelectionList<OptionList.Entry> {
}
@Override
public void render(GuiGraphics guiGraphics, int index, int top, int left, int width, int height, int mouseX, int mouseY, boolean isMouseOver, float partialTicks) {
public void renderContent(GuiGraphics guiGraphics, int mouseX, int mouseY, boolean isHovering, float partialTicks) {
int left = 0, top = 0;
MutableComponent nameComponent = getOptionComponent(option);
if(this.option.isUserDefined())
nameComponent = nameComponent.withStyle(style -> style.withItalic(true)).append(Component.translatable("modernfix.config.not_default"));
@ -209,17 +207,17 @@ public class OptionList extends ContainerObjectSelectionList<OptionList.Entry> {
return ImmutableList.of(this.toggleButton, this.helpButton);
}
public boolean mouseClicked(double mouseX, double mouseY, int button) {
public boolean mouseClicked(MouseButtonEvent event, boolean isDoubleClick) {
for(GuiEventListener listener : children()) {
if(listener.mouseClicked(mouseX, mouseY, button))
if(listener.mouseClicked(event, isDoubleClick))
return true;
}
return false;
}
public boolean mouseReleased(double mouseX, double mouseY, int button) {
public boolean mouseReleased(MouseButtonEvent event) {
for(GuiEventListener listener : children()) {
if(listener.mouseReleased(mouseX, mouseY, button))
if(listener.mouseReleased(event))
return true;
}
return false;

View File

@ -108,7 +108,7 @@ public class SparkLaunchProfiler {
@Override
public String getMinecraftVersion() {
return SharedConstants.getCurrentVersion().getName();
return SharedConstants.getCurrentVersion().name();
}
}

View File

@ -1,83 +0,0 @@
package org.embeddedt.modernfix.util;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.core.registries.BuiltInRegistries;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
public class ItemMesherMap<K> implements Map<K, ModelResourceLocation> {
private final Function<K, ModelResourceLocation> getLocation;
public ItemMesherMap(Function<K, ModelResourceLocation> getLocation) {
this.getLocation = getLocation;
}
@Override
public int size() {
return BuiltInRegistries.ITEM.keySet().size();
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public boolean containsKey(Object key) {
return true;
}
@Override
public boolean containsValue(Object value) {
return false;
}
@Override
public ModelResourceLocation get(Object key) {
return getLocation.apply((K)key);
}
@Nullable
@Override
public ModelResourceLocation put(K key, ModelResourceLocation value) {
throw new UnsupportedOperationException();
}
@Override
public ModelResourceLocation remove(Object key) {
throw new UnsupportedOperationException();
}
@Override
public void putAll(@NotNull Map<? extends K, ? extends ModelResourceLocation> m) {
throw new UnsupportedOperationException();
}
@Override
public void clear() {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public Set<K> keySet() {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public Collection<ModelResourceLocation> values() {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public Set<Map.Entry<K, ModelResourceLocation>> entrySet() {
throw new UnsupportedOperationException();
}
}

View File

@ -13,9 +13,15 @@ public class NamedPreparableResourceListener implements PreparableReloadListener
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);
public CompletableFuture<Void> reload(SharedState sharedState, Executor exectutor, PreparationBarrier barrier, Executor applyExectutor) {
return this.delegate.reload(sharedState, exectutor, barrier, applyExectutor);
}
@Override
public void prepareSharedState(SharedState sharedState) {
this.delegate.prepareSharedState(sharedState);
}
@Override

View File

@ -2,7 +2,7 @@ package org.embeddedt.modernfix.world;
import com.mojang.logging.LogUtils;
import net.minecraft.DefaultUncaughtExceptionHandlerWithName;
import net.minecraft.Util;
import net.minecraft.util.Util;
import net.minecraft.server.MinecraftServer;
import org.embeddedt.modernfix.duck.ITimeTrackingServer;
import org.slf4j.Logger;

Some files were not shown because too many files have changed in this diff Show More