|
@@ -0,0 +1,103 @@
|
|
|
+package malte0811.ferritecore.mixin;
|
|
|
+
|
|
|
+import malte0811.ferritecore.CachedOrPredicates;
|
|
|
+import net.minecraft.block.Block;
|
|
|
+import net.minecraft.block.BlockState;
|
|
|
+import net.minecraft.client.renderer.model.multipart.PropertyValueCondition;
|
|
|
+import net.minecraft.state.Property;
|
|
|
+import net.minecraft.state.StateContainer;
|
|
|
+import org.apache.commons.lang3.tuple.Pair;
|
|
|
+import org.spongepowered.asm.mixin.Final;
|
|
|
+import org.spongepowered.asm.mixin.Mixin;
|
|
|
+import org.spongepowered.asm.mixin.Overwrite;
|
|
|
+import org.spongepowered.asm.mixin.Shadow;
|
|
|
+
|
|
|
+import com.google.common.base.Splitter;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+import java.util.Optional;
|
|
|
+import java.util.concurrent.ConcurrentHashMap;
|
|
|
+import java.util.function.Predicate;
|
|
|
+
|
|
|
+import malte0811.ferritecore.HackyGlobalState;
|
|
|
+
|
|
|
+@Mixin(PropertyValueCondition.class)
|
|
|
+public class PropertyValueConditionMixin {
|
|
|
+ private static final Map<Pair<Property<?>, Comparable<?>>, Predicate<BlockState>> STATE_HAS_PROPERTY_CACHE = new ConcurrentHashMap<>();
|
|
|
+
|
|
|
+ @Shadow
|
|
|
+ @Final
|
|
|
+ private String key;
|
|
|
+ @Shadow
|
|
|
+ @Final
|
|
|
+ private String value;
|
|
|
+ @Shadow
|
|
|
+ @Final
|
|
|
+ private static Splitter SPLITTER;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @reason Use cached predicates in the case of multiple specified values
|
|
|
+ * TODO this should be possible with a less invasive mixin?
|
|
|
+ * @author malte0811
|
|
|
+ */
|
|
|
+ @Overwrite
|
|
|
+ public Predicate<BlockState> getPredicate(StateContainer<Block, BlockState> stateContainer) {
|
|
|
+ Property<?> property = stateContainer.getProperty(this.key);
|
|
|
+ if (property == null) {
|
|
|
+ throw new RuntimeException(String.format("Unknown property '%s' on '%s'", this.key, stateContainer.getOwner().toString()));
|
|
|
+ } else {
|
|
|
+ String valueNoInvert = this.value;
|
|
|
+ boolean invert = !valueNoInvert.isEmpty() && valueNoInvert.charAt(0) == '!';
|
|
|
+ if (invert) {
|
|
|
+ valueNoInvert = valueNoInvert.substring(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ List<String> matchedStates = SPLITTER.splitToList(valueNoInvert);
|
|
|
+ if (matchedStates.isEmpty()) {
|
|
|
+ throw new RuntimeException(String.format("Empty value '%s' for property '%s' on '%s'", this.value, this.key, stateContainer.getOwner().toString()));
|
|
|
+ } else {
|
|
|
+ Predicate<BlockState> isMatchedState;
|
|
|
+ if (matchedStates.size() == 1) {
|
|
|
+ isMatchedState = this.makePropertyPredicate(stateContainer, property, valueNoInvert);
|
|
|
+ } else {
|
|
|
+ List<Predicate<BlockState>> subPredicates = matchedStates.stream()
|
|
|
+ .map(subValue -> this.makePropertyPredicate(stateContainer, property, subValue))
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ isMatchedState = CachedOrPredicates.or(subPredicates);
|
|
|
+ }
|
|
|
+
|
|
|
+ return invert ? isMatchedState.negate() : isMatchedState;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @reason The vanilla implementation captures an Optional in the resulting lambda, which eats a lot of memory. A
|
|
|
+ * less aggressive Mixin would require fiddling with local variables, which is more effort than it would be worth
|
|
|
+ * for such an obscure method.
|
|
|
+ * Also adds a cache to avoid creating two Predicate instances for the same lookup
|
|
|
+ * @author malte0811
|
|
|
+ */
|
|
|
+ @Overwrite
|
|
|
+ private <T extends Comparable<T>>
|
|
|
+ Predicate<BlockState> makePropertyPredicate(
|
|
|
+ StateContainer<Block, BlockState> container, Property<T> property, String value
|
|
|
+ ) {
|
|
|
+ Optional<T> optional = property.parseValue(value);
|
|
|
+ if (!optional.isPresent()) {
|
|
|
+ throw new RuntimeException(String.format("Unknown value '%s' for property '%s' on '%s' in '%s'", value, this.key, container.getOwner().toString(), this.value));
|
|
|
+ } else {
|
|
|
+ T unwrapped = optional.get();
|
|
|
+ Pair<Property<?>, Comparable<?>> key = Pair.of(property, unwrapped);
|
|
|
+ return STATE_HAS_PROPERTY_CACHE.computeIfAbsent(
|
|
|
+ key,
|
|
|
+ pair -> {
|
|
|
+ Comparable<?> valueInt = pair.getRight();
|
|
|
+ Property<?> propInt = pair.getLeft();
|
|
|
+ return state -> state.get(propInt).equals(valueInt);
|
|
|
+ }
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|