From 7dfdfca1ae5d40c6658a6603ad7b85dcdd5dc26e Mon Sep 17 00:00:00 2001 From: MJ Date: Tue, 5 Jan 2021 22:06:25 +0100 Subject: [PATCH 01/21] Preparing for the next release. #331 --- CHANGELOG.md | 2 ++ version.txt | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26ad10f8..32d388c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ _See also: [the official LibGDX changelog](https://github.com/libgdx/libgdx/blob/master/CHANGES)._ +#### 1.9.13-SNAPSHOT + #### 1.9.13-b1 - **[UPDATE]** Updated to LibGDX 1.9.13. diff --git a/version.txt b/version.txt index 8c6c6615..f911a296 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.9.13-b1 +1.9.13-SNAPSHOT From 163dd78c51bfe75100b8f0e96f7ab81285e34d96 Mon Sep 17 00:00:00 2001 From: MJ Date: Wed, 13 Jan 2021 12:55:07 +0100 Subject: [PATCH 02/21] New LibGDX website link. #335 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e0a29242..5e7b8d2f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ [![GitHub Build](https://github.com/libktx/ktx/workflows/build/badge.svg)](https://github.com/libktx/ktx/actions?query=workflow%3Abuild) [![Kotlin](https://img.shields.io/badge/kotlin-1.4.21--2-orange.svg)](http://kotlinlang.org/) -[![LibGDX](https://img.shields.io/badge/libgdx-1.9.13-red.svg)](https://libgdx.badlogicgames.com/) +[![LibGDX](https://img.shields.io/badge/libgdx-1.9.13-red.svg)](https://libgdx.com/) [![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) [![KTX](.github/ktx-logo.png "KTX")](http://libktx.github.io) From 560c522bb9ce05eb18d6614c85c7f8352acd80f7 Mon Sep 17 00:00:00 2001 From: MJ Date: Fri, 5 Feb 2021 10:55:49 +0100 Subject: [PATCH 03/21] Kotlin updated to 1.4.30. #336 --- CHANGELOG.md | 2 ++ README.md | 2 +- gradle.properties | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32d388c5..2e4d8f09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ _See also: [the official LibGDX changelog](https://github.com/libgdx/libgdx/blob #### 1.9.13-SNAPSHOT +- **[UPDATE]** Updated to Kotlin 1.4.30. + #### 1.9.13-b1 - **[UPDATE]** Updated to LibGDX 1.9.13. diff --git a/README.md b/README.md index 5e7b8d2f..022241a9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![GitHub Build](https://github.com/libktx/ktx/workflows/build/badge.svg)](https://github.com/libktx/ktx/actions?query=workflow%3Abuild) -[![Kotlin](https://img.shields.io/badge/kotlin-1.4.21--2-orange.svg)](http://kotlinlang.org/) +[![Kotlin](https://img.shields.io/badge/kotlin-1.4.30-orange.svg)](http://kotlinlang.org/) [![LibGDX](https://img.shields.io/badge/libgdx-1.9.13-red.svg)](https://libgdx.com/) [![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) diff --git a/gradle.properties b/gradle.properties index 4a849460..d7bceecd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ libGroup=io.github.libktx -kotlinVersion=1.4.21-2 +kotlinVersion=1.4.30 junitPlatformVersion=1.2.0 dokkaVersion=1.4.10.2 From 2fd464b76775611ee764316303b6a3472c0186cb Mon Sep 17 00:00:00 2001 From: MJ Date: Fri, 5 Feb 2021 11:20:53 +0100 Subject: [PATCH 04/21] LibGDX updated to 1.9.14. #336 --- CHANGELOG.md | 5 ++++- README.md | 2 +- app/src/main/kotlin/ktx/app/graphics.kt | 11 ++++++----- app/src/test/kotlin/ktx/app/GraphicsTest.kt | 12 +++++++++++- buildSrc/src/main/kotlin/ktx/Versions.kt | 2 +- scene2d/src/test/kotlin/ktx/scene2d/widgetTest.kt | 2 +- version.txt | 2 +- vis/src/main/kotlin/ktx/scene2d/vis/factory.kt | 4 +--- vis/src/test/kotlin/ktx/scene2d/vis/widgetTest.kt | 2 +- 9 files changed, 27 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e4d8f09..1a6be50a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,11 @@ _See also: [the official LibGDX changelog](https://github.com/libgdx/libgdx/blob/master/CHANGES)._ -#### 1.9.13-SNAPSHOT +#### 1.9.14-SNAPSHOT +- **[UPDATE]** Updated to LibGDX 1.9.14. - **[UPDATE]** Updated to Kotlin 1.4.30. +- **[FEATURE]** (`ktx-app`) `clearScreen` now accepts additional `clearDepth` boolean parameter that controls whether +the `GL_DEPTH_BUFFER_BIT` is added to the mask. #### 1.9.13-b1 diff --git a/README.md b/README.md index 022241a9..3e2b24a6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ [![GitHub Build](https://github.com/libktx/ktx/workflows/build/badge.svg)](https://github.com/libktx/ktx/actions?query=workflow%3Abuild) [![Kotlin](https://img.shields.io/badge/kotlin-1.4.30-orange.svg)](http://kotlinlang.org/) -[![LibGDX](https://img.shields.io/badge/libgdx-1.9.13-red.svg)](https://libgdx.com/) +[![LibGDX](https://img.shields.io/badge/libgdx-1.9.14-red.svg)](https://libgdx.com/) [![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) [![KTX](.github/ktx-logo.png "KTX")](http://libktx.github.io) diff --git a/app/src/main/kotlin/ktx/app/graphics.kt b/app/src/main/kotlin/ktx/app/graphics.kt index 34bd7294..184da92f 100644 --- a/app/src/main/kotlin/ktx/app/graphics.kt +++ b/app/src/main/kotlin/ktx/app/graphics.kt @@ -1,17 +1,18 @@ package ktx.app -import com.badlogic.gdx.Gdx -import com.badlogic.gdx.graphics.GL20 +import com.badlogic.gdx.utils.ScreenUtils /** * Clears current screen with the selected color. Inlined to lower the total method count. Assumes alpha is 1f. + * Clears depth by default. * @param red red color value. * @param green green color value. * @param blue blue color value. * @param alpha color alpha value. Optional, defaults to 1f (non-transparent). + * @param clearDepth adds the GL_DEPTH_BUFFER_BIT mask if true. + * @see ScreenUtils.clear */ @Suppress("NOTHING_TO_INLINE") -inline fun clearScreen(red: Float, green: Float, blue: Float, alpha: Float = 1f) { - Gdx.gl.glClearColor(red, green, blue, alpha) - Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT or GL20.GL_DEPTH_BUFFER_BIT) +inline fun clearScreen(red: Float, green: Float, blue: Float, alpha: Float = 1f, clearDepth: Boolean = true) { + ScreenUtils.clear(red, green, blue, alpha, clearDepth) } diff --git a/app/src/test/kotlin/ktx/app/GraphicsTest.kt b/app/src/test/kotlin/ktx/app/GraphicsTest.kt index a39d3e9d..b4d85d78 100644 --- a/app/src/test/kotlin/ktx/app/GraphicsTest.kt +++ b/app/src/test/kotlin/ktx/app/GraphicsTest.kt @@ -21,7 +21,7 @@ class GraphicsTest { } @Test - fun `should clear with optional alpha`() { + fun `should clear screen with optional alpha`() { Gdx.gl = mock() clearScreen(0.25f, 0.5f, 0.75f) @@ -29,4 +29,14 @@ class GraphicsTest { verify(Gdx.gl).glClearColor(0.25f, 0.5f, 0.75f, 1f) verify(Gdx.gl).glClear(GL20.GL_COLOR_BUFFER_BIT or GL20.GL_DEPTH_BUFFER_BIT) } + + @Test + fun `should clear screen without the depth buffer`() { + Gdx.gl = mock() + + clearScreen(0.25f, 0.5f, 0.75f, alpha = 0.5f, clearDepth = false) + + verify(Gdx.gl).glClearColor(0.25f, 0.5f, 0.75f, 0.5f) + verify(Gdx.gl).glClear(GL20.GL_COLOR_BUFFER_BIT) + } } diff --git a/buildSrc/src/main/kotlin/ktx/Versions.kt b/buildSrc/src/main/kotlin/ktx/Versions.kt index 5b35f89f..bcb5fe42 100644 --- a/buildSrc/src/main/kotlin/ktx/Versions.kt +++ b/buildSrc/src/main/kotlin/ktx/Versions.kt @@ -1,6 +1,6 @@ package ktx -const val gdxVersion = "1.9.13" +const val gdxVersion = "1.9.14" const val kotlinCoroutinesVersion = "1.4.2" const val ashleyVersion = "1.7.3" diff --git a/scene2d/src/test/kotlin/ktx/scene2d/widgetTest.kt b/scene2d/src/test/kotlin/ktx/scene2d/widgetTest.kt index b63f53e5..d4f4e15b 100644 --- a/scene2d/src/test/kotlin/ktx/scene2d/widgetTest.kt +++ b/scene2d/src/test/kotlin/ktx/scene2d/widgetTest.kt @@ -493,7 +493,7 @@ class KTreeWidgetTest : NeedsLibGDX() { assertSame(actor, node.actor) assertSame(tree, node.tree) assertSame(tree, node.actor.parent) - assertSame(node, tree.nodes.first()) + assertSame(node, tree.rootNodes.first()) } } diff --git a/version.txt b/version.txt index f911a296..096cd194 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.9.13-SNAPSHOT +1.9.14-SNAPSHOT diff --git a/vis/src/main/kotlin/ktx/scene2d/vis/factory.kt b/vis/src/main/kotlin/ktx/scene2d/vis/factory.kt index 07ade737..d8802d93 100644 --- a/vis/src/main/kotlin/ktx/scene2d/vis/factory.kt +++ b/vis/src/main/kotlin/ktx/scene2d/vis/factory.kt @@ -943,8 +943,6 @@ inline fun KWidget.tabbedPane( contract { callsInPlace(init, InvocationKind.EXACTLY_ONCE) } val pane = KTabbedPane(style) val table = pane.table - var storage: S? = null - actor(table, { storage = it }) - pane.init(storage!!) + actor(table, { pane.init(it) }) return pane } diff --git a/vis/src/test/kotlin/ktx/scene2d/vis/widgetTest.kt b/vis/src/test/kotlin/ktx/scene2d/vis/widgetTest.kt index 657dd824..53eb4282 100644 --- a/vis/src/test/kotlin/ktx/scene2d/vis/widgetTest.kt +++ b/vis/src/test/kotlin/ktx/scene2d/vis/widgetTest.kt @@ -86,7 +86,7 @@ class KVisTreeTest : NeedsLibGDX() { assertSame(actor, node.actor) assertSame(tree, node.tree) assertSame(tree, node.actor.parent) - assertSame(node, tree.nodes.first()) + assertSame(node, tree.rootNodes.first()) } } From 3f4036531810f55e0502b702892dd4e67f14c407 Mon Sep 17 00:00:00 2001 From: MJ Date: Fri, 5 Feb 2021 21:45:09 +0100 Subject: [PATCH 05/21] VisUI updated to 1.4.10. #339 --- CHANGELOG.md | 1 + buildSrc/src/main/kotlin/ktx/Versions.kt | 2 +- vis/README.md | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a6be50a..45eee3ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ _See also: [the official LibGDX changelog](https://github.com/libgdx/libgdx/blob - **[UPDATE]** Updated to LibGDX 1.9.14. - **[UPDATE]** Updated to Kotlin 1.4.30. +- **[UPDATE]** Updated to VisUI 1.4.10. - **[FEATURE]** (`ktx-app`) `clearScreen` now accepts additional `clearDepth` boolean parameter that controls whether the `GL_DEPTH_BUFFER_BIT` is added to the mask. diff --git a/buildSrc/src/main/kotlin/ktx/Versions.kt b/buildSrc/src/main/kotlin/ktx/Versions.kt index bcb5fe42..c0eab545 100644 --- a/buildSrc/src/main/kotlin/ktx/Versions.kt +++ b/buildSrc/src/main/kotlin/ktx/Versions.kt @@ -4,7 +4,7 @@ const val gdxVersion = "1.9.14" const val kotlinCoroutinesVersion = "1.4.2" const val ashleyVersion = "1.7.3" -const val visUiVersion = "1.4.8" +const val visUiVersion = "1.4.10" const val spekVersion = "1.2.1" const val kotlinTestVersion = "2.0.7" diff --git a/vis/README.md b/vis/README.md index d7aba52a..3e4996c8 100644 --- a/vis/README.md +++ b/vis/README.md @@ -1,4 +1,4 @@ -[![VisUI](https://img.shields.io/badge/vis--ui-1.4.8-blue.svg)](https://github.com/kotcrab/vis-ui) +[![VisUI](https://img.shields.io/badge/vis--ui-1.4.10-blue.svg)](https://github.com/kotcrab/vis-ui) [![Maven Central](https://img.shields.io/maven-central/v/io.github.libktx/ktx-vis.svg)](https://search.maven.org/artifact/io.github.libktx/ktx-vis) # KTX: VisUI type-safe builders From 9aaaf2f77355b3195f9e50c9a6bf29b790851f98 Mon Sep 17 00:00:00 2001 From: MJ Date: Sun, 14 Feb 2021 11:16:47 +0100 Subject: [PATCH 06/21] AssetStorage snapshots for debugging. #344 --- CHANGELOG.md | 4 + assets-async/README.md | 1 + .../main/kotlin/ktx/assets/async/storage.kt | 73 +++++++++- .../ktx/assets/async/AssetStorageTest.kt | 128 +++++++++++++++++- 4 files changed, 204 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45eee3ac..e58e73bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ _See also: [the official LibGDX changelog](https://github.com/libgdx/libgdx/blob - **[UPDATE]** Updated to VisUI 1.4.10. - **[FEATURE]** (`ktx-app`) `clearScreen` now accepts additional `clearDepth` boolean parameter that controls whether the `GL_DEPTH_BUFFER_BIT` is added to the mask. +- **[FEATURE]** (`ktx-assets-async`) Added `AssetStorageSnapshot` class that stores a copy of `AssetStorage` state +for debugging purposes. Supports formatted string output with `prettyFormat`. +- **[FEATURE]** (`ktx-assets-async`) `AssetStorage` now includes `takeSnapshot` and `takeSnapshotAsync` methods that +allow to copy and inspect the internal state of the storage for debugging purposes. #### 1.9.13-b1 diff --git a/assets-async/README.md b/assets-async/README.md index be805414..a8521eb5 100644 --- a/assets-async/README.md +++ b/assets-async/README.md @@ -901,6 +901,7 @@ Closest equivalents in `AssetManager` and `AssetStorage` APIs: `finishLoading()` | N/A | `AssetStorage` does not provide methods that block the thread until all assets are loaded. Rely on `progress.isFinished` instead. `addAsset(String, Class, T)` | `add(String, T)` | `contains(String)` | `contains(String)`, `contains(Identifier)` | `AssetStorage` requires asset type, so the methods are generic. +`getDiagnostics` | `takeSnapshot`, `takeSnapshotAsync` | Returns a copy of the internal state. Returned `AssetStorageSnapshot` instance provides a `prettyPrint` method with formatted output. `setErrorHandler` | N/A, `try-catch` | With `AssetStorage` you can handle loading errors immediately with regular built-in `try-catch` syntax. Error listener is not required. `clear()` | `dispose()` | `AssetStorage.dispose` will not kill `AssetStorage` threads and can be safely used multiple times like `AssetManager.clear`. `dispose()` | `dispose()` | `AssetStorage` also provides a suspending variant with custom error handling. diff --git a/assets-async/src/main/kotlin/ktx/assets/async/storage.kt b/assets-async/src/main/kotlin/ktx/assets/async/storage.kt index 4f2dafa2..127bcb79 100644 --- a/assets-async/src/main/kotlin/ktx/assets/async/storage.kt +++ b/assets-async/src/main/kotlin/ktx/assets/async/storage.kt @@ -1194,6 +1194,46 @@ class AssetStorage( return dependencies?.map { it.identifier } ?: emptyList() } + /** + * Creates a deep copy of the internal assets storage. Returns an [AssetStorageSnapshot] + * with the current storage state. For debugging purposes. + * + * Note that the [CompletableDeferred] that store references to assets are preserved only + * when completed, otherwise new instances of [CompletableDeferred] are returned. Even if + * the [CompletableDeferred] instances are completed manually, they will not affect the + * internal state of the storage. + */ + suspend fun takeSnapshotAsync(): AssetStorageSnapshot { + lock.withLock { + return AssetStorageSnapshot( + assets = assets.mapValues { + @Suppress("UNCHECKED_CAST") val asset: Asset = it.value as Asset + val reference: CompletableDeferred = if (asset.reference.isCompleted || asset.reference.isCancelled) { + asset.reference + } else { + CompletableDeferred() + } + asset.copy(reference = reference) + } + ) + } + } + + /** + * Creates a deep copy of the internal assets storage. Returns an [AssetStorageSnapshot] + * with the current storage state. Blocks the current thread until the snapshot is complete. + * If assets are currently being loaded, avoid calling this method from within the rendering + * thread. For debugging purposes. + * + * Note that the [CompletableDeferred] that store references to assets are preserved only + * when completed, otherwise new instances of [CompletableDeferred] are returned. Even if + * the [CompletableDeferred] instances are completed manually, they will not affect the + * internal state of the storage. + */ + fun takeSnapshot(): AssetStorageSnapshot { + return runBlocking { takeSnapshotAsync() } + } + /** * Unloads all assets. Blocks current thread until are assets are unloaded. * Logs all disposing exceptions. @@ -1253,7 +1293,7 @@ class AssetStorage( /** * Container for a single asset of type [T] managed by [AssetStorage]. */ -internal data class Asset( +data class Asset( /** Stores asset loading data. */ val descriptor: AssetDescriptor, /** Unique identifier of the asset. */ @@ -1322,3 +1362,34 @@ data class Identifier( * is required, use [AssetDescriptor] for loading instead. */ fun AssetDescriptor.toIdentifier(): Identifier = Identifier(fileName, type) + +/** + * Stores a copy of state of an [AssetStorage]. For debugging purposes. + */ +data class AssetStorageSnapshot( + val assets: Map, Asset<*>> +) { + /** + * Prints [AssetStorage] state for debugging. Lists registered assets with their dependencies + * and reference counts. + */ + fun prettyPrint(): String { + return """[ +${assets.values + .sortedBy { it.identifier.type.name } + .sortedBy { it.identifier.path } + .joinToString(separator = "\n") { + """ "${it.identifier.path}" (${it.identifier.type.name}) { + references=${it.referenceCount}, + dependencies=${ + it.dependencies.joinToString(separator = ", ", prefix = "[", postfix = "]") { dependency -> + "\"${dependency.identifier.path}\" (${dependency.identifier.type.name})" + } + }, + loaded=${it.reference.isCompleted || it.reference.isCancelled}, + loader=${it.loader.javaClass.name}, + },""" +}} +]""" + } +} diff --git a/assets-async/src/test/kotlin/ktx/assets/async/AssetStorageTest.kt b/assets-async/src/test/kotlin/ktx/assets/async/AssetStorageTest.kt index 86f10757..daec372f 100644 --- a/assets-async/src/test/kotlin/ktx/assets/async/AssetStorageTest.kt +++ b/assets-async/src/test/kotlin/ktx/assets/async/AssetStorageTest.kt @@ -1513,7 +1513,7 @@ class AssetStorageTest : AsyncTest() { /** For loaders testing. */ open class FakeSyncLoader( - private val onLoad: (asset: FakeAsset) -> Unit, + private val onLoad: (asset: FakeAsset) -> Unit = {}, private val dependencies: GdxArray> = GdxArray.with() ) : SynchronousAssetLoader(ClasspathFileHandleResolver()) { @Suppress("UNCHECKED_CAST") @@ -2165,4 +2165,130 @@ class AssetStorageTest : AsyncTest() { // Should still count the reference, since load was called: assertEquals(2, storage.getReferenceCount(dependency)) } + + @Test + fun `should return empty snapshot of the storage state`() { + // Given: + val storage = AssetStorage(useDefaultLoaders = false) + + // When: + val snapshot = storage.takeSnapshot() + + // Then: + assertEquals(AssetStorageSnapshot(mapOf()), snapshot) + } + + @Test + fun `should return empty snapshot of the storage state asynchronously`() { + // Given: + val storage = AssetStorage(useDefaultLoaders = false) + + // When: + val snapshot = runBlocking { storage.takeSnapshotAsync() } + + // Then: + assertEquals(AssetStorageSnapshot(mapOf()), snapshot) + } + + @Test + fun `should take snapshot of the storage state`() { + // Given: + val storage = AssetStorage(useDefaultLoaders = false) + val path = "fake path" + val id = storage.getIdentifier(path) + val loadingStarted = CompletableFuture() + val loadingFinished = CompletableFuture() + storage.setLoader { + FakeSyncLoader( + onLoad = { + loadingStarted.complete(true) + loadingFinished.join() + } + ) + } + val reference = storage.loadAsync(path) + loadingStarted.join() + + // When: asset is not loaded yet: + var snapshot = storage.takeSnapshot() + + // Then: + assertFalse(storage.isLoaded(path)) + @Suppress("UNCHECKED_CAST") val asset: Asset = snapshot.assets[id] as Asset + assertEquals(id, asset.identifier) + assertFalse(asset.reference.isCompleted) + assertFalse(asset.reference.isCancelled) + assertEquals(1, asset.referenceCount) + assertEquals(listOf>(), asset.dependencies) + + // When: modifying asset loading manually: + asset.reference.complete(FakeAsset()) + asset.referenceCount = 10 + + // Then: internal state should be unmodified: + assertFalse(storage.isLoaded(path)) + assertEquals(1, storage.getReferenceCount(id)) + + // When: asset is loaded: + loadingFinished.complete(true) + val instance = runBlocking { reference.await() } + snapshot = storage.takeSnapshot() + + // Then: + assertTrue(storage.isLoaded(path)) + val loadedAsset = snapshot.assets[id]!! + assertEquals(id, loadedAsset.identifier) + assertTrue(loadedAsset.reference.isCompleted) + assertSame(instance, runBlocking { loadedAsset.reference.await() }) + assertEquals(1, loadedAsset.referenceCount) + assertEquals(listOf>(), loadedAsset.dependencies) + } + + @Test + fun `should pretty print snapshot`() { + // Given: + val firstId = Identifier("first.file", String::class.java) + @Suppress("UNCHECKED_CAST") val first = Asset( + descriptor = firstId.toAssetDescriptor(), + identifier = firstId, + reference = CompletableDeferred("test"), + dependencies = listOf(), + loader = FakeSyncLoader() as Loader, + referenceCount = 2 + ) + val secondId = Identifier("second.file", Int::class.java) + @Suppress("UNCHECKED_CAST") val second = Asset( + descriptor = secondId.toAssetDescriptor(), + identifier = secondId, + reference = CompletableDeferred(), + dependencies = listOf(first), + loader = FakeSyncLoader() as Loader, + referenceCount = 1 + ) + val snapshot = AssetStorageSnapshot( + assets = mapOf( + firstId to first, + secondId to second + ) + ) + + // When: + val output = snapshot.prettyPrint() + + // Then: + assertEquals("""[ + "first.file" (java.lang.String) { + references=2, + dependencies=[], + loaded=true, + loader=ktx.assets.async.AssetStorageTest${"$"}FakeSyncLoader, + }, + "second.file" (int) { + references=1, + dependencies=["first.file" (java.lang.String)], + loaded=false, + loader=ktx.assets.async.AssetStorageTest${"$"}FakeSyncLoader, + }, +]""", output) + } } From ada17b750279d7dcd77c79309049a52482d7863b Mon Sep 17 00:00:00 2001 From: MJ Date: Sun, 14 Feb 2021 13:41:30 +0100 Subject: [PATCH 07/21] Safe copy of asset dependencies. #344 --- .../main/kotlin/ktx/assets/async/storage.kt | 48 ++++++++++++------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/assets-async/src/main/kotlin/ktx/assets/async/storage.kt b/assets-async/src/main/kotlin/ktx/assets/async/storage.kt index 127bcb79..2ec52b21 100644 --- a/assets-async/src/main/kotlin/ktx/assets/async/storage.kt +++ b/assets-async/src/main/kotlin/ktx/assets/async/storage.kt @@ -1205,15 +1205,27 @@ class AssetStorage( */ suspend fun takeSnapshotAsync(): AssetStorageSnapshot { lock.withLock { + // Creating a safe copy of all assets without unfinished internal completable deferred instances: + val assetsCopy = assets.mapValues { + @Suppress("UNCHECKED_CAST") val asset: Asset = it.value as Asset + val reference: CompletableDeferred = if (asset.reference.isCompleted || asset.reference.isCancelled) { + asset.reference + } else { + CompletableDeferred() + } + asset.copy(reference = reference) + } + // Replacing dependencies with safe copies: return AssetStorageSnapshot( - assets = assets.mapValues { - @Suppress("UNCHECKED_CAST") val asset: Asset = it.value as Asset - val reference: CompletableDeferred = if (asset.reference.isCompleted || asset.reference.isCancelled) { - asset.reference + assets = assetsCopy.mapValues { + val asset = it.value + if (asset.dependencies.isEmpty()) { + asset } else { - CompletableDeferred() + asset.copy(dependencies = asset.dependencies.mapNotNull { dependency -> + assetsCopy[dependency.identifier] + }) } - asset.copy(reference = reference) } ) } @@ -1286,7 +1298,7 @@ class AssetStorage( } override fun toString(): String = "AssetStorage(assets=${ - assets.keys.sortedBy { it.path }.joinToString(separator = ", ", prefix = "[", postfix = "]") + assets.keys.sortedBy { it.path }.joinToString(separator = ", ", prefix = "[", postfix = "]") })" } @@ -1375,21 +1387,23 @@ data class AssetStorageSnapshot( */ fun prettyPrint(): String { return """[ -${assets.values - .sortedBy { it.identifier.type.name } - .sortedBy { it.identifier.path } - .joinToString(separator = "\n") { - """ "${it.identifier.path}" (${it.identifier.type.name}) { +${ + assets.values + .sortedBy { it.identifier.type.name } + .sortedBy { it.identifier.path } + .joinToString(separator = "\n") { + """ "${it.identifier.path}" (${it.identifier.type.name}) { references=${it.referenceCount}, dependencies=${ - it.dependencies.joinToString(separator = ", ", prefix = "[", postfix = "]") { dependency -> - "\"${dependency.identifier.path}\" (${dependency.identifier.type.name})" - } - }, + it.dependencies.joinToString(separator = ", ", prefix = "[", postfix = "]") { dependency -> + "\"${dependency.identifier.path}\" (${dependency.identifier.type.name})" + } + }, loaded=${it.reference.isCompleted || it.reference.isCancelled}, loader=${it.loader.javaClass.name}, },""" -}} + } + } ]""" } } From cc4c682fff02580fdb663aefe4b7155017faacdc Mon Sep 17 00:00:00 2001 From: Simon Klausner Date: Mon, 22 Feb 2021 15:00:22 +0100 Subject: [PATCH 08/21] add getOrPut extension function --- CHANGELOG.md | 4 + collections/README.md | 1 + .../src/main/kotlin/ktx/collections/maps.kt | 85 ++++++++++++++--- .../test/kotlin/ktx/collections/MapsTest.kt | 94 ++++++++++++++++--- 4 files changed, 156 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26ad10f8..fb2013df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ _See also: [the official LibGDX changelog](https://github.com/libgdx/libgdx/blob/master/CHANGES)._ +### 1.9.14-b1 + +- **[FEATURE]** (`ktx-collections`) Added `getOrPut` extension function for `maps` + #### 1.9.13-b1 - **[UPDATE]** Updated to LibGDX 1.9.13. diff --git a/collections/README.md b/collections/README.md index 2244b2e5..d032fa9d 100644 --- a/collections/README.md +++ b/collections/README.md @@ -96,6 +96,7 @@ has to be provided - since the method is inlined, no new lambda object will be c - `GdxArrayMap`: `com.badlogic.gdx.utils.ArrayMap` - All LibGDX map entries now feature `component1()` and `component2()` operator extension methods, so they can be destructed into a key and a value. +- `getOrPut` for `ObjectMap`, `IdentityMap`, `ArrayMap` and `IntMap` method that works like the Kotlin stdlib method #### Note diff --git a/collections/src/main/kotlin/ktx/collections/maps.kt b/collections/src/main/kotlin/ktx/collections/maps.kt index 916eba56..51a63ff9 100644 --- a/collections/src/main/kotlin/ktx/collections/maps.kt +++ b/collections/src/main/kotlin/ktx/collections/maps.kt @@ -2,16 +2,8 @@ package ktx.collections -import com.badlogic.gdx.utils.ArrayMap -import com.badlogic.gdx.utils.IdentityMap -import com.badlogic.gdx.utils.IntFloatMap -import com.badlogic.gdx.utils.IntIntMap -import com.badlogic.gdx.utils.IntMap -import com.badlogic.gdx.utils.LongMap -import com.badlogic.gdx.utils.ObjectIntMap -import com.badlogic.gdx.utils.ObjectMap +import com.badlogic.gdx.utils.* import com.badlogic.gdx.utils.ObjectMap.Entry -import com.badlogic.gdx.utils.ObjectSet /** Alias for [com.badlogic.gdx.utils.ObjectMap]. Added for consistency with other collections and factory methods. */ typealias GdxMap = ObjectMap @@ -32,8 +24,11 @@ const val defaultMapSize = 51 * @param loadFactor decides under what load the map is resized. * @return a new [ObjectMap]. */ -fun gdxMapOf(initialCapacity: Int = defaultMapSize, loadFactor: Float = defaultLoadFactor): GdxMap = - GdxMap(initialCapacity, loadFactor) +fun gdxMapOf( + initialCapacity: Int = defaultMapSize, + loadFactor: Float = defaultLoadFactor +): GdxMap = + GdxMap(initialCapacity, loadFactor) /** * @param keysToValues will be added to the map. @@ -176,7 +171,7 @@ inline fun Array.toGdxMap( * @return a new [IdentityMap], which compares keys by references. */ fun gdxIdentityMapOf(initialCapacity: Int = defaultMapSize, loadFactor: Float = defaultLoadFactor): - GdxIdentityMap = IdentityMap(initialCapacity, loadFactor) + GdxIdentityMap = IdentityMap(initialCapacity, loadFactor) /** * @param keysToValues will be added to the map. @@ -229,7 +224,7 @@ inline fun IdentityMap.iterate(action: (Key, Value, Mut * @return a new [IntIntMap] with primitive int keys and values. */ fun gdxIntIntMap(initialCapacity: Int = defaultMapSize, loadFactor: Float = defaultLoadFactor): IntIntMap = - IntIntMap(initialCapacity, loadFactor) + IntIntMap(initialCapacity, loadFactor) /** * @param key a value might be assigned to this key and stored in the map. @@ -257,7 +252,7 @@ operator fun IntIntMap.get(key: Int): Int = this.get(key, 0) * @return a new [IntFloatMap] with primitive int keys and primitive float values. */ fun gdxIntFloatMap(initialCapacity: Int = defaultMapSize, loadFactor: Float = defaultLoadFactor): IntFloatMap = - IntFloatMap(initialCapacity, loadFactor) + IntFloatMap(initialCapacity, loadFactor) /** * @param key a value might be assigned to this key and stored in the map. @@ -285,7 +280,7 @@ operator fun IntFloatMap.get(key: Int): Float = this.get(key, 0f) * @return a new [IntMap] with primitive int keys. */ fun gdxIntMap(initialCapacity: Int = defaultMapSize, loadFactor: Float = defaultLoadFactor): IntMap = - IntMap(initialCapacity, loadFactor) + IntMap(initialCapacity, loadFactor) /** * @param key a value might be assigned to this key and stored in the map. @@ -422,3 +417,63 @@ inline fun > GdxMap.flatten(): inline fun GdxMap.flatMap(transform: (Entry) -> Iterable): GdxArray { return this.map(transform).flatten() } + +/** + * Returns the value for the given [key]. If the [key] is not found in the map, + * calls the [defaultValue] function, puts its result into the map under the given [key] and returns it. + */ +inline fun GdxMap.getOrPut(key: Key, defaultValue: () -> Value): Value { + var value = this[key] + + if (value == null) { + value = defaultValue() + this[key] = value + } + + return value +} + +/** + * Returns the value for the given [key]. If the [key] is not found in the map, + * calls the [defaultValue] function, puts its result into the map under the given [key] and returns it. + */ +inline fun GdxIdentityMap.getOrPut(key: Key, defaultValue: () -> Value): Value { + var value = this[key] + + if (value == null) { + value = defaultValue() + this[key] = value + } + + return value +} + +/** + * Returns the value for the given [key]. If the [key] is not found in the map, + * calls the [defaultValue] function, puts its result into the map under the given [key] and returns it. + */ +inline fun GdxArrayMap.getOrPut(key: Key, defaultValue: () -> Value): Value { + var value = this[key] + + if (value == null) { + value = defaultValue() + this[key] = value + } + + return value +} + +/** + * Returns the value for the given [key]. If the [key] is not found in the map, + * calls the [defaultValue] function, puts its result into the map under the given [key] and returns it. + */ +inline fun IntMap.getOrPut(key: Int, defaultValue: () -> Value): Value { + var value = this[key] + + if (value == null) { + value = defaultValue() + this[key] = value + } + + return value +} diff --git a/collections/src/test/kotlin/ktx/collections/MapsTest.kt b/collections/src/test/kotlin/ktx/collections/MapsTest.kt index 716111d3..67039a91 100644 --- a/collections/src/test/kotlin/ktx/collections/MapsTest.kt +++ b/collections/src/test/kotlin/ktx/collections/MapsTest.kt @@ -1,20 +1,10 @@ package ktx.collections +import com.badlogic.gdx.utils.* import com.badlogic.gdx.utils.Array -import com.badlogic.gdx.utils.IdentityMap -import com.badlogic.gdx.utils.IntFloatMap -import com.badlogic.gdx.utils.IntIntMap -import com.badlogic.gdx.utils.IntMap -import com.badlogic.gdx.utils.LongMap -import com.badlogic.gdx.utils.ObjectIntMap -import com.badlogic.gdx.utils.ObjectMap -import com.badlogic.gdx.utils.ObjectSet -import java.util.LinkedList -import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Assert.assertNotNull -import org.junit.Assert.assertTrue +import org.junit.Assert.* import org.junit.Test +import java.util.* /** * Tests utilities for LibGDX custom HashMap equivalent - [ObjectMap]. @@ -393,4 +383,82 @@ class MapsTest { assertEquals("Three", gdxArrayMap[3]) assertEquals("Four", gdxArrayMap[4]) } + + @Test + fun `should return existing value for GdxMap when key exists`() { + val map = gdxMapOf("42" to 42) + + val actual = map.getOrPut("42") { 43 } + + assertEquals(42, actual) + } + + @Test + fun `should return and put default value to GdxMap when key does not exist`() { + val map = gdxMapOf() + + val actual = map.getOrPut("42") { 43 } + + assertEquals(43, actual) + assertTrue("42" in map) + } + + @Test + fun `should return existing value for GdxIdentityMap when key exists`() { + val map = gdxIdentityMapOf("42" to 42) + + val actual = map.getOrPut("42") { 43 } + + assertEquals(42, actual) + } + + @Test + fun `should return and put default value to GdxIdentityMap when key does not exist`() { + val map = gdxIdentityMapOf() + + val actual = map.getOrPut("42") { 43 } + + assertEquals(43, actual) + assertTrue("42" in map) + } + + @Test + fun `should return existing value for GdxArrayMap when key exists`() { + val map = GdxArrayMap() + map["42"] = 42 + + val actual = map.getOrPut("42") { 43 } + + assertEquals(42, actual) + } + + @Test + fun `should return and put default value to GdxArrayMap when key does not exist`() { + val map = GdxArrayMap() + + val actual = map.getOrPut("42") { 43 } + + assertEquals(43, actual) + assertTrue(map.containsKey("42")) + } + + @Test + fun `should return existing value for IntMap when key exists`() { + val map = IntMap() + map[42] = "42" + + val actual = map.getOrPut(42) { "43" } + + assertEquals("42", actual) + } + + @Test + fun `should return and put default value to IntMap when key does not exist`() { + val map = IntMap() + + val actual = map.getOrPut(42) { "43" } + + assertEquals("43", actual) + assertTrue(42 in map) + } } From 3f0910565890c3270bae98f045b3d0d1581b5ccd Mon Sep 17 00:00:00 2001 From: Simon Klausner Date: Mon, 22 Feb 2021 15:06:31 +0100 Subject: [PATCH 09/21] fix CHANGELOG.md --- CHANGELOG.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb2013df..2fe1b608 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,16 @@ _See also: [the official LibGDX changelog](https://github.com/libgdx/libgdx/blob/master/CHANGES)._ -### 1.9.14-b1 - +#### 1.9.14-SNAPSHOT + +- **[UPDATE]** Updated to LibGDX 1.9.14. +- **[UPDATE]** Updated to Kotlin 1.4.30. +- **[UPDATE]** Updated to VisUI 1.4.10. +- **[FEATURE]** (`ktx-app`) `clearScreen` now accepts additional `clearDepth` boolean parameter that controls whether + the `GL_DEPTH_BUFFER_BIT` is added to the mask. +- **[FEATURE]** (`ktx-assets-async`) Added `AssetStorageSnapshot` class that stores a copy of `AssetStorage` state + for debugging purposes. Supports formatted string output with `prettyFormat`. +- **[FEATURE]** (`ktx-assets-async`) `AssetStorage` now includes `takeSnapshot` and `takeSnapshotAsync` methods that + allow to copy and inspect the internal state of the storage for debugging purposes. - **[FEATURE]** (`ktx-collections`) Added `getOrPut` extension function for `maps` #### 1.9.13-b1 From 8453a98260f36292b63fe694e2c793c9d5c58f13 Mon Sep 17 00:00:00 2001 From: Simon Klausner Date: Mon, 22 Feb 2021 15:12:53 +0100 Subject: [PATCH 10/21] fix formatting and imports --- .../src/main/kotlin/ktx/collections/maps.kt | 25 +++++++++++-------- .../test/kotlin/ktx/collections/MapsTest.kt | 16 +++++++++--- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/collections/src/main/kotlin/ktx/collections/maps.kt b/collections/src/main/kotlin/ktx/collections/maps.kt index 51a63ff9..85a70ea8 100644 --- a/collections/src/main/kotlin/ktx/collections/maps.kt +++ b/collections/src/main/kotlin/ktx/collections/maps.kt @@ -2,8 +2,16 @@ package ktx.collections -import com.badlogic.gdx.utils.* +import com.badlogic.gdx.utils.ArrayMap +import com.badlogic.gdx.utils.IdentityMap +import com.badlogic.gdx.utils.IntFloatMap +import com.badlogic.gdx.utils.IntIntMap +import com.badlogic.gdx.utils.IntMap +import com.badlogic.gdx.utils.LongMap +import com.badlogic.gdx.utils.ObjectIntMap +import com.badlogic.gdx.utils.ObjectMap import com.badlogic.gdx.utils.ObjectMap.Entry +import com.badlogic.gdx.utils.ObjectSet /** Alias for [com.badlogic.gdx.utils.ObjectMap]. Added for consistency with other collections and factory methods. */ typealias GdxMap = ObjectMap @@ -24,11 +32,8 @@ const val defaultMapSize = 51 * @param loadFactor decides under what load the map is resized. * @return a new [ObjectMap]. */ -fun gdxMapOf( - initialCapacity: Int = defaultMapSize, - loadFactor: Float = defaultLoadFactor -): GdxMap = - GdxMap(initialCapacity, loadFactor) +fun gdxMapOf(initialCapacity: Int = defaultMapSize, loadFactor: Float = defaultLoadFactor): GdxMap = + GdxMap(initialCapacity, loadFactor) /** * @param keysToValues will be added to the map. @@ -171,7 +176,7 @@ inline fun Array.toGdxMap( * @return a new [IdentityMap], which compares keys by references. */ fun gdxIdentityMapOf(initialCapacity: Int = defaultMapSize, loadFactor: Float = defaultLoadFactor): - GdxIdentityMap = IdentityMap(initialCapacity, loadFactor) + GdxIdentityMap = IdentityMap(initialCapacity, loadFactor) /** * @param keysToValues will be added to the map. @@ -224,7 +229,7 @@ inline fun IdentityMap.iterate(action: (Key, Value, Mut * @return a new [IntIntMap] with primitive int keys and values. */ fun gdxIntIntMap(initialCapacity: Int = defaultMapSize, loadFactor: Float = defaultLoadFactor): IntIntMap = - IntIntMap(initialCapacity, loadFactor) + IntIntMap(initialCapacity, loadFactor) /** * @param key a value might be assigned to this key and stored in the map. @@ -252,7 +257,7 @@ operator fun IntIntMap.get(key: Int): Int = this.get(key, 0) * @return a new [IntFloatMap] with primitive int keys and primitive float values. */ fun gdxIntFloatMap(initialCapacity: Int = defaultMapSize, loadFactor: Float = defaultLoadFactor): IntFloatMap = - IntFloatMap(initialCapacity, loadFactor) + IntFloatMap(initialCapacity, loadFactor) /** * @param key a value might be assigned to this key and stored in the map. @@ -280,7 +285,7 @@ operator fun IntFloatMap.get(key: Int): Float = this.get(key, 0f) * @return a new [IntMap] with primitive int keys. */ fun gdxIntMap(initialCapacity: Int = defaultMapSize, loadFactor: Float = defaultLoadFactor): IntMap = - IntMap(initialCapacity, loadFactor) + IntMap(initialCapacity, loadFactor) /** * @param key a value might be assigned to this key and stored in the map. diff --git a/collections/src/test/kotlin/ktx/collections/MapsTest.kt b/collections/src/test/kotlin/ktx/collections/MapsTest.kt index 67039a91..ce49d133 100644 --- a/collections/src/test/kotlin/ktx/collections/MapsTest.kt +++ b/collections/src/test/kotlin/ktx/collections/MapsTest.kt @@ -1,10 +1,20 @@ package ktx.collections -import com.badlogic.gdx.utils.* import com.badlogic.gdx.utils.Array -import org.junit.Assert.* +import com.badlogic.gdx.utils.IdentityMap +import com.badlogic.gdx.utils.IntFloatMap +import com.badlogic.gdx.utils.IntIntMap +import com.badlogic.gdx.utils.IntMap +import com.badlogic.gdx.utils.LongMap +import com.badlogic.gdx.utils.ObjectIntMap +import com.badlogic.gdx.utils.ObjectMap +import com.badlogic.gdx.utils.ObjectSet +import java.util.LinkedList +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertTrue import org.junit.Test -import java.util.* /** * Tests utilities for LibGDX custom HashMap equivalent - [ObjectMap]. From 6e7b7ffbdecd1a15ed786efb9a0eb54a801b857c Mon Sep 17 00:00:00 2001 From: Simon Klausner Date: Mon, 22 Feb 2021 18:28:20 +0100 Subject: [PATCH 11/21] update README.md --- collections/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/collections/README.md b/collections/README.md index d032fa9d..09e83f02 100644 --- a/collections/README.md +++ b/collections/README.md @@ -96,7 +96,8 @@ has to be provided - since the method is inlined, no new lambda object will be c - `GdxArrayMap`: `com.badlogic.gdx.utils.ArrayMap` - All LibGDX map entries now feature `component1()` and `component2()` operator extension methods, so they can be destructed into a key and a value. -- `getOrPut` for `ObjectMap`, `IdentityMap`, `ArrayMap` and `IntMap` method that works like the Kotlin stdlib method +- `getOrPut` for `ObjectMap`, `IdentityMap`, `ArrayMap` and `IntMap` method to get an existing value to a given key +or if it does not exist, create a default value, add it to the map and return it. #### Note From 07c0eb5e8e07be1b9dc10d6ca990aa4660d8e794 Mon Sep 17 00:00:00 2001 From: Simon Klausner Date: Mon, 22 Feb 2021 18:54:42 +0100 Subject: [PATCH 12/21] change getOrPut implementation to work properly with null values in maps --- .../src/main/kotlin/ktx/collections/maps.kt | 52 +++++++++---------- .../test/kotlin/ktx/collections/MapsTest.kt | 51 ++++++++++++++++++ 2 files changed, 75 insertions(+), 28 deletions(-) diff --git a/collections/src/main/kotlin/ktx/collections/maps.kt b/collections/src/main/kotlin/ktx/collections/maps.kt index 85a70ea8..9a0313e9 100644 --- a/collections/src/main/kotlin/ktx/collections/maps.kt +++ b/collections/src/main/kotlin/ktx/collections/maps.kt @@ -428,14 +428,13 @@ inline fun GdxMap.flatMap(transform: (Entry GdxMap.getOrPut(key: Key, defaultValue: () -> Value): Value { - var value = this[key] - - if (value == null) { - value = defaultValue() - this[key] = value + return if (key in this) { + this[key] + } else { + val newValue = defaultValue() + this[key] = newValue + newValue } - - return value } /** @@ -443,14 +442,13 @@ inline fun GdxMap.getOrPut(key: Key, defaultValue: () - * calls the [defaultValue] function, puts its result into the map under the given [key] and returns it. */ inline fun GdxIdentityMap.getOrPut(key: Key, defaultValue: () -> Value): Value { - var value = this[key] - - if (value == null) { - value = defaultValue() - this[key] = value + return if (key in this) { + this[key] + } else { + val newValue = defaultValue() + this[key] = newValue + newValue } - - return value } /** @@ -458,14 +456,13 @@ inline fun GdxIdentityMap.getOrPut(key: Key, defaultVal * calls the [defaultValue] function, puts its result into the map under the given [key] and returns it. */ inline fun GdxArrayMap.getOrPut(key: Key, defaultValue: () -> Value): Value { - var value = this[key] - - if (value == null) { - value = defaultValue() - this[key] = value + return if (this.containsKey(key)) { + this[key] + } else { + val newValue = defaultValue() + this[key] = newValue + newValue } - - return value } /** @@ -473,12 +470,11 @@ inline fun GdxArrayMap.getOrPut(key: Key, defaultValue: * calls the [defaultValue] function, puts its result into the map under the given [key] and returns it. */ inline fun IntMap.getOrPut(key: Int, defaultValue: () -> Value): Value { - var value = this[key] - - if (value == null) { - value = defaultValue() - this[key] = value + return if (key in this) { + this[key] + } else { + val newValue = defaultValue() + this[key] = newValue + newValue } - - return value } diff --git a/collections/src/test/kotlin/ktx/collections/MapsTest.kt b/collections/src/test/kotlin/ktx/collections/MapsTest.kt index ce49d133..3db2cb8d 100644 --- a/collections/src/test/kotlin/ktx/collections/MapsTest.kt +++ b/collections/src/test/kotlin/ktx/collections/MapsTest.kt @@ -13,6 +13,7 @@ import java.util.LinkedList import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Test @@ -401,6 +402,7 @@ class MapsTest { val actual = map.getOrPut("42") { 43 } assertEquals(42, actual) + assertEquals(42, map["42"]) } @Test @@ -411,6 +413,17 @@ class MapsTest { assertEquals(43, actual) assertTrue("42" in map) + assertEquals(43, map["42"]) + } + + @Test + fun `should return null for GdxMap when null is stored for given key`() { + val map = gdxMapOf("42" to null) + + val actual = map.getOrPut("42") { 43 } + + assertNull(actual) + assertEquals(null, map["42"]) } @Test @@ -420,6 +433,7 @@ class MapsTest { val actual = map.getOrPut("42") { 43 } assertEquals(42, actual) + assertEquals(42, map["42"]) } @Test @@ -430,6 +444,17 @@ class MapsTest { assertEquals(43, actual) assertTrue("42" in map) + assertEquals(43, map["42"]) + } + + @Test + fun `should return null for GdxIdentityMap when null is stored for given key`() { + val map = gdxIdentityMapOf("42" to null) + + val actual = map.getOrPut("42") { 43 } + + assertNull(actual) + assertEquals(null, map["42"]) } @Test @@ -440,6 +465,7 @@ class MapsTest { val actual = map.getOrPut("42") { 43 } assertEquals(42, actual) + assertEquals(42, map["42"]) } @Test @@ -450,6 +476,18 @@ class MapsTest { assertEquals(43, actual) assertTrue(map.containsKey("42")) + assertEquals(43, map["42"]) + } + + @Test + fun `should return null for GdxArrayMap when null is stored for given key`() { + val map = GdxArrayMap() + map["42"] = null + + val actual = map.getOrPut("42") { 43 } + + assertNull(actual) + assertEquals(null, map["42"]) } @Test @@ -460,6 +498,7 @@ class MapsTest { val actual = map.getOrPut(42) { "43" } assertEquals("42", actual) + assertEquals("42", map[42]) } @Test @@ -470,5 +509,17 @@ class MapsTest { assertEquals("43", actual) assertTrue(42 in map) + assertEquals("43", map[42]) + } + + @Test + fun `should return null for IntMap when null is stored for given key`() { + val map = IntMap() + map.put(42, null) + + val actual = map.getOrPut(42) { "43" } + + assertNull(actual) + assertEquals(null, map[42]) } } From 30b40e2a5ec138459d5e4ca68cd49b6079fd580b Mon Sep 17 00:00:00 2001 From: Simon Klausner Date: Mon, 22 Feb 2021 19:08:17 +0100 Subject: [PATCH 13/21] minor improvement for getOrPut to only locate the key once if the value is already existing --- .../src/main/kotlin/ktx/collections/maps.kt | 52 ++++++++++--------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/collections/src/main/kotlin/ktx/collections/maps.kt b/collections/src/main/kotlin/ktx/collections/maps.kt index 9a0313e9..f412b45a 100644 --- a/collections/src/main/kotlin/ktx/collections/maps.kt +++ b/collections/src/main/kotlin/ktx/collections/maps.kt @@ -428,13 +428,14 @@ inline fun GdxMap.flatMap(transform: (Entry GdxMap.getOrPut(key: Key, defaultValue: () -> Value): Value { - return if (key in this) { - this[key] - } else { - val newValue = defaultValue() - this[key] = newValue - newValue + var value = this[key] + + if (value == null && key !in this) { + value = defaultValue() + this[key] = value } + + return value } /** @@ -442,13 +443,14 @@ inline fun GdxMap.getOrPut(key: Key, defaultValue: () - * calls the [defaultValue] function, puts its result into the map under the given [key] and returns it. */ inline fun GdxIdentityMap.getOrPut(key: Key, defaultValue: () -> Value): Value { - return if (key in this) { - this[key] - } else { - val newValue = defaultValue() - this[key] = newValue - newValue + var value = this[key] + + if (value == null && key !in this) { + value = defaultValue() + this[key] = value } + + return value } /** @@ -456,13 +458,14 @@ inline fun GdxIdentityMap.getOrPut(key: Key, defaultVal * calls the [defaultValue] function, puts its result into the map under the given [key] and returns it. */ inline fun GdxArrayMap.getOrPut(key: Key, defaultValue: () -> Value): Value { - return if (this.containsKey(key)) { - this[key] - } else { - val newValue = defaultValue() - this[key] = newValue - newValue + var value = this[key] + + if (value == null && !this.containsKey(key)) { + value = defaultValue() + this[key] = value } + + return value } /** @@ -470,11 +473,12 @@ inline fun GdxArrayMap.getOrPut(key: Key, defaultValue: * calls the [defaultValue] function, puts its result into the map under the given [key] and returns it. */ inline fun IntMap.getOrPut(key: Int, defaultValue: () -> Value): Value { - return if (key in this) { - this[key] - } else { - val newValue = defaultValue() - this[key] = newValue - newValue + var value = this[key] + + if (value == null && key !in this) { + value = defaultValue() + this[key] = value } + + return value } From 290db9dd07df06304e139c2a3ac5c77b72284d6e Mon Sep 17 00:00:00 2001 From: Simon Klausner Date: Tue, 23 Feb 2021 07:12:42 +0100 Subject: [PATCH 14/21] add documentation and test cases for null keys --- .../src/main/kotlin/ktx/collections/maps.kt | 4 ++++ .../test/kotlin/ktx/collections/MapsTest.kt | 23 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/collections/src/main/kotlin/ktx/collections/maps.kt b/collections/src/main/kotlin/ktx/collections/maps.kt index f412b45a..596ec879 100644 --- a/collections/src/main/kotlin/ktx/collections/maps.kt +++ b/collections/src/main/kotlin/ktx/collections/maps.kt @@ -426,6 +426,8 @@ inline fun GdxMap.flatMap(transform: (Entry GdxMap.getOrPut(key: Key, defaultValue: () -> Value): Value { var value = this[key] @@ -441,6 +443,8 @@ inline fun GdxMap.getOrPut(key: Key, defaultValue: () - /** * Returns the value for the given [key]. If the [key] is not found in the map, * calls the [defaultValue] function, puts its result into the map under the given [key] and returns it. + * + * Throws an [IllegalArgumentException][java.lang.IllegalArgumentException] when key is null. */ inline fun GdxIdentityMap.getOrPut(key: Key, defaultValue: () -> Value): Value { var value = this[key] diff --git a/collections/src/test/kotlin/ktx/collections/MapsTest.kt b/collections/src/test/kotlin/ktx/collections/MapsTest.kt index 3db2cb8d..05208e1c 100644 --- a/collections/src/test/kotlin/ktx/collections/MapsTest.kt +++ b/collections/src/test/kotlin/ktx/collections/MapsTest.kt @@ -16,6 +16,7 @@ import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Test +import java.lang.IllegalArgumentException /** * Tests utilities for LibGDX custom HashMap equivalent - [ObjectMap]. @@ -426,6 +427,12 @@ class MapsTest { assertEquals(null, map["42"]) } + @Test(expected = IllegalArgumentException::class) + fun `should throw an IllegalArgumentException when getOrPut is called with null key for GdxMap`() { + val map = gdxMapOf() + map.getOrPut(null) { "42" } + } + @Test fun `should return existing value for GdxIdentityMap when key exists`() { val map = gdxIdentityMapOf("42" to 42) @@ -457,6 +464,12 @@ class MapsTest { assertEquals(null, map["42"]) } + @Test(expected = IllegalArgumentException::class) + fun `should throw an IllegalArgumentException when getOrPut is called with null key for GdxIdentityMap`() { + val map = gdxIdentityMapOf() + map.getOrPut(null) { "42" } + } + @Test fun `should return existing value for GdxArrayMap when key exists`() { val map = GdxArrayMap() @@ -490,6 +503,16 @@ class MapsTest { assertEquals(null, map["42"]) } + @Test + fun `should return and put default value to GdxArrayMap when key is null`() { + val map = GdxArrayMap() + + val actual = map.getOrPut(null) { "42" } + + assertEquals("42", actual) + assertEquals("42", map[null]) + } + @Test fun `should return existing value for IntMap when key exists`() { val map = IntMap() From d74741b5acfb4c95f0c8ca0ac9c32923be468d66 Mon Sep 17 00:00:00 2001 From: MJ Date: Tue, 23 Feb 2021 09:11:51 +0100 Subject: [PATCH 15/21] Test refactor for getOrPut. #341 #345 --- CHANGELOG.md | 3 ++- .../src/test/kotlin/ktx/collections/MapsTest.kt | 17 ++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c32f9103..76f2b46d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,8 @@ the `GL_DEPTH_BUFFER_BIT` is added to the mask. for debugging purposes. Supports formatted string output with `prettyFormat`. - **[FEATURE]** (`ktx-assets-async`) `AssetStorage` now includes `takeSnapshot` and `takeSnapshotAsync` methods that allow to copy and inspect the internal state of the storage for debugging purposes. -- **[FEATURE]** (`ktx-collections`) Added `getOrPut` extension function for `maps` +- **[FEATURE]** (`ktx-collections`) Added `getOrPut` extension function for LibGDX map collections including +`ObjectMap`, `IdentityMap`, `ArrayMap` and `IntMap`. #### 1.9.13-b1 diff --git a/collections/src/test/kotlin/ktx/collections/MapsTest.kt b/collections/src/test/kotlin/ktx/collections/MapsTest.kt index 05208e1c..8f8d5ff8 100644 --- a/collections/src/test/kotlin/ktx/collections/MapsTest.kt +++ b/collections/src/test/kotlin/ktx/collections/MapsTest.kt @@ -9,6 +9,8 @@ import com.badlogic.gdx.utils.LongMap import com.badlogic.gdx.utils.ObjectIntMap import com.badlogic.gdx.utils.ObjectMap import com.badlogic.gdx.utils.ObjectSet +import io.kotlintest.matchers.shouldThrow +import java.lang.IllegalArgumentException import java.util.LinkedList import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse @@ -16,7 +18,6 @@ import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Test -import java.lang.IllegalArgumentException /** * Tests utilities for LibGDX custom HashMap equivalent - [ObjectMap]. @@ -427,10 +428,13 @@ class MapsTest { assertEquals(null, map["42"]) } - @Test(expected = IllegalArgumentException::class) + @Test fun `should throw an IllegalArgumentException when getOrPut is called with null key for GdxMap`() { val map = gdxMapOf() - map.getOrPut(null) { "42" } + + shouldThrow { + map.getOrPut(null) { "42" } + } } @Test @@ -464,10 +468,13 @@ class MapsTest { assertEquals(null, map["42"]) } - @Test(expected = IllegalArgumentException::class) + @Test fun `should throw an IllegalArgumentException when getOrPut is called with null key for GdxIdentityMap`() { val map = gdxIdentityMapOf() - map.getOrPut(null) { "42" } + + shouldThrow { + map.getOrPut(null) { "42" } + } } @Test From 46ca2f4e12b83c262bc74bc8f0873b462447a08a Mon Sep 17 00:00:00 2001 From: MJ Date: Tue, 23 Feb 2021 09:19:10 +0100 Subject: [PATCH 16/21] VisUI updated to 1.4.11. #338 --- CHANGELOG.md | 2 +- buildSrc/src/main/kotlin/ktx/Versions.kt | 2 +- vis/README.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76f2b46d..e8f620fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ _See also: [the official LibGDX changelog](https://github.com/libgdx/libgdx/blob - **[UPDATE]** Updated to LibGDX 1.9.14. - **[UPDATE]** Updated to Kotlin 1.4.30. -- **[UPDATE]** Updated to VisUI 1.4.10. +- **[UPDATE]** Updated to VisUI 1.4.11. - **[FEATURE]** (`ktx-app`) `clearScreen` now accepts additional `clearDepth` boolean parameter that controls whether the `GL_DEPTH_BUFFER_BIT` is added to the mask. - **[FEATURE]** (`ktx-assets-async`) Added `AssetStorageSnapshot` class that stores a copy of `AssetStorage` state diff --git a/buildSrc/src/main/kotlin/ktx/Versions.kt b/buildSrc/src/main/kotlin/ktx/Versions.kt index c0eab545..3cfbeb1a 100644 --- a/buildSrc/src/main/kotlin/ktx/Versions.kt +++ b/buildSrc/src/main/kotlin/ktx/Versions.kt @@ -4,7 +4,7 @@ const val gdxVersion = "1.9.14" const val kotlinCoroutinesVersion = "1.4.2" const val ashleyVersion = "1.7.3" -const val visUiVersion = "1.4.10" +const val visUiVersion = "1.4.11" const val spekVersion = "1.2.1" const val kotlinTestVersion = "2.0.7" diff --git a/vis/README.md b/vis/README.md index 3e4996c8..6cbc9104 100644 --- a/vis/README.md +++ b/vis/README.md @@ -1,4 +1,4 @@ -[![VisUI](https://img.shields.io/badge/vis--ui-1.4.10-blue.svg)](https://github.com/kotcrab/vis-ui) +[![VisUI](https://img.shields.io/badge/vis--ui-1.4.11-blue.svg)](https://github.com/kotcrab/vis-ui) [![Maven Central](https://img.shields.io/maven-central/v/io.github.libktx/ktx-vis.svg)](https://search.maven.org/artifact/io.github.libktx/ktx-vis) # KTX: VisUI type-safe builders From e737368d6c3e15975c3d96f7ef47f562e55e3cb4 Mon Sep 17 00:00:00 2001 From: MJ Date: Tue, 23 Feb 2021 10:17:09 +0100 Subject: [PATCH 17/21] AssetStorage documentation improvements. #259 --- assets-async/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/assets-async/README.md b/assets-async/README.md index a8521eb5..f641b749 100644 --- a/assets-async/README.md +++ b/assets-async/README.md @@ -338,7 +338,7 @@ fun loadAssets() { val assetStorage = AssetStorage(asyncContext = newAsyncContext(threads = 2)) // Instead of using Kotlin's built-in `async`, you can also use - // the `loadAsync` method of AssetStorage with is a shortcut for + // the `loadAsync` method of AssetStorage which is a shortcut for // `async(assetStorage.asyncContext) { assetStorage.load }`: val texture = assetStorage.loadAsync("images/logo.png") val font = assetStorage.loadAsync("fonts/font.fnt") @@ -589,7 +589,7 @@ fun createCustomAssetStorage(): AssetStorage { It is completely safe to call `load` and `loadAsync` multiple times with the same asset data, even just to obtain asset instances. In that sense, they can be used as an alternative to `getAsync` inside coroutines. -Instead of loading the same asset multiple times, `AssetStorage` will just increase the reference count +Instead of loading the same asset multiple times, `AssetStorage` will just increase the count of references to the asset and return the same instance on each request. This also works concurrently - the storage will always load just _one_ asset instance, regardless of how many different threads and coroutines called `load` in parallel. @@ -883,8 +883,8 @@ Closest equivalents in `AssetManager` and `AssetStorage` APIs: `AssetManager` | `AssetStorage` | Note :---: | :---: | --- -`get(String)` | `get(String)` | -`get(String, Class)` | `get(Identifier)` | +`get(String)` | `get(String)` | `AssetStorage` uses reified types to prevent from runtime class cast exceptions. +`get(String, Class)` | `get(Identifier)` | `Identifier` pairs file path and asset type to identify an asset. `get(AssetDescriptor)` | `get(AssetDescriptor)` | `load(String, Class)` | `loadAsync(String)` | `load(String)` can also be used as an alternative within coroutines. `load(String, Class, AssetLoaderParameters)` | `loadAsync(String, AssetLoaderParameters)` | `load(String, AssetLoaderParameters)` can also be used as an alternative within coroutines. @@ -909,8 +909,8 @@ Closest equivalents in `AssetManager` and `AssetStorage` APIs: ##### Integration with LibGDX and known unsupported features `AssetStorage` does its best to integrate with LibGDX APIs - including the `AssetLoader` implementations, which were -designed for the `AssetManager`. [A dedicated wrapper](src/main/kotlin/ktx/assets/async/wrapper.kt) extends and -overrides `AssetManager`, delegating a subset of supported methods to `AssetStorage`. The official `AssetLoader` +designed for the `AssetManager`. [A dedicated wrapper](src/main/kotlin/ktx/assets/async/AssetManagerWrapper.kt) extends +and overrides `AssetManager`, delegating a subset of supported methods to `AssetStorage`. The official `AssetLoader` implementations use supported methods such as `get`, but please note that some third-party loaders might not work out of the box with `AssetStorage`. Exceptions related to broken loaders include `UnsupportedMethodException` and `MissingDependencyException`. From 3e97d23c28be558c8afc7bbf7bf927bc14a289c0 Mon Sep 17 00:00:00 2001 From: MJ Date: Tue, 23 Feb 2021 10:34:40 +0100 Subject: [PATCH 18/21] DI documentation improvement. #309 --- inject/README.md | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/inject/README.md b/inject/README.md index 7c87b2a4..6725db12 100644 --- a/inject/README.md +++ b/inject/README.md @@ -122,7 +122,7 @@ class ClassWithLazyInjectedValue(context: Context) { Removing a registered provider: ```Kotlin context.remove() -// Note that this method work for both singletons and providers. +// Note that this method works for both singletons and providers. ``` Removing all components from the `Context`: @@ -175,6 +175,9 @@ class Container: Disposable { This will ensure that the `Context` itself will not attempt to dispose of the `Container`. +Note that this also applies to extensions of `KtxApplicationAdapter` and `KtxGame`, both of which +are `Disposable`. + ### Notes on implementation and design choices > How does it work? @@ -185,40 +188,38 @@ framework to extract the actual `Class` object from generic argument - and used Singletons are implemented as providers that always return the same instance of the selected type on each call. It _is_ dead simple and aims to introduce as little runtime overhead as possible. -> No scopes? Huh? +> Are scopes supported? -How often do you need these in simple games, anyway? More complex projects might benefit from features of mature -projects like [Koin](https://insert-koin.io/), but in most simple games you just end up needing some glue between -the components. Sometimes simplicity is something you aim for. +No. More complex projects might benefit from features of mature projects like [Koin](https://insert-koin.io/), +but in most simple games you just end up needing some glue between the components. Sometimes simplicity is something +you aim for. -As for testing scope, it should be obvious that you can just register different components during testing. Don't worry, -classes using `ktx-inject` are usually trivial to test. +As for testing scope, you can just register different components during testing. Classes using `ktx-inject` are usually +easy to test. -> Not even any named providers? +> Are named providers supported? Nope. Providers are mapped to the class of instances that they return - and that's it. Criteria systems - which are a sensible alternative to simple string names - are somewhat easy to use when your system is based on annotations, but we don't have much to work with when the goal is simplicity. -> Kodein-style single-parameter factories, anyone? +> What about Kodein-style single-parameter factories? It seems that Kodein keeps all its "providers" as single-parameter functions. To avoid wrapping all no-arg providers (which seem to be the most common by far) in `null`-consuming functions, factories are not implemented in `ktx-inject` -at all. Honestly, it's hard to get it right - single-parameter factories might not be enough in many situations and -type-safe multi-argument factories might look _really_ awkward_ in code thanks to a ton of generics. If you need -specialized providers, just create a simple class with `invoke` operator. +at all. If you need specialized providers, create a simple class with `invoke` operator. > Is this framework for me? This dependency injection system is as trivial as it gets. It will help you with platform-specific classes and gluing -your application together, but don't expect wonders. This library might be great if you're just starting with dependency -injection - all you need to learn is using a few simple functions. It's also hard to imagine a more lightweight -solution: getting a provider is a single call to a map. +your application together, but don't expect much more. This library might be great if you're just starting with +dependency injection - all you need to learn is using a few simple functions. It is also very lightweight: getting +a provider is a single call to a map. If you never end up needing more features, you might consider sticking with `ktx-inject` altogether, but just so you -know - there _are_ other Kotlin dependency injection and they work _great_ with LibGDX. There was no point in creating -another _complex_ dependency injection framework, and we were fully aware of that. Simplicity and little-to-none runtime -overhead - this pretty much sums up the strong sides of `ktx-inject`. +know - there _are_ other Kotlin dependency injection frameworks and they work well with LibGDX. There was no point in +creating another _complex_ dependency injection framework, and we were fully aware of that. Simplicity and +little-to-none runtime overhead is what sums up the strong sides of `ktx-inject`. ### Alternatives From 7ec962e3eb05a1d416f068fb8478a28d2c662ab1 Mon Sep 17 00:00:00 2001 From: MJ Date: Tue, 23 Feb 2021 10:43:42 +0100 Subject: [PATCH 19/21] KTX version updated to 1.9.14-b1. #338 --- CHANGELOG.md | 2 +- README.md | 4 ++-- version.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8f620fd..f2f8de52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ _See also: [the official LibGDX changelog](https://github.com/libgdx/libgdx/blob/master/CHANGES)._ -#### 1.9.14-SNAPSHOT +#### 1.9.14-b1 - **[UPDATE]** Updated to LibGDX 1.9.14. - **[UPDATE]** Updated to Kotlin 1.4.30. diff --git a/README.md b/README.md index 3e2b24a6..35d6528a 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ in your `build.gradle` file: ```Groovy ext { // Update this version to match the latest KTX release: - ktxVersion = '1.9.13-b1' + ktxVersion = '1.9.14-b1' } dependencies { @@ -129,7 +129,7 @@ repositories { ext { // Update this version to match the latest LibGDX release: - ktxVersion = '1.9.13-SNAPSHOT' + ktxVersion = '1.9.14-SNAPSHOT' } ``` diff --git a/version.txt b/version.txt index 096cd194..6f113fb9 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.9.14-SNAPSHOT +1.9.14-b1 From 7f927d0c3990692856cfa82964ef813c58e5781c Mon Sep 17 00:00:00 2001 From: MJ Date: Tue, 23 Feb 2021 11:06:41 +0100 Subject: [PATCH 20/21] Contributors update. #345 --- .github/CONTRIBUTORS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CONTRIBUTORS.md b/.github/CONTRIBUTORS.md index 433316bd..d5c4161a 100644 --- a/.github/CONTRIBUTORS.md +++ b/.github/CONTRIBUTORS.md @@ -43,7 +43,7 @@ Project contributors listed chronologically. * Improved [app](../app) utilities. * [@Quillraven](https://github.com/Quillraven) * Author of the [Tiled](../tiled) module. - * Contributed [actors](../actors) and [ashley](../ashley) utilities. + * Contributed [actors](../actors), [ashley](../ashley) and [collections](../collections) utilities. * Wrote a complete [KTX tutorial](https://github.com/Quillraven/SimpleKtxGame/wiki) based on the original LibGDX introduction. * Author of the [preferences](../preferences) module. * Tested and reviewed the [assets async](../assets-async) module. From ee6359d063749b9716fb551b6201920461a5e03a Mon Sep 17 00:00:00 2001 From: MJ Date: Tue, 23 Feb 2021 11:07:21 +0100 Subject: [PATCH 21/21] Sources documentation link update. #316 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 35d6528a..cfb68bdc 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,7 @@ Browse through the directories in the root folder to find out more about each li All public classes and functions are also documented with standard Kotlin _KDocs_. You can access the documentation by: -- Viewing the generated Dokka files hosted on the the [project website](https://libktx.github.io/ktx/). +- Viewing the generated Dokka files hosted on the the [project website](https://libktx.github.io/docs/). - Reading the sources directly. - Using the `doc` archive in [GitHub releases](https://github.com/libktx/ktx/releases) with generated Dokka files.