diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/attribute_supplier_dedup/AttributeSupplierBuilderMixin.java b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/attribute_supplier_dedup/AttributeSupplierBuilderMixin.java new file mode 100644 index 00000000..2953a86a --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/attribute_supplier_dedup/AttributeSupplierBuilderMixin.java @@ -0,0 +1,30 @@ +package org.embeddedt.modernfix.common.mixin.perf.attribute_supplier_dedup; + +import net.minecraft.world.entity.ai.attributes.Attribute; +import net.minecraft.world.entity.ai.attributes.AttributeInstance; +import net.minecraft.world.entity.ai.attributes.AttributeSupplier; +import org.embeddedt.modernfix.entity.AttributeInstanceTemplates; +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(AttributeSupplier.Builder.class) +public class AttributeSupplierBuilderMixin { + @Shadow + @Final + private Map builder; + + /** + * @author embeddedt + * @reason canonicalize identical AttributeInstance templates, many entities are created with the same values + */ + @Inject(method = "build", at = @At(value = "NEW", target = "(Ljava/util/Map;)Lnet/minecraft/world/entity/ai/attributes/AttributeSupplier;")) + private void deduplicateInstances(CallbackInfoReturnable cir) { + this.builder.replaceAll((a, i) -> AttributeInstanceTemplates.intern(i)); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/attribute_supplier_dedup/AttributeSupplierMixin.java b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/attribute_supplier_dedup/AttributeSupplierMixin.java new file mode 100644 index 00000000..5ffc3489 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/attribute_supplier_dedup/AttributeSupplierMixin.java @@ -0,0 +1,32 @@ +package org.embeddedt.modernfix.common.mixin.perf.attribute_supplier_dedup; + +import net.minecraft.world.entity.ai.attributes.Attribute; +import net.minecraft.world.entity.ai.attributes.AttributeInstance; +import net.minecraft.world.entity.ai.attributes.AttributeSupplier; +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; + +@Mixin(AttributeSupplier.class) +public class AttributeSupplierMixin { + @Shadow + @Final + @Mutable + private Map instances; + + /** + * @author embeddedt + * @reason Java 9's Map.of() implementation is significantly more compact than ImmutableMap, and we do not + * care about insertion order in this context + */ + @Inject(method = "", at = @At("RETURN")) + private void useCompactJavaMap(Map instances, CallbackInfo ci) { + this.instances = Map.copyOf(this.instances); + } +} \ No newline at end of file diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/compact_mojang_registries/BlockStateDataMixin.java b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/compact_mojang_registries/BlockStateDataMixin.java index eb85f08c..47b02f54 100644 --- a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/compact_mojang_registries/BlockStateDataMixin.java +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/compact_mojang_registries/BlockStateDataMixin.java @@ -35,7 +35,7 @@ public class BlockStateDataMixin { t = compactTag(ct); } t = TAG_INTERNER.addOrGet(t); - entries[i++] = Map.entry(key, t); + entries[i++] = Map.entry(key.intern(), t); } return new CompoundTag(Map.ofEntries(entries)); } diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/ModelManagerMixin.java b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/ModelManagerMixin.java index 72924a15..f12ab580 100644 --- a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/ModelManagerMixin.java +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/ModelManagerMixin.java @@ -64,7 +64,8 @@ public class ModelManagerMixin implements IExtendedModelManager { @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, ? extends CompletionStage>> deferBlockModelLoad(Function, ? extends CompletionStage>> fn, @Local(ordinal = 0, argsOnly = true) ResourceManager manager) { return resourceMap -> { - var cache = CacheUtil.simpleCacheForLambda(location -> loadSingleBlockModel(manager, location), 100L); + var fallbackModel = BlockModel.fromString(ModelBakery.MISSING_MODEL_MESH); + var cache = CacheUtil.simpleCacheForLambda(location -> loadSingleBlockModel(manager, location, fallbackModel), 100L); return CompletableFuture.completedFuture(Maps.asMap(Set.copyOf(resourceMap.keySet()), location -> cache.getUnchecked(location))); }; } @@ -81,13 +82,15 @@ public class ModelManagerMixin implements IExtendedModelManager { return ImmutableList.of(); } - private static BlockModel loadSingleBlockModel(ResourceManager manager, ResourceLocation location) { + private static BlockModel loadSingleBlockModel(ResourceManager manager, ResourceLocation location, BlockModel fallbackModel) { 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; + } catch (Exception e) { + // We must return some nonnull value to avoid breaking the map convention. The easiest solution + // is to just return a missing model template. + ModernFix.LOGGER.error("Couldn't load model {}, substituting missing", location, e); + return fallbackModel; } }).orElse(null); } diff --git a/src/main/java/org/embeddedt/modernfix/entity/AttributeInstanceTemplates.java b/src/main/java/org/embeddedt/modernfix/entity/AttributeInstanceTemplates.java new file mode 100644 index 00000000..f54b2579 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/entity/AttributeInstanceTemplates.java @@ -0,0 +1,44 @@ +package org.embeddedt.modernfix.entity; + +import it.unimi.dsi.fastutil.Hash; +import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet; +import net.minecraft.world.entity.ai.attributes.AttributeInstance; + +import java.util.Objects; + +public class AttributeInstanceTemplates { + private static final ObjectOpenCustomHashSet INTERNER = new ObjectOpenCustomHashSet<>(new Hash.Strategy<>() { + @Override + public int hashCode(AttributeInstance o) { + if (o == null) { + return 0; + } + int h = o.getAttribute().hashCode(); + h = 31 * h + Double.hashCode(o.getBaseValue()); + h = 31 * h + o.getModifiers().hashCode(); + return h; + } + + @Override + public boolean equals(AttributeInstance a, AttributeInstance b) { + if (a == b) { + return true; + } + if (a == null || b == null) { + return false; + } + return a.getAttribute() == b.getAttribute() + && a.getBaseValue() == b.getBaseValue() + && a.getModifiers().equals(b.getModifiers()); + } + }); + + public static AttributeInstance intern(AttributeInstance a) { + if (a == null || a.getClass() != AttributeInstance.class) { + return a; + } + synchronized (INTERNER) { + return INTERNER.addOrGet(a); + } + } +}