Merge remote-tracking branch 'origin/1.19.2' into 1.19.4

This commit is contained in:
embeddedt 2023-04-22 11:49:43 -04:00
commit 23ee5cf982
No known key found for this signature in database
GPG Key ID: A69433EC199B5613
27 changed files with 745 additions and 57 deletions

View File

@ -108,9 +108,11 @@ tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
def targetVersion = 17
/*
if (JavaVersion.current().isJava9Compatible()) {
options.release = targetVersion
}
*/
}
java {

View File

@ -1,26 +1,37 @@
package org.embeddedt.modernfix;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ServerLevel;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.server.ServerStartedEvent;
import net.minecraftforge.event.server.ServerStoppedEvent;
import net.minecraftforge.eventbus.api.EventPriority;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.*;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.config.ModConfig;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.event.lifecycle.FMLLoadCompleteEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.fml.loading.FMLConfig;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.fml.util.ObfuscationReflectionHelper;
import net.minecraftforge.network.NetworkConstants;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.embeddedt.modernfix.classloading.ModFileScanDataDeduplicator;
import org.embeddedt.modernfix.core.config.ModernFixConfig;
import org.embeddedt.modernfix.entity.EntityDataIDSyncHandler;
import org.embeddedt.modernfix.packet.PacketHandler;
import org.embeddedt.modernfix.registry.ObjectHolderClearer;
import org.embeddedt.modernfix.util.ClassInfoManager;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Field;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
@ -66,12 +77,14 @@ public class ModernFix {
// Register ourselves for server and other game events we are interested in
MinecraftForge.EVENT_BUS.register(this);
FMLJavaModLoadingContext.get().getModEventBus().addListener(this::commonSetup);
FMLJavaModLoadingContext.get().getModEventBus().addListener(this::onLoadComplete);
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> MinecraftForge.EVENT_BUS.register(new ModernFixClient()));
ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class, () -> new IExtensionPoint.DisplayTest(() -> NetworkConstants.IGNORESERVERONLY, (a, b) -> true));
ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, ModernFixConfig.COMMON_CONFIG);
MinecraftForge.EVENT_BUS.register(EntityDataIDSyncHandler.class);
PacketHandler.register();
ModFileScanDataDeduplicator.deduplicate();
}
private static boolean dfuModPresent() {
@ -91,6 +104,7 @@ public class ModernFix {
ModLoader.get().addWarning(new ModLoadingWarning(ModLoadingContext.get().getActiveContainer().getModInfo(), ModLoadingStage.COMMON_SETUP, "modernfix.no_lazydfu"));
});
}
ObjectHolderClearer.clearThrowables();
}
@SubscribeEvent
@ -99,5 +113,32 @@ public class ModernFix {
float gameStartTime = ManagementFactory.getRuntimeMXBean().getUptime() / 1000f;
ModernFix.LOGGER.warn("Dedicated server took " + gameStartTime + " seconds to load");
}
ClassInfoManager.clear();
}
@SubscribeEvent(priority = EventPriority.LOWEST)
public void onLoadComplete(FMLLoadCompleteEvent event) {
ClassInfoManager.clear();
}
@SubscribeEvent(priority = EventPriority.LOWEST)
public void onServerDead(ServerStoppedEvent event) {
/* Clear as much data from the integrated server as possible, in case a mod holds on to it */
try {
Field updatingMapField = ObfuscationReflectionHelper.findField(ChunkMap.class, "f_140129_");
Field visibleMapField = ObfuscationReflectionHelper.findField(ChunkMap.class, "f_140130_");
Field pendingUnloadsField = ObfuscationReflectionHelper.findField(ChunkMap.class, "f_140131_");
for(ServerLevel level : event.getServer().getAllLevels()) {
ChunkMap chunkMap = level.getChunkSource().chunkMap;
Long2ObjectMap<ChunkHolder> map = (Long2ObjectMap<ChunkHolder>)updatingMapField.get(chunkMap);
map.clear();
map = (Long2ObjectMap<ChunkHolder>)visibleMapField.get(chunkMap);
map.clear();
map = (Long2ObjectMap<ChunkHolder>)pendingUnloadsField.get(chunkMap);
map.clear();
}
} catch(RuntimeException | IllegalAccessException e) {
ModernFix.LOGGER.error("Couldn't clear chunk data", e);
}
}
}

View File

