Skip to content

Commit

Permalink
Release/1.0-RC3 (#33)
Browse files Browse the repository at this point in the history
* (#29) new addOrUpdate function (#31)

* (#30) new forEach function (#32)

* documentation and version upgrade for 1.0-RC3
  • Loading branch information
Quillraven committed Jan 30, 2022
1 parent bcf766e commit c395c5b
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 6 deletions.
33 changes: 28 additions & 5 deletions ReadMe.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -57,20 +57,20 @@ To use Fleks add it as a dependency to your project:
<dependency>
<groupId>io.github.quillraven.fleks</groupId>
<artifactId>Fleks</artifactId>
<version>1.0-RC2</version>
<version>1.0-RC3</version>
</dependency>
```

#### 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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
14 changes: 14 additions & 0 deletions src/main/kotlin/com/github/quillraven/fleks/component.kt
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,20 @@ class ComponentMapper<T>(
}
}

/**
* 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].
Expand Down
27 changes: 27 additions & 0 deletions src/main/kotlin/com/github/quillraven/fleks/entity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 <reified T : Any> ComponentMapper<T>.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 <reified T : Any> ComponentMapper<T>.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.
*/
Expand Down Expand Up @@ -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].
*/
Expand Down
7 changes: 7 additions & 0 deletions src/main/kotlin/com/github/quillraven/fleks/world.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
30 changes: 30 additions & 0 deletions src/test/kotlin/com/github/quillraven/fleks/ComponentTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<ComponentTestComponent>()
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<ComponentTestComponent>()
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) }
)
}
}
21 changes: 21 additions & 0 deletions src/test/kotlin/com/github/quillraven/fleks/EntityTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<EntityTestComponent>()
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()
Expand Down
15 changes: 15 additions & 0 deletions src/test/kotlin/com/github/quillraven/fleks/WorldTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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<Entity>()

w.forEach { actualEntities.add(it) }

assertContentEquals(listOf(e1, e3), actualEntities)
}
}

0 comments on commit c395c5b

Please sign in to comment.