diff --git a/.gitignore b/.gitignore index 7231c09aa..9a4a5f3ec 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ eclipse kotlin-js-store/ geary-benchmarks/.results +/truckconfig.json diff --git a/README.md b/README.md index 8b59ffc22..82e486ab0 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,21 @@ dependencies { } ``` +### Version catalog entries + +```toml +[versions] +geary = "x.y.z" + +[libraries] +geary-core = { module = "com.mineinabyss:geary-autoscan", version.ref = "geary" } +geary-actions = { module = "com.mineinabyss:geary-actions", version.ref = "geary" } +geary-autoscan = { module = "com.mineinabyss:geary-autoscan", version.ref = "geary" } +geary-prefabs = { module = "com.mineinabyss:geary-prefabs", version.ref = "geary" } +geary-serialization = { module = "com.mineinabyss:geary-serialization", version.ref = "geary" } +geary-uuid = { module = "com.mineinabyss:geary-uuid", version.ref = "geary" } +``` + ## Roadmap As the project matures, our primary goal is to make it useful to more people. Here are a handful of features we hope to achieve: diff --git a/addons/geary-actions/build.gradle.kts b/addons/geary-actions/build.gradle.kts new file mode 100644 index 000000000..f57f158e5 --- /dev/null +++ b/addons/geary-actions/build.gradle.kts @@ -0,0 +1,31 @@ +plugins { + id(idofrontLibs.plugins.mia.kotlin.multiplatform.get().pluginId) + id(idofrontLibs.plugins.mia.publication.get().pluginId) + alias(idofrontLibs.plugins.kotlinx.serialization) +} + +kotlin { + sourceSets { + val commonMain by getting { + dependencies { + implementation(project(":geary-core")) + implementation(project(":geary-serialization")) + + implementation(libs.uuid) + implementation(idofrontLibs.idofront.di) + } + } + val commonTest by getting { + dependencies { + implementation(kotlin("test")) + implementation(idofrontLibs.kotlinx.coroutines.test) + implementation(idofrontLibs.kotlinx.serialization.kaml) + implementation(idofrontLibs.kotest.assertions) + implementation(idofrontLibs.kotest.property) + implementation(idofrontLibs.idofront.di) + implementation(project(":geary-core")) + implementation(project(":geary-serialization")) + } + } + } +} diff --git a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/Action.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/Action.kt new file mode 100644 index 000000000..50a0d625a --- /dev/null +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/Action.kt @@ -0,0 +1,13 @@ +package com.mineinabyss.geary.actions + +import kotlin.jvm.JvmName + +interface Action { + /** Should this action create a copy of [ActionGroupContext] to run or not? */ + val useSubcontext: Boolean get() = true + + fun ActionGroupContext.execute(): Any? + +} + +fun Action.execute(context: ActionGroupContext) = context.execute() diff --git a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/ActionGroup.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/ActionGroup.kt new file mode 100644 index 000000000..ab6b2e2dd --- /dev/null +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/ActionGroup.kt @@ -0,0 +1,108 @@ +package com.mineinabyss.geary.actions + +import com.mineinabyss.geary.actions.actions.EmitEventAction +import com.mineinabyss.geary.actions.actions.EnsureAction +import com.mineinabyss.geary.actions.event_binds.* +import com.mineinabyss.geary.actions.expressions.Expression +import com.mineinabyss.geary.modules.geary +import com.mineinabyss.geary.serialization.serializers.InnerSerializer +import com.mineinabyss.geary.serialization.serializers.PolymorphicListAsMapSerializer +import com.mineinabyss.geary.serialization.serializers.SerializedComponents +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.ListSerializer + +class ActionEntry( + val action: Action, + val conditions: List?, + val register: String?, + val onFail: ActionGroup?, + val loop: Expression>?, + val environment: Map>?, +) + +@Serializable(with = ActionGroup.Serializer::class) +class ActionGroup( + val actions: List, +) : Action { + override fun ActionGroupContext.execute() { + actions.forEach { entry -> + val context = if (entry.action.useSubcontext) + entry.environment?.let { env -> this.plus(env.mapValues { eval(it.value) }) } ?: this + else this + try { + if (entry.loop != null) { + entry.loop.evaluate(context).forEach { loopEntry -> + val subcontext = context.copy() + subcontext.register("it", loopEntry) + executeEntry(subcontext, entry) + } + } else + executeEntry(context, entry) + } catch (e: ActionsCancelledException) { + entry.onFail?.execute(context) + return + } + } + } + + private fun executeEntry(context: ActionGroupContext, entry: ActionEntry) { + entry.conditions?.forEach { condition -> + condition.execute(context) + } + + val returned = entry.action.execute(context) + + if (entry.register != null) + context.register(entry.register, returned) + } + + class Serializer : InnerSerializer, ActionGroup>( + serialName = "geary:action_group", + inner = ListSerializer( + PolymorphicListAsMapSerializer.ofComponents( + PolymorphicListAsMapSerializer.Config( + customKeys = mapOf( + "when" to { ActionWhen.serializer() }, + "register" to { ActionRegister.serializer() }, + "onFail" to { ActionOnFail.serializer() }, + "loop" to { ActionLoop.serializer() } + ) + ) + ) + ), + inverseTransform = { TODO() }, + transform = { + val actions = it.mapNotNull { components -> + var action: Action? = null + var condition: List? = null + var register: String? = null + var loop: Expression>? = null + var onFail: ActionGroup? = null + var environment: Map>? = null + components.forEach { comp -> + when { + comp is ActionWhen -> condition = comp.conditions + comp is ActionRegister -> register = comp.register + comp is ActionOnFail -> onFail = comp.action + comp is ActionLoop -> loop = + Expression.parseExpression(comp.expression, serializersModule) as Expression> + + comp is ActionEnvironment -> environment = comp.environment + action != null -> geary.logger.w { "Multiple actions defined in one block!" } + else -> action = EmitEventAction.wrapIfNotAction(comp) + } + } + if (action == null) return@mapNotNull null + ActionEntry( + action = action!!, + conditions = condition, + register = register, + onFail = onFail, + loop = loop, + environment = environment + ) + } + ActionGroup(actions) + } + ) +} diff --git a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/ActionGroupContext.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/ActionGroupContext.kt new file mode 100644 index 000000000..ba9118826 --- /dev/null +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/ActionGroupContext.kt @@ -0,0 +1,38 @@ +package com.mineinabyss.geary.actions + +import com.mineinabyss.geary.actions.expressions.Expression +import com.mineinabyss.geary.datatypes.GearyEntity + +class ActionGroupContext() { + constructor(entity: GearyEntity) : this() { + this.entity = entity + } + + var entity: GearyEntity? + get() = environment["entity"] as? GearyEntity + set(value) { + environment["entity"] = value + } + + val environment: MutableMap = mutableMapOf() + + fun eval(expression: Expression): T = expression.evaluate(this) + + fun register(name: String, value: Any?) { + environment[name] = value + } + + fun copy(): ActionGroupContext { + val newContext = ActionGroupContext() + newContext.environment.putAll(environment) + return newContext + } + + fun plus(newEnvironment: Map): ActionGroupContext { + val newContext = copy() + newContext.environment.putAll(newEnvironment) + return newContext + } + + companion object +} diff --git a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/ActionsCancelledException.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/ActionsCancelledException.kt new file mode 100644 index 000000000..04072cdf6 --- /dev/null +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/ActionsCancelledException.kt @@ -0,0 +1,3 @@ +package com.mineinabyss.geary.actions + +class ActionsCancelledException : Exception() diff --git a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/Condition.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/Condition.kt new file mode 100644 index 000000000..1443912a1 --- /dev/null +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/Condition.kt @@ -0,0 +1,5 @@ +package com.mineinabyss.geary.actions + +interface Condition { + fun ActionGroupContext.execute(): Boolean +} diff --git a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/GearyActions.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/GearyActions.kt new file mode 100644 index 000000000..0c7505ca9 --- /dev/null +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/GearyActions.kt @@ -0,0 +1,19 @@ +package com.mineinabyss.geary.actions + +import com.mineinabyss.geary.actions.event_binds.bindEntityObservers +import com.mineinabyss.geary.actions.event_binds.parsePassive +import com.mineinabyss.geary.addons.dsl.GearyAddonWithDefault +import com.mineinabyss.geary.modules.geary + +class GearyActions { + companion object : GearyAddonWithDefault { + override fun default() = GearyActions() + + override fun GearyActions.install() { + geary.run { + bindEntityObservers() + parsePassive() + } + } + } +} diff --git a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/actions/BecomeAction.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/actions/BecomeAction.kt new file mode 100644 index 000000000..5bdc0f7db --- /dev/null +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/actions/BecomeAction.kt @@ -0,0 +1,27 @@ +package com.mineinabyss.geary.actions.actions + +import com.mineinabyss.geary.actions.Action +import com.mineinabyss.geary.actions.ActionGroupContext +import com.mineinabyss.geary.actions.expressions.EntityExpression +import com.mineinabyss.geary.serialization.serializers.InnerSerializer +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable(with = BecomeAction.Serializer::class) +@SerialName("geary:become") +class BecomeAction( + val become: EntityExpression, +) : Action { + override val useSubcontext: Boolean = false + + override fun ActionGroupContext.execute() { + entity = become.evaluate(this) + } + + object Serializer : InnerSerializer( + serialName = "geary:become", + inner = EntityExpression.serializer(), + inverseTransform = { it.become }, + transform = { BecomeAction(it) } + ) +} diff --git a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/actions/EmitEventAction.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/actions/EmitEventAction.kt new file mode 100644 index 000000000..8ccd52cf8 --- /dev/null +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/actions/EmitEventAction.kt @@ -0,0 +1,21 @@ +package com.mineinabyss.geary.actions.actions + +import com.mineinabyss.geary.actions.Action +import com.mineinabyss.geary.actions.ActionGroupContext +import com.mineinabyss.geary.datatypes.ComponentId +import com.mineinabyss.geary.helpers.componentId + +class EmitEventAction( + val eventId: ComponentId, + val data: Any?, +) : Action { + override fun ActionGroupContext.execute() { + entity?.emit(event = eventId, data = data) + } + + companion object { + fun from(data: Any) = EmitEventAction(componentId(data::class), data) + + fun wrapIfNotAction(data: Any) = if (data is Action) data else from(data) + } +} diff --git a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/actions/EnsureAction.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/actions/EnsureAction.kt new file mode 100644 index 000000000..95a2f895f --- /dev/null +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/actions/EnsureAction.kt @@ -0,0 +1,47 @@ +package com.mineinabyss.geary.actions.actions + +import com.mineinabyss.geary.actions.* +import com.mineinabyss.geary.helpers.componentId +import com.mineinabyss.geary.serialization.serializers.InnerSerializer +import com.mineinabyss.geary.serialization.serializers.PolymorphicListAsMapSerializer +import com.mineinabyss.geary.serialization.serializers.SerializedComponents +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient + +@Serializable(with = EnsureAction.Serializer::class) +class EnsureAction( + val conditions: SerializedComponents, +) : Action { + @Transient + private val flat = conditions.map { componentId(it::class) to it } + + override fun ActionGroupContext.execute() { + flat.forEach { (id, data) -> + when (data) { + is Condition -> with(data) { + if (!execute()) { + throw ActionsCancelledException() + } + } + + else -> entity?.emit(id, data) //TODO use geary condition system if we get one + } + } + } + + fun conditionsMet(context: ActionGroupContext): Boolean { + try { + execute(context) + } catch (e: ActionsCancelledException) { + return false + } + return true + } + + object Serializer : InnerSerializer( + serialName = "geary:ensure", + inner = PolymorphicListAsMapSerializer.ofComponents(), + inverseTransform = { it.conditions }, + transform = { EnsureAction(it) } + ) +} diff --git a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/actions/EvalAction.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/actions/EvalAction.kt new file mode 100644 index 000000000..f11e0221d --- /dev/null +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/actions/EvalAction.kt @@ -0,0 +1,23 @@ +package com.mineinabyss.geary.actions.actions + +import com.mineinabyss.geary.actions.Action +import com.mineinabyss.geary.actions.ActionGroupContext +import com.mineinabyss.geary.actions.expressions.Expression +import com.mineinabyss.geary.actions.expressions.InlineExpressionSerializer +import com.mineinabyss.geary.serialization.serializers.InnerSerializer +import kotlinx.serialization.Serializable + +@Serializable(with = EvalAction.Serializer::class) +class EvalAction( + val expression: Expression<*>, +) : Action { + override fun ActionGroupContext.execute() = + expression.evaluate(this) + + object Serializer : InnerSerializer, EvalAction>( + serialName = "geary:eval", + inner = InlineExpressionSerializer, + inverseTransform = { it.expression }, + transform = { EvalAction(it) } + ) +} diff --git a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/EntityObservers.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/EntityObservers.kt new file mode 100644 index 000000000..e92a9d4ca --- /dev/null +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/EntityObservers.kt @@ -0,0 +1,74 @@ +package com.mineinabyss.geary.actions.event_binds + +import com.mineinabyss.geary.actions.ActionGroup +import com.mineinabyss.geary.actions.actions.EnsureAction +import com.mineinabyss.geary.actions.expressions.Expression +import com.mineinabyss.geary.serialization.serializers.InnerSerializer +import com.mineinabyss.geary.serialization.serializers.SerializableComponentId +import kotlinx.serialization.Contextual +import kotlinx.serialization.ContextualSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.builtins.MapSerializer +import kotlinx.serialization.builtins.serializer +import kotlin.jvm.JvmInline + +@Serializable(with = EntityObservers.Serializer::class) +class EntityObservers( + val observers: List, +) { + class Serializer : InnerSerializer, EntityObservers>( + serialName = "geary:observe", + inner = MapSerializer( + SerializableComponentId.serializer(), + ActionGroup.serializer() + ), + inverseTransform = { TODO() }, + transform = { + EntityObservers( + it.map { (event, actionGroup) -> + EventBind(event, actionGroup = actionGroup) + } + ) + } + ) +} + + +@Serializable(with = ActionWhen.Serializer::class) +class ActionWhen(val conditions: List) { + class Serializer : InnerSerializer, ActionWhen>( + serialName = "geary:when", + inner = ListSerializer(EnsureAction.serializer()), + inverseTransform = ActionWhen::conditions, + transform = { ActionWhen(it) } + ) +} + +@JvmInline +@Serializable +value class ActionRegister(val register: String) + +@Serializable(with = ActionOnFail.Serializer::class) +class ActionOnFail(val action: ActionGroup) { + class Serializer : InnerSerializer( + serialName = "geary:on_fail", + inner = ActionGroup.Serializer(), + inverseTransform = ActionOnFail::action, + transform = { ActionOnFail(it) } + ) +} + +@JvmInline +@Serializable +value class ActionLoop(val expression: String) + +@Serializable(with = ActionEnvironment.Serializer::class) +class ActionEnvironment(val environment: Map>) { + object Serializer : InnerSerializer>, ActionEnvironment>( + serialName = "geary:with", + inner = MapSerializer(String.serializer(), Expression.serializer(ContextualSerializer(Any::class))), + inverseTransform = ActionEnvironment::environment, + transform = { ActionEnvironment(it) } + ) +} diff --git a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/EventBind.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/EventBind.kt new file mode 100644 index 000000000..f203abf28 --- /dev/null +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/EventBind.kt @@ -0,0 +1,10 @@ +package com.mineinabyss.geary.actions.event_binds + +import com.mineinabyss.geary.actions.ActionGroup +import com.mineinabyss.geary.serialization.serializers.SerializableComponentId + +class EventBind( + val event: SerializableComponentId, + val involving: List = listOf(), + val actionGroup: ActionGroup, +) diff --git a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseEntityObservers.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/ParseEntityObservers.kt similarity index 68% rename from addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseEntityObservers.kt rename to addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/ParseEntityObservers.kt index cd9055f4c..67870bdf0 100644 --- a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseEntityObservers.kt +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/ParseEntityObservers.kt @@ -1,10 +1,11 @@ -package com.mineinabyss.geary.prefabs.configuration.systems +package com.mineinabyss.geary.actions.event_binds +import com.mineinabyss.geary.actions.ActionGroupContext +import com.mineinabyss.geary.actions.execute import com.mineinabyss.geary.datatypes.EntityType import com.mineinabyss.geary.modules.GearyModule import com.mineinabyss.geary.observers.entity.observe import com.mineinabyss.geary.observers.events.OnSet -import com.mineinabyss.geary.prefabs.configuration.components.EntityObservers import com.mineinabyss.geary.systems.builders.observe import com.mineinabyss.geary.systems.query.query @@ -12,10 +13,10 @@ fun GearyModule.bindEntityObservers() = observe() .involving(query()) .exec { (observers) -> observers.observers.forEach { observer -> + val actionGroup = observer.actionGroup entity.observe(observer.event.id).involving(EntityType(observer.involving.map { it.id })).exec { - observer.emitEvents.forEach { event -> - entity.emit(event = event.componentId, data = event.data) - } + val context = ActionGroupContext(entity) + actionGroup.execute(context) } } entity.remove() diff --git a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/Passive.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/Passive.kt new file mode 100644 index 000000000..20ded7fd3 --- /dev/null +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/Passive.kt @@ -0,0 +1,60 @@ +package com.mineinabyss.geary.actions.event_binds; + +import com.mineinabyss.geary.actions.ActionGroup +import com.mineinabyss.geary.actions.ActionGroupContext +import com.mineinabyss.geary.actions.execute +import com.mineinabyss.geary.actions.serializers.DurationSerializer +import com.mineinabyss.geary.helpers.entity +import com.mineinabyss.geary.helpers.fastForEach +import com.mineinabyss.geary.modules.GearyModule +import com.mineinabyss.geary.observers.events.OnSet +import com.mineinabyss.geary.serialization.serializers.InnerSerializer +import com.mineinabyss.geary.serialization.serializers.SerializableComponentId +import com.mineinabyss.geary.systems.builders.observe +import com.mineinabyss.geary.systems.builders.system +import com.mineinabyss.geary.systems.query.query +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.ListSerializer +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds + +@Serializable +class SystemBind( + val match: List, + val every: @Serializable(with = DurationSerializer::class) Duration = 1.seconds, + val run: ActionGroup, +) + +@Serializable(with = Passive.Serializer::class) +class Passive( + val systems: List, +) { + object Serializer : InnerSerializer, Passive>( + serialName = "geary:passive", + inner = ListSerializer(SystemBind.serializer()), + inverseTransform = Passive::systems, + transform = { Passive(it) } + ) +} + +fun GearyModule.parsePassive() = observe() + .involving(query()) + .exec { (passive) -> + passive.systems.forEach { systemBind -> + val systemMatchingId = entity().id + entity.add(systemMatchingId) + system(query { + has(systemMatchingId) + has(systemBind.match.map { it.id }) + }).every(systemBind.every).execOnAll { + entities().fastForEach { entity -> + runCatching { + val context = ActionGroupContext(entity) + systemBind.run.execute(context) + }.onFailure { it.printStackTrace() } + } + } + } + entity.remove() + } + diff --git a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/expressions/EntityExpression.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/expressions/EntityExpression.kt new file mode 100644 index 000000000..4a269d1c8 --- /dev/null +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/expressions/EntityExpression.kt @@ -0,0 +1,18 @@ +package com.mineinabyss.geary.actions.expressions + +import com.mineinabyss.geary.actions.ActionGroupContext +import com.mineinabyss.geary.datatypes.GearyEntity +import com.mineinabyss.geary.helpers.parent +import kotlinx.serialization.Serializable +import kotlin.jvm.JvmInline + +@JvmInline +@Serializable +value class EntityExpression( + val expression: String, +) : Expression { + override fun evaluate(context: ActionGroupContext): GearyEntity { + return if (expression == "parent") context.entity?.parent!! + else Expression.Variable(expression).evaluate(context) + } +} diff --git a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/expressions/Expression.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/expressions/Expression.kt new file mode 100644 index 000000000..9e37193b3 --- /dev/null +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/expressions/Expression.kt @@ -0,0 +1,105 @@ +package com.mineinabyss.geary.actions.expressions + +import com.mineinabyss.geary.actions.ActionGroupContext +import kotlinx.serialization.ContextualSerializer +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.encoding.decodeStructure +import kotlinx.serialization.modules.SerializersModule +import kotlin.math.min + +@Serializable(with = Expression.Serializer::class) +sealed interface Expression { + fun evaluate(context: ActionGroupContext): T + data class Fixed( + val value: T, + ) : Expression { + override fun evaluate(context: ActionGroupContext): T = value + } + + data class Variable( + val expression: String, + ) : Expression { + override fun evaluate(context: ActionGroupContext): T { + if (expression == "entity") return context.entity as T + return context.environment[expression] as? T ?: error("Expression $expression not found in context") + } + } + + companion object { + fun parseExpression(string: String, module: SerializersModule): Expression<*> { + val (name, rem) = getFunctionName(string) + val reference = Variable(name) + if(rem == "") return reference + return foldFunctions(reference, rem, module) + } + + tailrec fun foldFunctions( + reference: Expression<*>, + remainder: String, + module: SerializersModule, + ): Expression<*> { + val (name, afterName) = getFunctionName(remainder) + val (yaml, afterYaml) = getYaml(afterName) + val functionExpr = FunctionExpression.parse(reference, name, yaml, module) + if (afterYaml == "") return functionExpr + return foldFunctions(functionExpr, afterYaml, module) + } + + fun getYaml(expr: String): Pair { + var brackets = 0 + val yamlEndIndex = expr.indexOfFirst { + if (it == '{') brackets++ + if (it == '}') brackets-- + brackets == 0 + } + if (yamlEndIndex <= 0) return "{}" to expr + return expr.take(yamlEndIndex + 1).trim() to expr.drop(yamlEndIndex + 1).trim() + } + + fun getFunctionName(expr: String): Pair { + val yamlStart = expr.indexOf('{').toUInt() + val nextSection = expr.indexOf('.').toUInt() + val end = min(yamlStart, nextSection).toInt() + if (end == -1) return expr.trim() to "" + return expr.take(end).trim() to expr.drop(end).removePrefix(".").trim() + } + } + + // TODO kaml handles contextual completely different form Json, can we somehow allow both? Otherwise + // kaml also has broken contextual serializer support that we need to work around :( + class Serializer(val serializer: KSerializer) : KSerializer> { + override val descriptor: SerialDescriptor = ContextualSerializer(Any::class).descriptor + + override fun deserialize(decoder: Decoder): Expression { + // Try reading string value, if serial type isn't string, this fails + runCatching { + decoder.decodeStructure(String.serializer().descriptor) { + decodeSerializableElement(String.serializer().descriptor, 0, String.serializer()) + } + }.onSuccess { string -> + if (string.startsWith("{{") && string.endsWith("}}")) + return parseExpression( + string.removePrefix("{{").removeSuffix("}}").trim(), + decoder.serializersModule + ) as Expression + } + + // Fallback to reading the value in-place + return decoder.decodeStructure(serializer.descriptor) { + Fixed(decodeSerializableElement(serializer.descriptor, 0, serializer)) + } + } + + override fun serialize(encoder: Encoder, value: Expression) { + TODO("Not yet implemented") + } + } + +} + +fun expr(value: T) = Expression.Fixed(value) diff --git a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/expressions/ExpressionEvaluator.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/expressions/ExpressionEvaluator.kt new file mode 100644 index 000000000..cacef8626 --- /dev/null +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/expressions/ExpressionEvaluator.kt @@ -0,0 +1,10 @@ +package com.mineinabyss.geary.actions.expressions + +import com.mineinabyss.geary.actions.ActionGroupContext + +class ExpressionEvaluator { + +} +// entity.location +// test.nearbyEntities { a: string, b: 10 } +// .filter(isType: [ minecraft:pig ]) \ No newline at end of file diff --git a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/expressions/FunctionExpression.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/expressions/FunctionExpression.kt new file mode 100644 index 000000000..6aac9fa55 --- /dev/null +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/expressions/FunctionExpression.kt @@ -0,0 +1,30 @@ +package com.mineinabyss.geary.actions.expressions + +import com.mineinabyss.geary.actions.ActionGroupContext +import com.mineinabyss.geary.serialization.serializableComponents +import com.mineinabyss.geary.serialization.serializers.SerializableComponentId +import kotlinx.serialization.modules.SerializersModule + +interface FunctionExpression { + companion object { + fun parse( + ref: Expression<*>, + name: String, + yaml: String, + module: SerializersModule, + ): FunctionExpressionWithInput<*, *> { + val compClass = SerializableComponentId.Serializer.getComponent(name, module) + val serializer = serializableComponents.serializers.getSerializerFor(compClass) + ?: error("No serializer found for component $name") + val expr = + serializableComponents.formats["yml"]!!.decodeFromString>(serializer, yaml) + return FunctionExpressionWithInput(ref, expr) + } + } + + fun ActionGroupContext.map(input: I): O + + fun map(input: I, context: ActionGroupContext): O { + return with(context) { map(input) } + } +} diff --git a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/expressions/FunctionExpressionWithInput.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/expressions/FunctionExpressionWithInput.kt new file mode 100644 index 000000000..e95cea1a9 --- /dev/null +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/expressions/FunctionExpressionWithInput.kt @@ -0,0 +1,13 @@ +package com.mineinabyss.geary.actions.expressions + +import com.mineinabyss.geary.actions.ActionGroupContext + +class FunctionExpressionWithInput( + val ref: Expression<*>, + val expr: FunctionExpression, +) : Expression { + override fun evaluate(context: ActionGroupContext): O { + val input = ref.evaluate(context) as I + return expr.map(input, context) + } +} diff --git a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/expressions/InlineExpressionSerializer.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/expressions/InlineExpressionSerializer.kt new file mode 100644 index 000000000..0d2b609a0 --- /dev/null +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/expressions/InlineExpressionSerializer.kt @@ -0,0 +1,23 @@ +package com.mineinabyss.geary.actions.expressions + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +object InlineExpressionSerializer : KSerializer> { + override val descriptor: SerialDescriptor = String.serializer().descriptor + + override fun deserialize(decoder: Decoder): Expression<*> { + return Expression.parseExpression( + decoder.decodeString(), + decoder.serializersModule + ) + } + + override fun serialize(encoder: Encoder, value: Expression<*>) { + TODO("Not yet implemented") + } + +} diff --git a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/serializers/DurationSerializer.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/serializers/DurationSerializer.kt new file mode 100644 index 000000000..478aa1fc4 --- /dev/null +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/serializers/DurationSerializer.kt @@ -0,0 +1,41 @@ +package com.mineinabyss.geary.actions.serializers + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlin.time.Duration +import kotlin.time.Duration.Companion.days +import kotlin.time.Duration.Companion.hours +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.minutes +import kotlin.time.Duration.Companion.seconds + +internal object DurationSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Time", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: Duration) = + encoder.encodeString(value.toString()) + + override fun deserialize(decoder: Decoder): Duration { + val string = decoder.decodeString() + return Duration.parseOrNull(string) ?: fromString(decoder.decodeString()) ?: error("Not a valid duration: $string") + } + + private fun fromString(string: String): Duration? { + val splitAt = string.indexOfFirst { it.isLetter() }.takeIf { it > 0 } ?: string.length + val value = string.take(splitAt).toDouble() + return when (string.drop(splitAt)) { + "ms" -> value.milliseconds + "s" -> value.seconds + "m" -> value.minutes + "h" -> value.hours + "d" -> value.days + "w" -> value.days * 7 + "mo" -> value.days * 31 + else -> null + } + } +} diff --git a/addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/observers/ConfigEntityObserversTests.kt b/addons/geary-actions/src/jvmTest/kotlin/com/mineinabyss/geary/actions/ConfigEntityObserversTests.kt similarity index 86% rename from addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/observers/ConfigEntityObserversTests.kt rename to addons/geary-actions/src/jvmTest/kotlin/com/mineinabyss/geary/actions/ConfigEntityObserversTests.kt index 6a96e7d99..24fa165bb 100644 --- a/addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/observers/ConfigEntityObserversTests.kt +++ b/addons/geary-actions/src/jvmTest/kotlin/com/mineinabyss/geary/actions/ConfigEntityObserversTests.kt @@ -1,9 +1,8 @@ -package com.mineinabyss.geary.prefabs.observers +package com.mineinabyss.geary.actions +import com.mineinabyss.geary.actions.event_binds.EntityObservers import com.mineinabyss.geary.modules.TestEngineModule import com.mineinabyss.geary.modules.geary -import com.mineinabyss.geary.prefabs.Prefabs -import com.mineinabyss.geary.prefabs.configuration.components.EntityObservers import com.mineinabyss.geary.serialization.dsl.serialization import com.mineinabyss.geary.serialization.dsl.withCommonComponentNames import com.mineinabyss.geary.serialization.formats.YamlFormat @@ -11,7 +10,6 @@ import com.mineinabyss.geary.serialization.serializableComponents import com.mineinabyss.geary.serialization.serializers.GearyEntitySerializer import com.mineinabyss.geary.systems.builders.observeWithData import com.mineinabyss.idofront.di.DI -import io.kotest.assertions.print.Print import io.kotest.matchers.shouldBe import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -32,7 +30,7 @@ class ConfigEntityObserversTests { fun createEngine() { DI.clear() geary(TestEngineModule) { - install(Prefabs) + install(GearyActions) serialization { withCommonComponentNames() @@ -53,9 +51,8 @@ class ConfigEntityObserversTests { // arrange val entityDef = """ geary:observe: - - event: geary:onSet - involving: [ geary:myComp ] - emit: + geary:onSet: + # TODO involving: [ geary:myComp ] - geary:print: string: "Hello World" """.trimIndent() diff --git a/addons/geary-actions/src/jvmTest/kotlin/com/mineinabyss/geary/actions/ExpressionDecodingTest.kt b/addons/geary-actions/src/jvmTest/kotlin/com/mineinabyss/geary/actions/ExpressionDecodingTest.kt new file mode 100644 index 000000000..48f32e1a0 --- /dev/null +++ b/addons/geary-actions/src/jvmTest/kotlin/com/mineinabyss/geary/actions/ExpressionDecodingTest.kt @@ -0,0 +1,85 @@ +package com.mineinabyss.geary.actions + +import com.charleskorn.kaml.Yaml +import com.mineinabyss.geary.actions.expressions.Expression +import com.mineinabyss.geary.actions.expressions.FunctionExpression +import com.mineinabyss.geary.datatypes.GearyEntity +import com.mineinabyss.geary.helpers.entity +import com.mineinabyss.geary.modules.TestEngineModule +import com.mineinabyss.geary.modules.geary +import com.mineinabyss.geary.serialization.dsl.serialization +import com.mineinabyss.geary.serialization.formats.YamlFormat +import com.mineinabyss.idofront.di.DI +import io.kotest.matchers.shouldBe +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.serializer +import org.junit.jupiter.api.Test + +class ExpressionDecodingTest { + @Serializable + data class TestData( + val name: Expression, + val age: Expression, + val regular: String, + ) + +// @org.junit.jupiter.api.Test +// fun `should correctly decode json`() { +// val input = """ +// { +// "age": "{{ test }}", +// "name": "variable", +// "regular": "{{ asdf }}" +// } +// """.trimIndent() +// Json.decodeFromString(input) shouldBe TestData( +// name = Expression.Fixed("variable"), +// age = Expression.Evaluate("test"), +// regular = "{{ asdf }}" +// ) +// } + + @Test + fun `should correctly decode yaml`() { + val input = """ + { + "age": "{{ test }}", + "name": "variable", + "regular": "{{ asdf }}" + } + """.trimIndent() + Yaml.default.decodeFromString(TestData.serializer(), input) shouldBe TestData( + name = Expression.Fixed("variable"), + age = Expression.Variable("test"), + regular = "{{ asdf }}" + ) + } + + @Serializable + @SerialName("geary:test_function") + class TestFunction(val string: String) : FunctionExpression { + override fun ActionGroupContext.map(input: GearyEntity): String { + return string + } + } + + @Test + fun shouldCorrectlyParseExpressionFunctions() { + DI.clear() + geary(TestEngineModule){ + serialization { + components { + component(TestFunction.serializer()) + } + format("yml", ::YamlFormat) + } + + } + + geary.pipeline.runStartupTasks() + val input = "'{{ entity.geary:testFunction{ string: test } }}'" + val expr = Yaml.default.decodeFromString(Expression.Serializer(String.serializer()), input) + expr.evaluate(ActionGroupContext(entity = entity())) shouldBe "test" + } +} diff --git a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/PrefabLoader.kt b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/PrefabLoader.kt index 7b36713c2..9b4abda0b 100644 --- a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/PrefabLoader.kt +++ b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/PrefabLoader.kt @@ -88,7 +88,7 @@ class PrefabLoader { var hadMalformed = false val key = PrefabKey.of(namespace, path.name.substringBeforeLast('.')) val decoded = runCatching { - val config = PolymorphicListAsMapSerializer.Config( + val config = PolymorphicListAsMapSerializer.Config( whenComponentMalformed = { if (!hadMalformed) logger.e("[$key] Problems reading components") hadMalformed = true diff --git a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/Prefabs.kt b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/Prefabs.kt index 11e01ca90..aa4310d08 100644 --- a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/Prefabs.kt +++ b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/Prefabs.kt @@ -33,7 +33,6 @@ interface Prefabs { createParseRelationWithDataListener() createTrackPrefabsByKeyListener() createCopyToInstancesSystem() - bindEntityObservers() reEmitEvent() } geary.pipeline.runOnOrAfter(GearyPhase.INIT_ENTITIES) { diff --git a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/components/EntityObservers.kt b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/components/EntityObservers.kt deleted file mode 100644 index 1a473bc52..000000000 --- a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/components/EntityObservers.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.mineinabyss.geary.prefabs.configuration.components - -import com.mineinabyss.geary.datatypes.ComponentId -import com.mineinabyss.geary.helpers.componentId -import com.mineinabyss.geary.serialization.serializers.InnerSerializer -import com.mineinabyss.geary.serialization.serializers.SerializedComponents -import kotlinx.serialization.Serializable -import kotlinx.serialization.Transient -import kotlinx.serialization.builtins.ListSerializer - -@Serializable(with = EntityObservers.Serializer::class) -class EntityObservers(val observers: List) { - class Serializer : InnerSerializer, EntityObservers>( - serialName = "geary:observe", - inner = ListSerializer(EventBind.serializer()), - inverseTransform = { it.observers }, - transform = ::EntityObservers - ) -} - -@Serializable -class EventBind( - val event: SerializableComponentId, - val using: SerializableComponentId? = null, - val involving: List = listOf(), - private val emit: List -) { - class CachedEvent(val componentId: ComponentId, val data: Any?) - - @Transient - val emitEvents = emit.flatMap { emitter -> - emitter.map { component -> - if (using != null) ReEmitEvent( - SerializableComponentId(using.id), - componentId(component::class), - component, - ) else component - } - }.map { CachedEvent(componentId(it::class), it) } -} diff --git a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/components/ReEmitEvent.kt b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/components/ReEmitEvent.kt index 5c722214f..2b965c8ef 100644 --- a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/components/ReEmitEvent.kt +++ b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/components/ReEmitEvent.kt @@ -1,6 +1,7 @@ package com.mineinabyss.geary.prefabs.configuration.components import com.mineinabyss.geary.datatypes.ComponentId +import com.mineinabyss.geary.serialization.serializers.SerializableComponentId data class ReEmitEvent( val findByRelationKind: SerializableComponentId, diff --git a/addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/GearyEntitySerializerTest.kt b/addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/GearyEntitySerializerTest.kt index ed8756863..82cd0247a 100644 --- a/addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/GearyEntitySerializerTest.kt +++ b/addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/GearyEntitySerializerTest.kt @@ -47,7 +47,7 @@ class GearyEntitySerializerTest { // act val entity = format.decodeFromString(GearyEntitySerializer, file, overrideSerializersModule = SerializersModule { - provideConfig(PolymorphicListAsMapSerializer.Config(namespaces = listOf("test"))) + provideConfig(PolymorphicListAsMapSerializer.Config(namespaces = listOf("test"))) }) // assert diff --git a/addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/SerializerTest.kt b/addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/SerializerTest.kt index 1f33cd5e9..77b777dcb 100644 --- a/addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/SerializerTest.kt +++ b/addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/SerializerTest.kt @@ -40,7 +40,7 @@ class SerializerTest { subclass(B::class) subclass(SubSerializers::class) } - provideConfig(PolymorphicListAsMapSerializer.Config(namespaces = listOf("test"))) + provideConfig(PolymorphicListAsMapSerializer.Config(namespaces = listOf("test"))) }) val mapSerializer = PolymorphicListAsMapSerializer(PolymorphicSerializer(Components::class)) diff --git a/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/serializers/InnerSerializer.kt b/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/serializers/InnerSerializer.kt index 9a1a81d42..fc422f72b 100644 --- a/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/serializers/InnerSerializer.kt +++ b/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/serializers/InnerSerializer.kt @@ -10,7 +10,7 @@ import kotlinx.serialization.encoding.Encoder abstract class InnerSerializer( val serialName: String, val inner: KSerializer, - val transform: (I) -> O, + val transform: Decoder.(I) -> O, val inverseTransform: (O) -> I, ) : KSerializer { override val descriptor = @@ -19,7 +19,7 @@ abstract class InnerSerializer( else SerialDescriptor(serialName, inner.descriptor) override fun deserialize(decoder: Decoder): O { - return transform(inner.deserialize(decoder)) + return transform(decoder, inner.deserialize(decoder)) } override fun serialize(encoder: Encoder, value: O) { diff --git a/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/serializers/PolymorphicListAsMapSerializer.kt b/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/serializers/PolymorphicListAsMapSerializer.kt index 07c91ac12..0b3187b10 100644 --- a/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/serializers/PolymorphicListAsMapSerializer.kt +++ b/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/serializers/PolymorphicListAsMapSerializer.kt @@ -21,7 +21,7 @@ open class PolymorphicListAsMapSerializer( serializer: KSerializer, ) : KSerializer> { // We need primary constructor to be a single serializer for generic serialization to work, use of() if manually creating - private var config: Config = Config() + private var config: Config = Config() val polymorphicSerializer = serializer as? PolymorphicSerializer ?: error("Serializer is not polymorphic") @@ -49,7 +49,7 @@ open class PolymorphicListAsMapSerializer( } else -> { - val componentSerializer = findSerializerFor(compositeDecoder.serializersModule, namespaces, key) + val componentSerializer = config.customKeys[key]?.invoke() ?: findSerializerFor(compositeDecoder.serializersModule, namespaces, key) .getOrElse { if (config.onMissingSerializer != OnMissing.IGNORE) { config.whenComponentMalformed(key) @@ -87,7 +87,7 @@ open class PolymorphicListAsMapSerializer( return components } - fun getParentConfig(serializersModule: SerializersModule): Config? { + fun getParentConfig(serializersModule: SerializersModule): Config<*>? { return (serializersModule.getContextual(ProvidedConfig::class) as? ProvidedConfig)?.config } @@ -116,18 +116,19 @@ open class PolymorphicListAsMapSerializer( ERROR, WARN, IGNORE } - data class Config( + data class Config( val namespaces: List = listOf(), val prefix: String = "", val onMissingSerializer: OnMissing = OnMissing.WARN, val skipMalformedComponents: Boolean = true, val whenComponentMalformed: (String) -> Unit = {}, + val customKeys: Map KSerializer> = mapOf(), ) companion object { fun of( serializer: PolymorphicSerializer, - config: Config = Config(), + config: Config = Config(), ): PolymorphicListAsMapSerializer { return PolymorphicListAsMapSerializer(serializer).apply { this.config = config @@ -135,12 +136,12 @@ open class PolymorphicListAsMapSerializer( } fun ofComponents( - config: Config = Config(), + config: Config = Config(), ) = of(PolymorphicSerializer(GearyComponent::class)).apply { this.config = config } - fun SerializersModuleBuilder.provideConfig(config: Config) { + fun SerializersModuleBuilder.provideConfig(config: Config<*>) { contextual(ProvidedConfig::class, ProvidedConfig(config)) } } diff --git a/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/serializers/ProvidedConfig.kt b/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/serializers/ProvidedConfig.kt index ef59bfb82..28650b3cb 100644 --- a/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/serializers/ProvidedConfig.kt +++ b/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/serializers/ProvidedConfig.kt @@ -8,7 +8,7 @@ import kotlinx.serialization.descriptors.buildSerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder -class ProvidedConfig(val config: PolymorphicListAsMapSerializer.Config) : KSerializer { +class ProvidedConfig(val config: PolymorphicListAsMapSerializer.Config<*>) : KSerializer { @OptIn(InternalSerializationApi::class) override val descriptor: SerialDescriptor = buildSerialDescriptor("PolymorphicListAsMapSerializer.Config", PolymorphicKind.SEALED) diff --git a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/components/SerializableComponentId.kt b/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/serializers/SerializableComponentId.kt similarity index 67% rename from addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/components/SerializableComponentId.kt rename to addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/serializers/SerializableComponentId.kt index 874e3391f..00de2ef14 100644 --- a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/components/SerializableComponentId.kt +++ b/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/serializers/SerializableComponentId.kt @@ -1,15 +1,17 @@ -package com.mineinabyss.geary.prefabs.configuration.components +package com.mineinabyss.geary.serialization.serializers +import com.mineinabyss.geary.datatypes.Component import com.mineinabyss.geary.datatypes.ComponentId import com.mineinabyss.geary.helpers.componentId import com.mineinabyss.geary.serialization.serializableComponents -import com.mineinabyss.geary.serialization.serializers.PolymorphicListAsMapSerializer import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.modules.SerializersModule +import kotlin.reflect.KClass @Serializable(with = SerializableComponentId.Serializer::class) class SerializableComponentId(val id: ComponentId) { @@ -19,16 +21,18 @@ class SerializableComponentId(val id: ComponentId) { private val polymorphicListAsMapSerializer = PolymorphicListAsMapSerializer.ofComponents() override fun deserialize(decoder: Decoder): SerializableComponentId { - val type = decoder.decodeString() - val namespaces = polymorphicListAsMapSerializer - .getParentConfig(decoder.serializersModule)?.namespaces - ?: emptyList() - val typeComponentId = componentId(serializableComponents.serializers.getClassFor(type, namespaces)) - return SerializableComponentId(typeComponentId) + return SerializableComponentId(componentId(getComponent(decoder.decodeString(), decoder.serializersModule))) } override fun serialize(encoder: Encoder, value: SerializableComponentId) { TODO() } + + fun getComponent(name: String, module: SerializersModule): KClass { + val namespaces = polymorphicListAsMapSerializer + .getParentConfig(module)?.namespaces + ?: emptyList() + return serializableComponents.serializers.getClassFor(name, namespaces) + } } } diff --git a/geary-catalog/build.gradle.kts b/geary-catalog/build.gradle.kts deleted file mode 100644 index 822cfee69..000000000 --- a/geary-catalog/build.gradle.kts +++ /dev/null @@ -1,23 +0,0 @@ -plugins { - `version-catalog` - id("com.mineinabyss.conventions.publication") -} - -catalog { - versionCatalog { - // Add aliases for all our conventions plugins - rootProject.file("addons").list() - ?.plus(rootProject.file(".").list()?.filter { it.startsWith("geary") } ?: emptyList()) - ?.forEach { name -> - library(name.removePrefix("geary-"), "com.mineinabyss:$name:$version") - } - } -} - -publishing { - publications { - create("maven") { - from(components["versionCatalog"]) - } - } -} diff --git a/gradle.properties b/gradle.properties index f426544d1..d28ee949a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,5 +2,5 @@ group=com.mineinabyss version=0.26 # Workaround for dokka builds failing on CI, see https://github.com/Kotlin/dokka/issues/1405 #org.gradle.jvmargs=-XX:MaxMetaspaceSize=512m -idofrontVersion=0.24.0 +idofrontVersion=0.25.3 kotlin.native.ignoreDisabledTargets=true diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a4413138c..9355b4155 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/settings.gradle.kts b/settings.gradle.kts index 43c7a9112..eb2b95f4d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -28,7 +28,6 @@ dependencyResolutionManagement { include( "geary-benchmarks", "geary-core", - "geary-catalog", ) // Go through addons directory and load all projects based on file name