ktx.assets.async.loaders.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ktx-assets-async Show documentation
Show all versions of ktx-assets-async Show documentation
Asynchronous coroutines-based asset loader for libGDX.
The newest version!
package ktx.assets.async
import com.badlogic.gdx.assets.AssetDescriptor
import com.badlogic.gdx.assets.AssetLoaderParameters
import com.badlogic.gdx.assets.AssetManager
import com.badlogic.gdx.assets.loaders.AssetLoader
import com.badlogic.gdx.assets.loaders.AsynchronousAssetLoader
import com.badlogic.gdx.assets.loaders.FileHandleResolver
import com.badlogic.gdx.assets.loaders.SynchronousAssetLoader
import com.badlogic.gdx.assets.loaders.resolvers.AbsoluteFileHandleResolver
import com.badlogic.gdx.files.FileHandle
import com.badlogic.gdx.graphics.Texture
import com.badlogic.gdx.graphics.g2d.PolygonRegion
import com.badlogic.gdx.graphics.g2d.PolygonRegionLoader
import com.badlogic.gdx.graphics.g2d.TextureRegion
import com.badlogic.gdx.utils.ObjectMap
import com.badlogic.gdx.utils.Array as GdxArray
/**
* Stores [AssetLoader] instances mapped by loaded asset type. Internal [AssetStorage] utility.
*
* Implementation note: libGDX loaders are not thread-safe. Instead, they assume that only a single asset is loaded
* at a time and use internal, non-synchronized fields to store temporary variables like the dependencies. To avoid
* threading issues, we use a separate loader for each loaded asset instead of singleton instances - hence
* the functional loader providers.
*/
internal class AssetLoaderStorage {
private val loaders: ObjectMap, AssetLoaderContainer<*>> = ObjectMap()
/**
* Provides a [Loader] for the given asset [type]. Optionally, file [path] can be given,
* as depending on the file suffix, a different loader might be used for the same asset type.
*/
fun getLoader(type: Class, path: String? = null): Loader? {
@Suppress("UNCHECKED_CAST")
val loadersForType = loaders[type] as AssetLoaderContainer? ?: return null
if (path == null || loadersForType.loadersBySuffix.size == 0) {
return loadersForType.mainLoader?.invoke()
}
var maxMatchingSuffixLength = 0
var loaderProvider = loadersForType.mainLoader
loadersForType.loadersBySuffix.forEach {
val suffix = it.key
if (maxMatchingSuffixLength < suffix.length && path.endsWith(suffix)) {
maxMatchingSuffixLength = suffix.length
loaderProvider = it.value
}
}
return loaderProvider?.invoke()
}
/**
* Adds or replaces [Loader] for the given class. [loaderProvider] is invoked
* each time an instance of the selected loader is requested. The loader will be
* associated with the given asset [type]. Optionally, a [suffix] can be given
* to a associate the loader with specific file paths.
*/
fun setLoaderProvider(type: Class, suffix: String? = null, loaderProvider: () -> Loader) {
validate(loaderProvider)
getOrCreateLoadersContainer(type).apply {
if (suffix.isNullOrEmpty()) {
mainLoader = loaderProvider
} else {
loadersBySuffix.put(suffix, loaderProvider)
}
}
}
private fun validate(loaderProvider: () -> Loader) {
val loader = loaderProvider()
if (loader !is SynchronousAssetLoader<*, *> && loader !is AsynchronousAssetLoader<*, *>) {
throw InvalidLoaderException(loader)
}
}
private fun getOrCreateLoadersContainer(type: Class): AssetLoaderContainer {
val loadersForType = loaders[type]
if (loadersForType == null) {
val container = AssetLoaderContainer()
loaders.put(type, container)
return container
}
@Suppress("UNCHECKED_CAST")
return loadersForType as AssetLoaderContainer
}
override fun toString(): String = "AssetLoaderStorage[loaders=$loaders]"
private class AssetLoaderContainer {
val loadersBySuffix: ObjectMap Loader> = ObjectMap()
var mainLoader: (() -> Loader)?
get() = loadersBySuffix[""]
set(value) {
loadersBySuffix.put("", value)
}
override fun toString(): String = loadersBySuffix.toString()
}
}
// Workarounds for libGDX generics API.
/** [AssetLoader] with improved generics. */
typealias Loader = AssetLoader>
/** [SynchronousAssetLoader] with improved generics. */
typealias SynchronousLoader = SynchronousAssetLoader>
/** [AsynchronousAssetLoader] with improved generics. */
typealias AsynchronousLoader = AsynchronousAssetLoader>
/** Casts [AssetDescriptor.params] stored with raw type. */
private val AssetDescriptor.parameters: AssetLoaderParameters?
@Suppress("UNCHECKED_CAST")
get() = params as AssetLoaderParameters?
/**
* Allows to use [AssetLoader.getDependencies] method with [AssetDescriptor].
* [assetDescriptor] contains asset data.
* Returns a [com.badlogic.gdx.utils.Array] with asset dependencies described
* with [AssetDescriptor] instances. Null if here are no dependencies.
*/
fun Loader<*>.getDependencies(assetDescriptor: AssetDescriptor<*>): GdxArray> =
@Suppress("UNCHECKED_CAST")
(this as AssetLoader<*, AssetLoaderParameters<*>>)
.getDependencies(assetDescriptor.fileName, assetDescriptor.file, assetDescriptor.parameters)
?: GdxArray(0)
/**
* Allows to use [SynchronousAssetLoader.load] method with [AssetDescriptor].
* [assetManager] provides asset dependencies for the loader.
* [assetDescriptor] contains asset data. Returns fully loaded [Asset] instance.
*/
fun SynchronousLoader.load(assetManager: AssetManager, assetDescriptor: AssetDescriptor): Asset =
@Suppress("UNCHECKED_CAST")
(this as SynchronousAssetLoader>)
.load(assetManager, assetDescriptor.fileName, assetDescriptor.file, assetDescriptor.parameters)
/**
* Allows to use [AsynchronousAssetLoader.loadAsync] method with [AssetDescriptor].
* Performs the asynchronous asset loading part without yielding results.
* [assetManager] provides asset dependencies for the loader.
* [assetDescriptor] contains asset data.
*/
fun AsynchronousLoader.loadAsync(assetManager: AssetManager, assetDescriptor: AssetDescriptor) =
@Suppress("UNCHECKED_CAST")
(this as AsynchronousAssetLoader>)
.loadAsync(assetManager, assetDescriptor.fileName, assetDescriptor.file, assetDescriptor.parameters)
/**
* Allows to use [AsynchronousAssetLoader.loadSync] method with [AssetDescriptor].
* Note that [loadAsync] must have been called first with the same asset data.
* [assetManager] provides asset dependencies for the loader.
* [assetDescriptor] contains asset data. Returns fully loaded [Asset] instance.
*/
fun AsynchronousLoader.loadSync(assetManager: AssetManager, assetDescriptor: AssetDescriptor): Asset =
@Suppress("UNCHECKED_CAST")
(this as AsynchronousAssetLoader>)
.loadSync(assetManager, assetDescriptor.fileName, assetDescriptor.file, assetDescriptor.parameters)
/** Required for [ManualLoader] by libGDX API. */
internal class ManualLoadingParameters : AssetLoaderParameters()
/** Mocks [AssetLoader] API for assets manually added to the [AssetStorage]. See [AssetStorage.add]. */
internal object ManualLoader : AssetLoader(AbsoluteFileHandleResolver()) {
private val emptyDependencies = GdxArray>(0)
override fun getDependencies(
fileName: String?,
file: FileHandle?,
parameter: ManualLoadingParameters?,
): GdxArray> = emptyDependencies
}
/**
* Specialized [PolygonRegionLoader] extension compatible with [AssetStorage].
* Tested via the [AssetStorage] test suite.
*/
internal class AssetStoragePolygonRegionLoader(
fileHandleResolver: FileHandleResolver,
) : PolygonRegionLoader(fileHandleResolver) {
override fun load(
manager: AssetManager,
fileName: String,
file: FileHandle,
parameter: PolygonRegionParameters?,
): PolygonRegion {
val assetStorage = (manager as AssetManagerWrapper).assetStorage
val texturePath = assetStorage.getDependencies(fileName).first().path
val texture = assetStorage.get(texturePath)
return load(TextureRegion(texture), file)
}
}