Skip to content

Commit

Permalink
fix(observers): Entity scoped observers not restricting by event type…
Browse files Browse the repository at this point in the history
…, not inheriting observers to instances when created before tracking observer

tests(observers): Add entity scoped observer tests
  • Loading branch information
0ffz committed Apr 30, 2024
1 parent 16aceba commit 989d435
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -11,15 +10,11 @@ import com.mineinabyss.geary.modules.archetypes
import com.mineinabyss.geary.modules.geary

class ArchetypeEventRunner : EventRunner {
private val eventToObserverMap = LongSparseArray<ObserverList>()

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,
Expand All @@ -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<ObserverList>() ?: return@forEach
observerList.forEach(involved, entity, exec)
val observerList = Relation.of(relation).target.toGeary().get<EventToObserversMap>() ?: 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(
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ObserverList>()

fun addObserver(observer: Observer) {
observer.listenToEvents.forEach { event ->
eventToObserverMap.getOrPut(event.toLong()) { ObserverList() }.add(observer)
}
}

operator fun get(event: ComponentId): ObserverList? = eventToObserverMap[event.toLong()]
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 <reified T : Any> GearyEntity.observe(): ObserverEventsBuilder<ObserverContext> {
Expand All @@ -20,9 +20,11 @@ inline fun <reified T : Any> GearyEntity.observeWithData(): ObserverEventsBuilde

fun GearyEntity.attachObserver(observer: Observer) {
val observerEntity = entity {
set(ObserverList().apply { add(observer) })
set(EventToObserversMap().apply { addObserver(observer) })
addRelation<ChildOf>(this@attachObserver) // Remove entity when original is removed
}
//TODO remove when prefabs auto propagate component adds down
instances.forEach { it.addRelation<Observer>(observerEntity) }
addRelation<Observer>(observerEntity)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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<String>()
scopedObserve.observe<Clicked>().exec { lines += "Scoped clicked $entity" }
geary.observe<Clicked>().exec { lines += "Global clicked $entity" }

// act
scopedObserve.emit<Clicked>()
globallyObserve.emit<Clicked>()

// 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<String>()
prefab.observe<Clicked>().exec { lines += "Scoped clicked $entity" }
val instance = entity { extend(prefab) }

// act
instance.emit<Clicked>()

// 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<String>()
prefab.observe<Clicked>().exec { lines += "Scoped clicked $entity" }

// act
instance.emit<Clicked>()

// assert
lines shouldBe """
Scoped clicked $instance
""".trimIndent().lines()
}
}

0 comments on commit 989d435

Please sign in to comment.