From 989d435588a763cc6cc2edaa0c939338055bd297 Mon Sep 17 00:00:00 2001 From: Danielle Voznyy Date: Mon, 29 Apr 2024 23:49:34 -0400 Subject: [PATCH] fix(observers): Entity scoped observers not restricting by event type, not inheriting observers to instances when created before tracking observer tests(observers): Add entity scoped observer tests --- .../geary/observers/ArchetypeEventRunner.kt | 15 ++-- .../geary/observers/EventToObserversMap.kt | 17 +++++ .../geary/observers/entity/EntityObserver.kt | 6 +- .../entity_scoped/EntityScopedObserverTest.kt | 69 +++++++++++++++++++ 4 files changed, 95 insertions(+), 12 deletions(-) create mode 100644 geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/EventToObserversMap.kt create mode 100644 geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/observers/entity_scoped/EntityScopedObserverTest.kt diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/ArchetypeEventRunner.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/ArchetypeEventRunner.kt index 7c574d25..b17ce19d 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/ArchetypeEventRunner.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/ArchetypeEventRunner.kt @@ -1,6 +1,5 @@ package com.mineinabyss.geary.observers -import androidx.collection.LongSparseArray import com.mineinabyss.geary.annotations.optin.UnsafeAccessors import com.mineinabyss.geary.datatypes.* import com.mineinabyss.geary.engine.archetypes.Archetype @@ -11,15 +10,11 @@ import com.mineinabyss.geary.modules.archetypes import com.mineinabyss.geary.modules.geary class ArchetypeEventRunner : EventRunner { - private val eventToObserverMap = LongSparseArray() - + private val eventToObserversMap = EventToObserversMap() override fun addObserver(observer: Observer) { - observer.listenToEvents.forEach { event -> - eventToObserverMap.getOrPut(event.toLong()) { ObserverList() }.add(observer) - } + eventToObserversMap.addObserver(observer) } - private inline fun matchObservers( eventType: ComponentId, involvedComponent: ComponentId, @@ -32,12 +27,12 @@ class ArchetypeEventRunner : EventRunner { // Run entity observers records.runOn(entity) { archetype, _ -> archetype.getRelationsByKind(observerComp) }.forEach { relation -> - val observerList = Relation.of(relation).target.toGeary().get() ?: return@forEach - observerList.forEach(involved, entity, exec) + val observerList = Relation.of(relation).target.toGeary().get() ?: return@forEach + observerList[eventType]?.forEach(involved, entity, exec) } // Run global observers - eventToObserverMap[eventType.toLong()]?.forEach(involved, entity, exec) + eventToObserversMap[eventType]?.forEach(involved, entity, exec) } override fun callEvent( diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/EventToObserversMap.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/EventToObserversMap.kt new file mode 100644 index 00000000..0f6d4459 --- /dev/null +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/EventToObserversMap.kt @@ -0,0 +1,17 @@ +package com.mineinabyss.geary.observers + +import androidx.collection.LongSparseArray +import com.mineinabyss.geary.datatypes.ComponentId +import com.mineinabyss.geary.datatypes.getOrPut + +class EventToObserversMap { + private val eventToObserverMap = LongSparseArray() + + fun addObserver(observer: Observer) { + observer.listenToEvents.forEach { event -> + eventToObserverMap.getOrPut(event.toLong()) { ObserverList() }.add(observer) + } + } + + operator fun get(event: ComponentId): ObserverList? = eventToObserverMap[event.toLong()] +} diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/entity/EntityObserver.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/entity/EntityObserver.kt index f05b9137..6d0f139a 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/entity/EntityObserver.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/entity/EntityObserver.kt @@ -6,8 +6,8 @@ import com.mineinabyss.geary.datatypes.GearyEntity import com.mineinabyss.geary.helpers.componentId import com.mineinabyss.geary.helpers.entity import com.mineinabyss.geary.modules.geary +import com.mineinabyss.geary.observers.EventToObserversMap import com.mineinabyss.geary.observers.Observer -import com.mineinabyss.geary.observers.ObserverList import com.mineinabyss.geary.observers.builders.* inline fun GearyEntity.observe(): ObserverEventsBuilder { @@ -20,9 +20,11 @@ inline fun GearyEntity.observeWithData(): ObserverEventsBuilde fun GearyEntity.attachObserver(observer: Observer) { val observerEntity = entity { - set(ObserverList().apply { add(observer) }) + set(EventToObserversMap().apply { addObserver(observer) }) addRelation(this@attachObserver) // Remove entity when original is removed } + //TODO remove when prefabs auto propagate component adds down + instances.forEach { it.addRelation(observerEntity) } addRelation(observerEntity) } diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/observers/entity_scoped/EntityScopedObserverTest.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/observers/entity_scoped/EntityScopedObserverTest.kt new file mode 100644 index 00000000..ec5b73b7 --- /dev/null +++ b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/observers/entity_scoped/EntityScopedObserverTest.kt @@ -0,0 +1,69 @@ +package com.mineinabyss.geary.observers.entity_scoped + +import com.mineinabyss.geary.helpers.entity +import com.mineinabyss.geary.helpers.tests.GearyTest +import com.mineinabyss.geary.modules.geary +import com.mineinabyss.geary.observers.entity.observe +import com.mineinabyss.geary.systems.builders.observe +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test + +class EntityScopedObserverTest : GearyTest() { + private sealed class Clicked + + @Test + fun `should only observe scoped events on correct entity`() { + // arrange + val scopedObserve = entity() + val globallyObserve = entity() + val lines = mutableListOf() + scopedObserve.observe().exec { lines += "Scoped clicked $entity" } + geary.observe().exec { lines += "Global clicked $entity" } + + // act + scopedObserve.emit() + globallyObserve.emit() + + // assert + lines shouldBe """ + Scoped clicked $scopedObserve + Global clicked $scopedObserve + Global clicked $globallyObserve + """.trimIndent().lines() + } + + @Test + fun `should propagate scoped observers to instances when instance created after tracking observer`() { + // arrange + val prefab = entity() + val lines = mutableListOf() + prefab.observe().exec { lines += "Scoped clicked $entity" } + val instance = entity { extend(prefab) } + + // act + instance.emit() + + // assert + lines shouldBe """ + Scoped clicked $instance + """.trimIndent().lines() + + } + + @Test + fun `should propagate scoped observers to instances when instance created before tracking observer`() { + // arrange + val prefab = entity() + val instance = entity { extend(prefab) } + val lines = mutableListOf() + prefab.observe().exec { lines += "Scoped clicked $entity" } + + // act + instance.emit() + + // assert + lines shouldBe """ + Scoped clicked $instance + """.trimIndent().lines() + } +}