From ef7c4f9462d40b9adee4d27cd53cef5edf9868f2 Mon Sep 17 00:00:00 2001 From: JellySquid Date: Thu, 3 Sep 2020 16:55:29 -0500 Subject: [PATCH] new: Flatten multipart model selector predicates --- .../state/StatePropertyPredicateHelper.java | 35 ++++++++++ .../common/state/all/AllMatchOneBoolean.java | 47 ++++++++++++++ .../common/state/all/AllMatchOneObject.java | 38 +++++++++++ .../common/state/any/AllMatchAnyObject.java | 39 +++++++++++ .../common/state/single/SingleMatchAny.java | 63 ++++++++++++++++++ .../common/state/single/SingleMatchOne.java | 36 ++++++++++ .../hydrogen/common/util/AllPredicate.java | 22 +++++++ .../hydrogen/common/util/AnyPredicate.java | 22 +++++++ .../json/MixinAndMultipartModelSelector.java | 29 +++++---- .../json/MixinOrMultipartModelSelector.java | 34 ++++++++++ .../MixinSimpleMultipartModelSelector.java | 65 ++++++++++--------- src/main/resources/hydrogen.mixins.json | 7 +- 12 files changed, 392 insertions(+), 45 deletions(-) create mode 100644 src/main/java/me/jellysquid/mods/hydrogen/common/state/StatePropertyPredicateHelper.java create mode 100644 src/main/java/me/jellysquid/mods/hydrogen/common/state/all/AllMatchOneBoolean.java create mode 100644 src/main/java/me/jellysquid/mods/hydrogen/common/state/all/AllMatchOneObject.java create mode 100644 src/main/java/me/jellysquid/mods/hydrogen/common/state/any/AllMatchAnyObject.java create mode 100644 src/main/java/me/jellysquid/mods/hydrogen/common/state/single/SingleMatchAny.java create mode 100644 src/main/java/me/jellysquid/mods/hydrogen/common/state/single/SingleMatchOne.java create mode 100644 src/main/java/me/jellysquid/mods/hydrogen/common/util/AllPredicate.java create mode 100644 src/main/java/me/jellysquid/mods/hydrogen/common/util/AnyPredicate.java create mode 100644 src/main/java/me/jellysquid/mods/hydrogen/mixin/client/model/json/MixinOrMultipartModelSelector.java diff --git a/src/main/java/me/jellysquid/mods/hydrogen/common/state/StatePropertyPredicateHelper.java b/src/main/java/me/jellysquid/mods/hydrogen/common/state/StatePropertyPredicateHelper.java new file mode 100644 index 0000000..9fe1630 --- /dev/null +++ b/src/main/java/me/jellysquid/mods/hydrogen/common/state/StatePropertyPredicateHelper.java @@ -0,0 +1,35 @@ +package me.jellysquid.mods.hydrogen.common.state; + +import me.jellysquid.mods.hydrogen.common.state.all.AllMatchOneBoolean; +import me.jellysquid.mods.hydrogen.common.state.all.AllMatchOneObject; +import me.jellysquid.mods.hydrogen.common.state.any.AllMatchAnyObject; +import me.jellysquid.mods.hydrogen.common.state.single.SingleMatchAny; +import me.jellysquid.mods.hydrogen.common.state.single.SingleMatchOne; +import me.jellysquid.mods.hydrogen.common.util.AllPredicate; +import me.jellysquid.mods.hydrogen.common.util.AnyPredicate; +import net.minecraft.block.BlockState; + +import java.util.List; +import java.util.function.Predicate; + +public class StatePropertyPredicateHelper { + @SuppressWarnings("unchecked") + public static Predicate allMatch(List> predicates) { + if (SingleMatchOne.areOfType(predicates)) { + if (SingleMatchOne.valuesMatchType(predicates, Boolean.class)) { + return new AllMatchOneBoolean(predicates); + } + + return new AllMatchOneObject(predicates); + } else if (SingleMatchAny.areOfType(predicates)) { + return new AllMatchAnyObject(predicates); + } + + return new AllPredicate<>(predicates.toArray(new Predicate[0])); + } + + @SuppressWarnings("unchecked") + public static Predicate anyMatch(List> predicates) { + return new AnyPredicate<>(predicates.toArray(new Predicate[0])); + } +} diff --git a/src/main/java/me/jellysquid/mods/hydrogen/common/state/all/AllMatchOneBoolean.java b/src/main/java/me/jellysquid/mods/hydrogen/common/state/all/AllMatchOneBoolean.java new file mode 100644 index 0000000..6a78191 --- /dev/null +++ b/src/main/java/me/jellysquid/mods/hydrogen/common/state/all/AllMatchOneBoolean.java @@ -0,0 +1,47 @@ +package me.jellysquid.mods.hydrogen.common.state.all; + +import me.jellysquid.mods.hydrogen.common.state.single.SingleMatchOne; +import net.minecraft.block.BlockState; +import net.minecraft.state.property.Property; + +import java.util.List; +import java.util.function.Predicate; + +public class AllMatchOneBoolean implements Predicate { + private final Property[] properties; + private final boolean[] values; + + public AllMatchOneBoolean(List> list) { + int size = list.size(); + + this.properties = new Property[size]; + this.values = new boolean[size]; + + for (int i = 0; i < size; i++) { + SingleMatchOne predicate = (SingleMatchOne) list.get(i); + + this.properties[i] = predicate.property; + this.values[i] = (boolean) predicate.value; + } + } + + public static boolean canReplace(List> list) { + return list.stream() + .allMatch(p -> { + return p instanceof SingleMatchOne && ((SingleMatchOne) p).value instanceof Boolean; + }); + } + + @Override + public boolean test(BlockState blockState) { + for (int i = 0; i < this.properties.length; i++) { + Boolean value = (Boolean) blockState.get(this.properties[i]); + + if (value != this.values[i]) { + return false; + } + } + + return true; + } +} diff --git a/src/main/java/me/jellysquid/mods/hydrogen/common/state/all/AllMatchOneObject.java b/src/main/java/me/jellysquid/mods/hydrogen/common/state/all/AllMatchOneObject.java new file mode 100644 index 0000000..a3795fa --- /dev/null +++ b/src/main/java/me/jellysquid/mods/hydrogen/common/state/all/AllMatchOneObject.java @@ -0,0 +1,38 @@ +package me.jellysquid.mods.hydrogen.common.state.all; + +import me.jellysquid.mods.hydrogen.common.state.single.SingleMatchOne; +import net.minecraft.block.BlockState; +import net.minecraft.state.property.Property; + +import java.util.List; +import java.util.function.Predicate; + +public class AllMatchOneObject implements Predicate { + private final Property[] properties; + private final Object[] values; + + public AllMatchOneObject(List> list) { + int size = list.size(); + + this.properties = new Property[size]; + this.values = new Object[size]; + + for (int i = 0; i < size; i++) { + SingleMatchOne predicate = (SingleMatchOne) list.get(i); + + this.properties[i] = predicate.property; + this.values[i] = predicate.value; + } + } + + @Override + public boolean test(BlockState blockState) { + for (int i = 0; i < this.properties.length; i++) { + if (blockState.get(this.properties[i]) != this.values[i]) { + return false; + } + } + + return true; + } +} diff --git a/src/main/java/me/jellysquid/mods/hydrogen/common/state/any/AllMatchAnyObject.java b/src/main/java/me/jellysquid/mods/hydrogen/common/state/any/AllMatchAnyObject.java new file mode 100644 index 0000000..4f55e18 --- /dev/null +++ b/src/main/java/me/jellysquid/mods/hydrogen/common/state/any/AllMatchAnyObject.java @@ -0,0 +1,39 @@ +package me.jellysquid.mods.hydrogen.common.state.any; + +import me.jellysquid.mods.hydrogen.common.state.single.SingleMatchAny; +import net.minecraft.block.BlockState; +import net.minecraft.state.property.Property; +import org.apache.commons.lang3.ArrayUtils; + +import java.util.List; +import java.util.function.Predicate; + +public class AllMatchAnyObject implements Predicate { + private final Property[] properties; + private final Object[][] values; + + public AllMatchAnyObject(List> list) { + int size = list.size(); + + this.properties = new Property[size]; + this.values = new Object[size][]; + + for (int i = 0; i < size; i++) { + SingleMatchAny predicate = (SingleMatchAny) list.get(i); + + this.properties[i] = predicate.property; + this.values[i] = predicate.values; + } + } + + @Override + public boolean test(BlockState blockState) { + for (int i = 0; i < this.properties.length; i++) { + if (!ArrayUtils.contains(this.values[i], blockState.get(this.properties[i]))) { + return false; + } + } + + return true; + } +} diff --git a/src/main/java/me/jellysquid/mods/hydrogen/common/state/single/SingleMatchAny.java b/src/main/java/me/jellysquid/mods/hydrogen/common/state/single/SingleMatchAny.java new file mode 100644 index 0000000..03743ed --- /dev/null +++ b/src/main/java/me/jellysquid/mods/hydrogen/common/state/single/SingleMatchAny.java @@ -0,0 +1,63 @@ +package me.jellysquid.mods.hydrogen.common.state.single; + +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import net.minecraft.block.BlockState; +import net.minecraft.state.property.Property; +import org.apache.commons.lang3.ArrayUtils; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.function.Predicate; + +public class SingleMatchAny implements Predicate { + public static final ObjectOpenHashSet PREDICATES = new ObjectOpenHashSet<>(); + + public final Property property; + public final Object[] values; + + private SingleMatchAny(Property property, List values) { + this.property = property; + this.values = values.toArray(); + } + + public static SingleMatchAny create(Property property, List values) { + return PREDICATES.addOrGet(new SingleMatchAny(property, values)); + } + + public static boolean areOfType(List> predicates) { + return predicates.stream() + .allMatch(p -> { + return p instanceof SingleMatchAny; + }); + } + + public static boolean valuesMatchType(List> predicates, Class type) { + return predicates.stream() + .allMatch(p -> { + return p instanceof SingleMatchAny && + Arrays.stream(((SingleMatchAny) p).values).allMatch(t -> type.isInstance(p)); + }); + } + + @Override + public boolean test(BlockState blockState) { + return ArrayUtils.contains(this.values, blockState.get(this.property)); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SingleMatchAny that = (SingleMatchAny) o; + return Objects.equals(property, that.property) && + Arrays.equals(values, that.values); + } + + @Override + public int hashCode() { + int result = Objects.hash(property); + result = 31 * result + Arrays.hashCode(values); + return result; + } +} diff --git a/src/main/java/me/jellysquid/mods/hydrogen/common/state/single/SingleMatchOne.java b/src/main/java/me/jellysquid/mods/hydrogen/common/state/single/SingleMatchOne.java new file mode 100644 index 0000000..f2106b5 --- /dev/null +++ b/src/main/java/me/jellysquid/mods/hydrogen/common/state/single/SingleMatchOne.java @@ -0,0 +1,36 @@ +package me.jellysquid.mods.hydrogen.common.state.single; + +import net.minecraft.block.BlockState; +import net.minecraft.state.property.Property; + +import java.util.List; +import java.util.function.Predicate; + +public class SingleMatchOne implements Predicate { + public final Property property; + public final Object value; + + public SingleMatchOne(Property property, Object value) { + this.property = property; + this.value = value; + } + + public static boolean areOfType(List> predicates) { + return predicates.stream() + .allMatch(p -> { + return p instanceof SingleMatchOne; + }); + } + + public static boolean valuesMatchType(List> predicates, Class type) { + return predicates.stream() + .allMatch(p -> { + return p instanceof SingleMatchOne && type.isInstance(((SingleMatchOne) p).value); + }); + } + + @Override + public boolean test(BlockState blockState) { + return blockState.get(this.property) == this.value; + } +} diff --git a/src/main/java/me/jellysquid/mods/hydrogen/common/util/AllPredicate.java b/src/main/java/me/jellysquid/mods/hydrogen/common/util/AllPredicate.java new file mode 100644 index 0000000..a4836a1 --- /dev/null +++ b/src/main/java/me/jellysquid/mods/hydrogen/common/util/AllPredicate.java @@ -0,0 +1,22 @@ +package me.jellysquid.mods.hydrogen.common.util; + +import java.util.function.Predicate; + +public class AllPredicate implements Predicate { + private final Predicate[] predicates; + + public AllPredicate(Predicate[] predicates) { + this.predicates = predicates; + } + + @Override + public boolean test(T t) { + for (Predicate predicate : this.predicates) { + if (!predicate.test(t)) { + return false; + } + } + + return true; + } +} diff --git a/src/main/java/me/jellysquid/mods/hydrogen/common/util/AnyPredicate.java b/src/main/java/me/jellysquid/mods/hydrogen/common/util/AnyPredicate.java new file mode 100644 index 0000000..6877466 --- /dev/null +++ b/src/main/java/me/jellysquid/mods/hydrogen/common/util/AnyPredicate.java @@ -0,0 +1,22 @@ +package me.jellysquid.mods.hydrogen.common.util; + +import java.util.function.Predicate; + +public class AnyPredicate implements Predicate { + private final Predicate[] predicates; + + public AnyPredicate(Predicate[] predicates) { + this.predicates = predicates; + } + + @Override + public boolean test(T t) { + for (Predicate predicate : this.predicates) { + if (predicate.test(t)) { + return true; + } + } + + return false; + } +} diff --git a/src/main/java/me/jellysquid/mods/hydrogen/mixin/client/model/json/MixinAndMultipartModelSelector.java b/src/main/java/me/jellysquid/mods/hydrogen/mixin/client/model/json/MixinAndMultipartModelSelector.java index 9f18748..f076cd2 100644 --- a/src/main/java/me/jellysquid/mods/hydrogen/mixin/client/model/json/MixinAndMultipartModelSelector.java +++ b/src/main/java/me/jellysquid/mods/hydrogen/mixin/client/model/json/MixinAndMultipartModelSelector.java @@ -1,17 +1,18 @@ package me.jellysquid.mods.hydrogen.mixin.client.model.json; -import me.jellysquid.mods.hydrogen.common.collections.CollectionHelper; +import com.google.common.collect.Streams; +import me.jellysquid.mods.hydrogen.common.state.StatePropertyPredicateHelper; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; import net.minecraft.client.render.model.json.AndMultipartModelSelector; import net.minecraft.client.render.model.json.MultipartModelSelector; +import net.minecraft.state.StateManager; 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 org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Redirect; -import java.util.Collection; -import java.util.List; -import java.util.stream.Collector; +import java.util.function.Predicate; import java.util.stream.Collectors; @Mixin(AndMultipartModelSelector.class) @@ -20,12 +21,14 @@ public class MixinAndMultipartModelSelector { @Final private Iterable selectors; - @Redirect(method = "getPredicate", at = @At(value = "INVOKE", target = "Ljava/util/stream/Collectors;toList()Ljava/util/stream/Collector;")) - private Collector> redirectGetPredicateCollector() { - if (this.selectors instanceof Collection) { - return CollectionHelper.toSizedList(((Collection) this.selectors).size()); - } - - return Collectors.toList(); + /** + * @author JellySquid + * @reason Flatten predicates + */ + @Overwrite + public Predicate getPredicate(StateManager stateManager) { + return StatePropertyPredicateHelper.allMatch(Streams.stream(this.selectors).map((multipartModelSelector) -> { + return multipartModelSelector.getPredicate(stateManager); + }).collect(Collectors.toList())); } } diff --git a/src/main/java/me/jellysquid/mods/hydrogen/mixin/client/model/json/MixinOrMultipartModelSelector.java b/src/main/java/me/jellysquid/mods/hydrogen/mixin/client/model/json/MixinOrMultipartModelSelector.java new file mode 100644 index 0000000..d7ca288 --- /dev/null +++ b/src/main/java/me/jellysquid/mods/hydrogen/mixin/client/model/json/MixinOrMultipartModelSelector.java @@ -0,0 +1,34 @@ +package me.jellysquid.mods.hydrogen.mixin.client.model.json; + +import com.google.common.collect.Streams; +import me.jellysquid.mods.hydrogen.common.state.StatePropertyPredicateHelper; +import me.jellysquid.mods.hydrogen.common.util.AnyPredicate; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.client.render.model.json.MultipartModelSelector; +import net.minecraft.client.render.model.json.OrMultipartModelSelector; +import net.minecraft.state.StateManager; +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 java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +@Mixin(OrMultipartModelSelector.class) +public class MixinOrMultipartModelSelector { + @Shadow @Final private Iterable selectors; + + /** + * @author JellySquid + * @reason Flatten predicates + */ + @Overwrite + public Predicate getPredicate(StateManager stateManager) { + return StatePropertyPredicateHelper.anyMatch(Streams.stream(this.selectors).map((multipartModelSelector) -> { + return multipartModelSelector.getPredicate(stateManager); + }).collect(Collectors.toList())); + } +} diff --git a/src/main/java/me/jellysquid/mods/hydrogen/mixin/client/model/json/MixinSimpleMultipartModelSelector.java b/src/main/java/me/jellysquid/mods/hydrogen/mixin/client/model/json/MixinSimpleMultipartModelSelector.java index 64ae3dc..1d86bdc 100644 --- a/src/main/java/me/jellysquid/mods/hydrogen/mixin/client/model/json/MixinSimpleMultipartModelSelector.java +++ b/src/main/java/me/jellysquid/mods/hydrogen/mixin/client/model/json/MixinSimpleMultipartModelSelector.java @@ -1,7 +1,8 @@ package me.jellysquid.mods.hydrogen.mixin.client.model.json; import com.google.common.base.Splitter; -import me.jellysquid.mods.hydrogen.common.collections.CollectionHelper; +import me.jellysquid.mods.hydrogen.common.state.single.SingleMatchAny; +import me.jellysquid.mods.hydrogen.common.state.single.SingleMatchOne; import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.client.render.model.json.SimpleMultipartModelSelector; @@ -11,19 +12,14 @@ import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Overwrite; import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Redirect; import java.util.List; import java.util.function.Predicate; -import java.util.stream.Collector; import java.util.stream.Collectors; @SuppressWarnings("UnstableApiUsage") @Mixin(SimpleMultipartModelSelector.class) public class MixinSimpleMultipartModelSelector { - private static final ThreadLocal> CAPTURED_LISTS = new ThreadLocal<>(); - @Shadow @Final private String key; @@ -32,45 +28,56 @@ public class MixinSimpleMultipartModelSelector { @Final private String valueString; - @Redirect(method = "getPredicate", at = @At(value = "INVOKE", target = "Lcom/google/common/base/Splitter;splitToList(Ljava/lang/CharSequence;)Ljava/util/List;", remap = false)) - private List captureSplitList(Splitter splitter, CharSequence sequence) { - List list = splitter.splitToList(sequence); - - CAPTURED_LISTS.set(list); - - return list; - } + @Shadow + @Final + private static Splitter VALUE_SPLITTER; /** * @author JellySquid */ - @Redirect(method = "getPredicate", at = @At(value = "INVOKE", target = "Ljava/util/stream/Collectors;toList()Ljava/util/stream/Collector;")) - private Collector> redirectGetPredicateCollector() { - List list = CAPTURED_LISTS.get(); + @Overwrite + public Predicate getPredicate(StateManager stateManager) { + Property property = stateManager.getProperty(this.key); + + if (property == null) { + throw new RuntimeException(String.format("Unknown property '%s' on '%s'", this.key, stateManager.getOwner().toString())); + } + + String valueString = this.valueString; + boolean negate = !valueString.isEmpty() && valueString.charAt(0) == '!'; + + if (negate) { + valueString = valueString.substring(1); + } + + List split = VALUE_SPLITTER.splitToList(valueString); + + if (split.isEmpty()) { + throw new RuntimeException(String.format("Empty value '%s' for property '%s' on '%s'", this.valueString, this.key, stateManager.getOwner().toString())); + } - if (list != null) { - CAPTURED_LISTS.remove(); + Predicate predicate; - return CollectionHelper.toSizedList(list.size()); + if (split.size() == 1) { + predicate = new SingleMatchOne(property, this.getPropertyValue(stateManager, property, valueString)); + } else { + predicate = SingleMatchAny.create(property, split.stream() + .map(str -> this.getPropertyValue(stateManager, property, str)) + .collect(Collectors.toList())); } - return Collectors.toList(); + return negate ? predicate.negate() : predicate; } - /** - * @author JellySquid - * @reason Avoid capturing the entire Optional - */ - @Overwrite - private Predicate createPredicate(StateManager stateFactory, Property property, String valueString) { + private Object getPropertyValue(StateManager stateFactory, Property property, String valueString) { Object value = property.parse(valueString) .orElse(null); if (value == null) { throw new RuntimeException(String.format("Unknown value '%s' for property '%s' on '%s' in '%s'", valueString, this.key, stateFactory.getOwner().toString(), this.valueString)); - } else { - return (blockState) -> blockState.get(property).equals(value); } + + return value; } } diff --git a/src/main/resources/hydrogen.mixins.json b/src/main/resources/hydrogen.mixins.json index 72d1b7b..05958db 100644 --- a/src/main/resources/hydrogen.mixins.json +++ b/src/main/resources/hydrogen.mixins.json @@ -10,13 +10,14 @@ "client.model.MixinModelLoader", "client.model.MixinMultipartBakedModel", "client.model.MixinWeightedBakedModel", + "client.model.json.MixinAndMultipartModelSelector", + "client.model.json.MixinOrMultipartModelSelector", "client.model.json.MixinModelOverride", "client.model.json.MixinModelOverrideList", - "client.model.json.MixinAndMultipartModelSelector", "client.model.json.MixinSimpleMultipartModelSelector" ], "mixins": [ - "state.MixinState", - "nbt.MixinCompoundTag" + "nbt.MixinCompoundTag", + "state.MixinState" ] } \ No newline at end of file