diff --git a/ReadMe.md b/ReadMe.md
index 2a217cb..a044daf 100644
--- a/ReadMe.md
+++ b/ReadMe.md
@@ -47,7 +47,7 @@ then use [Artemis-odb](https://github.com/junkdog/artemis-odb) or [Ashley](https
## Current Status
-Release Candidate version 1.0-RC2 is available on maven central since 19-Jan-2022. Please feel free to contribute to the
+Release Candidate version 1.0-RC3 is available on maven central since 30-Jan-2022. Please feel free to contribute to the
Discussions or Issues. Help is always appreciated.
To use Fleks add it as a dependency to your project:
@@ -57,20 +57,20 @@ To use Fleks add it as a dependency to your project:
io.github.quillraven.fleks
Fleks
- 1.0-RC2
+ 1.0-RC3
```
#### Gradle (Groovy)
```kotlin
-implementation 'io.github.quillraven.fleks:Fleks:1.0-RC2'
+implementation 'io.github.quillraven.fleks:Fleks:1.0-RC3'
```
#### Gradle (Kotlin)
```kotlin
-implementation("io.github.quillraven.fleks:Fleks:1.0-RC2")
+implementation("io.github.quillraven.fleks:Fleks:1.0-RC3")
```
## Current API and usage
@@ -233,7 +233,11 @@ class AnimationSystem(
If you need to modify the component configuration of an entity then this can be done via the `configureEntity` function
of an `IteratingSystem`. The purpose of this function is performance reasons to trigger internal calculations
-of Fleks only once instead of each time a component gets added or removed.
+of Fleks only once instead of each time a component gets added or removed. Inside `configureEntity` you get access to
+three special `ComponentMapper` functions:
+- `add`: adds a component to an entity
+- `remove`: removes a component from an entity
+- `addOrUpdate`: adds a component only if it does not exist yet. Otherwise, it just updates the existing component
Let's see how a system can look like that adds a `DeadComponent` to an entity and removes a `LifeComponent` when its
hitpoints are <= 0:
@@ -367,6 +371,25 @@ fun main() {
}
```
+If you ever need to iterate over entities outside a system then this is also possible but please note that
+systems are always the preferred way of iteration in an entity component system.
+The world's `forEach` function allows you to iterate over all active entities:
+
+```Kotlin
+ fun main() {
+ val world = World {}
+ val e1 = world.entity()
+ val e2 = world.entity()
+ val e3 = world.entity()
+ world.remove(e2)
+
+ // this will iterate over entities e1 and e3
+ world.forEach { entity ->
+ // do something with the entity
+ }
+}
+```
+
### Entity and Components
We now know how to create a world and add systems to it, but we don't know how to add entities to our world. This can be
diff --git a/build.gradle.kts b/build.gradle.kts
index a23c8a9..bca8f0b 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -7,7 +7,7 @@ plugins {
}
group = "io.github.quillraven.fleks"
-version = "1.0-RC2"
+version = "1.0-RC3"
java.sourceCompatibility = JavaVersion.VERSION_1_8
val bmSourceSetName = "benchmarks"
diff --git a/src/main/kotlin/com/github/quillraven/fleks/component.kt b/src/main/kotlin/com/github/quillraven/fleks/component.kt
index 0cab4ea..3abfa48 100644
--- a/src/main/kotlin/com/github/quillraven/fleks/component.kt
+++ b/src/main/kotlin/com/github/quillraven/fleks/component.kt
@@ -60,6 +60,20 @@ class ComponentMapper(
}
}
+ /**
+ * Creates a new component if the [entity] does not have it yet. Otherwise, updates the existing component.
+ * Applies the [configuration] in both cases and returns the component.
+ * Notifies any registered [ComponentListener] if a new component is created.
+ */
+ @PublishedApi
+ internal inline fun addOrUpdateInternal(entity: Entity, configuration: T.() -> Unit = {}): T {
+ return if (entity in this) {
+ this[entity].apply(configuration)
+ } else {
+ addInternal(entity, configuration)
+ }
+ }
+
/**
* Removes a component of the specific type from the given [entity].
* Notifies any registered [ComponentListener].
diff --git a/src/main/kotlin/com/github/quillraven/fleks/entity.kt b/src/main/kotlin/com/github/quillraven/fleks/entity.kt
index b33cb88..c0da713 100644
--- a/src/main/kotlin/com/github/quillraven/fleks/entity.kt
+++ b/src/main/kotlin/com/github/quillraven/fleks/entity.kt
@@ -47,6 +47,7 @@ class EntityCreateCfg(
/**
* Adds and returns a component of the given type to the [entity] and
* applies the [configuration] to the component.
+ * Notifies any registered [ComponentListener].
*
* @throws [FleksMissingNoArgsComponentConstructorException] if the component of the given type
* does not have a no argument constructor.
@@ -72,14 +73,27 @@ class EntityUpdateCfg {
* Adds and returns a component of the given type to the [entity] and applies the [configuration] to that component.
* If the [entity] already has a component of the given type then no new component is created and instead
* the existing one will be updated.
+ * Notifies any registered [ComponentListener].
*/
inline fun ComponentMapper.add(entity: Entity, configuration: T.() -> Unit = {}): T {
cmpMask.set(this.id)
return this.addInternal(entity, configuration)
}
+ /**
+ * Adds a new component of the given type to the [entity] if it does not have it yet.
+ * Otherwise, updates the already existing component.
+ * Applies the [configuration] in both cases and returns the component.
+ * Notifies any registered [ComponentListener] if a new component is created.
+ */
+ inline fun ComponentMapper.addOrUpdate(entity: Entity, configuration: T.() -> Unit = {}): T {
+ cmpMask.set(this.id)
+ return this.addOrUpdateInternal(entity, configuration)
+ }
+
/**
* Removes a component of the given type from the [entity].
+ * Notifies any registered [ComponentListener].
*
* @throws [ArrayIndexOutOfBoundsException] if the id of the [entity] exceeds the mapper's capacity.
*/
@@ -240,6 +254,19 @@ class EntityService(
}
}
+ /**
+ * Performs the given [action] on each active [entity][Entity].
+ */
+ fun forEach(action: (Entity) -> Unit) {
+ for (id in 0 until nextId) {
+ val entity = Entity(id)
+ if (removedEntities[entity.id]) {
+ continue
+ }
+ entity.run(action)
+ }
+ }
+
/**
* Clears the [delayRemoval] flag and removes [entities][Entity] which are part of the [delayedEntities].
*/
diff --git a/src/main/kotlin/com/github/quillraven/fleks/world.kt b/src/main/kotlin/com/github/quillraven/fleks/world.kt
index 6f019a8..6e13e44 100644
--- a/src/main/kotlin/com/github/quillraven/fleks/world.kt
+++ b/src/main/kotlin/com/github/quillraven/fleks/world.kt
@@ -177,6 +177,13 @@ class World(
entityService.removeAll()
}
+ /**
+ * Performs the given [action] on each active [entity][Entity].
+ */
+ fun forEach(action: (Entity) -> Unit) {
+ entityService.forEach(action)
+ }
+
/**
* Returns the specified [system][IntervalSystem] of the world.
*
diff --git a/src/test/kotlin/com/github/quillraven/fleks/ComponentTest.kt b/src/test/kotlin/com/github/quillraven/fleks/ComponentTest.kt
index 283f865..8c1af2c 100644
--- a/src/test/kotlin/com/github/quillraven/fleks/ComponentTest.kt
+++ b/src/test/kotlin/com/github/quillraven/fleks/ComponentTest.kt
@@ -229,4 +229,34 @@ internal class ComponentTest {
{ assertEquals("add", listener.lastCall) }
)
}
+
+ @Test
+ fun `add component if it does not exist yet`() {
+ val cmpService = ComponentService()
+ val mapper = cmpService.mapper()
+ val entity = Entity(1)
+
+ val cmp = mapper.addOrUpdateInternal(entity) { x++ }
+
+ assertAll(
+ { assertTrue(entity in mapper) },
+ { assertEquals(1, cmp.x) }
+ )
+ }
+
+ @Test
+ fun `update component if it already exists`() {
+ val cmpService = ComponentService()
+ val mapper = cmpService.mapper()
+ val entity = Entity(1)
+ val expectedCmp = mapper.addOrUpdateInternal(entity) { x++ }
+
+ val actualCmp = mapper.addOrUpdateInternal(entity) { x++ }
+
+ assertAll(
+ { assertTrue(entity in mapper) },
+ { assertEquals(expectedCmp, actualCmp) },
+ { assertEquals(2, actualCmp.x) }
+ )
+ }
}
diff --git a/src/test/kotlin/com/github/quillraven/fleks/EntityTest.kt b/src/test/kotlin/com/github/quillraven/fleks/EntityTest.kt
index 4a6993a..4ad687e 100644
--- a/src/test/kotlin/com/github/quillraven/fleks/EntityTest.kt
+++ b/src/test/kotlin/com/github/quillraven/fleks/EntityTest.kt
@@ -119,6 +119,27 @@ internal class EntityTest {
)
}
+ @Test
+ fun `update component of entity if it already exists with custom listener`() {
+ val cmpService = ComponentService()
+ val entityService = EntityService(32, cmpService)
+ val listener = EntityTestListener()
+ val expectedEntity = entityService.create { }
+ val mapper = cmpService.mapper()
+ entityService.addEntityListener(listener)
+
+ entityService.configureEntity(expectedEntity) {
+ mapper.add(expectedEntity) { ++x }
+ mapper.addOrUpdate(expectedEntity) { x++ }
+ }
+
+ assertAll(
+ { assertTrue(expectedEntity in mapper) },
+ { assertEquals(2f, mapper[expectedEntity].x) },
+ { assertEquals(1, listener.numCalls) }
+ )
+ }
+
@Test
fun `remove entity with a component immediately with custom listener`() {
val cmpService = ComponentService()
diff --git a/src/test/kotlin/com/github/quillraven/fleks/WorldTest.kt b/src/test/kotlin/com/github/quillraven/fleks/WorldTest.kt
index 4e245e3..9d4ff28 100644
--- a/src/test/kotlin/com/github/quillraven/fleks/WorldTest.kt
+++ b/src/test/kotlin/com/github/quillraven/fleks/WorldTest.kt
@@ -4,6 +4,7 @@ import org.junit.jupiter.api.Assertions.assertAll
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
+import kotlin.test.assertContentEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
@@ -255,4 +256,18 @@ internal class WorldTest {
}
}
}
+
+ @Test
+ fun `iterate over all active entities`() {
+ val w = World {}
+ val e1 = w.entity()
+ val e2 = w.entity()
+ val e3 = w.entity()
+ w.remove(e2)
+ val actualEntities = mutableListOf()
+
+ w.forEach { actualEntities.add(it) }
+
+ assertContentEquals(listOf(e1, e3), actualEntities)
+ }
}