diff --git a/src/commonMain/kotlin/com/github/quillraven/fleks/collection/bag.kt b/src/commonMain/kotlin/com/github/quillraven/fleks/collection/bag.kt index 15ef9d7..6c96f26 100644 --- a/src/commonMain/kotlin/com/github/quillraven/fleks/collection/bag.kt +++ b/src/commonMain/kotlin/com/github/quillraven/fleks/collection/bag.kt @@ -45,7 +45,7 @@ class Bag( /** * Returns the element at position [index] or null if there is no element or if [index] is out of bounds */ - inline fun getOrNull(index: Int): T? = values.getOrNull(index) + fun getOrNull(index: Int): T? = values.getOrNull(index) fun hasNoValueAtIndex(index: Int): Boolean { return index >= size || index < 0 || values[index] == null diff --git a/src/commonMain/kotlin/com/github/quillraven/fleks/collection/bitArray.kt b/src/commonMain/kotlin/com/github/quillraven/fleks/collection/bitArray.kt index 8a01a76..b757881 100644 --- a/src/commonMain/kotlin/com/github/quillraven/fleks/collection/bitArray.kt +++ b/src/commonMain/kotlin/com/github/quillraven/fleks/collection/bitArray.kt @@ -137,6 +137,22 @@ class BitArray( } } + inline fun clearAndForEachSetBit(action: (Int) -> Unit) { + for (word in bits.size - 1 downTo 0) { + var bitsAtWord = bits[word] + if (bitsAtWord != 0L) { + val w = word shl 6 + while (bitsAtWord != 0L) { + bits[word] = 0L // clear + // gets the distance from the start of the word to the highest (leftmost) bit + val bit = 63 - bitsAtWord.countLeadingZeroBits() + action(w + bit) + bitsAtWord = (bitsAtWord xor (1L shl bit)) // removes highest bit + } + } + } + } + override fun hashCode(): Int { if (bits.isEmpty()) { return 0 diff --git a/src/commonMain/kotlin/com/github/quillraven/fleks/component.kt b/src/commonMain/kotlin/com/github/quillraven/fleks/component.kt index 6c815d8..d1fe8a7 100644 --- a/src/commonMain/kotlin/com/github/quillraven/fleks/component.kt +++ b/src/commonMain/kotlin/com/github/quillraven/fleks/component.kt @@ -6,25 +6,61 @@ import kotlin.math.max import kotlin.native.concurrent.ThreadLocal /** - * A class that assigns a unique [id] per type of [Component] starting from 0. + * An interface that specifies a unique [id]. * This [id] is used internally by Fleks as an index for some arrays. - * Every [Component] class must have at least one [ComponentType]. */ -abstract class ComponentType { - val id: Int = nextId++ +// interface was necessary for (#118) to support enum classes as entity tags +// because enum classes can only inherit from an interface and not from an abstract class. +interface UniqueId { + val id: Int @ThreadLocal companion object { - private var nextId = 0 + internal var nextId = 0 } } +/** + * An abstract class that assigns a unique [id] per type of [Component] starting from 0. + * Every [Component] class must have at least one [ComponentType] which serves + * as a [UniqueId]. + */ +abstract class ComponentType : UniqueId { + override val id: Int = UniqueId.nextId++ +} + /** * Function to create an object for a [ComponentType] of type T. * This is a convenience function for [components][Component] that have more than one [ComponentType]. */ inline fun componentTypeOf(): ComponentType = object : ComponentType() {} +/** + * Type alias for a special type of [ComponentType] that is used to tag [entities][Entity]. + * A tag is a special form of a [Component] that does not have any data. It is stored + * more efficiently when compared to an empty [Component] and should therefore be preferred + * in those cases. + */ +typealias EntityTag = ComponentType + +/** + * Type alias for a special type of [UniqueId]. It can be used to make values of an enum + * class an [EntityTag]. + * + * ``` + * enum class MyTags : EntityTags by entityTagOf() { + * TAG_A, TAG_B + * } + * ``` + */ +typealias EntityTags = UniqueId + +/** + * Function to create an object for an [EntityTag]. + * It can be used to make values of an enum class an [EntityTag]. Refer to [EntityTags]. + */ +fun entityTagOf(): EntityTag = object : EntityTag() {} + /** * An interface that must be implemented by any component that is used for Fleks. * A component must have at least one [ComponentType] that is provided via the [type] function. @@ -194,14 +230,12 @@ class ComponentService { } /** - * Returns the [ComponentsHolder] of the given [index] inside the [holdersBag]. The index - * is linked to the id of a [ComponentType]. + * Returns the [ComponentsHolder] of the given [index] inside the [holdersBag] or null. + * The index is linked to the id of a [ComponentType]. * This function is only used internally at safe areas to speed up certain processes like * removing an [entity][Entity] or creating a snapshot via [World.snapshot]. - * - * @throws [IndexOutOfBoundsException] if the [index] exceeds the bag's capacity. */ - internal fun holderByIndex(index: Int): ComponentsHolder<*> { - return holdersBag[index] + internal fun holderByIndexOrNull(index: Int): ComponentsHolder<*>? { + return holdersBag.getOrNull(index) } } diff --git a/src/commonMain/kotlin/com/github/quillraven/fleks/entity.kt b/src/commonMain/kotlin/com/github/quillraven/fleks/entity.kt index 803d58f..e2a5afc 100644 --- a/src/commonMain/kotlin/com/github/quillraven/fleks/entity.kt +++ b/src/commonMain/kotlin/com/github/quillraven/fleks/entity.kt @@ -2,6 +2,7 @@ package com.github.quillraven.fleks import com.github.quillraven.fleks.collection.* import kotlinx.serialization.Serializable +import kotlin.jvm.JvmName /** * An entity of a [world][World]. It represents a unique identifier that is the combination @@ -47,22 +48,22 @@ abstract class EntityComponentContext( componentService.holder(type).getOrNull(this) /** - * Returns true if and only if the [entity][Entity] has a [component][Component] of the given [type]. + * Returns true if and only if the [entity][Entity] has a [component][Component] or [tag][EntityTag] of the given [type]. */ - inline operator fun > Entity.contains(type: ComponentType): Boolean = - this in componentService.holder(type) + operator fun Entity.contains(type: UniqueId<*>): Boolean = + componentService.world.entityService.compMasks.getOrNull(this.id)?.get(type.id) ?: false /** - * Returns true if and only if the [entity][Entity] has a [component][Component] of the given [type]. + * Returns true if and only if the [entity][Entity] has a [component][Component] or [tag][EntityTag] of the given [type]. */ - inline infix fun > Entity.has(type: ComponentType): Boolean = - this in componentService.holder(type) + infix fun Entity.has(type: UniqueId<*>): Boolean = + componentService.world.entityService.compMasks.getOrNull(this.id)?.get(type.id) ?: false /** - * Returns true if and only if the [entity][Entity] doesn't have a [component][Component] of the given [type]. + * Returns true if and only if the [entity][Entity] doesn't have a [component][Component] or [tag][EntityTag] of the given [type]. */ - inline infix fun > Entity.hasNo(type: ComponentType): Boolean = - this !in componentService.holder(type) + infix fun Entity.hasNo(type: UniqueId<*>): Boolean = + componentService.world.entityService.compMasks.getOrNull(this.id)?.get(type.id)?.not() ?: true /** * Updates the [entity][Entity] using the given [configuration] to add and remove [components][Component]. @@ -136,6 +137,27 @@ open class EntityCreateContext( holder.setWildcard(this, cmp) } } + + /** + * Sets the [tag][EntityTag] to the [entity][Entity]. + */ + operator fun Entity.plusAssign(tag: UniqueId<*>) { + compMasks[this.id].set(tag.id) + // We need to remember used tags in order to correctly return and load them using + // the snapshot functionality, because tags are not managed via ComponentHolder and + // the entity's component mask just knows about the tag's id. + // However, a snapshot should contain the real object instances related to an entity. + componentService.world.tagCache[tag.id] = tag + } + + /** + * Sets all [tags][EntityTag] on the given [entity][Entity]. + */ + @JvmName("plusAssignTags") + operator fun Entity.plusAssign(tags: List>) { + tags.forEach { this += it } + } + } /** @@ -146,6 +168,7 @@ class EntityUpdateContext( compService: ComponentService, compMasks: Bag, ) : EntityCreateContext(compService, compMasks) { + /** * Removes a [component][Component] of the given [type] from the [entity][Entity]. * @@ -165,10 +188,7 @@ class EntityUpdateContext( * If the [entity][Entity] does not have such a [component][Component] then [add] is called * to assign it to the [entity][Entity] and return it. */ - inline fun > Entity.getOrAdd( - type: ComponentType, - add: () -> T, - ): T { + inline fun > Entity.getOrAdd(type: ComponentType, add: () -> T): T { val holder: ComponentsHolder = componentService.holder(type) val existingCmp = holder.getOrNull(this) if (existingCmp != null) { @@ -180,6 +200,11 @@ class EntityUpdateContext( holder[this] = newCmp return newCmp } + + /** + * Removes the [tag][EntityTag] from the [entity][Entity]. + */ + operator fun Entity.minusAssign(tag: UniqueId<*>) = compMasks[this.id].clear(tag.id) } /** @@ -458,29 +483,35 @@ class EntityService( } /** - * Updates an [entity] with the given [components]. + * Updates an [entity] with the given [snapshot][Snapshot]. * Notifies all [families][World.allFamilies]. * This function is only used by [World.loadSnapshot] and [World.loadSnapshotOf], * and is therefore working with unsafe wildcards ('*'). */ - internal fun configure(entity: Entity, components: List>) { + internal fun configure(entity: Entity, snapshot: Snapshot) { val compMask = compMasks[entity.id] + val components = snapshot.components // remove any existing components that are not part of the new components to set - compMask.forEachSetBit { cmpId -> - if (components.any { it.type().id == cmpId }) return@forEachSetBit + compMask.clearAndForEachSetBit { cmpId -> + if (components.any { it.type().id == cmpId }) return@clearAndForEachSetBit // we can use holderByIndex because we can be sure that the holder already exists // because otherwise the entity would not even have the component - compService.holderByIndex(cmpId) -= entity + compService.holderByIndexOrNull(cmpId)?.minusAssign(entity) } - compMask.clearAll() // set new components components.forEach { cmp -> + compMask.set(cmp.type().id) val holder = compService.wildcardHolder(cmp.type()) holder.setWildcard(entity, cmp) - compMask.set(cmp.type().id) + } + + // set new tags + snapshot.tags.forEach { + compMask.set(it.id) + world.tagCache[it.id] = it } // notify families @@ -518,10 +549,9 @@ class EntityService( removeHook?.invoke(world, entity) // remove components - compMask.forEachSetBit { compId -> - compService.holderByIndex(compId) -= entity + compMask.clearAndForEachSetBit { compId -> + compService.holderByIndexOrNull(compId)?.minusAssign(entity) } - compMask.clearAll() // update families world.allFamilies.forEach { it.onEntityRemoved(entity) } diff --git a/src/commonMain/kotlin/com/github/quillraven/fleks/family.kt b/src/commonMain/kotlin/com/github/quillraven/fleks/family.kt index 6f03630..c758f65 100644 --- a/src/commonMain/kotlin/com/github/quillraven/fleks/family.kt +++ b/src/commonMain/kotlin/com/github/quillraven/fleks/family.kt @@ -26,7 +26,7 @@ data class FamilyDefinition( /** * Any [entity][Entity] must have all given [types] to be part of the [family][Family]. */ - fun all(vararg types: ComponentType<*>): FamilyDefinition { + fun all(vararg types: UniqueId<*>): FamilyDefinition { allOf = BitArray(types.size).also { bits -> types.forEach { bits.set(it.id) } } @@ -36,7 +36,7 @@ data class FamilyDefinition( /** * Any [entity][Entity] must not have any of the given [types] to be part of the [family][Family]. */ - fun none(vararg types: ComponentType<*>): FamilyDefinition { + fun none(vararg types: UniqueId<*>): FamilyDefinition { noneOf = BitArray(types.size).also { bits -> types.forEach { bits.set(it.id) } } @@ -46,7 +46,7 @@ data class FamilyDefinition( /** * Any [entity][Entity] must have at least one of the given [types] to be part of the [family][Family]. */ - fun any(vararg types: ComponentType<*>): FamilyDefinition { + fun any(vararg types: UniqueId<*>): FamilyDefinition { anyOf = BitArray(types.size).also { bits -> types.forEach { bits.set(it.id) } } diff --git a/src/commonMain/kotlin/com/github/quillraven/fleks/world.kt b/src/commonMain/kotlin/com/github/quillraven/fleks/world.kt index 05ab2d0..e08228b 100644 --- a/src/commonMain/kotlin/com/github/quillraven/fleks/world.kt +++ b/src/commonMain/kotlin/com/github/quillraven/fleks/world.kt @@ -201,6 +201,11 @@ fun configureWorld(entityCapacity: Int = 512, cfg: WorldConfiguration.() -> Unit return newWorld } +/** + * Snapshot for an [entity][Entity] that contains its [components][Component] and [tags][EntityTag]. + */ +data class Snapshot(val components: List>, val tags: List>) + /** * A world to handle [entities][Entity] and [systems][IntervalSystem]. * @@ -248,10 +253,16 @@ class World internal constructor( var systems = emptyArray() internal set + /** + * Cache of used [EntityTag] instances. Needed for snapshot functionality. + */ + @PublishedApi + internal val tagCache = mutableMapOf>() + init { /** * Maybe because of design flaws, the world reference of the ComponentService must be - * set in the world's constructor because the parent class (=BaseEntityExtensions) already + * set in the world's constructor because the parent class (=EntityComponentContext) already * requires a ComponentService, and it is not possible to pass "this" reference directly. * * That's why it is happening here to set it as soon as possible. @@ -435,35 +446,35 @@ class World internal constructor( * The values are a list of components that a specific entity has. If the entity * does not have any components then the value is an empty list. */ - fun snapshot(): Map>> { - val entityComps = mutableMapOf>>() + fun snapshot(): Map { + val result = mutableMapOf() - entityService.forEach { entity -> - val components = mutableListOf>() - val compMask = entityService.compMasks[entity.id] - compMask.forEachSetBit { cmpId -> - components += componentService.holderByIndex(cmpId)[entity] - } - entityComps[entity] = components - } + entityService.forEach { result[it] = snapshotOf(it) } - return entityComps + return result } /** * Returns a list that contains all components of the given [entity] of this world. * If the entity does not have any components then an empty list is returned. */ - fun snapshotOf(entity: Entity): List> { + fun snapshotOf(entity: Entity): Snapshot { val comps = mutableListOf>() + val tags = mutableListOf>() if (entity in entityService) { entityService.compMasks[entity.id].forEachSetBit { cmpId -> - comps += componentService.holderByIndex(cmpId)[entity] + val holder = componentService.holderByIndexOrNull(cmpId) + if (holder == null) { + // tag instead of component + tags += tagCache[cmpId] ?: throw FleksSnapshotException("Tag with id $cmpId was never assigned") + } else { + comps += holder[entity] + } } } - return comps + return Snapshot(comps, tags) } /** @@ -473,7 +484,7 @@ class World internal constructor( * * @throws FleksSnapshotException if a family iteration is currently in process. */ - fun loadSnapshot(snapshot: Map>>) { + fun loadSnapshot(snapshot: Map) { if (entityService.delayRemoval) { throw FleksSnapshotException("Snapshots cannot be loaded while a family iteration is in process") } @@ -495,24 +506,24 @@ class World internal constructor( repeat(maxId + 1) { val entity = Entity(it, version = (versionLookup[it]?.version ?: 0u) - 1u) this.recycle(entity) - val components = snapshot[versionLookup[it]] - if (components != null) { - // components for entity are provided -> create it + val entitySnapshot = snapshot[versionLookup[it]] + if (entitySnapshot != null) { + // snapshot for entity is provided -> create it // note that the id for the entity will be the recycled id from above - this.configure(this.create { }, components) + this.configure(this.create { }, entitySnapshot) } } } } /** - * Loads the given [entity] and its [components]. + * Loads the given [entity] and its [snapshot][Snapshot]. * If the entity does not exist yet, it will be created. * If the entity already exists it will be updated with the given components. * * @throws FleksSnapshotException if a family iteration is currently in process. */ - fun loadSnapshotOf(entity: Entity, components: List>) { + fun loadSnapshotOf(entity: Entity, snapshot: Snapshot) { if (entityService.delayRemoval) { throw FleksSnapshotException("Snapshots cannot be loaded while a family iteration is in process") } @@ -523,7 +534,7 @@ class World internal constructor( } // load components for entity - entityService.configure(entity, components) + entityService.configure(entity, snapshot) } /** diff --git a/src/commonTest/kotlin/com/github/quillraven/fleks/ComponentTest.kt b/src/commonTest/kotlin/com/github/quillraven/fleks/ComponentTest.kt index 9ee771d..405fecc 100644 --- a/src/commonTest/kotlin/com/github/quillraven/fleks/ComponentTest.kt +++ b/src/commonTest/kotlin/com/github/quillraven/fleks/ComponentTest.kt @@ -144,7 +144,7 @@ internal class ComponentTest { @Test fun getHolderByComponentId() { - val actual = testService.holderByIndex(0) + val actual = testService.holderByIndexOrNull(0) assertSame(testHolder, actual) } diff --git a/src/commonTest/kotlin/com/github/quillraven/fleks/EntityTagTest.kt b/src/commonTest/kotlin/com/github/quillraven/fleks/EntityTagTest.kt new file mode 100644 index 0000000..ca079ac --- /dev/null +++ b/src/commonTest/kotlin/com/github/quillraven/fleks/EntityTagTest.kt @@ -0,0 +1,157 @@ +package com.github.quillraven.fleks + +import com.github.quillraven.fleks.World.Companion.family +import kotlin.test.* + +data object Visible : EntityTag() + +class TestTagSystem(var ticks: Int = 0) : IteratingSystem(family { all(Visible, TestTags.PLAYER) }) { + override fun onTickEntity(entity: Entity) { + ++ticks + } +} + +enum class TestTags : EntityTags by entityTagOf() { + PLAYER, COLLISION +} + +class TestTagComponent : Component { + override fun type() = TestTagComponent + + companion object : ComponentType() +} + +class EntityTagTest { + + @Test + fun testTagAssignment() { + val world = configureWorld {} + val entity = world.entity { it += Visible } + + with(world) { + assertTrue(Visible in entity) + + entity.configure { it -= Visible } + + assertFalse(Visible in entity) + } + } + + @Test + fun testTagSystem() { + lateinit var testSystem: TestTagSystem + val world = configureWorld { + systems { + add(TestTagSystem().also { testSystem = it }) + } + } + val entity = world.entity { + it += Visible + it += TestTags.PLAYER + } + + world.update(1f) + assertEquals(1, testSystem.ticks) + + testSystem.ticks = 0 + with(world) { entity.configure { it -= Visible } } + world.update(1f) + assertEquals(0, testSystem.ticks) + } + + @Test + fun testEnumTags() { + assertNotEquals(TestTags.PLAYER.id, TestTags.COLLISION.id) + assertNotEquals(Visible.id, TestTags.PLAYER.id) + assertNotEquals(Visible.id, TestTags.COLLISION.id) + + val world = configureWorld {} + val entity = world.entity { it += TestTags.PLAYER } + + with(world) { + assertTrue(entity has TestTags.PLAYER) + + entity.configure { it -= TestTags.PLAYER } + + assertTrue(entity hasNo TestTags.PLAYER) + } + } + + @Test + fun testRemoveEntityWithTag() { + val world = configureWorld {} + val entity = world.entity { it += TestTags.PLAYER } + + with(world) { + entity.remove() + + assertFalse(entity in world) + } + } + + @Test + fun testSnapshotWithTag() { + val world = configureWorld {} + val entity = world.entity { it += TestTags.PLAYER } + + val snapshot = world.snapshotOf(entity) + assertEquals(TestTags.PLAYER, snapshot.tags[0]) + + world.removeAll(true) + with(world) { + assertFalse(entity has TestTags.PLAYER) + world.loadSnapshot(mapOf(entity to snapshot)) + assertTrue(entity has TestTags.PLAYER) + } + } + + @Test + fun testSnapshotWithTagAndComponent() { + val world = configureWorld {} + val component = TestTagComponent() + val entity = world.entity { + it += TestTags.PLAYER + it += component + } + + val snapshot = world.snapshotOf(entity) + assertEquals(TestTags.PLAYER, snapshot.tags[0]) + assertEquals(component, snapshot.components[0]) + + world.removeAll(true) + with(world) { + assertFalse(entity has TestTags.PLAYER) + assertFalse(entity has TestTagComponent) + + world.loadSnapshot(mapOf(entity to snapshot)) + + assertTrue(entity has TestTags.PLAYER) + assertTrue(entity has TestTagComponent) + } + } + + @Test + fun testLoadSnapshotOfTag() { + val world = configureWorld {} + val entity = world.entity { } + + world.loadSnapshot(mapOf(entity to Snapshot(emptyList(), listOf(TestTags.PLAYER)))) + + with(world) { + assertTrue(entity has TestTags.PLAYER) + assertTrue(TestTags.PLAYER.id in world.tagCache) + } + } + + @Test + fun testSetListOfTags() { + val world = configureWorld {} + val entity = world.entity { it += TestTags.entries } + + with(world) { + TestTags.entries.forEach { + assertTrue(it in entity) + } + } + } +} diff --git a/src/commonTest/kotlin/com/github/quillraven/fleks/WorldTest.kt b/src/commonTest/kotlin/com/github/quillraven/fleks/WorldTest.kt index 3a7ec86..77fbe2d 100644 --- a/src/commonTest/kotlin/com/github/quillraven/fleks/WorldTest.kt +++ b/src/commonTest/kotlin/com/github/quillraven/fleks/WorldTest.kt @@ -616,7 +616,7 @@ internal class WorldTest { assertEquals(expected.size, actual.size) expected.forEach { (entity, expectedComps) -> - val actualComps = actual[entity] + val actualComps = actual[entity]?.components assertNotNull(actualComps) assertEquals(expectedComps.size, actualComps.size) assertTrue(expectedComps.containsAll(actualComps) && actualComps.containsAll(expectedComps)) @@ -629,8 +629,8 @@ internal class WorldTest { val comp1 = WorldTestComponent() val e1 = w.entity { it += comp1 } val e2 = w.entity { } - val expected1 = listOf(comp1) - val expected2 = emptyList() + val expected1 = Snapshot(listOf(comp1), emptyList()) + val expected2 = Snapshot(emptyList(), emptyList()) assertEquals(expected1, w.snapshotOf(e1)) assertEquals(expected2, w.snapshotOf(e2)) @@ -664,13 +664,13 @@ internal class WorldTest { val w = configureWorld { } val entity = w.entity() val comps = listOf(WorldTestComponent()) - val snapshot = mapOf(entity to comps) + val snapshot = mapOf(entity to Snapshot(comps, emptyList())) w.loadSnapshot(snapshot) val actual = w.snapshotOf(entity) assertEquals(1, w.numEntities) - assertEquals(comps, actual) + assertEquals(comps, actual.components) } @Test @@ -681,7 +681,7 @@ internal class WorldTest { } val entity = w.entity() val comps = listOf(WorldTestComponent()) - val snapshot = mapOf(entity to comps) + val snapshot = mapOf(entity to Snapshot(comps, emptyList())) w.loadSnapshot(snapshot) val actual = w.snapshotOf(entity) @@ -691,7 +691,7 @@ internal class WorldTest { assertFalse(it in w) } assertTrue(entity in w) - assertEquals(comps, actual) + assertEquals(comps, actual.components) } @Test @@ -708,9 +708,9 @@ internal class WorldTest { val comp1 = WorldTestComponent() val comp2 = WorldTestComponent() val snapshot = mapOf( - Entity(3, version = 0u) to listOf(comp1, WorldTestComponent2()), - Entity(5, version = 0u) to listOf(comp2), - Entity(7, version = 0u) to listOf() + Entity(3, version = 0u) to Snapshot(listOf(comp1, WorldTestComponent2()), emptyList()), + Entity(5, version = 0u) to Snapshot(listOf(comp2), emptyList()), + Entity(7, version = 0u) to Snapshot(listOf(), emptyList()) ) w.loadSnapshot(snapshot) @@ -721,11 +721,15 @@ internal class WorldTest { assertEquals(3, w.numEntities) // actual snapshot after loading the test snapshot is loaded should match assertEquals(snapshot.size, actual.size) - snapshot.forEach { (entity, expectedComps) -> - val actualComps = actual[entity] - assertNotNull(actualComps) - assertEquals(expectedComps.size, actualComps.size) - assertTrue(expectedComps.containsAll(actualComps) && actualComps.containsAll(expectedComps)) + snapshot.forEach { (entity, entitySnapshot) -> + val actualSnapshot = actual[entity] + assertNotNull(actualSnapshot) + assertEquals(entitySnapshot.components.size, actualSnapshot.components.size) + assertTrue( + entitySnapshot.components.containsAll(actualSnapshot.components) && actualSnapshot.components.containsAll( + entitySnapshot.components + ) + ) } // 2 out of 3 loaded entities should be part of the IteratingSystem family assertEquals(2, w.system().numCallsEntity) @@ -742,7 +746,7 @@ internal class WorldTest { fun testCreateEntityAfterSnapshotLoaded() { val w = configureWorld { } val snapshot = mapOf( - Entity(1, version = 0u) to listOf>() + Entity(1, version = 0u) to Snapshot(listOf(), emptyList()) ) w.loadSnapshot(snapshot) @@ -761,7 +765,7 @@ internal class WorldTest { val components = listOf(WorldTestComponent()) assertFalse { entity in family } - w.loadSnapshotOf(entity, components) + w.loadSnapshotOf(entity, Snapshot(components, emptyList())) assertEquals(1, w.numEntities) assertTrue { with(w) { entity has WorldTestComponent } } @@ -776,7 +780,7 @@ internal class WorldTest { val components = listOf(WorldTestComponent()) assertFalse { entity in family } - w.loadSnapshotOf(entity, components) + w.loadSnapshotOf(entity, Snapshot(components, emptyList())) assertEquals(1, w.numEntities) assertTrue { with(w) { entity has WorldTestComponent } } @@ -795,7 +799,7 @@ internal class WorldTest { assertTrue { entity in family2 } assertFalse { entity in family } - w.loadSnapshotOf(entity, components) + w.loadSnapshotOf(entity, Snapshot(components, emptyList())) assertTrue { with(w) { entity has WorldTestComponent } } assertTrue { with(w) { entity hasNo WorldTestComponent2 } } @@ -812,32 +816,33 @@ internal class WorldTest { w.entity { it += WorldTestComponent() } f.forEach { - assertFailsWith { w.loadSnapshotOf(entity, components) } + assertFailsWith { w.loadSnapshotOf(entity, Snapshot(components, emptyList())) } } } - data class FollowerComponent(val leader:Entity) : Component{ + data class FollowerComponent(val leader: Entity) : Component { override fun type() = FollowerComponent + companion object : ComponentType() } @Test - fun testLoadSnapshotWithReferenceToEntity(){ - val w = configureWorld { } - val leaderA = w.entity { } - val followerA = w.entity{ + fun testLoadSnapshotWithReferenceToEntity() { + val w = configureWorld { } + val leaderA = w.entity { } + val followerA = w.entity { it += FollowerComponent(leaderA) } w -= leaderA - val leaderB = w.entity { } - val followerB = w.entity{ + val leaderB = w.entity { } + val followerB = w.entity { it += FollowerComponent(leaderB) } val snapshot = w.snapshot() w.loadSnapshot(snapshot) - with(w){ + with(w) { assertFalse { leaderA in w } assertTrue { followerA in w } assertFalse { followerA[FollowerComponent].leader in w }