From 9e7ecb7a249a11d61121662788b1d5f9c6c932ec Mon Sep 17 00:00:00 2001 From: Danielle Voznyy Date: Thu, 25 Jul 2024 16:45:46 -0400 Subject: [PATCH 01/15] Start fiddling with observer system rework --- .../components/EntityObservers.kt | 109 +++++++++++++++--- .../systems/ParseEntityObservers.kt | 15 ++- 2 files changed, 107 insertions(+), 17 deletions(-) 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 index 1a473bc5..2a2c291c 100644 --- 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 @@ -1,40 +1,119 @@ package com.mineinabyss.geary.prefabs.configuration.components import com.mineinabyss.geary.datatypes.ComponentId +import com.mineinabyss.geary.datatypes.GearyEntity import com.mineinabyss.geary.helpers.componentId +import com.mineinabyss.geary.helpers.parent +import com.mineinabyss.geary.prefabs.configuration.components.EventBind.CachedEvent import com.mineinabyss.geary.serialization.serializers.InnerSerializer +import com.mineinabyss.geary.serialization.serializers.PolymorphicListAsMapSerializer import com.mineinabyss.geary.serialization.serializers.SerializedComponents +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.builtins.MapSerializer +import kotlin.jvm.JvmInline @Serializable(with = EntityObservers.Serializer::class) class EntityObservers(val observers: List) { - class Serializer : InnerSerializer, EntityObservers>( + class Serializer : InnerSerializer>, EntityObservers>( serialName = "geary:observe", - inner = ListSerializer(EventBind.serializer()), - inverseTransform = { it.observers }, - transform = ::EntityObservers + inner = MapSerializer( + SerializableComponentId.serializer(), + ListSerializer(PolymorphicListAsMapSerializer.ofComponents()) + ), + inverseTransform = { it.observers.associate { it.event to it.emit } }, + transform = { EntityObservers(it.map { (event, emit) -> EventBind(event, emit = emit) }) } ) } + +@Serializable +abstract class Expression { + abstract fun evaluate(context: RoleContext): T +} + + +@JvmInline @Serializable +value class EntityExpression( + val expression: String, +) /*: Expression()*/ { + fun evaluate(context: RoleContext): GearyEntity { + return if (expression == "parent") context.entity.parent!! + else TODO() + } +} + +class RoleContext( + var entity: GearyEntity, +) { + fun eval(expression: Expression): T = expression.evaluate(this) +} + +interface Action { + fun RoleContext.execute() +} + +interface Condition { + fun RoleContext.execute(): Boolean +} + + +class RoleCancelledException : Exception() + +@Serializable(with = BecomeAction.Serializer::class) +@SerialName("geary:become") +class BecomeAction( + val become: EntityExpression, +) : Action { + override fun RoleContext.execute() { + entity = become.evaluate(this) + } + + object Serializer : InnerSerializer( + serialName = "geary:become", + inner = EntityExpression.serializer(), + inverseTransform = { it.become }, + transform = ::BecomeAction + ) +} + + +@Serializable(with = EnsureAction.Serializer::class) +class EnsureAction( + val conditions: SerializedComponents, +) : Action { + @Transient + private val flat = conditions.map { CachedEvent(componentId(it::class), it) } + + override fun RoleContext.execute() { + flat.forEach { + when (val condition = it.data) { + is Condition -> with(condition) { + if(!execute()) throw RoleCancelledException() + } + else -> entity.emit(it.componentId, it.data) //TODO use geary condition system if we get one + } + } + } + + object Serializer: InnerSerializer( + serialName = "geary:ensure", + inner = PolymorphicListAsMapSerializer.ofComponents(), + inverseTransform = { it.conditions }, + transform = { EnsureAction(it) } + ) +} + class EventBind( val event: SerializableComponentId, - val using: SerializableComponentId? = null, val involving: List = listOf(), - private val emit: List + 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) } + val emitEvents = emit.flatten().map { CachedEvent(componentId(it::class), it) } } diff --git a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseEntityObservers.kt b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseEntityObservers.kt index cd9055f4..2440c288 100644 --- a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseEntityObservers.kt +++ b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseEntityObservers.kt @@ -4,7 +4,10 @@ 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.Action import com.mineinabyss.geary.prefabs.configuration.components.EntityObservers +import com.mineinabyss.geary.prefabs.configuration.components.RoleCancelledException +import com.mineinabyss.geary.prefabs.configuration.components.RoleContext import com.mineinabyss.geary.systems.builders.observe import com.mineinabyss.geary.systems.query.query @@ -13,8 +16,16 @@ fun GearyModule.bindEntityObservers() = observe() .exec { (observers) -> observers.observers.forEach { observer -> 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 = RoleContext(entity) + try { + observer.emitEvents.forEach { event -> + when (val data = event.data) { + is Action -> with(data) { context.execute() } + else -> context.entity.emit(event = event.componentId, data = event.data) + } + } + } catch (_: RoleCancelledException) { + } } } From 3c7ed2abc13a19fa0aaae1a840113b3eca693030 Mon Sep 17 00:00:00 2001 From: Danielle Voznyy Date: Thu, 25 Jul 2024 18:33:00 -0400 Subject: [PATCH 02/15] feat: Revamped action system as a new addon --- addons/geary-actions/build.gradle.kts | 19 +++ .../com/mineinabyss/geary/actions/Action.kt | 5 + .../mineinabyss/geary/actions/ActionGroup.kt | 15 +++ .../geary/actions/ActionGroupContext.kt | 10 ++ .../actions/ActionsCancelledException.kt | 3 + .../mineinabyss/geary/actions/Condition.kt | 5 + .../mineinabyss/geary/actions/GearyActions.kt | 18 +++ .../geary/actions/actions/BecomeAction.kt | 25 ++++ .../geary/actions/actions/EmitEventAction.kt | 21 ++++ .../geary/actions/actions/EnsureAction.kt | 38 ++++++ .../actions/event_binds/EntityObservers.kt | 24 ++++ .../geary/actions/event_binds/EventBind.kt | 10 ++ .../event_binds/ParseEntityObservers.kt | 27 ++++ .../actions/expressions/EntityExpression.kt | 18 +++ .../geary/actions/expressions/Expression.kt | 9 ++ .../components/EntityObservers.kt | 119 ------------------ .../configuration/components/ReEmitEvent.kt | 1 + .../systems/ParseEntityObservers.kt | 34 ----- .../serializers}/SerializableComponentId.kt | 3 +- 19 files changed, 249 insertions(+), 155 deletions(-) create mode 100644 addons/geary-actions/build.gradle.kts create mode 100644 addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/Action.kt create mode 100644 addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/ActionGroup.kt create mode 100644 addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/ActionGroupContext.kt create mode 100644 addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/ActionsCancelledException.kt create mode 100644 addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/Condition.kt create mode 100644 addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/GearyActions.kt create mode 100644 addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/actions/BecomeAction.kt create mode 100644 addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/actions/EmitEventAction.kt create mode 100644 addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/actions/EnsureAction.kt create mode 100644 addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/EntityObservers.kt create mode 100644 addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/EventBind.kt create mode 100644 addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/ParseEntityObservers.kt create mode 100644 addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/expressions/EntityExpression.kt create mode 100644 addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/expressions/Expression.kt delete mode 100644 addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/components/EntityObservers.kt delete mode 100644 addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseEntityObservers.kt rename addons/{geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/components => geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/serializers}/SerializableComponentId.kt (90%) diff --git a/addons/geary-actions/build.gradle.kts b/addons/geary-actions/build.gradle.kts new file mode 100644 index 00000000..57b20c4e --- /dev/null +++ b/addons/geary-actions/build.gradle.kts @@ -0,0 +1,19 @@ +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) + } + } + } +} 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 00000000..dcb50e35 --- /dev/null +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/Action.kt @@ -0,0 +1,5 @@ +package com.mineinabyss.geary.actions + +interface Action { + fun ActionGroupContext.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 00000000..1f3e5ab6 --- /dev/null +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/ActionGroup.kt @@ -0,0 +1,15 @@ +package com.mineinabyss.geary.actions + +class ActionGroup( + val actions: List, +) { + fun execute(context: ActionGroupContext) { + actions.forEach { + try { + with(it) { context.execute() } + } catch (e: ActionsCancelledException) { + return + } + } + } +} 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 00000000..9d90310e --- /dev/null +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/ActionGroupContext.kt @@ -0,0 +1,10 @@ +package com.mineinabyss.geary.actions + +import com.mineinabyss.geary.actions.expressions.Expression +import com.mineinabyss.geary.datatypes.GearyEntity + +class ActionGroupContext( + var entity: GearyEntity, +) { + fun eval(expression: Expression): T = expression.evaluate(this) +} 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 00000000..04072cdf --- /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 00000000..1443912a --- /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 00000000..a70efd33 --- /dev/null +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/GearyActions.kt @@ -0,0 +1,18 @@ +package com.mineinabyss.geary.actions + +import com.mineinabyss.geary.actions.event_binds.bindEntityObservers +import com.mineinabyss.geary.addons.GearyPhase +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() + } + } + } +} 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 00000000..360b38ed --- /dev/null +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/actions/BecomeAction.kt @@ -0,0 +1,25 @@ +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 fun ActionGroupContext.execute() { + entity = become.evaluate(this) + } + + object Serializer : InnerSerializer( + serialName = "geary:become", + inner = EntityExpression.serializer(), + inverseTransform = { it.become }, + transform = ::BecomeAction + ) +} 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 00000000..c491a3b3 --- /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 00000000..6b5afcd8 --- /dev/null +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/actions/EnsureAction.kt @@ -0,0 +1,38 @@ +package com.mineinabyss.geary.actions.actions + +import com.mineinabyss.geary.actions.Action +import com.mineinabyss.geary.actions.ActionsCancelledException +import com.mineinabyss.geary.actions.ActionGroupContext +import com.mineinabyss.geary.actions.event_binds.EventBind +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 { EventBind.CachedEvent(componentId(it::class), it) } + + override fun ActionGroupContext.execute() { + flat.forEach { + when (val condition = it.data) { + is Conwdition -> with(condition) { + if(!execute()) throw ActionsCancelledException() + } + else -> entity.emit(it.componentId, it.data) //TODO use geary condition system if we get one + } + } + } + + 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/event_binds/EntityObservers.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/EntityObservers.kt new file mode 100644 index 00000000..b08a7baf --- /dev/null +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/EntityObservers.kt @@ -0,0 +1,24 @@ +package com.mineinabyss.geary.actions.event_binds + +import com.mineinabyss.geary.serialization.serializers.InnerSerializer +import com.mineinabyss.geary.serialization.serializers.PolymorphicListAsMapSerializer +import com.mineinabyss.geary.serialization.serializers.SerializableComponentId +import com.mineinabyss.geary.serialization.serializers.SerializedComponents +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.builtins.MapSerializer + +@Serializable(with = EntityObservers.Serializer::class) +class EntityObservers(val observers: List) { + class Serializer : InnerSerializer>, EntityObservers>( + serialName = "geary:observe", + inner = MapSerializer( + SerializableComponentId.serializer(), + ListSerializer(PolymorphicListAsMapSerializer.ofComponents()) + ), + inverseTransform = { it.observers.associate { it.event to it.emit } }, + transform = { EntityObservers(it.map { (event, emit) -> EventBind(event, emit = emit) }) } + ) +} + + 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 00000000..47e64bf3 --- /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.serialization.serializers.SerializableComponentId +import com.mineinabyss.geary.serialization.serializers.SerializedComponents + +class EventBind( + val event: SerializableComponentId, + val involving: List = listOf(), + val emit: List, +) diff --git a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/ParseEntityObservers.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/ParseEntityObservers.kt new file mode 100644 index 00000000..b6820164 --- /dev/null +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/ParseEntityObservers.kt @@ -0,0 +1,27 @@ +package com.mineinabyss.geary.actions.event_binds + +import com.mineinabyss.geary.actions.ActionGroup +import com.mineinabyss.geary.actions.ActionGroupContext +import com.mineinabyss.geary.actions.actions.EmitEventAction +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.systems.builders.observe +import com.mineinabyss.geary.systems.query.query + +fun GearyModule.bindEntityObservers() = observe() + .involving(query()) + .exec { (observers) -> + observers.observers.forEach { observer -> + val actionGroup = ActionGroup( + actions = observer.emit.flatten().map { EmitEventAction.wrapIfNotAction(it) } + ) + entity.observe(observer.event.id).involving(EntityType(observer.involving.map { it.id })).exec { + val context = ActionGroupContext(entity) + actionGroup.execute(context) + } + } + 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 00000000..7c05216b --- /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()*/ { + fun evaluate(context: ActionGroupContext): GearyEntity { + return if (expression == "parent") context.entity.parent!! + else TODO() + } +} 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 00000000..75fa1f69 --- /dev/null +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/expressions/Expression.kt @@ -0,0 +1,9 @@ +package com.mineinabyss.geary.actions.expressions + +import com.mineinabyss.geary.actions.ActionGroupContext +import kotlinx.serialization.Serializable + +@Serializable +abstract class Expression { + abstract fun evaluate(context: ActionGroupContext): T +} 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 2a2c291c..00000000 --- a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/components/EntityObservers.kt +++ /dev/null @@ -1,119 +0,0 @@ -package com.mineinabyss.geary.prefabs.configuration.components - -import com.mineinabyss.geary.datatypes.ComponentId -import com.mineinabyss.geary.datatypes.GearyEntity -import com.mineinabyss.geary.helpers.componentId -import com.mineinabyss.geary.helpers.parent -import com.mineinabyss.geary.prefabs.configuration.components.EventBind.CachedEvent -import com.mineinabyss.geary.serialization.serializers.InnerSerializer -import com.mineinabyss.geary.serialization.serializers.PolymorphicListAsMapSerializer -import com.mineinabyss.geary.serialization.serializers.SerializedComponents -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.Transient -import kotlinx.serialization.builtins.ListSerializer -import kotlinx.serialization.builtins.MapSerializer -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(), - ListSerializer(PolymorphicListAsMapSerializer.ofComponents()) - ), - inverseTransform = { it.observers.associate { it.event to it.emit } }, - transform = { EntityObservers(it.map { (event, emit) -> EventBind(event, emit = emit) }) } - ) -} - - -@Serializable -abstract class Expression { - abstract fun evaluate(context: RoleContext): T -} - - -@JvmInline -@Serializable -value class EntityExpression( - val expression: String, -) /*: Expression()*/ { - fun evaluate(context: RoleContext): GearyEntity { - return if (expression == "parent") context.entity.parent!! - else TODO() - } -} - -class RoleContext( - var entity: GearyEntity, -) { - fun eval(expression: Expression): T = expression.evaluate(this) -} - -interface Action { - fun RoleContext.execute() -} - -interface Condition { - fun RoleContext.execute(): Boolean -} - - -class RoleCancelledException : Exception() - -@Serializable(with = BecomeAction.Serializer::class) -@SerialName("geary:become") -class BecomeAction( - val become: EntityExpression, -) : Action { - override fun RoleContext.execute() { - entity = become.evaluate(this) - } - - object Serializer : InnerSerializer( - serialName = "geary:become", - inner = EntityExpression.serializer(), - inverseTransform = { it.become }, - transform = ::BecomeAction - ) -} - - -@Serializable(with = EnsureAction.Serializer::class) -class EnsureAction( - val conditions: SerializedComponents, -) : Action { - @Transient - private val flat = conditions.map { CachedEvent(componentId(it::class), it) } - - override fun RoleContext.execute() { - flat.forEach { - when (val condition = it.data) { - is Condition -> with(condition) { - if(!execute()) throw RoleCancelledException() - } - else -> entity.emit(it.componentId, it.data) //TODO use geary condition system if we get one - } - } - } - - object Serializer: InnerSerializer( - serialName = "geary:ensure", - inner = PolymorphicListAsMapSerializer.ofComponents(), - inverseTransform = { it.conditions }, - transform = { EnsureAction(it) } - ) -} - -class EventBind( - val event: SerializableComponentId, - val involving: List = listOf(), - val emit: List, -) { - class CachedEvent(val componentId: ComponentId, val data: Any?) - - @Transient - val emitEvents = emit.flatten().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 5c722214..2b965c8e 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/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseEntityObservers.kt b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseEntityObservers.kt deleted file mode 100644 index 2440c288..00000000 --- a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseEntityObservers.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.mineinabyss.geary.prefabs.configuration.systems - -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.Action -import com.mineinabyss.geary.prefabs.configuration.components.EntityObservers -import com.mineinabyss.geary.prefabs.configuration.components.RoleCancelledException -import com.mineinabyss.geary.prefabs.configuration.components.RoleContext -import com.mineinabyss.geary.systems.builders.observe -import com.mineinabyss.geary.systems.query.query - -fun GearyModule.bindEntityObservers() = observe() - .involving(query()) - .exec { (observers) -> - observers.observers.forEach { observer -> - entity.observe(observer.event.id).involving(EntityType(observer.involving.map { it.id })).exec { - val context = RoleContext(entity) - try { - observer.emitEvents.forEach { event -> - when (val data = event.data) { - is Action -> with(data) { context.execute() } - else -> context.entity.emit(event = event.componentId, data = event.data) - } - } - } catch (_: RoleCancelledException) { - - } - } - } - entity.remove() - } - 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 90% 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 874e3391..a2929942 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,9 +1,8 @@ -package com.mineinabyss.geary.prefabs.configuration.components +package com.mineinabyss.geary.serialization.serializers 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 From b0c4850248581e090bd89fa828771e925be8a6b2 Mon Sep 17 00:00:00 2001 From: Danielle Voznyy Date: Thu, 25 Jul 2024 18:50:36 -0400 Subject: [PATCH 03/15] fix: Fix failing tests and build --- addons/geary-actions/build.gradle.kts | 11 +++++++++++ .../geary/actions/actions/EnsureAction.kt | 11 ++++++----- .../geary/actions}/ConfigEntityObserversTests.kt | 13 +++++-------- .../kotlin/com/mineinabyss/geary/prefabs/Prefabs.kt | 1 - 4 files changed, 22 insertions(+), 14 deletions(-) rename addons/{geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/observers => geary-actions/src/jvmTest/kotlin/com/mineinabyss/geary/actions}/ConfigEntityObserversTests.kt (86%) diff --git a/addons/geary-actions/build.gradle.kts b/addons/geary-actions/build.gradle.kts index 57b20c4e..ac29d641 100644 --- a/addons/geary-actions/build.gradle.kts +++ b/addons/geary-actions/build.gradle.kts @@ -15,5 +15,16 @@ kotlin { implementation(idofrontLibs.idofront.di) } } + val commonTest by getting { + dependencies { + implementation(kotlin("test")) + implementation(idofrontLibs.kotlinx.coroutines.test) + 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/actions/EnsureAction.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/actions/EnsureAction.kt index 6b5afcd8..c7031d6c 100644 --- 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 @@ -3,6 +3,7 @@ package com.mineinabyss.geary.actions.actions import com.mineinabyss.geary.actions.Action import com.mineinabyss.geary.actions.ActionsCancelledException import com.mineinabyss.geary.actions.ActionGroupContext +import com.mineinabyss.geary.actions.Condition import com.mineinabyss.geary.actions.event_binds.EventBind import com.mineinabyss.geary.helpers.componentId import com.mineinabyss.geary.serialization.serializers.InnerSerializer @@ -16,15 +17,15 @@ class EnsureAction( val conditions: SerializedComponents, ) : Action { @Transient - private val flat = conditions.map { EventBind.CachedEvent(componentId(it::class), it) } + private val flat = conditions.map { componentId(it::class) to it } override fun ActionGroupContext.execute() { - flat.forEach { - when (val condition = it.data) { - is Conwdition -> with(condition) { + flat.forEach { (id, data) -> + when (data) { + is Condition -> with(data) { if(!execute()) throw ActionsCancelledException() } - else -> entity.emit(it.componentId, it.data) //TODO use geary condition system if we get one + else -> entity.emit(id, data) //TODO use geary condition system if we get one } } } 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 6a96e7d9..24fa165b 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-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/Prefabs.kt b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/Prefabs.kt index 11e01ca9..aa4310d0 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) { From 96951e1e3deee6ceaa7c2b11329825914330358b Mon Sep 17 00:00:00 2001 From: Danielle Voznyy Date: Thu, 25 Jul 2024 22:53:17 -0400 Subject: [PATCH 04/15] chore: Stop publishing geary-catalog because gradle never invalidates local cache on it leading to confusing issues Publish version catalog entries in README instead --- README.md | 15 +++++++++++++++ geary-catalog/build.gradle.kts | 23 ----------------------- settings.gradle.kts | 1 - 3 files changed, 15 insertions(+), 24 deletions(-) delete mode 100644 geary-catalog/build.gradle.kts diff --git a/README.md b/README.md index 8b59ffc2..82e486ab 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/geary-catalog/build.gradle.kts b/geary-catalog/build.gradle.kts deleted file mode 100644 index 822cfee6..00000000 --- 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/settings.gradle.kts b/settings.gradle.kts index 43c7a911..eb2b95f4 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 From 17779921909fb1e192e2fb2f3aedcc81b677861a Mon Sep 17 00:00:00 2001 From: Danielle Voznyy Date: Sat, 27 Jul 2024 19:37:13 -0400 Subject: [PATCH 05/15] feat: Implement expressions with yaml config support feat: Add support for registering action results under variable names, and conditions per action --- addons/geary-actions/build.gradle.kts | 1 + .../com/mineinabyss/geary/actions/Action.kt | 2 +- .../mineinabyss/geary/actions/ActionGroup.kt | 21 ++++++- .../geary/actions/ActionGroupContext.kt | 6 ++ .../geary/actions/actions/EmitEventAction.kt | 4 +- .../actions/event_binds/EntityObservers.kt | 55 ++++++++++++++++-- .../geary/actions/event_binds/EventBind.kt | 3 +- .../event_binds/ParseEntityObservers.kt | 4 +- .../geary/actions/expressions/Expression.kt | 58 ++++++++++++++++++- .../geary/actions/ExpressionDecodingTest.kt | 55 ++++++++++++++++++ .../mineinabyss/geary/prefabs/PrefabLoader.kt | 2 +- .../PolymorphicListAsMapSerializer.kt | 15 ++--- .../serializers/ProvidedConfig.kt | 2 +- 13 files changed, 202 insertions(+), 26 deletions(-) create mode 100644 addons/geary-actions/src/jvmTest/kotlin/com/mineinabyss/geary/actions/ExpressionDecodingTest.kt diff --git a/addons/geary-actions/build.gradle.kts b/addons/geary-actions/build.gradle.kts index ac29d641..f57f158e 100644 --- a/addons/geary-actions/build.gradle.kts +++ b/addons/geary-actions/build.gradle.kts @@ -19,6 +19,7 @@ kotlin { 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) 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 index dcb50e35..085efc1d 100644 --- 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 @@ -1,5 +1,5 @@ package com.mineinabyss.geary.actions interface Action { - fun ActionGroupContext.execute() + fun ActionGroupContext.execute(): Any? } 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 index 1f3e5ab6..a8e576ff 100644 --- 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 @@ -1,12 +1,27 @@ package com.mineinabyss.geary.actions +import com.mineinabyss.geary.actions.actions.EnsureAction + +class ActionEntry( + val action: Action, + val conditions: List?, + val register: String?, +) + class ActionGroup( - val actions: List, + val actions: List, ) { fun execute(context: ActionGroupContext) { - actions.forEach { + actions.forEach { entry -> try { - with(it) { context.execute() } + entry.conditions?.forEach { condition -> + with(condition) { context.execute() } + } + + val returned = with(entry.action) { context.execute() } + + if (entry.register != null) + context.register(entry.register, returned) } catch (e: ActionsCancelledException) { return } 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 index 9d90310e..cee8faf2 100644 --- 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 @@ -6,5 +6,11 @@ import com.mineinabyss.geary.datatypes.GearyEntity class ActionGroupContext( var entity: GearyEntity, ) { + val environment: MutableMap = mutableMapOf() + fun eval(expression: Expression): T = expression.evaluate(this) + + fun register(name: String, value: Any?) { + environment[name] = value + } } 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 index c491a3b3..6c45e45c 100644 --- 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 @@ -8,7 +8,7 @@ import com.mineinabyss.geary.helpers.componentId class EmitEventAction( val eventId: ComponentId, val data: Any?, -): Action { +) : Action { override fun ActionGroupContext.execute() { entity.emit(event = eventId, data = data) } @@ -16,6 +16,6 @@ class EmitEventAction( companion object { fun from(data: Any) = EmitEventAction(componentId(data::class), data) - fun wrapIfNotAction(data: Any) = if(data is Action) data else from(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/event_binds/EntityObservers.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/EntityObservers.kt index b08a7baf..573c54cb 100644 --- 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 @@ -1,5 +1,9 @@ package com.mineinabyss.geary.actions.event_binds +import com.mineinabyss.geary.actions.Action +import com.mineinabyss.geary.actions.ActionEntry +import com.mineinabyss.geary.actions.actions.EmitEventAction +import com.mineinabyss.geary.actions.actions.EnsureAction import com.mineinabyss.geary.serialization.serializers.InnerSerializer import com.mineinabyss.geary.serialization.serializers.PolymorphicListAsMapSerializer import com.mineinabyss.geary.serialization.serializers.SerializableComponentId @@ -7,18 +11,61 @@ import com.mineinabyss.geary.serialization.serializers.SerializedComponents import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.builtins.MapSerializer +import kotlin.jvm.JvmInline @Serializable(with = EntityObservers.Serializer::class) -class EntityObservers(val observers: List) { +class EntityObservers( + val observers: List, +) { class Serializer : InnerSerializer>, EntityObservers>( serialName = "geary:observe", inner = MapSerializer( SerializableComponentId.serializer(), - ListSerializer(PolymorphicListAsMapSerializer.ofComponents()) + ListSerializer( + PolymorphicListAsMapSerializer.ofComponents( + PolymorphicListAsMapSerializer.Config( + customKeys = mapOf( + "when" to ActionWhen.serializer(), + "register" to ActionRegister.serializer() + ) + ) + ) + ) ), - inverseTransform = { it.observers.associate { it.event to it.emit } }, - transform = { EntityObservers(it.map { (event, emit) -> EventBind(event, emit = emit) }) } + inverseTransform = { TODO() }, + transform = { + EntityObservers( + it.map { (event, emit) -> + val actions = emit.map { components -> + var action: Action? = null + var condition: List? = null + var register: String? = null + components.forEach { comp -> + when { + comp is ActionWhen -> condition = comp.conditions + comp is ActionRegister -> register = comp.register + action != null -> error("Multiple actions defined in one block!") + else -> action = EmitEventAction.wrapIfNotAction(comp) + } + } + ActionEntry( + action = action!!, + conditions = condition, + register = register + ) + } + EventBind(event, emit = actions) + } + ) + } ) } +@JvmInline +@Serializable +value class ActionWhen(val conditions: List) + +@JvmInline +@Serializable +value class ActionRegister(val register: String) 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 index 47e64bf3..84c8e94c 100644 --- 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 @@ -1,10 +1,11 @@ package com.mineinabyss.geary.actions.event_binds +import com.mineinabyss.geary.actions.ActionEntry import com.mineinabyss.geary.serialization.serializers.SerializableComponentId import com.mineinabyss.geary.serialization.serializers.SerializedComponents class EventBind( val event: SerializableComponentId, val involving: List = listOf(), - val emit: List, + val emit: List, ) diff --git a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/ParseEntityObservers.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/ParseEntityObservers.kt index b6820164..a70e21e4 100644 --- a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/ParseEntityObservers.kt +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/ParseEntityObservers.kt @@ -14,9 +14,7 @@ fun GearyModule.bindEntityObservers() = observe() .involving(query()) .exec { (observers) -> observers.observers.forEach { observer -> - val actionGroup = ActionGroup( - actions = observer.emit.flatten().map { EmitEventAction.wrapIfNotAction(it) } - ) + val actionGroup = ActionGroup(observer.emit) entity.observe(observer.event.id).involving(EntityType(observer.involving.map { it.id })).exec { val context = ActionGroupContext(entity) actionGroup.execute(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 index 75fa1f69..0fb32cc4 100644 --- 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 @@ -1,9 +1,61 @@ package com.mineinabyss.geary.actions.expressions import com.mineinabyss.geary.actions.ActionGroupContext +import kotlinx.serialization.ContextualSerializer +import kotlinx.serialization.InternalSerializationApi +import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.AbstractDecoder +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.encoding.decodeStructure +import kotlinx.serialization.modules.SerializersModule -@Serializable -abstract class Expression { - abstract fun evaluate(context: ActionGroupContext): T +@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 Evaluate( + val expression: String, + ) : Expression { + override fun evaluate(context: ActionGroupContext): T { + return context.environment[expression] as? T ?: error("Expression $expression not found in context") + } + } + + // 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> { + @OptIn(InternalSerializationApi::class) + override val descriptor: SerialDescriptor = + ContextualSerializer(Any::class).descriptor//buildSerialDescriptor("ExpressionSerializer", SerialKind.CONTEXTUAL) + + 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 Evaluate(string.removePrefix("{{").removeSuffix("}}").trim()) + } + + // 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") + } + } } 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 00000000..fc1ae9a1 --- /dev/null +++ b/addons/geary-actions/src/jvmTest/kotlin/com/mineinabyss/geary/actions/ExpressionDecodingTest.kt @@ -0,0 +1,55 @@ +package com.mineinabyss.geary.actions + +import com.charleskorn.kaml.Yaml +import com.mineinabyss.geary.actions.expressions.Expression +import com.mineinabyss.geary.serialization.formats.YamlFormat +import com.mineinabyss.geary.serialization.serializableComponents +import io.kotest.matchers.shouldBe +import kotlinx.serialization.Contextual +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.json.Json +import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.modules.contextual +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 }}" +// ) +// } + + @org.junit.jupiter.api.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.Evaluate("test"), + regular = "{{ asdf }}" + ) + } +} 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 7b36713c..9b4abda0 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-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 07c91ac1..23243083 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] ?: 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> = 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 ef59bfb8..28650b3c 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) From 96284a4128b79534265d1152ef51f19de1fb99ae Mon Sep 17 00:00:00 2001 From: Danielle Voznyy Date: Sat, 27 Jul 2024 22:22:30 -0400 Subject: [PATCH 06/15] feat: Config driven passive systems --- .../mineinabyss/geary/actions/ActionGroup.kt | 47 +++++++++++++++ .../mineinabyss/geary/actions/GearyActions.kt | 3 +- .../actions/event_binds/EntityObservers.kt | 42 ++----------- .../geary/actions/event_binds/EventBind.kt | 5 +- .../event_binds/ParseEntityObservers.kt | 4 +- .../geary/actions/event_binds/Passive.kt | 59 +++++++++++++++++++ .../actions/serializers/DurationSerializer.kt | 41 +++++++++++++ 7 files changed, 157 insertions(+), 44 deletions(-) create mode 100644 addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/Passive.kt create mode 100644 addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/serializers/DurationSerializer.kt 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 index a8e576ff..08247769 100644 --- 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 @@ -1,6 +1,15 @@ 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.ActionRegister +import com.mineinabyss.geary.actions.event_binds.ActionWhen +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, @@ -8,6 +17,7 @@ class ActionEntry( val register: String?, ) +@Serializable(with = ActionGroup.Serializer::class) class ActionGroup( val actions: List, ) { @@ -27,4 +37,41 @@ class ActionGroup( } } } + + object Serializer : InnerSerializer, ActionGroup>( + serialName = "geary:action_group", + inner = ListSerializer( + PolymorphicListAsMapSerializer.ofComponents( + PolymorphicListAsMapSerializer.Config( + customKeys = mapOf( + "when" to ActionWhen.serializer(), + "register" to ActionRegister.serializer() + ) + ) + ) + ), + inverseTransform = { TODO() }, + transform = { + val actions = it.mapNotNull { components -> + var action: Action? = null + var condition: List? = null + var register: String? = null + components.forEach { comp -> + when { + comp is ActionWhen -> condition = comp.conditions + comp is ActionRegister -> register = comp.register + 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 + ) + } + ActionGroup(actions) + } + ) } 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 index a70efd33..0c7505ca 100644 --- 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 @@ -1,7 +1,7 @@ package com.mineinabyss.geary.actions import com.mineinabyss.geary.actions.event_binds.bindEntityObservers -import com.mineinabyss.geary.addons.GearyPhase +import com.mineinabyss.geary.actions.event_binds.parsePassive import com.mineinabyss.geary.addons.dsl.GearyAddonWithDefault import com.mineinabyss.geary.modules.geary @@ -12,6 +12,7 @@ class GearyActions { override fun GearyActions.install() { geary.run { bindEntityObservers() + parsePassive() } } } 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 index 573c54cb..cd4b65f1 100644 --- 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 @@ -1,15 +1,10 @@ package com.mineinabyss.geary.actions.event_binds -import com.mineinabyss.geary.actions.Action -import com.mineinabyss.geary.actions.ActionEntry -import com.mineinabyss.geary.actions.actions.EmitEventAction +import com.mineinabyss.geary.actions.ActionGroup import com.mineinabyss.geary.actions.actions.EnsureAction import com.mineinabyss.geary.serialization.serializers.InnerSerializer -import com.mineinabyss.geary.serialization.serializers.PolymorphicListAsMapSerializer import com.mineinabyss.geary.serialization.serializers.SerializableComponentId -import com.mineinabyss.geary.serialization.serializers.SerializedComponents import kotlinx.serialization.Serializable -import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.builtins.MapSerializer import kotlin.jvm.JvmInline @@ -17,44 +12,17 @@ import kotlin.jvm.JvmInline class EntityObservers( val observers: List, ) { - class Serializer : InnerSerializer>, EntityObservers>( + class Serializer : InnerSerializer, EntityObservers>( serialName = "geary:observe", inner = MapSerializer( SerializableComponentId.serializer(), - ListSerializer( - PolymorphicListAsMapSerializer.ofComponents( - PolymorphicListAsMapSerializer.Config( - customKeys = mapOf( - "when" to ActionWhen.serializer(), - "register" to ActionRegister.serializer() - ) - ) - ) - ) + ActionGroup.Serializer ), inverseTransform = { TODO() }, transform = { EntityObservers( - it.map { (event, emit) -> - val actions = emit.map { components -> - var action: Action? = null - var condition: List? = null - var register: String? = null - components.forEach { comp -> - when { - comp is ActionWhen -> condition = comp.conditions - comp is ActionRegister -> register = comp.register - action != null -> error("Multiple actions defined in one block!") - else -> action = EmitEventAction.wrapIfNotAction(comp) - } - } - ActionEntry( - action = action!!, - conditions = condition, - register = register - ) - } - EventBind(event, emit = actions) + it.map { (event, actionGroup) -> + EventBind(event, actionGroup = actionGroup) } ) } 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 index 84c8e94c..f203abf2 100644 --- 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 @@ -1,11 +1,10 @@ package com.mineinabyss.geary.actions.event_binds -import com.mineinabyss.geary.actions.ActionEntry +import com.mineinabyss.geary.actions.ActionGroup import com.mineinabyss.geary.serialization.serializers.SerializableComponentId -import com.mineinabyss.geary.serialization.serializers.SerializedComponents class EventBind( val event: SerializableComponentId, val involving: List = listOf(), - val emit: List, + val actionGroup: ActionGroup, ) diff --git a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/ParseEntityObservers.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/ParseEntityObservers.kt index a70e21e4..d50082bc 100644 --- a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/ParseEntityObservers.kt +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/ParseEntityObservers.kt @@ -1,8 +1,6 @@ package com.mineinabyss.geary.actions.event_binds -import com.mineinabyss.geary.actions.ActionGroup import com.mineinabyss.geary.actions.ActionGroupContext -import com.mineinabyss.geary.actions.actions.EmitEventAction import com.mineinabyss.geary.datatypes.EntityType import com.mineinabyss.geary.modules.GearyModule import com.mineinabyss.geary.observers.entity.observe @@ -14,7 +12,7 @@ fun GearyModule.bindEntityObservers() = observe() .involving(query()) .exec { (observers) -> observers.observers.forEach { observer -> - val actionGroup = ActionGroup(observer.emit) + val actionGroup = observer.actionGroup entity.observe(observer.event.id).involving(EntityType(observer.involving.map { it.id })).exec { val context = ActionGroupContext(entity) actionGroup.execute(context) 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 00000000..4535e0e5 --- /dev/null +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/Passive.kt @@ -0,0 +1,59 @@ +package com.mineinabyss.geary.actions.event_binds; + +import com.mineinabyss.geary.actions.ActionGroup +import com.mineinabyss.geary.actions.ActionGroupContext +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 + ) +} + +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/serializers/DurationSerializer.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/serializers/DurationSerializer.kt new file mode 100644 index 00000000..478aa1fc --- /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 + } + } +} From e506c84a8b907a1a28563ddfc1df9aca3f9291e7 Mon Sep 17 00:00:00 2001 From: Danielle Voznyy Date: Sat, 27 Jul 2024 22:53:37 -0400 Subject: [PATCH 07/15] fix: ActionWhen serializer --- .../geary/actions/event_binds/EntityObservers.kt | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) 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 index cd4b65f1..14286b36 100644 --- 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 @@ -5,6 +5,7 @@ import com.mineinabyss.geary.actions.actions.EnsureAction import com.mineinabyss.geary.serialization.serializers.InnerSerializer import com.mineinabyss.geary.serialization.serializers.SerializableComponentId import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.builtins.MapSerializer import kotlin.jvm.JvmInline @@ -30,9 +31,15 @@ class EntityObservers( } -@JvmInline -@Serializable -value class ActionWhen(val conditions: List) +@Serializable(with = ActionWhen.Serializer::class) +class ActionWhen(val conditions: List) { + object Serializer : InnerSerializer, ActionWhen>( + serialName = "geary:when", + inner = ListSerializer(EnsureAction.serializer()), + inverseTransform = ActionWhen::conditions, + transform = ::ActionWhen + ) +} @JvmInline @Serializable From 7c14aa33b8e3d6415bbbdad3aebb95cb862075d6 Mon Sep 17 00:00:00 2001 From: Danielle Voznyy Date: Sun, 28 Jul 2024 21:17:12 -0400 Subject: [PATCH 08/15] fix: Get onFail working in actions --- .gitignore | 1 + .../com/mineinabyss/geary/actions/ActionGroup.kt | 15 +++++++++++---- .../geary/actions/actions/EnsureAction.kt | 10 ++++------ .../geary/actions/event_binds/EntityObservers.kt | 14 ++++++++++++-- .../serializers/PolymorphicListAsMapSerializer.kt | 4 ++-- 5 files changed, 30 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 7231c09a..9a4a5f3e 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ eclipse kotlin-js-store/ geary-benchmarks/.results +/truckconfig.json 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 index 08247769..7610f4aa 100644 --- 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 @@ -2,6 +2,7 @@ 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.ActionOnFail import com.mineinabyss.geary.actions.event_binds.ActionRegister import com.mineinabyss.geary.actions.event_binds.ActionWhen import com.mineinabyss.geary.modules.geary @@ -15,6 +16,7 @@ class ActionEntry( val action: Action, val conditions: List?, val register: String?, + val onFail: ActionGroup?, ) @Serializable(with = ActionGroup.Serializer::class) @@ -33,19 +35,21 @@ class ActionGroup( if (entry.register != null) context.register(entry.register, returned) } catch (e: ActionsCancelledException) { + entry.onFail?.execute(context) return } } } - object Serializer : InnerSerializer, ActionGroup>( + class Serializer : InnerSerializer, ActionGroup>( serialName = "geary:action_group", inner = ListSerializer( PolymorphicListAsMapSerializer.ofComponents( PolymorphicListAsMapSerializer.Config( customKeys = mapOf( - "when" to ActionWhen.serializer(), - "register" to ActionRegister.serializer() + "when" to { ActionWhen.serializer() }, + "register" to { ActionRegister.serializer() }, + "onFail" to { ActionOnFail.serializer() } ) ) ) @@ -56,10 +60,12 @@ class ActionGroup( var action: Action? = null var condition: List? = null var register: String? = null + var onFail: ActionGroup? = null components.forEach { comp -> when { comp is ActionWhen -> condition = comp.conditions comp is ActionRegister -> register = comp.register + comp is ActionOnFail -> onFail = comp.action action != null -> geary.logger.w { "Multiple actions defined in one block!" } else -> action = EmitEventAction.wrapIfNotAction(comp) } @@ -68,7 +74,8 @@ class ActionGroup( ActionEntry( action = action!!, conditions = condition, - register = register + register = register, + onFail = onFail ) } ActionGroup(actions) 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 index c7031d6c..5b673a5e 100644 --- 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 @@ -1,10 +1,6 @@ package com.mineinabyss.geary.actions.actions -import com.mineinabyss.geary.actions.Action -import com.mineinabyss.geary.actions.ActionsCancelledException -import com.mineinabyss.geary.actions.ActionGroupContext -import com.mineinabyss.geary.actions.Condition -import com.mineinabyss.geary.actions.event_binds.EventBind +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 @@ -23,7 +19,9 @@ class EnsureAction( flat.forEach { (id, data) -> when (data) { is Condition -> with(data) { - if(!execute()) throw ActionsCancelledException() + if(!execute()) { + throw ActionsCancelledException() + } } else -> entity.emit(id, data) //TODO use geary condition system if we get one } 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 index 14286b36..6bb5574b 100644 --- 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 @@ -17,7 +17,7 @@ class EntityObservers( serialName = "geary:observe", inner = MapSerializer( SerializableComponentId.serializer(), - ActionGroup.Serializer + ActionGroup.serializer() ), inverseTransform = { TODO() }, transform = { @@ -33,7 +33,7 @@ class EntityObservers( @Serializable(with = ActionWhen.Serializer::class) class ActionWhen(val conditions: List) { - object Serializer : InnerSerializer, ActionWhen>( + class Serializer : InnerSerializer, ActionWhen>( serialName = "geary:when", inner = ListSerializer(EnsureAction.serializer()), inverseTransform = ActionWhen::conditions, @@ -44,3 +44,13 @@ class ActionWhen(val conditions: List) { @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 + ) +} 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 23243083..0b3187b1 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 @@ -49,7 +49,7 @@ open class PolymorphicListAsMapSerializer( } else -> { - val componentSerializer = config.customKeys[key] ?: 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) @@ -122,7 +122,7 @@ open class PolymorphicListAsMapSerializer( val onMissingSerializer: OnMissing = OnMissing.WARN, val skipMalformedComponents: Boolean = true, val whenComponentMalformed: (String) -> Unit = {}, - val customKeys: Map> = mapOf(), + val customKeys: Map KSerializer> = mapOf(), ) companion object { From 4d8c7305ed0d6484d3e895f1a8a9b1da9795896a Mon Sep 17 00:00:00 2001 From: Danielle Voznyy Date: Sun, 28 Jul 2024 21:30:44 -0400 Subject: [PATCH 09/15] fix: Fix PolymorphicListAsMapSerializer.Config in tests --- .../com/mineinabyss/geary/prefabs/GearyEntitySerializerTest.kt | 2 +- .../kotlin/com/mineinabyss/geary/prefabs/SerializerTest.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 ed875686..82cd0247 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 1f33cd5e..77b777dc 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)) From 3ad752148fe8b5fe3eb00bfa49530ca945093561 Mon Sep 17 00:00:00 2001 From: Danielle Voznyy Date: Wed, 31 Jul 2024 12:09:04 -0400 Subject: [PATCH 10/15] feat(actions): Loop support --- .../com/mineinabyss/geary/actions/Action.kt | 5 +++ .../mineinabyss/geary/actions/ActionGroup.kt | 43 +++++++++++++------ .../geary/actions/ActionGroupContext.kt | 6 +++ .../actions/event_binds/EntityObservers.kt | 4 ++ .../event_binds/ParseEntityObservers.kt | 1 + .../geary/actions/event_binds/Passive.kt | 1 + .../actions/expressions/EntityExpression.kt | 6 +-- .../geary/actions/expressions/Expression.kt | 2 + 8 files changed, 53 insertions(+), 15 deletions(-) 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 index 085efc1d..8c2aab05 100644 --- 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 @@ -1,5 +1,10 @@ package com.mineinabyss.geary.actions +import kotlin.jvm.JvmName + interface Action { 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 index 7610f4aa..4f1309ea 100644 --- 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 @@ -2,9 +2,11 @@ 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.ActionLoop import com.mineinabyss.geary.actions.event_binds.ActionOnFail import com.mineinabyss.geary.actions.event_binds.ActionRegister import com.mineinabyss.geary.actions.event_binds.ActionWhen +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 @@ -17,23 +19,25 @@ class ActionEntry( val conditions: List?, val register: String?, val onFail: ActionGroup?, + val loop: Expression>?, ) @Serializable(with = ActionGroup.Serializer::class) class ActionGroup( val actions: List, -) { - fun execute(context: ActionGroupContext) { +): Action { + override fun ActionGroupContext.execute() { + val context = this actions.forEach { entry -> try { - entry.conditions?.forEach { condition -> - with(condition) { context.execute() } - } - - val returned = with(entry.action) { context.execute() } - - if (entry.register != null) - context.register(entry.register, returned) + if (entry.loop != null) { + entry.loop.evaluate(context).forEach { loopEntry -> + val subcontext = context.copy() + subcontext.register("item", loopEntry) + executeEntry(subcontext, entry) + } + } else + executeEntry(context, entry) } catch (e: ActionsCancelledException) { entry.onFail?.execute(context) return @@ -41,6 +45,17 @@ class ActionGroup( } } + private fun executeEntry(context: ActionGroupContext, entry: ActionEntry) { + entry.conditions?.forEach { condition -> + with(condition) { context.execute() } + } + + val returned = with(entry.action) { context.execute() } + + if (entry.register != null) + context.register(entry.register, returned) + } + class Serializer : InnerSerializer, ActionGroup>( serialName = "geary:action_group", inner = ListSerializer( @@ -49,7 +64,8 @@ class ActionGroup( customKeys = mapOf( "when" to { ActionWhen.serializer() }, "register" to { ActionRegister.serializer() }, - "onFail" to { ActionOnFail.serializer() } + "onFail" to { ActionOnFail.serializer() }, + "loop" to { ActionLoop.serializer() } ) ) ) @@ -60,12 +76,14 @@ class ActionGroup( var action: Action? = null var condition: List? = null var register: String? = null + var loop: Expression>? = null var onFail: ActionGroup? = 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.Evaluate(comp.expression) action != null -> geary.logger.w { "Multiple actions defined in one block!" } else -> action = EmitEventAction.wrapIfNotAction(comp) } @@ -75,7 +93,8 @@ class ActionGroup( action = action!!, conditions = condition, register = register, - onFail = onFail + onFail = onFail, + loop = loop ) } 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 index cee8faf2..5f070db4 100644 --- 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 @@ -13,4 +13,10 @@ class ActionGroupContext( fun register(name: String, value: Any?) { environment[name] = value } + + fun copy(): ActionGroupContext { + val newContext = ActionGroupContext(entity) + newContext.environment.putAll(environment) + return newContext + } } 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 index 6bb5574b..d5d0d557 100644 --- 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 @@ -54,3 +54,7 @@ class ActionOnFail(val action: ActionGroup) { transform = ::ActionOnFail ) } + +@JvmInline +@Serializable +value class ActionLoop(val expression: String) diff --git a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/ParseEntityObservers.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/ParseEntityObservers.kt index d50082bc..67870bdf 100644 --- a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/ParseEntityObservers.kt +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/ParseEntityObservers.kt @@ -1,6 +1,7 @@ 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 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 index 4535e0e5..2d9b80d7 100644 --- 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 @@ -2,6 +2,7 @@ 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 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 index 7c05216b..961c0d98 100644 --- 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 @@ -10,9 +10,9 @@ import kotlin.jvm.JvmInline @Serializable value class EntityExpression( val expression: String, -) /*: Expression()*/ { - fun evaluate(context: ActionGroupContext): GearyEntity { +) : Expression { + override fun evaluate(context: ActionGroupContext): GearyEntity { return if (expression == "parent") context.entity.parent!! - else TODO() + else Expression.Evaluate(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 index 0fb32cc4..29b78c9c 100644 --- 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 @@ -59,3 +59,5 @@ sealed interface Expression { } } } + +fun expr(value: T) = Expression.Fixed(value) From fa76ca7b13dec9abc08fc22087a2462057646d8f Mon Sep 17 00:00:00 2001 From: Danielle Voznyy Date: Fri, 2 Aug 2024 13:25:46 -0400 Subject: [PATCH 11/15] feat: Start working on better inline expression support --- .../geary/actions/expressions/Expression.kt | 66 +++++++++++++++++++ .../expressions/ExpressionEvaluator.kt | 10 +++ 2 files changed, 76 insertions(+) create mode 100644 addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/expressions/ExpressionEvaluator.kt 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 index 29b78c9c..b7ed8320 100644 --- 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 @@ -12,6 +12,7 @@ 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 { @@ -30,6 +31,53 @@ sealed interface Expression { } } + companion object { + val funcRegex = Regex("[.{}]") + fun parseExpression(string: String, module: SerializersModule): Expression<*> { + val before = string.substringBefore(".") + val after = string.substringAfter(".") + val reference = Evaluate(before) + return foldFunctions(reference, after, module) + } + + // entity.doSomething(at: {{ entity.location }}, ) + + 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 { + if (!expr.startsWith("{")) return "{}" to expr + var brackets = 0 + val yamlEndIndex = expr.indexOfFirst { + if (it == '{') brackets++ + if (it == '}') brackets-- + brackets != 0 + } + return expr.take(yamlEndIndex - 1) to expr.drop(yamlEndIndex - 1) + } + + 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 to null + return expr.take(end) to expr.drop(end) + } + + fun of(string: String): Expression<*> { + + } + } + // 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> { @@ -60,4 +108,22 @@ sealed interface Expression { } } +abstract class FunctionExpression( + val input: Expression, + val name: String, + val yaml: String +) : Expression { + companion object { + fun parse(ref: Expression<*>, name: String, yaml: String, module: SerializersModule): FunctionExpression<*, *> { + TODO() + } + } + + abstract fun map(input: I, context: ActionGroupContext): O + + override fun evaluate(context: ActionGroupContext): O { + return map(context.eval(input), context) + } +} + 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 00000000..cacef862 --- /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 From 1c136d42114ce8852c5f4bc9c5e7d70eda8bc789 Mon Sep 17 00:00:00 2001 From: Danielle Voznyy Date: Fri, 2 Aug 2024 14:41:43 -0400 Subject: [PATCH 12/15] feat: Inline expressions using yaml --- .../mineinabyss/geary/actions/ActionGroup.kt | 4 +- .../geary/actions/actions/BecomeAction.kt | 2 +- .../geary/actions/actions/EvalAction.kt | 23 +++++++ .../actions/event_binds/EntityObservers.kt | 4 +- .../geary/actions/event_binds/Passive.kt | 2 +- .../actions/expressions/EntityExpression.kt | 2 +- .../geary/actions/expressions/Expression.kt | 62 ++++++------------- .../actions/expressions/FunctionExpression.kt | 30 +++++++++ .../FunctionExpressionWithInput.kt | 13 ++++ .../expressions/InlineExpressionSerializer.kt | 23 +++++++ .../geary/actions/ExpressionDecodingTest.kt | 44 ++++++++++--- .../serializers/InnerSerializer.kt | 4 +- .../serializers/SerializableComponentId.kt | 17 +++-- 13 files changed, 165 insertions(+), 65 deletions(-) create mode 100644 addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/actions/EvalAction.kt create mode 100644 addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/expressions/FunctionExpression.kt create mode 100644 addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/expressions/FunctionExpressionWithInput.kt create mode 100644 addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/expressions/InlineExpressionSerializer.kt 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 index 4f1309ea..375a0348 100644 --- 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 @@ -33,7 +33,7 @@ class ActionGroup( if (entry.loop != null) { entry.loop.evaluate(context).forEach { loopEntry -> val subcontext = context.copy() - subcontext.register("item", loopEntry) + subcontext.register("it", loopEntry) executeEntry(subcontext, entry) } } else @@ -83,7 +83,7 @@ class ActionGroup( comp is ActionWhen -> condition = comp.conditions comp is ActionRegister -> register = comp.register comp is ActionOnFail -> onFail = comp.action - comp is ActionLoop -> loop = Expression.Evaluate(comp.expression) + comp is ActionLoop -> loop = Expression.parseExpression(comp.expression, serializersModule) as Expression> action != null -> geary.logger.w { "Multiple actions defined in one block!" } else -> action = EmitEventAction.wrapIfNotAction(comp) } 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 index 360b38ed..f4a125ca 100644 --- 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 @@ -20,6 +20,6 @@ class BecomeAction( serialName = "geary:become", inner = EntityExpression.serializer(), inverseTransform = { it.become }, - transform = ::BecomeAction + transform = { BecomeAction(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 00000000..f11e0221 --- /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 index d5d0d557..f6a1e732 100644 --- 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 @@ -37,7 +37,7 @@ class ActionWhen(val conditions: List) { serialName = "geary:when", inner = ListSerializer(EnsureAction.serializer()), inverseTransform = ActionWhen::conditions, - transform = ::ActionWhen + transform = { ActionWhen(it) } ) } @@ -51,7 +51,7 @@ class ActionOnFail(val action: ActionGroup) { serialName = "geary:on_fail", inner = ActionGroup.Serializer(), inverseTransform = ActionOnFail::action, - transform = ::ActionOnFail + transform = { ActionOnFail(it) } ) } 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 index 2d9b80d7..20ded7fd 100644 --- 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 @@ -33,7 +33,7 @@ class Passive( serialName = "geary:passive", inner = ListSerializer(SystemBind.serializer()), inverseTransform = Passive::systems, - transform = ::Passive + transform = { Passive(it) } ) } 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 index 961c0d98..70d1b287 100644 --- 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 @@ -13,6 +13,6 @@ value class EntityExpression( ) : Expression { override fun evaluate(context: ActionGroupContext): GearyEntity { return if (expression == "parent") context.entity.parent!! - else Expression.Evaluate(expression).evaluate(context) + 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 index b7ed8320..9e37193b 100644 --- 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 @@ -2,12 +2,10 @@ package com.mineinabyss.geary.actions.expressions import com.mineinabyss.geary.actions.ActionGroupContext import kotlinx.serialization.ContextualSerializer -import kotlinx.serialization.InternalSerializationApi import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.serializer import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.AbstractDecoder import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.encoding.decodeStructure @@ -23,67 +21,59 @@ sealed interface Expression { override fun evaluate(context: ActionGroupContext): T = value } - data class Evaluate( + 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 { - val funcRegex = Regex("[.{}]") fun parseExpression(string: String, module: SerializersModule): Expression<*> { - val before = string.substringBefore(".") - val after = string.substringAfter(".") - val reference = Evaluate(before) - return foldFunctions(reference, after, module) + val (name, rem) = getFunctionName(string) + val reference = Variable(name) + if(rem == "") return reference + return foldFunctions(reference, rem, module) } - // entity.doSomething(at: {{ entity.location }}, ) - tailrec fun foldFunctions( reference: Expression<*>, remainder: String, - module: SerializersModule + module: SerializersModule, ): Expression<*> { val (name, afterName) = getFunctionName(remainder) - val (yaml, afterYaml) = getYaml(afterName ?: "{}") + 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 { - if (!expr.startsWith("{")) return "{}" to expr var brackets = 0 val yamlEndIndex = expr.indexOfFirst { if (it == '{') brackets++ if (it == '}') brackets-- - brackets != 0 + brackets == 0 } - return expr.take(yamlEndIndex - 1) to expr.drop(yamlEndIndex - 1) + if (yamlEndIndex <= 0) return "{}" to expr + return expr.take(yamlEndIndex + 1).trim() to expr.drop(yamlEndIndex + 1).trim() } - fun getFunctionName(expr: String): Pair { + 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 to null - return expr.take(end) to expr.drop(end) - } - - fun of(string: String): Expression<*> { - + 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> { - @OptIn(InternalSerializationApi::class) - override val descriptor: SerialDescriptor = - ContextualSerializer(Any::class).descriptor//buildSerialDescriptor("ExpressionSerializer", SerialKind.CONTEXTUAL) + 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 @@ -93,7 +83,10 @@ sealed interface Expression { } }.onSuccess { string -> if (string.startsWith("{{") && string.endsWith("}}")) - return Evaluate(string.removePrefix("{{").removeSuffix("}}").trim()) + return parseExpression( + string.removePrefix("{{").removeSuffix("}}").trim(), + decoder.serializersModule + ) as Expression } // Fallback to reading the value in-place @@ -106,24 +99,7 @@ sealed interface Expression { TODO("Not yet implemented") } } -} -abstract class FunctionExpression( - val input: Expression, - val name: String, - val yaml: String -) : Expression { - companion object { - fun parse(ref: Expression<*>, name: String, yaml: String, module: SerializersModule): FunctionExpression<*, *> { - TODO() - } - } - - abstract fun map(input: I, context: ActionGroupContext): O - - override fun evaluate(context: ActionGroupContext): O { - return map(context.eval(input), context) - } } fun expr(value: T) = Expression.Fixed(value) 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 00000000..6aac9fa5 --- /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 00000000..e95cea1a --- /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 00000000..0d2b609a --- /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/jvmTest/kotlin/com/mineinabyss/geary/actions/ExpressionDecodingTest.kt b/addons/geary-actions/src/jvmTest/kotlin/com/mineinabyss/geary/actions/ExpressionDecodingTest.kt index fc1ae9a1..48f32e1a 100644 --- 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 @@ -2,15 +2,18 @@ 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.geary.serialization.serializableComponents +import com.mineinabyss.idofront.di.DI import io.kotest.matchers.shouldBe -import kotlinx.serialization.Contextual +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.serializer -import kotlinx.serialization.json.Json -import kotlinx.serialization.modules.SerializersModule -import kotlinx.serialization.modules.contextual import org.junit.jupiter.api.Test class ExpressionDecodingTest { @@ -37,7 +40,7 @@ class ExpressionDecodingTest { // ) // } - @org.junit.jupiter.api.Test + @Test fun `should correctly decode yaml`() { val input = """ { @@ -48,8 +51,35 @@ class ExpressionDecodingTest { """.trimIndent() Yaml.default.decodeFromString(TestData.serializer(), input) shouldBe TestData( name = Expression.Fixed("variable"), - age = Expression.Evaluate("test"), + 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-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 9a1a81d4..fc422f72 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/SerializableComponentId.kt b/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/serializers/SerializableComponentId.kt index a2929942..00de2ef1 100644 --- a/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/serializers/SerializableComponentId.kt +++ b/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/serializers/SerializableComponentId.kt @@ -1,5 +1,6 @@ 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 @@ -9,6 +10,8 @@ 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) { @@ -18,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) + } } } From c72731a65c01ef11921b9701bf6e5138757f39a5 Mon Sep 17 00:00:00 2001 From: Danielle Voznyy Date: Wed, 21 Aug 2024 22:36:20 -0400 Subject: [PATCH 13/15] feat: add conditionsMet helper to EnsureAction feat: Use environment for entity in ActionGroupContext, allow creating without an entity --- .../mineinabyss/geary/actions/ActionGroup.kt | 4 ++-- .../geary/actions/ActionGroupContext.kt | 18 ++++++++++++++---- .../geary/actions/actions/EnsureAction.kt | 14 ++++++++++++-- gradle.properties | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 5 files changed, 30 insertions(+), 10 deletions(-) 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 index 375a0348..2d0cdde9 100644 --- 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 @@ -47,10 +47,10 @@ class ActionGroup( private fun executeEntry(context: ActionGroupContext, entry: ActionEntry) { entry.conditions?.forEach { condition -> - with(condition) { context.execute() } + condition.execute(context) } - val returned = with(entry.action) { context.execute() } + val returned = entry.action.execute(context) if (entry.register != null) context.register(entry.register, returned) 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 index 5f070db4..bc8e7244 100644 --- 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 @@ -3,9 +3,17 @@ package com.mineinabyss.geary.actions import com.mineinabyss.geary.actions.expressions.Expression import com.mineinabyss.geary.datatypes.GearyEntity -class ActionGroupContext( - var entity: 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) @@ -15,8 +23,10 @@ class ActionGroupContext( } fun copy(): ActionGroupContext { - val newContext = ActionGroupContext(entity) + val newContext = ActionGroupContext() newContext.environment.putAll(environment) return newContext } + + companion object } 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 index 5b673a5e..cec7525f 100644 --- 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 @@ -19,16 +19,26 @@ class EnsureAction( flat.forEach { (id, data) -> when (data) { is Condition -> with(data) { - if(!execute()) { + if (!execute()) { throw ActionsCancelledException() } } + else -> entity.emit(id, data) //TODO use geary condition system if we get one } } } - object Serializer: InnerSerializer( + 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 }, diff --git a/gradle.properties b/gradle.properties index f426544d..d28ee949 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 a4413138..9355b415 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 From 07f082db70d81edc0e0e18766daad9319286a10c Mon Sep 17 00:00:00 2001 From: Danielle Voznyy Date: Fri, 23 Aug 2024 15:36:08 -0400 Subject: [PATCH 14/15] feat: with tag for actions to support passing environment --- .../com/mineinabyss/geary/actions/Action.kt | 3 +++ .../mineinabyss/geary/actions/ActionGroup.kt | 21 ++++++++++++------- .../geary/actions/ActionGroupContext.kt | 10 +++++++-- .../geary/actions/actions/BecomeAction.kt | 2 ++ .../geary/actions/actions/EmitEventAction.kt | 2 +- .../geary/actions/actions/EnsureAction.kt | 2 +- .../actions/event_binds/EntityObservers.kt | 14 +++++++++++++ .../actions/expressions/EntityExpression.kt | 2 +- 8 files changed, 43 insertions(+), 13 deletions(-) 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 index 8c2aab05..50a0d625 100644 --- 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 @@ -3,6 +3,9 @@ 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? } 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 index 2d0cdde9..ab6b2e2d 100644 --- 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 @@ -2,10 +2,7 @@ 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.ActionLoop -import com.mineinabyss.geary.actions.event_binds.ActionOnFail -import com.mineinabyss.geary.actions.event_binds.ActionRegister -import com.mineinabyss.geary.actions.event_binds.ActionWhen +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 @@ -20,15 +17,18 @@ class ActionEntry( val register: String?, val onFail: ActionGroup?, val loop: Expression>?, + val environment: Map>?, ) @Serializable(with = ActionGroup.Serializer::class) class ActionGroup( val actions: List, -): Action { +) : Action { override fun ActionGroupContext.execute() { - val context = this 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 -> @@ -78,12 +78,16 @@ class ActionGroup( 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 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) } @@ -94,7 +98,8 @@ class ActionGroup( conditions = condition, register = register, onFail = onFail, - loop = loop + 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 index bc8e7244..ba911882 100644 --- 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 @@ -8,8 +8,8 @@ class ActionGroupContext() { this.entity = entity } - var entity: GearyEntity - get() = environment["entity"] as GearyEntity + var entity: GearyEntity? + get() = environment["entity"] as? GearyEntity set(value) { environment["entity"] = value } @@ -28,5 +28,11 @@ class ActionGroupContext() { 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/actions/BecomeAction.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/actions/BecomeAction.kt index f4a125ca..5bdc0f7d 100644 --- 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 @@ -12,6 +12,8 @@ import kotlinx.serialization.Serializable class BecomeAction( val become: EntityExpression, ) : Action { + override val useSubcontext: Boolean = false + override fun ActionGroupContext.execute() { entity = become.evaluate(this) } 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 index 6c45e45c..8ccd52cf 100644 --- 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 @@ -10,7 +10,7 @@ class EmitEventAction( val data: Any?, ) : Action { override fun ActionGroupContext.execute() { - entity.emit(event = eventId, data = data) + entity?.emit(event = eventId, data = data) } companion object { 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 index cec7525f..95a2f895 100644 --- 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 @@ -24,7 +24,7 @@ class EnsureAction( } } - else -> entity.emit(id, data) //TODO use geary condition system if we get one + else -> entity?.emit(id, data) //TODO use geary condition system if we get one } } } 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 index f6a1e732..ca5224a5 100644 --- 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 @@ -2,11 +2,15 @@ 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) @@ -58,3 +62,13 @@ class ActionOnFail(val action: ActionGroup) { @JvmInline @Serializable value class ActionLoop(val expression: String) + +@Serializable +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/expressions/EntityExpression.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/expressions/EntityExpression.kt index 70d1b287..4a269d1c 100644 --- 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 @@ -12,7 +12,7 @@ value class EntityExpression( val expression: String, ) : Expression { override fun evaluate(context: ActionGroupContext): GearyEntity { - return if (expression == "parent") context.entity.parent!! + return if (expression == "parent") context.entity?.parent!! else Expression.Variable(expression).evaluate(context) } } From 9cf62e419108baaad0d58a42271c470d98ca636c Mon Sep 17 00:00:00 2001 From: Danielle Voznyy Date: Fri, 23 Aug 2024 15:53:07 -0400 Subject: [PATCH 15/15] fix: Use correct serializer on ActionEnvironment --- .../mineinabyss/geary/actions/event_binds/EntityObservers.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index ca5224a5..e92a9d4c 100644 --- 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 @@ -63,7 +63,7 @@ class ActionOnFail(val action: ActionGroup) { @Serializable value class ActionLoop(val expression: String) -@Serializable +@Serializable(with = ActionEnvironment.Serializer::class) class ActionEnvironment(val environment: Map>) { object Serializer : InnerSerializer>, ActionEnvironment>( serialName = "geary:with",