close
The Wayback Machine - https://web.archive.org/web/20200928155258/https://github.com/libktx/ktx/issues/260
Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add coroutines support to AssetManager #260

Open
czyzby opened this issue Mar 31, 2020 · 1 comment
Open

Add coroutines support to AssetManager #260

czyzby opened this issue Mar 31, 2020 · 1 comment
Labels
Milestone

Comments

@czyzby
Copy link
Member

@czyzby czyzby commented Mar 31, 2020

  • Add loadAsync extension functions to AssetManager.
  • Support all default assets even with nullable asset loader parameters.
  • Preserve LoadedCallback instances assigned by the users.
  • Include error handling.

AssetManager supports loading callbacks via AssetLoaderParameters.LoadedCallback, which are executed on the main rendering thread after the assets are loaded. While this is not enough to make AssetManager truly non-blocking or to force it to use coroutines for the actual asset loading, coroutines-compatible extensions can be added to simplify the API.

Early proof-of-concept:

inline fun <reified T> AssetManager.loadAsync(
  path: String, parameters: AssetLoaderParameters<T>? = null
): Deferred<T> = loadAsync(AssetDescriptor(path, T::class.java, parameters))

fun <T> AssetManager.loadAsync(assetDescriptor: AssetDescriptor<T>): Deferred<T> {
  synchronized(this) {
    // isLoaded and get are both synchronized. We want to perform these in a single step.
    if (isLoaded(assetDescriptor)) {
      // Increasing reference count:
      load(assetDescriptor)
      // Returning loaded instance:
      return CompletableDeferred(this[assetDescriptor])
    }
  }
  val result = CompletableDeferred<T>()

  // We can complete the deferred via loaded callback. However, parameters can be null.
  val path = assetDescriptor.fileName
  val parameters = assetDescriptor.params
    ?: getDefaultParametersForLoader<T>(getLoader(assetDescriptor.type, path), path)

  val userDefinedCallback = parameters.loadedCallback
  parameters.loadedCallback = LoadedCallback { assetManager, fileName, type ->
    try {
      // If user defined a custom callback, we still want it to execute:
      userDefinedCallback?.finishedLoading(assetManager, fileName, type)
      result.complete(assetManager.get(assetDescriptor))
    } catch (exception: Throwable) {
      result.completeExceptionally(exception)
    }
  }
  load(assetDescriptor)
  return result
}

fun <T> getDefaultParametersForLoader(
  loader: AssetLoader<*, AssetLoaderParameters<*>>,
  path: String
): AssetLoaderParameters<T> = LoaderParametersSupplier[loader.javaClass, path]

private typealias ParametersSupplier<T> = (path: String) -> AssetLoaderParameters<T>

object LoaderParametersSupplier {
  private val loaderToParameters = mutableMapOf<Class<Loader<*>>, ParametersSupplier<*>>()

  init {
    this[TextureLoader::class.java] = { TextureLoader.TextureParameter() }
    // TODO Other types.
  }

  @Suppress("UNCHECKED_CAST")
  operator fun <T> get(type: Class<Loader<*>>, path: String): AssetLoaderParameters<T> {
    val supplier = loaderToParameters[type] ?: throw GdxRuntimeException("Unregistered loader type: $type")
    return supplier(path) as AssetLoaderParameters<T>
  }

  operator fun <T, L : Loader<T>> set(type: Class<L>, parametersSupplier: ParametersSupplier<T>) {
    @Suppress("UNCHECKED_CAST")
    loaderToParameters[type as Class<Loader<*>>] = parametersSupplier
  }
}
fun usageExample(assetManager: AssetManager) {
  KtxAsync.launch { 
    // Suspends until loaded:
    val texture = assetManager.loadAsync("logo.png", TextureParameter()).await()
    // Resumes when texture is ready to use.
  }
}

See also: #182.

@czyzby czyzby added this to the 1.9.10 milestone Mar 31, 2020
czyzby added a commit that referenced this issue Apr 18, 2020
@czyzby
Copy link
Member Author

@czyzby czyzby commented May 18, 2020

Simple extension function would rely on too many static variables and global objects (e.g. loader parameters supplier, callbacks map) if we wanted to provide a simple API with complete error handling. This is because AssetManager lacks getErrorListener method and without it completing deferreds exceptionally is difficult.

Instead of providing extensions methods for AssetManager, KTX will provide AsyncAssetManager class that implements asynchronous asset loading with proper error handling.

@czyzby czyzby modified the milestones: 1.9.10, 1.9.11 Jul 28, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
1 participant
You can’t perform that action at this time.