@ -0,0 +1,89 @@
package org.embeddedt.modernfix.classloading;
import com.google.common.collect.Interner;
import com.google.common.collect.Interners;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.forgespi.language.ModFileScanData;
import net.minecraftforge.forgespi.locating.IModFile;
import org.objectweb.asm.Type;
import java.lang.reflect.Field;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
public class ModFileScanDataDeduplicator {
private final Interner<Type> typeInterner = Interners.newStrongInterner();
private final Function<Type, Type> internerFn = type -> type != null ? typeInterner.intern(type) : null;
private static Field classClazzField, parentField, interfacesField, annotationClazzField, annotationTypeField;
private static final boolean reflectionSuccessful;
static {
boolean success = false;
try {
classClazzField = ModFileScanData.ClassData.class.getDeclaredField("clazz");
classClazzField.setAccessible(true);
parentField = ModFileScanData.ClassData.class.getDeclaredField("parent");
parentField.setAccessible(true);
interfacesField = ModFileScanData.ClassData.class.getDeclaredField("interfaces");
interfacesField.setAccessible(true);
annotationClazzField = ModFileScanData.AnnotationData.class.getDeclaredField("clazz");
annotationClazzField.setAccessible(true);
annotationTypeField = ModFileScanData.AnnotationData.class.getDeclaredField("annotationType");
annotationTypeField.setAccessible(true);
success = true;
} catch(ReflectiveOperationException | RuntimeException e) {
}
reflectionSuccessful = success;
}
ModFileScanDataDeduplicator() {
}
private void runDeduplication() {
ModList.get().forEachModFile(this::deduplicateFile);
}
private void deduplicateFile(IModFile file) {
ModFileScanData data = file.getScanResult();
if(data != null) {
data.getClasses().forEach(this::deduplicateClass);
data.getAnnotations().forEach(this::deduplicateAnnotation);
}
}
private void deduplicateClass(ModFileScanData.ClassData data) {
try {
Type type = (Type)classClazzField.get(data);
type = internerFn.apply(type);
classClazzField.set(data, type);
type = (Type)parentField.get(data);
type = internerFn.apply(type);
parentField.set(data, type);
Set<Type> types = (Set<Type>)interfacesField.get(data);
types = types.stream().map(internerFn).collect(Collectors.toSet());
interfacesField.set(data, types);
} catch(ReflectiveOperationException e) {
}
}
private void deduplicateAnnotation(ModFileScanData.AnnotationData data) {
try {
Type type = (Type)annotationClazzField.get(data);
type = internerFn.apply(type);
annotationClazzField.set(data, type);
type = (Type)annotationTypeField.get(data);
type = internerFn.apply(type);
annotationTypeField.set(data, type);
} catch(ReflectiveOperationException e) {
}
}
public static void deduplicate() {
if(!reflectionSuccessful)
return;
new ModFileScanDataDeduplicator().runDeduplication();
}
}

View File

