From 6ad9a51fd8ac27a31c0e0f1ff48829ffdd591f1e Mon Sep 17 00:00:00 2001 From: Danielle Voznyy Date: Thu, 1 Feb 2024 12:06:02 -0500 Subject: [PATCH] Feat: Listen to entity extensions Feat: reimplement CopyToInstances --- .../mineinabyss/geary/prefabs/PrefabLoader.kt | 2 + .../com/mineinabyss/geary/prefabs/Prefabs.kt | 6 +- .../components/CopyToInstances.kt | 33 ++++++++--- .../systems/CopyToInstancesSystem.kt | 16 ++++++ .../geary/prefabs/CopyToInstancesTest.kt | 55 +++++++++++++++++++ .../PolymorphicListAsMapSerializer.kt | 6 +- .../geary/components/events/AddedComponent.kt | 2 + .../geary/datatypes/family/MutableFamily.kt | 6 +- .../mineinabyss/geary/engine/Components.kt | 1 + .../geary/engine/archetypes/Archetype.kt | 6 +- .../geary/modules/TestEngineModule.kt | 3 + .../com/mineinabyss/geary/systems/Listener.kt | 17 ++++-- .../geary/helpers/tests/GearyTest.kt | 16 +++--- 13 files changed, 141 insertions(+), 28 deletions(-) create mode 100644 addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/CopyToInstancesSystem.kt create mode 100644 addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/CopyToInstancesTest.kt 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 9e6a994dc..f622f5bc2 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 @@ -7,6 +7,7 @@ import com.mineinabyss.geary.datatypes.Entity import com.mineinabyss.geary.datatypes.GearyComponent import com.mineinabyss.geary.helpers.entity import com.mineinabyss.geary.modules.geary +import com.mineinabyss.geary.prefabs.configuration.components.CopyToInstances import com.mineinabyss.geary.prefabs.configuration.components.Prefab import com.mineinabyss.geary.prefabs.helpers.inheritPrefabs import com.mineinabyss.geary.serialization.dsl.serializableComponents @@ -67,6 +68,7 @@ class PrefabLoader { val entity = writeTo ?: entity() entity.addRelation() entity.addRelation() + entity.addRelation() entity.set(Prefab(path)) decoded.getOrNull()?.let { entity.setAll(it) } entity.set(key) 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 158972837..2e1c0c5ae 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 @@ -5,10 +5,7 @@ import com.mineinabyss.geary.addons.Namespaced import com.mineinabyss.geary.addons.dsl.GearyAddonWithDefault import com.mineinabyss.geary.addons.dsl.GearyDSL import com.mineinabyss.geary.modules.geary -import com.mineinabyss.geary.prefabs.configuration.systems.ParseChildOnPrefab -import com.mineinabyss.geary.prefabs.configuration.systems.ParseChildrenOnPrefab -import com.mineinabyss.geary.prefabs.configuration.systems.ParseRelationOnPrefab -import com.mineinabyss.geary.prefabs.configuration.systems.ParseRelationWithDataSystem +import com.mineinabyss.geary.prefabs.configuration.systems.* import com.mineinabyss.geary.prefabs.systems.TrackPrefabsByKeySystem import com.mineinabyss.idofront.di.DI @@ -32,6 +29,7 @@ interface Prefabs { ParseRelationOnPrefab(), ParseRelationWithDataSystem(), TrackPrefabsByKeySystem(), + CopyToInstancesSystem(), ) geary.pipeline.runOnOrAfter(GearyPhase.INIT_ENTITIES) { loader.loadOrUpdatePrefabs() diff --git a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/components/CopyToInstances.kt b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/components/CopyToInstances.kt index 446d5b2ee..a3fdaf181 100644 --- a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/components/CopyToInstances.kt +++ b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/components/CopyToInstances.kt @@ -3,6 +3,7 @@ package com.mineinabyss.geary.prefabs.configuration.components import com.mineinabyss.geary.datatypes.Component import com.mineinabyss.geary.datatypes.Entity import com.mineinabyss.geary.serialization.dsl.serializableComponents +import com.mineinabyss.geary.serialization.serializers.SerializedComponents import kotlinx.serialization.Polymorphic import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -17,20 +18,38 @@ import kotlinx.serialization.Serializable @Serializable @SerialName("geary:copy_to_instances") data class CopyToInstances( - private val temporary: Set<@Polymorphic Component> = setOf(), - private val persisting: Set<@Polymorphic Component> = setOf(), + private val temporary: SerializedComponents? = null, + private val persisting: SerializedComponents? = null, ) { + @Serializable + private data class DeepCopy( + val temporary: List<@Polymorphic Component>?, + val persisting: List<@Polymorphic Component>? + ) + val formats get() = serializableComponents.formats // This is the safest and cleanest way to deep-copy, even if a little performance intense. - private val serializedComponents by lazy { formats.binaryFormat.encodeToByteArray(serializer(), this) } + private val serializedComponents by lazy { + formats.binaryFormat.encodeToByteArray( + DeepCopy.serializer(), + DeepCopy(temporary, persisting) + ) + } - private fun getDeepCopied() = formats.binaryFormat.decodeFromByteArray(serializer(), serializedComponents) + private fun getDeepCopied() = formats.binaryFormat.decodeFromByteArray( + DeepCopy.serializer(), serializedComponents + ) - fun decodeComponentsTo(entity: Entity, override: Boolean = true) { + fun decodeComponentsTo(entity: Entity) { val (instance, persist) = getDeepCopied() //order of addition specifies that persisting components should override all - entity.setAll(instance, override) - entity.setAllPersisting(persist, override) + if (instance != null) { + entity.setAll(instance, override = false) + } + if (persist != null) { + entity.setAllPersisting(persist, override = false) + } } } + diff --git a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/CopyToInstancesSystem.kt b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/CopyToInstancesSystem.kt new file mode 100644 index 000000000..e9737014e --- /dev/null +++ b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/CopyToInstancesSystem.kt @@ -0,0 +1,16 @@ +package com.mineinabyss.geary.prefabs.configuration.systems + +import com.mineinabyss.geary.annotations.optin.UnsafeAccessors +import com.mineinabyss.geary.prefabs.configuration.components.CopyToInstances +import com.mineinabyss.geary.systems.GearyListener +import com.mineinabyss.geary.systems.accessors.Pointers + +class CopyToInstancesSystem : GearyListener() { + private val Pointers.baseEntity by whenExtendedEntity() + + @OptIn(UnsafeAccessors::class) + override fun Pointers.handle() { + val copy = baseEntity.get() ?: return + copy.decodeComponentsTo(target.entity) + } +} diff --git a/addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/CopyToInstancesTest.kt b/addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/CopyToInstancesTest.kt new file mode 100644 index 000000000..956745ce8 --- /dev/null +++ b/addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/CopyToInstancesTest.kt @@ -0,0 +1,55 @@ +package com.mineinabyss.geary.prefabs + +import com.mineinabyss.geary.components.relations.NoInherit +import com.mineinabyss.geary.helpers.entity +import com.mineinabyss.geary.modules.TestEngineModule +import com.mineinabyss.geary.modules.geary +import com.mineinabyss.geary.prefabs.configuration.components.CopyToInstances +import com.mineinabyss.geary.serialization.dsl.serialization +import com.mineinabyss.idofront.di.DI +import io.kotest.matchers.shouldBe +import kotlinx.serialization.builtins.serializer +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +class CopyToInstancesTest { + private val testKey = PrefabKey.of("test:1") + + @BeforeEach + fun createEngine() { + DI.clear() + geary(TestEngineModule) { + install(Prefabs) + + serialization { + components { + component(String.serializer()) + component(Int.serializer()) + } + } + } + geary.pipeline.runStartupTasks() + } + + @Test + fun `should correctly add temporary and persisting components with CopyToInstances`() { + // arrange + val prefab = entity { + set( + CopyToInstances( + temporary = listOf(42), + persisting = listOf("Hello world") + ) + ) + addRelation() + } + + // act + val instance = entity { extend(prefab) } + + // assert + instance.get() shouldBe "Hello world" + instance.get() shouldBe 42 + instance.getAllPersisting() shouldBe listOf("Hello world") + } +} 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 bec4eb1a3..62de00b0f 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 @@ -4,9 +4,7 @@ import com.mineinabyss.geary.datatypes.GearyComponent import com.mineinabyss.geary.serialization.ComponentSerializers.Companion.fromCamelCaseToSnakeCase import com.mineinabyss.geary.serialization.ComponentSerializers.Companion.hasNamespace import com.mineinabyss.geary.serialization.ProvidedNamespaces -import kotlinx.serialization.ContextualSerializer -import kotlinx.serialization.KSerializer -import kotlinx.serialization.PolymorphicSerializer +import kotlinx.serialization.* import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.builtins.MapSerializer import kotlinx.serialization.builtins.serializer @@ -15,6 +13,8 @@ import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.modules.SerializersModule +typealias SerializedComponents = @Serializable(with = PolymorphicListAsMapSerializer::class) List<@Polymorphic GearyComponent> + open class PolymorphicListAsMapSerializer( serializer: KSerializer, ) : KSerializer> { diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/components/events/AddedComponent.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/components/events/AddedComponent.kt index 00f7daa0d..bad3c27fb 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/components/events/AddedComponent.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/components/events/AddedComponent.kt @@ -14,3 +14,5 @@ sealed class AddedComponent sealed class SetComponent sealed class UpdatedComponent + +sealed class ExtendedEntity diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/family/MutableFamily.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/family/MutableFamily.kt index 7a91666f8..1ff3624d8 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/family/MutableFamily.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/family/MutableFamily.kt @@ -3,11 +3,11 @@ package com.mineinabyss.geary.datatypes.family import com.mineinabyss.geary.components.events.AddedComponent import com.mineinabyss.geary.components.events.SetComponent import com.mineinabyss.geary.components.events.UpdatedComponent -import com.mineinabyss.geary.modules.geary import com.mineinabyss.geary.datatypes.* import com.mineinabyss.geary.engine.archetypes.Archetype import com.mineinabyss.geary.helpers.componentId import com.mineinabyss.geary.helpers.componentIdWithNullable +import com.mineinabyss.geary.modules.geary inline fun family(init: MutableFamily.Selector.And.() -> Unit): Family { return MutableFamily.Selector.And().apply(init) @@ -129,6 +129,10 @@ sealed class MutableFamily : Family { onAdd.hasRelation(id) } + fun onExtendedEntity() { + onAdd.hasRelation(geary.components.extendedEntity, geary.components.any) + } + inline fun or(init: Or.() -> Unit) { add(Or().apply(init)) } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/Components.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/Components.kt index 12d5131b7..32e00c38d 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/Components.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/Components.kt @@ -17,6 +17,7 @@ class Components { val addedComponent = componentId() val setComponent = componentId() val updatedComponent = componentId() + val extendedEntity = componentId() val entityRemoved = componentId() val childOf = componentId() val instanceOf = componentId() diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/Archetype.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/Archetype.kt index 5e65d9a84..4663986e8 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/Archetype.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/Archetype.kt @@ -293,12 +293,13 @@ class Archetype internal constructor( fun instantiateTo( base: Record, instance: Record, + callEvent: Boolean = true, ) { instance.archetype.addComponent(instance, Relation.of(base.entity).id, true) - type.filter { !it.holdsData() }.forEach { + val noInheritComponents = EntityType(getRelationsByKind(componentId()).map { it.target }) + type.filter { !it.holdsData() && it !in noInheritComponents }.forEach { instance.archetype.addComponent(instance, it, true) } - val noInheritComponents = EntityType(getRelationsByKind(componentId()).map { it.target }) dataHoldingType.forEach { if (it.withoutRole(HOLDS_DATA) in noInheritComponents) return@forEach instance.archetype.setComponent(instance, it, get(base.row, it)!!, true) @@ -306,6 +307,7 @@ class Archetype internal constructor( base.entity.children.forEach { it.addParent(instance.entity) } + if (callEvent) callComponentModifyEvent(geary.components.extendedEntity, instance, base.entity.id) } /** diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/TestEngineModule.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/TestEngineModule.kt index e99fc956d..b2542c786 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/TestEngineModule.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/TestEngineModule.kt @@ -1,5 +1,6 @@ package com.mineinabyss.geary.modules +import com.mineinabyss.geary.datatypes.maps.SynchronizedTypeMap import com.mineinabyss.geary.engine.archetypes.EntityByArchetypeProvider /** @@ -10,8 +11,10 @@ import com.mineinabyss.geary.engine.archetypes.EntityByArchetypeProvider */ class TestEngineModule( reuseIDsAfterRemoval: Boolean = true, + useSynchronized: Boolean = false, ) : ArchetypeEngineModule() { override val entityProvider = EntityByArchetypeProvider(reuseIDsAfterRemoval) + override val records = if (useSynchronized) SynchronizedTypeMap(super.records) else super.records companion object : GearyModuleProviderWithDefault { override fun init(module: TestEngineModule) { diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/Listener.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/Listener.kt index a516c752a..8b6b5bdea 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/Listener.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/Listener.kt @@ -1,6 +1,9 @@ package com.mineinabyss.geary.systems +import com.mineinabyss.geary.components.events.ExtendedEntity +import com.mineinabyss.geary.datatypes.GearyEntity import com.mineinabyss.geary.helpers.componentId +import com.mineinabyss.geary.helpers.toGeary import com.mineinabyss.geary.systems.accessors.* import com.mineinabyss.geary.systems.accessors.type.ComponentAccessor @@ -21,7 +24,7 @@ abstract class Listener : AccessorOperations(), System { onStart() } - private fun getIndexForHolder(holder: AccessorHolder): Int= when(holder) { + private fun getIndexForHolder(holder: AccessorHolder): Int = when (holder) { target -> 0 event -> 1 source -> 2 @@ -41,23 +44,29 @@ abstract class Listener : AccessorOperations(), System { } /** Fires when an entity has a component of type [T] set or updated. */ - inline fun , reified A> T.whenSetOnTarget(): ReadWriteEntitySelectingAccessor { + inline fun , reified A> T.whenSetOnTarget(): ReadWriteEntitySelectingAccessor { event.mutableFamily.onSet(componentId()) return this.on(target) } /** Fires when an entity has a component of type [T] set, only if it was not set before. */ - inline fun , reified A> T.whenFirstSetOnTarget(): ReadWriteEntitySelectingAccessor { + inline fun , reified A> T.whenFirstSetOnTarget(): ReadWriteEntitySelectingAccessor { event.mutableFamily.onFirstSet(componentId()) return this.on(target) } /** Fires when an entity has a component of type [T] added, updates are not considered since no data changes. */ - inline fun , reified A> T.whenAddedOnTarget(): ReadWriteEntitySelectingAccessor { + inline fun , reified A> T.whenAddedOnTarget(): ReadWriteEntitySelectingAccessor { event.mutableFamily.onAdd(componentId()) return this.on(event) } + /** Fires when an entity has a component of type [T] added, updates are not considered since no data changes. */ + fun whenExtendedEntity(): ReadOnlyEntitySelectingAccessor, GearyEntity> { + event.mutableFamily.onExtendedEntity() + return getRelations().map { it.single().target.toGeary() }.on(event) + } + abstract fun Pointers.handle() } diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/helpers/tests/GearyTest.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/helpers/tests/GearyTest.kt index c6ae691f1..8cc63eb7f 100644 --- a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/helpers/tests/GearyTest.kt +++ b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/helpers/tests/GearyTest.kt @@ -29,13 +29,15 @@ abstract class GearyTest { startEngine() } - suspend inline fun concurrentOperation( - times: Int = 10000, - crossinline run: suspend (id: Int) -> Unit - ): List> { - return withContext(Dispatchers.Default) { - (0 until times).map { id -> - async { run(id) } + companion object { + suspend inline fun concurrentOperation( + times: Int = 10000, + crossinline run: suspend (id: Int) -> T + ): List> { + return withContext(Dispatchers.Default) { + (0 until times).map { id -> + async { run(id) } + } } } }