Skip to content

Commit

Permalink
Added synchronous get and getOrNull to AssetStorage. #182
Browse files Browse the repository at this point in the history
  • Loading branch information
czyzby committed Mar 31, 2020
1 parent f6602f6 commit 7e8003e
Show file tree
Hide file tree
Showing 8 changed files with 412 additions and 175 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand All @@ -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
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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.
Expand Down
33 changes: 22 additions & 11 deletions assets-async/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,9 @@ See [`ktx-async`](../async) setup section to enable coroutines in your project.

`AssetStorage` contains the following core methods:

- `get: Deferred<T>` - 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<T>` - 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
Expand Down Expand Up @@ -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<Texture>("images/logo.png")
// Immediately returns loaded asset or null if missing:
val textureOrNull = assetStorage.getOrNull<Texture>("images/logo.png")

// Returns true is asset is in the storage, loaded or not:
assetStorage.contains<Texture>("images/logo.png")
// Returns true if the asset loading has finished:
Expand All @@ -365,19 +372,22 @@ fun accessAsset(assetStorage: AssetStorage) {
assetStorage.getDependencies<Texture>("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<Texture> = 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<Texture> = 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<Texture>("images/logo.png").await()
texture = assetStorage.getAsync<Texture>("images/logo.png").await()

// Now the coroutine is resumed and `texture` can be used.
}
Expand Down Expand Up @@ -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`.
}
}
```
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
5 changes: 3 additions & 2 deletions assets-async/src/main/kotlin/ktx/assets/async/errors.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<*>) :
Expand Down
Loading

0 comments on commit 7e8003e

Please sign in to comment.