Merge commit 'd64a1c760b7cd66393b8cb962501278624f23444' into 1.21.1

This commit is contained in:
embeddedt 2025-12-26 18:35:57 -05:00
commit 935365604d
No known key found for this signature in database
GPG Key ID: A69433EC199B5613
37 changed files with 60 additions and 1316 deletions

View File

@ -1,21 +0,0 @@
package org.embeddedt.modernfix;
import com.google.common.cache.CacheLoader;
import org.apache.commons.lang3.tuple.Pair;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class FileWalker extends CacheLoader<Pair<Path, Integer>, List<Path>> {
public static final FileWalker INSTANCE = new FileWalker();
@Override
public List<Path> load(Pair<Path, Integer> key) throws Exception {
try(Stream<Path> stream = Files.walk(key.getLeft(), key.getRight())) {
return stream.collect(Collectors.toList());
}
}
}

View File

@ -1,10 +1,8 @@
package org.embeddedt.modernfix;
import net.minecraft.client.Minecraft;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.MemoryReserve;
import net.minecraft.world.entity.Entity;
import org.embeddedt.modernfix.api.constants.IntegrationConstants;
import org.embeddedt.modernfix.api.entrypoint.ModernFixClientIntegration;
import org.embeddedt.modernfix.core.ModernFixMixinPlugin;
@ -98,25 +96,6 @@ public class ModernFixClient {
}
}
/**
* Check if the IDs match and remap them if not.
* @return true if ID remap was needed
*/
private static boolean compareAndSwitchIds(Class<? extends Entity> eClass, String fieldName, EntityDataAccessor<?> accessor, int newId) {
if(accessor.id != newId) {
ModernFix.LOGGER.warn("Corrected ID mismatch on {} field {}. Client had {} but server wants {}.",
eClass,
fieldName,
accessor.id,
newId);
accessor.id = newId;
return true;
} else {
ModernFix.LOGGER.debug("{} {} ID fine: {}", eClass, fieldName, newId);
return false;
}
}
public void onServerStarted(MinecraftServer server) {
if(!ModernFixMixinPlugin.instance.isOptionEnabled("feature.integrated_server_watchdog.IntegratedWatchdog"))
return;

View File

@ -6,7 +6,7 @@ import net.minecraft.world.level.block.state.BlockState;
import org.embeddedt.modernfix.duck.IBlockState;
public class BlockStateCacheHandler {
public static void rebuildParallel(boolean force) {
public static void invalidateCache() {
synchronized (BlockBehaviour.BlockStateBase.class) {
for (BlockState blockState : Block.BLOCK_STATE_REGISTRY) {
((IBlockState)blockState).clearCache();

View File

@ -1,62 +0,0 @@
package org.embeddedt.modernfix.blockstate;
import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntMaps;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.StateHolder;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
public class FerriteCorePostProcess {
private static final boolean willPostProcess;
private static final MethodHandle theTable, toKeyIndex;
static {
boolean success = true;
MethodHandle table = null, keyIndex = null;
try {
Class<?> fastMap = Class.forName("malte0811.ferritecore.fastmap.FastMap");
Field field = fastMap.getDeclaredField("toKeyIndex");
field.setAccessible(true);
keyIndex = MethodHandles.publicLookup().unreflectSetter(field);
field = StateHolder.class.getDeclaredField("ferritecore_globalTable");
field.setAccessible(true);
table = MethodHandles.publicLookup().unreflectGetter(field);
} catch(ReflectiveOperationException | RuntimeException e) {
e.printStackTrace();
success = false;
}
willPostProcess = success;
theTable = table;
toKeyIndex = keyIndex;
}
private static final Object2IntMap<?> EMPTY_MAP;
static {
Object2IntArrayMap<?> map = new Object2IntArrayMap<>();
map.defaultReturnValue(-1);
EMPTY_MAP = Object2IntMaps.unmodifiable(map);
}
public static <O, S extends StateHolder<O, S>> void postProcess(StateDefinition<O, S> state) {
if(!willPostProcess)
return;
try {
if(state.getProperties().size() == 0) {
for(S holder : state.getPossibleStates()) {
// deduplicate Object2IntMap objects from FerriteCore
// will probably be fixed upstream at some point, but likely not for older versions
Object table = theTable.invoke(holder);
toKeyIndex.invoke(table, EMPTY_MAP);
}
}
} catch(Throwable e) {
e.printStackTrace();
}
}
}

View File

@ -1,78 +0,0 @@
package org.embeddedt.modernfix.chunk;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.Fluids;
import org.jetbrains.annotations.Nullable;
public class SafeBlockGetter implements BlockGetter {
private final ServerLevel wrapped;
private final Thread mainThread;
public SafeBlockGetter(ServerLevel wrapped) {
this.wrapped = wrapped;
this.mainThread = Thread.currentThread();
}
public boolean shouldUse() {
return Thread.currentThread() != this.mainThread;
}
@Nullable
private BlockGetter getChunkSafe(BlockPos pos) {
// can safely call getChunkForLighting off-thread
BlockGetter access = this.wrapped.getChunkSource().getChunkForLighting(pos.getX() >> 4, pos.getZ() >> 4);
if(!(access instanceof ChunkAccess))
return null;
ChunkAccess chunk = (ChunkAccess)access;
if(!chunk.getPersistedStatus().isOrAfter(ChunkStatus.FULL))
return null;
return chunk;
}
@Override
public int getMaxBuildHeight() {
return this.wrapped.getMaxBuildHeight();
}
@Override
public int getMaxLightLevel() {
return this.wrapped.getMaxLightLevel();
}
@Override
public int getMinBuildHeight() {
return this.wrapped.getMinBuildHeight();
}
@Override
public int getHeight() {
return this.wrapped.getHeight();
}
@Nullable
@Override
public BlockEntity getBlockEntity(BlockPos pos) {
BlockGetter g = getChunkSafe(pos);
return g == null ? null : g.getBlockEntity(pos);
}
@Override
public BlockState getBlockState(BlockPos pos) {
BlockGetter g = getChunkSafe(pos);
return g == null ? Blocks.AIR.defaultBlockState() : g.getBlockState(pos);
}
@Override
public FluidState getFluidState(BlockPos pos) {
BlockGetter g = getChunkSafe(pos);
return g == null ? Fluids.EMPTY.defaultFluidState() : g.getFluidState(pos);
}
}

View File

@ -1,22 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.chunk_deadlock;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.state.BlockBehaviour;
import org.embeddedt.modernfix.chunk.SafeBlockGetter;
import org.embeddedt.modernfix.duck.ISafeBlockGetter;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
@Mixin(value = BlockBehaviour.BlockStateBase.class, priority = 100)
public class BlockStateBaseMixin {
@ModifyVariable(method = "getOffset", at = @At("HEAD"), argsOnly = true, index = 1)
private BlockGetter useSafeGetter(BlockGetter g) {
if(g instanceof ISafeBlockGetter) {
SafeBlockGetter replacement = ((ISafeBlockGetter) g).mfix$getSafeBlockGetter();
if(replacement.shouldUse())
return replacement;
}
return g;
}
}

View File

@ -1,18 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.chunk_deadlock;
import net.minecraft.server.level.ServerLevel;
import org.embeddedt.modernfix.chunk.SafeBlockGetter;
import org.embeddedt.modernfix.duck.ISafeBlockGetter;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
@Mixin(ServerLevel.class)
public class ServerLevelMixin implements ISafeBlockGetter {
@Unique
private final SafeBlockGetter mfix$safeBlockGetter = new SafeBlockGetter((ServerLevel)(Object)this);
@Override
public SafeBlockGetter mfix$getSafeBlockGetter() {
return mfix$safeBlockGetter;
}
}

View File

@ -1,36 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.concurrency;
import net.minecraft.client.Minecraft;
import net.minecraft.util.thread.BlockableEventLoop;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Mixin;
import java.util.function.BooleanSupplier;
@Mixin(Minecraft.class)
@ClientOnlyMixin
public abstract class MinecraftMixin<R extends Runnable> extends BlockableEventLoop<R> {
protected MinecraftMixin(String p_i50403_1_) {
super(p_i50403_1_);
}
@Override
public void managedBlock(BooleanSupplier pIsDone) {
if(!this.isSameThread()) {
ModernFix.LOGGER.warn("A mod is calling Minecraft.managedBlock from the wrong thread. This is most likely related to one of our parallelizations.");
ModernFix.LOGGER.warn("ModernFix will work around this, however ideally the issue should be patched in the other mod.");
ModernFix.LOGGER.warn("Stacktrace", new IllegalThreadStateException());
while(!pIsDone.getAsBoolean()) {
try {
Thread.sleep(100);
} catch(InterruptedException e) {
Thread.currentThread().interrupt();
}
}
} else {
super.managedBlock(pIsDone);
}
}
}

View File

@ -1,22 +0,0 @@
package org.embeddedt.modernfix.common.mixin.feature.direct_stack_trace;
import net.minecraft.CrashReport;
import net.minecraft.CrashReportCategory;
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.CallbackInfoReturnable;
@Mixin(CrashReport.class)
public class CrashReportMixin {
@Shadow @Final private Throwable exception;
@Inject(method = "addCategory(Ljava/lang/String;I)Lnet/minecraft/CrashReportCategory;", at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;error(Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;)V", remap = false))
private void dumpStacktrace(String s, int i, CallbackInfoReturnable<CrashReportCategory> cir) {
new Exception("ModernFix crash stacktrace").printStackTrace();
if(this.exception != null)
this.exception.printStackTrace();
}
}

View File

@ -1,70 +0,0 @@
package org.embeddedt.modernfix.common.mixin.feature.stalled_chunk_load_detection;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.Registries;
import net.minecraft.server.level.ChunkResult;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.Biomes;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.chunk.EmptyLevelChunk;
import org.embeddedt.modernfix.ModernFix;
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.CallbackInfoReturnable;
import java.util.concurrent.*;
@Mixin(value = ServerChunkCache.class, priority = 1100)
public abstract class ServerChunkCacheMixin {
@Shadow @Final private Thread mainThread;
@Shadow @Final public ServerLevel level;
@Shadow protected abstract CompletableFuture<ChunkResult<ChunkAccess>> getChunkFutureMainThread(int k, int l, ChunkStatus arg, boolean bl);
@Shadow @Final private ServerChunkCache.MainThreadExecutor mainThreadProcessor;
private final boolean debugDeadServerAccess = Boolean.getBoolean("modernfix.debugBadChunkloading");
@Inject(method = "getChunk", at = @At("HEAD"), cancellable = true)
private void bailIfServerDead(int chunkX, int chunkZ, ChunkStatus requiredStatus, boolean load, CallbackInfoReturnable<ChunkAccess> cir) {
if(!this.level.getServer().isRunning() && !this.mainThread.isAlive()) {
ModernFix.LOGGER.fatal("A mod is accessing chunks from a stopped server (this will also cause memory leaks)");
if(debugDeadServerAccess) {
new Exception().printStackTrace();
}
Holder<Biome> plains = this.level.registryAccess().registryOrThrow(Registries.BIOME).getHolderOrThrow(Biomes.PLAINS);
cir.setReturnValue(new EmptyLevelChunk(this.level, new ChunkPos(chunkX, chunkZ), plains));
} else if(Thread.currentThread() != this.mainThread) {
CompletableFuture<ChunkResult<ChunkAccess>> future = CompletableFuture.supplyAsync(() -> this.getChunkFutureMainThread(chunkX, chunkZ, requiredStatus, false), this.mainThreadProcessor).join();
if(!future.isDone()) {
// Wait at least 500 milliseconds before printing anything
ChunkResult<ChunkAccess> resultingChunk = null;
try {
resultingChunk = future.get(500, TimeUnit.MILLISECONDS);
} catch(InterruptedException | ExecutionException | TimeoutException ignored) {
}
if(resultingChunk != null && resultingChunk.isSuccess()) {
cir.setReturnValue(resultingChunk.orElse(null));
return;
}
if(debugDeadServerAccess)
ModernFix.LOGGER.warn("Async loading of a chunk was requested, this might not be desirable", new Exception());
try {
resultingChunk = future.get(10, TimeUnit.SECONDS);
if(resultingChunk.isSuccess()) {
cir.setReturnValue(resultingChunk.orElse(null));
return;
}
} catch(InterruptedException | ExecutionException | TimeoutException e) {
ModernFix.LOGGER.error("Async chunk load took way too long, this needs to be reported to the appropriate mod.", e);
}
//cir.setReturnValue(new EmptyLevelChunk(this.level, new ChunkPos(chunkX, chunkZ)));
}
}
}
}

View File

@ -1,28 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.cache_model_materials;
import net.minecraft.client.renderer.block.model.multipart.MultiPart;
import net.minecraft.resources.ResourceLocation;
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.Collection;
@Mixin(MultiPart.class)
@ClientOnlyMixin
public class MultipartMixin {
private Collection<ResourceLocation> dependencyCache = null;
@Inject(method = "getDependencies", at = @At("HEAD"), cancellable = true)
private void useDependencyCache(CallbackInfoReturnable<Collection<ResourceLocation>> cir) {
if(dependencyCache != null)
cir.setReturnValue(dependencyCache);
}
@Inject(method = "getDependencies", at = @At("RETURN"))
private void storeDependencyCache(CallbackInfoReturnable<Collection<ResourceLocation>> cir) {
if(dependencyCache == null)
dependencyCache = cir.getReturnValue();
}
}

View File

@ -0,0 +1,50 @@
package org.embeddedt.modernfix.common.mixin.perf.compact_mojang_registries;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.util.datafix.fixes.BlockStateData;
import org.spongepowered.asm.mixin.Mixin;
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.CallbackInfo;
import java.util.Map;
@Mixin(value = BlockStateData.class, priority = 2000)
public class BlockStateDataMixin {
@Unique
private static ObjectOpenHashSet<Tag> TAG_INTERNER;
/**
* @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) {
TAG_INTERNER = new ObjectOpenHashSet<>();
}
Map.Entry<String, Tag>[] entries = new Map.Entry[tag.size()];
int i = 0;
for (var key : tag.getAllKeys()) {
Tag t = tag.get(key);
if (t instanceof CompoundTag ct) {
t = compactTag(ct);
}
t = TAG_INTERNER.addOrGet(t);
entries[i++] = Map.entry(key, t);
}
return new CompoundTag(Map.ofEntries(entries));
}
@Inject(method = "<clinit>", at = @At("RETURN"))
private static void clearInterner(CallbackInfo ci) {
if (TAG_INTERNER != null) {
TAG_INTERNER.clear();
TAG_INTERNER.trim();
}
}
}

View File

@ -1,15 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.deduplicate_climate_parameters;
import net.minecraft.world.level.biome.Climate;
import org.embeddedt.modernfix.dedup.ClimateCache;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin({ Climate.Parameter.class, Climate.ParameterPoint.class })
public class ParameterMixin {
@Redirect(method = "*", at = @At(value = "NEW", target = "net/minecraft/world/level/biome/Climate$Parameter"), require = 0)
private static Climate.Parameter internParameterStatic(long min, long max) {
return ClimateCache.MFIX_INTERNER.intern(new Climate.Parameter(min, max));
}
}

View File

@ -1,30 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.deduplicate_location;
import net.minecraft.resources.ResourceLocation;
import org.embeddedt.modernfix.dedup.IdentifierCaches;
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;
@Mixin(ResourceLocation.class)
public class MixinResourceLocation {
@Mutable
@Shadow
@Final
private String namespace;
@Mutable
@Shadow
@Final
private String path;
@Inject(method = "<init>", at = @At("RETURN"))
private void reinit(String string, String string2, CallbackInfo ci) {
this.namespace = IdentifierCaches.NAMESPACES.deduplicate(this.namespace);
this.path = IdentifierCaches.PATH.deduplicate(this.path);
}
}

View File

@ -1,38 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_entity_renderers;
import net.minecraft.client.renderer.entity.EntityRenderDispatcher;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.entity.EntityRendererMap;
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.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Map;
@Mixin(EntityRenderDispatcher.class)
@ClientOnlyMixin
public class EntityRenderDispatcherMixin {
@Shadow private Map<EntityType<?>, EntityRenderer<?>> renderers;
private EntityRendererMap mfix$dynamicRenderers;
@Inject(method = "getRenderer", at = @At("RETURN"), cancellable = true)
private <T extends Entity> void checkNullness(T entity, CallbackInfoReturnable<EntityRenderer<? super T>> cir) {
// apparently some mods yeet the renderers map and cause issues
if(cir.getReturnValue() == null)
cir.setReturnValue((EntityRenderer<? super T>)mfix$dynamicRenderers.get(entity.getType()));
}
@Redirect(method = "onResourceManagerReload", at = @At(value = "FIELD", opcode = Opcodes.PUTFIELD, target = "Lnet/minecraft/client/renderer/entity/EntityRenderDispatcher;renderers:Ljava/util/Map;"))
private void setRendererField(EntityRenderDispatcher instance, Map<EntityType<?>, EntityRenderer<?>> incomingMap) {
this.renderers = incomingMap;
this.mfix$dynamicRenderers = (EntityRendererMap)incomingMap;
}
}

View File

@ -1,29 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_entity_renderers;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.entity.EntityRenderers;
import net.minecraft.world.entity.EntityType;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.entity.EntityRendererMap;
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.CallbackInfoReturnable;
import java.util.Map;
@Mixin(EntityRenderers.class)
@ClientOnlyMixin
public class EntityRenderersMixin {
@Shadow @Final private static Map<EntityType<?>, EntityRendererProvider<?>> PROVIDERS;
@Inject(method = "createEntityRenderers", at = @At("HEAD"), cancellable = true)
private static void createDynamicRendererLoader(EntityRendererProvider.Context context, CallbackInfoReturnable<Map<EntityType<?>, EntityRenderer<?>>> cir) {
cir.setReturnValue(new EntityRendererMap(PROVIDERS, context));
ModernFix.LOGGER.info("Dynamic entity renderer hook setup");
}
}

View File

@ -1,25 +1,22 @@
package org.embeddedt.modernfix.common.mixin.perf.model_optimizations;
import net.minecraft.world.level.block.state.properties.Property;
import org.embeddedt.modernfix.dedup.IdentifierCaches;
import org.spongepowered.asm.mixin.*;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
@Mixin(Property.class)
public class PropertyMixin {
@Shadow @Mutable
@Final private String name;
@Shadow private Integer hashCode;
@Shadow @Final private String name;
@Shadow @Final private Class clazz;
@Redirect(method = "<init>", at = @At(value = "FIELD", target = "Lnet/minecraft/world/level/block/state/properties/Property;name:Ljava/lang/String;"))
private void internName(Property instance, String name) {
this.name = IdentifierCaches.PROPERTY.deduplicate(name);
@ModifyVariable(method = "<init>", at = @At("HEAD"), ordinal = 0, argsOnly = true)
private static String internName(String name) {
return name.intern();
}
/**
* @author embeddedt
* @reason compare hashcodes if generated, use reference equality for speed
@ -32,7 +29,8 @@ public class PropertyMixin {
return false;
} else {
Property<?> property = (Property)p_equals_1_;
/* reference equality is safe here because of deduplication */
/* reference equality is safe here because of interning above */
//noinspection StringEquality
return this.clazz == property.getValueClass() && this.name == property.getName();
}
}

View File

@ -1,28 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.mojang_registry_size;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
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.Map;
@Mixin(ResourceKey.class)
public class ResourceKeyMixin<T> {
private static final Map<ResourceLocation, Map<ResourceLocation, ResourceKey<?>>> INTERNING_MAP = new Object2ObjectOpenHashMap<>();
@Inject(method = "create(Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/resources/ResourceLocation;)Lnet/minecraft/resources/ResourceKey;", at = @At("HEAD"), cancellable = true)
private static <T> void createEfficient(ResourceLocation parent, ResourceLocation location, CallbackInfoReturnable<ResourceKey<T>> cir) {
synchronized (ResourceKey.class) {
Map<ResourceLocation, ResourceKey<?>> keys = INTERNING_MAP.computeIfAbsent(parent, k -> new Object2ObjectOpenHashMap<>());
ResourceKey<?> key = keys.get(location);
if(key == null) {
key = new ResourceKey<>(parent, location);
keys.put(location, key);
}
cir.setReturnValue((ResourceKey<T>)key);
}
}
}

View File

@ -16,7 +16,7 @@ import java.util.function.Consumer;
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.rebuildParallel(true);
BlockStateCacheHandler.invalidateCache();
return o -> {};
}

View File

@ -1,9 +0,0 @@
package org.embeddedt.modernfix.dedup;
import com.google.common.collect.Interner;
import com.google.common.collect.Interners;
import net.minecraft.world.level.biome.Climate;
public class ClimateCache {
public static final Interner<Climate.Parameter> MFIX_INTERNER = Interners.newStrongInterner();
}

View File

@ -1,56 +0,0 @@
package org.embeddedt.modernfix.dedup;
import it.unimi.dsi.fastutil.Hash;
import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet;
import java.util.Objects;
public class DeduplicationCache<T> {
private final ObjectOpenCustomHashSet<T> pool;
private int attemptedInsertions = 0;
private int deduplicated = 0;
public DeduplicationCache(Hash.Strategy<T> strategy) {
this.pool = new ObjectOpenCustomHashSet<>(strategy);
}
public DeduplicationCache() {
this.pool = new ObjectOpenCustomHashSet<>(new Hash.Strategy<T>() {
@Override
public int hashCode(T o) {
return Objects.hashCode(o);
}
@Override
public boolean equals(T a, T b) {
return Objects.equals(a, b);
}
});
}
public synchronized T deduplicate(T item) {
this.attemptedInsertions++;
T result = this.pool.addOrGet(item);
if (result != item) {
this.deduplicated++;
}
return result;
}
public synchronized void clearCache() {
this.attemptedInsertions = 0;
this.deduplicated = 0;
this.pool.clear();
}
@Override
public synchronized String toString() {
return String.format("DeduplicationCache ( %d/%d de-duplicated, %d pooled )",
this.deduplicated, this.attemptedInsertions, this.pool.size());
}
}

View File

@ -1,16 +0,0 @@
package org.embeddedt.modernfix.dedup;
import org.embeddedt.modernfix.ModernFix;
public class IdentifierCaches {
public static final DeduplicationCache<String> NAMESPACES = new DeduplicationCache<>();
public static final DeduplicationCache<String> PATH = new DeduplicationCache<>();
public static final DeduplicationCache<String> PROPERTY = new DeduplicationCache<>();
public static void printDebug() {
ModernFix.LOGGER.info("[[[ Identifier de-duplication statistics ]]]");
ModernFix.LOGGER.info("Namespace cache: {}", NAMESPACES);
ModernFix.LOGGER.info("Path cache: {}", PATH);
}
}

View File

@ -1,5 +0,0 @@
package org.embeddedt.modernfix.duck;
public interface ICachedMaterialsModel {
public void clearMaterialsCache();
}

View File

@ -1,7 +0,0 @@
package org.embeddedt.modernfix.duck;
import net.minecraft.world.level.storage.LevelStorageSource;
public interface ILevelSave {
public void runWorldPersistenceHooks(LevelStorageSource format);
}

View File

@ -1,7 +0,0 @@
package org.embeddedt.modernfix.duck;
import org.embeddedt.modernfix.chunk.SafeBlockGetter;
public interface ISafeBlockGetter {
SafeBlockGetter mfix$getSafeBlockGetter();
}

View File

@ -1,10 +0,0 @@
package org.embeddedt.modernfix.duck.reuse_datapacks;
import net.minecraft.server.ReloadableServerResources;
import java.util.Collection;
public interface ICachingResourceClient {
void setCachedResources(ReloadableServerResources r);
void setCachedDataPackConfig(Collection<String> c);
}

View File

@ -1,8 +0,0 @@
package org.embeddedt.modernfix.dynamicresources;
import net.minecraft.client.renderer.block.model.BlockFaceUV;
public class UVController {
public static final ThreadLocal<Boolean> useDummyUv = ThreadLocal.withInitial(() -> Boolean.FALSE);
public static final BlockFaceUV dummyUv = new BlockFaceUV(new float[4], 0);
}

View File

@ -1,185 +0,0 @@
package org.embeddedt.modernfix.entity;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Iterators;
import com.google.common.collect.Maps;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.entity.EntityRenderers;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.entity.EntityType;
import org.embeddedt.modernfix.ModernFix;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutionException;
@SuppressWarnings("OptionalAssignedToNull")
public class EntityRendererMap implements Map<EntityType<?>, EntityRenderer<?>> {
private final Map<EntityType<?>, EntityRendererProvider<?>> rendererProviders;
private final LoadingCache<EntityType<?>, Optional<EntityRenderer<?>>> rendererMap;
private final EntityRendererProvider.Context context;
public EntityRendererMap(Map<EntityType<?>, EntityRendererProvider<?>> rendererProviders, EntityRendererProvider.Context context) {
this.rendererProviders = rendererProviders;
this.context = context;
this.rendererMap = CacheBuilder.newBuilder().build(new RenderConstructor());
}
class RenderConstructor extends CacheLoader<EntityType<?>, Optional<EntityRenderer<?>>> {
@Override
public Optional<EntityRenderer<?>> load(EntityType<?> key) throws Exception {
EntityRendererProvider<?> provider = rendererProviders.get(key);
if(provider == null)
return Optional.empty();
synchronized(EntityRenderers.class) {
EntityRenderer<?> renderer;
try {
renderer = provider.create(context);
ModernFix.LOGGER.info("Loaded entity {}", BuiltInRegistries.ENTITY_TYPE.getKey(key));
} catch(RuntimeException e) {
ModernFix.LOGGER.error("Failed to create entity model for " + BuiltInRegistries.ENTITY_TYPE.getKey(key) + ":", e);
renderer = new ErroredEntityRenderer<>(context);
}
return Optional.ofNullable(renderer);
}
}
}
@Override
public int size() {
return rendererProviders.size();
}
@Override
public boolean isEmpty() {
return rendererProviders.isEmpty();
}
@Override
public boolean containsKey(Object o) {
return rendererProviders.containsKey(o);
}
@Override
public boolean containsValue(Object o) {
return false;
}
@Override
public EntityRenderer<?> get(Object o) {
try {
Optional<EntityRenderer<?>> renderer = rendererMap.get((EntityType<?>)o);
return renderer.orElse(null);
} catch (IllegalStateException e) {
return null; /* emulate value not being present if recursive load occurs */
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
@Nullable
@Override
public EntityRenderer<?> put(EntityType<?> entityType, EntityRenderer<?> entityRenderer) {
Optional<EntityRenderer<?>> old = rendererMap.getIfPresent(entityType);
rendererMap.put(entityType, Optional.ofNullable(entityRenderer));
return old != null ? old.orElse(null) : null;
}
@Override
public EntityRenderer<?> remove(Object o) {
Optional<EntityRenderer<?>> old = rendererMap.getIfPresent(o);
rendererMap.invalidate(o);
return old != null ? old.orElse(null) : null;
}
@Override
public void putAll(@NotNull Map<? extends EntityType<?>, ? extends EntityRenderer<?>> map) {
rendererMap.putAll(Maps.transformValues(map, Optional::ofNullable));
}
@Override
public void clear() {
rendererMap.invalidateAll();
}
@NotNull
@Override
public Set<EntityType<?>> keySet() {
return rendererProviders.keySet();
}
@NotNull
@Override
public Collection<EntityRenderer<?>> values() {
return new AbstractCollection<>() {
@Override
public Iterator<EntityRenderer<?>> iterator() {
return Iterators.transform(Iterators.unmodifiableIterator(rendererProviders.keySet().iterator()), EntityRendererMap.this::get);
}
@Override
public int size() {
return rendererProviders.size();
}
};
}
private class Entry implements Map.Entry<EntityType<?>, EntityRenderer<?>> {
private final EntityType<?> key;
private Entry(EntityType<?> key) {
this.key = key;
}
@Override
public EntityType<?> getKey() {
return key;
}
@Override
public EntityRenderer<?> getValue() {
return get(key);
}
@Override
public EntityRenderer<?> setValue(EntityRenderer<?> value) {
return put(key, value);
}
}
@NotNull
@Override
public Set<Map.Entry<EntityType<?>, EntityRenderer<?>>> entrySet() {
return new AbstractSet<>() {
@Override
public Iterator<Map.Entry<EntityType<?>, EntityRenderer<?>>> iterator() {
return Iterators.transform(Iterators.unmodifiableIterator(rendererProviders.keySet().iterator()), Entry::new);
}
@Override
public boolean contains(Object o) {
if (o instanceof Map.Entry<?,?> e) {
return rendererProviders.containsKey(e.getKey()) && Objects.equals(get(e.getKey()), e.getValue());
} else {
return false;
}
}
@Override
public int size() {
return rendererProviders.size();
}
};
}
}

View File

@ -1,30 +0,0 @@
package org.embeddedt.modernfix.entity;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.culling.Frustum;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.texture.TextureAtlas;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.Entity;
public class ErroredEntityRenderer<T extends Entity> extends EntityRenderer<T> {
public ErroredEntityRenderer(EntityRendererProvider.Context arg) {
super(arg);
}
@Override
public ResourceLocation getTextureLocation(T entity) {
return TextureAtlas.LOCATION_BLOCKS;
}
@Override
public boolean shouldRender(T livingEntity, Frustum camera, double camX, double camY, double camZ) {
return false;
}
@Override
public void render(T entity, float entityYaw, float partialTick, PoseStack poseStack, MultiBufferSource buffer, int packedLight) {
}
}

View File

@ -1,15 +0,0 @@
package org.embeddedt.modernfix.sound;
import com.mojang.blaze3d.audio.SoundBuffer;
import net.minecraft.resources.ResourceLocation;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
public class SoundBufferCache extends LinkedHashMap<ResourceLocation, CompletableFuture<SoundBuffer>> {
@Override
protected boolean removeEldestEntry(Map.Entry<ResourceLocation, CompletableFuture<SoundBuffer>> eldest) {
return super.removeEldestEntry(eldest);
}
}

View File

@ -1,47 +0,0 @@
package org.embeddedt.modernfix.tickables;
import org.jetbrains.annotations.Nullable;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class LoadableTickableObject<T> implements TickableObject {
private volatile int ticksInactive = 0;
private final int timeout;
private final Supplier<T> loader;
private final Consumer<T> finalizer;
private volatile T theObject = null;
public LoadableTickableObject(int timeout, Supplier<T> loader, Consumer<T> finalizer) {
this(timeout, loader, finalizer, null);
}
public LoadableTickableObject(int timeout, Supplier<T> loader, Consumer<T> finalizer, @Nullable T initialValue) {
this.timeout = timeout;
this.loader = loader;
this.finalizer = finalizer;
this.theObject = initialValue;
}
public T get() {
synchronized (this) {
ticksInactive++;
T obj = theObject;
if(obj == null) {
obj = loader.get();
theObject = obj;
}
return obj;
}
}
public final void tick() {
synchronized (this) {
ticksInactive++;
if(ticksInactive >= this.timeout) {
finalizer.accept(theObject);
theObject = null;
}
}
}
}

View File

@ -1,5 +0,0 @@
package org.embeddedt.modernfix.tickables;
public interface TickableObject {
void tick();
}

View File

@ -1,18 +0,0 @@
package org.embeddedt.modernfix.tickables;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class TickableObjectManager {
private static final List<TickableObject> TICKABLE_OBJECT_LIST = new CopyOnWriteArrayList<>();
public static void register(TickableObject object) {
TICKABLE_OBJECT_LIST.add(object);
}
public static void runTick() {
for(TickableObject o : TICKABLE_OBJECT_LIST) {
o.tick();
}
}
}

View File

@ -1,48 +0,0 @@
package org.embeddedt.modernfix.util;
import com.google.common.base.Function;
import com.google.common.collect.Interner;
import com.google.common.collect.Interners;
import com.google.common.collect.Maps;
import java.util.HashMap;
import java.util.Map;
/**
* Replacement backing map for CompoundTags that interns keys.
*/
public class CanonizingStringMap<T> extends HashMap<String, T> {
private static final Interner<String> KEY_INTERNER = Interners.newWeakInterner();
private static String intern(String key) {
return key != null ? KEY_INTERNER.intern(key) : null;
}
public CanonizingStringMap() {
super();
}
@Override
public T put(String key, T value) {
return super.put(intern(key), value);
}
@Override
public void putAll(Map<? extends String, ? extends T> m) {
if(m.isEmpty())
return;
HashMap<String, T> tmp = new HashMap<>();
m.forEach((k, v) -> tmp.put(intern(k), v));
super.putAll(tmp);
}
private void putAllWithoutInterning(Map<? extends String, ? extends T> m) {
super.putAll(m);
}
public static <T> CanonizingStringMap<T> deepCopy(CanonizingStringMap<T> incomingMap, Function<T, T> deepCopier) {
CanonizingStringMap<T> newMap = new CanonizingStringMap<>();
newMap.putAllWithoutInterning(Maps.transformValues(incomingMap, deepCopier));
return newMap;
}
}

View File

@ -1,162 +0,0 @@
package org.embeddedt.modernfix.util;
import com.google.common.collect.ImmutableSet;
import com.mojang.serialization.Lifecycle;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.level.*;
import net.minecraft.core.RegistryAccess;
import net.minecraft.world.Difficulty;
import net.minecraft.world.level.dimension.end.EndDragonFight;
import net.minecraft.world.level.levelgen.WorldOptions;
import net.minecraft.world.level.storage.WorldData;
import net.minecraft.world.level.storage.ServerLevelData;
import java.util.Collections;
import java.util.Set;
public class DummyServerConfiguration implements WorldData {
@Override
public WorldDataConfiguration getDataConfiguration() {
return null;
}
@Override
public void setDataConfiguration(WorldDataConfiguration arg) {
}
@Override
public Set<String> getRemovedFeatureFlags() {
return Collections.emptySet();
}
@Override
public boolean wasModded() {
return true;
}
@Override
public Set<String> getKnownServerBrands() {
return ImmutableSet.of("forge");
}
@Override
public void setModdedInfo(String name, boolean isModded) {
}
@Override
public CompoundTag getCustomBossEvents() {
return null;
}
@Override
public void setCustomBossEvents(CompoundTag nbt) {
}
@Override
public ServerLevelData overworldData() {
return null;
}
@Override
public LevelSettings getLevelSettings() {
return null;
}
@Override
public CompoundTag createTag(RegistryAccess registries, CompoundTag hostPlayerNBT) {
return null;
}
@Override
public boolean isHardcore() {
return false;
}
@Override
public int getVersion() {
return 0;
}
@Override
public String getLevelName() {
return null;
}
@Override
public GameType getGameType() {
return null;
}
@Override
public void setGameType(GameType type) {
}
@Override
public boolean isAllowCommands() {
return false;
}
@Override
public Difficulty getDifficulty() {
return null;
}
@Override
public void setDifficulty(Difficulty difficulty) {
}
@Override
public boolean isDifficultyLocked() {
return false;
}
@Override
public void setDifficultyLocked(boolean locked) {
}
@Override
public GameRules getGameRules() {
return null;
}
@Override
public CompoundTag getLoadedPlayerTag() {
return null;
}
@Override
public EndDragonFight.Data endDragonFightData() {
return EndDragonFight.Data.DEFAULT;
}
@Override
public void setEndDragonFightData(EndDragonFight.Data data) {
}
@Override
public WorldOptions worldGenOptions() {
return null;
}
@Override
public boolean isFlatWorld() {
return false;
}
@Override
public boolean isDebugWorld() {
return false;
}
@Override
public Lifecycle worldGenSettingsLifecycle() {
return Lifecycle.stable();
}
}

View File

@ -1,132 +0,0 @@
package org.embeddedt.modernfix.util;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* Simple forwarding map implementation that allows layering multiple maps together.
*/
public class LayeredForwardingMap<K, V> implements Map<K, V> {
private final Map<K, V>[] layers;
public LayeredForwardingMap(Map<K, V>[] layers) {
if(layers.length < 1)
throw new IllegalArgumentException();
for(Map<K, V> layer : layers) {
if(layer == null)
throw new IllegalArgumentException();
}
this.layers = layers;
}
@Override
public int size() {
return 1;
}
@Override
public boolean isEmpty() {
for(Map<K, V> map : layers) {
if(!map.isEmpty())
return false;
}
return true;
}
@Override
public boolean containsKey(Object key) {
for(Map<K, V> map : layers) {
if(map.containsKey(key))
return true;
}
return false;
}
@Override
public boolean containsValue(Object value) {
for(Map<K, V> map : layers) {
if(map.containsValue(value))
return true;
}
return false;
}
@Override
public V get(Object key) {
for(Map<K, V> map : layers) {
V value = map.get(key);
if(value != null)
return value;
}
return null;
}
@Nullable
@Override
public V put(K key, V value) {
if(value == null)
throw new IllegalArgumentException();
V originalValue = null;
for(Map<K, V> map : layers) {
V oldVal = map.remove(key);
if(originalValue == null)
originalValue = oldVal;
map.put(key, value);
}
return originalValue;
}
@Override
public V remove(Object key) {
for(Map<K, V> map : layers) {
map.remove(key);
}
return null;
}
@Override
public void putAll(@NotNull Map<? extends K, ? extends V> m) {
for(V value : m.values()) {
if(value == null)
throw new IllegalArgumentException();
}
for(Map<K, V> map : layers) {
map.putAll(m);
}
}
@Override
public void clear() {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public Set<K> keySet() {
Set<K> keys = new ObjectOpenHashSet<>();
for(Map<K, V> map : layers) {
keys.addAll(map.keySet());
}
return Collections.unmodifiableSet(keys);
}
@NotNull
@Override
public Collection<V> values() {
Set<K> keys = keySet();
List<V> vals = new ArrayList<>();
for(K key : keys) {
vals.add(get(key));
}
return vals;
}
@NotNull
@Override
public Set<Entry<K, V>> entrySet() {
throw new UnsupportedOperationException();
}
}

View File

@ -1,26 +0,0 @@
package org.embeddedt.modernfix.neoforge.classloading;
/**
* Sometimes mods have classes that circularly reference each other. If each of these classes ends up being loaded
* from two mods, a deadlock occurs.
*
* To avoid this problem we maintain a list of classes that should be loaded early and do it via Class.forName.
*/
public class ClassLoadHack {
private static final String[] classesToLoadEarly = new String[] {
"team.creative.creativecore.common.config.ConfigTypeConveration",
"team.creative.creativecore.common.util.ingredient.CreativeIngredient"
};
public static void loadModClasses() {
for(String clzName : classesToLoadEarly) {
try {
Class.forName(clzName);
} catch(Throwable e) {
if(!(e instanceof ClassNotFoundException)) {
e.printStackTrace();
}
}
}
}
}