From 7efb7bb63d7af290b5b67d4fead3fcda606ffa06 Mon Sep 17 00:00:00 2001 From: poolcritter Date: Sat, 21 Feb 2026 01:09:55 +0000 Subject: [PATCH] feat(ioticiotas): iota component serde Change-Id: I88f7408c1ada8f1af3bcbc71ac7ecfcd6a6a6964 Skip-Changelog: unreleased, trivial Skip-Tests: nothing here is visible to Hex Skip-Feature: core functionality of addon --- project/ioticiotas/build.gradle.kts | 4 +- .../resources/data/ioticiotas/mixins.json | 5 +- .../org/eu/net/pool/ioticiotas/main.scala | 49 ++++++++++++++++--- .../pool/ioticiotas/mixin/IotaTypeMixin.java | 28 +++++++++++ 4 files changed, 77 insertions(+), 9 deletions(-) create mode 100644 project/ioticiotas/src/main/scala/org/eu/net/pool/ioticiotas/mixin/IotaTypeMixin.java diff --git a/project/ioticiotas/build.gradle.kts b/project/ioticiotas/build.gradle.kts index 8834686..d73bd56 100644 --- a/project/ioticiotas/build.gradle.kts +++ b/project/ioticiotas/build.gradle.kts @@ -17,6 +17,7 @@ dependencies { api("org.scala-lang:scala-library:2.13.6") api(project(":util", "namedElements")) modImplementation("at.petra-k.hexcasting:hexcasting-fabric-$minecraft_version:0.11.3") + modImplementation("at.petra-k.paucal:paucal-fabric-$minecraft_version:0.6.0-pre-118") modRuntimeOnly("com.samsthenerd.inline:inline-fabric:$minecraft_version-1.0.1") modRuntimeOnly("dev.onyxstudios.cardinal-components-api:cardinal-components-block:5.2.3") modRuntimeOnly("dev.onyxstudios.cardinal-components-api:cardinal-components-item:5.2.3") @@ -36,8 +37,9 @@ tasks.processResources { put("discord", "https://discord.com/users/758407438251720795") } - depends("phlib", ">=0.1.3 <=0.2.0") + depends("phlib", ">=0.1.3 <0.2.0") + entrypoint("org.eu.net.pool.ioticiotas.main\$package::init") mixins("data/ioticiotas/mixins.json") } } diff --git a/project/ioticiotas/src/main/resources/data/ioticiotas/mixins.json b/project/ioticiotas/src/main/resources/data/ioticiotas/mixins.json index 44adc95..9c5cfa4 100644 --- a/project/ioticiotas/src/main/resources/data/ioticiotas/mixins.json +++ b/project/ioticiotas/src/main/resources/data/ioticiotas/mixins.json @@ -3,6 +3,7 @@ "package": "org.eu.net.pool.ioticiotas.mixin", "compatibilityLevel": "JAVA_17", "mixins": [ - "IotaMixin" + "IotaMixin", + "IotaTypeMixin" ] -} +} \ No newline at end of file diff --git a/project/ioticiotas/src/main/scala/org/eu/net/pool/ioticiotas/main.scala b/project/ioticiotas/src/main/scala/org/eu/net/pool/ioticiotas/main.scala index 593c00b..acd4788 100644 --- a/project/ioticiotas/src/main/scala/org/eu/net/pool/ioticiotas/main.scala +++ b/project/ioticiotas/src/main/scala/org/eu/net/pool/ioticiotas/main.scala @@ -4,10 +4,13 @@ package ioticiotas import at.petrak.hexcasting.api.casting.iota.Iota import com.mojang.serialization.Codec import net.minecraft.nbt.{NbtCompound, NbtElement, NbtOps} -import phlib.{*, given} +import net.minecraft.server.world.ServerWorld +import net.minecraft.util.Identifier +import org.eu.net.pool.phlib.{*, given} -import scala.annotation.targetName -import scala.annotation.meta.* +import scala.language.implicitConversions + +private [ioticiotas] implicit def id(path: String): Identifier = Identifier.of("ioticiotas", path) extension [I <: Iota](x: I) def copied: I = x.cloneIota.asInstanceOf[I] @@ -29,19 +32,53 @@ class IotaComponentAccess[I <: Iota](private val iota: I): copy.`ioticiotas:componentMap` = map.toMap copy +private [ioticiotas] object JavaAccess: + def readComponents(iota: Iota, tag: NbtCompound, world: ServerWorld): Unit = + val map = Map.newBuilder[IotaComponent[?], Any] + for + name <- tag.getKeys + id <- Option(Identifier.tryParse(name)) + key <- Option(IotaComponent.registry.get(id)) + data <- key.deserialize(tag.get(name)) + do + map.addOne(key, data) + iota.`ioticiotas:componentMap` = map.result + def writeComponents(iota: Iota, tag: NbtCompound): Unit = + val map = iota.`ioticiotas:componentMap` + map match + case null => + case m: Map[IotaComponent[?], ?] => + for case (k: IotaComponent[t]) -> v <- m; data <- k.serialize(v.asInstanceOf[t]) do + tag.put(IotaComponent.registry.getId(k).toString, data) + private trait IotaComponentHolder: this: Iota => - var `ioticiotas:componentMap`: collection.immutable.Map[IotaComponent[?], ?] | Null = null // cannot use Option here as Mixin does not run field initializers + // 2026-06-12 pool: we use uninitialized here because mixin cannot be convinced to run mixins' + // default-init leaves it null so we just check for that instead and initialize on use + var `ioticiotas:componentMap`: collection.immutable.Map[IotaComponent[?], ?] | Null = compiletime.uninitialized def cloneIota: Iota private given Conversion[Iota, IotaComponentHolder] = _.asInstanceOf package mixin: import org.spongepowered.asm.mixin.Mixin - @Mixin(value = Array(classOf[Iota])) + @Mixin(Array(classOf[Iota])) private class IotaMixin extends Object with IotaComponentHolder with Cloneable: this: Iota => - def cloneIota: Iota = super[Object].clone().asInstanceOf[Iota] + def cloneIota: Iota = super.clone().asInstanceOf[Iota] abstract class IotaComponent[T]: + def serialize(value: T): Option[NbtElement] + def deserialize(data: NbtElement): Option[T] def ->(value: T) = Entry(value) class Entry(val value: T): def component = IotaComponent.this +object IotaComponent extends Registrar[IotaComponent[?]]("components"): + def codecingly[T: Codec as c] = new IotaComponent[T]: + override def serialize(value: T): Option[NbtElement] = + // 2026-06-12 pool 02:59: we throw here to avoid silently discarding data + Some(c.encodeStart(NbtOps.INSTANCE, value).getOrThrow(false, IllegalArgumentException(_))) + override def deserialize(data: NbtElement): Option[T] = + val o = c.decode(NbtOps.INSTANCE, data).get().left() + Option.when(o.isPresent)(o.get().getFirst) + +private[ioticiotas] def init(): Unit = + IotaComponent.registry diff --git a/project/ioticiotas/src/main/scala/org/eu/net/pool/ioticiotas/mixin/IotaTypeMixin.java b/project/ioticiotas/src/main/scala/org/eu/net/pool/ioticiotas/mixin/IotaTypeMixin.java new file mode 100644 index 0000000..71243cb --- /dev/null +++ b/project/ioticiotas/src/main/scala/org/eu/net/pool/ioticiotas/mixin/IotaTypeMixin.java @@ -0,0 +1,28 @@ +package org.eu.net.pool.ioticiotas.mixin; + +import at.petrak.hexcasting.api.casting.iota.Iota; +import at.petrak.hexcasting.api.casting.iota.IotaType; +import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.server.world.ServerWorld; +import org.eu.net.pool.ioticiotas.JavaAccess; +import org.eu.net.pool.ioticiotas.JavaAccess$; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(IotaType.class) +public class IotaTypeMixin { + @WrapMethod(method = "deserialize(Lnet/minecraft/nbt/NbtCompound;Lnet/minecraft/server/world/ServerWorld;)Lat/petrak/hexcasting/api/casting/iota/Iota;") + private static Iota wrapDeserialize(NbtCompound tag, ServerWorld world, Operation original) { + var iota = original.call(tag, world); + JavaAccess.readComponents(iota, tag, world); + return iota; + } + + @WrapMethod(method = "serialize") + private static NbtCompound wrapSerialize(Iota iota, Operation original) { + var tag = original.call(iota); + JavaAccess.writeComponents(iota, tag); + return tag; + } +} -- 2.51.2