@ -7,6 +7,7 @@ import org.apache.logging.log4j.Logger;
import org.embeddedt.modernfix.classloading.FastAccessTransformerList;
import org.embeddedt.modernfix.core.config.ModernFixEarlyConfig;
import org.embeddedt.modernfix.core.config.Option;
import org.embeddedt.modernfix.dfu.DFUBlaster;
import org.embeddedt.modernfix.load.ModWorkManagerQueue;
import org.embeddedt.modernfix.util.DummyList;
import org.objectweb.asm.Opcodes;
@ -42,6 +43,7 @@ public class ModernFixMixinPlugin implements IMixinConfigPlugin {
FastAccessTransformerList.attemptReplace();
ModWorkManagerQueue.replace();
DFUBlaster.blastMaps();
/* https://github.com/FabricMC/Mixin/pull/99 */
try {

View File

@ -37,10 +37,13 @@ public class ModernFixEarlyConfig {
/* Use a simpler ArrayMap if FerriteCore is using the map intelligently anyway */
this.addMixinRule("perf.state_definition_construct", modPresent("ferritecore"));
this.addMixinRule("perf.cache_strongholds", true);
this.addMixinRule("perf.dedup_blockstate_flattening_map", false);
this.addMixinRule("perf.clear_mixin_classinfo", false);
this.addMixinRule("perf.cache_upgraded_structures", true);
this.addMixinRule("perf.compress_blockstate", false);
this.addMixinRule("bugfix.concurrency", true);
this.addMixinRule("bugfix.edge_chunk_not_saved", true);
this.addMixinRule("perf.dynamic_structure_manager", true);
this.addMixinRule("bugfix.chunk_deadlock", true);
this.addMixinRule("perf.thread_priorities", true);
this.addMixinRule("perf.scan_cache", true);
@ -65,14 +68,17 @@ public class ModernFixEarlyConfig {
disableIfModPresent("mixin.perf.async_jei", "modernui");
disableIfModPresent("mixin.perf.compress_biome_container", "chocolate", "betterendforge");
disableIfModPresent("mixin.bugfix.mc218112", "performant");
disableIfModPresent("mixin.perf.faster_baking", "touhou_little_maid");
disableIfModPresent("mixin.perf.reuse_datapacks", "tac");
}
private void disableIfModPresent(String configName, String... ids) {
for(String id : ids) {
if(FMLLoader.getLoadingModList().getModFileById(id) != null) {
this.options.get(configName).addModOverride(false, id);
Option option = this.options.get(configName);
if(option != null)
option.addModOverride(false, id);
else
LOGGER.warn("Can't disable missing option {}", configName);
}
}
}

View File

@ -0,0 +1,49 @@
package org.embeddedt.modernfix.dfu;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.mojang.datafixers.RewriteResult;
import com.mojang.datafixers.TypeRewriteRule;
import com.mojang.datafixers.functions.PointFreeRule;
import com.mojang.datafixers.types.Type;
import com.mojang.datafixers.util.Pair;
import org.apache.commons.lang3.tuple.Triple;
import org.embeddedt.modernfix.ModernFix;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.IntFunction;
public class DFUBlaster {
public static void blastMaps() {
Cache<Pair<IntFunction<RewriteResult<?, ?>>, Integer>, RewriteResult<?, ?>> hmapApplyCache = CacheBuilder.newBuilder()
.maximumSize(200) /* should mean approximately 50MB used max */
.expireAfterAccess(3, TimeUnit.MINUTES)
.build();
Cache<Triple<Type<?>, TypeRewriteRule, PointFreeRule>, Optional<? extends RewriteResult<?, ?>>> rewriteCache = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterAccess(3, TimeUnit.MINUTES)
.build();
try {
Class<?> FOLD_CLASS = Class.forName("com.mojang.datafixers.functions.Fold");
Field hmapField = FOLD_CLASS.getDeclaredField("HMAP_APPLY_CACHE");
hmapField.setAccessible(true);
final Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe)theUnsafe.get(null);
Object base = unsafe.staticFieldBase(hmapField);
long offset = unsafe.staticFieldOffset(hmapField);
unsafe.putObject(base, offset, hmapApplyCache.asMap());
Field rewriteCacheField = Type.class.getDeclaredField("REWRITE_CACHE");
rewriteCacheField.setAccessible(true);
base = unsafe.staticFieldBase(rewriteCacheField);
offset = unsafe.staticFieldOffset(rewriteCacheField);
unsafe.putObject(base, offset, rewriteCache.asMap());
} catch(Throwable e) {
ModernFix.LOGGER.error("Could not replace DFU map", e);
}
}
}

View File

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

View File

@ -1,8 +0,0 @@
package org.embeddedt.modernfix.duck;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.resources.ResourceLocation;
public interface IDynamicModelBakery {
BakedModel bakeDefault(ResourceLocation modelLocation);
}

View File

@ -0,0 +1,14 @@
package org.embeddedt.modernfix.duck;
import com.google.common.collect.ImmutableList;
import net.minecraft.client.resources.model.BakedModel;
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.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
public interface IExtendedModelBakery {
ImmutableList<BlockState> getBlockStatesForMRL(StateDefinition<Block, BlockState> stateDefinition, ModelResourceLocation location);
BakedModel bakeDefault(ResourceLocation modelLocation);
}

View File

@ -2,13 +2,12 @@ package org.embeddedt.modernfix.dynamicresources;
import com.mojang.math.Transformation;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.BlockModelRotation;
import net.minecraft.client.resources.model.ModelBakery;
import net.minecraft.resources.ResourceLocation;
import org.apache.commons.lang3.tuple.Triple;
import org.embeddedt.modernfix.duck.IDynamicModelBakery;
import org.embeddedt.modernfix.duck.IExtendedModelBakery;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
@ -55,7 +54,7 @@ public class DynamicBakedModelProvider implements Map<ResourceLocation, BakedMod
@Override
public BakedModel get(Object o) {
BakedModel model = permanentOverrides.get(o);
return model != null ? model : ((IDynamicModelBakery)bakery).bakeDefault((ResourceLocation)o);
return model != null ? model : ((IExtendedModelBakery)bakery).bakeDefault((ResourceLocation)o);
}
@Nullable

View File

@ -0,0 +1,8 @@
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

@ -0,0 +1,108 @@
package org.embeddedt.modernfix.mixin.perf.cache_model_materials;
import com.mojang.datafixers.util.Either;
import net.minecraft.client.renderer.block.model.BlockModel;
import net.minecraft.client.resources.model.Material;
import org.embeddedt.modernfix.duck.ICachedMaterialsModel;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
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.Collection;
import java.util.Map;
import java.util.Set;
@Mixin(BlockModel.class)
public class BlockModelMixin {
@Shadow @Final @Mutable public Map<String, Either<Material, String>> textureMap;
/**
* @author embeddedt
* @reason detect changes to the texture map, and clear the material cache as needed
*/
@Inject(method = "<init>", at = @At("RETURN"))
private void useTrackingTextureMap(CallbackInfo ci) {
Map<String, Either<Material, String>> backingMap = this.textureMap;
ICachedMaterialsModel cacheHolder = (ICachedMaterialsModel)this;
this.textureMap = new Map<String, Either<Material, String>>() {
@Override
public int size() {
return backingMap.size();
}
@Override
public boolean isEmpty() {
return backingMap.isEmpty();
}
@Override
public boolean containsKey(Object o) {
return backingMap.containsKey(o);
}
@Override
public boolean containsValue(Object o) {
return backingMap.containsValue(o);
}
@Override
public Either<Material, String> get(Object o) {
return backingMap.get(o);
}
@Nullable
@Override
public Either<Material, String> put(String s, Either<Material, String> materialStringEither) {
Either<Material, String> old = backingMap.put(s, materialStringEither);
cacheHolder.clearMaterialsCache();
return old;
}
@Override
public Either<Material, String> remove(Object o) {
Either<Material, String> e = backingMap.remove(o);
cacheHolder.clearMaterialsCache();
return e;
}
@Override
public void putAll(@NotNull Map<? extends String, ? extends Either<Material, String>> map) {
backingMap.putAll(map);
cacheHolder.clearMaterialsCache();
}
@Override
public void clear() {
backingMap.clear();
cacheHolder.clearMaterialsCache();
}
@NotNull
@Override
public Set<String> keySet() {
cacheHolder.clearMaterialsCache();
return backingMap.keySet();
}
@NotNull
@Override
public Collection<Either<Material, String>> values() {
cacheHolder.clearMaterialsCache();
return backingMap.values();
}
@NotNull
@Override
public Set<Entry<String, Either<Material, String>>> entrySet() {
cacheHolder.clearMaterialsCache();
return backingMap.entrySet();
}
};
}
}

View File

@ -0,0 +1,21 @@
package org.embeddedt.modernfix.mixin.perf.dedup_blockstate_flattening_map;
import net.minecraft.util.datafix.fixes.BlockStateData;
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(BlockStateData.class)
public class BlockStateDataMixin {
@Inject(method = {"register", "finalizeMaps"}, at = @At("HEAD"), cancellable = true)
private static void noFlattening(CallbackInfo ci) {
ci.cancel();
}
@Inject(method = {"upgradeBlockStateTag", "upgradeBlock(I)Ljava/lang/String;", "upgradeBlock(Ljava/lang/String;)Ljava/lang/String;", "getTag"}, at = @At("HEAD"), require = 4)
private static void preventCorruption(CallbackInfoReturnable<?> cir) {
throw new UnsupportedOperationException("Performing the Flattening is currently disabled in the ModernFix config.");
}
}

View File

@ -0,0 +1,31 @@
package org.embeddedt.modernfix.mixin.perf.dedup_blockstate_flattening_map;
import com.mojang.serialization.Dynamic;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtOps;
import net.minecraft.util.datafix.fixes.ChunkPalettedStorageFix;
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.CallbackInfoReturnable;
import java.util.function.Consumer;
@Mixin(ChunkPalettedStorageFix.class)
public class ChunkPalettedStorageFixMixin {
@Redirect(method = "<clinit>", at = @At(value = "INVOKE", target = "Lcom/mojang/datafixers/DataFixUtils;make(Ljava/lang/Object;Ljava/util/function/Consumer;)Ljava/lang/Object;"))
private static Object skipMakingMap(Object o, Consumer<?> consumer) {
return o;
}
@Redirect(method = "<clinit>", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/datafix/fixes/BlockStateData;getTag(I)Lcom/mojang/serialization/Dynamic;"))
private static Dynamic<?> getFakeAirTag(int id) {
return new Dynamic<>(NbtOps.INSTANCE, new CompoundTag());
}
@Inject(method = "fix", at = @At("HEAD"))
private void skipFix(CallbackInfoReturnable<Dynamic<?>> cir) {
throw new UnsupportedOperationException("No Flattening for you.");
}
}

View File

@ -0,0 +1,21 @@
package org.embeddedt.modernfix.mixin.perf.dynamic_resources;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonElement;
import net.minecraft.client.renderer.block.model.BlockElementFace;
import org.embeddedt.modernfix.dynamicresources.UVController;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import java.lang.reflect.Type;
@Mixin(BlockElementFace.Deserializer.class)
public class BlockElementFaceDeserializerMixin {
@Redirect(method = "deserialize(Lcom/google/gson/JsonElement;Ljava/lang/reflect/Type;Lcom/google/gson/JsonDeserializationContext;)Lnet/minecraft/client/renderer/block/model/BlockElementFace;",
at = @At(value = "INVOKE", target = "Lcom/google/gson/JsonDeserializationContext;deserialize(Lcom/google/gson/JsonElement;Ljava/lang/reflect/Type;)Ljava/lang/Object;", ordinal = 0))
private Object skipUvsForInitialLoad(JsonDeserializationContext context, JsonElement element, Type type) {
return UVController.useDummyUv.get() ? UVController.dummyUv : context.deserialize(element, type);
}
}

View File

@ -7,21 +7,18 @@ import com.google.common.cache.RemovalNotification;
import com.google.common.collect.ImmutableList;
import com.mojang.math.Transformation;
import net.minecraft.client.renderer.block.model.BlockModel;
import net.minecraft.client.renderer.block.model.ItemModelGenerator;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.model.*;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
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 net.minecraft.world.level.block.state.properties.Property;
import net.minecraftforge.client.model.geometry.GeometryLoaderManager;
import org.apache.commons.lang3.tuple.Triple;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.duck.IDynamicModelBakery;
import org.embeddedt.modernfix.dynamicresources.DynamicBakedModelProvider;
import org.embeddedt.modernfix.duck.IExtendedModelBakery;
import org.slf4j.Logger;
import org.spongepowered.asm.mixin.*;
import org.spongepowered.asm.mixin.injection.At;
@ -39,7 +36,7 @@ import java.util.function.BiFunction;
/* high priority so that our injectors are added before other mods' */
@Mixin(value = ModelBakery.class, priority = 600)
public abstract class ModelBakeryMixin implements IDynamicModelBakery {
public abstract class ModelBakeryMixin implements IExtendedModelBakery {
private static final boolean debugDynamicModelLoading = Boolean.getBoolean("modernfix.debugDynamicModelLoading");
@ -268,8 +265,13 @@ public abstract class ModelBakeryMixin implements IDynamicModelBakery {
@Override
public BakedModel bakeDefault(ResourceLocation modelLocation) {
ModelBakery self = (ModelBakery)(Object)this;
ModelBakery self = (ModelBakery) (Object) this;
ModelBaker theBaker = self.new ModelBakerImpl(textureGetter, modelLocation);
return theBaker.bake(modelLocation, BlockModelRotation.X0_Y0);
}
@Override
public ImmutableList<BlockState> getBlockStatesForMRL(StateDefinition<Block, BlockState> stateDefinition, ModelResourceLocation location) {
return loadOnlyRelevantBlockState(stateDefinition, location);
}
}

View File

@ -1,5 +1,6 @@
package org.embeddedt.modernfix.mixin.perf.dynamic_resources.ctm;
import com.google.common.collect.ImmutableList;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.client.renderer.ItemBlockRenderTypes;
import net.minecraft.client.renderer.RenderType;
@ -9,13 +10,17 @@ import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.client.resources.model.MultiPartBakedModel;
import net.minecraft.client.resources.model.WeightedBakedModel;
import net.minecraft.core.Holder;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.client.ChunkRenderTypeSet;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.EventPriority;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.registries.ForgeRegistries;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.duck.IExtendedModelBakery;
import org.embeddedt.modernfix.dynamicresources.DynamicModelBakeEvent;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
@ -28,18 +33,22 @@ import team.chisel.ctm.client.model.AbstractCTMBakedModel;
import team.chisel.ctm.client.util.CTMPackReloadListener;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
@Mixin(CTMPackReloadListener.class)
public abstract class CTMPackReloadListenerMixin {
/* caches the original render checks */
@Shadow @Final private static Map<Holder.Reference<Block>, Predicate<RenderType>> blockRenderChecks;
private static Map<Holder.Reference<Block>, Predicate<RenderType>> renderCheckOverrides = new ConcurrentHashMap<>();
private static ChunkRenderTypeSet DEFAULT_TYPE_SET = ChunkRenderTypeSet.of(RenderType.solid());
@Shadow protected abstract Predicate<RenderType> getLayerCheck(BlockState state, BakedModel model);
@Shadow protected abstract ChunkRenderTypeSet getExistingRenderCheck(Block block);
private Map<ModelResourceLocation, BlockState> locationToState = new Object2ObjectOpenHashMap<>();
@Inject(method = "<init>", at = @At("RETURN"))
private void onInit(CallbackInfo ci) {
MinecraftForge.EVENT_BUS.addListener(EventPriority.LOW, this::onModelBake);
@ -47,31 +56,48 @@ public abstract class CTMPackReloadListenerMixin {
@Overwrite(remap = false)
private void refreshLayerHacks() {
blockRenderChecks.forEach((b, p) -> ItemBlockRenderTypes.setRenderLayer((Block) b.get(), p));
blockRenderChecks.clear();
if(locationToState.isEmpty()) {
renderCheckOverrides.clear();
if(blockRenderChecks.isEmpty()) {
for(Block block : ForgeRegistries.BLOCKS.getValues()) {
for(BlockState state : block.getStateDefinition().getPossibleStates()) {
locationToState.put(BlockModelShaper.stateToModelLocation(state), state);
}
Holder.Reference<Block> holder = ForgeRegistries.BLOCKS.getDelegateOrThrow(block);
ChunkRenderTypeSet original = this.getExistingRenderCheck(block);
if(original == null)
original = DEFAULT_TYPE_SET;
blockRenderChecks.put(holder, original::contains);
ItemBlockRenderTypes.setRenderLayer(block, type -> this.useOverrideIfPresent(holder, type));
}
}
}
private boolean useOverrideIfPresent(Holder.Reference<Block> delegate, RenderType type) {
Predicate<RenderType> override = renderCheckOverrides.get(delegate);
if(override == null)
override = blockRenderChecks.get(delegate);
return override.test(type);
}
private void onModelBake(DynamicModelBakeEvent event) {
if(!(event.getModel() instanceof AbstractCTMBakedModel || event.getModel() instanceof WeightedBakedModel || event.getModel() instanceof MultiPartBakedModel))
return;
BlockState state = locationToState.get(event.getLocation());
if(state == null)
/* we construct a new ResourceLocation because an MRL is coming in */
Block block = ForgeRegistries.BLOCKS.getValue(new ResourceLocation(event.getLocation().getNamespace(), event.getLocation().getPath()));
Holder.Reference<Block> delegate = block != null ? ForgeRegistries.BLOCKS.getDelegateOrThrow(block) : null;
if(block == null || block == Blocks.AIR || renderCheckOverrides.containsKey(delegate))
return;
Block block = state.getBlock();
Holder.Reference<Block> delegate = ForgeRegistries.BLOCKS.getDelegateOrThrow(block);
if(blockRenderChecks.containsKey(delegate))
/* find all states that match this MRL */
ImmutableList<BlockState> allStates;
try {
allStates = ((IExtendedModelBakery)(Object)event.getModelLoader()).getBlockStatesForMRL(block.getStateDefinition(), (ModelResourceLocation)event.getLocation());
} catch(RuntimeException e) {
ModernFix.LOGGER.error("Couldn't get state for MRL " + event.getLocation(), e);
return;
Predicate<RenderType> newPredicate = this.getLayerCheck(state, event.getModel());
if(newPredicate != null) {
blockRenderChecks.put(delegate, this.getExistingRenderCheck(block)::contains);
ItemBlockRenderTypes.setRenderLayer(block, newPredicate);
}
for(BlockState state : allStates) {
Predicate<RenderType> newPredicate = this.getLayerCheck(state, event.getModel());
if(newPredicate != null) {
renderCheckOverrides.put(delegate, newPredicate);
return;
}
}
}
}

View File

@ -0,0 +1,37 @@
package org.embeddedt.modernfix.mixin.perf.dynamic_structure_manager;
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.server.packs.resources.ResourceManager;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
import net.minecraft.world.level.storage.LevelStorageSource;
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.Map;
import java.util.Optional;
@Mixin(StructureTemplateManager.class)
public class StructureManagerMixin {
@Shadow @Final @Mutable
private Map<ResourceLocation, 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()
.softValues()
.build();
this.structureRepository = structureCache.asMap();
}
}

View File

@ -5,8 +5,10 @@ import com.mojang.datafixers.util.Pair;
import net.minecraft.server.packs.PackType;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.resource.PathPackResources;
import org.embeddedt.modernfix.util.CachedResourcePath;
import org.embeddedt.modernfix.util.FileUtil;
import org.jetbrains.annotations.NotNull;
import org.embeddedt.modernfix.util.PackTypeHelper;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@ -40,9 +42,9 @@ public abstract class PathPackResourcesMixin {
@Shadow @NotNull protected abstract Set<String> getNamespacesFromDisk(PackType type);
private EnumMap<PackType, Set<String>> namespacesByType;
private EnumMap<PackType, HashMap<String, List<Pair<Path, String>>>> rootListingByNamespaceAndType;
private EnumMap<PackType, HashMap<String, List<CachedResourcePath>>> rootListingByNamespaceAndType;
private boolean hasGeneratedListings;
private Set<String> containedPaths;
private Set<CachedResourcePath> containedPaths;
private FileSystem resourcePackFS;
@ -59,24 +61,25 @@ public abstract class PathPackResourcesMixin {
synchronized (this) {
if(hasGeneratedListings)
return;
EnumMap<PackType, HashMap<String, List<Pair<Path, String>>>> rootListingByNamespaceAndType = new EnumMap<>(PackType.class);
HashSet<String> containedPaths = new HashSet<>();
EnumMap<PackType, HashMap<String, List<CachedResourcePath>>> rootListingByNamespaceAndType = new EnumMap<>(PackType.class);
HashSet<CachedResourcePath> containedPaths = new HashSet<>();
for(PackType type : PackType.values()) {
Set<String> namespaces = this.getNamespacesFromDisk(type);
HashMap<String, List<Pair<Path, String>>> rootListingForNamespaces = new HashMap<>();
HashMap<String, List<CachedResourcePath>> rootListingForNamespaces = new HashMap<>();
for(String namespace : namespaces) {
try {
Path root = this.resolve(type.getDirectory(), namespace).toAbsolutePath();
try (Stream<Path> stream = Files.walk(root)) {
ArrayList<Pair<Path, String>> rootListingPaths = new ArrayList<>();
ArrayList<CachedResourcePath> rootListingPaths = new ArrayList<>();
stream
.map(path -> root.relativize(path.toAbsolutePath()))
.filter(this::isValidCachedResourcePath)
.forEach(path -> {
if(!path.toString().endsWith(".mcmeta"))
rootListingPaths.add(Pair.of(path, slashJoiner.join(path)));
String mergedPath = slashJoiner.join(type.getDirectory(), namespace, path);
containedPaths.add(mergedPath);
CachedResourcePath listing = new CachedResourcePath(path);
if(!listing.getFileName().endsWith(".mcmeta")) {
rootListingPaths.add(listing);
}
containedPaths.add(new CachedResourcePath(new String[] { type.getDirectory(), namespace }, listing));
});
rootListingPaths.trimToSize();
rootListingForNamespaces.put(namespace, rootListingPaths);
@ -85,7 +88,8 @@ public abstract class PathPackResourcesMixin {
rootListingForNamespaces.put(namespace, Collections.emptyList());
}
}
rootListingByNamespaceAndType.put(type, rootListingForNamespaces);
if(PackTypeHelper.isVanillaPackType(type))
rootListingByNamespaceAndType.put(type, rootListingForNamespaces);
}
this.rootListingByNamespaceAndType = rootListingByNamespaceAndType;
this.containedPaths = containedPaths;
@ -94,7 +98,11 @@ public abstract class PathPackResourcesMixin {
}
private boolean isValidCachedResourcePath(Path path) {
if(path.getFileName() == null || path.getNameCount() == 0)
return false;
String str = path.toString();
if(str.length() == 0)
return false;
for(int i = 0; i < str.length(); i++) {
if(!ResourceLocation.validPathChar(str.charAt(i))) {
return false;
@ -105,6 +113,8 @@ public abstract class PathPackResourcesMixin {
@Inject(method = "getNamespaces", at = @At("HEAD"), cancellable = true)
private void useCacheForNamespaces(PackType type, CallbackInfoReturnable<Set<String>> cir) {
if(!PackTypeHelper.isVanillaPackType(type))
return;
Set<String> cachedNamespaces;
synchronized (this.namespacesByType) {
cachedNamespaces = this.namespacesByType.get(type);
@ -116,6 +126,8 @@ public abstract class PathPackResourcesMixin {
@Inject(method = "getNamespaces", at = @At("TAIL"))
private void storeCacheForNamespaces(PackType type, CallbackInfoReturnable<Set<String>> cir) {
if(!PackTypeHelper.isVanillaPackType(type))
return;
synchronized (this.namespacesByType) {
this.namespacesByType.put(type, cir.getReturnValue());
}
@ -124,7 +136,7 @@ public abstract class PathPackResourcesMixin {
//@Inject(method = "hasResource(Ljava/lang/String;)Z", at = @At(value = "HEAD"), cancellable = true)
private void useCacheForExistence(String path, CallbackInfoReturnable<Boolean> cir) {
this.generateResourceCache();
cir.setReturnValue(this.containedPaths.contains(FileUtil.normalize(path)));
cir.setReturnValue(this.containedPaths.contains(new CachedResourcePath(path)));
}
/**
@ -134,16 +146,18 @@ public abstract class PathPackResourcesMixin {
@Inject(method = "getResources", at = @At("HEAD"), cancellable = true, remap = false)
public void getResources(PackType type, String resourceNamespace, String pathIn, Predicate<ResourceLocation> filter, CallbackInfoReturnable<Collection<ResourceLocation>> cir)
{
if(!PackTypeHelper.isVanillaPackType(type))
return;
this.generateResourceCache();
if(!pathIn.endsWith("/"))
pathIn = pathIn + "/";
final String testPath = pathIn;
Collection<ResourceLocation> cachedListing = this.rootListingByNamespaceAndType.get(type).getOrDefault(resourceNamespace, Collections.emptyList()).stream().
filter(path -> path.getSecond().startsWith(testPath)). // Make sure the target path is inside this one
filter(path -> path.getFullPath().startsWith(testPath)). // Make sure the target path is inside this one
// Finally we need to form the RL, so use the first name as the domain, and the rest as the path
// It is VERY IMPORTANT that we do not rely on Path.toString as this is inconsistent between operating systems
// Join the path names ourselves to force forward slashes
map(path -> new ResourceLocation(resourceNamespace, path.getSecond())).
map(path -> new ResourceLocation(resourceNamespace, path.getFullPath())).
filter(filter::test). // Test the file name against the predicate
collect(Collectors.toList());
cir.setReturnValue(cachedListing);

View File

@ -16,11 +16,11 @@ import java.util.concurrent.locks.ReentrantLock;
public class BlockColorsMixin {
private Lock mapLock = new ReentrantLock();
@Inject(method = "register", at = @At("HEAD"))
private void lockMapBeforeAccess(BlockColor pBlockColor, Block[] pBlocks, CallbackInfo ci) {
private void lockMapBeforeAccess(CallbackInfo ci) {
mapLock.lock();
}
@Inject(method = "register", at = @At("TAIL"))
private void unlockMap(BlockColor pBlockColor, Block[] pBlocks, CallbackInfo ci) {
private void unlockMap(CallbackInfo ci) {
mapLock.unlock();
}
}

View File

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

View File

@ -0,0 +1,47 @@
package org.embeddedt.modernfix.registry;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.fml.util.ObfuscationReflectionHelper;
import net.minecraftforge.registries.ObjectHolderRegistry;
import org.embeddedt.modernfix.ModernFix;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
public class ObjectHolderClearer {
/**
* Many of the built-in Forge holders needlessly hold on to an exception.
*/
public static void clearThrowables() {
Set<Consumer<Predicate<ResourceLocation>>> holders = ObfuscationReflectionHelper.getPrivateValue(ObjectHolderRegistry.class, null, "objectHolders");
if(holders != null) {
int numCleared = 0;
HashMap<Class<?>, Field> throwableField = new HashMap<>();
Throwable singletonThrowable = new Throwable("[This stacktrace was cleared to save memory]");
try {
for(Consumer<Predicate<ResourceLocation>> holder : holders) {
Field target = throwableField.computeIfAbsent(holder.getClass(), clz -> {
Field[] clzFields = clz.getDeclaredFields();
for(Field f : clzFields) {
if(Throwable.class.isAssignableFrom(f.getType())) {
f.setAccessible(true);
return f;
}
}
return null;
});
if(target != null) {
target.set(holder, singletonThrowable);
numCleared++;
}
}
} catch(RuntimeException | ReflectiveOperationException | NoClassDefFoundError ignored) {
}
ModernFix.LOGGER.debug("Cleared " + numCleared + " object holder stacktrace references");
}
}
}

View File

@ -0,0 +1,101 @@
package org.embeddedt.modernfix.util;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Interner;
import com.google.common.collect.Interners;
import com.google.common.collect.Streams;
import java.lang.ref.WeakReference;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
public class CachedResourcePath {
private final String[] pathComponents;
private int hashCode = 0;
private static final Interner<String> PATH_COMPONENT_INTERNER = Interners.newStrongInterner();
private static final Splitter SLASH_SPLITTER = Splitter.on('/');
private static final Joiner SLASH_JOINER = Joiner.on('/');
private WeakReference<String> fullPathCache = new WeakReference<>(null);
private static final String[] NO_PREFIX = new String[0];
public CachedResourcePath(Path path) {
this(NO_PREFIX, path, path.getNameCount());
}
public CachedResourcePath(String s) {
// normalize so we can guarantee there are no empty sections
this(NO_PREFIX, SLASH_SPLITTER.splitToList(FileUtil.normalize(s)));
}
public <T> CachedResourcePath(String[] prefixElements, Collection<T> collection) {
this(prefixElements, collection, collection.size());
}
public <T> CachedResourcePath(String[] prefixElements, Iterable<T> path, int count) {
String[] components = new String[prefixElements.length + count];
int i = 0;
while(i < prefixElements.length) {
components[i] = PATH_COMPONENT_INTERNER.intern(prefixElements[i]);
i++;
}
for(Object component : path) {
String s = component.toString();
if(s.length() == 0)
continue;
components[i] = PATH_COMPONENT_INTERNER.intern(s);
i++;
}
pathComponents = components;
}
public CachedResourcePath(String[] prefixElements, CachedResourcePath other) {
String[] components = new String[prefixElements.length + other.pathComponents.length];
int i = 0;
while(i < prefixElements.length) {
components[i] = PATH_COMPONENT_INTERNER.intern(prefixElements[i]);
i++;
}
System.arraycopy(other.pathComponents, 0, components, i, other.pathComponents.length);
pathComponents = components;
}
@Override
public int hashCode() {
int result = hashCode;
if(result != 0)
return result;
hashCode = Arrays.hashCode(pathComponents);
return hashCode;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CachedResourcePath that = (CachedResourcePath) o;
return Arrays.equals(pathComponents, that.pathComponents);
}
public String getFileName() {
return pathComponents[pathComponents.length - 1];
}
public int getNameCount() {
return pathComponents.length;
}
public String getFullPath() {
String fPath = fullPathCache.get();
if(fPath == null) {
fPath = SLASH_JOINER.join(pathComponents);
fullPathCache = new WeakReference<>(fPath);
}
return fPath;
}
}

View File

@ -0,0 +1,31 @@
package org.embeddedt.modernfix.util;
import org.embeddedt.modernfix.core.ModernFixMixinPlugin;
import org.spongepowered.asm.mixin.transformer.ClassInfo;
import java.lang.reflect.Field;
import java.util.Iterator;
import java.util.Map;
public class ClassInfoManager {
private static Map<String, ClassInfo> classInfoCache = null;
public static void clear() {
if(!ModernFixMixinPlugin.instance.isOptionEnabled("perf.clear_mixin_classinfo.ClassInfoManager"))
return;
if(classInfoCache == null) {
try {
Field field = ClassInfo.class.getDeclaredField("cache");
field.setAccessible(true);
classInfoCache = (Map<String, ClassInfo>)field.get(null);
} catch(ReflectiveOperationException | RuntimeException e) {
e.printStackTrace();
return;
}
}
try {
classInfoCache.entrySet().removeIf(entry -> !entry.getKey().equals("java/lang/Object") && (entry.getValue() == null || !entry.getValue().isMixin()));
} catch(RuntimeException e) {
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,9 @@
package org.embeddedt.modernfix.util;
import net.minecraft.server.packs.PackType;
public class PackTypeHelper {
public static boolean isVanillaPackType(PackType type) {
return type == PackType.CLIENT_RESOURCES || type == PackType.SERVER_DATA;
}
}

View File

@ -9,25 +9,29 @@ import org.slf4j.Logger;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.lang.ref.WeakReference;
import java.util.concurrent.TimeUnit;
public class IntegratedWatchdog extends Thread {
private static final Logger LOGGER = LogUtils.getLogger();
private final MinecraftServer server;
private final WeakReference<MinecraftServer> server;
private static final long MAX_TICK_DELTA = 40*1000;
public IntegratedWatchdog(MinecraftServer server) {
this.server = server;
this.server = new WeakReference<>(server);
this.setDaemon(true);
this.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandlerWithName(LOGGER));
this.setName("ModernFix integrated server watchdog");
}
public void run() {
while(server.isRunning()) {
long nextTick = this.server.getNextTickTime();
while(true) {
MinecraftServer server = this.server.get();
if(server == null || !server.isRunning())
return;
long nextTick = server.getNextTickTime();
long curTime = Util.getMillis();
long delta = curTime - nextTick;
if(delta > MAX_TICK_DELTA) {
@ -53,6 +57,7 @@ public class IntegratedWatchdog extends Thread {
nextTick = 0;
curTime = 0;
}
server = null; /* allow GC */
try {
Thread.sleep(nextTick + MAX_TICK_DELTA - curTime);
} catch(InterruptedException ignored) {

View File

@ -7,6 +7,7 @@
"refmap": "modernfix.refmap.json",
"mixins": [
"bugfix.edge_chunk_not_saved.ChunkManagerMixin",
"perf.dynamic_structure_manager.StructureManagerMixin",
"bugfix.chunk_deadlock.ServerChunkCacheMixin",
"perf.remove_biome_temperature_cache.BiomeMixin",
"perf.reduce_blockstate_cache_rebuilds.GameDataMixin",
@ -31,6 +32,8 @@
"perf.state_definition_construct.StateDefinitionMixin",
"perf.compress_blockstate.BlockStateBaseMixin",
"perf.compress_blockstate.BlockBehaviourMixin",
"perf.dedup_blockstate_flattening_map.BlockStateDataMixin",
"perf.dedup_blockstate_flattening_map.ChunkPalettedStorageFixMixin",
"devenv.MinecraftServerMixin"
],
"client": [
@ -38,6 +41,7 @@
"core.SynchedEntityDataMixin",
"feature.measure_time.MinecraftMixin",
"bugfix.concurrency.MinecraftMixin",
"perf.dynamic_resources.BlockElementFaceDeserializerMixin",
"perf.dynamic_resources.BlockModelShaperMixin",
"perf.dynamic_resources.ItemModelShaperMixin",
"perf.dynamic_resources.ModelBakerImplMixin",
@ -53,6 +57,7 @@
"perf.model_optimizations.PropertyMixin",
"perf.thread_priorities.IntegratedServerMixin",
"safety.BlockColorsMixin",
"safety.ItemColorsMixin",
"perf.flatten_model_predicates.AndConditionMixin",
"perf.flatten_model_predicates.OrConditionMixin",
"perf.flatten_model_predicates.PropertyValueConditionMixin",