com.github.czyzby.autumn.mvc.component.asset.AssetService Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gdx-autumn-mvc Show documentation
Show all versions of gdx-autumn-mvc Show documentation
MVC framework based on LibGDX using Autumn for components management and LML as view templates.
package com.github.czyzby.autumn.mvc.component.asset;
import com.badlogic.gdx.assets.AssetLoaderParameters;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.assets.loaders.AssetLoader;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.badlogic.gdx.utils.ObjectMap;
import com.badlogic.gdx.utils.ObjectSet;
import com.badlogic.gdx.utils.ObjectSet.ObjectSetIterator;
import com.badlogic.gdx.utils.reflect.Field;
import com.badlogic.gdx.utils.reflect.ReflectionException;
import com.github.czyzby.autumn.annotation.Destroy;
import com.github.czyzby.autumn.annotation.Inject;
import com.github.czyzby.autumn.context.Context;
import com.github.czyzby.autumn.context.ContextDestroyer;
import com.github.czyzby.autumn.context.ContextInitializer;
import com.github.czyzby.autumn.mvc.component.asset.dto.injection.ArrayAssetInjection;
import com.github.czyzby.autumn.mvc.component.asset.dto.injection.AssetInjection;
import com.github.czyzby.autumn.mvc.component.asset.dto.injection.ObjectMapAssetInjection;
import com.github.czyzby.autumn.mvc.component.asset.dto.injection.ObjectSetAssetInjection;
import com.github.czyzby.autumn.mvc.component.asset.dto.injection.StandardAssetInjection;
import com.github.czyzby.autumn.mvc.component.asset.dto.provider.ArrayAssetProvider;
import com.github.czyzby.autumn.mvc.component.asset.dto.provider.AssetProvider;
import com.github.czyzby.autumn.mvc.component.asset.dto.provider.ObjectMapAssetProvider;
import com.github.czyzby.autumn.mvc.component.asset.dto.provider.ObjectSetAssetProvider;
import com.github.czyzby.autumn.mvc.config.AutumnActionPriority;
import com.github.czyzby.autumn.mvc.config.AutumnMessage;
import com.github.czyzby.autumn.mvc.stereotype.Asset;
import com.github.czyzby.autumn.processor.AbstractAnnotationProcessor;
import com.github.czyzby.autumn.processor.event.MessageDispatcher;
import com.github.czyzby.kiwi.util.gdx.asset.Disposables;
import com.github.czyzby.kiwi.util.gdx.asset.lazy.Lazy;
import com.github.czyzby.kiwi.util.gdx.asset.lazy.provider.ObjectProvider;
import com.github.czyzby.kiwi.util.gdx.collection.GdxArrays;
import com.github.czyzby.kiwi.util.gdx.collection.GdxMaps;
import com.github.czyzby.kiwi.util.gdx.collection.GdxSets;
import com.github.czyzby.kiwi.util.gdx.reflection.Annotations;
import com.github.czyzby.kiwi.util.gdx.reflection.Reflection;
/** Wraps around two internal {@link com.badlogic.gdx.assets.AssetManager}s, providing utilities for asset loading.
* Allows to load assets both eagerly and by constant updating, without forcing loading of "lazy" assets upon "eager"
* request, like AssetManager does (see {@link com.badlogic.gdx.assets.AssetManager#finishLoadingAsset(String)}
* implementation - it basically loads everything, waiting for a specific asset to get loaded). Note that some wrapped
* methods provide additional utility and validations, so direct access to the managers is not advised.
*
* @author MJ */
public class AssetService extends AbstractAnnotationProcessor {
private final AssetManager assetManager = new AssetManager();
/** There is no reliable way of keeping both eagerly and normally loaded assets together, while preserving a way to
* both load assets by constant updating and load SOME assets at once. That's why this service uses two managers. */
private final AssetManager eagerAssetManager = new AssetManager();
private final ObjectSet assetInjections = GdxSets.newSet();
private final ObjectSet scheduledAssets = GdxSets.newSet();
private final Array onLoadActions = GdxArrays.newArray();
@Inject private MessageDispatcher messageDispatcher;
/** @param loader asset loader for the selected type. Will be registered in all managed {@link AssetManager}
* instances.
* @param assetClass class of the loaded asset.
* @see AssetManager#setLoader(Class, AssetLoader)
* @param type of registered loader. */
public void registerLoader(final AssetLoader> loader,
final Class assetClass) {
assetManager.setLoader(assetClass, loader);
eagerAssetManager.setLoader(assetClass, loader);
}
/** @param loader asset loader for the selected type. Will be registered in all managed {@link AssetManager}
* instances.
* @param suffix allows to filter files.
* @param assetClass class of the loaded asset.
* @see AssetManager#setLoader(Class, String, AssetLoader)
* @param type of registered loader. */
public void registerLoader(final AssetLoader> loader, final String suffix,
final Class assetClass) {
assetManager.setLoader(assetClass, suffix, loader);
eagerAssetManager.setLoader(assetClass, suffix, loader);
}
/** Schedules loading of the selected asset, if it was not scheduled already.
*
* @param assetPath internal path to the asset.
* @param assetClass class of the asset. */
public void load(final String assetPath, final Class> assetClass) {
load(assetPath, assetClass, null);
}
/** Schedules loading of the selected asset, if it was not scheduled already.
*
* @param assetPath assetPath internal path to the asset.
* @param assetClass assetClass class of the asset.
* @param loadingParameters specific loading parameters.
* @param type of asset class to load. */
public void load(final String assetPath, final Class assetClass,
final AssetLoaderParameters loadingParameters) {
if (isAssetNotScheduled(assetPath)) {
assetManager.load(assetPath, assetClass, loadingParameters);
}
}
private boolean isAssetNotScheduled(final String assetPath) {
return !isLoaded(assetPath) && !scheduledAssets.contains(assetPath);
}
/** @param assetPath internal path to the asset.
* @return true if the asset is fully loaded. */
public boolean isLoaded(final String assetPath) {
return assetManager.isLoaded(assetPath) || eagerAssetManager.isLoaded(assetPath);
}
/** Schedules disposing of the selected asset.
*
* @param assetPath internal path to the asset. */
public void unload(final String assetPath) {
if (assetManager.isLoaded(assetPath) || scheduledAssets.contains(assetPath)) {
assetManager.unload(assetPath);
} else if (eagerAssetManager.isLoaded(assetPath)) {
eagerAssetManager.unload(assetPath);
}
}
/** Immediately loads all scheduled assets. */
public void finishLoading() {
assetManager.finishLoading();
doOnLoadingFinish();
}
private void invokeOnLoadActions() {
for (final Runnable action : onLoadActions) {
if (action != null) {
action.run();
}
}
onLoadActions.clear();
}
/** Immediately loads the chosen asset. Schedules loading of the asset if it wasn't selected to be loaded already.
*
* @param assetPath internal path to the asset.
* @param assetClass class of the loaded asset.
* @return instance of the loaded asset.
* @param type of asset class to load. */
public Type finishLoading(final String assetPath, final Class assetClass) {
return finishLoading(assetPath, assetClass, null);
}
/** Immediately loads the chosen asset. Schedules loading of the asset if it wasn't selected to be loaded already.
*
* @param assetPath internal path to the asset.
* @param assetClass class of the loaded asset.
* @param loadingParameters used if asset is not already loaded.
* @return instance of the loaded asset.
* @param type of asset class to load. */
public Type finishLoading(final String assetPath, final Class assetClass,
final AssetLoaderParameters loadingParameters) {
if (assetManager.isLoaded(assetPath)) {
return assetManager.get(assetPath, assetClass);
}
if (!eagerAssetManager.isLoaded(assetPath)) {
eagerAssetManager.load(assetPath, assetClass, loadingParameters);
eagerAssetManager.finishLoadingAsset(assetPath);
}
return eagerAssetManager.get(assetPath, assetClass);
}
private void injectRequestedAssets() {
for (final ObjectSetIterator iterator = assetInjections.iterator(); iterator.hasNext();) {
final AssetInjection assetInjection = iterator.next();
if (assetInjection.inject(this)) {
assetInjection.removeScheduledAssets(scheduledAssets);
iterator.remove();
}
}
}
/** Manually updates wrapped asset manager.
*
* @return true if all scheduled assets are loaded. */
public boolean update() {
final boolean isLoaded = assetManager.update();
if (isLoaded) {
doOnLoadingFinish();
}
return isLoaded;
}
private void doOnLoadingFinish() {
injectRequestedAssets();
invokeOnLoadActions();
messageDispatcher.postMessage(AutumnMessage.ASSETS_LOADED);
}
/** @return progress of asset loading. Does not include eagerly loaded assets. */
public float getLoadingProgress() {
return assetManager.getProgress();
}
/** @return progress of asset loading. Includes eagerly loaded assets. */
public float getTotalLoadingProgress() {
return assetManager.getProgress() * eagerAssetManager.getProgress();
}
/** @return direct reference to internal {@link AssetManager} instance. Use with care.
* @see #getEagerAssetManager() */
public AssetManager getAssetManager() {
return assetManager;
}
/** @return direct reference to internal {@link AssetManager} used for eager asset loading. For synchronous asset
* loading purposes. Use with care. */
public AssetManager getEagerAssetManager() {
return eagerAssetManager;
}
/** @param assetPath internal path to the asset.
* @param assetClass class of the asset.
* @return an instance of the loaded asset, if available.
* @param type of asset class to get. */
public Type get(final String assetPath, final Class assetClass) {
if (assetManager.isLoaded(assetPath)) {
return assetManager.get(assetPath, assetClass);
}
return eagerAssetManager.get(assetPath, assetClass);
}
@Destroy(priority = AutumnActionPriority.MIN_PRIORITY)
private void destroy() {
Disposables.disposeOf(assetManager, eagerAssetManager);
}
@Override
public Class getSupportedAnnotationType() {
return Asset.class;
}
@Override
public boolean isSupportingFields() {
return true;
}
@Override
public void processField(final Field field, final Asset annotation, final Object component, final Context context,
final ContextInitializer initializer, final ContextDestroyer contextDestroyer) {
validateAssetData(component, field, annotation);
if (field.getType().equals(Lazy.class)) {
handleLazyAssetInjection(component, field, annotation);
} else if (field.getType().equals(Array.class)) {
handleArrayInjection(component, field, annotation);
} else if (field.getType().equals(ObjectSet.class)) {
handleSetInjection(component, field, annotation);
} else if (field.getType().equals(ObjectMap.class)) {
handleMapInjection(component, field, annotation);
} else {
handleRegularAssetInjection(component, field, annotation);
}
}
private static void validateAssetData(final Object component, final Field field, final Asset assetData) {
if (assetData.value().length == 0) {
throw new GdxRuntimeException("Asset paths array cannot be empty. Found empty array in field: " + field
+ " of component: " + component + ".");
}
if (assetData.keys().length != 0 && assetData.value().length != assetData.keys().length) {
throw new GdxRuntimeException(
"In @Asset annotation, keys() array length (if specified) has to match value() array length. Found different lengths in field: "
+ field + " of component: " + component + ".");
}
}
private void handleLazyAssetInjection(final Object component, final Field field, final Asset assetData) {
if (Annotations.isNotVoid(assetData.lazyCollection())) {
handleLazyAssetCollectionInjection(component, field, assetData);
return;
} else if (assetData.value().length != 1) {
throw new GdxRuntimeException(
"Lazy wrapper can contain only one asset if lazy collection type is not provided. Found multiple assets in field: "
+ field + " of component: " + component);
}
final String assetPath = assetData.value()[0];
if (!assetData.loadOnDemand()) {
load(assetPath, assetData.type());
}
try {
Reflection.setFieldValue(field, component,
Lazy.providedBy(new AssetProvider(this, assetPath, assetData.type(), assetData.loadOnDemand())));
} catch (final ReflectionException exception) {
throw new GdxRuntimeException("Unable to inject lazy asset.", exception);
}
}
private void handleLazyAssetCollectionInjection(final Object component, final Field field, final Asset assetData) {
final String[] assetPaths = assetData.value();
final Class> assetType = assetData.type();
if (!assetData.loadOnDemand()) {
for (final String assetPath : assetPaths) {
load(assetPath, assetType);
}
}
try {
ObjectProvider> provider;
final Class> collectionClass = assetData.lazyCollection();
if (collectionClass.equals(Array.class)) {
provider = new ArrayAssetProvider(this, assetPaths, assetType, assetData.loadOnDemand());
} else if (collectionClass.equals(ObjectSet.class)) {
provider = new ObjectSetAssetProvider(this, assetPaths, assetType, assetData.loadOnDemand());
} else if (collectionClass.equals(ObjectMap.class)) {
provider = new ObjectMapAssetProvider(this, assetPaths, assetData.keys(), assetType,
assetData.loadOnDemand());
} else {
throw new GdxRuntimeException("Unsupported collection type in annotated class of component: "
+ component + ". Expected Array, ObjectSet or ObjectMap, received: " + collectionClass + ".");
}
Reflection.setFieldValue(field, component, Lazy.providedBy(provider));
} catch (final ReflectionException exception) {
throw new GdxRuntimeException("Unable to inject lazy asset collection.", exception);
}
}
private void handleRegularAssetInjection(final Object component, final Field field, final Asset assetData) {
if (assetData.value().length != 1) {
throw new GdxRuntimeException(
"Regular fields can store only 1 asset. If the field is a collection, its type is not currently supported: only LibGDX Array, ObjectSet and ObjectMap are permitted. Regular arrays will not be supported. Found multiple assets in field: "
+ field + " of component: " + component);
}
final String assetPath = assetData.value()[0];
if (assetData.loadOnDemand()) {
// Loaded immediately.
@SuppressWarnings("unchecked") final Object asset = finishLoading(assetPath, field.getType());
try {
Reflection.setFieldValue(field, component, asset);
} catch (final ReflectionException exception) {
throw new GdxRuntimeException("Unable to inject asset loaded on demand.", exception);
}
} else {
load(assetPath, field.getType());
// Scheduled to be loaded, delayed injection.
assetInjections.add(new StandardAssetInjection(field, assetPath, component));
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void handleArrayInjection(final Object component, final Field field, final Asset assetData) {
if (assetData.loadOnDemand()) {
try {
Array assets = (Array) Reflection.getFieldValue(field, component);
if (assets == null) {
assets = GdxArrays.newArray();
}
for (final String assetPath : assetData.value()) {
assets.add(finishLoading(assetPath, assetData.type()));
}
Reflection.setFieldValue(field, component, assets);
} catch (final ReflectionException exception) {
throw new GdxRuntimeException("Unable to inject array of assets into: " + component, exception);
}
} else {
for (final String assetPath : assetData.value()) {
load(assetPath, assetData.type());
}
// Scheduled to be loaded, delayed injection.
assetInjections.add(new ArrayAssetInjection(assetData.value(), assetData.type(), field, component));
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void handleSetInjection(final Object component, final Field field, final Asset assetData) {
if (assetData.loadOnDemand()) {
try {
ObjectSet assets = (ObjectSet) Reflection.getFieldValue(field, component);
if (assets == null) {
assets = GdxSets.newSet();
}
for (final String assetPath : assetData.value()) {
assets.add(finishLoading(assetPath, assetData.type()));
}
Reflection.setFieldValue(field, component, assets);
} catch (final ReflectionException exception) {
throw new GdxRuntimeException("Unable to inject set of assets into: " + component, exception);
}
} else {
for (final String assetPath : assetData.value()) {
load(assetPath, assetData.type());
}
// Scheduled to be loaded, delayed injection.
assetInjections.add(new ObjectSetAssetInjection(assetData.value(), assetData.type(), field, component));
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void handleMapInjection(final Object component, final Field field, final Asset assetData) {
if (assetData.loadOnDemand()) {
final String[] assetPaths = assetData.value();
final String[] assetKeys = assetData.keys().length == 0 ? assetData.value() : assetData.keys();
try {
ObjectMap assets = (ObjectMap) Reflection.getFieldValue(field, component);
if (assets == null) {
assets = GdxMaps.newObjectMap();
}
for (int assetIndex = 0; assetIndex < assetPaths.length; assetIndex++) {
assets.put(assetKeys[assetIndex], finishLoading(assetPaths[assetIndex], assetData.type()));
}
Reflection.setFieldValue(field, component, assets);
} catch (final ReflectionException exception) {
throw new GdxRuntimeException("Unable to inject array of assets into: " + component, exception);
}
} else {
for (final String assetPath : assetData.value()) {
load(assetPath, assetData.type());
}
// Scheduled to be loaded, delayed injection.
assetInjections.add(new ObjectMapAssetInjection(assetData.value(), assetData.keys(), assetData.type(),
field, component));
}
}
/** @param action will be executed after all currently scheduled assets are loaded. This requires an
* {@link #update()} or {@link #finishLoading()} call. */
public void addOnLoadAction(final Runnable action) {
onLoadActions.add(action);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy