Skip to content
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

Text codec #31

Open
wants to merge 11 commits into
base: sinoalice
Choose a base branch
from
144 changes: 144 additions & 0 deletions src/main/kotlin/dev/robustum/core/codec/KotlinOps.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package dev.robustum.core.codec

import com.mojang.datafixers.util.Pair
import com.mojang.serialization.DataResult
import com.mojang.serialization.DynamicOps
import dev.robustum.core.extensions.DataPair
import it.unimi.dsi.fastutil.bytes.ByteList
import it.unimi.dsi.fastutil.ints.IntList
import it.unimi.dsi.fastutil.longs.LongList
import java.nio.ByteBuffer
import java.util.stream.Collectors
import java.util.stream.Stream

object KotlinOps : DynamicOps<Any> {
override fun empty(): Any = Unit

override fun <U : Any> convertTo(outOps: DynamicOps<U>, input: Any): U {
if (input is Map<*, *>) {
return convertMap(outOps, input)
}
if (input is ByteList) {
return outOps.createByteList(ByteBuffer.wrap(input.toByteArray()))
}
if (input is IntList) {
return outOps.createIntList(input.stream().mapToInt { it })
}
if (input is LongList) {
return outOps.createLongList(input.stream().mapToLong { it })
}
if (input is List<*>) {
return convertList(outOps, input)
}
if (input is String) {
return outOps.createString(input)
}
if (input is Boolean) {
return outOps.createBoolean(input)
}
if (input is Byte) {
return outOps.createByte(input)
}
if (input is Short) {
return outOps.createShort(input)
}
if (input is Int) {
return outOps.createInt(input)
}
if (input is Long) {
return outOps.createLong(input)
}
if (input is Float) {
return outOps.createFloat(input)
}
if (input is Double) {
return outOps.createDouble(input)
}
if (input is Number) {
return outOps.createNumeric(input)
}
throw IllegalStateException("Unsupported class: $input")
}

override fun getNumberValue(input: Any): DataResult<Number> = when (input) {
is Number -> DataResult.success(input)
else -> DataResult.error("Not a number: $input")
}

override fun createNumeric(i: Number): Any = i

override fun getStringValue(input: Any): DataResult<String> = when (input) {
is String -> DataResult.success(input)
else -> DataResult.error("Not a string: $input")
}

override fun createString(value: String): Any = value

override fun mergeToList(list: Any, value: Any): DataResult<in Any> {
if (list == empty()) {
return DataResult.success(listOf(value))
}
if (list is List<*>) {
if (list.isEmpty()) {
return DataResult.success(listOf(value))
}
return DataResult.success(
buildList {
addAll(list)
add(value)
},
)
}
return DataResult.error("Not a list: $list")
}

override fun mergeToMap(map: Any, key: Any, value: Any): DataResult<in Any> {
if (map == empty()) {
return DataResult.success(mapOf(key to value))
}
if (map is Map<*, *>) {
if (map.isEmpty()) {
return DataResult.success(mapOf(key to value))
}
return DataResult.success(
buildMap {
putAll(map)
put(key, value)
},
)
}
return DataResult.error("Not a map: $map")
}

override fun getMapValues(input: Any): DataResult<Stream<Pair<in Any, in Any>>> {
if (input is Map<*, *>) {
return DataResult.success(
input
.mapNotNull { (k, v) ->
if (k == null) return@mapNotNull null
if (v == null) return@mapNotNull null
DataPair.of(k, v)
}.stream(),
)
}
return DataResult.error("Not a map: $input")
}

override fun createMap(map: Stream<Pair<in Any, in Any>>): Any = map.collect(Collectors.toMap({ it.first }, { it.second }))

override fun getStream(input: Any): DataResult<Stream<in Any>> = when (input) {
is List<*> -> DataResult.success(input.stream())
else -> DataResult.error("Not a list: $input")
}

override fun createList(input: Stream<in Any>): Any = input.toList()

override fun remove(input: Any, key: String): Any = when (input) {
is Map<*, *> -> buildMap {
putAll(input)
remove(key)
}

else -> input
}
}
31 changes: 29 additions & 2 deletions src/main/kotlin/dev/robustum/core/codec/RobustumCodecs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,29 @@ import dev.robustum.core.recipe.ItemIngredient
import dev.robustum.core.registry.RegistryEntryList
import net.minecraft.block.Block
import net.minecraft.block.Blocks
import net.minecraft.fluid.Fluid
import net.minecraft.fluid.Fluids
import net.minecraft.item.Item
import net.minecraft.item.ItemStack
import net.minecraft.item.Items
import net.minecraft.nbt.NbtCompound
import net.minecraft.recipe.Ingredient
import net.minecraft.tag.Tag
import net.minecraft.tag.TagGroup
import net.minecraft.util.DyeColor
import net.minecraft.util.Identifier
import net.minecraft.util.registry.Registry
import java.util.*

