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:
parent
c63b9de971
commit
23a5f2985e
7
TODO.txt
Normal file
7
TODO.txt
Normal 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
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
package org.embeddedt.modernfix.duck;
|
||||
|
||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
||||
|
||||
public interface IBlockStateModelLoader {
|
||||
void loadSpecificBlock(ModelResourceLocation location);
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
package org.embeddedt.modernfix.duck;
|
||||
|
||||
import org.embeddedt.modernfix.world.StrongholdLocationCache;
|
||||
|
||||
public interface IServerLevel {
|
||||
StrongholdLocationCache mfix$getStrongholdCache();
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 :) */
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
package org.embeddedt.modernfix.dynamicresources;
|
||||
|
||||
public class ModelMissingException extends RuntimeException {
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
package org.embeddedt.modernfix.neoforge.dynresources;
|
||||
|
||||
public interface IModelBakerImpl {
|
||||
void mfix$ignoreCache();
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ public class SparkLaunchProfiler {
|
|||
|
||||
@Override
|
||||
public String getMinecraftVersion() {
|
||||
return SharedConstants.getCurrentVersion().getName();
|
||||
return SharedConstants.getCurrentVersion().name();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in New Issue
Block a user