Optimize memory usage of entity attribute templates

This commit is contained in:
embeddedt 2026-01-25 19:27:27 -05:00
parent 9bc5f06a19
commit 3926f27d33
No known key found for this signature in database
GPG Key ID: A69433EC199B5613
3 changed files with 106 additions and 0 deletions

View File

@ -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<Attribute, AttributeInstance> 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<AttributeSupplier> cir) {
this.builder.replaceAll((a, i) -> AttributeInstanceTemplates.intern(i));
}
}

View File

@ -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<Attribute, AttributeInstance> 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 = "<init>", at = @At("RETURN"))
private void useCompactJavaMap(Map<Attribute, AttributeInstance> instances, CallbackInfo ci) {
this.instances = Map.copyOf(this.instances);
}
}

View File

@ -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<AttributeInstance> 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);
}
}
}