object RobustumCodecs {
// Any //

@JvmField
val ANY: Codec<Any> = KotlinOps.toCodec()

// Block //
/**
* [net.minecraft.block.Blocks.AIR]を受け付けない[net.minecraft.block.Block]の[com.mojang.serialization.Codec]です。
* [Blocks.AIR]を受け付けない[Block]の[Codec]です。
*/
@JvmField
val BLOCK: Codec<Block> = lazyCodec { Registry.BLOCK }.validate { block: Block ->
Expand All @@ -33,9 +41,28 @@ object RobustumCodecs {
}
}

// DyeColor //
/**
* [DyeColor]の[Codec]です。
*/
@JvmField
val DYE_COLOR: Codec<DyeColor> = identifiedCodec(DyeColor.entries)

// Fluid //
/**
* [Fluids.EMPTY]を受け付けない[Fluid]の[Codec]です。
*/
@JvmField
val FLUID: Codec<Fluid> = lazyCodec { Registry.FLUID }.validate { fluid: Fluid ->
when (fluid) {
Fluids.EMPTY -> DataResult.error("Fluid must not be minecraft:empty")
else -> DataResult.success(fluid)
}
}

// ItemStack //
/**
* [net.minecraft.item.Items.AIR]を受け付けない[net.minecraft.item.Item]の[Codec]です。
* [Items.AIR]を受け付けない[Item]の[Codec]です。
*/
@JvmField
val ITEM: Codec<Item> = lazyCodec { Registry.ITEM }.validate { item: Item ->
Expand Down
84 changes: 77 additions & 7 deletions src/main/kotlin/dev/robustum/core/extensions/CodecExtensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,31 @@ 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.MapCodec
import dev.robustum.core.codec.KeyDispatchCodec
import com.mojang.serialization.Encoder
import dev.robustum.core.codec.OptionalCodec
import net.minecraft.util.StringIdentifiable
import net.minecraft.util.collection.DefaultedList
import java.util.*
import java.util.function.Function
import kotlin.jvm.optionals.getOrNull

typealias DataPair<F, S> = Pair<F, S>

// Encoder //

/**
* 指定された[input]を[B]にキャストしてエンコードします。
* @param A [input]のクラス
* @param B [A]を継承したクラス
* @param T [ops]のクラス
* @return [A]を[B]にキャストできなかった場合は[DataResult.error]
*/
@Suppress("UNCHECKED_CAST")
fun <A : Any, B : A, T : Any> Encoder<B>.forceEncode(input: A, ops: DynamicOps<T>, prefix: T): DataResult<T> =
(input as? B)?.let { encode(it, ops, prefix) } ?: DataResult.error("Failed to encode!")

// Decoder //

// Codec //

/**
Expand All @@ -28,9 +44,41 @@ fun <A : Any> lazyCodec(getter: () -> Codec<A>): Codec<A> = object : Codec<A> {
override fun <T : Any> decode(ops: DynamicOps<T>, input: T): DataResult<Pair<A, T>> = getter().decode(ops, input)
}

fun <A : Any> alternativeCodec(first: Codec<A>, second: Codec<A>): Codec<A> = Codec.either(first, second).xmap(
{ either: Either<A, A> -> either.map(Function.identity(), Function.identity()) },
Either<A, A>::left,
fun <A : Any> anyCodec(vararg child: Codec<out A>): Codec<A> = anyCodec(child.toList())

/**
* 指定された[children]のいずれかで変換する[Codec]を返します。
*/
fun <A : Any> anyCodec(children: List<Codec<out A>>): Codec<A> = object : Codec<A> {
override fun <T : Any> encode(input: A, ops: DynamicOps<T>, prefix: T): DataResult<T> {
for (codec: Codec<out A> in children) {
runCatching {
val result: DataResult<T> = codec.forceEncode(input, ops, prefix)
if (result.isSucceeded) {
return result
}
}
}
return DataResult.error("Failed to encode input!")
}

override fun <T : Any> decode(ops: DynamicOps<T>, input: T): DataResult<Pair<A, T>> {
for (codec: Codec<out A> in children) {
val result: DataResult<out Pair<out A, T>> = codec.decode(ops, input)
if (result.isSucceeded) {
return result.map { pair: Pair<out A, T> -> pair.mapFirst { it as A } }
}
}
return DataResult.error("Failed to decode input!")
}
}

/**
* [StringIdentifiable.asString]を元に,指定された[entries]から[Codec]を返します。
*/
fun <T : StringIdentifiable> identifiedCodec(entries: Collection<T>): Codec<T> = Codec.STRING.comapFlatMap(
{ name: String -> entries.firstOrNull { entry: T -> entry.asString() == name }.toDataResult("Unknown entry: $name") },
StringIdentifiable::asString,
)

/**
Expand All @@ -41,7 +89,7 @@ fun <A : Any> alternativeCodec(first: Codec<A>, second: Codec<A>): Codec<A> = Co
*/
fun <A : Any> Codec<A>.validate(validator: (A) -> DataResult<A>): Codec<A> = flatXmap(validator, validator)

fun <A : Any, S : Any> Codec<A>.dispatch(type: Function<S, A>, codec: Function<A, MapCodec<S>>): Codec<S> = dispatch("type", type, codec)
/*fun <A : Any, S : Any> Codec<A>.dispatch(type: Function<S, A>, codec: Function<A, MapCodec<S>>): Codec<S> = dispatch("type", type, codec)

fun <A : Any, S : Any> Codec<A>.dispatch(typeKey: String, type: Function<S, A>, codec: Function<A, MapCodec<S>>): Codec<S> =
dispatchPartial(typeKey, type.andThen(DataResult<A>::success), codec.andThen(DataResult<A>::success))
Expand All @@ -50,7 +98,7 @@ fun <A : Any, S : Any> Codec<A>.dispatchPartial(
typeKey: String,
type: Function<S, DataResult<A>>,
codec: Function<A, DataResult<MapCodec<S>>>,
): Codec<S> = KeyDispatchCodec(typeKey, this, type::apply, codec::apply).codec()
): Codec<S> = KeyDispatchCodec(typeKey, this, type::apply, codec::apply).codec()*/

fun <A : Any> Codec<A>.optionalOf(): Codec<Optional<A>> = OptionalCodec(this)

Expand Down Expand Up @@ -137,3 +185,25 @@ fun <T : Any> T?.toDataResult(errorMessage: String): DataResult<T> = this?.let(D

fun <R : Any, T : Any> DataResult<R>.mapNotNull(transform: (R) -> T?): DataResult<T> =
flatMap { result: R -> transform(result).toDataResult("Transformed value was null!") }

/**
* 指定された[DataResult]の値を[Optional]に包んで返します。
*/
fun <R : Any> DataResult<R>.getOptional(): Optional<R> = get().left()

/**
* 指定された[DataResult]の値をnullableな形で返します。
*/
fun <R : Any> DataResult<R>.getOrNull(): R? = getOptional().getOrNull()

/**
* 指定された[DataResult]の値をnullでない形で返します。
* @return [getOrNull]がnullの場合,[defaultValue]から返す
*/
fun <R : Any> DataResult<R>.getOrDefault(defaultValue: R): R = getOrNull() ?: defaultValue

/**
* 指定された[DataResult]の値をnullでない形で返します。
* @return [getOrNull]がnullの場合,[value]から返す
*/
fun <R : Any> DataResult<R>.getOrElse(value: () -> R): R = getOrNull() ?: value()
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
package dev.robustum.core.extensions

import com.mojang.serialization.Codec
import com.mojang.serialization.Dynamic
import com.mojang.serialization.DynamicOps

/**
* 指定された[DynamicOps]を[Codec]に変換します。
* @see [dev.robustum.core.codec.RobustumCodecs.ANY]
*/
fun <T : Any> DynamicOps<T>.toCodec(): Codec<T> = Codec.PASSTHROUGH.xmap(
{ dynamic: Dynamic<*> -> dynamic.convert(this).value },
{ obj: T -> Dynamic(this, obj) },
)

// List //

/**
Expand Down
2 changes: 0 additions & 2 deletions src/main/kotlin/dev/robustum/core/registry/IdentifiedEntry.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,3 @@ sealed interface IdentifiedEntry<T : Any> {
val id: Identifier
val value: T
}

internal data class IdentifiedEntryImpl<T : Any>(override val id: Identifier, override val value: T) : IdentifiedEntry<T>
2 changes: 2 additions & 0 deletions src/main/kotlin/dev/robustum/core/registry/RegistryLookup.kt
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,6 @@ interface RegistryLookup<T : Any> {
?: DataResult.error("Unknown tag: $tag")
}
}

private data class IdentifiedEntryImpl<T : Any>(override val id: Identifier, override val value: T) : IdentifiedEntry<T>
}
16 changes: 16 additions & 0 deletions src/main/kotlin/dev/robustum/core/text/KeyBindTextSerializer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package dev.robustum.core.text

import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.text.KeybindText
import net.minecraft.util.Identifier

object KeyBindTextSerializer : TextSerializer<KeybindText> {
override val id: Identifier = Identifier("keybind")
override val codec: Codec<KeybindText> = RecordCodecBuilder.create { instance ->
instance
.group(
Codec.STRING.fieldOf("keybind").forGetter(KeybindText::getKey),
).apply(instance, ::KeybindText)
}
}
17 changes: 17 additions & 0 deletions src/main/kotlin/dev/robustum/core/text/LiteralTextSerializer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package dev.robustum.core.text

import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.text.LiteralText
import net.minecraft.util.Identifier

object LiteralTextSerializer : TextSerializer<LiteralText> {
override val id: Identifier = Identifier("literal")

override val codec: Codec<LiteralText> = RecordCodecBuilder.create { instance ->
instance
.group(
Codec.STRING.fieldOf("text").forGetter(LiteralText::asString),
).apply(instance, ::LiteralText)
}
}
Loading