Skip to content

Commit

Permalink
KTX 1.9.14-b1 release. #338
Browse files Browse the repository at this point in the history
  • Loading branch information
czyzby committed Feb 23, 2021
2 parents d4b1c0a + ee6359d commit c67da29
Show file tree
Hide file tree
Showing 19 changed files with 510 additions and 49 deletions.
2 changes: 1 addition & 1 deletion .github/CONTRIBUTORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
_See also: [the official LibGDX changelog](https://github.com/libgdx/libgdx/blob/master/CHANGES)._

#### 1.9.14-b1

- **[UPDATE]** Updated to LibGDX 1.9.14.
- **[UPDATE]** Updated to Kotlin 1.4.30.
- **[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
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 LibGDX map collections including
`ObjectMap`, `IdentityMap`, `ArrayMap` and `IntMap`.

#### 1.9.13-b1

- **[UPDATE]** Updated to LibGDX 1.9.13.
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -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/)
[![Kotlin](https://img.shields.io/badge/kotlin-1.4.30-orange.svg)](http://kotlinlang.org/)
[![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)
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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'
}
```

Expand All @@ -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.

Expand Down
11 changes: 6 additions & 5 deletions app/src/main/kotlin/ktx/app/graphics.kt
Original file line number Diff line number Diff line change
@@ -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)
}
12 changes: 11 additions & 1 deletion app/src/test/kotlin/ktx/app/GraphicsTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,22 @@ 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)

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)
}
}
13 changes: 7 additions & 6 deletions assets-async/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<Texture>("images/logo.png")
val font = assetStorage.loadAsync<BitmapFont>("fonts/font.fnt")
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -883,8 +883,8 @@ Closest equivalents in `AssetManager` and `AssetStorage` APIs:

`AssetManager` | `AssetStorage` | Note
:---: | :---: | ---
`get<T>(String)` | `get<T>(String)` |
`get(String, Class<T>)` | `get(Identifier<T>)` |
`get<T>(String)` | `get<T>(String)` | `AssetStorage` uses reified types to prevent from runtime class cast exceptions.
`get(String, Class<T>)` | `get(Identifier<T>)` | `Identifier` pairs file path and asset type to identify an asset.
`get(AssetDescriptor<T>)` | `get(AssetDescriptor<T>)` |
`load(String, Class<T>)` | `loadAsync<T>(String)` | `load<T>(String)` can also be used as an alternative within coroutines.
`load(String, Class<T>, AssetLoaderParameters<T>)` | `loadAsync<T>(String, AssetLoaderParameters<T>)` | `load<T>(String, AssetLoaderParameters<T>)` can also be used as an alternative within coroutines.
Expand All @@ -901,15 +901,16 @@ 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>, T)` | `add<T>(String, T)` |
`contains(String)` | `contains<T>(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.

##### 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`.
Expand Down
89 changes: 87 additions & 2 deletions assets-async/src/main/kotlin/ktx/assets/async/storage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1194,6 +1194,58 @@ 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 {
// Creating a safe copy of all assets without unfinished internal completable deferred instances:
val assetsCopy = assets.mapValues {
@Suppress("UNCHECKED_CAST") val asset: Asset<Any> = it.value as Asset<Any>
val reference: CompletableDeferred<Any> = if (asset.reference.isCompleted || asset.reference.isCancelled) {
asset.reference
} else {
CompletableDeferred()
}
asset.copy(reference = reference)
}
// Replacing dependencies with safe copies:
return AssetStorageSnapshot(
assets = assetsCopy.mapValues {
val asset = it.value
if (asset.dependencies.isEmpty()) {
asset
} else {
asset.copy(dependencies = asset.dependencies.mapNotNull { dependency ->
assetsCopy[dependency.identifier]
})
}
}
)
}
}

/**
* 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.
Expand Down Expand Up @@ -1246,14 +1298,14 @@ 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 = "]")
})"
}

/**
* Container for a single asset of type [T] managed by [AssetStorage].
*/
internal data class Asset<T>(
data class Asset<T>(
/** Stores asset loading data. */
val descriptor: AssetDescriptor<T>,
/** Unique identifier of the asset. */
Expand Down Expand Up @@ -1322,3 +1374,36 @@ data class Identifier<T>(
* is required, use [AssetDescriptor] for loading instead.
*/
fun <T> AssetDescriptor<T>.toIdentifier(): Identifier<T> = Identifier(fileName, type)

/**
* Stores a copy of state of an [AssetStorage]. For debugging purposes.
*/
data class AssetStorageSnapshot(
val assets: Map<Identifier<*>, 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},
},"""
}
}
]"""
}
}
Loading

0 comments on commit c67da29

Please sign in to comment.