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

com.github.czyzby.autumn.mvc.component.asset.AssetService Maven / Gradle / Ivy

Go to download

MVC framework based on LibGDX using Autumn for components management and LML as view templates.

There is a newer version: 1.9.1.9.6
Show newest version

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