From 7e8003ec4d33e2f4c06cc49f0ffeec55fecaaf25 Mon Sep 17 00:00:00 2001 From: MJ Date: Wed, 1 Apr 2020 00:22:49 +0200 Subject: [PATCH] Added synchronous get and getOrNull to AssetStorage. #182 --- CHANGELOG.md | 6 +- README.md | 6 +- assets-async/README.md | 33 +- .../main/kotlin/ktx/assets/async/errors.kt | 5 +- .../main/kotlin/ktx/assets/async/storage.kt | 179 ++++++++-- .../main/kotlin/ktx/assets/async/wrapper.kt | 21 +- .../kotlin/ktx/assets/async/storageTest.kt | 312 ++++++++++++------ .../ktx/freetype/async/freetypeAsyncTest.kt | 25 +- 8 files changed, 412 insertions(+), 175 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e98d9590..8776aec4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,9 @@ - **[FEATURE]** (`ktx-ashley`) Added `Entity.contains` (`in` operator) that checks if an `Entity` has a `Component`. - **[FEATURE]** (`ktx-assets-async`) Added a new KTX module: coroutines-based asset loading. - `AssetStorage` is a non-blocking coroutines-based alternative to LibGDX `AssetManager`. - - `get` operator obtains an asset from the storage as `Deferred`. + - `get` operator obtains an asset from the storage or throws a `MissingAssetException`. + - `getOrNull` obtains an asset from the storage or return `null` if the asset is unavailable. + - `getAsync` obtains a reference to the asset from the storage as `Deferred`. - `load` schedules asynchronous loading of an asset. - `unload` schedules asynchronous unloading of an asset. - `add` allows to manually add a loaded asset to `AssetManager`. @@ -23,6 +25,8 @@ - `getDependencies` returns a list of dependencies of the selected asset. - `getAssetDescriptor` creates an `AssetDescriptor` with loading data for the selected asset. - `getIdentifier` creates an `Identifier` uniquely pointing to an asset of selected type and file path. + - `Identifier` data class added as an utility to uniquely identify assets by their type and path. + - `Identifier.toAssetDescriptor` allows to convert an `Identifier` to an `AssetDescriptor`. - `AssetDescriptor.toIdentifier` allows to convert an `AssetDescriptor` to `Identifier` used to uniquely identify `AssetStorage` assets. - **[FEATURE]** (`ktx-async`) Added `RenderingScope` factory function for custom scopes using rendering thread dispatcher. - **[FEATURE]** (`ktx-async`) `newAsyncContext` and `newSingleThreadAsyncContext` now support `threadName` parameter diff --git a/README.md b/README.md index c52bcb42..5ad89a6a 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Examples of Kotlin language features used to improve usability, performance and * *Extension methods* with sensible *default parameters*. * *Inline methods* with reduced runtime overhead for various listeners, builders and loggers. * *Nullable types* which improve typing information of selected interfaces and functions. -* *Default interface methods* simplifying the implementation. +* *Default interface methods* for common interfaces, simplifying their implementations. * *Type-safe builders* for GUI, styling and physics engine. * *Coroutines context* providing concurrency utilities and non-blocking asset loading. * *Reified types* that simplify usage of methods normally consuming `Class` parameters. @@ -33,7 +33,7 @@ You can include selected **KTX** modules based on the needs of your application. Module | Dependency name | Description :---: | :--- | --- -[actors](actors) | `ktx-actors` | General [`Scene2D`](https://github.com/libgdx/libgdx/wiki/Scene2d) GUI utilities for stages, actors, actions and event listeners. +[actors](actors) | `ktx-actors` | [`Scene2D`](https://github.com/libgdx/libgdx/wiki/Scene2d) GUI extensions for stages, actors, actions and event listeners. [app](app) | `ktx-app` | `ApplicationListener` implementations and general application utilities. [ashley](ashley) | `ktx-ashley` | [`Ashley`](https://github.com/libgdx/ashley) entity-component-system utilities. [assets](assets) | `ktx-assets` | Resources management utilities. @@ -45,7 +45,7 @@ Module | Dependency name | Description [freetype-async](freetype-async) | `ktx-freetype-async` | Non-blocking `FreeType` fonts loading using coroutines. [graphics](graphics) | `ktx-graphics` | Utilities related to rendering tools and graphics. [i18n](i18n) | `ktx-i18n` | Internationalization API utilities. -[inject](inject) | `ktx-inject` | A simple dependency injection system with low overhead and no reflection usage. +[inject](inject) | `ktx-inject` | A dependency injection system with low overhead and no reflection usage. [json](json) | `ktx-json` | Utilities for LibGDX [JSON](https://github.com/libgdx/libgdx/wiki/Reading-and-writing-JSON) serialization API. [log](log) | `ktx-log` | Minimal runtime overhead cross-platform logging using inlined functions. [math](math) | `ktx-math` | Operator functions for LibGDX math API and general math utilities. diff --git a/assets-async/README.md b/assets-async/README.md index bdaad9fe..2a46664f 100644 --- a/assets-async/README.md +++ b/assets-async/README.md @@ -137,7 +137,9 @@ See [`ktx-async`](../async) setup section to enable coroutines in your project. `AssetStorage` contains the following core methods: -- `get: Deferred` - returns a `Deferred` reference to the asset if it was scheduled for loading. +- `get: T` - returns a loaded asset or throws `MissingAssetException` if the asset is unavailable. +- `getOrNull: T?` - returns a loaded asset or `null` if the asset is unavailable. +- `getAsync: Deferred` - returns a `Deferred` reference to the asset if it was scheduled for loading. Suspending `await()` can be called to obtain the asset instance. `isCompleted` can be used to check if the asset loading was finished. - `load: T` _(suspending)_ - schedules asset for asynchronous loading. Suspends the coroutine until @@ -355,6 +357,11 @@ fun accessAsset(assetStorage: AssetStorage) { // but AssetStorage also allows you to access assets // already loaded by other coroutines. + // Immediately returns loaded asset or throws exception if missing: + var texture = assetStorage.get("images/logo.png") + // Immediately returns loaded asset or null if missing: + val textureOrNull = assetStorage.getOrNull("images/logo.png") + // Returns true is asset is in the storage, loaded or not: assetStorage.contains("images/logo.png") // Returns true if the asset loading has finished: @@ -365,19 +372,22 @@ fun accessAsset(assetStorage: AssetStorage) { assetStorage.getDependencies("images/logo.png") KtxAsync.launch { - // By default, AssetStorage will not suspend the coroutine - // to get the asset and instead will return a Kotlin Deferred - // reference. This allows you to handle the asset however - // you need: - val asset: Deferred = assetStorage["images/logo.png"] + // You can also access your assets in coroutines, so you can + // wait for the assets to be loaded. + + // When calling getAsync, AssetStorage will not throw an exception + // or return null if the asset is still loading. Instead, it will + // return a Kotlin Deferred reference. This allows you suspend the + // coroutine until the asset is loaded: + val asset: Deferred = assetStorage.getAsync("images/logo.png") // Checking if the asset loading has finished: asset.isCompleted // Suspending the coroutine to obtain asset instance: - var texture = asset.await() + texture = asset.await() // If you want to suspend the coroutine to wait for the asset, // you can do this in a single line: - texture = assetStorage.get("images/logo.png").await() + texture = assetStorage.getAsync("images/logo.png").await() // Now the coroutine is resumed and `texture` can be used. } @@ -461,7 +471,7 @@ fun loadAsset(assetStorage: AssetStorage) { // Note that if the asset loading ended with an exception, // the same exception will be rethrown each time the asset - // is accessed with `get.await()` or `load`. + // is accessed with `get`, `getOrNull`, `getAsync.await` or `load`. } } ``` @@ -490,7 +500,7 @@ fun createCustomAssetStorage(): AssetStorage { ##### Multiple calls of `load` and `unload` It is completely safe to call `load` multiple times with the same asset data, even to obtain asset instances -without dealing with `Deferred`. In that sense, it can be used as an alternative to `get`. +without dealing with `Deferred`. In that sense, it 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 to the asset and return the same instance on each request. This also works concurrently - the storage will @@ -584,7 +594,8 @@ in either of the examples, you will notice that the deadlocks no longer occur. It does not mean that `runBlocking` will always cause a deadlock, however. You can safely use `runBlocking`: - For `dispose`, both suspending and non-suspending variants. -- For all non-suspending methods such as `contains`, `isLoaded`, `getReferenceCount`, `setLoader`, `getLoader`. +- For all non-suspending methods such as `get`, `getOrNull`, `contains`, `isLoaded`, `setLoader`, `getLoader`. +- For `add`. While `add` does suspend the coroutine, it requires neither the rendering thread nor the loading threads. - For `load` and `get.await` calls requesting already loaded assets. **Use with caution.** - From within other threads than the main rendering thread and the `AssetStorage` loading threads. These threads will be blocked until the operation is finished, which isn't ideal, but at least the loading will remain possible. diff --git a/assets-async/src/main/kotlin/ktx/assets/async/errors.kt b/assets-async/src/main/kotlin/ktx/assets/async/errors.kt index be663ea9..1c839127 100644 --- a/assets-async/src/main/kotlin/ktx/assets/async/errors.kt +++ b/assets-async/src/main/kotlin/ktx/assets/async/errors.kt @@ -19,13 +19,14 @@ import com.badlogic.gdx.utils.GdxRuntimeException open class AssetStorageException(message: String, cause: Throwable? = null) : GdxRuntimeException(message, cause) /** - * Thrown when the asset requested by [AssetStorage.get] is not available in the [AssetStorage]. + * Thrown when the asset requested by an [AssetStorage.get] variant is not available + * in the [AssetStorage] at all or has not been loaded yet. */ class MissingAssetException(identifier: Identifier<*>) : AssetStorageException(message = "Asset: $identifier is not loaded.") /** - * Thrown by [AssetStorage.load] or [AssetStorage.get] when the requested asset + * Thrown by [AssetStorage.load] or [AssetStorage.get] variant when the requested asset * was unloaded asynchronously. */ class UnloadedAssetException(identifier: Identifier<*>) : 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 3c92fe5f..6c3b8a43 100644 --- a/assets-async/src/main/kotlin/ktx/assets/async/storage.kt +++ b/assets-async/src/main/kotlin/ktx/assets/async/storage.kt @@ -1,5 +1,3 @@ -@file:Suppress("DeferredIsResult") - package ktx.assets.async import com.badlogic.gdx.assets.AssetDescriptor @@ -109,20 +107,36 @@ class AssetStorage( } /** - * Returns the reference to the asset wrapped with [Deferred]. - * Use [Deferred.await] to obtain the instance. + * Returns a loaded asset of type [T] loaded from selected [path] or throws [MissingAssetException] + * if the asset is not loaded yet or was never scheduled for loading. Rethrows any exceptions + * encountered during asset loading. * * [T] is the type of the asset. Must match the type requested during loading. - * [identifier] uniquely identifies a file by its path and type. + * [path] must match the asset path passed during loading. * - * To avoid concurrency issues, it is encouraged to load assets with [load] and save the returned instances - * of the loaded assets rather than to rely on [get]. + * This method might throw the following exceptions: + * - [MissingAssetException] if the asset of [T] type with the given [path] was never added with [load] or [add]. + * - [UnloadedAssetException] if the asset was already unloaded asynchronously. + * - [MissingLoaderException] if the [AssetLoader] for asset of requested type is not registered. + * - [InvalidLoaderException] if the [AssetLoader] implementation of requested type is invalid. + * - [AssetLoadingException] if the [AssetLoader] has thrown an exception during loading. + * - [MissingDependencyException] is the [AssetLoader] is unable to obtain an instance of asset's dependency. + * - [UnsupportedMethodException] is the [AssetLoader] uses unsupported operation on [AssetManagerWrapper]. * - * Note that while the result is a [CompletableDeferred], it should never be completed manually. - * Instead, rely on the [AssetStorage] to load the asset. + * See also [getOrNull] and [getAsync]. + */ + inline operator fun get(path: String): T = this[getIdentifier(path)] + + /** + * Returns a loaded asset of type [T] described by [descriptor] or throws [MissingAssetException] + * if the asset is not loaded yet or was never scheduled for loading. Rethrows any exceptions + * encountered during asset loading. * - * Using [Deferred.await] might throw the following exceptions: - * - [MissingAssetException] if the asset with [identifier] was never added with [load] or [add]. + * [T] is the type of the asset. Must match the type requested during loading. + * [descriptor] contains the asset data. See [getAssetDescriptor]. + * + * This method might throw the following exceptions: + * - [MissingAssetException] if the asset of [T] type described by [descriptor] was never added with [load] or [add]. * - [UnloadedAssetException] if the asset was already unloaded asynchronously. * - [MissingLoaderException] if the [AssetLoader] for asset of requested type is not registered. * - [InvalidLoaderException] if the [AssetLoader] implementation of requested type is invalid. @@ -130,17 +144,96 @@ class AssetStorage( * - [MissingDependencyException] is the [AssetLoader] is unable to obtain an instance of asset's dependency. * - [UnsupportedMethodException] is the [AssetLoader] uses unsupported operation on [AssetManagerWrapper]. * - * Otherwise, using [Deferred.await] will suspend the coroutine until the asset is loaded - * and return its instance. + * See also [getOrNull] and [getAsync]. */ - operator fun get(identifier: Identifier): Deferred { - val asset = assets[identifier] - @Suppress("UNCHECKED_CAST") - return if (asset != null) asset.reference as Deferred else getMissingAssetReference(identifier) + operator fun get(descriptor: AssetDescriptor): T = this[descriptor.toIdentifier()] + + /** + * Returns a loaded asset of type [T] identified by [identifier] or throws [MissingAssetException] + * if the asset is not loaded yet or was never scheduled for loading. Rethrows any exceptions + * encountered during asset loading. + * + * [T] is the type of the asset. Must match the type requested during loading. + * [identifier] uniquely identifies a file by its path and type. See [Identifier]. + * + * This method might throw the following exceptions: + * - [MissingAssetException] if the asset of [T] type identified by [identifier] was never added with [load] or [add]. + * - [UnloadedAssetException] if the asset was already unloaded asynchronously. + * - [MissingLoaderException] if the [AssetLoader] for asset of requested type is not registered. + * - [InvalidLoaderException] if the [AssetLoader] implementation of requested type is invalid. + * - [AssetLoadingException] if the [AssetLoader] has thrown an exception during loading. + * - [MissingDependencyException] is the [AssetLoader] is unable to obtain an instance of asset's dependency. + * - [UnsupportedMethodException] is the [AssetLoader] uses unsupported operation on [AssetManagerWrapper]. + * + * See also [getOrNull] and [getAsync]. + */ + operator fun get(identifier: Identifier): T { + val reference = getAsync(identifier) + @Suppress( "EXPERIMENTAL_API_USAGE") // Avoids runBlocking call. + return if (reference.isCompleted) reference.getCompleted() else throw MissingAssetException(identifier) } + /** + * Returns a loaded asset of type [T] loaded from selected [path] or `null` + * if the asset is not loaded yet or was never scheduled for loading. + * Rethrows any exceptions encountered during asset loading. + * + * [T] is the type of the asset. Must match the type requested during loading. + * [path] must match the asset path passed during loading. + * + * This method might throw the following exceptions: + * - [UnloadedAssetException] if the asset was already unloaded asynchronously. + * - [MissingLoaderException] if the [AssetLoader] for asset of requested type is not registered. + * - [InvalidLoaderException] if the [AssetLoader] implementation of requested type is invalid. + * - [AssetLoadingException] if the [AssetLoader] has thrown an exception during loading. + * - [MissingDependencyException] is the [AssetLoader] is unable to obtain an instance of asset's dependency. + * - [UnsupportedMethodException] is the [AssetLoader] uses unsupported operation on [AssetManagerWrapper]. + * + * See also [get] and [getAsync]. + */ + inline fun getOrNull(path: String): T? = getOrNull(getIdentifier(path)) - private fun getMissingAssetReference(identifier: Identifier): Deferred = CompletableDeferred().apply { - completeExceptionally(MissingAssetException(identifier)) + /** + * Returns a loaded asset of type [T] described by [descriptor] or `null` + * if the asset is not loaded yet or was never scheduled for loading. + * Rethrows any exceptions encountered during asset loading. + * + * [T] is the type of the asset. Must match the type requested during loading. + * [descriptor] contains the asset data. See [getAssetDescriptor]. + * + * This method might throw the following exceptions: + * - [UnloadedAssetException] if the asset was already unloaded asynchronously. + * - [MissingLoaderException] if the [AssetLoader] for asset of requested type is not registered. + * - [InvalidLoaderException] if the [AssetLoader] implementation of requested type is invalid. + * - [AssetLoadingException] if the [AssetLoader] has thrown an exception during loading. + * - [MissingDependencyException] is the [AssetLoader] is unable to obtain an instance of asset's dependency. + * - [UnsupportedMethodException] is the [AssetLoader] uses unsupported operation on [AssetManagerWrapper]. + * + * See also [get] and [getAsync]. + */ + fun getOrNull(descriptor: AssetDescriptor): T? = getOrNull(descriptor.toIdentifier()) + + /** + * Returns a loaded asset of type [T] identified by [identifier] or `null` + * if the asset is not loaded yet or was never scheduled for loading. + * Rethrows any exceptions encountered during asset loading. + * + * [T] is the type of the asset. Must match the type requested during loading. + * [identifier] uniquely identifies a file by its path and type. See [Identifier]. + * + * This method might throw the following exceptions: + * - [UnloadedAssetException] if the asset was already unloaded asynchronously. + * - [MissingLoaderException] if the [AssetLoader] for asset of requested type is not registered. + * - [InvalidLoaderException] if the [AssetLoader] implementation of requested type is invalid. + * - [AssetLoadingException] if the [AssetLoader] has thrown an exception during loading. + * - [MissingDependencyException] is the [AssetLoader] is unable to obtain an instance of asset's dependency. + * - [UnsupportedMethodException] is the [AssetLoader] uses unsupported operation on [AssetManagerWrapper]. + * + * See also [get] and [getAsync]. + */ + fun getOrNull(identifier: Identifier): T? { + val asset = assets[identifier] + @Suppress( "UNCHECKED_CAST", "EXPERIMENTAL_API_USAGE") // Avoids runBlocking call. + return if (asset == null || !asset.reference.isCompleted) null else asset.reference.getCompleted() as T } /** @@ -150,9 +243,6 @@ class AssetStorage( * [T] is the type of the asset. Must match the type requested during loading. * [path] must match the asset path passed during loading. * - * To avoid concurrency issues, it is encouraged to load assets with [load] and save the returned instances - * of the loaded assets rather than to rely on [get]. - * * Note that while the result is a [CompletableDeferred], it should never be completed manually. * Instead, rely on the [AssetStorage] to load the asset. * @@ -167,8 +257,10 @@ class AssetStorage( * * Otherwise, using [Deferred.await] will suspend the coroutine until the asset is loaded * and return its instance. + * + * See also [get] and [getOrNull] for synchronous alternatives. */ - inline operator fun get(path: String): Deferred = get(getIdentifier(path)) + inline fun getAsync(path: String): Deferred = getAsync(getIdentifier(path)) /** * Returns the reference to the asset wrapped with [Deferred]. Use [Deferred.await] to obtain the instance. @@ -177,9 +269,6 @@ class AssetStorage( * [T] is the type of the asset. Must match the type requested during loading. * [descriptor] contains the asset data. See [getAssetDescriptor]. * - * To avoid concurrency issues, it is encouraged to load assets with [load] and save the returned instances - * of the loaded assets rather than to rely on [get]. - * * Note that while the result is a [CompletableDeferred], it should never be completed manually. * Instead, rely on the [AssetStorage] to load the asset. * @@ -194,8 +283,44 @@ class AssetStorage( * * Otherwise, using [Deferred.await] will suspend the coroutine until the asset is loaded * and return its instance. + * + * See also [get] and [getOrNull] for synchronous alternatives. */ - operator fun get(descriptor: AssetDescriptor): Deferred = get(descriptor.toIdentifier()) + fun getAsync(descriptor: AssetDescriptor): Deferred = getAsync(descriptor.toIdentifier()) + + /** + * Returns the reference to the asset wrapped with [Deferred]. + * Use [Deferred.await] to obtain the instance. + * + * [T] is the type of the asset. Must match the type requested during loading. + * [identifier] uniquely identifies a file by its path and type. See [Identifier]. + * + * Note that while the result is a [CompletableDeferred], it should never be completed manually. + * Instead, rely on the [AssetStorage] to load the asset. + * + * Using [Deferred.await] might throw the following exceptions: + * - [MissingAssetException] if the asset with [identifier] was never added with [load] or [add]. + * - [UnloadedAssetException] if the asset was already unloaded asynchronously. + * - [MissingLoaderException] if the [AssetLoader] for asset of requested type is not registered. + * - [InvalidLoaderException] if the [AssetLoader] implementation of requested type is invalid. + * - [AssetLoadingException] if the [AssetLoader] has thrown an exception during loading. + * - [MissingDependencyException] is the [AssetLoader] is unable to obtain an instance of asset's dependency. + * - [UnsupportedMethodException] is the [AssetLoader] uses unsupported operation on [AssetManagerWrapper]. + * + * Otherwise, using [Deferred.await] will suspend the coroutine until the asset is loaded + * and return its instance. + * + * See also [get] and [getOrNull] for synchronous alternatives. + */ + fun getAsync(identifier: Identifier): Deferred { + val asset = assets[identifier] + @Suppress("UNCHECKED_CAST") + return if (asset != null) asset.reference as Deferred else getMissingAssetReference(identifier) + } + + private fun getMissingAssetReference(identifier: Identifier): Deferred = CompletableDeferred().apply { + completeExceptionally(MissingAssetException(identifier)) + } /** * Checks whether an asset in the selected [path] with [T] type is already loaded. diff --git a/assets-async/src/main/kotlin/ktx/assets/async/wrapper.kt b/assets-async/src/main/kotlin/ktx/assets/async/wrapper.kt index 06d850d2..cb02f0ed 100644 --- a/assets-async/src/main/kotlin/ktx/assets/async/wrapper.kt +++ b/assets-async/src/main/kotlin/ktx/assets/async/wrapper.kt @@ -8,7 +8,6 @@ import com.badlogic.gdx.assets.loaders.AssetLoader import com.badlogic.gdx.assets.loaders.FileHandleResolver import com.badlogic.gdx.utils.Logger import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking import ktx.async.KtxAsync import com.badlogic.gdx.utils.Array as GdxArray @@ -82,20 +81,14 @@ internal class AssetManagerWrapper(val assetStorage: AssetStorage) override fun get(assetDescriptor: AssetDescriptor): Asset = get(assetDescriptor.fileName, assetDescriptor.type) - override fun get(fileName: String, type: Class): Asset = - runBlocking { - val identifier = Identifier(type, fileName) - val asset = assetStorage[identifier] - if (asset.isCompleted) { - try { - asset.await() - } catch (exception: Throwable) { - throw MissingDependencyException(identifier, exception) - } - } else { - throw MissingDependencyException(identifier) - } + override fun get(fileName: String, type: Class): Asset { + val identifier = Identifier(type, fileName) + return try { + assetStorage[identifier] + } catch (exception: Throwable) { + throw MissingDependencyException(identifier, exception) } + } @Deprecated("Not supported by AssetStorage.", replaceWith = ReplaceWith("get(fileName, type)")) override fun get(fileName: String): Asset = throw UnsupportedMethodException("get(String)") diff --git a/assets-async/src/test/kotlin/ktx/assets/async/storageTest.kt b/assets-async/src/test/kotlin/ktx/assets/async/storageTest.kt index 350fd67c..a89a0f91 100644 --- a/assets-async/src/test/kotlin/ktx/assets/async/storageTest.kt +++ b/assets-async/src/test/kotlin/ktx/assets/async/storageTest.kt @@ -31,6 +31,7 @@ import com.google.common.collect.Sets import com.nhaarman.mockitokotlin2.* import io.kotlintest.matchers.shouldThrow import kotlinx.coroutines.* +import kotlinx.coroutines.future.asCompletableFuture import ktx.assets.TextAssetLoader.TextAssetLoaderParameters import ktx.async.* import org.junit.* @@ -88,12 +89,6 @@ class AssetStorageTest : AsyncTest() { (Gdx.audio as OpenALAudio).dispose() } - /** - * Testing utility. Obtains instance of [T] by blocking the thread until the - * [Deferred] is completed. Rethrows any exceptions caught by [Deferred]. - */ - private fun Deferred.joinAndGet(): T = runBlocking { await() } - @Test fun `should load text assets`() { // Given: @@ -106,7 +101,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertEquals("Content.", asset) assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(emptyList(), storage.getDependencies(path)) } @@ -125,7 +120,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertEquals("Content.", asset) assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(emptyList(), storage.getDependencies(path)) } @@ -143,7 +138,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertEquals("Content.", asset) assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(emptyList(), storage.getDependencies(path)) } @@ -161,7 +156,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertEquals("Content.", asset) assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(emptyList(), storage.getDependencies(path)) } @@ -181,7 +176,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertEquals("Content.", asset) assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(emptyList(), storage.getDependencies(path)) } @@ -213,13 +208,13 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(listOf(storage.getIdentifier(dependency)), storage.getDependencies(path)) // Font dependencies: assertTrue(storage.isLoaded(dependency)) assertEquals(1, storage.getReferenceCount(dependency)) - assertSame(asset.region.texture, storage.get(dependency).joinAndGet()) + assertSame(asset.region.texture, storage.get(dependency)) storage.dispose() } @@ -237,13 +232,13 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(listOf(storage.getIdentifier(dependency)), storage.getDependencies(path)) // Font dependencies: assertTrue(storage.isLoaded(dependency)) assertEquals(1, storage.getReferenceCount(dependency)) - assertSame(asset.region.texture, storage.get(dependency).joinAndGet()) + assertSame(asset.region.texture, storage.get(dependency)) storage.dispose() } @@ -261,13 +256,13 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(listOf(storage.getIdentifier(dependency)), storage.getDependencies(path)) // Font dependencies: assertTrue(storage.isLoaded(dependency)) assertEquals(1, storage.getReferenceCount(dependency)) - assertSame(asset.region.texture, storage.get(dependency).joinAndGet()) + assertSame(asset.region.texture, storage.get(dependency)) storage.dispose() } @@ -301,7 +296,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(emptyList(), storage.getDependencies(path)) @@ -320,7 +315,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(emptyList(), storage.getDependencies(path)) @@ -340,7 +335,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(emptyList(), storage.getDependencies(path)) @@ -374,7 +369,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(emptyList(), storage.getDependencies(path)) @@ -393,7 +388,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(emptyList(), storage.getDependencies(path)) @@ -412,7 +407,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(emptyList(), storage.getDependencies(path)) @@ -446,12 +441,12 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(listOf(storage.getIdentifier(dependency)), storage.getDependencies(path)) // Atlas dependencies: assertTrue(storage.isLoaded(dependency)) - assertSame(asset.textures.first(), storage.get(dependency).joinAndGet()) + assertSame(asset.textures.first(), storage.get(dependency)) assertEquals(1, storage.getReferenceCount(dependency)) storage.dispose() @@ -470,12 +465,12 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(listOf(storage.getIdentifier(dependency)), storage.getDependencies(path)) // Atlas dependencies: assertTrue(storage.isLoaded(dependency)) - assertSame(asset.textures.first(), storage.get(dependency).joinAndGet()) + assertSame(asset.textures.first(), storage.get(dependency)) assertEquals(1, storage.getReferenceCount(dependency)) storage.dispose() @@ -494,12 +489,12 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(listOf(storage.getIdentifier(dependency)), storage.getDependencies(path)) // Atlas dependencies: assertTrue(storage.isLoaded(dependency)) - assertSame(asset.textures.first(), storage.get(dependency).joinAndGet()) + assertSame(asset.textures.first(), storage.get(dependency)) assertEquals(1, storage.getReferenceCount(dependency)) storage.dispose() @@ -534,7 +529,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(emptyList(), storage.getDependencies(path)) @@ -553,7 +548,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(emptyList(), storage.getDependencies(path)) @@ -572,7 +567,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(emptyList(), storage.getDependencies(path)) @@ -605,7 +600,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(emptyList(), storage.getDependencies(path)) @@ -624,7 +619,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(emptyList(), storage.getDependencies(path)) @@ -643,7 +638,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(emptyList(), storage.getDependencies(path)) @@ -678,18 +673,18 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertNotNull(asset.get("default", Button.ButtonStyle::class.java)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(listOf(storage.getIdentifier(atlas)), storage.getDependencies(path)) // Skin dependencies: assertTrue(storage.isLoaded(atlas)) assertEquals(1, storage.getReferenceCount(atlas)) - assertSame(asset.atlas, storage.get(atlas).joinAndGet()) + assertSame(asset.atlas, storage.get(atlas)) assertEquals(listOf(storage.getIdentifier(texture)), storage.getDependencies(atlas)) // Atlas dependencies: assertTrue(storage.isLoaded(texture)) - assertSame(asset.atlas.textures.first(), storage.get(texture).joinAndGet()) + assertSame(asset.atlas.textures.first(), storage.get(texture)) assertEquals(1, storage.getReferenceCount(texture)) storage.dispose() @@ -709,18 +704,18 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertNotNull(asset.get("default", Button.ButtonStyle::class.java)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(listOf(storage.getIdentifier(atlas)), storage.getDependencies(path)) // Skin dependencies: assertTrue(storage.isLoaded(atlas)) assertEquals(1, storage.getReferenceCount(atlas)) - assertSame(asset.atlas, storage.get(atlas).joinAndGet()) + assertSame(asset.atlas, storage.get(atlas)) assertEquals(listOf(storage.getIdentifier(texture)), storage.getDependencies(atlas)) // Atlas dependencies: assertTrue(storage.isLoaded(texture)) - assertSame(asset.atlas.textures.first(), storage.get(texture).joinAndGet()) + assertSame(asset.atlas.textures.first(), storage.get(texture)) assertEquals(1, storage.getReferenceCount(texture)) storage.dispose() @@ -740,18 +735,18 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertNotNull(asset.get("default", Button.ButtonStyle::class.java)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(listOf(storage.getIdentifier(atlas)), storage.getDependencies(path)) // Skin dependencies: assertTrue(storage.isLoaded(atlas)) assertEquals(1, storage.getReferenceCount(atlas)) - assertSame(asset.atlas, storage.get(atlas).joinAndGet()) + assertSame(asset.atlas, storage.get(atlas)) assertEquals(listOf(storage.getIdentifier(texture)), storage.getDependencies(atlas)) // Atlas dependencies: assertTrue(storage.isLoaded(texture)) - assertSame(asset.atlas.textures.first(), storage.get(texture).joinAndGet()) + assertSame(asset.atlas.textures.first(), storage.get(texture)) assertEquals(1, storage.getReferenceCount(texture)) storage.dispose() @@ -790,7 +785,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) assertEquals("Value.", asset["key"]) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(emptyList(), storage.getDependencies(path)) @@ -810,7 +805,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) assertEquals("Value.", asset["key"]) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(emptyList(), storage.getDependencies(path)) @@ -830,7 +825,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) assertEquals("Value.", asset["key"]) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(emptyList(), storage.getDependencies(path)) @@ -863,7 +858,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(emptyList(), storage.getDependencies(path)) @@ -882,7 +877,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(emptyList(), storage.getDependencies(path)) @@ -901,7 +896,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(emptyList(), storage.getDependencies(path)) @@ -935,12 +930,12 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(listOf(storage.getIdentifier(dependency)), storage.getDependencies(path)) // Particle dependencies: assertTrue(storage.isLoaded(dependency)) - assertNotNull(storage.get(dependency).joinAndGet()) + assertNotNull(storage.get(dependency)) assertEquals(1, storage.getReferenceCount(dependency)) storage.dispose() @@ -959,12 +954,12 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(listOf(storage.getIdentifier(dependency)), storage.getDependencies(path)) // Particle dependencies: assertTrue(storage.isLoaded(dependency)) - assertNotNull(storage.get(dependency).joinAndGet()) + assertNotNull(storage.get(dependency)) assertEquals(1, storage.getReferenceCount(dependency)) storage.dispose() @@ -983,12 +978,12 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(listOf(storage.getIdentifier(dependency)), storage.getDependencies(path)) // Particle dependencies: assertTrue(storage.isLoaded(dependency)) - assertNotNull(storage.get(dependency).joinAndGet()) + assertNotNull(storage.get(dependency)) assertEquals(1, storage.getReferenceCount(dependency)) storage.dispose() @@ -1023,7 +1018,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(emptyList(), storage.getDependencies(path)) @@ -1042,7 +1037,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(emptyList(), storage.getDependencies(path)) @@ -1061,7 +1056,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(emptyList(), storage.getDependencies(path)) @@ -1094,7 +1089,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(emptyList(), storage.getDependencies(path)) @@ -1113,7 +1108,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(emptyList(), storage.getDependencies(path)) @@ -1132,7 +1127,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(emptyList(), storage.getDependencies(path)) @@ -1165,7 +1160,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(emptyList(), storage.getDependencies(path)) @@ -1184,7 +1179,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(emptyList(), storage.getDependencies(path)) @@ -1203,7 +1198,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(emptyList(), storage.getDependencies(path)) @@ -1238,7 +1233,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(emptyList(), storage.getDependencies(path)) @@ -1259,7 +1254,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(emptyList(), storage.getDependencies(path)) @@ -1280,7 +1275,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(emptyList(), storage.getDependencies(path)) @@ -1315,7 +1310,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(emptyList(), storage.getDependencies(path)) @@ -1334,7 +1329,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(emptyList(), storage.getDependencies(path)) @@ -1353,7 +1348,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(emptyList(), storage.getDependencies(path)) @@ -1376,12 +1371,115 @@ class AssetStorageTest : AsyncTest() { } @Test - fun `should return deferred that throws exception when attempting to get unloaded asset`() { + fun `should throw exception when attempting to get unloaded asset`() { + // Given: + val storage = AssetStorage() + + // Expect: + shouldThrow { + storage.get("ktx/assets/async/string.txt") + } + } + + @Test + fun `should return null when attempting to get unloaded asset or null`() { + // Given: + val storage = AssetStorage() + + // When: + val asset = storage.getOrNull("ktx/assets/async/string.txt") + + // Then: + assertNull(asset) + } + + @Test + fun `should return deferred that throws exception when attempting to get unloaded asset asynchronously`() { + // Given: + val storage = AssetStorage() + + // When: + val result = storage.getAsync("ktx/assets/async/string.txt") + + // Expect: + shouldThrow { + runBlocking { result.await() } + } + } + + @Test + fun `should throw exception when attempting to get unloaded asset with identifier`() { + // Given: + val storage = AssetStorage() + val identifier = storage.getIdentifier("ktx/assets/async/string.txt") + + // Expect: + shouldThrow { + storage[identifier] + } + } + + @Test + fun `should return null when attempting to get unloaded asset or null with identifier`() { + // Given: + val storage = AssetStorage() + val identifier = storage.getIdentifier("ktx/assets/async/string.txt") + + // When: + val asset = storage.getOrNull(identifier) + + // Then: + assertNull(asset) + } + + @Test + fun `should return deferred that throws exception when attempting to get unloaded asset asynchronously with identifier`() { + // Given: + val storage = AssetStorage() + val identifier = storage.getIdentifier("ktx/assets/async/string.txt") + + // When: + val result = storage.getAsync(identifier) + + // Expect: + shouldThrow { + runBlocking { result.await() } + } + } + + @Test + fun `should throw exception when attempting to get unloaded asset with descriptor`() { // Given: val storage = AssetStorage() + val descriptor = storage.getAssetDescriptor("ktx/assets/async/string.txt") + + // Expect: + shouldThrow { + storage[descriptor] + } + } + + @Test + fun `should return null when attempting to get unloaded asset or null with descriptor`() { + // Given: + val storage = AssetStorage() + val descriptor = storage.getAssetDescriptor("ktx/assets/async/string.txt") // When: - val result = storage.get("ktx/assets/async/string.txt") + val asset = storage.getOrNull(descriptor) + + // Then: + assertNull(asset) + } + + @Test + fun `should return deferred that throws exception when attempting to get unloaded asset asynchronously with descriptor`() { + // Given: + val storage = AssetStorage() + val descriptor = storage.getAssetDescriptor("ktx/assets/async/string.txt") + + // When: + val result = storage.getAsync(descriptor) // Expect: shouldThrow { @@ -1401,7 +1499,9 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.contains(path)) assertTrue(storage.isLoaded(path)) - assertEquals("Content.", storage.get(path).joinAndGet()) + assertEquals("Content.", storage.get(path)) + assertEquals("Content.", storage.getOrNull(path)) + assertEquals("Content.", runBlocking { storage.getAsync(path).await() }) assertEquals(emptyList(), storage.getDependencies(path)) } @@ -1417,7 +1517,9 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(identifier in storage) assertTrue(storage.isLoaded(identifier)) - assertEquals("Content.", storage[identifier].joinAndGet()) + assertEquals("Content.", storage[identifier]) + assertEquals("Content.", storage.getOrNull(identifier)) + assertEquals("Content.", runBlocking { storage.getAsync(identifier).await() }) assertEquals(emptyList(), storage.getDependencies(identifier)) } @@ -1433,7 +1535,9 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(descriptor in storage) assertTrue(storage.isLoaded(descriptor)) - assertEquals("Content.", storage[descriptor].joinAndGet()) + assertEquals("Content.", storage[descriptor]) + assertEquals("Content.", storage.getOrNull(descriptor)) + assertEquals("Content.", runBlocking { storage.getAsync(descriptor).await() }) assertEquals(emptyList(), storage.getDependencies(descriptor)) } @@ -1530,7 +1634,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(fakePath)) - assertSame(asset, storage.get(fakePath).joinAndGet()) + assertSame(asset, storage.get(fakePath)) assertEquals(1, storage.getReferenceCount(fakePath)) } @@ -1547,7 +1651,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(descriptor)) - assertSame(asset, storage[descriptor].joinAndGet()) + assertSame(asset, storage[descriptor]) assertEquals(1, storage.getReferenceCount(descriptor)) } @@ -1564,7 +1668,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(identifier)) - assertSame(asset, storage[identifier].joinAndGet()) + assertSame(asset, storage[identifier]) assertEquals(1, storage.getReferenceCount(identifier)) } @@ -1582,7 +1686,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) } @Test @@ -1599,7 +1703,7 @@ class AssetStorageTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(path)) assertTrue(storage.isLoaded(path)) - assertSame(asset, storage.get(path).joinAndGet()) + assertSame(asset, storage.get(path)) storage.dispose() } @@ -1621,7 +1725,7 @@ class AssetStorageTest : AsyncTest() { assertTrue(storage.isLoaded(path)) assertEquals(1, storage.getReferenceCount(path)) assertEquals(1, storage.getReferenceCount(path)) - assertNotSame(storage.get(path).joinAndGet(), storage.get(path).joinAndGet()) + assertNotSame(storage.get(path), storage.get(path)) storage.dispose() } @@ -1650,8 +1754,8 @@ class AssetStorageTest : AsyncTest() { runBlocking { tasks.joinAll() } assertTrue(storage.isLoaded(firstPath)) assertTrue(storage.isLoaded(secondPath)) - assertSame(tasks[0].joinAndGet(), storage.get(firstPath).joinAndGet()) - assertSame(tasks[1].joinAndGet(), storage.get(secondPath).joinAndGet()) + assertSame(tasks[0].asCompletableFuture().join(), storage.get(firstPath)) + assertSame(tasks[1].asCompletableFuture().join(), storage.get(secondPath)) storage.dispose() } @@ -1937,7 +2041,7 @@ class AssetStorageTest : AsyncTest() { assertEquals(100, storage.getReferenceCount(path)) assertTrue(storage.isLoaded(dependency)) assertEquals(100, storage.getReferenceCount(dependency)) - assertEquals(1, assets.map { it.joinAndGet() }.toSet().size) + assertEquals(1, assets.map { it.asCompletableFuture().join() }.toSet().size) storage.dispose() } @@ -1987,7 +2091,7 @@ class AssetStorageTest : AsyncTest() { runBlocking { assets.joinAll() } assertTrue(storage.isLoaded(path)) assertEquals(1, storage.getReferenceCount(path)) - assertEquals("Content.", storage.get(path).joinAndGet()) + assertEquals("Content.", storage.get(path)) storage.dispose() } @@ -2026,8 +2130,8 @@ class AssetStorageTest : AsyncTest() { assertTrue(storage.isLoaded(dependency)) assertEquals(1, storage.getReferenceCount(dependency)) assertSame( - storage.get(path).joinAndGet().region.texture, - storage.get(dependency).joinAndGet() + storage.get(path).region.texture, + storage.get(dependency) ) storage.dispose() @@ -2068,9 +2172,9 @@ class AssetStorageTest : AsyncTest() { assertEquals(1, storage.getReferenceCount(dependency)) assertTrue(storage.isLoaded(nestedDependency)) assertEquals(1, storage.getReferenceCount(nestedDependency)) - val skin = storage.get(path).joinAndGet() - val atlas = storage.get(dependency).joinAndGet() - val texture = storage.get(nestedDependency).joinAndGet() + val skin = storage.get(path) + val atlas = storage.get(dependency) + val texture = storage.get(nestedDependency) assertSame(skin.atlas, atlas) assertSame(atlas.textures.first(), texture) @@ -2092,7 +2196,7 @@ class AssetStorageTest : AsyncTest() { val loads = AtomicInteger() val unloads = AtomicInteger() - // When: spawning 1000 coroutines that randomly load or unload the asset: + // When: spawning 1000 coroutines that randomly load or unload the asset and try to access it: val assets = (1..1000).map { val result = CompletableDeferred() KtxAsync.launch(schedulers) { @@ -2106,6 +2210,12 @@ class AssetStorageTest : AsyncTest() { val unloaded = storage.unload(path) if (unloaded) unloads.incrementAndGet() } + try { + // Concurrent access: + storage.getOrNull(path) + } catch (expected: UnloadedAssetException) { + // Assets can be unloaded asynchronously. This is OK. + } result.complete(true) } result @@ -2229,7 +2339,7 @@ class AssetStorageTest : AsyncTest() { assertEquals(0, storage.getReferenceCount(it)) assertEquals(emptyList(), storage.getDependencies(it)) shouldThrow { - storage[it].joinAndGet() + storage[it] } } } @@ -2503,7 +2613,7 @@ class AssetStorageTest : AsyncTest() { // Then: asset should still be loaded, but the callback exception must be logged: loggingFinished.join() assertTrue(storage.isLoaded(path)) - assertEquals("Content.", storage.get(path).joinAndGet()) + assertEquals("Content.", storage.get(path)) verify(logger).error(any(), eq(exception)) } @@ -2620,7 +2730,7 @@ class AssetStorageTest : AsyncTest() { assertTrue(storage.contains(path)) assertEquals(1, storage.getReferenceCount(path)) shouldThrow { - storage.get(path).joinAndGet() + storage.get(path) } } @@ -2663,7 +2773,7 @@ class AssetStorageTest : AsyncTest() { assertTrue(exception is UnloadedAssetException) assertFalse(storage.contains(path)) shouldThrow { - storage.get(path).joinAndGet() + storage.get(path) } } @@ -2690,7 +2800,7 @@ class AssetStorageTest : AsyncTest() { assertTrue(storage.contains(path)) assertEquals(1, storage.getReferenceCount(path)) shouldThrow { - storage.get(path).joinAndGet() + storage.get(path) } } @@ -2717,7 +2827,7 @@ class AssetStorageTest : AsyncTest() { assertTrue(storage.contains(path)) assertEquals(1, storage.getReferenceCount(path)) shouldThrow { - storage.get(path).joinAndGet() + storage.get(path) } } @@ -2741,7 +2851,7 @@ class AssetStorageTest : AsyncTest() { assertTrue(storage.contains(path)) assertEquals(1, storage.getReferenceCount(path)) shouldThrow { - storage.get(path).joinAndGet() + storage.get(path) } } @@ -2765,7 +2875,7 @@ class AssetStorageTest : AsyncTest() { assertTrue(storage.contains(path)) assertEquals(1, storage.getReferenceCount(path)) shouldThrow { - storage.get(path).joinAndGet() + storage.get(path) } } @@ -2891,7 +3001,7 @@ class AssetStorageTest : AsyncTest() { assertFalse(storage.isLoaded(identifier)) loadingFinished.complete(true) - runBlocking { storage.get(path).await() } + runBlocking { storage.getAsync(path).await() } assertTrue(identifier in storage) assertTrue(storage.isLoaded(identifier)) } diff --git a/freetype-async/src/test/kotlin/ktx/freetype/async/freetypeAsyncTest.kt b/freetype-async/src/test/kotlin/ktx/freetype/async/freetypeAsyncTest.kt index b27b6a9d..6a7a5819 100644 --- a/freetype-async/src/test/kotlin/ktx/freetype/async/freetypeAsyncTest.kt +++ b/freetype-async/src/test/kotlin/ktx/freetype/async/freetypeAsyncTest.kt @@ -9,7 +9,6 @@ import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGeneratorLoader import com.badlogic.gdx.graphics.g2d.freetype.FreetypeFontLoader import com.nhaarman.mockitokotlin2.mock import io.kotlintest.matchers.shouldThrow -import kotlinx.coroutines.Deferred import kotlinx.coroutines.async import kotlinx.coroutines.runBlocking import ktx.assets.async.AssetStorage @@ -91,12 +90,6 @@ class FreeTypeAsyncTest : AsyncTest() { assertTrue(storage.getLoader() is FreetypeFontLoader) } - /** - * Testing utility. Obtains instance of [T] by blocking the thread until the - * [Deferred] is completed. Rethrows any exceptions caught by [Deferred]. - */ - private fun Deferred.joinAndGet(): T = runBlocking { await() } - @Test fun `should load OTF file into BitmapFont`() { // Given: @@ -108,7 +101,7 @@ class FreeTypeAsyncTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(otfFile)) - assertSame(asset, storage.get(otfFile).joinAndGet()) + assertSame(asset, storage.get(otfFile)) assertEquals(1, storage.getReferenceCount(otfFile)) // Automatically loads a generator for the font: assertEquals(listOf(storage.getIdentifier("$otfFile.gen")), @@ -134,7 +127,7 @@ class FreeTypeAsyncTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(otfFile)) - assertSame(asset, storage.get(otfFile).joinAndGet()) + assertSame(asset, storage.get(otfFile)) assertEquals(1, storage.getReferenceCount(otfFile)) // Automatically loads a generator for the font: assertEquals(listOf(storage.getIdentifier("$otfFile.gen")), @@ -172,7 +165,7 @@ class FreeTypeAsyncTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(ttfFile)) - assertSame(asset, storage.get(ttfFile).joinAndGet()) + assertSame(asset, storage.get(ttfFile)) assertEquals(1, storage.getReferenceCount(ttfFile)) // Automatically loads a generator for the font: assertEquals(listOf(storage.getIdentifier("$ttfFile.gen")), @@ -198,7 +191,7 @@ class FreeTypeAsyncTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(ttfFile)) - assertSame(asset, storage.get(ttfFile).joinAndGet()) + assertSame(asset, storage.get(ttfFile)) assertEquals(1, storage.getReferenceCount(ttfFile)) // Automatically loads a generator for the font: assertEquals(listOf(storage.getIdentifier("$ttfFile.gen")), @@ -241,7 +234,7 @@ class FreeTypeAsyncTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(otfFile)) - assertSame(asset, storage.get(otfFile).joinAndGet()) + assertSame(asset, storage.get(otfFile)) assertEquals(1, storage.getReferenceCount(otfFile)) // Automatically loads a generator for the font: assertEquals(listOf(storage.getIdentifier("$otfFile.gen")), @@ -266,7 +259,7 @@ class FreeTypeAsyncTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(ttfFile)) - assertSame(asset, storage.get(ttfFile).joinAndGet()) + assertSame(asset, storage.get(ttfFile)) assertEquals(1, storage.getReferenceCount(ttfFile)) // Automatically loads a generator for the font: assertEquals(listOf(storage.getIdentifier("$ttfFile.gen")), @@ -288,7 +281,7 @@ class FreeTypeAsyncTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(otfFile)) - assertSame(asset, storage.get(otfFile).joinAndGet()) + assertSame(asset, storage.get(otfFile)) assertEquals(1, storage.getReferenceCount(otfFile)) assertEquals(emptyList(), storage.getDependencies(otfFile)) @@ -306,7 +299,7 @@ class FreeTypeAsyncTest : AsyncTest() { // Then: assertTrue(storage.isLoaded(ttfFile)) - assertSame(asset, storage.get(ttfFile).joinAndGet()) + assertSame(asset, storage.get(ttfFile)) assertEquals(1, storage.getReferenceCount(ttfFile)) assertEquals(emptyList(), storage.getDependencies(ttfFile)) @@ -379,7 +372,7 @@ class FreeTypeAsyncTest : AsyncTest() { assertEquals(0, storage.getReferenceCount(it)) assertEquals(emptyList(), storage.getDependencies(it)) shouldThrow { - storage[it].joinAndGet() + storage[it] } } }