Multithreaded AssetLoading

Hi Folks,



I've recently implemented a MTed AssetManagers - well in fact 2, but more on that later.



The idea is to use a threadsafe AssetManager (like the default DesktopAssetManager is) capsulate it into another AssetManager which adds the ability to load FutureAssets. E.g. loadFutureAsset(AssetKey<T> asset) return a Future<T>.



Internally it's implemented with an TaskExecutorService (a Cached one; could be easily changed).



The second implementation can capsulate several AssetManagers into one and will divide the loading tasks between them. (This only makes sense, if the assetManagers are not threadsafe)



Is there any interest in such code and how to use it/what the use-cases are?

(Btw… one drawback is, that you have to shutdown the CachedThreadPoolExecutor)

Would it be possible for you to share the first one? :stuck_out_tongue:

This is something that people were asking for a lot. The DesktopAssetManager indeed can be used from many threads at the same time without any issues, so the second implementation is probably unnecessary.

Okay; So here is the first one (well; it may look a bit strange. But this way you can even still implement your own AssetManager with only implementing few methods.)



package com.blemme30.loader;

import com.jme3.asset.*;
import com.jme3.audio.AudioData;
import com.jme3.audio.AudioKey;
import com.jme3.font.BitmapFont;
import com.jme3.material.Material;
import com.jme3.scene.Spatial;
import com.jme3.shader.Shader;
import com.jme3.shader.ShaderKey;
import com.jme3.texture.Texture;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.jcip.annotations.ThreadSafe;

/**
 *
 * @author tim8dev
 */
@ThreadSafe
public abstract class MTAssetManager implements AssetManager {
    private static final Logger logger = Logger.getLogger(MTAssetManager.class.getName());
    protected final ExecutorService exec;

    public static MTAssetManager encapsulateThreadSafe(AssetManager threadSafeManager) {
        return new MTTSManager(Executors.newCachedThreadPool(), threadSafeManager);
    }

    protected MTAssetManager(ExecutorService exec) {
        this.exec = exec;
    }

    // abstract methods:
    public abstract<T> Future<T> loadFutureAsset(AssetKey<T> key);

    public void shutdownNow() {
        List<Runnable> runs = exec.shutdownNow();
        if (runs != null && !runs.isEmpty())
            logger.log(Level.WARNING, "Objects not loaded due to shutting down.{0}", runs);
    }

    public void shutdown() {
        try {
            // wait a half second = 500 millies
            exec.awaitTermination(500, TimeUnit.MILLISECONDS);
        } catch (InterruptedException ex) {
            logger.log(Level.SEVERE, null, ex);
        } finally {
            if(!exec.isShutdown()) {
                List<Runnable> runs = exec.shutdownNow();
                if(runs != null && !runs.isEmpty())
                    logger.log(Level.WARNING, "Objects not loaded due to shutting down.{0}", runs);
            }
        }
    }

    // helper method for loading a Future immediately:
    private<T> T loadAssetImmediately(AssetKey<T> key) {
        try {
            return loadFutureAsset(key).get();
        } catch (InterruptedException ex) {
            logger.log(Level.WARNING, "Failed to load resource: " + key, ex);
        } catch (ExecutionException ex) {
            logger.log(Level.WARNING, "Failed to load resource: " + key, ex);
        }
        return null;
    }
    
    @Override
    public Object loadAsset(AssetKey key) {
        return loadAssetImmediately(key);
    }

    @Override
    public AudioData loadAudio(AudioKey key) {
        return (AudioData) loadAssetImmediately(key);
    }

    @Override
    public BitmapFont loadFont(String name) {
        return loadAssetImmediately(new AssetKey<BitmapFont>(name));
    }

    @Override
    public Material loadMaterial(String name) {
        return loadAssetImmediately(new AssetKey<Material>(name));
    }

    @Override
    public Spatial loadModel(ModelKey key) {
        return loadAssetImmediately(key);
    }

    @Override
    public Shader loadShader(ShaderKey key) {
        return loadAssetImmediately(key);
    }

    @Override
    public Texture loadTexture(TextureKey key) {
        return loadAssetImmediately(key);
    }
    
    // methods forwarding to Assetkeys.

    @Override
    public AudioData loadAudio(String name) {
        return loadAudio(new AudioKey(name));
    }

    @Override
    public Object loadAsset(String name) {
        return loadAsset(new AssetKey(name));
    }

    @Override
    public Spatial loadModel(String name) {
        return loadModel(new ModelKey(name));
    }

    @Override
    public Texture loadTexture(String name) {
        return loadTexture(new TextureKey(name));
    }

    /**
     * Multithreaded AssetManager delegating to one Threadsafe Managers.
     */
    private static class MTTSManager extends MTAssetManager {
        private final AssetManager manager;

        MTTSManager(ExecutorService exec, AssetManager threadSafeManager) {
            super(exec);
            this.manager = threadSafeManager;
        }

        @Override
        public void registerLoader(String loaderClassName, String... extensions) {
            manager.registerLoader(loaderClassName, extensions);
        }

        @Override
        public void registerLocator(String rootPath, String locatorClassName) {
            manager.registerLocator(rootPath, locatorClassName);
        }

        @Override
        public void setAssetEventListener(AssetEventListener listener) {
            manager.setAssetEventListener(listener);
        }

        @Override
        public <T> Future<T> loadFutureAsset(final AssetKey<T> key) {
            return exec.submit(new Callable<T>() {
                @Override public T call() {
                    return manager.loadAsset(key);
                }
            });
        }
    }

}