From ac151627526e5e0810beab9a377165684f85c212 Mon Sep 17 00:00:00 2001 From: MJ Date: Thu, 1 Jun 2017 21:35:16 +0200 Subject: [PATCH 01/15] Updated contributors list. #77 --- .github/CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CONTRIBUTORS.md b/.github/CONTRIBUTORS.md index 3f890b17..e5c3843e 100644 --- a/.github/CONTRIBUTORS.md +++ b/.github/CONTRIBUTORS.md @@ -10,6 +10,7 @@ Project contributors listed chronologically. * Contributed various utilities from [Ore Infinium](https://github.com/sreich/ore-infinium) project. * [@raincole](https://github.com/raincole) * Provided insightful review of the [`Async`](../async) module. + Contributed LibGDX [collections](../collections) utilities. * [@Jkly](https://github.com/Jkly) * Author of [gdx-box2d-kotlin](https://github.com/Jkly/gdx-box2d-kotlin), which inspired the `Box2D` **KTX** library. Provided insightful review of the [`Box2D`](../box2d) module. From 4733c217a139343a3790483f4f2ec9af376192e6 Mon Sep 17 00:00:00 2001 From: MJ Date: Thu, 1 Jun 2017 21:36:21 +0200 Subject: [PATCH 02/15] Prepared for next release. #77 --- CHANGELOG.md | 2 ++ version.txt | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2437d11e..567a4d73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +#### 1.9.6-SNAPSHOT + #### 1.9.6-b4 - **[FEATURE]** (`ktx-collections`) Added `map`, `filter` and `flatten` extension methods that return LibGDX collections. diff --git a/version.txt b/version.txt index 72636139..51c785cd 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.9.6-b4 +1.9.6-SNAPSHOT From cae90da600d5d6bde392bd7f2064aa523233dc25 Mon Sep 17 00:00:00 2001 From: MJ Date: Thu, 1 Jun 2017 21:42:57 +0200 Subject: [PATCH 03/15] Expanded Maven Central uploading section. #77 --- .github/CONTRIBUTING.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index fbc6d843..ebe7e9bc 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -54,7 +54,8 @@ Releasing a new KTX version: - Change `libVersion` settings in `version.txt`. KTX uses the same versioning schema as LibGDX (mimicking the LibGDX version that it was compiled against) with optional `-b#` or `-SNAPSHOT` suffixes depending on version status. - Run `gradle build install uploadArchives closeAndPromoteRepository` to push artifacts to both _Maven Local_ and -_Maven Central_. +_Maven Central_. Note that Maven plugin has its issues and you might need to run `gradle promoteRepository` after the +previous task sequence (if it fails on the `closeAndPromoteRepository` task). - Run `gradle distZip` to prepare archive with KTX sources, compiled binary and documentation. - Upload the archive to [releases](https://github.com/libktx/ktx/releases) section. Tag should match released version. Name of the release should match `KTX $libVersion`. Copy latest [changelog](../CHANGELOG.md) entries to release From f72f1995206048279cc7756fd280b9d0da43dee5 Mon Sep 17 00:00:00 2001 From: Jingkei Ly Date: Mon, 5 Jun 2017 22:43:17 +0100 Subject: [PATCH 04/15] utilities for Ashley entity component system --- CHANGELOG.md | 1 + ashley/README.md | 83 +++++++++++++++++ ashley/build.gradle | 32 +++++++ ashley/gradle.properties | 2 + .../src/main/kotlin/ktx/ashley/AshleyDsl.kt | 8 ++ .../kotlin/ktx/ashley/componentMappers.kt | 17 ++++ .../main/kotlin/ktx/ashley/engine/engines.kt | 52 +++++++++++ .../ktx/ashley/engine/pool/pooledEngines.kt | 89 ++++++++++++++++++ ashley/src/main/kotlin/ktx/ashley/entities.kt | 66 +++++++++++++ ashley/src/main/kotlin/ktx/ashley/families.kt | 61 ++++++++++++ .../kotlin/ktx/ashley/ComponentMappersSpec.kt | 17 ++++ .../test/kotlin/ktx/ashley/EntitiesSpec.kt | 62 +++++++++++++ .../test/kotlin/ktx/ashley/FamiliesSpec.kt | 67 +++++++++++++ .../src/test/kotlin/ktx/ashley/RigidBody.kt | 5 + ashley/src/test/kotlin/ktx/ashley/Texture.kt | 10 ++ .../src/test/kotlin/ktx/ashley/Transform.kt | 10 ++ .../kotlin/ktx/ashley/engine/EnginesSpec.kt | 76 +++++++++++++++ .../ashley/engine/pool/PooledEnginesSpec.kt | 93 +++++++++++++++++++ gradle.properties | 2 + settings.gradle | 1 + 20 files changed, 754 insertions(+) create mode 100644 ashley/README.md create mode 100644 ashley/build.gradle create mode 100644 ashley/gradle.properties create mode 100644 ashley/src/main/kotlin/ktx/ashley/AshleyDsl.kt create mode 100644 ashley/src/main/kotlin/ktx/ashley/componentMappers.kt create mode 100644 ashley/src/main/kotlin/ktx/ashley/engine/engines.kt create mode 100644 ashley/src/main/kotlin/ktx/ashley/engine/pool/pooledEngines.kt create mode 100644 ashley/src/main/kotlin/ktx/ashley/entities.kt create mode 100644 ashley/src/main/kotlin/ktx/ashley/families.kt create mode 100644 ashley/src/test/kotlin/ktx/ashley/ComponentMappersSpec.kt create mode 100644 ashley/src/test/kotlin/ktx/ashley/EntitiesSpec.kt create mode 100644 ashley/src/test/kotlin/ktx/ashley/FamiliesSpec.kt create mode 100644 ashley/src/test/kotlin/ktx/ashley/RigidBody.kt create mode 100644 ashley/src/test/kotlin/ktx/ashley/Texture.kt create mode 100644 ashley/src/test/kotlin/ktx/ashley/Transform.kt create mode 100644 ashley/src/test/kotlin/ktx/ashley/engine/EnginesSpec.kt create mode 100644 ashley/src/test/kotlin/ktx/ashley/engine/pool/PooledEnginesSpec.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 567a4d73..127112c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ #### 1.9.6-SNAPSHOT +- **[FEATURE]** (`ktx-ashley`) new **KTX** module with Ashley entity component system utilities: `ktx-ashley`. #### 1.9.6-b4 diff --git a/ashley/README.md b/ashley/README.md new file mode 100644 index 00000000..1e971b60 --- /dev/null +++ b/ashley/README.md @@ -0,0 +1,83 @@ +# KTX: `Ashley` entity component system utilities + +Utilities and type-safe builders for the [Ashley](https://github.com/libgdx/ashley) entity component system. + +### Why? +We can make using [Ashley](https://github.com/libgdx/ashley) with Kotlin more pleasant where Java Classes are +required for many method calls by providing helper methods using reified types. Additionally, creating Entities and +their respective Components can result in a lot of declarative-style code which is suited nicely to a readable type-safe +builder DSL. + +### Guide + +`ktx-ashley` provides extensions and utilities for using [Ashley](https://github.com/libgdx/ashley) with Kotlin: + +- `Engine.add` and `Engine.entity` extension methods provide type-safe building DSL for creating non-pooled Entities +- `PooledEngine.add` and `PooledEngine.entity` extension methods provide type-safe building DSL for creating pooled Entities +- `mapperFor` factory method for create `ComponentMapper` instance. +- `ktx.ashley.entities.*` accessors for Entity objects +- `ktx.ashley.families.*` for constructing Family builders with KClasses + +### Usage examples + +Creating a new pooled Entity (similar for non-pooled Entities): +```Kotlin +val engine = PooledEngine() + +val entity = engine.entity { + with() + with { + x = 1f + y = 1f + } +} +``` + +Creating multiple new entities: +```Kotlin +val engine = PooledEngine() + +fun setupEngine() = engine.add { + entity { + with { + x = 1f + y = 1f + } + } + entity { + with { + x = 2f + y = 2f + } + } +} +``` + +Creating a ComponentMapper: +```Kotlin +val transformMapper = mapperFor() +``` + +Getting a Component from an Entity: +```Kotlin +val component = entity[transformMapper] +``` + +Check a Component on an Entity: +```Kotlin +val component = entity.has(transformMapper) +``` + +Remove a Component from an Entity: +```Kotlin +fun removeTransform() = entity.remove() +``` + +Create component Family to match all Components and excluding any: +```Kotlin +var family = all(Texture::class, Transform::class).exclude(RigidBody::class) +``` + +#### Additional documentation + +- [Ashley](https://github.com/libgdx/ashley) diff --git a/ashley/build.gradle b/ashley/build.gradle new file mode 100644 index 00000000..87fd16b4 --- /dev/null +++ b/ashley/build.gradle @@ -0,0 +1,32 @@ +buildscript { + ext { + junitPlatformVersion = "1.0.0-M4" + } + dependencies { + classpath "org.junit.platform:junit-platform-gradle-plugin:$junitPlatformVersion" + } + repositories { + mavenCentral() + } +} + +dependencies { + provided "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" + provided "com.badlogicgames.ashley:ashley:$ashleyVersion" + + testCompile "org.jetbrains.spek:spek-api:$spekVersion" + testCompile 'org.assertj:assertj-core:3.8.0' + + testRuntime "org.junit.platform:junit-platform-launcher:$junitPlatformVersion" + testRuntime "org.jetbrains.spek:spek-junit-platform-engine:$spekVersion" +} + +apply plugin: 'org.junit.platform.gradle.plugin' + +junitPlatform { + filters { + engines { + include 'spek' + } + } +} \ No newline at end of file diff --git a/ashley/gradle.properties b/ashley/gradle.properties new file mode 100644 index 00000000..7c046421 --- /dev/null +++ b/ashley/gradle.properties @@ -0,0 +1,2 @@ +projectName=ktx-ashley +projectDesc=Ashley entity component system utilities for Kotlin LibGDX applications. diff --git a/ashley/src/main/kotlin/ktx/ashley/AshleyDsl.kt b/ashley/src/main/kotlin/ktx/ashley/AshleyDsl.kt new file mode 100644 index 00000000..e68d11a6 --- /dev/null +++ b/ashley/src/main/kotlin/ktx/ashley/AshleyDsl.kt @@ -0,0 +1,8 @@ +package ktx.ashley + +/** + * Marks KTX Ashley type-safe builders. + */ +@DslMarker +@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE) +annotation class AshleyDsl \ No newline at end of file diff --git a/ashley/src/main/kotlin/ktx/ashley/componentMappers.kt b/ashley/src/main/kotlin/ktx/ashley/componentMappers.kt new file mode 100644 index 00000000..cf883c63 --- /dev/null +++ b/ashley/src/main/kotlin/ktx/ashley/componentMappers.kt @@ -0,0 +1,17 @@ +package ktx.ashley + +import com.badlogic.ashley.core.Component +import com.badlogic.ashley.core.ComponentMapper + +/** + * Creates a [ComponentMapper] for the specified [Component] type. + * + * Provides `O(1)` retrieval of [Component]s for an [com.badlogic.ashley.core.Entity]. + * + * @param T the [Component] type to create a [ComponentMapper] for + * @return the [ComponentMapper] + * @see ComponentMapper + */ +inline fun mapperFor(): ComponentMapper { + return ComponentMapper.getFor(T::class.java) +} \ No newline at end of file diff --git a/ashley/src/main/kotlin/ktx/ashley/engine/engines.kt b/ashley/src/main/kotlin/ktx/ashley/engine/engines.kt new file mode 100644 index 00000000..87848af5 --- /dev/null +++ b/ashley/src/main/kotlin/ktx/ashley/engine/engines.kt @@ -0,0 +1,52 @@ +package ktx.ashley.engine + +import com.badlogic.ashley.core.Component +import com.badlogic.ashley.core.Engine +import com.badlogic.ashley.core.Entity +import ktx.ashley.AshleyDsl + +/** + * Creates an instance of the component [T] and adds it to this [Entity]. + * + * @param T the [Component] type to get or create + * @param configure inlined function with [T] as the receiver to allow additional configuration of the [Component] + * @return the created [Component] + */ +inline fun Entity.with(configure: (@AshleyDsl T).() -> Unit): T { + return with().also(configure) +} + +/** + * Creates a pooled instance of the component [T] and adds it to this [Entity]. + * + * @param T the [Component] type to get or create + * @return the pooled [Component] + */ +inline fun Entity.with(): T { + val component = T::class.java.newInstance() + add(component) + return component +} + +/** + * Builder function for [Engine]. + * + * @param configure inlined function with *this* [Engine] as the receiver to allow further configuration. + */ +inline fun Engine.add(configure: (@AshleyDsl Engine).() -> Unit) { + configure(this) +} + +/** + * Create and add an [Entity] to the [Engine]. + * + * @param configure inlined function with the pooled [Entity] as the receiver to allow further configuration of + * the [Entity]. The [Entity] holds the [Entity] created and the [Engine] that created it. + * @return the created [Entity] + */ +inline fun Engine.entity(configure: Entity.() -> Unit): Entity { + val entity = Entity().also(configure) + addEntity(entity) + return entity +} + diff --git a/ashley/src/main/kotlin/ktx/ashley/engine/pool/pooledEngines.kt b/ashley/src/main/kotlin/ktx/ashley/engine/pool/pooledEngines.kt new file mode 100644 index 00000000..a2d70d90 --- /dev/null +++ b/ashley/src/main/kotlin/ktx/ashley/engine/pool/pooledEngines.kt @@ -0,0 +1,89 @@ +package ktx.ashley.engine.pool + +import com.badlogic.ashley.core.Component +import com.badlogic.ashley.core.Entity +import com.badlogic.ashley.core.PooledEngine +import ktx.ashley.AshleyDsl + +/** + * Get or create a [Component] from the [PooledEngine]. + * + * @param T the [Component] to get or create + * @param configure inlined function with [T] as the receiver to allow further configuration. + * @return the pooled [Component] + * @see PooledEngine.createComponent + */ +inline fun PooledEngine.create(configure: T.() -> Unit): T { + return create().also(configure) +} + +/** + * Get or create a [Component] from the [PooledEngine]. + * + * @param T the [Component] to get or create + * @return the pooled [Component] + * @see PooledEngine.createComponent + */ +inline fun PooledEngine.create(): T = createComponent(T::class.java) + +/** + * An [Entity] created by a [PooledEngine]. + * + * Provides methods for adding [Component]s to the [PooledEngine] and the [Entity]. + * + * @property engine the [PooledEngine] to create [Component]s + * @property entity the [Entity] to add [Component]s to + * @constructor creates a [PooledEntity] + */ +@AshleyDsl +class PooledEntity(val engine: PooledEngine, val entity: Entity) { + + /** + * Get or creates a pooled instance of the component [T] and adds it to this [entity][PooledEntity]. + * + * @param T the [Component] type to get or create + * @param configure inlined function with [T] as the receiver to allow additional configuration of the [Component] + * @return the pooled [Component] + * @see [create] + */ + inline fun with(configure: (@AshleyDsl T).() -> Unit): T { + return with().also(configure) + } + + /** + * Get or creates a pooled instance of the component [T] and adds it to this [entity][PooledEntity]. + * + * @param T the [Component] type to get or create + * @return the pooled [Component] + * @see [create] + */ + inline fun with(): T { + val component = engine.create() + entity.add(component) + return component + } +} + +/** + * Builder function for [PooledEngine]. + * + * @param configure inlined function with *this* [PooledEngine] as the receiver to allow further configuration. + */ +inline fun PooledEngine.add(configure: (@AshleyDsl PooledEngine).() -> Unit) { + configure(this) +} + +/** + * Create and add a pooled [Entity] to the [PooledEngine]. + * + * @param configure inlined function with the pooled [PooledEntity] as the receiver to allow further configuration of + * the [Entity]. The [PooledEntity] holds the [Entity] created and the [PooledEngine] that created it. + * @return the pooled [Entity] + */ +inline fun PooledEngine.entity(configure: PooledEntity.() -> Unit): Entity { + val entity = createEntity() + configure(PooledEntity(this, entity)) + addEntity(entity) + return entity +} + diff --git a/ashley/src/main/kotlin/ktx/ashley/entities.kt b/ashley/src/main/kotlin/ktx/ashley/entities.kt new file mode 100644 index 00000000..88e74373 --- /dev/null +++ b/ashley/src/main/kotlin/ktx/ashley/entities.kt @@ -0,0 +1,66 @@ +package ktx.ashley + +import com.badlogic.ashley.core.Component +import com.badlogic.ashley.core.ComponentMapper +import com.badlogic.ashley.core.Entity + +/** + * Gets the specified [Component] from the [Entity]. + * + * Note that this function provides `O(logn)` performance for [Component] retrieval. It is recommended that retrieving + * a [Component] is done using [get(ComponentMapper)]. + * + * @see ComponentMapper + * @see [get(ComponentMapper)] + * @param T the [Component] type to search for + * @return the specified [Component]. Otherwise `null` if the [Entity] does not have it. + */ +inline fun Entity.get(): T? { + return getComponent(T::class.java) +} + +/** + * Gets the specified [Component] from the [Entity] with a [ComponentMapper]. + * + * @see ComponentMapper + * @see mapperFor + * @param T the [Component] type to get + * @param mapper the [ComponentMapper] to retrieve the [Component] with + * @return the specified [Component]. Otherwise `null` if the [Entity] does not have it. + */ +operator fun Entity.get(mapper: ComponentMapper): T? { + return mapper.get(this) +} + +/** + * Whether the [Entity] has the specified [Component]. + * + * @see ComponentMapper + * @param T the [Component] type to inspect + * @param mapper the [ComponentMapper] to check the [Component] with + * @return `true` if the [Entity] has the specified component, and `false` otherwise + */ +fun Entity.has(mapper: ComponentMapper): Boolean { + return mapper.has(this) +} + +/** + * Whether the [Entity] does not have the specified [Component]. + * + * @see ComponentMapper + * @param T the [Component] type to inspect + * @param mapper the [ComponentMapper] to check the [Component] with + * @return `true` if the [Entity] does not have the specified component, and `false` otherwise + */ +fun Entity.hasNot(mapper: ComponentMapper): Boolean { + return !has(mapper) +} + +/** + * Removes the specified [Component] from the [Entity]. + * + * @param T the [Component] type to remove + */ +inline fun Entity.remove() { + remove(T::class.java) +} \ No newline at end of file diff --git a/ashley/src/main/kotlin/ktx/ashley/families.kt b/ashley/src/main/kotlin/ktx/ashley/families.kt new file mode 100644 index 00000000..75f00821 --- /dev/null +++ b/ashley/src/main/kotlin/ktx/ashley/families.kt @@ -0,0 +1,61 @@ +package ktx.ashley + +import com.badlogic.ashley.core.Component +import com.badlogic.ashley.core.Family +import com.badlogic.ashley.core.Family.Builder +import kotlin.reflect.KClass + +/** + * @param components matches [entities][com.badlogic.ashley.core.Entity] with at least one of the specified components. + * @return a new [Builder] for a [Family] + */ +fun one(vararg components: KClass): Builder { + return Family.one(*toJavaClassArray(components) ) +} + +/** + * @param components matches [entities][com.badlogic.ashley.core.Entity] with all of the specified components. + * @return a new [Builder] for a [Family] + */ +fun all(vararg components: KClass): Builder { + return Family.all(*toJavaClassArray(components) ) +} + +/** + * @param components does not match [entities][com.badlogic.ashley.core.Entity] with any of the specified components. + * @return a new [Builder] for a [Family] + */ +fun exclude(vararg components: KClass): Builder { + return Family.exclude(*toJavaClassArray(components) ) +} + +/** + * @receiver the [Builder] for creating a [Family] + * @param components matches [entities][com.badlogic.ashley.core.Entity] with at least one of the specified components. + * @return the received [Builder] for the [Family] + */ +fun Builder.one(vararg components: KClass): Builder { + return one(*toJavaClassArray(components) ) +} + +/** + * @receiver the [Builder] for creating a [Family] + * @param components matches [entities][com.badlogic.ashley.core.Entity] with all of the specified components. + * @return the received [Builder] for the [Family] + */ +fun Builder.all(vararg components: KClass): Builder { + return all(*toJavaClassArray(components) ) +} + +/** + * @receiver the [Builder] for creating a [Family] + * @param components does not match [entities][com.badlogic.ashley.core.Entity] with any of the specified components. + * @return the received [Builder] for the [Family] + */ +fun Builder.exclude(vararg components: KClass): Builder { + return exclude(*toJavaClassArray(components) ) +} + +private fun toJavaClassArray(components: Array>): Array> { + return components.map { c -> c.java }.toTypedArray() +} diff --git a/ashley/src/test/kotlin/ktx/ashley/ComponentMappersSpec.kt b/ashley/src/test/kotlin/ktx/ashley/ComponentMappersSpec.kt new file mode 100644 index 00000000..d9338983 --- /dev/null +++ b/ashley/src/test/kotlin/ktx/ashley/ComponentMappersSpec.kt @@ -0,0 +1,17 @@ +package ktx.ashley + +import com.badlogic.ashley.core.Entity +import org.assertj.core.api.Assertions.assertThat +import org.jetbrains.spek.api.Spek +import org.jetbrains.spek.api.dsl.describe +import org.jetbrains.spek.api.dsl.it + +object ComponentMappersSpec: Spek({ + describe("utilities for component mappers") { + val entity = Entity() + it("should return a component mapper via a reified type") { + val mapper = mapperFor() + assertThat(mapper.has(entity)).isFalse() + } + } +}) \ No newline at end of file diff --git a/ashley/src/test/kotlin/ktx/ashley/EntitiesSpec.kt b/ashley/src/test/kotlin/ktx/ashley/EntitiesSpec.kt new file mode 100644 index 00000000..21b46a54 --- /dev/null +++ b/ashley/src/test/kotlin/ktx/ashley/EntitiesSpec.kt @@ -0,0 +1,62 @@ +package ktx.ashley + +import com.badlogic.ashley.core.Entity +import org.assertj.core.api.Assertions.* +import org.jetbrains.spek.api.Spek +import org.jetbrains.spek.api.dsl.describe +import org.jetbrains.spek.api.dsl.it + +object EntitiesSpec: Spek({ + describe("utilities for entities") { + val transform by memoized { + Transform() + } + val entity by memoized { + Entity().apply { + add(transform) + } + } + describe("get operator") { + it("should get component with component mapper for better performance") { + assertThat(entity[Transform.mapper]).isSameAs(transform) + } + it("should return null if there is no component") { + assertThat(entity[Texture.mapper]).isNull() + } + } + + describe("get component function") { + it("should get component by reified type") { + assertThat(entity.get()).isSameAs(transform) + } + it("should return null if there is no component") { + assertThat(entity.get()).isNull() + } + } + + describe("remove component function") { + it("should remove a component by reified type") { + entity.remove() + assertThat(entity.getComponent(Transform::class.java)).isNull() + } + } + + describe("has component function") { + it("should return true if exists") { + assertThat(entity.has(Transform.mapper)).isTrue() + } + it("should return false if does not exists") { + assertThat(entity.has(Texture.mapper)).isFalse() + } + } + + describe("has not component function") { + it("should return false if exists") { + assertThat(entity.hasNot(Transform.mapper)).isFalse() + } + it("should return true if does not exists") { + assertThat(entity.hasNot(Texture.mapper)).isTrue() + } + } + } +}) \ No newline at end of file diff --git a/ashley/src/test/kotlin/ktx/ashley/FamiliesSpec.kt b/ashley/src/test/kotlin/ktx/ashley/FamiliesSpec.kt new file mode 100644 index 00000000..b23d49de --- /dev/null +++ b/ashley/src/test/kotlin/ktx/ashley/FamiliesSpec.kt @@ -0,0 +1,67 @@ +package ktx.ashley + +import com.badlogic.ashley.core.Entity +import org.assertj.core.api.Assertions.assertThat +import org.jetbrains.spek.api.Spek +import org.jetbrains.spek.api.dsl.describe +import org.jetbrains.spek.api.dsl.it + +object FamiliesSpec: Spek({ + describe("utilities for component families") { + val textureEntity = Entity().apply { + add(Texture()) + } + + val rigidBodyEntity = Entity().apply { + add(RigidBody()) + } + + val textureAndTransformEntity = Entity().apply { + add(Texture()) + add(Transform()) + } + + val allComponentsEntity = Entity().apply { + add(Texture()) + add(Transform()) + add(RigidBody()) + } + + it("should create a family that matches one of component") { + val family = one(Texture::class, Transform::class).get() + assertThat(family.matches(textureEntity)).isTrue() + assertThat(family.matches(rigidBodyEntity)).isFalse() + } + + it("should create a family that matches all components") { + val family = all(Texture::class, Transform::class).get() + assertThat(family.matches(textureEntity)).isFalse() + assertThat(family.matches(textureAndTransformEntity)).isTrue() + } + + it("should create a family that matches any excluded components") { + assertThat(exclude(Transform::class).get().matches(textureEntity)).isTrue() + assertThat(exclude(Texture::class).get().matches(textureEntity)).isFalse() + } + + describe("composite families") { + it("should build a family chained with matching any of one component") { + val family = exclude(Transform::class).one(Texture::class, RigidBody::class) + assertThat(family.get().matches(textureEntity)).isTrue() + assertThat(family.get().matches(textureAndTransformEntity)).isFalse() + } + + it("should build a family chained with matching all components") { + val family = exclude(RigidBody::class).all(Texture::class, Transform::class) + assertThat(family.get().matches(allComponentsEntity)).isFalse() + assertThat(family.get().matches(textureAndTransformEntity)).isTrue() + } + + it("should build a family chained with excluding components") { + val family = one(RigidBody::class).exclude(Texture::class, Transform::class) + assertThat(family.get().matches(allComponentsEntity)).isFalse() + assertThat(family.get().matches(rigidBodyEntity)).isTrue() + } + } + } +}) \ No newline at end of file diff --git a/ashley/src/test/kotlin/ktx/ashley/RigidBody.kt b/ashley/src/test/kotlin/ktx/ashley/RigidBody.kt new file mode 100644 index 00000000..884c95b4 --- /dev/null +++ b/ashley/src/test/kotlin/ktx/ashley/RigidBody.kt @@ -0,0 +1,5 @@ +package ktx.ashley + +import com.badlogic.ashley.core.Component + +class RigidBody: Component \ No newline at end of file diff --git a/ashley/src/test/kotlin/ktx/ashley/Texture.kt b/ashley/src/test/kotlin/ktx/ashley/Texture.kt new file mode 100644 index 00000000..f6132ab8 --- /dev/null +++ b/ashley/src/test/kotlin/ktx/ashley/Texture.kt @@ -0,0 +1,10 @@ +package ktx.ashley + +import com.badlogic.ashley.core.Component +import com.badlogic.ashley.core.ComponentMapper + +class Texture: Component { + companion object { + val mapper = ComponentMapper.getFor(Texture::class.java)!! + } +} \ No newline at end of file diff --git a/ashley/src/test/kotlin/ktx/ashley/Transform.kt b/ashley/src/test/kotlin/ktx/ashley/Transform.kt new file mode 100644 index 00000000..ff050f62 --- /dev/null +++ b/ashley/src/test/kotlin/ktx/ashley/Transform.kt @@ -0,0 +1,10 @@ +package ktx.ashley + +import com.badlogic.ashley.core.Component +import com.badlogic.ashley.core.ComponentMapper + +class Transform(var x:Float = 0f, var y: Float = 0f) : Component { + companion object { + val mapper = ComponentMapper.getFor(Transform::class.java)!! + } +} \ No newline at end of file diff --git a/ashley/src/test/kotlin/ktx/ashley/engine/EnginesSpec.kt b/ashley/src/test/kotlin/ktx/ashley/engine/EnginesSpec.kt new file mode 100644 index 00000000..a6631f58 --- /dev/null +++ b/ashley/src/test/kotlin/ktx/ashley/engine/EnginesSpec.kt @@ -0,0 +1,76 @@ +package ktx.ashley.engine + +import com.badlogic.ashley.core.Engine +import ktx.ashley.Texture +import ktx.ashley.Transform +import ktx.ashley.all +import ktx.ashley.one +import org.assertj.core.api.Assertions.assertThat +import org.jetbrains.spek.api.Spek +import org.jetbrains.spek.api.dsl.describe +import org.jetbrains.spek.api.dsl.it + +object EnginesSpec : Spek({ + describe("utilities for engines") { + val engine by memoized { + Engine() + } + describe("multiple entity creation DSL") { + it("should add an entity") { + engine.add { + entity {} + } + assertThat(engine.entities.size()).isEqualTo(1) + } + + it("should add multiple entities") { + engine.add { + entity {} + entity {} + } + assertThat(engine.entities.size()).isEqualTo(2) + } + } + + describe("single entity creation DSL") { + it("should add an entity and return it") { + val entity = engine.entity {} + assertThat(engine.entities.size()).isEqualTo(1) + assertThat(engine.entities[0]).isEqualTo(entity) + } + it("should add an entity with a component with default configuration") { + engine.entity { + with() + } + val transformEntities = engine.getEntitiesFor(one(Transform::class).get()) + val component = transformEntities.single().getComponent(Transform::class.java) + assertThat(component.x).isEqualTo(0f) + assertThat(component.y).isEqualTo(0f) + } + it("should add an entity with a component with configuration") { + engine.entity { + with { + x = 1f + y = 2f + } + } + val transformEntities = engine.getEntitiesFor(one(Transform::class).get()) + val component = transformEntities.single().getComponent(Transform::class.java) + assertThat(component.x).isEqualTo(1f) + assertThat(component.y).isEqualTo(2f) + } + it("should add multiple different components") { + engine.entity { + with() + with() + } + val transformEntities = engine.getEntitiesFor(all(Transform::class, Texture::class).get()) + assertThat(transformEntities.size()).isEqualTo(1) + + val entity = transformEntities.single() + assertThat(entity.getComponent(Transform::class.java)).isNotNull() + assertThat(entity.getComponent(Texture::class.java)).isNotNull() + } + } + } +}) diff --git a/ashley/src/test/kotlin/ktx/ashley/engine/pool/PooledEnginesSpec.kt b/ashley/src/test/kotlin/ktx/ashley/engine/pool/PooledEnginesSpec.kt new file mode 100644 index 00000000..3c138dad --- /dev/null +++ b/ashley/src/test/kotlin/ktx/ashley/engine/pool/PooledEnginesSpec.kt @@ -0,0 +1,93 @@ +package ktx.ashley.engine.pool + +import com.badlogic.ashley.core.PooledEngine +import ktx.ashley.Texture +import ktx.ashley.Transform +import ktx.ashley.all +import ktx.ashley.one +import org.assertj.core.api.Assertions.assertThat +import org.jetbrains.spek.api.Spek +import org.jetbrains.spek.api.dsl.describe +import org.jetbrains.spek.api.dsl.it + +object PooledEnginesSpec : Spek({ + describe("utilities for pooled engines") { + val engine by memoized { + PooledEngine() + } + describe("creating a component") { + val component = engine.create() + it("should create a pooled component with reified type") { + assertThat(component.x).isEqualTo(0f) + assertThat(component.y).isEqualTo(0f) + } + } + describe("creating a component with configuration") { + val component = engine.create { + x = 1f + y = 2f + } + it("should create a pooled component with reified type") { + assertThat(component.x).isEqualTo(1f) + assertThat(component.y).isEqualTo(2f) + } + } + describe("multiple entity creation DSL") { + it("should add an entity") { + engine.add { + entity {} + } + assertThat(engine.entities.size()).isEqualTo(1) + } + + it("should add multiple entities") { + engine.add { + entity {} + entity {} + } + assertThat(engine.entities.size()).isEqualTo(2) + } + } + + describe("single entity creation DSL") { + it("should add an entity and return it") { + val entity = engine.entity {} + assertThat(engine.entities.size()).isEqualTo(1) + assertThat(engine.entities[0]).isEqualTo(entity) + } + it("should add an entity with a component with default configuration") { + engine.entity { + with() + } + val transformEntities = engine.getEntitiesFor(one(Transform::class).get()) + val component = transformEntities.single().getComponent(Transform::class.java) + assertThat(component.x).isEqualTo(0f) + assertThat(component.y).isEqualTo(0f) + } + it("should add an entity with a component with configuration") { + engine.entity { + with { + x = 1f + y = 2f + } + } + val transformEntities = engine.getEntitiesFor(one(Transform::class).get()) + val component = transformEntities.single().getComponent(Transform::class.java) + assertThat(component.x).isEqualTo(1f) + assertThat(component.y).isEqualTo(2f) + } + it("should add multiple different components") { + engine.entity { + with() + with() + } + val transformEntities = engine.getEntitiesFor(all(Transform::class, Texture::class).get()) + assertThat(transformEntities.size()).isEqualTo(1) + + val entity = transformEntities.single() + assertThat(entity.getComponent(Transform::class.java)).isNotNull() + assertThat(entity.getComponent(Texture::class.java)).isNotNull() + } + } + } +}) diff --git a/gradle.properties b/gradle.properties index 293392a0..b46dab1d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,5 @@ libGroup=io.github.libktx +ashleyVersion=1.7.0 gdxVersion=1.9.6 visUiVersion=1.3.0 kotlinVersion=1.1.2-3 @@ -11,6 +12,7 @@ slf4jVersion=1.7.25 wireMockVersion=2.6.0 mockitoVersion=2.7.22 nexusPluginVersion=0.5.3 +spekVersion=1.1.2 configurationsPluginVersion=3.0.3 # The following properties are mock-ups. To deploy the archives, diff --git a/settings.gradle b/settings.gradle index b07154dc..435b3bd6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,5 +1,6 @@ include 'actors', 'app', + 'ashley', 'assets', 'async', 'box2d', From f6d2459ae6b6a3ca541b82bdf4ea9203b1fe0877 Mon Sep 17 00:00:00 2001 From: Jingkei Ly Date: Mon, 5 Jun 2017 23:56:32 +0100 Subject: [PATCH 05/15] use gradlew to ensure same gradle version --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b4504079..8ce3364c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,8 @@ language: java jdk: - oraclejdk8 -install: gradle assemble install -x dokka -x dokkaJavadoc -script: gradle check -x dokka -x dokkaJavadoc +install: ./gradlew assemble install -x dokka -x dokkaJavadoc +script: ./gradlew check -x dokka -x dokkaJavadoc before_cache: - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock From c276fff94974342980ecb3c1bce44bf4bae4300d Mon Sep 17 00:00:00 2001 From: Jingkei Ly Date: Tue, 6 Jun 2017 10:29:38 +0100 Subject: [PATCH 06/15] address PR comments - add imports to examples - move junit5 platform build config to root - move version numbers to root gradle - remove trailing newlines - use = shorthand where possible - add warning about using the wrong extensions - added example for non-pooled DSL --- ashley/README.md | 118 +++++++++++++++--- ashley/build.gradle | 18 +-- .../kotlin/ktx/ashley/componentMappers.kt | 4 +- .../main/kotlin/ktx/ashley/engine/engines.kt | 27 +--- .../ktx/ashley/engine/pool/pooledEngines.kt | 8 +- ashley/src/main/kotlin/ktx/ashley/entities.kt | 18 +-- ashley/src/main/kotlin/ktx/ashley/families.kt | 8 +- .../kotlin/ktx/ashley/ComponentMappersSpec.kt | 10 +- .../test/kotlin/ktx/ashley/EntitiesSpec.kt | 2 +- .../test/kotlin/ktx/ashley/FamiliesSpec.kt | 12 +- .../src/test/kotlin/ktx/ashley/RigidBody.kt | 2 +- ashley/src/test/kotlin/ktx/ashley/Texture.kt | 2 +- .../src/test/kotlin/ktx/ashley/Transform.kt | 2 +- .../kotlin/ktx/ashley/engine/EnginesSpec.kt | 22 ++-- .../ashley/engine/pool/PooledEnginesSpec.kt | 10 +- build.gradle | 1 + gradle.properties | 2 + 17 files changed, 154 insertions(+), 112 deletions(-) diff --git a/ashley/README.md b/ashley/README.md index 1e971b60..e8b806b1 100644 --- a/ashley/README.md +++ b/ashley/README.md @@ -3,27 +3,36 @@ Utilities and type-safe builders for the [Ashley](https://github.com/libgdx/ashley) entity component system. ### Why? -We can make using [Ashley](https://github.com/libgdx/ashley) with Kotlin more pleasant where Java Classes are -required for many method calls by providing helper methods using reified types. Additionally, creating Entities and -their respective Components can result in a lot of declarative-style code which is suited nicely to a readable type-safe +We can make using [Ashley](https://github.com/libgdx/ashley) with Kotlin more pleasant by providing helper methods using +reified types where Java `Classes` are required for method calls. Additionally, creating `Entities` and +their respective `Components` can result in a lot of declarative-style code which is suited nicely to a readable type-safe builder DSL. ### Guide `ktx-ashley` provides extensions and utilities for using [Ashley](https://github.com/libgdx/ashley) with Kotlin: -- `Engine.add` and `Engine.entity` extension methods provide type-safe building DSL for creating non-pooled Entities -- `PooledEngine.add` and `PooledEngine.entity` extension methods provide type-safe building DSL for creating pooled Entities +- `Engine.add` and `Engine.entity` extension methods provide type-safe building DSL for creating non-pooled `Entities` +- `PooledEngine.add` and `PooledEngine.entity` extension methods provide type-safe building DSL for creating pooled `Entities` + - Note: ensure that extensions are imported from the correct package `ktx.ashley.engine.pool.*`, otherwise + the `Engine` extensions will be imported entities and components will not be pooled - `mapperFor` factory method for create `ComponentMapper` instance. -- `ktx.ashley.entities.*` accessors for Entity objects -- `ktx.ashley.families.*` for constructing Family builders with KClasses +- `ktx.ashley.entities.*` accessors for `Entity` objects +- `ktx.ashley.families.*` for constructing `Family` builders with `KClasses` ### Usage examples -Creating a new pooled Entity (similar for non-pooled Entities): +Creating a new pooled `Entity`: ```Kotlin +import com.badlogic.ashley.core.Component +import com.badlogic.ashley.core.PooledEngine +import ktx.ashley.engine.pool.entity + val engine = PooledEngine() +class Texture: Component +class Transform(var x:Float = 0f, var y:Float = 0f): Component + val entity = engine.entity { with() with { @@ -35,8 +44,14 @@ val entity = engine.entity { Creating multiple new entities: ```Kotlin +import com.badlogic.ashley.core.Component +import com.badlogic.ashley.core.PooledEngine +import ktx.ashley.engine.pool.* + val engine = PooledEngine() +class Transform(var x:Float = 0f, var y:Float = 0f): Component + fun setupEngine() = engine.add { entity { with { @@ -53,29 +68,102 @@ fun setupEngine() = engine.add { } ``` -Creating a ComponentMapper: +Create new non-pooled `Entity`: +```Kotlin +import com.badlogic.ashley.core.Component +import com.badlogic.ashley.core.Engine +import ktx.ashley.engine.* + +val engine = Engine() + +class Texture: Component +class Transform(var x:Float = 0f, var y:Float = 0f): Component + +val entity = engine.entity { + add(Texture()) + add(Transform( + x = 1f, + y = 1f + )) +} +``` + +Creating a `ComponentMapper`: ```Kotlin +import com.badlogic.ashley.core.Component +import ktx.ashley.mapperFor + +class Transform: Component + val transformMapper = mapperFor() ``` -Getting a Component from an Entity: +Getting a `Component` from an `Entity`: ```Kotlin +import com.badlogic.ashley.core.Component +import com.badlogic.ashley.core.PooledEngine +import ktx.ashley.engine.pool.entity +import ktx.ashley.get +import ktx.ashley.mapperFor + +class Transform: Component + +val engine = PooledEngine() +val transformMapper = mapperFor() +val entity = engine.entity { + with() +} val component = entity[transformMapper] ``` -Check a Component on an Entity: +Check a `Component` on an `Entity``: ```Kotlin +import com.badlogic.ashley.core.Component +import com.badlogic.ashley.core.PooledEngine +import ktx.ashley.engine.pool.entity +import ktx.ashley.get +import ktx.ashley.mapperFor + +class Transform: Component + +val engine = PooledEngine() +val transformMapper = mapperFor() +val entity = engine.entity { + with() +} val component = entity.has(transformMapper) ``` -Remove a Component from an Entity: +Remove a `Component` from an `Entity`: ```Kotlin -fun removeTransform() = entity.remove() +import com.badlogic.ashley.core.Component +import com.badlogic.ashley.core.PooledEngine +import ktx.ashley.engine.pool.entity +import ktx.ashley.remove + +class Transform: Component + +val engine = PooledEngine() +val entity = engine.entity { + with() +} + +fun removeTransform() { + entity.remove() +} ``` -Create component Family to match all Components and excluding any: +Create component `Family` to match all `Components` with an exclusion: ```Kotlin -var family = all(Texture::class, Transform::class).exclude(RigidBody::class) +import com.badlogic.ashley.core.Component +import ktx.ashley.all +import ktx.ashley.exclude + +class Texture: Component +class Transform: Component +class RigidBody: Component + +var family = allOf(Texture::class, Transform::class).exclude(RigidBody::class) ``` #### Additional documentation diff --git a/ashley/build.gradle b/ashley/build.gradle index 87fd16b4..23da24e1 100644 --- a/ashley/build.gradle +++ b/ashley/build.gradle @@ -1,32 +1,20 @@ -buildscript { - ext { - junitPlatformVersion = "1.0.0-M4" - } - dependencies { - classpath "org.junit.platform:junit-platform-gradle-plugin:$junitPlatformVersion" - } - repositories { - mavenCentral() - } -} +apply plugin: 'org.junit.platform.gradle.plugin' dependencies { provided "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" provided "com.badlogicgames.ashley:ashley:$ashleyVersion" testCompile "org.jetbrains.spek:spek-api:$spekVersion" - testCompile 'org.assertj:assertj-core:3.8.0' + testCompile "org.assertj:assertj-core:$assertjVersion" testRuntime "org.junit.platform:junit-platform-launcher:$junitPlatformVersion" testRuntime "org.jetbrains.spek:spek-junit-platform-engine:$spekVersion" } -apply plugin: 'org.junit.platform.gradle.plugin' - junitPlatform { filters { engines { include 'spek' } } -} \ No newline at end of file +} diff --git a/ashley/src/main/kotlin/ktx/ashley/componentMappers.kt b/ashley/src/main/kotlin/ktx/ashley/componentMappers.kt index cf883c63..45134be4 100644 --- a/ashley/src/main/kotlin/ktx/ashley/componentMappers.kt +++ b/ashley/src/main/kotlin/ktx/ashley/componentMappers.kt @@ -12,6 +12,4 @@ import com.badlogic.ashley.core.ComponentMapper * @return the [ComponentMapper] * @see ComponentMapper */ -inline fun mapperFor(): ComponentMapper { - return ComponentMapper.getFor(T::class.java) -} \ No newline at end of file +inline fun mapperFor(): ComponentMapper = ComponentMapper.getFor(T::class.java) diff --git a/ashley/src/main/kotlin/ktx/ashley/engine/engines.kt b/ashley/src/main/kotlin/ktx/ashley/engine/engines.kt index 87848af5..c6943821 100644 --- a/ashley/src/main/kotlin/ktx/ashley/engine/engines.kt +++ b/ashley/src/main/kotlin/ktx/ashley/engine/engines.kt @@ -1,33 +1,9 @@ package ktx.ashley.engine -import com.badlogic.ashley.core.Component import com.badlogic.ashley.core.Engine import com.badlogic.ashley.core.Entity import ktx.ashley.AshleyDsl -/** - * Creates an instance of the component [T] and adds it to this [Entity]. - * - * @param T the [Component] type to get or create - * @param configure inlined function with [T] as the receiver to allow additional configuration of the [Component] - * @return the created [Component] - */ -inline fun Entity.with(configure: (@AshleyDsl T).() -> Unit): T { - return with().also(configure) -} - -/** - * Creates a pooled instance of the component [T] and adds it to this [Entity]. - * - * @param T the [Component] type to get or create - * @return the pooled [Component] - */ -inline fun Entity.with(): T { - val component = T::class.java.newInstance() - add(component) - return component -} - /** * Builder function for [Engine]. * @@ -40,8 +16,7 @@ inline fun Engine.add(configure: (@AshleyDsl Engine).() -> Unit) { /** * Create and add an [Entity] to the [Engine]. * - * @param configure inlined function with the pooled [Entity] as the receiver to allow further configuration of - * the [Entity]. The [Entity] holds the [Entity] created and the [Engine] that created it. + * @param configure inlined function with the pooled [Entity] as the receiver to allow further configuration of the [Entity]. * @return the created [Entity] */ inline fun Engine.entity(configure: Entity.() -> Unit): Entity { diff --git a/ashley/src/main/kotlin/ktx/ashley/engine/pool/pooledEngines.kt b/ashley/src/main/kotlin/ktx/ashley/engine/pool/pooledEngines.kt index a2d70d90..56c2cdd8 100644 --- a/ashley/src/main/kotlin/ktx/ashley/engine/pool/pooledEngines.kt +++ b/ashley/src/main/kotlin/ktx/ashley/engine/pool/pooledEngines.kt @@ -13,9 +13,7 @@ import ktx.ashley.AshleyDsl * @return the pooled [Component] * @see PooledEngine.createComponent */ -inline fun PooledEngine.create(configure: T.() -> Unit): T { - return create().also(configure) -} +inline fun PooledEngine.create(configure: T.() -> Unit): T = create().also(configure) /** * Get or create a [Component] from the [PooledEngine]. @@ -46,9 +44,7 @@ class PooledEntity(val engine: PooledEngine, val entity: Entity) { * @return the pooled [Component] * @see [create] */ - inline fun with(configure: (@AshleyDsl T).() -> Unit): T { - return with().also(configure) - } + inline fun with(configure: (@AshleyDsl T).() -> Unit): T = with().also(configure) /** * Get or creates a pooled instance of the component [T] and adds it to this [entity][PooledEntity]. diff --git a/ashley/src/main/kotlin/ktx/ashley/entities.kt b/ashley/src/main/kotlin/ktx/ashley/entities.kt index 88e74373..c0279b02 100644 --- a/ashley/src/main/kotlin/ktx/ashley/entities.kt +++ b/ashley/src/main/kotlin/ktx/ashley/entities.kt @@ -15,9 +15,7 @@ import com.badlogic.ashley.core.Entity * @param T the [Component] type to search for * @return the specified [Component]. Otherwise `null` if the [Entity] does not have it. */ -inline fun Entity.get(): T? { - return getComponent(T::class.java) -} +inline fun Entity.get(): T? = getComponent(T::class.java) /** * Gets the specified [Component] from the [Entity] with a [ComponentMapper]. @@ -28,9 +26,7 @@ inline fun Entity.get(): T? { * @param mapper the [ComponentMapper] to retrieve the [Component] with * @return the specified [Component]. Otherwise `null` if the [Entity] does not have it. */ -operator fun Entity.get(mapper: ComponentMapper): T? { - return mapper.get(this) -} +operator fun Entity.get(mapper: ComponentMapper): T? = mapper.get(this) /** * Whether the [Entity] has the specified [Component]. @@ -40,9 +36,7 @@ operator fun Entity.get(mapper: ComponentMapper): T? { * @param mapper the [ComponentMapper] to check the [Component] with * @return `true` if the [Entity] has the specified component, and `false` otherwise */ -fun Entity.has(mapper: ComponentMapper): Boolean { - return mapper.has(this) -} +fun Entity.has(mapper: ComponentMapper): Boolean = mapper.has(this) /** * Whether the [Entity] does not have the specified [Component]. @@ -52,9 +46,7 @@ fun Entity.has(mapper: ComponentMapper): Boolean { * @param mapper the [ComponentMapper] to check the [Component] with * @return `true` if the [Entity] does not have the specified component, and `false` otherwise */ -fun Entity.hasNot(mapper: ComponentMapper): Boolean { - return !has(mapper) -} +fun Entity.hasNot(mapper: ComponentMapper): Boolean = !has(mapper) /** * Removes the specified [Component] from the [Entity]. @@ -63,4 +55,4 @@ fun Entity.hasNot(mapper: ComponentMapper): Boolean { */ inline fun Entity.remove() { remove(T::class.java) -} \ No newline at end of file +} diff --git a/ashley/src/main/kotlin/ktx/ashley/families.kt b/ashley/src/main/kotlin/ktx/ashley/families.kt index 75f00821..577624e6 100644 --- a/ashley/src/main/kotlin/ktx/ashley/families.kt +++ b/ashley/src/main/kotlin/ktx/ashley/families.kt @@ -9,7 +9,7 @@ import kotlin.reflect.KClass * @param components matches [entities][com.badlogic.ashley.core.Entity] with at least one of the specified components. * @return a new [Builder] for a [Family] */ -fun one(vararg components: KClass): Builder { +fun oneOf(vararg components: KClass): Builder { return Family.one(*toJavaClassArray(components) ) } @@ -17,7 +17,7 @@ fun one(vararg components: KClass): Builder { * @param components matches [entities][com.badlogic.ashley.core.Entity] with all of the specified components. * @return a new [Builder] for a [Family] */ -fun all(vararg components: KClass): Builder { +fun allOf(vararg components: KClass): Builder { return Family.all(*toJavaClassArray(components) ) } @@ -34,7 +34,7 @@ fun exclude(vararg components: KClass): Builder { * @param components matches [entities][com.badlogic.ashley.core.Entity] with at least one of the specified components. * @return the received [Builder] for the [Family] */ -fun Builder.one(vararg components: KClass): Builder { +fun Builder.oneOf(vararg components: KClass): Builder { return one(*toJavaClassArray(components) ) } @@ -43,7 +43,7 @@ fun Builder.one(vararg components: KClass): Builder { * @param components matches [entities][com.badlogic.ashley.core.Entity] with all of the specified components. * @return the received [Builder] for the [Family] */ -fun Builder.all(vararg components: KClass): Builder { +fun Builder.allOf(vararg components: KClass): Builder { return all(*toJavaClassArray(components) ) } diff --git a/ashley/src/test/kotlin/ktx/ashley/ComponentMappersSpec.kt b/ashley/src/test/kotlin/ktx/ashley/ComponentMappersSpec.kt index d9338983..a5f5fb88 100644 --- a/ashley/src/test/kotlin/ktx/ashley/ComponentMappersSpec.kt +++ b/ashley/src/test/kotlin/ktx/ashley/ComponentMappersSpec.kt @@ -8,10 +8,12 @@ import org.jetbrains.spek.api.dsl.it object ComponentMappersSpec: Spek({ describe("utilities for component mappers") { - val entity = Entity() - it("should return a component mapper via a reified type") { + val entity = Entity().apply { + add(Texture()) + } + it("should return a component mapper for the provided a reified type") { val mapper = mapperFor() - assertThat(mapper.has(entity)).isFalse() + assertThat(mapper.has(entity)).isTrue() } } -}) \ No newline at end of file +}) diff --git a/ashley/src/test/kotlin/ktx/ashley/EntitiesSpec.kt b/ashley/src/test/kotlin/ktx/ashley/EntitiesSpec.kt index 21b46a54..d4e9ae5a 100644 --- a/ashley/src/test/kotlin/ktx/ashley/EntitiesSpec.kt +++ b/ashley/src/test/kotlin/ktx/ashley/EntitiesSpec.kt @@ -59,4 +59,4 @@ object EntitiesSpec: Spek({ } } } -}) \ No newline at end of file +}) diff --git a/ashley/src/test/kotlin/ktx/ashley/FamiliesSpec.kt b/ashley/src/test/kotlin/ktx/ashley/FamiliesSpec.kt index b23d49de..0a3f94eb 100644 --- a/ashley/src/test/kotlin/ktx/ashley/FamiliesSpec.kt +++ b/ashley/src/test/kotlin/ktx/ashley/FamiliesSpec.kt @@ -28,13 +28,13 @@ object FamiliesSpec: Spek({ } it("should create a family that matches one of component") { - val family = one(Texture::class, Transform::class).get() + val family = oneOf(Texture::class, Transform::class).get() assertThat(family.matches(textureEntity)).isTrue() assertThat(family.matches(rigidBodyEntity)).isFalse() } it("should create a family that matches all components") { - val family = all(Texture::class, Transform::class).get() + val family = allOf(Texture::class, Transform::class).get() assertThat(family.matches(textureEntity)).isFalse() assertThat(family.matches(textureAndTransformEntity)).isTrue() } @@ -46,22 +46,22 @@ object FamiliesSpec: Spek({ describe("composite families") { it("should build a family chained with matching any of one component") { - val family = exclude(Transform::class).one(Texture::class, RigidBody::class) + val family = exclude(Transform::class).oneOf(Texture::class, RigidBody::class) assertThat(family.get().matches(textureEntity)).isTrue() assertThat(family.get().matches(textureAndTransformEntity)).isFalse() } it("should build a family chained with matching all components") { - val family = exclude(RigidBody::class).all(Texture::class, Transform::class) + val family = exclude(RigidBody::class).allOf(Texture::class, Transform::class) assertThat(family.get().matches(allComponentsEntity)).isFalse() assertThat(family.get().matches(textureAndTransformEntity)).isTrue() } it("should build a family chained with excluding components") { - val family = one(RigidBody::class).exclude(Texture::class, Transform::class) + val family = oneOf(RigidBody::class).exclude(Texture::class, Transform::class) assertThat(family.get().matches(allComponentsEntity)).isFalse() assertThat(family.get().matches(rigidBodyEntity)).isTrue() } } } -}) \ No newline at end of file +}) diff --git a/ashley/src/test/kotlin/ktx/ashley/RigidBody.kt b/ashley/src/test/kotlin/ktx/ashley/RigidBody.kt index 884c95b4..5f59ba5d 100644 --- a/ashley/src/test/kotlin/ktx/ashley/RigidBody.kt +++ b/ashley/src/test/kotlin/ktx/ashley/RigidBody.kt @@ -2,4 +2,4 @@ package ktx.ashley import com.badlogic.ashley.core.Component -class RigidBody: Component \ No newline at end of file +class RigidBody: Component diff --git a/ashley/src/test/kotlin/ktx/ashley/Texture.kt b/ashley/src/test/kotlin/ktx/ashley/Texture.kt index f6132ab8..5caa297d 100644 --- a/ashley/src/test/kotlin/ktx/ashley/Texture.kt +++ b/ashley/src/test/kotlin/ktx/ashley/Texture.kt @@ -7,4 +7,4 @@ class Texture: Component { companion object { val mapper = ComponentMapper.getFor(Texture::class.java)!! } -} \ No newline at end of file +} diff --git a/ashley/src/test/kotlin/ktx/ashley/Transform.kt b/ashley/src/test/kotlin/ktx/ashley/Transform.kt index ff050f62..7bed5e2b 100644 --- a/ashley/src/test/kotlin/ktx/ashley/Transform.kt +++ b/ashley/src/test/kotlin/ktx/ashley/Transform.kt @@ -7,4 +7,4 @@ class Transform(var x:Float = 0f, var y: Float = 0f) : Component { companion object { val mapper = ComponentMapper.getFor(Transform::class.java)!! } -} \ No newline at end of file +} diff --git a/ashley/src/test/kotlin/ktx/ashley/engine/EnginesSpec.kt b/ashley/src/test/kotlin/ktx/ashley/engine/EnginesSpec.kt index a6631f58..76e81532 100644 --- a/ashley/src/test/kotlin/ktx/ashley/engine/EnginesSpec.kt +++ b/ashley/src/test/kotlin/ktx/ashley/engine/EnginesSpec.kt @@ -3,8 +3,8 @@ package ktx.ashley.engine import com.badlogic.ashley.core.Engine import ktx.ashley.Texture import ktx.ashley.Transform -import ktx.ashley.all -import ktx.ashley.one +import ktx.ashley.allOf +import ktx.ashley.oneOf import org.assertj.core.api.Assertions.assertThat import org.jetbrains.spek.api.Spek import org.jetbrains.spek.api.dsl.describe @@ -40,31 +40,31 @@ object EnginesSpec : Spek({ } it("should add an entity with a component with default configuration") { engine.entity { - with() + add(Transform()) } - val transformEntities = engine.getEntitiesFor(one(Transform::class).get()) + val transformEntities = engine.getEntitiesFor(oneOf(Transform::class).get()) val component = transformEntities.single().getComponent(Transform::class.java) assertThat(component.x).isEqualTo(0f) assertThat(component.y).isEqualTo(0f) } it("should add an entity with a component with configuration") { engine.entity { - with { - x = 1f + add(Transform( + x = 1f, y = 2f - } + )) } - val transformEntities = engine.getEntitiesFor(one(Transform::class).get()) + val transformEntities = engine.getEntitiesFor(oneOf(Transform::class).get()) val component = transformEntities.single().getComponent(Transform::class.java) assertThat(component.x).isEqualTo(1f) assertThat(component.y).isEqualTo(2f) } it("should add multiple different components") { engine.entity { - with() - with() + add(Transform()) + add(Texture()) } - val transformEntities = engine.getEntitiesFor(all(Transform::class, Texture::class).get()) + val transformEntities = engine.getEntitiesFor(allOf(Transform::class, Texture::class).get()) assertThat(transformEntities.size()).isEqualTo(1) val entity = transformEntities.single() diff --git a/ashley/src/test/kotlin/ktx/ashley/engine/pool/PooledEnginesSpec.kt b/ashley/src/test/kotlin/ktx/ashley/engine/pool/PooledEnginesSpec.kt index 3c138dad..7642f112 100644 --- a/ashley/src/test/kotlin/ktx/ashley/engine/pool/PooledEnginesSpec.kt +++ b/ashley/src/test/kotlin/ktx/ashley/engine/pool/PooledEnginesSpec.kt @@ -3,8 +3,8 @@ package ktx.ashley.engine.pool import com.badlogic.ashley.core.PooledEngine import ktx.ashley.Texture import ktx.ashley.Transform -import ktx.ashley.all -import ktx.ashley.one +import ktx.ashley.allOf +import ktx.ashley.oneOf import org.assertj.core.api.Assertions.assertThat import org.jetbrains.spek.api.Spek import org.jetbrains.spek.api.dsl.describe @@ -59,7 +59,7 @@ object PooledEnginesSpec : Spek({ engine.entity { with() } - val transformEntities = engine.getEntitiesFor(one(Transform::class).get()) + val transformEntities = engine.getEntitiesFor(oneOf(Transform::class).get()) val component = transformEntities.single().getComponent(Transform::class.java) assertThat(component.x).isEqualTo(0f) assertThat(component.y).isEqualTo(0f) @@ -71,7 +71,7 @@ object PooledEnginesSpec : Spek({ y = 2f } } - val transformEntities = engine.getEntitiesFor(one(Transform::class).get()) + val transformEntities = engine.getEntitiesFor(oneOf(Transform::class).get()) val component = transformEntities.single().getComponent(Transform::class.java) assertThat(component.x).isEqualTo(1f) assertThat(component.y).isEqualTo(2f) @@ -81,7 +81,7 @@ object PooledEnginesSpec : Spek({ with() with() } - val transformEntities = engine.getEntitiesFor(all(Transform::class, Texture::class).get()) + val transformEntities = engine.getEntitiesFor(allOf(Transform::class, Texture::class).get()) assertThat(transformEntities.size()).isEqualTo(1) val entity = transformEntities.single() diff --git a/build.gradle b/build.gradle index a19c60e1..b93f7232 100644 --- a/build.gradle +++ b/build.gradle @@ -8,6 +8,7 @@ buildscript { classpath "io.codearte.gradle.nexus:gradle-nexus-staging-plugin:$nexusPluginVersion" classpath "com.netflix.nebula:gradle-extra-configurations-plugin:$configurationsPluginVersion" classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokkaVersion" + classpath "org.junit.platform:junit-platform-gradle-plugin:$junitPlatformVersion" } } diff --git a/gradle.properties b/gradle.properties index b46dab1d..b8ad0eb6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,8 +6,10 @@ kotlinVersion=1.1.2-3 kotlinCoroutinesVersion=0.15 kotlinTestVersion=2.0.0 kotlinMockitoVersion=1.4.0 +assertjVersion=3.8.0 dokkaVersion=0.9.13 junitVersion=4.12 +junitPlatformVersion=1.0.0-M4 slf4jVersion=1.7.25 wireMockVersion=2.6.0 mockitoVersion=2.7.22 From 1d62fc66887ec2ede19c71d31420b13bc362d9c9 Mon Sep 17 00:00:00 2001 From: Jingkei Ly Date: Tue, 6 Jun 2017 17:15:51 +0100 Subject: [PATCH 07/15] removed non-pooled engine utilities - fixed some examples --- ashley/README.md | 37 ++------- .../main/kotlin/ktx/ashley/engine/engines.kt | 27 ------- .../ashley/{engine/pool => }/pooledEngines.kt | 3 +- .../{engine/pool => }/PooledEnginesSpec.kt | 6 +- .../kotlin/ktx/ashley/engine/EnginesSpec.kt | 76 ------------------- 5 files changed, 9 insertions(+), 140 deletions(-) delete mode 100644 ashley/src/main/kotlin/ktx/ashley/engine/engines.kt rename ashley/src/main/kotlin/ktx/ashley/{engine/pool => }/pooledEngines.kt (97%) rename ashley/src/test/kotlin/ktx/ashley/{engine/pool => }/PooledEnginesSpec.kt (95%) delete mode 100644 ashley/src/test/kotlin/ktx/ashley/engine/EnginesSpec.kt diff --git a/ashley/README.md b/ashley/README.md index e8b806b1..26c9545a 100644 --- a/ashley/README.md +++ b/ashley/README.md @@ -12,10 +12,7 @@ builder DSL. `ktx-ashley` provides extensions and utilities for using [Ashley](https://github.com/libgdx/ashley) with Kotlin: -- `Engine.add` and `Engine.entity` extension methods provide type-safe building DSL for creating non-pooled `Entities` - `PooledEngine.add` and `PooledEngine.entity` extension methods provide type-safe building DSL for creating pooled `Entities` - - Note: ensure that extensions are imported from the correct package `ktx.ashley.engine.pool.*`, otherwise - the `Engine` extensions will be imported entities and components will not be pooled - `mapperFor` factory method for create `ComponentMapper` instance. - `ktx.ashley.entities.*` accessors for `Entity` objects - `ktx.ashley.families.*` for constructing `Family` builders with `KClasses` @@ -26,7 +23,7 @@ Creating a new pooled `Entity`: ```Kotlin import com.badlogic.ashley.core.Component import com.badlogic.ashley.core.PooledEngine -import ktx.ashley.engine.pool.entity +import ktx.ashley.entity val engine = PooledEngine() @@ -46,7 +43,7 @@ Creating multiple new entities: ```Kotlin import com.badlogic.ashley.core.Component import com.badlogic.ashley.core.PooledEngine -import ktx.ashley.engine.pool.* +import ktx.ashley.* val engine = PooledEngine() @@ -68,26 +65,6 @@ fun setupEngine() = engine.add { } ``` -Create new non-pooled `Entity`: -```Kotlin -import com.badlogic.ashley.core.Component -import com.badlogic.ashley.core.Engine -import ktx.ashley.engine.* - -val engine = Engine() - -class Texture: Component -class Transform(var x:Float = 0f, var y:Float = 0f): Component - -val entity = engine.entity { - add(Texture()) - add(Transform( - x = 1f, - y = 1f - )) -} -``` - Creating a `ComponentMapper`: ```Kotlin import com.badlogic.ashley.core.Component @@ -102,7 +79,7 @@ Getting a `Component` from an `Entity`: ```Kotlin import com.badlogic.ashley.core.Component import com.badlogic.ashley.core.PooledEngine -import ktx.ashley.engine.pool.entity +import ktx.ashley.entity import ktx.ashley.get import ktx.ashley.mapperFor @@ -120,8 +97,8 @@ Check a `Component` on an `Entity``: ```Kotlin import com.badlogic.ashley.core.Component import com.badlogic.ashley.core.PooledEngine -import ktx.ashley.engine.pool.entity -import ktx.ashley.get +import ktx.ashley.entity +import ktx.ashley.has import ktx.ashley.mapperFor class Transform: Component @@ -138,7 +115,7 @@ Remove a `Component` from an `Entity`: ```Kotlin import com.badlogic.ashley.core.Component import com.badlogic.ashley.core.PooledEngine -import ktx.ashley.engine.pool.entity +import ktx.ashley.entity import ktx.ashley.remove class Transform: Component @@ -156,7 +133,7 @@ fun removeTransform() { Create component `Family` to match all `Components` with an exclusion: ```Kotlin import com.badlogic.ashley.core.Component -import ktx.ashley.all +import ktx.ashley.allOf import ktx.ashley.exclude class Texture: Component diff --git a/ashley/src/main/kotlin/ktx/ashley/engine/engines.kt b/ashley/src/main/kotlin/ktx/ashley/engine/engines.kt deleted file mode 100644 index c6943821..00000000 --- a/ashley/src/main/kotlin/ktx/ashley/engine/engines.kt +++ /dev/null @@ -1,27 +0,0 @@ -package ktx.ashley.engine - -import com.badlogic.ashley.core.Engine -import com.badlogic.ashley.core.Entity -import ktx.ashley.AshleyDsl - -/** - * Builder function for [Engine]. - * - * @param configure inlined function with *this* [Engine] as the receiver to allow further configuration. - */ -inline fun Engine.add(configure: (@AshleyDsl Engine).() -> Unit) { - configure(this) -} - -/** - * Create and add an [Entity] to the [Engine]. - * - * @param configure inlined function with the pooled [Entity] as the receiver to allow further configuration of the [Entity]. - * @return the created [Entity] - */ -inline fun Engine.entity(configure: Entity.() -> Unit): Entity { - val entity = Entity().also(configure) - addEntity(entity) - return entity -} - diff --git a/ashley/src/main/kotlin/ktx/ashley/engine/pool/pooledEngines.kt b/ashley/src/main/kotlin/ktx/ashley/pooledEngines.kt similarity index 97% rename from ashley/src/main/kotlin/ktx/ashley/engine/pool/pooledEngines.kt rename to ashley/src/main/kotlin/ktx/ashley/pooledEngines.kt index 56c2cdd8..26cf0399 100644 --- a/ashley/src/main/kotlin/ktx/ashley/engine/pool/pooledEngines.kt +++ b/ashley/src/main/kotlin/ktx/ashley/pooledEngines.kt @@ -1,9 +1,8 @@ -package ktx.ashley.engine.pool +package ktx.ashley import com.badlogic.ashley.core.Component import com.badlogic.ashley.core.Entity import com.badlogic.ashley.core.PooledEngine -import ktx.ashley.AshleyDsl /** * Get or create a [Component] from the [PooledEngine]. diff --git a/ashley/src/test/kotlin/ktx/ashley/engine/pool/PooledEnginesSpec.kt b/ashley/src/test/kotlin/ktx/ashley/PooledEnginesSpec.kt similarity index 95% rename from ashley/src/test/kotlin/ktx/ashley/engine/pool/PooledEnginesSpec.kt rename to ashley/src/test/kotlin/ktx/ashley/PooledEnginesSpec.kt index 7642f112..3e5b140c 100644 --- a/ashley/src/test/kotlin/ktx/ashley/engine/pool/PooledEnginesSpec.kt +++ b/ashley/src/test/kotlin/ktx/ashley/PooledEnginesSpec.kt @@ -1,10 +1,6 @@ -package ktx.ashley.engine.pool +package ktx.ashley import com.badlogic.ashley.core.PooledEngine -import ktx.ashley.Texture -import ktx.ashley.Transform -import ktx.ashley.allOf -import ktx.ashley.oneOf import org.assertj.core.api.Assertions.assertThat import org.jetbrains.spek.api.Spek import org.jetbrains.spek.api.dsl.describe diff --git a/ashley/src/test/kotlin/ktx/ashley/engine/EnginesSpec.kt b/ashley/src/test/kotlin/ktx/ashley/engine/EnginesSpec.kt deleted file mode 100644 index 76e81532..00000000 --- a/ashley/src/test/kotlin/ktx/ashley/engine/EnginesSpec.kt +++ /dev/null @@ -1,76 +0,0 @@ -package ktx.ashley.engine - -import com.badlogic.ashley.core.Engine -import ktx.ashley.Texture -import ktx.ashley.Transform -import ktx.ashley.allOf -import ktx.ashley.oneOf -import org.assertj.core.api.Assertions.assertThat -import org.jetbrains.spek.api.Spek -import org.jetbrains.spek.api.dsl.describe -import org.jetbrains.spek.api.dsl.it - -object EnginesSpec : Spek({ - describe("utilities for engines") { - val engine by memoized { - Engine() - } - describe("multiple entity creation DSL") { - it("should add an entity") { - engine.add { - entity {} - } - assertThat(engine.entities.size()).isEqualTo(1) - } - - it("should add multiple entities") { - engine.add { - entity {} - entity {} - } - assertThat(engine.entities.size()).isEqualTo(2) - } - } - - describe("single entity creation DSL") { - it("should add an entity and return it") { - val entity = engine.entity {} - assertThat(engine.entities.size()).isEqualTo(1) - assertThat(engine.entities[0]).isEqualTo(entity) - } - it("should add an entity with a component with default configuration") { - engine.entity { - add(Transform()) - } - val transformEntities = engine.getEntitiesFor(oneOf(Transform::class).get()) - val component = transformEntities.single().getComponent(Transform::class.java) - assertThat(component.x).isEqualTo(0f) - assertThat(component.y).isEqualTo(0f) - } - it("should add an entity with a component with configuration") { - engine.entity { - add(Transform( - x = 1f, - y = 2f - )) - } - val transformEntities = engine.getEntitiesFor(oneOf(Transform::class).get()) - val component = transformEntities.single().getComponent(Transform::class.java) - assertThat(component.x).isEqualTo(1f) - assertThat(component.y).isEqualTo(2f) - } - it("should add multiple different components") { - engine.entity { - add(Transform()) - add(Texture()) - } - val transformEntities = engine.getEntitiesFor(allOf(Transform::class, Texture::class).get()) - assertThat(transformEntities.size()).isEqualTo(1) - - val entity = transformEntities.single() - assertThat(entity.getComponent(Transform::class.java)).isNotNull() - assertThat(entity.getComponent(Texture::class.java)).isNotNull() - } - } - } -}) From aded35edd6efd41f457bc1cf19115881df504fc0 Mon Sep 17 00:00:00 2001 From: MJ Date: Tue, 6 Jun 2017 19:11:47 +0200 Subject: [PATCH 08/15] Updated Dokka version. #10 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index b8ad0eb6..dfeaca17 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,7 +7,7 @@ kotlinCoroutinesVersion=0.15 kotlinTestVersion=2.0.0 kotlinMockitoVersion=1.4.0 assertjVersion=3.8.0 -dokkaVersion=0.9.13 +dokkaVersion=0.9.14 junitVersion=4.12 junitPlatformVersion=1.0.0-M4 slf4jVersion=1.7.25 From f1ff61dc8528d77387146cf29196bd754966378a Mon Sep 17 00:00:00 2001 From: MJ Date: Tue, 6 Jun 2017 19:52:21 +0200 Subject: [PATCH 09/15] Improved Ashley module documentation. #23 --- .github/CONTRIBUTORS.md | 6 +- CHANGELOG.md | 5 ++ ashley/README.md | 60 ++++++++++--------- .../ktx/ashley/{AshleyDsl.kt => dsl.kt} | 2 +- ashley/src/main/kotlin/ktx/ashley/entities.kt | 49 ++++++++------- ashley/src/main/kotlin/ktx/ashley/families.kt | 47 ++++++--------- .../{componentMappers.kt => mappers.kt} | 7 ++- .../main/kotlin/ktx/ashley/pooledEngines.kt | 39 ++++++------ .../test/kotlin/ktx/ashley/EntitiesSpec.kt | 12 +++- 9 files changed, 116 insertions(+), 111 deletions(-) rename ashley/src/main/kotlin/ktx/ashley/{AshleyDsl.kt => dsl.kt} (83%) rename ashley/src/main/kotlin/ktx/ashley/{componentMappers.kt => mappers.kt} (69%) diff --git a/.github/CONTRIBUTORS.md b/.github/CONTRIBUTORS.md index e5c3843e..dcdf5050 100644 --- a/.github/CONTRIBUTORS.md +++ b/.github/CONTRIBUTORS.md @@ -3,7 +3,7 @@ Project contributors listed chronologically. * [@czyzby](https://github.com/czyzby) * Author of most libraries, main maintainer. * [@kotcrab](https://github.com/kotcrab) - * Author of [`VisUI`](../vis) extension. + * Author of [`VisUI` extension](../vis). * [@MrPlow442](https://github.com/MrPlow442) * Contributed LibGDX [collections](../collections) utilities. * [@sreich](https://github.com/sreich) @@ -12,5 +12,5 @@ Project contributors listed chronologically. * Provided insightful review of the [`Async`](../async) module. Contributed LibGDX [collections](../collections) utilities. * [@Jkly](https://github.com/Jkly) - * Author of [gdx-box2d-kotlin](https://github.com/Jkly/gdx-box2d-kotlin), which inspired the `Box2D` **KTX** library. - Provided insightful review of the [`Box2D`](../box2d) module. + * Author of [`Ashley` module](../ashley), as well as the [gdx-box2d-kotlin](https://github.com/Jkly/gdx-box2d-kotlin) + library, which inspired the `Box2D` **KTX** module. Provided insightful review of the [`Box2D`](../box2d) module. diff --git a/CHANGELOG.md b/CHANGELOG.md index 127112c6..7c579211 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ #### 1.9.6-SNAPSHOT - **[FEATURE]** (`ktx-ashley`) new **KTX** module with Ashley entity component system utilities: `ktx-ashley`. + - `PooledEngine.add` and `PooledEngine.entity` extension methods. + - `PooledEntity` wrapping `Entity` and providing access to `PooledEngine` API. + - `mapperFor` factory method that allows to create `ComponentMapper` instances. + - Accessors for `Entity` objects using `ComponentMappers`: `get`, `has`, `hasNot`, `remove`. + - DSL methods for constructing `Family` builders with `KClass` instances: `oneOf`, `allOf`, `exclude`. #### 1.9.6-b4 diff --git a/ashley/README.md b/ashley/README.md index 26c9545a..bc95ed91 100644 --- a/ashley/README.md +++ b/ashley/README.md @@ -3,27 +3,32 @@ Utilities and type-safe builders for the [Ashley](https://github.com/libgdx/ashley) entity component system. ### Why? -We can make using [Ashley](https://github.com/libgdx/ashley) with Kotlin more pleasant by providing helper methods using -reified types where Java `Classes` are required for method calls. Additionally, creating `Entities` and -their respective `Components` can result in a lot of declarative-style code which is suited nicely to a readable type-safe + +Since [Ashley](https://github.com/libgdx/ashley) contains many generic methods consuming `Class` instances, Kotlin can +provide a pleasant DSL via inlined methods reified generic types. Additionally, creating `Entities` and their respective +`Components` can result in a lot of declarative-style code which is greatly improved by an easily readable type-safe builder DSL. ### Guide -`ktx-ashley` provides extensions and utilities for using [Ashley](https://github.com/libgdx/ashley) with Kotlin: +`ktx-ashley` provides the following extensions and utilities: -- `PooledEngine.add` and `PooledEngine.entity` extension methods provide type-safe building DSL for creating pooled `Entities` -- `mapperFor` factory method for create `ComponentMapper` instance. -- `ktx.ashley.entities.*` accessors for `Entity` objects -- `ktx.ashley.families.*` for constructing `Family` builders with `KClasses` +- `PooledEngine.add` and `PooledEngine.entity` extension methods provide type-safe building DSL for creating pooled `Entities`. +- `PooledEntity` is an `Entity` wrapper that allows to create `Component` instances using using the `PooledEngine` via +`with` methods. +- `mapperFor` factory method allows to create `ComponentMapper` instances. +- Accessors for `Entity` objects using `ComponentMappers`: `get`, `has`, `hasNot`, `remove`. +- Top-level and `Builder` extension DSL methods for constructing `Family` builders with `KClass` instances: `oneOf`, +`allOf`, `exclude`. ### Usage examples Creating a new pooled `Entity`: + ```Kotlin import com.badlogic.ashley.core.Component import com.badlogic.ashley.core.PooledEngine -import ktx.ashley.entity +import ktx.ashley.* val engine = PooledEngine() @@ -40,6 +45,7 @@ val entity = engine.entity { ``` Creating multiple new entities: + ```Kotlin import com.badlogic.ashley.core.Component import com.badlogic.ashley.core.PooledEngine @@ -66,6 +72,7 @@ fun setupEngine() = engine.add { ``` Creating a `ComponentMapper`: + ```Kotlin import com.badlogic.ashley.core.Component import ktx.ashley.mapperFor @@ -76,47 +83,45 @@ val transformMapper = mapperFor() ``` Getting a `Component` from an `Entity`: + ```Kotlin import com.badlogic.ashley.core.Component import com.badlogic.ashley.core.PooledEngine -import ktx.ashley.entity -import ktx.ashley.get -import ktx.ashley.mapperFor +import ktx.ashley.* class Transform: Component val engine = PooledEngine() -val transformMapper = mapperFor() +val transform = mapperFor() val entity = engine.entity { with() } -val component = entity[transformMapper] +val component: Transform = entity[transform] ``` -Check a `Component` on an `Entity``: +Checking if an `Entity` has a `Component`: + ```Kotlin import com.badlogic.ashley.core.Component import com.badlogic.ashley.core.PooledEngine -import ktx.ashley.entity -import ktx.ashley.has -import ktx.ashley.mapperFor +import ktx.ashley.* class Transform: Component val engine = PooledEngine() -val transformMapper = mapperFor() +val transform = mapperFor() val entity = engine.entity { with() } -val component = entity.has(transformMapper) +val canBeTransformed: Boolean = entity.has(transform) ``` -Remove a `Component` from an `Entity`: +Removing a `Component` from an `Entity`: + ```Kotlin import com.badlogic.ashley.core.Component import com.badlogic.ashley.core.PooledEngine -import ktx.ashley.entity -import ktx.ashley.remove +import ktx.ashley.* class Transform: Component @@ -130,11 +135,11 @@ fun removeTransform() { } ``` -Create component `Family` to match all `Components` with an exclusion: +Creating a component `Family` that matches all entities with the selected `Component` types with an exclusion: + ```Kotlin import com.badlogic.ashley.core.Component -import ktx.ashley.allOf -import ktx.ashley.exclude +import ktx.ashley.* class Texture: Component class Transform: Component @@ -145,4 +150,5 @@ var family = allOf(Texture::class, Transform::class).exclude(RigidBody::class) #### Additional documentation -- [Ashley](https://github.com/libgdx/ashley) +- [Ashley repository.](https://github.com/libgdx/ashley) +- [A classic article on Entity Component Systems.](http://t-machine.org/index.php/2007/09/03/entity-systems-are-the-future-of-mmog-development-part-1/) diff --git a/ashley/src/main/kotlin/ktx/ashley/AshleyDsl.kt b/ashley/src/main/kotlin/ktx/ashley/dsl.kt similarity index 83% rename from ashley/src/main/kotlin/ktx/ashley/AshleyDsl.kt rename to ashley/src/main/kotlin/ktx/ashley/dsl.kt index e68d11a6..8e73a231 100644 --- a/ashley/src/main/kotlin/ktx/ashley/AshleyDsl.kt +++ b/ashley/src/main/kotlin/ktx/ashley/dsl.kt @@ -5,4 +5,4 @@ package ktx.ashley */ @DslMarker @Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE) -annotation class AshleyDsl \ No newline at end of file +annotation class AshleyDsl diff --git a/ashley/src/main/kotlin/ktx/ashley/entities.kt b/ashley/src/main/kotlin/ktx/ashley/entities.kt index c0279b02..d24ead2e 100644 --- a/ashley/src/main/kotlin/ktx/ashley/entities.kt +++ b/ashley/src/main/kotlin/ktx/ashley/entities.kt @@ -4,55 +4,54 @@ import com.badlogic.ashley.core.Component import com.badlogic.ashley.core.ComponentMapper import com.badlogic.ashley.core.Entity -/** - * Gets the specified [Component] from the [Entity]. - * - * Note that this function provides `O(logn)` performance for [Component] retrieval. It is recommended that retrieving - * a [Component] is done using [get(ComponentMapper)]. - * - * @see ComponentMapper - * @see [get(ComponentMapper)] - * @param T the [Component] type to search for - * @return the specified [Component]. Otherwise `null` if the [Entity] does not have it. - */ -inline fun Entity.get(): T? = getComponent(T::class.java) - /** * Gets the specified [Component] from the [Entity] with a [ComponentMapper]. * - * @see ComponentMapper - * @see mapperFor * @param T the [Component] type to get * @param mapper the [ComponentMapper] to retrieve the [Component] with * @return the specified [Component]. Otherwise `null` if the [Entity] does not have it. + * @see ComponentMapper.get + * @see mapperFor */ operator fun Entity.get(mapper: ComponentMapper): T? = mapper.get(this) /** - * Whether the [Entity] has the specified [Component]. + * Gets the specified [Component] from the [Entity]. + * + * Note that this function provides `O(log n)` performance for [Component] retrieval. It is recommended that retrieving + * a [Component] is done using [get] consuming a [ComponentMapper]. * + * @param T the [Component] type to search for. + * @return the specified [Component]. Otherwise `null` if the [Entity] does not have it. * @see ComponentMapper - * @param T the [Component] type to inspect + */ +inline fun Entity.get(): T? = getComponent(T::class.java) + +/** + * Checks whether the [Entity] has the specified [Component]. + * + * @param T the [Component] type to inspect. * @param mapper the [ComponentMapper] to check the [Component] with * @return `true` if the [Entity] has the specified component, and `false` otherwise + * @see ComponentMapper.has */ -fun Entity.has(mapper: ComponentMapper): Boolean = mapper.has(this) +fun Entity.has(mapper: ComponentMapper): Boolean = mapper.has(this) /** * Whether the [Entity] does not have the specified [Component]. * - * @see ComponentMapper - * @param T the [Component] type to inspect + * @param T the [Component] type to inspect. * @param mapper the [ComponentMapper] to check the [Component] with * @return `true` if the [Entity] does not have the specified component, and `false` otherwise + * @see ComponentMapper.has */ -fun Entity.hasNot(mapper: ComponentMapper): Boolean = !has(mapper) +fun Entity.hasNot(mapper: ComponentMapper): Boolean = !has(mapper) /** * Removes the specified [Component] from the [Entity]. * - * @param T the [Component] type to remove + * @param T the [Component] type to remove. + * @return the removed [Component] instance (if was present and matched the specified type) or null. + * @see Entity.remove */ -inline fun Entity.remove() { - remove(T::class.java) -} +inline fun Entity.remove(): Component? = remove(T::class.java) as? T diff --git a/ashley/src/main/kotlin/ktx/ashley/families.kt b/ashley/src/main/kotlin/ktx/ashley/families.kt index 577624e6..cd339122 100644 --- a/ashley/src/main/kotlin/ktx/ashley/families.kt +++ b/ashley/src/main/kotlin/ktx/ashley/families.kt @@ -7,55 +7,42 @@ import kotlin.reflect.KClass /** * @param components matches [entities][com.badlogic.ashley.core.Entity] with at least one of the specified components. - * @return a new [Builder] for a [Family] + * @return a new [Builder] for a [Family]. */ -fun oneOf(vararg components: KClass): Builder { - return Family.one(*toJavaClassArray(components) ) -} +fun oneOf(vararg components: KClass): Builder = Family.one(*toJavaClassArray(components)) /** * @param components matches [entities][com.badlogic.ashley.core.Entity] with all of the specified components. - * @return a new [Builder] for a [Family] + * @return a new [Builder] for a [Family]. */ -fun allOf(vararg components: KClass): Builder { - return Family.all(*toJavaClassArray(components) ) -} +fun allOf(vararg components: KClass): Builder = Family.all(*toJavaClassArray(components)) /** * @param components does not match [entities][com.badlogic.ashley.core.Entity] with any of the specified components. - * @return a new [Builder] for a [Family] + * @return a new [Builder] for a [Family]. */ -fun exclude(vararg components: KClass): Builder { - return Family.exclude(*toJavaClassArray(components) ) -} +fun exclude(vararg components: KClass): Builder = Family.exclude(*toJavaClassArray(components)) /** - * @receiver the [Builder] for creating a [Family] + * @receiver the [Builder] for creating a [Family]. * @param components matches [entities][com.badlogic.ashley.core.Entity] with at least one of the specified components. - * @return the received [Builder] for the [Family] + * @return the received [Builder] for the [Family]. */ -fun Builder.oneOf(vararg components: KClass): Builder { - return one(*toJavaClassArray(components) ) -} +fun Builder.oneOf(vararg components: KClass): Builder = one(*toJavaClassArray(components)) /** - * @receiver the [Builder] for creating a [Family] + * @receiver the [Builder] for creating a [Family]. * @param components matches [entities][com.badlogic.ashley.core.Entity] with all of the specified components. - * @return the received [Builder] for the [Family] + * @return the received [Builder] for the [Family]. */ -fun Builder.allOf(vararg components: KClass): Builder { - return all(*toJavaClassArray(components) ) -} +fun Builder.allOf(vararg components: KClass): Builder = all(*toJavaClassArray(components)) /** - * @receiver the [Builder] for creating a [Family] + * @receiver the [Builder] for creating a [Family]. * @param components does not match [entities][com.badlogic.ashley.core.Entity] with any of the specified components. - * @return the received [Builder] for the [Family] + * @return the received [Builder] for the [Family]. */ -fun Builder.exclude(vararg components: KClass): Builder { - return exclude(*toJavaClassArray(components) ) -} +fun Builder.exclude(vararg components: KClass): Builder = exclude(*toJavaClassArray(components)) -private fun toJavaClassArray(components: Array>): Array> { - return components.map { c -> c.java }.toTypedArray() -} +private fun toJavaClassArray(components: Array>): Array> + = Array(components.size) { index -> components[index].java } diff --git a/ashley/src/main/kotlin/ktx/ashley/componentMappers.kt b/ashley/src/main/kotlin/ktx/ashley/mappers.kt similarity index 69% rename from ashley/src/main/kotlin/ktx/ashley/componentMappers.kt rename to ashley/src/main/kotlin/ktx/ashley/mappers.kt index 45134be4..f53d6dd6 100644 --- a/ashley/src/main/kotlin/ktx/ashley/componentMappers.kt +++ b/ashley/src/main/kotlin/ktx/ashley/mappers.kt @@ -8,8 +8,9 @@ import com.badlogic.ashley.core.ComponentMapper * * Provides `O(1)` retrieval of [Component]s for an [com.badlogic.ashley.core.Entity]. * - * @param T the [Component] type to create a [ComponentMapper] for - * @return the [ComponentMapper] + * @param T the [Component] type to create a [ComponentMapper] for. + * @return a [ComponentMapper] * @see ComponentMapper + * @see Component */ -inline fun mapperFor(): ComponentMapper = ComponentMapper.getFor(T::class.java) +inline fun mapperFor(): ComponentMapper = ComponentMapper.getFor(T::class.java) diff --git a/ashley/src/main/kotlin/ktx/ashley/pooledEngines.kt b/ashley/src/main/kotlin/ktx/ashley/pooledEngines.kt index 26cf0399..083d0ed0 100644 --- a/ashley/src/main/kotlin/ktx/ashley/pooledEngines.kt +++ b/ashley/src/main/kotlin/ktx/ashley/pooledEngines.kt @@ -7,52 +7,52 @@ import com.badlogic.ashley.core.PooledEngine /** * Get or create a [Component] from the [PooledEngine]. * - * @param T the [Component] to get or create + * @param T the type of [Component] to get or create. * @param configure inlined function with [T] as the receiver to allow further configuration. - * @return the pooled [Component] + * @return a pooled [Component] instance of the selected type. * @see PooledEngine.createComponent */ -inline fun PooledEngine.create(configure: T.() -> Unit): T = create().also(configure) +inline fun PooledEngine.create(configure: T.() -> Unit): T = create().also(configure) /** * Get or create a [Component] from the [PooledEngine]. * - * @param T the [Component] to get or create - * @return the pooled [Component] + * @param T the type of [Component] to get or create. + * @return a pooled [Component] instance of the selected type. * @see PooledEngine.createComponent */ -inline fun PooledEngine.create(): T = createComponent(T::class.java) +inline fun PooledEngine.create(): T = createComponent(T::class.java) /** * An [Entity] created by a [PooledEngine]. * * Provides methods for adding [Component]s to the [PooledEngine] and the [Entity]. * - * @property engine the [PooledEngine] to create [Component]s - * @property entity the [Entity] to add [Component]s to - * @constructor creates a [PooledEntity] + * @property engine the [PooledEngine] providing [Components][Component]. + * @property entity the [Entity] to add [Components][Component] to. */ @AshleyDsl -class PooledEntity(val engine: PooledEngine, val entity: Entity) { - +class PooledEntity( + val engine: PooledEngine, + val entity: Entity) { /** * Get or creates a pooled instance of the component [T] and adds it to this [entity][PooledEntity]. * - * @param T the [Component] type to get or create - * @param configure inlined function with [T] as the receiver to allow additional configuration of the [Component] - * @return the pooled [Component] + * @param T the [Component] type to get or create. + * @param configure inlined function with [T] as the receiver to allow additional configuration of the [Component]. + * @return the pooled [Component]. * @see [create] */ - inline fun with(configure: (@AshleyDsl T).() -> Unit): T = with().also(configure) + inline fun with(configure: (@AshleyDsl T).() -> Unit): T = with().also(configure) /** * Get or creates a pooled instance of the component [T] and adds it to this [entity][PooledEntity]. * - * @param T the [Component] type to get or create - * @return the pooled [Component] + * @param T the [Component] type to get or create. + * @return the pooled [Component]. * @see [create] */ - inline fun with(): T { + inline fun with(): T { val component = engine.create() entity.add(component) return component @@ -73,7 +73,7 @@ inline fun PooledEngine.add(configure: (@AshleyDsl PooledEngine).() -> Unit) { * * @param configure inlined function with the pooled [PooledEntity] as the receiver to allow further configuration of * the [Entity]. The [PooledEntity] holds the [Entity] created and the [PooledEngine] that created it. - * @return the pooled [Entity] + * @return the pooled [Entity]. */ inline fun PooledEngine.entity(configure: PooledEntity.() -> Unit): Entity { val entity = createEntity() @@ -81,4 +81,3 @@ inline fun PooledEngine.entity(configure: PooledEntity.() -> Unit): Entity { addEntity(entity) return entity } - diff --git a/ashley/src/test/kotlin/ktx/ashley/EntitiesSpec.kt b/ashley/src/test/kotlin/ktx/ashley/EntitiesSpec.kt index d4e9ae5a..8eee5af1 100644 --- a/ashley/src/test/kotlin/ktx/ashley/EntitiesSpec.kt +++ b/ashley/src/test/kotlin/ktx/ashley/EntitiesSpec.kt @@ -6,7 +6,7 @@ import org.jetbrains.spek.api.Spek import org.jetbrains.spek.api.dsl.describe import org.jetbrains.spek.api.dsl.it -object EntitiesSpec: Spek({ +object EntitiesSpec : Spek({ describe("utilities for entities") { val transform by memoized { Transform() @@ -36,8 +36,16 @@ object EntitiesSpec: Spek({ describe("remove component function") { it("should remove a component by reified type") { - entity.remove() + val component = entity.remove() + assertThat(entity.getComponent(Transform::class.java)).isNull() + assertThat(component).isEqualTo(transform) + } + it("should fail to remove an absent component by reified type") { + val component = entity.remove() + + assertThat(entity.getComponent(Texture::class.java)).isNull() + assertThat(component).isNull() } } From b83230f99ffe116aeb422f9ce2ec2889c96d6bac Mon Sep 17 00:00:00 2001 From: MJ Date: Tue, 6 Jun 2017 19:54:28 +0200 Subject: [PATCH 10/15] Improved mapper utility documentation. #23 --- ashley/src/main/kotlin/ktx/ashley/mappers.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ashley/src/main/kotlin/ktx/ashley/mappers.kt b/ashley/src/main/kotlin/ktx/ashley/mappers.kt index f53d6dd6..a8aa08a4 100644 --- a/ashley/src/main/kotlin/ktx/ashley/mappers.kt +++ b/ashley/src/main/kotlin/ktx/ashley/mappers.kt @@ -9,7 +9,7 @@ import com.badlogic.ashley.core.ComponentMapper * Provides `O(1)` retrieval of [Component]s for an [com.badlogic.ashley.core.Entity]. * * @param T the [Component] type to create a [ComponentMapper] for. - * @return a [ComponentMapper] + * @return a [ComponentMapper] matching the selected component type. * @see ComponentMapper * @see Component */ From 8875c9aa00491d04de4be39c3d1acc8e3d360986 Mon Sep 17 00:00:00 2001 From: MJ Date: Tue, 6 Jun 2017 21:59:33 +0200 Subject: [PATCH 11/15] Refactored EventListener factory methods. #80 --- CHANGELOG.md | 8 + actors/README.md | 104 +++++++-- actors/src/main/kotlin/ktx/actors/events.kt | 206 +++++++++++++++--- .../src/test/kotlin/kts/actors/eventsTest.kt | 131 ++++++++++- 4 files changed, 386 insertions(+), 63 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c579211..2d5c93e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,12 @@ #### 1.9.6-SNAPSHOT +- **[FEATURE]** (`ktx-actors`) `onChange`, `onClick`, `onKey`, `onKeyDown`, `onKeyUp`, `onScrollFocus` and `onKeyboardFocus` +factory methods for `EventListener` instances were added. Contrary to existing factory methods, these use minimal set +of parameters to make listeners creation as concise as possible. +- **[CHANGE]** (`ktx-actors`) Existing `onChange`, `onClick`, `onKey`, `onKeyDown`, `onKeyUp`, `onScrollFocus` and +`onKeyboardFocus` factory methods where renamed to `onChangeEvent`, `onClickEvent`, `onKeyEvent`, `onKeyDownEvent`, +`onKeyUpEvent`, `onScrollFocusEvent` and `onKeyboardFocusEvent` respectively. Their excessive amount of parameters, +useful only on rare occasions, led to unnecessary boilerplate during listeners creation. See `ktx-actors` file +documentation for migration guide. - **[FEATURE]** (`ktx-ashley`) new **KTX** module with Ashley entity component system utilities: `ktx-ashley`. - `PooledEngine.add` and `PooledEngine.entity` extension methods. - `PooledEntity` wrapping `Entity` and providing access to `PooledEngine` API. diff --git a/actors/README.md b/actors/README.md index 1d05cace..76dd9bf9 100644 --- a/actors/README.md +++ b/actors/README.md @@ -26,8 +26,7 @@ a `Group` with `actor in group` syntax. #### Event listeners - Lambda-compatible `Actor.onChange` method was added. Allows to listen to `ChangeEvents`. -- Lambda-compatible `Actor.onClick` methods were added. (More verbose version has access to local coordinates of the click -events.) Attaches `ClickListeners`. +- Lambda-compatible `Actor.onClick` method was added. Attaches `ClickListeners`. - Lambda-compatible `Actor.onKey` method was added. Allows to listen to `InputEvents` with `keyTyped` type. - Lambda-compatible `Actor.onKeyDown` and `Actor.onKeyUp` methods were added. They allow to listen to `InputEvents` with `keyDown` and `keyUp` type, consuming key code of the pressed or released key (see LibGDX `Keys` class). @@ -35,6 +34,11 @@ with `keyDown` and `keyUp` type, consuming key code of the pressed or released k - Lambda-compatible `Actor.onKeyboardFocus` method was added. Allows to listen to `FocusEvents` with `keyboard` type. - `KtxInputListener` is an open class that extends `InputListener` with no-op default implementations and type improvements (nullability data). +- `onChangeEvent`, `onClickEvent`, `onKeyEvent`, `onKeyDownEvent`, `onKeyUpEvent`, `onScrollFocusEvent` and +`onKeyboardFocusEvent` `Actor` extension methods were added. They consume the relevant `Event` instances as lambda +parameters. Both listener factory variants are inlined, but the ones ending with *Event* provide more lambda parameters +and allow to inspect the original `Event` instance that triggered the listener. Regular listener factory methods should +be enough for most use cases. #### Actions @@ -90,25 +94,79 @@ textField.setKeyboardFocus(true) scrollPane.setScrollFocus(false) ``` -Adding event listeners: +Adding a `ChangeListener` to an `Actor`: + +```Kotlin +import ktx.actors.* + +button.onChange { + println("Button changed!") +} + +button.onChangeEvent { changeEvent, actor -> + // If you need access to the original ChangeEvent, use this expanded method variant. + println("$actor changed by $changeEvent!") +} +``` + +Adding a `ClickListener` to an Actor: + +```Kotlin +import ktx.actors.* + +label.onClick { + println("Label clicked!") +} + +label.onClickEvent { inputEvent, actor -> + // If you need access to the original InputEvent, use this expanded method variant. + println("$actor clicked by $inputEvent!") +} +label.onClickEvent { inputEvent, actor, x, y -> + // If you need access to the local actor click coordinates, use this expanded method variant. + println("$actor clicked by $inputEvent at ($x, $y)!") +} +``` + +Adding an `EventListener` which consumes typed characters: + ```Kotlin import ktx.actors.* -button.onChange { changeEvent, button -> - println("$button changed!") } - -label.onClick { inputEvent, label -> - println("$label clicked!") } -table.onClick { inputEvent, table, x, y -> - println("$table clicked at (${x}, ${y})!") } - -textField.onKey { inputEvent, textField, key -> - println("Typed $key char in ${textField}!") } - -scrollPane.onScrollFocus { focusEvent, scrollPane -> - println("$scrollPane focus: ${event.isFocused}!") } -textField.onKeyboardFocus { focusEvent, textField -> - println("$textField focus: ${event.isFocused}!") } +textField.onKey { key -> + println("Typed $key char to text field!") +} + +textField.onKeyEvent { inputEvent, actor, key -> + // If you need access to the original InputEvent, use this expanded method variant. + println("Typed $key char to $actor by $inputEvent!") +} +``` + +Adding `EventListeners` which listen to `FocusEvents`: + +```Kotlin +import ktx.actors.* + +// FocusEvent with scroll type: +scrollPane.onScrollFocus { focused -> + println("Scroll pane is focused: $focused!") +} + +scrollPane.onScrollFocusEvent { focusEvent, actor -> + // If you need access to the original FocusEvent, use this expanded method variant. + println("$actor is focused: ${focusEvent.isFocused}!") +} + +// FocusEvent with keyboard type: +textField.onKeyboardFocus { focused -> + println("Text field is focused: $focused!") +} + +textField.onKeyboardFocusEvent { focusEvent, actor -> + // If you need access to the original FocusEvent, use this expanded method variant. + println("$actor is focused: ${focusEvent.isFocused}!") +} ``` Chaining actions (`SequenceAction` utility): @@ -159,6 +217,16 @@ class MyInputListener : KtxInputListener() { } ``` +#### Migration guide + +In **KTX** up to `1.9.6-b4`, extension methods `onChange`, `onClick`, `onKey`, `onKeyDown`, `onKeyUp`, `onScrollFocus` +and `onKeyboardFocus` consumed `Event` and `Actor` instances. This lead to common usage of `_, _ ->`, which was against +the goal of boilerplate-less listeners. That is why the existing listener factory methods where renamed with `Event` +suffix, and a new set of extension methods with the same names were added - this time consuming a minimal amount of +parameters. Add `Event` suffix to each of your listener methods or refactor them to the new API with less parameters. +For example, if you used `Actor.onChange` extension method, use `Actor.onChangeEvent` instead or remove the `event, actor` +parameters if you do not really need them at all. + ### Alternatives - [VisUI](https://github.com/kotcrab/vis-editor/wiki/VisUI) includes some `Scene2D` utilities, as well as some extended diff --git a/actors/src/main/kotlin/ktx/actors/events.kt b/actors/src/main/kotlin/ktx/actors/events.kt index 0d185d68..efd28017 100644 --- a/actors/src/main/kotlin/ktx/actors/events.kt +++ b/actors/src/main/kotlin/ktx/actors/events.kt @@ -14,9 +14,24 @@ import com.badlogic.gdx.scenes.scene2d.utils.FocusListener.FocusEvent.Type.scrol * @param listener invoked each time a [ChangeEvent] is fired for this actor. * @return [ChangeListener] instance. */ -inline fun Widget.onChange(crossinline listener: (event: ChangeEvent, actor: Widget) -> Unit): ChangeListener { +inline fun Actor.onChange(crossinline listener: () -> Unit): ChangeListener { val changeListener = object : ChangeListener() { - override fun changed(event: ChangeEvent, actor: Actor) = listener(event, this@onChange) + override fun changed(event: ChangeEvent, actor: Actor) = listener() + } + this.addListener(changeListener) + return changeListener +} + +/** + * Attaches a [ChangeListener] to this actor. + * @param listener invoked each time a [ChangeEvent] is fired for this actor. Consumes the triggered [ChangeEvent] and + * the [Actor] that the listener was originally attached to. + * @return [ChangeListener] instance. + */ +inline fun Widget.onChangeEvent( + crossinline listener: (event: ChangeEvent, actor: Widget) -> Unit): ChangeListener { + val changeListener = object : ChangeListener() { + override fun changed(event: ChangeEvent, actor: Actor) = listener(event, this@onChangeEvent) } this.addListener(changeListener) return changeListener @@ -24,13 +39,12 @@ inline fun Widget.onChange(crossinline listener: (event: Change /** * Attaches a [ClickListener] to this actor. - * @param listener invoked each time this actor is clicked. The received floats are local x and y coordinates of the actor. + * @param listener invoked each time this actor is clicked. * @return [ClickListener] instance. */ -inline fun Widget.onClick(crossinline listener: (event: InputEvent, actor: Widget, x: Float, y: Float) -> Unit) - : ClickListener { +inline fun Actor.onClick(crossinline listener: () -> Unit): ClickListener { val clickListener = object : ClickListener() { - override fun clicked(event: InputEvent, x: Float, y: Float) = listener(event, this@onClick, x, y) + override fun clicked(event: InputEvent, x: Float, y: Float) = listener() } this.addListener(clickListener) return clickListener @@ -38,29 +52,66 @@ inline fun Widget.onClick(crossinline listener: (event: InputEv /** * Attaches a [ClickListener] to this actor. - * @param listener invoked each time this actor is clicked. + * @param listener invoked each time this actor is clicked. Consumes the triggered [InputEvent] and the [Actor] that + * the listener was originally attached to. * @return [ClickListener] instance. */ -inline fun Widget.onClick(crossinline listener: (event: InputEvent, actor: Widget) -> Unit): ClickListener { +inline fun Widget.onClickEvent( + crossinline listener: (event: InputEvent, actor: Widget) -> Unit): ClickListener { val clickListener = object : ClickListener() { - override fun clicked(event: InputEvent, x: Float, y: Float) = listener(event, this@onClick) + override fun clicked(event: InputEvent, x: Float, y: Float) = listener(event, this@onClickEvent) } this.addListener(clickListener) return clickListener } +/** + * Attaches a [ClickListener] to this actor. + * @param listener invoked each time this actor is clicked. Consumes the triggered [InputEvent] and the [Actor] that + * the listener was originally attached to. The received floats are local X and Y coordinates of the actor. + * @return [ClickListener] instance. + */ +inline fun Widget.onClickEvent( + crossinline listener: (event: InputEvent, actor: Widget, x: Float, y: Float) -> Unit): ClickListener { + val clickListener = object : ClickListener() { + override fun clicked(event: InputEvent, x: Float, y: Float) = listener(event, this@onClickEvent, x, y) + } + this.addListener(clickListener) + return clickListener +} + +/** + * Attaches an [EventListener] optimized to listen for key type events fired for this actor. + * @param catchEvent if true, the event will not be passed further after it is handled by this listener. Defaults to false. + * @param listener invoked each time this actor is keyboard-focused and a key is typed. Consumes the typed character. + * @return [EventListener] instance. + */ +inline fun Actor.onKey( + catchEvent: Boolean = false, + crossinline listener: (character: Char) -> Unit): EventListener { + val keyListener = EventListener { event -> + if (event is InputEvent && event.type === keyTyped) { + listener(event.character) + catchEvent + } else false + } + this.addListener(keyListener) + return keyListener +} + /** * Attaches an [EventListener] optimized to listen for key type events fired for this actor. * @param catchEvent if true, the event will not be passed further after it is handled by this listener. Defaults to false. - * @param listener invoked each time this actor is keyboard-focused and a key is typed. + * @param listener invoked each time this actor is keyboard-focused and a key is typed. Consumes the triggered + * [InputEvent], the [Actor] that the listener was originally attached to and the typed character. * @return [EventListener] instance. */ -inline fun Widget.onKey(catchEvent: Boolean = false, - crossinline listener: (event: InputEvent, actor: Widget, character: Char) -> Unit) - : EventListener { +inline fun Widget.onKeyEvent( + catchEvent: Boolean = false, + crossinline listener: (event: InputEvent, actor: Widget, character: Char) -> Unit): EventListener { val keyListener = EventListener { event -> if (event is InputEvent && event.type === keyTyped) { - listener(event, this@onKey, event.character) + listener(event, this@onKeyEvent, event.character) catchEvent } else false } @@ -71,15 +122,37 @@ inline fun Widget.onKey(catchEvent: Boolean = false, /** * Attaches an [EventListener] optimized to listen for keyDown type events fired for this actor. * @param catchEvent if true, the event will not be passed further after it is handled by this listener. Defaults to false. - * @param listener invoked each time this actor is keyboard-focused and a key is pressed. + * @param listener invoked each time this actor is keyboard-focused and a key is pressed. Consumes the pressed key code. + * @return [EventListener] instance. + * @see com.badlogic.gdx.Input.Keys + */ +inline fun Actor.onKeyDown( + catchEvent: Boolean = false, + crossinline listener: (keyCode: Int) -> Unit): EventListener { + val keyDownListener = EventListener { event -> + if (event is InputEvent && event.type === keyDown) { + listener(event.keyCode) + catchEvent + } else false + } + this.addListener(keyDownListener) + return keyDownListener +} + +/** + * Attaches an [EventListener] optimized to listen for keyDown type events fired for this actor. + * @param catchEvent if true, the event will not be passed further after it is handled by this listener. Defaults to false. + * @param listener invoked each time this actor is keyboard-focused and a key is pressed. Consumes the triggered + * [InputEvent], the [Actor] that the listener was originally attached to and the pressed key code. * @return [EventListener] instance. + * @see com.badlogic.gdx.Input.Keys */ -inline fun Widget.onKeyDown(catchEvent: Boolean = false, - crossinline listener: (event: InputEvent, actor: Widget, keyCode: Int) -> Unit) - : EventListener { +inline fun Widget.onKeyDownEvent( + catchEvent: Boolean = false, + crossinline listener: (event: InputEvent, actor: Widget, keyCode: Int) -> Unit): EventListener { val keyDownListener = EventListener { event -> if (event is InputEvent && event.type === keyDown) { - listener(event, this@onKeyDown, event.keyCode) + listener(event, this@onKeyDownEvent, event.keyCode) catchEvent } else false } @@ -90,15 +163,16 @@ inline fun Widget.onKeyDown(catchEvent: Boolean = false, /** * Attaches an [EventListener] optimized to listen for keyUp type events fired for this actor. * @param catchEvent if true, the event will not be passed further after it is handled by this listener. Defaults to false. - * @param listener invoked each time this actor is keyboard-focused and a key is released. + * @param listener invoked each time this actor is keyboard-focused and a key is released. Consumes the released key code. * @return [EventListener] instance. + * @see com.badlogic.gdx.Input.Keys */ -inline fun Widget.onKeyUp(catchEvent: Boolean = false, - crossinline listener: (event: InputEvent, actor: Widget, keyCode: Int) -> Unit) - : EventListener { +inline fun Actor.onKeyUp( + catchEvent: Boolean = false, + crossinline listener: (keyCode: Int) -> Unit): EventListener { val keyUpListener = EventListener { event -> if (event is InputEvent && event.type === keyUp) { - listener(event, this@onKeyUp, event.keyCode) + listener(event.keyCode) catchEvent } else false } @@ -106,19 +180,81 @@ inline fun Widget.onKeyUp(catchEvent: Boolean = false, return keyUpListener } +/** + * Attaches an [EventListener] optimized to listen for keyUp type events fired for this actor. + * @param catchEvent if true, the event will not be passed further after it is handled by this listener. Defaults to false. + * @param listener invoked each time this actor is keyboard-focused and a key is released. Consumes the triggered + * [InputEvent], the [Actor] that the listener was originally attached to and the released key code. + * @return [EventListener] instance. + * @see com.badlogic.gdx.Input.Keys + */ +inline fun Widget.onKeyUpEvent( + catchEvent: Boolean = false, + crossinline listener: (event: InputEvent, actor: Widget, keyCode: Int) -> Unit): EventListener { + val keyUpListener = EventListener { event -> + if (event is InputEvent && event.type === keyUp) { + listener(event, this@onKeyUpEvent, event.keyCode) + catchEvent + } else false + } + this.addListener(keyUpListener) + return keyUpListener +} + +/** + * Attaches an [EventListener] optimized to listen for scroll focus events fired for this actor. + * @param catchEvent if true, the event will not be passed further after it is handled by this listener. Defaults to false. + * @param listener will be invoked each time scroll focus status changes on this actor. Consumes a flag marking whether + * the actor is currently focused. + * @return [EventListener] instance. + */ +inline fun Actor.onScrollFocus( + catchEvent: Boolean = false, + crossinline listener: (focused: Boolean) -> Unit): EventListener { + val focusListener = EventListener { event -> + if (event is FocusEvent && event.type === scroll) { + listener(event.isFocused) + catchEvent + } else false + } + this.addListener(focusListener) + return focusListener +} + /** * Attaches an [EventListener] optimized to listen for scroll focus events fired for this actor. * @param catchEvent if true, the event will not be passed further after it is handled by this listener. Defaults to false. * @param listener will be invoked each time scroll focus status changes on this actor. [FocusEvent.isFocused] can be used - * to determine current status. + * to determine current status. Consumes the triggered [FocusEvent] and the [Actor] that the listener was originally + * attached to. * @return [EventListener] instance. */ -inline fun Widget.onScrollFocus(catchEvent: Boolean = false, - crossinline listener: (event: FocusEvent, actor: Widget) -> Unit) - : EventListener { +inline fun Widget.onScrollFocusEvent( + catchEvent: Boolean = false, + crossinline listener: (event: FocusEvent, actor: Widget) -> Unit): EventListener { val focusListener = EventListener { event -> if (event is FocusEvent && event.type === scroll) { - listener(event, this@onScrollFocus) + listener(event, this@onScrollFocusEvent) + catchEvent + } else false + } + this.addListener(focusListener) + return focusListener +} + +/** + * Attaches an [EventListener] optimized to listen for keyboard focus events fired for this actor. + * @param catchEvent if true, the event will not be passed further after it is handled by this listener. Defaults to false. + * @param listener will be invoked each time keyboard focus status changes on this actor. Consumes a flag marking whether + * the actor is currently focused. + * @return [EventListener] instance. + */ +inline fun Actor.onKeyboardFocus( + catchEvent: Boolean = false, + crossinline listener: (focused: Boolean) -> Unit): EventListener { + val focusListener = EventListener { event -> + if (event is FocusEvent && event.type === keyboard) { + listener(event.isFocused) catchEvent } else false } @@ -130,15 +266,16 @@ inline fun Widget.onScrollFocus(catchEvent: Boolean = false, * Attaches an [EventListener] optimized to listen for keyboard focus events fired for this actor. * @param catchEvent if true, the event will not be passed further after it is handled by this listener. Defaults to false. * @param listener will be invoked each time keyboard focus status changes on this actor. [FocusEvent.isFocused] can be used - * to determine current status. + * to determine current status. Consumes the triggered [FocusEvent] and the [Actor] that the listener was originally + * attached to. * @return [EventListener] instance. */ -inline fun Widget.onKeyboardFocus(catchEvent: Boolean = false, - crossinline listener: (event: FocusEvent, actor: Widget) -> Unit) - : EventListener { +inline fun Widget.onKeyboardFocusEvent( + catchEvent: Boolean = false, + crossinline listener: (event: FocusEvent, actor: Widget) -> Unit): EventListener { val focusListener = EventListener { event -> if (event is FocusEvent && event.type === keyboard) { - listener(event, this@onKeyboardFocus) + listener(event, this@onKeyboardFocusEvent) catchEvent } else false } @@ -149,6 +286,7 @@ inline fun Widget.onKeyboardFocus(catchEvent: Boolean = false, /** * Extends [com.badlogic.gdx.scenes.scene2d.InputListener] for the sole purpose of getting guarantees on nullability. * Provides no-op implementations of all methods, making them optional to implement. + * @see InputListener */ open class KtxInputListener : InputListener() { override fun enter(event: InputEvent, x: Float, y: Float, pointer: Int, fromActor: Actor?) = Unit diff --git a/actors/src/test/kotlin/kts/actors/eventsTest.kt b/actors/src/test/kotlin/kts/actors/eventsTest.kt index 552ab012..396cffd7 100644 --- a/actors/src/test/kotlin/kts/actors/eventsTest.kt +++ b/actors/src/test/kotlin/kts/actors/eventsTest.kt @@ -24,7 +24,21 @@ class EventsTest { val actor = Actor() var changed = false - val listener = actor.onChange { event, actor -> changed = true } + val listener = actor.onChange { changed = true } + + assertNotNull(listener) + assertTrue(listener in actor.listeners) + assertTrue(listener is ChangeListener) + actor.fire(ChangeEvent()) + assertTrue(changed) + } + + @Test + fun `should attach ChangeListener consuming ChangeEvent`() { + val actor = Actor() + var changed = false + + val listener = actor.onChangeEvent { event, widget -> changed = true } assertNotNull(listener) assertTrue(listener in actor.listeners) @@ -37,7 +51,7 @@ class EventsTest { fun `should attach ClickListener`() { val actor = Actor() - val listener = actor.onClick { event, actor -> } + val listener = actor.onClick { } assertNotNull(listener) assertTrue(listener in actor.listeners) @@ -45,10 +59,21 @@ class EventsTest { } @Test - fun `should attach ClickListener with local coordinates`() { + fun `should attach ClickListener consuming InputEvent`() { val actor = Actor() - val listener = actor.onClick { event, actor, x, y -> } + val listener = actor.onClickEvent { event, widget -> } + + assertNotNull(listener) + assertTrue(listener in actor.listeners) + assertTrue(listener is ClickListener) + } + + @Test + fun `should attach ClickListener consuming InputEvent with local coordinates`() { + val actor = Actor() + + val listener = actor.onClickEvent { event, widget, x, y -> } assertNotNull(listener) assertTrue(listener in actor.listeners) @@ -60,7 +85,24 @@ class EventsTest { val actor = Actor() var typed: Char? = null - val listener = actor.onKey { event, actor, key -> typed = key } + val listener = actor.onKey { key -> typed = key } + + assertNotNull(listener) + assertTrue(listener in actor.listeners) + assertNull(typed) + val event = InputEvent() + event.character = 'a' + event.type = keyTyped + actor.fire(event) + assertEquals('a', typed) + } + + @Test + fun `should attach key listener consuming InputEvent`() { + val actor = Actor() + var typed: Char? = null + + val listener = actor.onKeyEvent { event, widget, key -> typed = key } assertNotNull(listener) assertTrue(listener in actor.listeners) @@ -77,7 +119,24 @@ class EventsTest { val actor = Actor() var pressed: Int? = null - val listener = actor.onKeyDown { event, actor, keyCode -> pressed = keyCode } + val listener = actor.onKeyDown { keyCode -> pressed = keyCode } + + assertNotNull(listener) + assertTrue(listener in actor.listeners) + assertNull(pressed) + val event = InputEvent() + event.keyCode = Keys.A + event.type = keyDown + actor.fire(event) + assertEquals(Keys.A, pressed) + } + + @Test + fun `should attach key down listener consuming InputEvent`() { + val actor = Actor() + var pressed: Int? = null + + val listener = actor.onKeyDownEvent { event, widget, keyCode -> pressed = keyCode } assertNotNull(listener) assertTrue(listener in actor.listeners) @@ -94,7 +153,25 @@ class EventsTest { val actor = Actor() var released: Int? = null - val listener = actor.onKeyUp { event, actor, keyCode -> released = keyCode } + val listener = actor.onKeyUp { keyCode -> released = keyCode } + + assertNotNull(listener) + assertTrue(listener in actor.listeners) + + assertNull(released) + val event = InputEvent() + event.keyCode = Keys.A + event.type = keyUp + actor.fire(event) + assertEquals(Keys.A, released) + } + + @Test + fun `should attach key up listener consuming InputEvent`() { + val actor = Actor() + var released: Int? = null + + val listener = actor.onKeyUpEvent { event, widget, keyCode -> released = keyCode } assertNotNull(listener) assertTrue(listener in actor.listeners) @@ -108,11 +185,11 @@ class EventsTest { } @Test - fun `should scroll focus listener`() { + fun `should attach scroll focus listener`() { val actor = Actor() var focused = false - val listener = actor.onScrollFocus { event, actor -> focused = event.isFocused } + val listener = actor.onScrollFocus { focused = it } assertNotNull(listener) assertTrue(listener in actor.listeners) @@ -124,11 +201,43 @@ class EventsTest { } @Test - fun `should keyboard focus listener`() { + fun `should attach scroll focus listener consuming FocusEvent`() { + val actor = Actor() + var focused = false + + val listener = actor.onScrollFocusEvent { event, widget -> focused = event.isFocused } + + assertNotNull(listener) + assertTrue(listener in actor.listeners) + val event = FocusEvent() + event.type = scroll + event.isFocused = true + actor.fire(event) + assertTrue(focused) + } + + @Test + fun `should attach keyboard focus listener`() { + val actor = Actor() + var focused = false + + val listener = actor.onKeyboardFocus { focused = it } + + assertNotNull(listener) + assertTrue(listener in actor.listeners) + val event = FocusEvent() + event.type = keyboard + event.isFocused = true + actor.fire(event) + assertTrue(focused) + } + + @Test + fun `should attach keyboard focus listener consuming FocusEvent`() { val actor = Actor() var focused = false - val listener = actor.onKeyboardFocus { event, actor -> focused = event.isFocused } + val listener = actor.onKeyboardFocusEvent { event, widget -> focused = event.isFocused } assertNotNull(listener) assertTrue(listener in actor.listeners) From 742c5bbbd316705b48ed789d84afe62ac8349da1 Mon Sep 17 00:00:00 2001 From: MJ Date: Tue, 13 Jun 2017 19:17:48 +0200 Subject: [PATCH 12/15] Updated kotlinx-coroutines-core to 0.16. #82 --- CHANGELOG.md | 1 + gradle.properties | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d5c93e5..5f0246b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ #### 1.9.6-SNAPSHOT +- **[UPDATE]** (`ktx-async`) Updated `kotlinx-coroutines-core` to `0.16`. - **[FEATURE]** (`ktx-actors`) `onChange`, `onClick`, `onKey`, `onKeyDown`, `onKeyUp`, `onScrollFocus` and `onKeyboardFocus` factory methods for `EventListener` instances were added. Contrary to existing factory methods, these use minimal set of parameters to make listeners creation as concise as possible. diff --git a/gradle.properties b/gradle.properties index dfeaca17..d31dc5ac 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ ashleyVersion=1.7.0 gdxVersion=1.9.6 visUiVersion=1.3.0 kotlinVersion=1.1.2-3 -kotlinCoroutinesVersion=0.15 +kotlinCoroutinesVersion=0.16 kotlinTestVersion=2.0.0 kotlinMockitoVersion=1.4.0 assertjVersion=3.8.0 From 6bc39c25d01b272880fa5bdee9cb51147113e5d1 Mon Sep 17 00:00:00 2001 From: MJ Date: Tue, 13 Jun 2017 20:52:19 +0200 Subject: [PATCH 13/15] Added additional info about coroutines usage. #82 --- async/README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/async/README.md b/async/README.md index 4e0eda79..a04fb14a 100644 --- a/async/README.md +++ b/async/README.md @@ -1,3 +1,5 @@ +[![Kotlin](https://img.shields.io/badge/kotlin--coroutines-0.16-orange.svg)](http://kotlinlang.org/) + # KTX: coroutines support and threading utilities [Coroutines](https://kotlinlang.org/docs/reference/coroutines.html) support and general asynchronous operations @@ -12,6 +14,26 @@ as improve existing asynchronous APIs to feel more like Kotlin. ### Guide +#### Setup + +Before using `ktx-async`, make sure to include the Kotlin coroutines library in your Gradle script: + +```Groovy +compile group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutinesVersion +``` + +The `coroutinesVersion` _must_ match the coroutines version that the `ktx-async` library was compiled against - +otherwise it might cause runtime errors. Since coroutines are currently an experimental feature, you should enable them +first: + +```Groovy +kotlin { + experimental { + coroutines 'enable' + } +} +``` + #### Coroutines `ktx-async` provides a coroutines context implementation: `KtxAsync`. It allows to launch suspending, non-blocking From 5326d3314224cfe1258115a116bbf4f14a8baf51 Mon Sep 17 00:00:00 2001 From: MJ Date: Tue, 13 Jun 2017 20:57:50 +0200 Subject: [PATCH 14/15] Updated to Kotlin 1.1.2-5. #83 --- CHANGELOG.md | 6 ++++-- README.md | 2 +- app/src/test/kotlin/ktx/app/graphicsTest.kt | 2 +- build.gradle | 3 +-- collections/src/test/kotlin/ktx/collections/mapsTest.kt | 2 +- gradle.properties | 7 +++---- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f0246b8..3702fbcf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ #### 1.9.6-SNAPSHOT -- **[UPDATE]** (`ktx-async`) Updated `kotlinx-coroutines-core` to `0.16`. + +- **[UPDATE]** Updated to Kotlin 1.1.2-5. +- **[UPDATE]** Updated to Kotlin Coroutines 0.16. - **[FEATURE]** (`ktx-actors`) `onChange`, `onClick`, `onKey`, `onKeyDown`, `onKeyUp`, `onScrollFocus` and `onKeyboardFocus` factory methods for `EventListener` instances were added. Contrary to existing factory methods, these use minimal set of parameters to make listeners creation as concise as possible. @@ -29,7 +31,7 @@ documentation for migration guide. #### 1.9.6-b3 - **[UPDATE]** Updated to Kotlin 1.1.2-3. -- **[UPDATE]** Updated to Kotlin Coroutines to 0.15. +- **[UPDATE]** Updated to Kotlin Coroutines 0.15. - **[CHANGE]** (`ktx-assets`) Static `AssetManager` instance container - `Assets` - was removed. All top level functions depending on the global `AssetManager` were removed. - **[FEATURE]** (`ktx-assets`) Added `FileType.getResolver` extension method creating `FileHandleResolver` instances. diff --git a/README.md b/README.md index 49ec4d64..632b6cd5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ [![Travis CI](https://travis-ci.org/libktx/ktx.svg?branch=master)](https://travis-ci.org/libktx/ktx) [![Maven Central](https://img.shields.io/maven-central/v/io.github.libktx/ktx-async.svg)](https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22io.github.libktx%22) -[![Kotlin](https://img.shields.io/badge/kotlin-1.1.2--3-orange.svg)](http://kotlinlang.org/) +[![Kotlin](https://img.shields.io/badge/kotlin-1.1.2--5-orange.svg)](http://kotlinlang.org/) [![LibGDX](https://img.shields.io/badge/libgdx-1.9.6-red.svg)](https://libgdx.badlogicgames.com/) [![KTX](.github/ktx-logo.png "KTX")](http://libktx.github.io) diff --git a/app/src/test/kotlin/ktx/app/graphicsTest.kt b/app/src/test/kotlin/ktx/app/graphicsTest.kt index 71b89072..3078cb89 100644 --- a/app/src/test/kotlin/ktx/app/graphicsTest.kt +++ b/app/src/test/kotlin/ktx/app/graphicsTest.kt @@ -5,9 +5,9 @@ import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.GL20 import com.badlogic.gdx.graphics.g2d.Batch import com.badlogic.gdx.graphics.glutils.ShaderProgram +import com.nhaarman.mockito_kotlin.mock import com.nhaarman.mockito_kotlin.never import com.nhaarman.mockito_kotlin.verify -import io.kotlintest.mock.mock import org.junit.Assert.* import org.junit.Test diff --git a/build.gradle b/build.gradle index b93f7232..2c80ab19 100644 --- a/build.gradle +++ b/build.gradle @@ -45,9 +45,8 @@ subprojects { dependencies { provided "com.badlogicgames.gdx:gdx:$gdxVersion" testCompile "junit:junit:$junitVersion" - testCompile "org.mockito:mockito-core:$mockitoVersion" testCompile "io.kotlintest:kotlintest:$kotlinTestVersion" - testCompile "com.nhaarman:mockito-kotlin:$kotlinMockitoVersion" + testCompile "com.nhaarman:mockito-kotlin-kt1.1:$kotlinMockitoVersion" testCompile "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" } diff --git a/collections/src/test/kotlin/ktx/collections/mapsTest.kt b/collections/src/test/kotlin/ktx/collections/mapsTest.kt index 53787503..f910ddc8 100644 --- a/collections/src/test/kotlin/ktx/collections/mapsTest.kt +++ b/collections/src/test/kotlin/ktx/collections/mapsTest.kt @@ -410,7 +410,7 @@ class MapsTest { @Test fun `should map elements to lists and flatten them into a new GdxArray`() { val map = gdxMapOf("One" to 1, "Two" to 2, "Three" to 3) - val result = map.flatMap { e -> List(e.value) { e.value } } + val result = map.flatMap { (_, value) -> List(value!!) { value } } result.sort() assertTrue(result is GdxArray) diff --git a/gradle.properties b/gradle.properties index d31dc5ac..3361eea9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,17 +2,16 @@ libGroup=io.github.libktx ashleyVersion=1.7.0 gdxVersion=1.9.6 visUiVersion=1.3.0 -kotlinVersion=1.1.2-3 +kotlinVersion=1.1.2-5 kotlinCoroutinesVersion=0.16 -kotlinTestVersion=2.0.0 -kotlinMockitoVersion=1.4.0 +kotlinTestVersion=2.0.3 +kotlinMockitoVersion=1.5.0 assertjVersion=3.8.0 dokkaVersion=0.9.14 junitVersion=4.12 junitPlatformVersion=1.0.0-M4 slf4jVersion=1.7.25 wireMockVersion=2.6.0 -mockitoVersion=2.7.22 nexusPluginVersion=0.5.3 spekVersion=1.1.2 configurationsPluginVersion=3.0.3 From efcb144afd06518cda74f3652c21bbeeeb58603b Mon Sep 17 00:00:00 2001 From: MJ Date: Tue, 13 Jun 2017 21:38:12 +0200 Subject: [PATCH 15/15] Changed version to 1.9.6-b5. #83 --- CHANGELOG.md | 2 +- version.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3702fbcf..a8ba72e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -#### 1.9.6-SNAPSHOT +#### 1.9.6-b5 - **[UPDATE]** Updated to Kotlin 1.1.2-5. - **[UPDATE]** Updated to Kotlin Coroutines 0.16. diff --git a/version.txt b/version.txt index 51c785cd..fd16202b 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.9.6-SNAPSHOT +1.9.6-b5