From 3926f27d33ad00f8ed738c6297fa1b4652e3067c Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sun, 25 Jan 2026 19:27:27 -0500 Subject: [PATCH] Optimize memory usage of entity attribute templates --- .../AttributeSupplierBuilderMixin.java | 30 +++++++++++++ .../AttributeSupplierMixin.java | 32 ++++++++++++++ .../entity/AttributeInstanceTemplates.java | 44 +++++++++++++++++++ 3 files changed, 106 insertions(+) create mode 100644 src/main/java/org/embeddedt/modernfix/common/mixin/perf/attribute_supplier_dedup/AttributeSupplierBuilderMixin.java create mode 100644 src/main/java/org/embeddedt/modernfix/common/mixin/perf/attribute_supplier_dedup/AttributeSupplierMixin.java create mode 100644 src/main/java/org/embeddedt/modernfix/entity/AttributeInstanceTemplates.java 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/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); + } + } +}