(A a, B b, C c, D d, E e, F f, G g, H h, I i, J j, K k,
+ L l, M m, N n, O o, P p) implements Tuple {
+ @Override
+ public List asList() {
+ return List.of(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p);
+ }
+ }
+}
diff --git a/src/main/java/com/mojang/serialization/codecs/TupleCodec.java b/src/main/java/com/mojang/serialization/codecs/TupleCodec.java
new file mode 100644
index 00000000..69e27f6a
--- /dev/null
+++ b/src/main/java/com/mojang/serialization/codecs/TupleCodec.java
@@ -0,0 +1,433 @@
+package com.mojang.serialization.codecs;
+
+import com.mojang.datafixers.util.*;
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.DataResult;
+import com.mojang.serialization.DynamicOps;
+import com.mojang.serialization.ListBuilder;
+import com.mojang.datafixers.util.Tuple.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+/**
+ * The TupleCodec class provides a heterogeneous list akin to how Tuple relates to List in a similar way Object does to Map.
+ * Example:
+ * {@code
+ * var codec = TupleCodec.tuple(Codec.INT, Codec.FLOAT); // T2Codec
+ * codec.decode("[1, 2.0]"); // Decodes to List(1, 2.0)
+ * }
+ * @author TT432
+ */
+@SuppressWarnings({"unchecked", "rawtypes", "unused"})
+public sealed interface TupleCodec extends Codec> {
+ List> getCodecs();
+
+ static T1Codec tuple(Codec codec1) {
+ return new T1Codec<>(codec1);
+ }
+
+ static T2Codec tuple(Codec codec1, Codec codec2) {
+ return new T2Codec<>(codec1, codec2);
+ }
+
+ static T3Codec tuple(Codec codec1, Codec codec2, Codec codec3) {
+ return new T3Codec<>(codec1, codec2, codec3);
+ }
+
+ static T4Codec tuple(Codec codec1, Codec codec2, Codec codec3, Codec codec4) {
+ return new T4Codec<>(codec1, codec2, codec3, codec4);
+ }
+
+ static T5Codec tuple(Codec codec1, Codec codec2, Codec codec3, Codec codec4, Codec codec5) {
+ return new T5Codec<>(codec1, codec2, codec3, codec4, codec5);
+ }
+
+ static T6Codec tuple(Codec codec1, Codec codec2, Codec codec3, Codec codec4, Codec codec5, Codec codec6) {
+ return new T6Codec<>(codec1, codec2, codec3, codec4, codec5, codec6);
+ }
+
+ static T7Codec tuple(Codec codec1, Codec codec2, Codec codec3, Codec codec4, Codec codec5, Codec codec6, Codec codec7) {
+ return new T7Codec<>(codec1, codec2, codec3, codec4, codec5, codec6, codec7);
+ }
+
+ static T8Codec tuple(Codec codec1, Codec codec2, Codec codec3, Codec codec4, Codec codec5, Codec codec6, Codec codec7, Codec codec8) {
+ return new T8Codec<>(codec1, codec2, codec3, codec4, codec5, codec6, codec7, codec8);
+ }
+
+ static T9Codec tuple(Codec codec1, Codec codec2, Codec codec3, Codec codec4, Codec codec5, Codec codec6, Codec codec7, Codec codec8, Codec codec9) {
+ return new T9Codec<>(codec1, codec2, codec3, codec4, codec5, codec6, codec7, codec8, codec9);
+ }
+
+ static T10Codec tuple(Codec codec1, Codec codec2, Codec codec3, Codec codec4, Codec codec5, Codec codec6, Codec codec7, Codec codec8, Codec codec9, Codec codec10) {
+ return new T10Codec<>(codec1, codec2, codec3, codec4, codec5, codec6, codec7, codec8, codec9, codec10);
+ }
+
+ static T11Codec tuple(Codec codec1, Codec codec2, Codec codec3, Codec codec4, Codec codec5, Codec codec6, Codec codec7, Codec codec8, Codec codec9, Codec codec10, Codec codec11) {
+ return new T11Codec<>(codec1, codec2, codec3, codec4, codec5, codec6, codec7, codec8, codec9, codec10, codec11);
+ }
+
+ static T12Codec tuple(Codec codec1, Codec codec2, Codec codec3, Codec codec4, Codec codec5, Codec codec6, Codec codec7, Codec codec8, Codec codec9, Codec codec10, Codec codec11, Codec codec12) {
+ return new T12Codec<>(codec1, codec2, codec3, codec4, codec5, codec6, codec7, codec8, codec9, codec10, codec11, codec12);
+ }
+
+ static T13Codec tuple(Codec codec1, Codec codec2, Codec codec3, Codec codec4, Codec codec5, Codec codec6, Codec codec7, Codec codec8, Codec codec9, Codec codec10, Codec codec11, Codec codec12, Codec codec13) {
+ return new T13Codec<>(codec1, codec2, codec3, codec4, codec5, codec6, codec7, codec8, codec9, codec10, codec11, codec12, codec13);
+ }
+
+ static T14Codec tuple(Codec codec1, Codec codec2, Codec codec3, Codec codec4, Codec codec5, Codec codec6, Codec codec7, Codec codec8, Codec codec9, Codec codec10, Codec codec11, Codec codec12, Codec codec13, Codec codec14) {
+ return new T14Codec<>(codec1, codec2, codec3, codec4, codec5, codec6, codec7, codec8, codec9, codec10, codec11, codec12, codec13, codec14);
+ }
+
+ static T15Codec tuple(Codec codec1, Codec codec2, Codec codec3, Codec codec4, Codec codec5, Codec codec6, Codec codec7, Codec codec8, Codec codec9, Codec codec10, Codec codec11, Codec codec12, Codec codec13, Codec codec14, Codec codec15) {
+ return new T15Codec<>(codec1, codec2, codec3, codec4, codec5, codec6, codec7, codec8, codec9, codec10, codec11, codec12, codec13, codec14, codec15);
+ }
+
+ static T16Codec tuple(Codec codec1, Codec codec2, Codec codec3, Codec codec4, Codec codec5, Codec codec6, Codec codec7, Codec codec8, Codec codec9, Codec codec10, Codec codec11, Codec codec12, Codec codec13, Codec codec14, Codec codec15, Codec codec16) {
+ return new T16Codec<>(codec1, codec2, codec3, codec4, codec5, codec6, codec7, codec8, codec9, codec10, codec11, codec12, codec13, codec14, codec15, codec16);
+ }
+
+ @Override
+ default DataResult, T>> decode(DynamicOps ops, T input) {
+ DataResult> stream = ops.getStream(input);
+ return stream.error()
+ ., T>>>map(streamPartialResult ->
+ DataResult.error(streamPartialResult::message))
+ .orElse(stream.result()., T>>>map(s -> {
+ List> codecs = getCodecs();
+ List list = s.toList();
+
+ if (list.size() != codecs.size()) {
+ return DataResult.error(() -> "can't process as " + this + ", size not equals.");
+ }
+
+ List result = new ArrayList<>();
+
+ for (int i = 0; i < codecs.size(); i++) {
+ var decode = codecs.get(i).decode(ops, list.get(i)).get().mapBoth(Pair::getFirst, Function.identity());
+
+ decode.ifLeft(result::add);
+
+ var right = decode.right();
+
+ if (right.isPresent()) {
+ return DataResult.error(() -> this + " error: " + right.get().message());
+ }
+ }
+
+ return DataResult.success(Pair.of(result, input));
+ }).orElse(DataResult.error(() -> this + " can't parse as list.")));
+ }
+
+ @Override
+ default DataResult encode(List input, DynamicOps ops, T prefix) {
+ List> codecs = getCodecs();
+
+ if (input.size() != codecs.size())
+ return DataResult.error(() -> "can't encode " + this + ", because input array size not equals this.");
+
+ final ListBuilder builder = ops.listBuilder();
+
+ for (int i = 0; i < input.size(); i++) {
+ builder.add(((Codec) codecs.get(i)).encodeStart(ops, input.get(i)));
+ }
+
+ return builder.build(prefix);
+ }
+
+ record T1Codec(Codec codec) implements TupleCodec {
+ @Override
+ public List> getCodecs() {
+ return List.of(codec);
+ }
+
+ public Codec bmap(Function to, Function from) {
+ return xmap(l -> to.apply((A) l.get(0)), from.andThen(List::of));
+ }
+
+ @Override
+ public String toString() {
+ return "Tuple1[" + codec + "]";
+ }
+ }
+
+ record T2Codec(Codec codec1, Codec codec2) implements TupleCodec {
+ @Override
+ public List> getCodecs() {
+ return List.of(codec1, codec2);
+ }
+
+ public Codec bmap(BiFunction to, Function> from) {
+ return xmap(l -> to.apply((A) l.get(0), (B) l.get(1)), from.andThen(Tuple::asList));
+ }
+
+ @Override
+ public String toString() {
+ return "Tuple2[" + codec1 + ", " + codec2 + "]";
+ }
+ }
+
+ record T3Codec(Codec codec1, Codec codec2, Codec codec3) implements TupleCodec {
+ @Override
+ public List> getCodecs() {
+ return List.of(codec1, codec2, codec3);
+ }
+
+ public Codec bmap(Function3 to, Function> from) {
+ return xmap(l -> to.apply((A) l.get(0), (B) l.get(1), (C) l.get(2)), from.andThen(Tuple::asList));
+ }
+
+ @Override
+ public String toString() {
+ return "Tuple3[" + codec1 + ", " + codec2 + ", " + codec3 + "]";
+ }
+ }
+
+ record T4Codec(Codec codec1, Codec codec2, Codec codec3,
+ Codec codec4) implements TupleCodec {
+ @Override
+ public List> getCodecs() {
+ return List.of(codec1, codec2, codec3, codec4);
+ }
+
+ public Codec bmap(Function4 to, Function> from) {
+ return xmap(l -> to.apply((A) l.get(0), (B) l.get(1), (C) l.get(2), (D) l.get(3)), from.andThen(Tuple::asList));
+ }
+
+ @Override
+ public String toString() {
+ return "Tuple4[" + codec1 + ", " + codec2 + ", " + codec3 + ", " + codec4 + "]";
+ }
+ }
+
+ record T5Codec(Codec codec1, Codec codec2, Codec codec3, Codec codec4,
+ Codec codec5) implements TupleCodec {
+ @Override
+ public List> getCodecs() {
+ return List.of(codec1, codec2, codec3, codec4, codec5);
+ }
+
+ public Codec bmap(Function5 to, Function> from) {
+ return xmap(l -> to.apply((A) l.get(0), (B) l.get(1), (C) l.get(2), (D) l.get(3), (E) l.get(4)), from.andThen(Tuple::asList));
+ }
+
+ @Override
+ public String toString() {
+ return "Tuple5[" + codec1 + ", " + codec2 + ", " + codec3 + ", " + codec4 + ", " + codec5 + "]";
+ }
+ }
+
+ record T6Codec(Codec codec1, Codec codec2, Codec codec3, Codec codec4,
+ Codec codec5, Codec codec6) implements TupleCodec {
+ @Override
+ public List> getCodecs() {
+ return List.of(codec1, codec2, codec3, codec4, codec5, codec6);
+ }
+
+ public Codec bmap(Function6 to, Function> from) {
+ return xmap(l -> to.apply((A) l.get(0), (B) l.get(1), (C) l.get(2), (D) l.get(3), (E) l.get(4), (F) l.get(5)), from.andThen(Tuple::asList));
+ }
+
+ @Override
+ public String toString() {
+ return "Tuple6[" + codec1 + ", " + codec2 + ", " + codec3 + ", " + codec4 + ", " + codec5 + ", " + codec6 + "]";
+ }
+ }
+
+ record T7Codec(Codec codec1, Codec codec2, Codec codec3, Codec codec4,
+ Codec codec5, Codec codec6, Codec codec7) implements TupleCodec {
+ @Override
+ public List> getCodecs() {
+ return List.of(codec1, codec2, codec3, codec4, codec5, codec6, codec7);
+ }
+
+ public Codec bmap(Function7 to, Function> from) {
+ return xmap(l -> to.apply((A) l.get(0), (B) l.get(1), (C) l.get(2), (D) l.get(3), (E) l.get(4), (F) l.get(5), (G) l.get(6)), from.andThen(Tuple::asList));
+ }
+
+ @Override
+ public String toString() {
+ return "Tuple7[" + codec1 + ", " + codec2 + ", " + codec3 + ", " + codec4 + ", " + codec5 + ", " + codec6 + ", " + codec7 + "]";
+ }
+ }
+
+ record T8Codec(Codec codec1, Codec codec2, Codec codec3, Codec codec4,
+ Codec codec5, Codec codec6, Codec codec7,
+ Codec codec8) implements TupleCodec {
+ @Override
+ public List> getCodecs() {
+ return List.of(codec1, codec2, codec3, codec4, codec5, codec6, codec7, codec8);
+ }
+
+ public