diff --git a/geary-benchmarks/build.gradle.kts b/geary-benchmarks/build.gradle.kts index b264a8cae..cda135b93 100644 --- a/geary-benchmarks/build.gradle.kts +++ b/geary-benchmarks/build.gradle.kts @@ -45,7 +45,9 @@ benchmark { } create("specific") { - include("Unpack6Benchmark") +// include("Unpack6Benchmark") + include("NewEntityBenchmark") + include("ManyComponentsBenchmark") warmups = 1 iterations = 1 iterationTime = 3 diff --git a/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/instantiation/ManyComponentsBenchmark.kt b/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/instantiation/ManyComponentsBenchmark.kt index 95ed9bae4..e2cb84c9d 100644 --- a/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/instantiation/ManyComponentsBenchmark.kt +++ b/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/instantiation/ManyComponentsBenchmark.kt @@ -27,7 +27,7 @@ class ManyComponentsBenchmark { } @Benchmark - fun create1MilEntitiesWithUniqueComponentEach() { + fun createTenThousandEntitiesWithUniqueComponentEach() { repeat(10000) { entity { addRelation(it.toLong().toGeary()) @@ -38,5 +38,5 @@ class ManyComponentsBenchmark { fun main() { geary(TestEngineModule) - ManyComponentsBenchmark().create1MilEntitiesWithUniqueComponentEach() + ManyComponentsBenchmark().createTenThousandEntitiesWithUniqueComponentEach() } diff --git a/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/instantiation/NewEntityBenchmark.kt b/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/instantiation/NewEntityBenchmark.kt index 7f3d3e104..01f4e0bbf 100644 --- a/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/instantiation/NewEntityBenchmark.kt +++ b/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/instantiation/NewEntityBenchmark.kt @@ -91,6 +91,6 @@ class NewEntityBenchmark { fun main() { geary(TestEngineModule) repeat(100) { - NewEntityBenchmark().create1MilEntitiesWith6Components() + NewEntityBenchmark().create1MilEntitiesWith6ComponentsWithoutComponentIdCalls() } } diff --git a/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/unpacking/Unpack6Benchmark.kt b/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/unpacking/Unpack6Benchmark.kt index b1d889ece..85cc8d594 100644 --- a/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/unpacking/Unpack6Benchmark.kt +++ b/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/unpacking/Unpack6Benchmark.kt @@ -79,8 +79,8 @@ fun main() { Unpack6Benchmark().apply { setUp() repeat(10000) { - unpack1of6CompNoDelegate() -// unpack6of6CompNoDelegate() +// unpack1of6CompNoDelegate() + unpack6of6CompNoDelegate() } } } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/BucketedULongArray.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/BucketedULongArray.kt new file mode 100644 index 000000000..982c462bd --- /dev/null +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/BucketedULongArray.kt @@ -0,0 +1,53 @@ +package com.mineinabyss.geary.datatypes + +private const val bucketSize: Int = 1024 + +class BucketedULongArray() { + private val buckets = mutableListOf() + var maxSupportedSize = 0 + private set + var size = 0 + private set + + val lastIndex get() = size - 1 + + operator fun get(index: Int): ULong { + val bucketIndex = index / bucketSize + val bucket = buckets[bucketIndex] + return bucket[index % bucketSize].toULong() + } + + fun ensureSize(including: Int) { + var maxSupportedSize = maxSupportedSize + while (including >= maxSupportedSize) { + buckets.add(LongArray(bucketSize)) + maxSupportedSize += bucketSize + } + this.maxSupportedSize = maxSupportedSize + } + + operator fun set(index: Int, value: ULong) { + val bucketIndex = index / bucketSize + ensureSize(index) + val bucket = buckets[bucketIndex] + if (index >= size) size = index + 1 + bucket[index % bucketSize] = value.toLong() + } + + fun add(value: ULong) { + val index = size + set(index, value) + } + + fun getAll(): ULongArray { + return ULongArray(size) { get(it) } + } + + fun removeLastOrNull(): ULong? { + if (size == 0) return null + return get(lastIndex).also { + size-- + if (size % bucketSize == 0) buckets.removeLast() + } + } +} diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/Entity.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/Entity.kt index 52ba4f12e..752cf0a60 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/Entity.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/Entity.kt @@ -146,7 +146,7 @@ value class Entity(val id: EntityId) { * @return Whether the component was present before removal. */ inline fun remove(): Boolean = - remove(componentId()) || remove(componentId() and ENTITY_MASK) + remove(componentId()) /** Removes a component whose class is [kClass] from this entity. */ fun remove(kClass: KClass<*>): Boolean = diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/GearyAliases.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/GearyAliases.kt index 1adb38e3c..b91cabc0d 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/GearyAliases.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/GearyAliases.kt @@ -3,7 +3,6 @@ package com.mineinabyss.geary.datatypes import kotlinx.serialization.Polymorphic typealias GearyEntityType = EntityType -typealias GearyRecord = Record typealias GearyRelation = Relation /** diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/IdList.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/IdList.kt index bd395dd6e..76344d0a7 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/IdList.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/IdList.kt @@ -17,11 +17,15 @@ class IdList { fun add(value: ULong) { if (size == backingArr.size) { - backingArr = backingArr.copyOf(size * growFactor) + grow() } backingArr[size++] = value } + fun grow(){ + backingArr = backingArr.copyOf(size * growFactor) + } + fun removeLastOrNull(): ULong? { if (size == 0) return null return backingArr[--size] @@ -31,7 +35,21 @@ class IdList { return backingArr[--size] } + internal fun removeAt(index: Int) { + if (index == -1) return + // replace with last + backingArr[index] = backingArr[--size] + } + fun getEntities(): Sequence { return backingArr.asSequence().take(size).map { it.toGeary() } } + + fun indexOf(value: ULong): Int { + var n = 0 + val size = size + while (n < size) + if (backingArr[n++] == value) return n - 1 + return -1 + } } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/Record.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/Record.kt deleted file mode 100644 index 40661d9f9..000000000 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/Record.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.mineinabyss.geary.datatypes - -import com.mineinabyss.geary.engine.archetypes.Archetype -import kotlinx.atomicfu.locks.SynchronizedObject - - -class Record @PublishedApi internal constructor( - archetype: Archetype, - row: Int -) : SynchronizedObject() { - var archetype: Archetype - internal set - var row: Int - internal set - - init { - this.archetype = archetype - this.row = row - } - - val entity: Entity get() = archetype.getEntity(row) - - operator fun component1(): Archetype = archetype - operator fun component2(): Int = row -} diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/TypeRoles.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/TypeRoles.kt index 0d4b85285..d8280c204 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/TypeRoles.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/TypeRoles.kt @@ -4,9 +4,9 @@ package com.mineinabyss.geary.datatypes //can't make const because of the shl -val NO_ROLE: ULong = 0uL -val RELATION: ULong = 1uL shl 63 -val HOLDS_DATA: ULong = 1uL shl 62 +const val NO_ROLE: ULong = 0uL +const val RELATION: ULong = 0x8000000000000000uL // 1 shl 63 +const val HOLDS_DATA: ULong = 0x4000000000000000uL // 1 shl 62 //4 //5 //5 diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/ArrayTypeMap.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/ArrayTypeMap.kt index 5590494de..bdf6369fc 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/ArrayTypeMap.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/ArrayTypeMap.kt @@ -1,33 +1,62 @@ package com.mineinabyss.geary.datatypes.maps +import com.mineinabyss.geary.datatypes.BucketedULongArray import com.mineinabyss.geary.datatypes.Entity -import com.mineinabyss.geary.datatypes.Record +import com.mineinabyss.geary.engine.archetypes.Archetype -class ArrayTypeMap : TypeMap { - private val map: ArrayList = arrayListOf() + +open class ArrayTypeMap : TypeMap { + @PublishedApi + internal val archList = arrayListOf() + + // private val map: ArrayList = arrayListOf() +// private var archIndexes = IntArray(10) +// private var rows = IntArray(10) + @PublishedApi + internal var archAndRow = BucketedULongArray() + var size = 0 // We don't return nullable record to avoid boxing. // Accessing an entity that doesn't exist is indicative of a problem elsewhere and should be made obvious. - override fun get(entity: Entity): Record = map[entity.id.toInt()] - ?: error("Tried to access components on an entity that no longer exists (${entity.id})") +// override fun get(entity: Entity): Record { +// val info = archAndRow[entity.id.toInt()] +// return Record( +// archList[(info shr 32).toInt()], +// info.toInt() +// ) +// } + open fun getArchAndRow(entity: Entity): ULong { + return archAndRow[entity.id.toInt()] + } - override fun set(entity: Entity, record: Record) { + override fun set(entity: Entity, archetype: Archetype, row: Int) { val id = entity.id.toInt() - if (map.size == id) { - map.add(record) - return - } - if (contains(entity)) error("Tried setting the record of an entity that already exists.") - while (map.size <= id) map.add(null) - map[id] = record + archAndRow[id] = (indexOrAdd(archetype).toULong() shl 32) or row.toULong() + } + + fun indexOrAdd(archetype: Archetype): Int { + if (archetype.indexInRecords != -1) return archetype.indexInRecords + val index = archList.indexOf(archetype) + archetype.indexInRecords = index + return if (index == -1) { + archList.add(archetype) + archList.lastIndex + } else index } override fun remove(entity: Entity) { - map[entity.id.toInt()] = null + val id = entity.id.toInt() + archAndRow[id] = 0UL } override operator fun contains(entity: Entity): Boolean { val id = entity.id.toInt() - return map.size > id && map[id] != null + return id < archAndRow.size && archAndRow[id] != 0uL + } + + + inline fun runOn(entity: Entity, run: (archetype: Archetype, row: Int) -> T): T { + val info = getArchAndRow(entity) + return run(archList[(info shr 32).toInt()], info.toInt()) } } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/CompId2ArchetypeMap.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/CompId2ArchetypeMap.kt index a1ee488e2..31e8b29a0 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/CompId2ArchetypeMap.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/CompId2ArchetypeMap.kt @@ -1,50 +1,66 @@ package com.mineinabyss.geary.datatypes.maps import com.mineinabyss.geary.datatypes.GearyComponentId +import com.mineinabyss.geary.datatypes.IdList import com.mineinabyss.geary.engine.archetypes.Archetype /** * Inlined class that acts as a map of components to archetypes. Uses archetype ids for better performance. */ -expect class CompId2ArchetypeMap() { - operator fun get(id: GearyComponentId): Archetype? - operator fun set(id: GearyComponentId, archetype: Archetype) - fun entries(): Set> - - fun clear() - - fun remove(id: GearyComponentId) - - operator fun contains(id: GearyComponentId): Boolean - - fun getOrSet(id: GearyComponentId, put: () -> Archetype): Archetype - - val size: Int -} - -class CompId2ArchetypeMapViaMutableMap { - val inner: MutableMap = mutableMapOf() - operator fun get(id: GearyComponentId): Archetype? = inner[id] +class CompId2ArchetypeMap { + // val inner = Long2ObjectArrayMap() + val ids = IdList() + val values = mutableListOf() + // actual operator fun get(id: GearyComponentId): Archetype? = +// values[entries.indexOf(id).also { if (it == -1) return null }] operator fun set(id: GearyComponentId, archetype: Archetype) { - inner[id] = archetype + val index = ids.indexOf(id) + if (index == -1) { + ids.add(id) + values.add(archetype) + } else { + values[index] = archetype + } } - fun entries(): Set> = inner.entries - fun remove(id: GearyComponentId) { - inner.remove(id) + val index = ids.indexOf(id) + if (index != -1) { + ids.removeAt(index) + values[index] = values[values.lastIndex] + values.removeLast() + } } fun clear() { - inner.clear() + ids.size = 0 + values.clear() + } + + inline fun forEach(action: (ULong, Archetype) -> Unit) { + for(i in 0 until ids.size) { + action(ids[i], values[i]) + } } - val size: Int get() = inner.size + val size: Int get() = ids.size + + operator fun contains(id: GearyComponentId): Boolean = ids.indexOf(id) != -1 - operator fun contains(id: GearyComponentId): Boolean = inner.containsKey(id) + inline fun getOrElse(id: GearyComponentId, defaultValue: () -> Archetype): Archetype { + val index = ids.indexOf(id) + return if (index == -1) defaultValue() else values[index] + } - fun getOrSet(id: GearyComponentId, put: () -> Archetype): Archetype { - return inner[id] ?: put().also { inner[id] = it } + inline fun getOrSet(id: GearyComponentId, put: () -> Archetype): Archetype { + val index = ids.indexOf(id) + if (index == -1) { + val arc = put() + ids.add(id) + values.add(arc) + return arc + } + return values[index] } } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/SynchronizedTypeMap.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/SynchronizedTypeMap.kt index 2012d53b0..ea8c18c97 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/SynchronizedTypeMap.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/SynchronizedTypeMap.kt @@ -1,15 +1,20 @@ package com.mineinabyss.geary.datatypes.maps import com.mineinabyss.geary.datatypes.Entity -import com.mineinabyss.geary.datatypes.Record +import com.mineinabyss.geary.engine.archetypes.Archetype import kotlinx.atomicfu.locks.SynchronizedObject import kotlinx.atomicfu.locks.synchronized -class SynchronizedTypeMap(private val map: TypeMap) : TypeMap { +class SynchronizedArrayTypeMap() : ArrayTypeMap() { private val lock = SynchronizedObject() - override fun get(entity: Entity): Record = synchronized(lock) { map[entity] } - override fun set(entity: Entity, record: Record) = synchronized(lock) { map[entity] = record } - override fun remove(entity: Entity) = synchronized(lock) { map.remove(entity) } - override fun contains(entity: Entity): Boolean = synchronized(lock) { map.contains(entity) } + override fun getArchAndRow(entity: Entity): ULong { + return synchronized(lock) { super.getArchAndRow(entity) } + } + override fun set(entity: Entity, archetype: Archetype, row: Int) { + synchronized(lock) { super.set(entity, archetype, row) } + } + + override fun remove(entity: Entity) = synchronized(lock) { super.remove(entity) } + override fun contains(entity: Entity): Boolean = synchronized(lock) { super.contains(entity) } } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/TypeMap.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/TypeMap.kt index f07cf5fd5..ae802116a 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/TypeMap.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/TypeMap.kt @@ -1,16 +1,16 @@ package com.mineinabyss.geary.datatypes.maps import com.mineinabyss.geary.datatypes.Entity -import com.mineinabyss.geary.datatypes.Record +import com.mineinabyss.geary.engine.archetypes.Archetype interface TypeMap { // We don't return nullable record to avoid boxing. // Accessing an entity that doesn't exist is indicative of a problem elsewhere and should be made obvious. /** Gets the record of a given entity, or throws an error if the entity id is not active in the engine. */ - operator fun get(entity: Entity): Record +// operator fun get(entity: Entity): Record /** Updates the record of a given entity */ - operator fun set(entity: Entity, record: Record) + operator fun set(entity: Entity, archetype: Archetype, row: Int) /** Removes a record associated with an entity. */ fun remove(entity: Entity) 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 2d9c5946f..55e066809 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 @@ -37,6 +37,7 @@ class Archetype internal constructor( // This is way slower as a Boolean? because of boxing private var allowUnregister: Byte = 0 + internal var indexInRecords = -1 /** Component ids in the type that are to hold data */ // Currently all relations must hold data and the HOLDS_DATA bit on them corresponds to the component part. @@ -100,18 +101,19 @@ class Archetype internal constructor( /** Returns the archetype associated with adding [componentId] to this archetype's [type]. */ operator fun plus(componentId: ComponentId): Archetype { - componentAddEdges[componentId]?.let { return it } - if (componentId.holdsData()) { - // Try to get via the component without the data role - componentAddEdges[componentId.withoutRole(HOLDS_DATA)] - ?.plus(componentId)?.let { return it } - if (componentId.withoutRole(HOLDS_DATA) !in type) - return this + componentId.withoutRole(HOLDS_DATA) + componentId + return componentAddEdges.getOrElse(componentId) { + val archetype = archetypeProvider.getArchetype(type + componentId) + updateComponentEdgesFor(componentId, archetype) + archetype } - val archetype = archetypeProvider.getArchetype(type + componentId) + } + + private fun updateComponentEdgesFor( + componentId: ComponentId, + archetype: Archetype, + ) { componentAddEdges[componentId] = archetype archetype.componentRemoveEdges[componentId] = this - return archetype } /** Returns the archetype associated with removing [componentId] to this archetype's [type]. */ @@ -130,12 +132,12 @@ class Archetype internal constructor( * @return The new [Record] to be associated with this entity from now on. */ private fun moveWithNewComponent( - record: Record, + oldArc: Archetype, + oldRow: Int, newComponent: Component, newComponentId: ComponentId, entity: EntityId, - ) = move(record, entity) { - val (oldArc, oldRow) = record + ) = move(entity) { val newCompIndex = indexOf(newComponentId) // Add before new comp @@ -154,21 +156,21 @@ class Archetype internal constructor( } private fun moveOnlyAdding( - record: Record, + oldArc: Archetype, + oldRow: Int, entity: EntityId - ) = move(record, entity) { - val (oldArc, oldRow) = record + ) = move(entity) { for (i in 0..componentData.lastIndex) { componentData[i].add(oldArc.componentData[i][oldRow]) } } private fun moveWithoutComponent( - record: Record, + oldArc: Archetype, + oldRow: Int, withoutComponentId: ComponentId, entity: EntityId, - ) = move(record, entity) { - val (oldArc, oldRow) = record + ) = move(entity) { val withoutCompIndex = oldArc.indexOf(withoutComponentId) // If removing a component that's added and not set, we just copy all data @@ -190,93 +192,102 @@ class Archetype internal constructor( } } - internal fun createWithoutData(entity: Entity, existingRecord: Record) { - move(existingRecord, entity.id) {} - } - internal fun createWithoutData(entity: Entity): Record { + internal fun createWithoutData(entity: Entity): Int { ids.add(entity.id) - return Record(this, ids.lastIndex) + return ids.lastIndex } internal inline fun move( - record: Record, entity: EntityId, copyData: () -> Unit - ) { + ): Int { if (unregistered) error("Tried adding entity to archetype that is no longer registered. Was it referenced outside of Geary?") ids.add(entity) + val row = ids.lastIndex copyData() - record.row = -1 - record.archetype = this - record.row = ids.lastIndex + records[Entity(entity), this] = row + return row } // For the following few functions, both entity and row are passed to avoid doing several array look-ups // (ex when set calls remove). + internal fun addComponent( + row: Int, + componentId: ComponentId, + callEvent: Boolean, + ) = addComponent(row, componentId, callEvent) { _, _ -> } + /** * Add a [componentId] to an entity represented by [record], moving it to the appropriate archetype. * - * @return Whether the record has changed. + * @return New archetype for entity */ - internal fun addComponent( - record: Record, + internal inline fun addComponent( + row: Int, componentId: ComponentId, callEvent: Boolean, - ): Boolean { + onUpdated: (Archetype, row: Int) -> Unit + ) { // if already present in this archetype, stop here since we don't need to update any data - if (contains(componentId)) return false + if (contains(componentId)) return val moveTo = this + (componentId.withoutRole(HOLDS_DATA)) - val row = record.row val entityId = ids[row] - moveTo.moveOnlyAdding(record, entityId) + val newRow = moveTo.moveOnlyAdding(this, row, entityId) removeEntity(row) - if (callEvent) callComponentModifyEvent(geary.components.addedComponent, record, componentId) + if (callEvent) moveTo.callComponentModifyEvent(geary.components.addedComponent, newRow, componentId) - return true + onUpdated(moveTo, newRow) } + internal fun setComponent( + row: Int, + componentId: ComponentId, + data: Component, + callEvent: Boolean, + ) = setComponent(row, componentId, data, callEvent) { _, _ -> } + /** * Sets [data] at a [componentId] for an [record], moving it to the appropriate archetype. * Will ensure this component without [HOLDS_DATA] is always present. * * @return Whether the record has changed. */ - internal fun setComponent( - record: Record, + internal inline fun setComponent( + row: Int, componentId: ComponentId, data: Component, callEvent: Boolean, - ): Boolean { - val row = record.row + onUpdated: (Archetype, row: Int) -> Unit + ) { val dataComponent = componentId.withRole(HOLDS_DATA) //If component already in this type, just update the data val addIndex = indexOf(dataComponent) if (addIndex != -1) { componentData[addIndex][row] = data - if (callEvent) callComponentModifyEvent(geary.components.updatedComponent, record, componentId) - return false + if (callEvent) callComponentModifyEvent(geary.components.updatedComponent, row, componentId) + return } //if component is not already added, add it, then set val entityId = ids[row] - val moveTo = this + dataComponent - moveTo.moveWithNewComponent(record, data, dataComponent, entityId) + val moveTo = this + dataComponent.withoutRole(HOLDS_DATA) + dataComponent + val newRow = moveTo.moveWithNewComponent(this, row, data, dataComponent, entityId) removeEntity(row) // Component add listeners must query the target, this is an optimization - if (callEvent) callComponentModifyEvent(geary.components.setComponent, record, componentId) - return true + if (callEvent) moveTo.callComponentModifyEvent(geary.components.setComponent, newRow, componentId) + return onUpdated(moveTo, newRow) } - fun callComponentModifyEvent(eventType: ComponentId, record: Record, componentId: ComponentId) { + fun callComponentModifyEvent(eventType: ComponentId, row: Int, componentId: ComponentId) { // Archetype for the set event, if this doesn't have an event listener we skip val eventArc = archetypeProvider.getArchetype( GearyEntityType(ulongArrayOf(Relation.of(eventType, componentId).id)) @@ -285,29 +296,43 @@ class Archetype internal constructor( temporaryEntity { event -> event.add(geary.components.keepArchetype, noEvent = true) event.addRelation(eventType, componentId, noEvent = true) - eventRunner.callEvent(record, records[event], null) + records.runOn(event) { eventArc, eventRow -> + eventRunner.callEvent( + this, row, + eventArc, eventRow, + null, null + ) + + } } } } fun instantiateTo( - base: Record, - instance: Record, + baseRow: Int, + instanceArch: Archetype, + instanceRow: Int, callEvent: Boolean = true, ) { - instance.archetype.addComponent(instance, Relation.of(base.entity).id, true) + val baseEntity = this.getEntity(baseRow) + var currArch = instanceArch + var currRow = instanceRow + currArch.addComponent(currRow, Relation.of(baseEntity).id, true) { arch, row -> + currArch = arch; currRow = row + } + val noInheritComponents = EntityType(getRelationsByKind(componentId()).map { it.target }) type.filter { !it.holdsData() && it !in noInheritComponents }.forEach { - instance.archetype.addComponent(instance, it, true) + currArch.addComponent(currRow, it, true) { arch, row -> currArch = arch; currRow = row } } dataHoldingType.forEach { if (it.withoutRole(HOLDS_DATA) in noInheritComponents) return@forEach - instance.archetype.setComponent(instance, it, get(base.row, it)!!, true) + currArch.setComponent(currRow, it, get(baseRow, it)!!, true) { arch, row -> currArch = arch; currRow = row } } - base.entity.children.fastForEach { - it.addParent(instance.entity) + baseEntity.children.fastForEach { + it.addParent(instanceArch.getEntity(instanceRow)) } - if (callEvent) callComponentModifyEvent(geary.components.extendedEntity, instance, base.entity.id) + if (callEvent) currArch.callComponentModifyEvent(geary.components.extendedEntity, currRow, baseEntity.id) } /** @@ -316,20 +341,17 @@ class Archetype internal constructor( * @return Whether the record has changed. */ internal fun removeComponent( - record: Record, + row: Int, component: ComponentId ): Boolean { - with(record.archetype) { - val row = record.row - val entityId = ids[row] + val entityId = ids[row] - if (component !in type) return false + if (component !in type) return false - val moveTo = this - component + val moveTo = this - component - moveTo.moveWithoutComponent(record, component, entityId) - removeEntity(row) - } + moveTo.moveWithoutComponent(this, row, component, entityId) + removeEntity(row) return true } @@ -341,7 +363,7 @@ class Archetype internal constructor( if (allowUnregister == FALSE) return archetypes.queryManager.unregisterArchetype(this) unregistered = true - for ((id, archetype) in componentRemoveEdges.entries()) { + componentRemoveEdges.forEach { id, archetype -> archetype.componentAddEdges.remove(id) archetype.unregisterIfEmpty() } @@ -419,10 +441,7 @@ class Archetype internal constructor( val replacement = ids[lastIndex] ids[row] = replacement componentData.fastForEach { it[row] = it.last() } - records[replacement.toGeary()].apply { - this.archetype = this@Archetype - this.row = row - } + records.set(replacement.toGeary(), this@Archetype, row) } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/ArchetypeEventRunner.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/ArchetypeEventRunner.kt index 6d2a9f41e..37685ff8a 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/ArchetypeEventRunner.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/ArchetypeEventRunner.kt @@ -1,8 +1,6 @@ package com.mineinabyss.geary.engine.archetypes import com.mineinabyss.geary.datatypes.Entity -import com.mineinabyss.geary.datatypes.Record -import com.mineinabyss.geary.datatypes.maps.TypeMap import com.mineinabyss.geary.engine.EventRunner import com.mineinabyss.geary.helpers.fastForEach import com.mineinabyss.geary.modules.archetypes @@ -10,28 +8,38 @@ import com.mineinabyss.geary.systems.Listener import com.mineinabyss.geary.systems.query.QueriedEntity class ArchetypeEventRunner : EventRunner { - private val records: TypeMap get() = archetypes.records + private val records get() = archetypes.records override fun callEvent(target: Entity, event: Entity, source: Entity?) { - callEvent(records[target], records[event], source?.let { records[source] }) + records.runOn(target) { targetArc, targetRow -> + records.runOn(event) { eventArc, eventRow -> + if(source != null) records.runOn(source) { sourceArc, sourceRow -> + callEvent(targetArc, targetRow, eventArc, eventRow, sourceArc, sourceRow) + } else callEvent(targetArc, targetRow, eventArc, eventRow, null, null) + } + } } - fun callEvent(target: Record, event: Record, source: Record?) { - val eventArc = event.archetype - val targetArc = target.archetype - val sourceArc = source?.archetype - - fun QueriedEntity.reset(record: Record) { - originalArchetype = record.archetype - originalRow = record.row + fun callEvent( + targetArc: Archetype, + targetRow: Int, + eventArc: Archetype, + eventRow: Int, + sourceArc: Archetype?, + sourceRow: Int? + ) { + fun QueriedEntity.reset(arch: Archetype, row: Int) { + originalArchetype = arch + originalRow = row delegated = false } fun callListener(listener: Listener<*>) { val query = listener.query - query.event.reset(event) - query.reset(target) - source?.let { query.source.reset(it) } + query.event.reset(eventArc, eventRow) + query.reset(targetArc, targetRow) + if(sourceArc != null && sourceRow != null) + query.source.reset(sourceArc, sourceRow) listener.run() } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/EntityByArchetypeProvider.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/EntityByArchetypeProvider.kt index 31c1ca17c..a2d75b6ea 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/EntityByArchetypeProvider.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/EntityByArchetypeProvider.kt @@ -4,6 +4,7 @@ import com.mineinabyss.geary.datatypes.Entity import com.mineinabyss.geary.datatypes.EntityStack import com.mineinabyss.geary.datatypes.EntityType import com.mineinabyss.geary.datatypes.GearyEntity +import com.mineinabyss.geary.datatypes.maps.ArrayTypeMap import com.mineinabyss.geary.datatypes.maps.TypeMap import com.mineinabyss.geary.engine.EntityProvider import com.mineinabyss.geary.helpers.fastForEach @@ -17,8 +18,9 @@ import kotlinx.atomicfu.atomic class EntityByArchetypeProvider( private val reuseIDsAfterRemoval: Boolean = true, ) : EntityProvider { - private val records: TypeMap by lazy { archetypes.records } - private val archetypeProvider: ArchetypeProvider by lazy { archetypes.archetypeProvider } + private lateinit var records: ArrayTypeMap +// private val archetypeProvider: ArchetypeProvider by lazy { archetypes.archetypeProvider } + private lateinit var root: Archetype private val removedEntities: EntityStack = EntityStack() private val currId = atomic(0L) @@ -46,18 +48,25 @@ class EntityByArchetypeProvider( } } - val (archetype, row) = records[entity] - archetype.removeEntity(row) + records.runOn(entity) { archetype, row -> + archetype.removeEntity(row) + records.remove(entity) + removedEntities.push(entity) + } + } - records.remove(entity) - removedEntities.push(entity) + fun init(records: ArrayTypeMap, root: Archetype) { + this.records = records + this.root = root } - override fun getType(entity: Entity): EntityType = records[entity].archetype.type + override fun getType(entity: Entity): EntityType = records.runOn(entity) { archetype, _ -> + archetype.type + } private fun createRecord(entity: Entity) { - val root = archetypeProvider.rootArchetype - val createdRecord = root.createWithoutData(entity) - records[entity] = createdRecord + val root = root + val row = root.createWithoutData(entity) + records.set(entity, root, row) } } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/SimpleArchetypeProvider.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/SimpleArchetypeProvider.kt index fe67438a7..2cc338315 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/SimpleArchetypeProvider.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/SimpleArchetypeProvider.kt @@ -30,7 +30,7 @@ class SimpleArchetypeProvider : ArchetypeProvider { override fun getArchetype(entityType: EntityType): Archetype = synchronized(archetypeWriteLock) { var node = rootArchetype entityType.forEach { compId -> - node = node.componentAddEdges[compId] ?: createArchetype(node, compId) + node = node.componentAddEdges.getOrElse(compId) { createArchetype(node, compId) } } return node } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/operations/ArchetypeMutateOperations.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/operations/ArchetypeMutateOperations.kt index 45a8129d2..ee3d8af82 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/operations/ArchetypeMutateOperations.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/operations/ArchetypeMutateOperations.kt @@ -1,13 +1,15 @@ package com.mineinabyss.geary.engine.archetypes.operations import com.mineinabyss.geary.datatypes.* +import com.mineinabyss.geary.datatypes.maps.ArrayTypeMap import com.mineinabyss.geary.datatypes.maps.TypeMap import com.mineinabyss.geary.engine.EntityMutateOperations import com.mineinabyss.geary.engine.archetypes.ArchetypeProvider +import com.mineinabyss.geary.helpers.toGeary import com.mineinabyss.geary.modules.archetypes class ArchetypeMutateOperations : EntityMutateOperations { - private val records: TypeMap get() = archetypes.records + private lateinit var records: ArrayTypeMap private val archetypeProvider: ArchetypeProvider get() = archetypes.archetypeProvider override fun setComponentFor( @@ -16,11 +18,11 @@ class ArchetypeMutateOperations : EntityMutateOperations { data: Component, noEvent: Boolean ) { - records[entity].apply { + records.runOn(entity) { archetype, row -> // Only add HOLDS_DATA if this isn't a relation. All relations implicitly hold data currently and that bit // corresponds to the component part of the relation. val componentWithRole = componentId.withRole(HOLDS_DATA) - archetype.setComponent(this, componentWithRole, data, !noEvent) + archetype.setComponent(row, componentWithRole, data, !noEvent) } } @@ -29,26 +31,38 @@ class ArchetypeMutateOperations : EntityMutateOperations { componentId: ComponentId, noEvent: Boolean ) { - records[entity].apply { - archetype.addComponent(this, componentId.withoutRole(HOLDS_DATA), !noEvent) + records.runOn(entity) { archetype, row -> + archetype.addComponent(row, componentId.withoutRole(HOLDS_DATA), !noEvent) } } override fun extendFor(entity: Entity, base: Entity) { - val prefabRec = records[base] - prefabRec.archetype.instantiateTo(prefabRec, records[entity]) + records.runOn(base) { archetype, row -> + records.runOn(entity) { entityArch, entityRow -> + archetype.instantiateTo(row, entityArch, entityRow) + } + } } - override fun removeComponentFor(entity: Entity, componentId: ComponentId): Boolean = - records[entity].run { - val a = archetype.removeComponent(this, componentId.withRole(HOLDS_DATA)) - val b = archetype.removeComponent(this, componentId.withoutRole(HOLDS_DATA)) - a || b // return whether anything was changed + override fun removeComponentFor(entity: Entity, componentId: ComponentId): Boolean { + val a = records.runOn(entity) { archetype, row -> + archetype.removeComponent(row, componentId.withRole(HOLDS_DATA)) + } + val b = records.runOn(entity) { archetype, row -> + archetype.removeComponent(row, componentId.withoutRole(HOLDS_DATA)) } + return a || b // return whether anything was changed + } override fun clearEntity(entity: Entity) { - val record = records[entity] - record.archetype.removeEntity(record.row) - archetypeProvider.rootArchetype.createWithoutData(entity, record) + records.runOn(entity) { archetype, row -> + archetype.removeEntity(row) + val newRow = archetypeProvider.rootArchetype.createWithoutData(entity) + records.set(entity, archetypes.archetypeProvider.rootArchetype, newRow) + } + } + + fun init(records: ArrayTypeMap) { + this.records = records } } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/operations/ArchetypeReadOperations.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/operations/ArchetypeReadOperations.kt index 690cb9154..0c4dc5876 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/operations/ArchetypeReadOperations.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/operations/ArchetypeReadOperations.kt @@ -1,25 +1,26 @@ package com.mineinabyss.geary.engine.archetypes.operations import com.mineinabyss.geary.datatypes.* -import com.mineinabyss.geary.datatypes.maps.TypeMap import com.mineinabyss.geary.engine.EntityReadOperations import com.mineinabyss.geary.modules.archetypes import com.mineinabyss.geary.systems.accessors.RelationWithData class ArchetypeReadOperations : EntityReadOperations { - private val records: TypeMap get() = archetypes.records + private val records get() = archetypes.records override fun getComponentFor(entity: Entity, componentId: ComponentId): Component? { - val (archetype, row) = records[entity] - return archetype[row, componentId.let { if (it.hasRole(RELATION)) it else it.withRole(HOLDS_DATA) }] + records.runOn(entity) { archetype, row -> + return archetype[row, componentId.let { if (it.hasRole(RELATION)) it else it.withRole(HOLDS_DATA) }] + } } override fun getComponentsFor(entity: Entity): Array { - val (archetype, row) = records[entity] - return archetype.getComponents(row).also { array -> - archetype.relationsWithData.forEach { relation -> - val i = archetype.indexOf(relation) - array[i] = RelationWithData(array[i], null, Relation.of(relation)) + records.runOn(entity) { archetype, row -> + return archetype.getComponents(row).also { array -> + archetype.relationsWithData.forEach { relation -> + val i = archetype.indexOf(relation) + array[i] = RelationWithData(array[i], null, Relation.of(relation)) + } } } } @@ -32,15 +33,13 @@ class ArchetypeReadOperations : EntityReadOperations { entity: Entity, kind: ComponentId, target: EntityId, - ): List> { - val (arc, row) = records[entity] - - return arc.readRelationDataFor(row, kind, target, arc.getRelations(kind, target)) + ): List> = records.runOn(entity) { archetype, row -> + return archetype.readRelationDataFor(row, kind, target, archetype.getRelations(kind, target)) } override fun getRelationsFor(entity: Entity, kind: ComponentId, target: EntityId): List = - records[entity].archetype.getRelations(kind, target) + records.runOn(entity) { archetype, _ -> archetype.getRelations(kind, target) } override fun hasComponentFor(entity: Entity, componentId: ComponentId): Boolean = - componentId in records[entity].archetype + records.runOn(entity) { archetype, _ -> componentId in archetype } } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/ArchetypeEngineModule.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/ArchetypeEngineModule.kt index 216e9b57f..81d8ad67a 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/ArchetypeEngineModule.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/ArchetypeEngineModule.kt @@ -25,13 +25,14 @@ open class ArchetypeEngineModule( override val eventRunner = ArchetypeEventRunner() override val pipeline = PipelineImpl() + open val records: ArrayTypeMap = ArrayTypeMap() + override val read = ArchetypeReadOperations() override val write = ArchetypeMutateOperations() override val entityProvider = EntityByArchetypeProvider() override val componentProvider = ComponentAsEntityProvider() override val defaults: Defaults = Defaults() - open val records: TypeMap = ArrayTypeMap() open val archetypeProvider: ArchetypeProvider = SimpleArchetypeProvider() override val components by lazy { Components() } @@ -44,6 +45,8 @@ open class ArchetypeEngineModule( override fun init(module: ArchetypeEngineModule) { DI.add(module) DI.add(module) + module.entityProvider.init(module.records, module.archetypeProvider.rootArchetype) + module.write.init(module.records) module.componentProvider.createComponentInfo() } 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 b2542c786..bc05055a8 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,6 +1,6 @@ package com.mineinabyss.geary.modules -import com.mineinabyss.geary.datatypes.maps.SynchronizedTypeMap +import com.mineinabyss.geary.datatypes.maps.SynchronizedArrayTypeMap import com.mineinabyss.geary.engine.archetypes.EntityByArchetypeProvider /** @@ -13,8 +13,8 @@ class TestEngineModule( reuseIDsAfterRemoval: Boolean = true, useSynchronized: Boolean = false, ) : ArchetypeEngineModule() { + override val records = if (useSynchronized) SynchronizedArrayTypeMap() else super.records 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/query/QueriedEntity.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/query/QueriedEntity.kt index 06f63b7c3..9ffd48227 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/query/QueriedEntity.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/query/QueriedEntity.kt @@ -2,7 +2,7 @@ package com.mineinabyss.geary.systems.query import com.mineinabyss.geary.annotations.optin.UnsafeAccessors import com.mineinabyss.geary.datatypes.Entity -import com.mineinabyss.geary.datatypes.Record +import com.mineinabyss.geary.datatypes.GearyEntity import com.mineinabyss.geary.datatypes.family.Family import com.mineinabyss.geary.datatypes.family.family import com.mineinabyss.geary.engine.archetypes.Archetype @@ -42,10 +42,16 @@ open class QueriedEntity( internal var originalRow = 0 @UnsafeAccessors - val archetype: Archetype get() = if (delegated) delegate!!.archetype else originalArchetype - val row: Int get() = if (delegated) delegate!!.row else originalRow + val archetype: Archetype + get() = if (delegated) + archetypes.records.runOn(delegate!!) { archetype, _ -> archetype } + else originalArchetype + val row: Int + get() = if (delegated) + archetypes.records.runOn(delegate!!) { _, row -> row } + else originalRow - private var delegate: Record? = null + private var delegate: GearyEntity? = null @PublishedApi internal var delegated = false @@ -55,7 +61,7 @@ open class QueriedEntity( get() { val entity = archetype.getEntity(row) if (!delegated) { - delegate = archetypes.records[entity] + delegate = entity } delegated = true return entity diff --git a/geary-core/src/jsMain/kotlin/com/mineinabyss/geary/datatypes/maps/CompId2ArchetypeMap.kt b/geary-core/src/jsMain/kotlin/com/mineinabyss/geary/datatypes/maps/CompId2ArchetypeMap.kt deleted file mode 100644 index 75da47620..000000000 --- a/geary-core/src/jsMain/kotlin/com/mineinabyss/geary/datatypes/maps/CompId2ArchetypeMap.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.mineinabyss.geary.datatypes.maps - -actual typealias CompId2ArchetypeMap = CompId2ArchetypeMapViaMutableMap diff --git a/geary-core/src/jvmMain/kotlin/com/mineinabyss/geary/datatypes/maps/CompId2ArchetypeMap.kt b/geary-core/src/jvmMain/kotlin/com/mineinabyss/geary/datatypes/maps/CompId2ArchetypeMap.kt deleted file mode 100644 index 8116b1652..000000000 --- a/geary-core/src/jvmMain/kotlin/com/mineinabyss/geary/datatypes/maps/CompId2ArchetypeMap.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.mineinabyss.geary.datatypes.maps - -import com.mineinabyss.geary.datatypes.GearyComponentId -import com.mineinabyss.geary.engine.archetypes.Archetype -import it.unimi.dsi.fastutil.longs.Long2ObjectArrayMap - -actual class CompId2ArchetypeMap { - val inner = Long2ObjectArrayMap() - actual operator fun get(id: GearyComponentId): Archetype? = inner[id.toLong()] - actual operator fun set(id: GearyComponentId, archetype: Archetype) { - inner[id.toLong()] = archetype - } - - actual fun remove(id: GearyComponentId) { - inner.remove(id.toLong()) - } - - actual fun clear() { - inner.clear() - } - - actual fun entries(): Set> = inner.mapKeys { it.key.toULong() }.entries - - actual val size: Int get() = inner.size - - actual operator fun contains(id: GearyComponentId): Boolean = inner.containsKey(id.toLong()) - - actual inline fun getOrSet(id: GearyComponentId, put: () -> Archetype): Archetype { - return inner[id.toLong()] ?: put().also { set(id, it) } - } -} diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/accessors/ListenerLiveEntityModificationTests.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/accessors/ListenerLiveEntityModificationTests.kt index c06f82259..9de16c8b3 100644 --- a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/accessors/ListenerLiveEntityModificationTests.kt +++ b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/accessors/ListenerLiveEntityModificationTests.kt @@ -56,7 +56,16 @@ class ListenerLiveEntityModificationTests : GearyTest() { count shouldBe 1 } - @OptIn(UnsafeAccessors::class) + @Test + fun `testing`() { + entity { + set(Comp1(1)) + remove() + set(Comp1(10)) + } + + } + @Test fun `should allow data modify when entity archetype changed by REMOVE`() { resetEngine()