Skip to content

Rendering API v1 (Draft) #171

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 14 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ allprojects {
maven(url = "https://maven.glass-launcher.net/babric")
maven(url = "https://maven.glass-launcher.net/snapshots")
maven(url = "https://maven.glass-launcher.net/releases")
maven(url = "https://oss.sonatype.org/content/repositories/snapshots/")
maven(url = "https://jitpack.io/")
mavenCentral()
exclusiveContent {
Expand Down Expand Up @@ -74,6 +75,7 @@ allprojects {
"transitiveImplementation"("com.github.ben-manes.caffeine:caffeine:${project.properties["caffeine_version"]}")
"transitiveImplementation"("com.mojang:datafixerupper:${project.properties["dfu_version"]}")
"transitiveImplementation"("maven.modrinth:spasm:${project.properties["spasm_version"]}")
"transitiveImplementation"("org.joml:joml:${project.properties["joml_version"]}")

// convenience stuff
// adds some useful annotations for data classes. does not add any dependencies
Expand Down Expand Up @@ -257,6 +259,7 @@ dependencies {
include("com.github.ben-manes.caffeine:caffeine:${project.properties["caffeine_version"]}")
include("com.mojang:datafixerupper:${project.properties["dfu_version"]}")
include("maven.modrinth:spasm:${project.properties["spasm_version"]}")
include("org.joml:joml:${project.properties["joml_version"]}")
}

// Makes java shut up
Expand Down
3 changes: 2 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ fabric.loom.multiProjectOptimisation=true
unsafeevents_version = e31096e
fastutil_version = 8.5.8
caffeine_version = 3.0.5
dfu_version = 6.0.6
dfu_version = 8.0.16
spasm_version = 0.2.2
joml_version = 1.10.8

# Mod Properties
mod_version = 2.0.0-alpha.4
Expand Down
3 changes: 2 additions & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ include(":station-armor-api-v0")
include(":station-localization-api-v0")
include(":station-achievements-v0")
include(":station-keybindings-v0")
include(":station-renderer-api-v0")
//include(":station-renderer-api-v0")
include(":station-renderer-api-v1")
include(":station-audio-loader-v0")
include(":station-lifecycle-events-v0")
include(":station-vanilla-checker-v0")
Expand Down
2 changes: 1 addition & 1 deletion src/test/java/net/modificationstation/sltest/SLTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import org.apache.logging.log4j.Logger;

public class SLTest {
public static final Namespace NAMESPACE = Namespace.resolve();
public static final Namespace NAMESPACE = Namespace.of("sltest");

public static final Logger LOGGER = NAMESPACE.getLogger();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,45 +1,81 @@
package net.modificationstation.stationapi.api.util;

import com.mojang.datafixers.util.Pair;
import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import com.mojang.serialization.DynamicOps;
import com.mojang.serialization.Keyable;
import it.unimi.dsi.fastutil.objects.Object2ReferenceMap;
import it.unimi.dsi.fastutil.objects.Object2ReferenceMaps;
import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap;
import net.modificationstation.stationapi.api.util.dynamic.Codecs;
import org.jetbrains.annotations.Nullable;

import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.function.ToIntFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
* An interface, usually implemented by enums, that allows the object to be serialized
* by codecs. An instance is identified using a string.
*
* @apiNote To make an enum serializable with codecs, implement this on the enum class,
* implement {@link #asString} to return a unique ID, and add a {@code static final}
* field that holds {@linkplain #createCodec the codec for the enum}.
*/
public interface StringIdentifiable {
int MAPIFY_THRESHOLD = 16;

/**
* {@return the unique string representation of the enum, used for serialization}
*/
String asString();

/**
* Creates a codec that serializes an enum implementing this interface either
* using its ordinals (when compressed) or using its {@link #asString()} method.
*/
static <E extends Enum<E> & StringIdentifiable> StringIdentifiable.EnumCodec<E> createCodec(Supplier<E[]> enumValues) {
return createCodec(enumValues, id -> id);
}

/**
* Creates a codec that serializes an enum implementing this interface either
* using its ordinals (when compressed) or using its {@link #asString()} method
* and a given decode function.
*/
static <E extends Enum<E> & StringIdentifiable> Codec<E> createCodec(Supplier<E[]> enumValues) {
E[] enums = enumValues.get();
if (enums.length > MAPIFY_THRESHOLD) {
//noinspection Convert2MethodRef
Object2ReferenceMap<String, E> map = Object2ReferenceMaps.unmodifiable(Arrays.stream(enums).collect(Collectors.toMap(StringIdentifiable::asString, Function.identity(), (o, o2) -> { throw new IllegalStateException(String.format("Duplicate key %s", o)); }, () -> new Object2ReferenceOpenHashMap<>())));
return new Codec<>(enums, id -> id == null ? null : map.get(id));
static <E extends Enum<E> & StringIdentifiable> StringIdentifiable.EnumCodec<E> createCodec(
Supplier<E[]> enumValues, Function<String, String> valueNameTransformer
) {
E[] enums = (E[])enumValues.get();
Function<String, E> function = createMapper(enums, valueNameTransformer);
return new StringIdentifiable.EnumCodec<>(enums, function);
}

static <T extends StringIdentifiable> Codec<T> createBasicCodec(Supplier<T[]> values) {
T[] identifiableValues = values.get();
Function<String, T> function = createMapper(identifiableValues, valueName -> valueName);
ToIntFunction<T> toIntFunction = Util.lastIndexGetter(Arrays.asList(identifiableValues));
return new StringIdentifiable.BasicCodec<>(identifiableValues, function, toIntFunction);
}

static <T extends StringIdentifiable> Function<String, T> createMapper(T[] values, Function<String, String> valueNameTransformer) {
if (values.length > 16) {
Map<String, T> map = Arrays.stream(values)
.collect(Collectors.toMap(value -> valueNameTransformer.apply(value.asString()), value -> value));
return name -> name == null ? null : map.get(name);
} else {
return name -> {
for (T stringIdentifiable : values) {
if (valueNameTransformer.apply(stringIdentifiable.asString()).equals(name)) {
return stringIdentifiable;
}
}

return null;
};
}
return new Codec<>(enums, id -> {
for (E enum_ : enums) {
if (!enum_.asString().equals(id)) continue;
return enum_;
}
return null;
});
}

static Keyable toKeyable(final StringIdentifiable[] values) {
Expand All @@ -52,29 +88,45 @@ public <T> Stream<T> keys(DynamicOps<T> ops) {
};
}

class Codec<E extends Enum<E>>
implements com.mojang.serialization.Codec<E> {
private final com.mojang.serialization.Codec<E> base;
private final Function<String, E> idToIdentifiable;
class BasicCodec<S extends StringIdentifiable> implements Codec<S> {
private final Codec<S> codec;

public Codec(E[] values, Function<String, E> idToIdentifiable) {
this.base = Codecs.orCompressed(Codecs.idChecked(identifiable -> ((StringIdentifiable) identifiable).asString(), idToIdentifiable), Codecs.rawIdChecked(Enum::ordinal, ordinal -> ordinal >= 0 && ordinal < values.length ? values[ordinal] : null, -1));
this.idToIdentifiable = idToIdentifiable;
public BasicCodec(S[] values, Function<String, S> idToIdentifiable, ToIntFunction<S> identifiableToOrdinal) {
this.codec = Codecs.orCompressed(
Codec.stringResolver(StringIdentifiable::asString, idToIdentifiable),
Codecs.rawIdChecked(identifiableToOrdinal, ordinal -> ordinal >= 0 && ordinal < values.length ? values[ordinal] : null, -1)
);
}

@Override
public <T> DataResult<Pair<E, T>> decode(DynamicOps<T> ops, T input) {
return this.base.decode(ops, input);
public <T> DataResult<com.mojang.datafixers.util.Pair<S, T>> decode(DynamicOps<T> ops, T input) {
return this.codec.decode(ops, input);
}

@Override
public <T> DataResult<T> encode(E enum_, DynamicOps<T> dynamicOps, T object) {
return this.base.encode(enum_, dynamicOps, object);
public <T> DataResult<T> encode(S stringIdentifiable, DynamicOps<T> dynamicOps, T object) {
return this.codec.encode(stringIdentifiable, dynamicOps, object);
}
}

class EnumCodec<E extends Enum<E> & StringIdentifiable> extends StringIdentifiable.BasicCodec<E> {
private final Function<String, E> idToIdentifiable;

public EnumCodec(E[] values, Function<String, E> idToIdentifiable) {
super(values, idToIdentifiable, Enum::ordinal);
this.idToIdentifiable = idToIdentifiable;
}

@Nullable
public E byId(@Nullable String id) {
return this.idToIdentifiable.apply(id);
return (E)this.idToIdentifiable.apply(id);
}

public E byId(@Nullable String id, E fallback) {
return Objects.requireNonNullElse(this.byId(id), fallback);
}

public E byId(@Nullable String id, Supplier<? extends E> fallbackSupplier) {
return Objects.requireNonNullElseGet(this.byId(id), fallbackSupplier);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,17 @@ public Boolean getBoolObj() {
return value;
}

/**
* Gets the value of this tri-state.
* If the value is {@link TriState#UNSET} then use the supplied value.
*
* @param value the value to fall back to
* @return the value of the tri-state or the supplied value if {@link TriState#UNSET}.
*/
public boolean orElse(boolean value) {
return this == UNSET ? value : this.getBool();
}

@API
public boolean getBool() {
Boolean b = getBoolObj();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@
import com.mojang.serialization.DataResult;
import it.unimi.dsi.fastutil.Hash;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap;
import it.unimi.dsi.fastutil.objects.Reference2ReferenceMaps;
import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
import it.unimi.dsi.fastutil.objects.*;
import net.fabricmc.loader.api.FabricLoader;
import net.modificationstation.stationapi.api.util.crash.CrashException;
import org.apache.commons.io.IOUtils;
Expand Down Expand Up @@ -294,6 +291,22 @@ public static <T> void shuffle(ObjectArrayList<T> list, Random random) {
}
}

public static <T> ToIntFunction<T> lastIndexGetter(List<T> values) {
int i = values.size();
if (i < 8) {
return values::indexOf;
} else {
Object2IntMap<T> object2IntMap = new Object2IntOpenHashMap<>(i);
object2IntMap.defaultReturnValue(-1);

for (int j = 0; j < i; j++) {
object2IntMap.put((T)values.get(j), j);
}

return object2IntMap;
}
}

public static Consumer<String> addPrefix(String prefix, Consumer<String> consumer) {
return string -> consumer.accept(prefix + string);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package net.modificationstation.stationapi.api.util.dynamic;

import com.google.common.base.Suppliers;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.ImmutableList;
import com.mojang.datafixers.util.Pair;
import com.mojang.serialization.*;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import it.unimi.dsi.fastutil.floats.FloatArrayList;
import it.unimi.dsi.fastutil.floats.FloatList;
import net.modificationstation.stationapi.api.util.Identifier;
import net.modificationstation.stationapi.api.util.Util;
import net.modificationstation.stationapi.api.util.Uuids;
import org.apache.commons.lang3.mutable.MutableObject;
import org.joml.*;

import java.time.Instant;
import java.time.format.DateTimeFormatter;
Expand All @@ -27,6 +32,50 @@
*/
public class Codecs {
public static final Codec<UUID> UUID = Uuids.CODEC;
public static final Codec<Vector3f> VECTOR_3F = Codec.FLOAT
.listOf()
.comapFlatMap(
list -> Util.toArray(list, 3).map(elements -> new Vector3f(elements.get(0), elements.get(1), elements.get(2))),
vec -> List.of(vec.x(), vec.y(), vec.z())
);
public static final Codec<Vector4f> VECTOR_4F = Codec.FLOAT
.listOf()
.comapFlatMap(
list -> Util.toArray(list, 4).map(elements -> new Vector4f(elements.get(0), elements.get(1), elements.get(2), elements.get(3))),
vec -> List.of(vec.x(), vec.y(), vec.z(), vec.w())
);
public static final Codec<Quaternionf> QUATERNION_F = Codec.FLOAT
.listOf()
.comapFlatMap(
list -> Util.toArray(list, 4)
.map(elements -> new Quaternionf(elements.get(0), elements.get(1), elements.get(2), elements.get(3)).normalize()),
quaternion -> List.of(quaternion.x, quaternion.y, quaternion.z, quaternion.w)
);
public static final Codec<AxisAngle4f> AXIS_ANGLE_4F = RecordCodecBuilder.create(
instance -> instance.group(
Codec.FLOAT.fieldOf("angle").forGetter(axisAngle4f -> axisAngle4f.angle),
VECTOR_3F.fieldOf("axis").forGetter(axisAngle4f -> new Vector3f(axisAngle4f.x, axisAngle4f.y, axisAngle4f.z))
)
.apply(instance, AxisAngle4f::new)
);
public static final Codec<Quaternionf> ROTATION = Codec.withAlternative(QUATERNION_F, AXIS_ANGLE_4F.xmap(Quaternionf::new, AxisAngle4f::new));
public static final Codec<Matrix4fc> MATRIX_4F = Codec.FLOAT.listOf().comapFlatMap(list -> Util.toArray(list, 16).map(elements -> {
Matrix4f matrix4f = new Matrix4f();

for (int i = 0; i < elements.size(); i++) {
matrix4f.setRowColumn(i >> 2, i & 3, elements.get(i));
}

return matrix4f.determineProperties();
}), matrix4f -> {
FloatList floatList = new FloatArrayList(16);

for (int i = 0; i < 16; i++) {
floatList.add(matrix4f.getRowColumn(i >> 2, i & 3));
}

return floatList;
});
public static final Codec<Integer> NONNEGATIVE_INT = Codecs.rangedInt(0, Integer.MAX_VALUE, v -> "Value must be non-negative: " + v);
public static final Codec<Integer> POSITIVE_INT = Codecs.rangedInt(1, Integer.MAX_VALUE, v -> "Value must be positive: " + v);
public static final Codec<Float> POSITIVE_FLOAT = Codecs.rangedFloat(0.0f, Float.MAX_VALUE, v -> "Value must be positive: " + v);
Expand Down Expand Up @@ -116,8 +165,14 @@ public static <E> Codec<E> rawIdChecked(ToIntFunction<E> elementToRawId, IntFunc
});
}

public static <E> Codec<E> idChecked(Function<E, String> elementToId, Function<String, E> idToElement) {
return Codec.STRING.flatXmap(id -> Optional.ofNullable(idToElement.apply(id)).map(DataResult::success).orElseGet(() -> DataResult.error(() -> "Unknown element name:" + id)), element -> Optional.ofNullable(elementToId.apply(element)).map(DataResult::success).orElseGet(() -> DataResult.error(() -> "Element with unknown name: " + element)));
public static <I, E> Codec<E> idChecked(Codec<I> idCodec, Function<I, E> idToElement, Function<E, I> elementToId) {
return idCodec.flatXmap(id -> {
E object = (E)idToElement.apply(id);
return object == null ? DataResult.error(() -> "Unknown element id: " + id) : DataResult.success(object);
}, element -> {
I object = (I)elementToId.apply(element);
return object == null ? DataResult.error(() -> "Element with unknown id: " + element) : DataResult.success(object);
});
}

public static <E> Codec<E> orCompressed(final Codec<E> uncompressedCodec, final Codec<E> compressedCodec) {
Expand Down Expand Up @@ -394,6 +449,26 @@ public <T> DataResult<T> encode(A input, DynamicOps<T> ops, T prefix) {
}
}

public static class IdMapper<I, V> {
private final BiMap<I, V> values = HashBiMap.create();

public Codec<V> getCodec(Codec<I> idCodec) {
BiMap<V, I> biMap = this.values.inverse();
return Codecs.idChecked(idCodec, this.values::get, biMap::get);
}

public Codecs.IdMapper<I, V> put(I id, V value) {
Objects.requireNonNull(value, () -> "Value for " + id + " is null");
this.values.put(id, value);
return this;
}

public V putIfAbsent(I id, V value) {
Objects.requireNonNull(value, () -> "Value for " + id + " is null");
return this.values.putIfAbsent(id, value);
}
}

public record TagEntryId(Identifier id, boolean tag) {
@Override
public String toString() {
Expand Down
2 changes: 1 addition & 1 deletion station-blockitems-v0/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ addModuleDependencies(project,
"station-registry-api-v0",
"station-blocks-v0",
"station-items-v0",
"station-renderer-api-v0",
"station-renderer-api-v1",
"station-templates-v0",
"station-flattening-v0"
)
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ private boolean stationapi_handlePlace(
.world(world)
.player(player)
.x(x).y(y).z(z)
.side(Direction.byId(side))
.side(Direction.byIndex(side))
.block(BlockRegistry.INSTANCE.get(id))
.meta(meta)
.blockItem(blockItem)
Expand Down
Loading