Make state_definition_construct degrade gracefully if map is used like a hashmap

Related: #452
This commit is contained in:
embeddedt 2024-08-09 18:02:39 -04:00
parent c0eaf29cb5
commit 49464451dd
No known key found for this signature in database
GPG Key ID: A69433EC199B5613
2 changed files with 29 additions and 15 deletions

View File

@ -1,5 +1,6 @@
package org.embeddedt.modernfix.blockstate; package org.embeddedt.modernfix.blockstate;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.world.level.block.state.properties.Property; import net.minecraft.world.level.block.state.properties.Property;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -14,6 +15,7 @@ import java.util.*;
*/ */
public class FakeStateMap<S> implements Map<Map<Property<?>, Comparable<?>>, S> { public class FakeStateMap<S> implements Map<Map<Property<?>, Comparable<?>>, S> {
private final Map<Property<?>, Comparable<?>>[] keys; private final Map<Property<?>, Comparable<?>>[] keys;
private Map<Map<Property<?>, Comparable<?>>, S> fastLookup;
private final Object[] values; private final Object[] values;
private int usedSlots; private int usedSlots;
public FakeStateMap(int numStates) { public FakeStateMap(int numStates) {
@ -34,22 +36,39 @@ public class FakeStateMap<S> implements Map<Map<Property<?>, Comparable<?>>, S>
@Override @Override
public boolean containsKey(Object o) { public boolean containsKey(Object o) {
throw new UnsupportedOperationException(); return getFastLookup().containsKey(o);
} }
@Override @Override
public boolean containsValue(Object o) { public boolean containsValue(Object o) {
throw new UnsupportedOperationException(); return getFastLookup().containsValue(o);
}
@SuppressWarnings("unchecked")
private Map<Map<Property<?>, Comparable<?>>, S> getFastLookup() {
if(fastLookup == null) {
var map = new Object2ObjectOpenHashMap<Map<Property<?>, Comparable<?>>, S>(usedSlots);
Map<Property<?>, Comparable<?>>[] keys = this.keys;
Object[] values = this.values;
for(int i = 0; i < usedSlots; i++) {
map.put(keys[i], (S)values[i]);
}
fastLookup = map;
}
return fastLookup;
} }
@Override @Override
public S get(Object o) { public S get(Object o) {
throw new UnsupportedOperationException(); return getFastLookup().get(o);
} }
@Nullable @Nullable
@Override @Override
public S put(Map<Property<?>, Comparable<?>> propertyComparableMap, S s) { public S put(Map<Property<?>, Comparable<?>> propertyComparableMap, S s) {
if(fastLookup != null) {
throw new IllegalStateException("Cannot populate map after fast lookup is built");
}
keys[usedSlots] = propertyComparableMap; keys[usedSlots] = propertyComparableMap;
values[usedSlots] = s; values[usedSlots] = s;
usedSlots++; usedSlots++;
@ -70,7 +89,7 @@ public class FakeStateMap<S> implements Map<Map<Property<?>, Comparable<?>>, S>
@Override @Override
public void clear() { public void clear() {
for(int i = 0; i < this.keys.length; i++) { for(int i = 0; i < usedSlots; i++) {
this.keys[i] = null; this.keys[i] = null;
this.values[i] = null; this.values[i] = null;
} }

View File

@ -6,23 +6,25 @@ import net.minecraft.world.level.block.state.StateHolder;
import net.minecraft.world.level.block.state.properties.Property; import net.minecraft.world.level.block.state.properties.Property;
import org.embeddedt.modernfix.annotation.RequiresMod; import org.embeddedt.modernfix.annotation.RequiresMod;
import org.embeddedt.modernfix.blockstate.FakeStateMap; import org.embeddedt.modernfix.blockstate.FakeStateMap;
import org.embeddedt.modernfix.blockstate.FerriteCorePostProcess;
import org.embeddedt.modernfix.platform.ModernFixPlatformHooks;
import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyVariable; import org.spongepowered.asm.mixin.injection.ModifyVariable;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Map; import java.util.Map;
// This optimization requires FerriteCore to be worthwhile, otherwise the FakeStateMap degrades to hash internally
@Mixin(StateDefinition.class) @Mixin(StateDefinition.class)
@RequiresMod("ferritecore") @RequiresMod("ferritecore")
public class StateDefinitionMixin<O, S extends StateHolder<O, S>> { public class StateDefinitionMixin<O, S extends StateHolder<O, S>> {
@Shadow @Final private ImmutableSortedMap<String, Property<?>> propertiesByName; @Shadow @Final private ImmutableSortedMap<String, Property<?>> propertiesByName;
/**
* @author embeddedt
* @reason write states into a custom array map for fast iteration by FerriteCore, no need to waste time hashing
* and growing
*/
@ModifyVariable(method = "<init>", at = @At(value = "STORE", ordinal = 0), ordinal = 1, index = 8) @ModifyVariable(method = "<init>", at = @At(value = "STORE", ordinal = 0), ordinal = 1, index = 8)
private Map<Map<Property<?>, Comparable<?>>, S> useArrayMap(Map<Map<Property<?>, Comparable<?>>, S> in) { private Map<Map<Property<?>, Comparable<?>>, S> useArrayMap(Map<Map<Property<?>, Comparable<?>>, S> in) {
int numStates = 1; int numStates = 1;
@ -31,11 +33,4 @@ public class StateDefinitionMixin<O, S extends StateHolder<O, S>> {
} }
return new FakeStateMap<>(numStates); return new FakeStateMap<>(numStates);
} }
@Inject(method = "<init>", at = @At("TAIL"))
private void postProcess(CallbackInfo ci) {
// keep in dev only until upstream FC releases
if(ModernFixPlatformHooks.INSTANCE.isDevEnv())
FerriteCorePostProcess.postProcess((StateDefinition<O, S>)(Object)this);
}
} }