From 1246b7713ee1e686a573ba29213fe12553a00fe8 Mon Sep 17 00:00:00 2001 From: Quillraven Date: Mon, 23 Dec 2024 22:07:28 +0100 Subject: [PATCH] add indexOfFirst and indexOfLast to EntityBag and family. Also, add goToFirst and goToLast to EntityBagIterator --- .../fleks/collection/EntityBagIterator.kt | 28 ++++++++++++++ .../quillraven/fleks/collection/entityBag.kt | 12 ++++++ .../fleks/collection/mutableEntityBag.kt | 28 ++++++++++++++ .../com/github/quillraven/fleks/family.kt | 18 +++++++++ .../fleks/FamilyBagFunctionsTest.kt | 7 ++++ .../fleks/collection/EntityBagIteratorTest.kt | 38 +++++++++++++++++-- 6 files changed, 127 insertions(+), 4 deletions(-) diff --git a/src/commonMain/kotlin/com/github/quillraven/fleks/collection/EntityBagIterator.kt b/src/commonMain/kotlin/com/github/quillraven/fleks/collection/EntityBagIterator.kt index ec28a93..d6285c9 100644 --- a/src/commonMain/kotlin/com/github/quillraven/fleks/collection/EntityBagIterator.kt +++ b/src/commonMain/kotlin/com/github/quillraven/fleks/collection/EntityBagIterator.kt @@ -63,4 +63,32 @@ data class EntityBagIterator(private val bag: EntityBag) { fun reset() { currentIdx = 0 } + + /** + * Moves the iterator to the first [Entity] matching the given [predicate] and returns it. + * If there is no such [Entity] then the iterator is reset and [Entity.NONE] is returned instead. + */ + fun goToFirst(predicate: (Entity) -> Boolean): Entity { + currentIdx = bag.indexOfFirst(predicate) + if (currentIdx == -1) { + reset() + return Entity.NONE + } + + return bag[currentIdx] + } + + /** + * Moves the iterator to the last [Entity] matching the given [predicate] and returns it. + * If there is no such [Entity] then the iterator is reset and [Entity.NONE] is returned instead. + */ + fun goToLast(predicate: (Entity) -> Boolean): Entity { + currentIdx = bag.indexOfLast(predicate) + if (currentIdx == -1) { + reset() + return Entity.NONE + } + + return bag[currentIdx] + } } diff --git a/src/commonMain/kotlin/com/github/quillraven/fleks/collection/entityBag.kt b/src/commonMain/kotlin/com/github/quillraven/fleks/collection/entityBag.kt index 8f3f878..45bb0a3 100644 --- a/src/commonMain/kotlin/com/github/quillraven/fleks/collection/entityBag.kt +++ b/src/commonMain/kotlin/com/github/quillraven/fleks/collection/entityBag.kt @@ -118,6 +118,18 @@ interface EntityBag { */ fun count(predicate: (Entity) -> Boolean): Int + /** + * Returns the index of the first [Entity] matching the given [predicate], + * or -1 if the bag does not contain such an [Entity]. + */ + fun indexOfFirst(predicate: (Entity) -> Boolean): Int + + /** + * Returns the index of the last [Entity] matching the given [predicate], + * or -1 if the bag does not contain such an [Entity]. + */ + fun indexOfLast(predicate: (Entity) -> Boolean): Int + /** * Returns a [List] containing only [entities][Entity] matching the given [predicate]. */ diff --git a/src/commonMain/kotlin/com/github/quillraven/fleks/collection/mutableEntityBag.kt b/src/commonMain/kotlin/com/github/quillraven/fleks/collection/mutableEntityBag.kt index 7ce4f96..4f2e6ee 100644 --- a/src/commonMain/kotlin/com/github/quillraven/fleks/collection/mutableEntityBag.kt +++ b/src/commonMain/kotlin/com/github/quillraven/fleks/collection/mutableEntityBag.kt @@ -295,6 +295,34 @@ class MutableEntityBag( return result } + /** + * Returns the index of the first [Entity] matching the given [predicate], + * or -1 if the bag does not contain such an [Entity]. + */ + override inline fun indexOfFirst(predicate: (Entity) -> Boolean): Int { + for (i in 0 until size) { + val entity = values[i] + if (predicate(entity)) { + return i + } + } + return -1 + } + + /** + * Returns the index of the last [Entity] matching the given [predicate], + * or -1 if the bag does not contain such an [Entity]. + */ + override inline fun indexOfLast(predicate: (Entity) -> Boolean): Int { + for (i in size - 1 downTo 0) { + val entity = values[i] + if (predicate(entity)) { + return i + } + } + return -1 + } + /** * Returns a [List] containing only [entities][Entity] matching the given [predicate]. */ diff --git a/src/commonMain/kotlin/com/github/quillraven/fleks/family.kt b/src/commonMain/kotlin/com/github/quillraven/fleks/family.kt index 737b2c2..99419a9 100644 --- a/src/commonMain/kotlin/com/github/quillraven/fleks/family.kt +++ b/src/commonMain/kotlin/com/github/quillraven/fleks/family.kt @@ -259,6 +259,24 @@ data class Family( */ fun count(predicate: (Entity) -> Boolean): Int = mutableEntities.count(predicate) + /** + * Returns the index of the first [Entity] matching the given [predicate], + * or -1 if the family does not contain such an [Entity]. + */ + fun indexOfFirst(predicate: (Entity) -> Boolean): Int = mutableEntities.indexOfFirst(predicate) + + /** + * Returns the index of the last [Entity] matching the given [predicate], + * or -1 if the family does not contain such an [Entity]. + */ + fun indexOfLast(predicate: (Entity) -> Boolean): Int = mutableEntities.indexOfLast(predicate) + + /** + * Creates an [EntityBagIterator] for the family. If the family gets updated + * during iteration then [EntityBagIterator.reset] must be called to guarantee correct iterator behavior. + */ + fun iterator(): EntityBagIterator = EntityBagIterator(mutableEntities) + /** * Returns a [Map] containing key-value pairs provided by the [transform] function applied to * each [entity][Entity] of the family. diff --git a/src/commonTest/kotlin/com/github/quillraven/fleks/FamilyBagFunctionsTest.kt b/src/commonTest/kotlin/com/github/quillraven/fleks/FamilyBagFunctionsTest.kt index 1f2ce4d..97bacf9 100644 --- a/src/commonTest/kotlin/com/github/quillraven/fleks/FamilyBagFunctionsTest.kt +++ b/src/commonTest/kotlin/com/github/quillraven/fleks/FamilyBagFunctionsTest.kt @@ -449,4 +449,11 @@ class FamilyBagFunctionsTest { assertEquals(entityBagOf(testEntity1, testEntity2), testFamily.take(2)) assertEquals(entityBagOf(testEntity1, testEntity2), testFamily.take(3)) } + + @Test + fun testIndexOf() { + assertEquals(0, testFamily.indexOfFirst { it.id == 0 }) + assertEquals(1, testFamily.indexOfLast { it.id == 1 }) + assertEquals(-1, testFamily.indexOfLast { it.id == 99 }) + } } diff --git a/src/commonTest/kotlin/com/github/quillraven/fleks/collection/EntityBagIteratorTest.kt b/src/commonTest/kotlin/com/github/quillraven/fleks/collection/EntityBagIteratorTest.kt index 67e6b84..8db2071 100644 --- a/src/commonTest/kotlin/com/github/quillraven/fleks/collection/EntityBagIteratorTest.kt +++ b/src/commonTest/kotlin/com/github/quillraven/fleks/collection/EntityBagIteratorTest.kt @@ -25,9 +25,9 @@ class EntityBagIteratorTest { @Test fun `test iterator on bag with single entity`() { val entity = Entity(1, 0u) - val emptyBag = mutableEntityBagOf(entity) + val testBag = mutableEntityBagOf(entity) - val iterator = emptyBag.iterator() + val iterator = testBag.iterator() assertTrue(iterator.hasNext()) assertFalse(iterator.hasPrevious()) @@ -49,9 +49,9 @@ class EntityBagIteratorTest { fun `test iterator on bag with two entities`() { val entity1 = Entity(1, 0u) val entity2 = Entity(2, 0u) - val emptyBag = mutableEntityBagOf(entity1, entity2) + val testBag = mutableEntityBagOf(entity1, entity2) - val iterator = emptyBag.iterator() + val iterator = testBag.iterator() assertTrue(iterator.hasNext()) assertFalse(iterator.hasPrevious()) @@ -72,4 +72,34 @@ class EntityBagIteratorTest { assertEquals(entity2, iterator.previous(loop = true)) } + @Test + fun `test goToFirst`() { + val entity1 = Entity(1, 0u) + val entity2 = Entity(2, 0u) + val entity3 = Entity(2, 1u) // same id on purpose to check that goTo stops at first entity + val testBag = mutableEntityBagOf(entity1, entity2, entity3) + + val iterator = testBag.iterator() + + assertEquals(entity2, iterator.goToFirst { e -> e.id == 2 }) + assertEquals(entity1, iterator.previous()) + assertEquals(Entity.NONE, iterator.goToFirst { e -> e.id == 3 }) + assertEquals(entity1, iterator.next()) + } + + @Test + fun `test goToLast`() { + val entity1 = Entity(1, 0u) + val entity2 = Entity(2, 0u) + val entity3 = Entity(2, 1u) // same id on purpose to check that goTo stops at first entity + val testBag = mutableEntityBagOf(entity1, entity2, entity3) + + val iterator = testBag.iterator() + + assertEquals(entity3, iterator.goToLast { e -> e.id == 2 }) + assertEquals(entity2, iterator.previous()) + assertEquals(Entity.NONE, iterator.goToLast { e -> e.id == 3 }) + assertEquals(entity1, iterator.next()) + } + }