Package org.terasology.asset

Source Code of org.terasology.asset.AssetManager

/*
* Copyright 2013 MovingBlocks
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.terasology.asset;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Table;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terasology.asset.sources.ArchiveSource;
import org.terasology.asset.sources.AssetSourceCollection;
import org.terasology.asset.sources.DirectorySource;
import org.terasology.engine.TerasologyConstants;
import org.terasology.entitySystem.prefab.Prefab;
import org.terasology.logic.behavior.asset.BehaviorTree;
import org.terasology.module.Module;
import org.terasology.module.ModuleEnvironment;
import org.terasology.module.sandbox.API;
import org.terasology.naming.Name;
import org.terasology.persistence.ModuleContext;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

// TODO: Split out an interface, possibly two with one for loading and adding assets, the other with disposal and other more management methods
@API
public class AssetManager {

    private static final Logger logger = LoggerFactory.getLogger(AssetManager.class);

    private ModuleEnvironment environment;
    private Map<Name, AssetSource> assetSources = Maps.newHashMap();
    private Map<AssetType, Map<String, AssetLoader<?>>> assetLoaders = Maps.newEnumMap(AssetType.class);
    private Map<AssetUri, Asset<?>> assetCache = Maps.newHashMap();
    private Map<AssetUri, AssetSource> overrides = Maps.newHashMap();
    private Map<AssetType, AssetFactory<?, ?>> factories = Maps.newHashMap();
    private Map<AssetType, Table<Name, Name, AssetUri>> uriLookup = Maps.newHashMap();
    private ListMultimap<AssetType, AssetResolver<?, ?>> resolvers = ArrayListMultimap.create();

    public AssetManager(ModuleEnvironment environment) {
        for (AssetType type : AssetType.values()) {
            uriLookup.put(type, HashBasedTable.<Name, Name, AssetUri>create());
        }
        setEnvironment(environment);
    }

    public ModuleEnvironment getEnvironment() {
        return environment;
    }

    public void setEnvironment(ModuleEnvironment environment) {
        this.environment = environment;
        assetSources.clear();
        for (Module module : environment) {
            Collection<Path> location = module.getLocations();
            if (!location.isEmpty()) {
                List<AssetSource> sources = Lists.newArrayList();
                for (Path path : location) {
                    sources.add(createAssetSource(module.getId(), path));
                }
                AssetSource source = new AssetSourceCollection(module.getId(), sources);
                assetSources.put(source.getSourceId(), source);

                for (AssetUri asset : source.list()) {
                    uriLookup.get(asset.getAssetType()).put(asset.getAssetName(), asset.getModuleName(), asset);
                }
            }
        }
        applyOverrides();
        refresh();
    }

    private AssetSource createAssetSource(Name id, Path path) {
        if (Files.isRegularFile(path)) {
            return new ArchiveSource(id, path.toFile(), TerasologyConstants.ASSETS_SUBDIRECTORY, TerasologyConstants.OVERRIDES_SUBDIRECTORY,
                    TerasologyConstants.DELTAS_SUBDIRECTORY);
        } else {
            return new DirectorySource(id, path.resolve(TerasologyConstants.ASSETS_SUBDIRECTORY),
                    path.resolve(TerasologyConstants.OVERRIDES_SUBDIRECTORY), path.resolve(TerasologyConstants.DELTAS_SUBDIRECTORY));
        }
    }

    public <T extends Asset<U>, U extends AssetData> void addResolver(AssetType assetType, AssetResolver<T, U> resolver) {
        resolvers.put(assetType, resolver);
    }

    public void setAssetFactory(AssetType type, AssetFactory<?, ?> factory) {
        factories.put(type, factory);
    }

    public List<AssetUri> resolveAll(AssetType type, String name) {
        AssetUri uri = new AssetUri(type, name);
        if (uri.isValid()) {
            return Lists.newArrayList(uri);
        }

        return resolveAll(type, new Name(name));
    }

    public List<AssetUri> resolveAll(AssetType type, Name name) {
        List<AssetUri> results = Lists.newArrayList(uriLookup.get(type).row(name).values());
        for (AssetResolver<?, ?> resolver : resolvers.get(type)) {
            AssetUri additionalUri = resolver.resolve(name);
            if (additionalUri != null) {
                results.add(additionalUri);
            }
        }
        return results;
    }

    public AssetUri resolve(AssetType type, String name) {
        List<AssetUri> possibilities = resolveAll(type, name);
        switch (possibilities.size()) {
            case 0:
                logger.warn("Failed to resolve {}:{}", type, name);
                return null;
            case 1:
                return possibilities.get(0);
            default:
                Module context = ModuleContext.getContext();
                if (context != null) {
                    Set<Name> dependencies = environment.getDependencyNamesOf(context.getId());
                    Iterator<AssetUri> iterator = possibilities.iterator();
                    while (iterator.hasNext()) {
                        AssetUri possibleUri = iterator.next();
                        if (context.getId().equals(possibleUri.getModuleName())) {
                            return possibleUri;
                        }
                        if (!dependencies.contains(possibleUri.getModuleName())) {
                            iterator.remove();
                        }
                    }
                    if (possibilities.size() == 1) {
                        return possibilities.get(0);
                    }
                }
                logger.warn("Failed to resolve {}:{} - too many valid matches {}", type, name, possibilities);
                return null;
        }
    }

    public <T extends AssetData> T resolveAndLoadData(AssetType type, String name, Class<T> dataClass) {
        AssetData data = resolveAndLoadData(type, name);
        if (dataClass.isInstance(data)) {
            return dataClass.cast(data);
        }
        return null;
    }

    public <T extends AssetData> T resolveAndTryLoadData(AssetType type, String name, Class<T> dataClass) {
        AssetData data = resolveAndTryLoadData(type, name);
        if (dataClass.isInstance(data)) {
            return dataClass.cast(data);
        }
        return null;
    }

    public AssetData resolveAndLoadData(AssetType type, String name) {
        AssetUri uri = resolve(type, name);
        if (uri != null) {
            return loadAssetData(uri, true);
        }
        return null;
    }

    public AssetData resolveAndTryLoadData(AssetType type, String name) {
        AssetUri uri = resolve(type, name);
        if (uri != null) {
            return loadAssetData(uri, false);
        }
        return null;
    }

    public <T extends Asset<?>> T resolveAndLoad(AssetType type, String name, Class<T> assetClass) {
        Asset<?> result = resolveAndLoad(type, name);
        if (assetClass.isInstance(result)) {
            return assetClass.cast(result);
        }
        return null;
    }

    public <T extends Asset<?>> T resolveAndTryLoad(AssetType type, String name, Class<T> assetClass) {
        Asset<?> result = resolveAndTryLoad(type, name);
        if (assetClass.isInstance(result)) {
            return assetClass.cast(result);
        }
        return null;
    }

    public Asset<?> resolveAndLoad(AssetType type, String name) {
        AssetUri uri = resolve(type, name);
        if (uri != null) {
            return loadAsset(uri, false);
        }
        return null;
    }

    public Asset<?> resolveAndTryLoad(AssetType type, String name) {
        AssetUri uri = resolve(type, name);
        if (uri != null) {
            return loadAsset(uri, true);
        }
        return null;
    }

    public <T> T tryLoadAssetData(AssetUri uri, Class<T> type) {
        AssetData data = loadAssetData(uri, false);
        if (type.isInstance(data)) {
            return type.cast(data);
        }
        return null;
    }

    public <T> T loadAssetData(AssetUri uri, Class<T> type) {
        AssetData data = loadAssetData(uri, true);
        if (type.isInstance(data)) {
            return type.cast(data);
        }
        return null;
    }

    public void register(AssetType type, String extension, AssetLoader<?> loader) {
        Map<String, AssetLoader<?>> assetTypeMap = assetLoaders.get(type);
        if (assetTypeMap == null) {
            assetTypeMap = Maps.newHashMap();
            assetLoaders.put(type, assetTypeMap);
        }
        assetTypeMap.put(extension.toLowerCase(Locale.ENGLISH), loader);
    }

    public <T extends Asset<?>> T tryLoadAsset(AssetUri uri, Class<T> type) {
        Asset<?> result = loadAsset(uri, false);
        if (type.isInstance(result)) {
            return type.cast(result);
        }
        return null;
    }

    public Asset<?> loadAsset(AssetUri uri) {
        return loadAsset(uri, true);
    }

    public <D extends AssetData> void reload(Asset<D> asset) {
        AssetData data = loadAssetData(asset.getURI(), false);
        if (data != null) {
            try {
                asset.reload((D) data);
            } catch (Exception e) {
                logger.error("Could not reload asset " + asset.getURI(), e);
            }
        }
    }

    public <T extends Asset<?>> T loadAsset(AssetUri uri, Class<T> assetClass) {
        Asset<?> result = loadAsset(uri, true);
        if (assetClass.isInstance(result)) {
            return assetClass.cast(result);
        }
        return null;
    }

    private AssetData loadAssetData(AssetUri uri, boolean logErrors) {
        if (!uri.isValid()) {
            return null;
        }

        List<URL> urls = getAssetURLs(uri);
        if (urls.size() == 0) {
            if (logErrors) {
                logger.warn("Unable to resolve asset: {}", uri);
            }
            return null;
        }

        for (URL url : urls) {
            int extensionIndex = url.toString().lastIndexOf('.');
            if (extensionIndex == -1) {
                continue;
            }

            String extension = url.toString().substring(extensionIndex + 1).toLowerCase(Locale.ENGLISH);
            Map<String, AssetLoader<?>> extensionMap = assetLoaders.get(uri.getAssetType());
            if (extensionMap == null) {
                continue;
            }

            AssetLoader<?> loader = extensionMap.get(extension);
            if (loader == null) {
                continue;
            }

            Module module = environment.get(uri.getModuleName());
            List<URL> deltas;
            if (uri.getAssetType().isDeltaSupported()) {
                deltas = Lists.newArrayList();
                for (Module deltaModule : environment.getModulesOrderedByDependencies()) {
                    AssetSource source = assetSources.get(deltaModule.getId());
                    if (source != null) {
                        deltas.addAll(source.getDelta(uri));
                    }
                }
            } else {
                deltas = Collections.emptyList();
            }
            try (InputStream stream = AccessController.doPrivileged(new PrivilegedOpenStream(url))) {
                urls.remove(url);
                urls.add(0, url);
                return loader.load(module, stream, urls, deltas);
            } catch (PrivilegedActionException e) {
                logger.error("Error reading asset {}", uri, e.getCause());
                return null;
            } catch (IOException ioe) {
                logger.error("Error reading asset {}", uri, ioe);
                return null;
            }
        }
        logger.warn("Unable to resolve asset: {}", uri);
        return null;
    }

    private <D extends AssetData> Asset<?> loadAsset(AssetUri uri, boolean logErrors) {
        if (!uri.isValid()) {
            return null;
        }

        Asset<?> asset = assetCache.get(uri);
        if (asset != null) {
            return asset;
        }

        AssetFactory<D, Asset<D>> factory = (AssetFactory<D, Asset<D>>) factories.get(uri.getAssetType());
        if (factory == null) {
            logger.error("No asset factory set for assets of type {}", uri.getAssetType());
            return null;
        }

        for (AssetResolver<?, ?> resolver : resolvers.get(uri.getAssetType())) {
            AssetResolver<Asset<D>, D> typedResolver = (AssetResolver<Asset<D>, D>) resolver;
            Asset<D> result = typedResolver.resolve(uri, factory);
            if (result != null) {
                assetCache.put(uri, result);
                return result;
            }
        }

        try (ModuleContext.ContextSpan ignored = ModuleContext.setContext(environment.get(uri.getModuleName()))) {
            AssetData data = loadAssetData(uri, logErrors);

            if (data != null) {
                // TODO: verify that data class matches factory data class type
                // for example: if (factory.getDataTypeClass().isInstance(data)) ..
                asset = factory.buildAsset(uri, (D) data);
                if (asset != null) {
                    logger.debug("Loaded {}", uri);
                    assetCache.put(uri, asset);
                }
            }
        } catch (Exception e) {
            logger.error("Error loading asset: {}", uri, e);
        }
        return asset;
    }

    public void refresh() {
        List<Asset<?>> keepAndReload = Lists.newArrayList();
        List<Asset<?>> dispose = Lists.newArrayList();

        for (Asset<?> asset : assetCache.values()) {
            if (asset.getURI().getModuleName().equals(TerasologyConstants.ENGINE_MODULE) && !(asset instanceof Prefab) && !(asset instanceof BehaviorTree)) {
                keepAndReload.add(asset);
            } else {
                dispose.add(asset);
            }
        }

        for (Asset<?> asset : keepAndReload) {
            reload(asset);
        }

        for (Asset<?> asset : dispose) {
            dispose(asset);
        }
    }

    public void dispose(Asset<?> asset) {
        if (!asset.isDisposed()) {
            try {
                asset.dispose();
            } catch (Exception e) {
                logger.error("Could not dispose asset {}", asset.getURI(), e);
            }
        }
        assetCache.remove(asset.getURI());
    }

    public <U extends AssetData> Asset<U> generateAsset(AssetUri uri, U data) {
        AssetFactory<U, Asset<U>> assetFactory = (AssetFactory<U, Asset<U>>) factories.get(uri.getAssetType());
        if (assetFactory == null) {
            logger.warn("Unsupported asset type: {}", uri.getAssetType());
            return null;
        }
        Asset<U> asset = (Asset<U>) assetCache.get(uri);
        if (asset != null) {
            logger.debug("Reloading {} with newly generated data", uri);
            asset.reload(data);
        } else {
            asset = assetFactory.buildAsset(uri, data);
            if (asset != null && !asset.isDisposed()) {
                assetCache.put(uri, asset);
            }
        }
        return asset;
    }

    public <U extends AssetData> Asset<U> generateTemporaryAsset(AssetType type, U data) {
        return generateAsset(new AssetUri(type, "temp", UUID.randomUUID().toString()), data);
    }

    public <D extends AssetData> void removeAssetSource(AssetSource source) {
        assetSources.remove(source.getSourceId());
        for (AssetUri override : source.listOverrides()) {
            if (overrides.get(override).equals(source)) {
                overrides.remove(override);
                Asset<D> asset = (Asset<D>) assetCache.get(override);
                if (asset != null) {
                    if (TerasologyConstants.ENGINE_MODULE.equals(override.getModuleName())) {
                        AssetData data = loadAssetData(override, true);
                        asset.reload((D) data);
                    } else {
                        dispose(asset);
                    }
                }
            }
        }
        for (Table<Name, Name, AssetUri> table : uriLookup.values()) {
            Map<Name, AssetUri> columnMap = table.column(source.getSourceId());
            for (AssetUri value : columnMap.values()) {
                Asset<?> asset = assetCache.remove(value);
                if (asset != null) {
                    asset.dispose();
                }
            }
            columnMap.clear();
        }
    }

    public void applyOverrides() {
        overrides.clear();
        for (AssetSource assetSource : assetSources.values()) {
            for (AssetUri overrideURI : assetSource.listOverrides()) {
                overrides.put(overrideURI, assetSource);
            }
        }
    }

    public Iterable<AssetUri> listAssets() {
        return new Iterable<AssetUri>() {

            @Override
            public Iterator<AssetUri> iterator() {
                return new AllAssetIterator();
            }
        };
    }

    public Iterable<AssetUri> listAssets(final AssetType type) {
        return new Iterable<AssetUri>() {

            @Override
            public Iterator<AssetUri> iterator() {
                return new TypedAssetIterator(type);
            }
        };
    }

    public <T> Iterable<T> listLoadedAssets(final AssetType type, Class<T> assetClass) {
        List<T> results = Lists.newArrayList();
        for (Map.Entry<AssetUri, Asset<?>> entry : assetCache.entrySet()) {
            if (entry.getKey().getAssetType() == type && assetClass.isInstance(entry.getValue())) {
                results.add(assetClass.cast(entry.getValue()));
            }
        }
        return results;
    }

    public Iterable<Name> listModuleNames() {
        return assetSources.keySet();
    }

    public List<URL> getAssetURLs(AssetUri uri) {
        AssetSource overrideSource = overrides.get(uri);
        if (overrideSource != null) {
            return overrideSource.getOverride(uri);
        } else {
            AssetSource source = assetSources.get(uri.getModuleName());
            if (source != null) {
                return source.get(uri);
            }
        }
        return Lists.newArrayList();
    }

    public InputStream getAssetStream(AssetUri uri) throws IOException {
        final List<URL> assetURLs = getAssetURLs(uri);

        if (assetURLs.isEmpty()) {
            return null;
        }

        try {
            return AccessController.doPrivileged(new PrivilegedOpenStream(assetURLs.get(0)));
        } catch (PrivilegedActionException e) {
            throw new IOException(e.getCause().getMessage(), e.getCause());
        }
    }

    private class AllAssetIterator implements Iterator<AssetUri> {
        Iterator<AssetSource> sourceIterator;
        Iterator<AssetUri> currentUriIterator;
        AssetUri next;

        public AllAssetIterator() {
            sourceIterator = assetSources.values().iterator();
            if (sourceIterator.hasNext()) {
                currentUriIterator = sourceIterator.next().list().iterator();
            } else {
                currentUriIterator = Collections.emptyIterator();
            }
            iterate();
        }

        @Override
        public boolean hasNext() {
            return next != null;
        }

        @Override
        public AssetUri next() {
            AssetUri result = next;
            iterate();
            return result;
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException();
        }

        private void iterate() {
            while (!currentUriIterator.hasNext() && sourceIterator.hasNext()) {
                currentUriIterator = sourceIterator.next().list().iterator();
            }
            if (currentUriIterator.hasNext()) {
                next = currentUriIterator.next();
            } else {
                next = null;
            }
        }
    }

    private class TypedAssetIterator implements Iterator<AssetUri> {
        AssetType type;
        Iterator<AssetSource> sourceIterator;
        Iterator<AssetUri> currentUriIterator;
        AssetUri next;

        public TypedAssetIterator(AssetType type) {
            this.type = type;
            sourceIterator = assetSources.values().iterator();
            if (sourceIterator.hasNext()) {
                currentUriIterator = sourceIterator.next().list(type).iterator();
            } else {
                currentUriIterator = Collections.emptyIterator();
            }
            iterate();
        }

        @Override
        public boolean hasNext() {
            return next != null;
        }

        @Override
        public AssetUri next() {
            AssetUri result = next;
            iterate();
            return result;
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException();
        }

        private void iterate() {
            while (!currentUriIterator.hasNext() && sourceIterator.hasNext()) {
                currentUriIterator = sourceIterator.next().list(type).iterator();
            }
            if (currentUriIterator.hasNext()) {
                next = currentUriIterator.next();
            } else {
                next = null;
            }
        }
    }
}
TOP

Related Classes of org.terasology.asset.AssetManager

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.