All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ktx.assets.async.loaders.kt Maven / Gradle / Ivy

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)
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy