• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*******************************************************************************
2  * Copyright 2011 See AUTHORS file.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  ******************************************************************************/
16 
17 package com.badlogic.gdx.assets;
18 
19 import java.util.Stack;
20 
21 import com.badlogic.gdx.Application;
22 import com.badlogic.gdx.assets.loaders.AssetLoader;
23 import com.badlogic.gdx.assets.loaders.BitmapFontLoader;
24 import com.badlogic.gdx.assets.loaders.FileHandleResolver;
25 import com.badlogic.gdx.assets.loaders.I18NBundleLoader;
26 import com.badlogic.gdx.assets.loaders.MusicLoader;
27 import com.badlogic.gdx.assets.loaders.ParticleEffectLoader;
28 import com.badlogic.gdx.assets.loaders.PixmapLoader;
29 import com.badlogic.gdx.assets.loaders.SkinLoader;
30 import com.badlogic.gdx.assets.loaders.SoundLoader;
31 import com.badlogic.gdx.assets.loaders.TextureAtlasLoader;
32 import com.badlogic.gdx.assets.loaders.TextureLoader;
33 import com.badlogic.gdx.assets.loaders.resolvers.InternalFileHandleResolver;
34 import com.badlogic.gdx.audio.Music;
35 import com.badlogic.gdx.audio.Sound;
36 import com.badlogic.gdx.graphics.Pixmap;
37 import com.badlogic.gdx.graphics.Texture;
38 import com.badlogic.gdx.graphics.g2d.BitmapFont;
39 import com.badlogic.gdx.graphics.g2d.ParticleEffect;
40 import com.badlogic.gdx.graphics.g2d.PolygonRegion;
41 import com.badlogic.gdx.graphics.g2d.PolygonRegionLoader;
42 import com.badlogic.gdx.graphics.g2d.TextureAtlas;
43 import com.badlogic.gdx.graphics.g3d.Model;
44 import com.badlogic.gdx.graphics.g3d.loader.G3dModelLoader;
45 import com.badlogic.gdx.graphics.g3d.loader.ObjLoader;
46 import com.badlogic.gdx.scenes.scene2d.ui.Skin;
47 import com.badlogic.gdx.utils.Array;
48 import com.badlogic.gdx.utils.Disposable;
49 import com.badlogic.gdx.utils.GdxRuntimeException;
50 import com.badlogic.gdx.utils.I18NBundle;
51 import com.badlogic.gdx.utils.JsonReader;
52 import com.badlogic.gdx.utils.Logger;
53 import com.badlogic.gdx.utils.ObjectIntMap;
54 import com.badlogic.gdx.utils.ObjectMap;
55 import com.badlogic.gdx.utils.ObjectSet;
56 import com.badlogic.gdx.utils.TimeUtils;
57 import com.badlogic.gdx.utils.UBJsonReader;
58 import com.badlogic.gdx.utils.async.AsyncExecutor;
59 import com.badlogic.gdx.utils.async.ThreadUtils;
60 import com.badlogic.gdx.utils.reflect.ClassReflection;
61 
62 /** Loads and stores assets like textures, bitmapfonts, tile maps, sounds, music and so on.
63  * @author mzechner */
64 public class AssetManager implements Disposable {
65 	final ObjectMap<Class, ObjectMap<String, RefCountedContainer>> assets = new ObjectMap();
66 	final ObjectMap<String, Class> assetTypes = new ObjectMap();
67 	final ObjectMap<String, Array<String>> assetDependencies = new ObjectMap();
68 	final ObjectSet<String> injected = new ObjectSet();
69 
70 	final ObjectMap<Class, ObjectMap<String, AssetLoader>> loaders = new ObjectMap();
71 	final Array<AssetDescriptor> loadQueue = new Array();
72 	final AsyncExecutor executor;
73 
74 	final Stack<AssetLoadingTask> tasks = new Stack();
75 	AssetErrorListener listener = null;
76 	int loaded = 0;
77 	int toLoad = 0;
78 
79 	final FileHandleResolver resolver;
80 
81 	Logger log = new Logger("AssetManager", Application.LOG_NONE);
82 
83 	/** Creates a new AssetManager with all default loaders. */
AssetManager()84 	public AssetManager () {
85 		this(new InternalFileHandleResolver());
86 	}
87 
88 	/** Creates a new AssetManager with all default loaders. */
AssetManager(FileHandleResolver resolver)89 	public AssetManager (FileHandleResolver resolver) {
90 		this(resolver, true);
91 	}
92 
93 	/** Creates a new AssetManager with optionally all default loaders. If you don't add the default loaders then you do have to
94 	 * manually add the loaders you need, including any loaders they might depend on.
95 	 * @param defaultLoaders whether to add the default loaders */
AssetManager(FileHandleResolver resolver, boolean defaultLoaders)96 	public AssetManager (FileHandleResolver resolver, boolean defaultLoaders) {
97 		this.resolver = resolver;
98 		if (defaultLoaders) {
99 			setLoader(BitmapFont.class, new BitmapFontLoader(resolver));
100 			setLoader(Music.class, new MusicLoader(resolver));
101 			setLoader(Pixmap.class, new PixmapLoader(resolver));
102 			setLoader(Sound.class, new SoundLoader(resolver));
103 			setLoader(TextureAtlas.class, new TextureAtlasLoader(resolver));
104 			setLoader(Texture.class, new TextureLoader(resolver));
105 			setLoader(Skin.class, new SkinLoader(resolver));
106 			setLoader(ParticleEffect.class, new ParticleEffectLoader(resolver));
107 			setLoader(com.badlogic.gdx.graphics.g3d.particles.ParticleEffect.class,
108 				new com.badlogic.gdx.graphics.g3d.particles.ParticleEffectLoader(resolver));
109 			setLoader(PolygonRegion.class, new PolygonRegionLoader(resolver));
110 			setLoader(I18NBundle.class, new I18NBundleLoader(resolver));
111 			setLoader(Model.class, ".g3dj", new G3dModelLoader(new JsonReader(), resolver));
112 			setLoader(Model.class, ".g3db", new G3dModelLoader(new UBJsonReader(), resolver));
113 			setLoader(Model.class, ".obj", new ObjLoader(resolver));
114 		}
115 		executor = new AsyncExecutor(1);
116 	}
117 
118 	/** Returns the {@link FileHandleResolver} for which this AssetManager
119 	 * was loaded with.
120 	 * @return the file handle resolver which this AssetManager uses */
getFileHandleResolver()121 	public FileHandleResolver getFileHandleResolver () {
122 		return resolver;
123 	}
124 
125 	/** @param fileName the asset file name
126 	 * @return the asset */
get(String fileName)127 	public synchronized <T> T get (String fileName) {
128 		Class<T> type = assetTypes.get(fileName);
129 		if (type == null) throw new GdxRuntimeException("Asset not loaded: " + fileName);
130 		ObjectMap<String, RefCountedContainer> assetsByType = assets.get(type);
131 		if (assetsByType == null) throw new GdxRuntimeException("Asset not loaded: " + fileName);
132 		RefCountedContainer assetContainer = assetsByType.get(fileName);
133 		if (assetContainer == null) throw new GdxRuntimeException("Asset not loaded: " + fileName);
134 		T asset = assetContainer.getObject(type);
135 		if (asset == null) throw new GdxRuntimeException("Asset not loaded: " + fileName);
136 		return asset;
137 	}
138 
139 	/** @param fileName the asset file name
140 	 * @param type the asset type
141 	 * @return the asset */
get(String fileName, Class<T> type)142 	public synchronized <T> T get (String fileName, Class<T> type) {
143 		ObjectMap<String, RefCountedContainer> assetsByType = assets.get(type);
144 		if (assetsByType == null) throw new GdxRuntimeException("Asset not loaded: " + fileName);
145 		RefCountedContainer assetContainer = assetsByType.get(fileName);
146 		if (assetContainer == null) throw new GdxRuntimeException("Asset not loaded: " + fileName);
147 		T asset = assetContainer.getObject(type);
148 		if (asset == null) throw new GdxRuntimeException("Asset not loaded: " + fileName);
149 		return asset;
150 	}
151 
152 	/** @param type the asset type
153 	 * @return all the assets matching the specified type */
getAll(Class<T> type, Array<T> out)154 	public synchronized <T> Array<T> getAll (Class<T> type, Array<T> out) {
155 		ObjectMap<String, RefCountedContainer> assetsByType = assets.get(type);
156 		if (assetsByType != null) {
157 			for (ObjectMap.Entry<String, RefCountedContainer> asset : assetsByType.entries()) {
158 				out.add(asset.value.getObject(type));
159 			}
160 		}
161 		return out;
162 	}
163 
164 	/** @param assetDescriptor the asset descriptor
165 	 * @return the asset */
get(AssetDescriptor<T> assetDescriptor)166 	public synchronized <T> T get (AssetDescriptor<T> assetDescriptor) {
167 		return get(assetDescriptor.fileName, assetDescriptor.type);
168 	}
169 
170 	/** Removes the asset and all its dependencies, if they are not used by other assets.
171 	 * @param fileName the file name */
unload(String fileName)172 	public synchronized void unload (String fileName) {
173 		// check if it's currently processed (and the first element in the stack, thus not a dependency)
174 		// and cancel if necessary
175 		if (tasks.size() > 0) {
176 			AssetLoadingTask currAsset = tasks.firstElement();
177 			if (currAsset.assetDesc.fileName.equals(fileName)) {
178 				currAsset.cancel = true;
179 				log.debug("Unload (from tasks): " + fileName);
180 				return;
181 			}
182 		}
183 
184 		// check if it's in the queue
185 		int foundIndex = -1;
186 		for (int i = 0; i < loadQueue.size; i++) {
187 			if (loadQueue.get(i).fileName.equals(fileName)) {
188 				foundIndex = i;
189 				break;
190 			}
191 		}
192 		if (foundIndex != -1) {
193 			toLoad--;
194 			loadQueue.removeIndex(foundIndex);
195 			log.debug("Unload (from queue): " + fileName);
196 			return;
197 		}
198 
199 		// get the asset and its type
200 		Class type = assetTypes.get(fileName);
201 		if (type == null) throw new GdxRuntimeException("Asset not loaded: " + fileName);
202 
203 		RefCountedContainer assetRef = assets.get(type).get(fileName);
204 
205 		// if it is reference counted, decrement ref count and check if we can really get rid of it.
206 		assetRef.decRefCount();
207 		if (assetRef.getRefCount() <= 0) {
208 			log.debug("Unload (dispose): " + fileName);
209 
210 			// if it is disposable dispose it
211 			if (assetRef.getObject(Object.class) instanceof Disposable) ((Disposable)assetRef.getObject(Object.class)).dispose();
212 
213 			// remove the asset from the manager.
214 			assetTypes.remove(fileName);
215 			assets.get(type).remove(fileName);
216 		} else {
217 			log.debug("Unload (decrement): " + fileName);
218 		}
219 
220 		// remove any dependencies (or just decrement their ref count).
221 		Array<String> dependencies = assetDependencies.get(fileName);
222 		if (dependencies != null) {
223 			for (String dependency : dependencies) {
224 				if (isLoaded(dependency)) unload(dependency);
225 			}
226 		}
227 		// remove dependencies if ref count < 0
228 		if (assetRef.getRefCount() <= 0) {
229 			assetDependencies.remove(fileName);
230 		}
231 	}
232 
233 	/** @param asset the asset
234 	 * @return whether the asset is contained in this manager */
containsAsset(T asset)235 	public synchronized <T> boolean containsAsset (T asset) {
236 		ObjectMap<String, RefCountedContainer> typedAssets = assets.get(asset.getClass());
237 		if (typedAssets == null) return false;
238 		for (String fileName : typedAssets.keys()) {
239 			T otherAsset = (T)typedAssets.get(fileName).getObject(Object.class);
240 			if (otherAsset == asset || asset.equals(otherAsset)) return true;
241 		}
242 		return false;
243 	}
244 
245 	/** @param asset the asset
246 	 * @return the filename of the asset or null */
getAssetFileName(T asset)247 	public synchronized <T> String getAssetFileName (T asset) {
248 		for (Class assetType : assets.keys()) {
249 			ObjectMap<String, RefCountedContainer> typedAssets = assets.get(assetType);
250 			for (String fileName : typedAssets.keys()) {
251 				T otherAsset = (T)typedAssets.get(fileName).getObject(Object.class);
252 				if (otherAsset == asset || asset.equals(otherAsset)) return fileName;
253 			}
254 		}
255 		return null;
256 	}
257 
258 	/** @param fileName the file name of the asset
259 	 * @return whether the asset is loaded */
isLoaded(String fileName)260 	public synchronized boolean isLoaded (String fileName) {
261 		if (fileName == null) return false;
262 		return assetTypes.containsKey(fileName);
263 	}
264 
265 	/** @param fileName the file name of the asset
266 	 * @return whether the asset is loaded */
isLoaded(String fileName, Class type)267 	public synchronized boolean isLoaded (String fileName, Class type) {
268 		ObjectMap<String, RefCountedContainer> assetsByType = assets.get(type);
269 		if (assetsByType == null) return false;
270 		RefCountedContainer assetContainer = assetsByType.get(fileName);
271 		if (assetContainer == null) return false;
272 		return assetContainer.getObject(type) != null;
273 	}
274 
275 	/** Returns the default loader for the given type
276 	 * @param type The type of the loader to get
277 	 * @return The loader capable of loading the type, or null if none exists */
getLoader(final Class<T> type)278 	public <T> AssetLoader getLoader (final Class<T> type) {
279 		return getLoader(type, null);
280 	}
281 
282 	/** Returns the loader for the given type and the specified filename. If no loader exists for the specific filename, the default
283 	 * loader for that type is returned.
284 	 * @param type The type of the loader to get
285 	 * @param fileName The filename of the asset to get a loader for, or null to get the default loader
286 	 * @return The loader capable of loading the type and filename, or null if none exists */
getLoader(final Class<T> type, final String fileName)287 	public <T> AssetLoader getLoader (final Class<T> type, final String fileName) {
288 		final ObjectMap<String, AssetLoader> loaders = this.loaders.get(type);
289 		if (loaders == null || loaders.size < 1) return null;
290 		if (fileName == null) return loaders.get("");
291 		AssetLoader result = null;
292 		int l = -1;
293 		for (ObjectMap.Entry<String, AssetLoader> entry : loaders.entries()) {
294 			if (entry.key.length() > l && fileName.endsWith(entry.key)) {
295 				result = entry.value;
296 				l = entry.key.length();
297 			}
298 		}
299 		return result;
300 	}
301 
302 	/** Adds the given asset to the loading queue of the AssetManager.
303 	 * @param fileName the file name (interpretation depends on {@link AssetLoader})
304 	 * @param type the type of the asset. */
load(String fileName, Class<T> type)305 	public synchronized <T> void load (String fileName, Class<T> type) {
306 		load(fileName, type, null);
307 	}
308 
309 	/** Adds the given asset to the loading queue of the AssetManager.
310 	 * @param fileName the file name (interpretation depends on {@link AssetLoader})
311 	 * @param type the type of the asset.
312 	 * @param parameter parameters for the AssetLoader. */
load(String fileName, Class<T> type, AssetLoaderParameters<T> parameter)313 	public synchronized <T> void load (String fileName, Class<T> type, AssetLoaderParameters<T> parameter) {
314 		AssetLoader loader = getLoader(type, fileName);
315 		if (loader == null) throw new GdxRuntimeException("No loader for type: " + ClassReflection.getSimpleName(type));
316 
317 		// reset stats
318 		if (loadQueue.size == 0) {
319 			loaded = 0;
320 			toLoad = 0;
321 		}
322 
323 		// check if an asset with the same name but a different type has already been added.
324 
325 		// check preload queue
326 		for (int i = 0; i < loadQueue.size; i++) {
327 			AssetDescriptor desc = loadQueue.get(i);
328 			if (desc.fileName.equals(fileName) && !desc.type.equals(type))
329 				throw new GdxRuntimeException("Asset with name '" + fileName
330 					+ "' already in preload queue, but has different type (expected: " + ClassReflection.getSimpleName(type)
331 					+ ", found: " + ClassReflection.getSimpleName(desc.type) + ")");
332 		}
333 
334 		// check task list
335 		for (int i = 0; i < tasks.size(); i++) {
336 			AssetDescriptor desc = tasks.get(i).assetDesc;
337 			if (desc.fileName.equals(fileName) && !desc.type.equals(type))
338 				throw new GdxRuntimeException("Asset with name '" + fileName
339 					+ "' already in task list, but has different type (expected: " + ClassReflection.getSimpleName(type) + ", found: "
340 					+ ClassReflection.getSimpleName(desc.type) + ")");
341 		}
342 
343 		// check loaded assets
344 		Class otherType = assetTypes.get(fileName);
345 		if (otherType != null && !otherType.equals(type))
346 			throw new GdxRuntimeException("Asset with name '" + fileName + "' already loaded, but has different type (expected: "
347 				+ ClassReflection.getSimpleName(type) + ", found: " + ClassReflection.getSimpleName(otherType) + ")");
348 
349 		toLoad++;
350 		AssetDescriptor assetDesc = new AssetDescriptor(fileName, type, parameter);
351 		loadQueue.add(assetDesc);
352 		log.debug("Queued: " + assetDesc);
353 	}
354 
355 	/** Adds the given asset to the loading queue of the AssetManager.
356 	 * @param desc the {@link AssetDescriptor} */
load(AssetDescriptor desc)357 	public synchronized void load (AssetDescriptor desc) {
358 		load(desc.fileName, desc.type, desc.params);
359 	}
360 
361 	/** Updates the AssetManager, keeping it loading any assets in the preload queue.
362 	 * @return true if all loading is finished. */
update()363 	public synchronized boolean update () {
364 		try {
365 			if (tasks.size() == 0) {
366 				// loop until we have a new task ready to be processed
367 				while (loadQueue.size != 0 && tasks.size() == 0) {
368 					nextTask();
369 				}
370 				// have we not found a task? We are done!
371 				if (tasks.size() == 0) return true;
372 			}
373 			return updateTask() && loadQueue.size == 0 && tasks.size() == 0;
374 		} catch (Throwable t) {
375 			handleTaskError(t);
376 			return loadQueue.size == 0;
377 		}
378 	}
379 
380 	/** Updates the AssetManager continuously for the specified number of milliseconds, yielding the CPU to the loading thread
381 	 * between updates. This may block for less time if all loading tasks are complete. This may block for more time if the portion
382 	 * of a single task that happens in the GL thread takes a long time.
383 	 * @return true if all loading is finished. */
update(int millis)384 	public boolean update (int millis) {
385 		long endTime = TimeUtils.millis() + millis;
386 		while (true) {
387 			boolean done = update();
388 			if (done || TimeUtils.millis() > endTime) return done;
389 			ThreadUtils.yield();
390 		}
391 	}
392 
393 	/** Blocks until all assets are loaded. */
finishLoading()394 	public void finishLoading () {
395 		log.debug("Waiting for loading to complete...");
396 		while (!update())
397 			ThreadUtils.yield();
398 		log.debug("Loading complete.");
399 	}
400 
401 	/** Blocks until the specified asset is loaded.
402 	 * @param fileName the file name (interpretation depends on {@link AssetLoader}) */
finishLoadingAsset(String fileName)403 	public void finishLoadingAsset (String fileName) {
404 		log.debug("Waiting for asset to be loaded: " + fileName);
405 		while (!isLoaded(fileName)) {
406 			update();
407 			ThreadUtils.yield();
408 		}
409 		log.debug("Asset loaded: " + fileName);
410 	}
411 
injectDependencies(String parentAssetFilename, Array<AssetDescriptor> dependendAssetDescs)412 	synchronized void injectDependencies (String parentAssetFilename, Array<AssetDescriptor> dependendAssetDescs) {
413 		ObjectSet<String> injected = this.injected;
414 		for (AssetDescriptor desc : dependendAssetDescs) {
415 			if (injected.contains(desc.fileName)) continue; // Ignore subsequent dependencies if there are duplicates.
416 			injected.add(desc.fileName);
417 			injectDependency(parentAssetFilename, desc);
418 		}
419 		injected.clear();
420 	}
421 
injectDependency(String parentAssetFilename, AssetDescriptor dependendAssetDesc)422 	private synchronized void injectDependency (String parentAssetFilename, AssetDescriptor dependendAssetDesc) {
423 		// add the asset as a dependency of the parent asset
424 		Array<String> dependencies = assetDependencies.get(parentAssetFilename);
425 		if (dependencies == null) {
426 			dependencies = new Array();
427 			assetDependencies.put(parentAssetFilename, dependencies);
428 		}
429 		dependencies.add(dependendAssetDesc.fileName);
430 
431 		// if the asset is already loaded, increase its reference count.
432 		if (isLoaded(dependendAssetDesc.fileName)) {
433 			log.debug("Dependency already loaded: " + dependendAssetDesc);
434 			Class type = assetTypes.get(dependendAssetDesc.fileName);
435 			RefCountedContainer assetRef = assets.get(type).get(dependendAssetDesc.fileName);
436 			assetRef.incRefCount();
437 			incrementRefCountedDependencies(dependendAssetDesc.fileName);
438 		}
439 		// else add a new task for the asset.
440 		else {
441 			log.info("Loading dependency: " + dependendAssetDesc);
442 			addTask(dependendAssetDesc);
443 		}
444 	}
445 
446 	/** Removes a task from the loadQueue and adds it to the task stack. If the asset is already loaded (which can happen if it was
447 	 * a dependency of a previously loaded asset) its reference count will be increased. */
nextTask()448 	private void nextTask () {
449 		AssetDescriptor assetDesc = loadQueue.removeIndex(0);
450 
451 		// if the asset not meant to be reloaded and is already loaded, increase its reference count
452 		if (isLoaded(assetDesc.fileName)) {
453 			log.debug("Already loaded: " + assetDesc);
454 			Class type = assetTypes.get(assetDesc.fileName);
455 			RefCountedContainer assetRef = assets.get(type).get(assetDesc.fileName);
456 			assetRef.incRefCount();
457 			incrementRefCountedDependencies(assetDesc.fileName);
458 			if (assetDesc.params != null && assetDesc.params.loadedCallback != null) {
459 				assetDesc.params.loadedCallback.finishedLoading(this, assetDesc.fileName, assetDesc.type);
460 			}
461 			loaded++;
462 		} else {
463 			// else add a new task for the asset.
464 			log.info("Loading: " + assetDesc);
465 			addTask(assetDesc);
466 		}
467 	}
468 
469 	/** Adds a {@link AssetLoadingTask} to the task stack for the given asset.
470 	 * @param assetDesc */
addTask(AssetDescriptor assetDesc)471 	private void addTask (AssetDescriptor assetDesc) {
472 		AssetLoader loader = getLoader(assetDesc.type, assetDesc.fileName);
473 		if (loader == null) throw new GdxRuntimeException("No loader for type: " + ClassReflection.getSimpleName(assetDesc.type));
474 		tasks.push(new AssetLoadingTask(this, assetDesc, loader, executor));
475 	}
476 
477 	/** Adds an asset to this AssetManager */
addAsset(final String fileName, Class<T> type, T asset)478 	protected <T> void addAsset (final String fileName, Class<T> type, T asset) {
479 		// add the asset to the filename lookup
480 		assetTypes.put(fileName, type);
481 
482 		// add the asset to the type lookup
483 		ObjectMap<String, RefCountedContainer> typeToAssets = assets.get(type);
484 		if (typeToAssets == null) {
485 			typeToAssets = new ObjectMap<String, RefCountedContainer>();
486 			assets.put(type, typeToAssets);
487 		}
488 		typeToAssets.put(fileName, new RefCountedContainer(asset));
489 	}
490 
491 	/** Updates the current task on the top of the task stack.
492 	 * @return true if the asset is loaded or the task was cancelled. */
updateTask()493 	private boolean updateTask () {
494 		AssetLoadingTask task = tasks.peek();
495 
496 		boolean complete = true;
497 		try {
498 			complete = task.cancel || task.update();
499 		} catch (RuntimeException ex) {
500 			task.cancel = true;
501 			taskFailed(task.assetDesc, ex);
502 		}
503 
504 		// if the task has been cancelled or has finished loading
505 		if (complete) {
506 			// increase the number of loaded assets and pop the task from the stack
507 			if (tasks.size() == 1) loaded++;
508 			tasks.pop();
509 
510 			if (task.cancel) return true;
511 
512 			addAsset(task.assetDesc.fileName, task.assetDesc.type, task.getAsset());
513 
514 			// otherwise, if a listener was found in the parameter invoke it
515 			if (task.assetDesc.params != null && task.assetDesc.params.loadedCallback != null) {
516 				task.assetDesc.params.loadedCallback.finishedLoading(this, task.assetDesc.fileName, task.assetDesc.type);
517 			}
518 
519 			long endTime = TimeUtils.nanoTime();
520 			log.debug("Loaded: " + (endTime - task.startTime) / 1000000f + "ms " + task.assetDesc);
521 
522 			return true;
523 		}
524 		return false;
525 	}
526 
527 	/** Called when a task throws an exception during loading. The default implementation rethrows the exception. A subclass may
528 	 * supress the default implementation when loading assets where loading failure is recoverable. */
taskFailed(AssetDescriptor assetDesc, RuntimeException ex)529 	protected void taskFailed (AssetDescriptor assetDesc, RuntimeException ex) {
530 		throw ex;
531 	}
532 
incrementRefCountedDependencies(String parent)533 	private void incrementRefCountedDependencies (String parent) {
534 		Array<String> dependencies = assetDependencies.get(parent);
535 		if (dependencies == null) return;
536 
537 		for (String dependency : dependencies) {
538 			Class type = assetTypes.get(dependency);
539 			RefCountedContainer assetRef = assets.get(type).get(dependency);
540 			assetRef.incRefCount();
541 			incrementRefCountedDependencies(dependency);
542 		}
543 	}
544 
545 	/** Handles a runtime/loading error in {@link #update()} by optionally invoking the {@link AssetErrorListener}.
546 	 * @param t */
handleTaskError(Throwable t)547 	private void handleTaskError (Throwable t) {
548 		log.error("Error loading asset.", t);
549 
550 		if (tasks.isEmpty()) throw new GdxRuntimeException(t);
551 
552 		// pop the faulty task from the stack
553 		AssetLoadingTask task = tasks.pop();
554 		AssetDescriptor assetDesc = task.assetDesc;
555 
556 		// remove all dependencies
557 		if (task.dependenciesLoaded && task.dependencies != null) {
558 			for (AssetDescriptor desc : task.dependencies) {
559 				unload(desc.fileName);
560 			}
561 		}
562 
563 		// clear the rest of the stack
564 		tasks.clear();
565 
566 		// inform the listener that something bad happened
567 		if (listener != null) {
568 			listener.error(assetDesc, t);
569 		} else {
570 			throw new GdxRuntimeException(t);
571 		}
572 	}
573 
574 	/** Sets a new {@link AssetLoader} for the given type.
575 	 * @param type the type of the asset
576 	 * @param loader the loader */
setLoader(Class<T> type, AssetLoader<T, P> loader)577 	public synchronized <T, P extends AssetLoaderParameters<T>> void setLoader (Class<T> type, AssetLoader<T, P> loader) {
578 		setLoader(type, null, loader);
579 	}
580 
581 	/** Sets a new {@link AssetLoader} for the given type.
582 	 * @param type the type of the asset
583 	 * @param suffix the suffix the filename must have for this loader to be used or null to specify the default loader.
584 	 * @param loader the loader */
setLoader(Class<T> type, String suffix, AssetLoader<T, P> loader)585 	public synchronized <T, P extends AssetLoaderParameters<T>> void setLoader (Class<T> type, String suffix,
586 		AssetLoader<T, P> loader) {
587 		if (type == null) throw new IllegalArgumentException("type cannot be null.");
588 		if (loader == null) throw new IllegalArgumentException("loader cannot be null.");
589 		log.debug("Loader set: " + ClassReflection.getSimpleName(type) + " -> " + ClassReflection.getSimpleName(loader.getClass()));
590 		ObjectMap<String, AssetLoader> loaders = this.loaders.get(type);
591 		if (loaders == null) this.loaders.put(type, loaders = new ObjectMap<String, AssetLoader>());
592 		loaders.put(suffix == null ? "" : suffix, loader);
593 	}
594 
595 	/** @return the number of loaded assets */
getLoadedAssets()596 	public synchronized int getLoadedAssets () {
597 		return assetTypes.size;
598 	}
599 
600 	/** @return the number of currently queued assets */
getQueuedAssets()601 	public synchronized int getQueuedAssets () {
602 		return loadQueue.size + tasks.size();
603 	}
604 
605 	/** @return the progress in percent of completion. */
getProgress()606 	public synchronized float getProgress () {
607 		if (toLoad == 0) return 1;
608 		return Math.min(1, loaded / (float)toLoad);
609 	}
610 
611 	/** Sets an {@link AssetErrorListener} to be invoked in case loading an asset failed.
612 	 * @param listener the listener or null */
setErrorListener(AssetErrorListener listener)613 	public synchronized void setErrorListener (AssetErrorListener listener) {
614 		this.listener = listener;
615 	}
616 
617 	/** Disposes all assets in the manager and stops all asynchronous loading. */
618 	@Override
dispose()619 	public synchronized void dispose () {
620 		log.debug("Disposing.");
621 		clear();
622 		executor.dispose();
623 	}
624 
625 	/** Clears and disposes all assets and the preloading queue. */
clear()626 	public synchronized void clear () {
627 		loadQueue.clear();
628 		while (!update())
629 			;
630 
631 		ObjectIntMap<String> dependencyCount = new ObjectIntMap<String>();
632 		while (assetTypes.size > 0) {
633 			// for each asset, figure out how often it was referenced
634 			dependencyCount.clear();
635 			Array<String> assets = assetTypes.keys().toArray();
636 			for (String asset : assets) {
637 				dependencyCount.put(asset, 0);
638 			}
639 
640 			for (String asset : assets) {
641 				Array<String> dependencies = assetDependencies.get(asset);
642 				if (dependencies == null) continue;
643 				for (String dependency : dependencies) {
644 					int count = dependencyCount.get(dependency, 0);
645 					count++;
646 					dependencyCount.put(dependency, count);
647 				}
648 			}
649 
650 			// only dispose of assets that are root assets (not referenced)
651 			for (String asset : assets) {
652 				if (dependencyCount.get(asset, 0) == 0) {
653 					unload(asset);
654 				}
655 			}
656 		}
657 
658 		this.assets.clear();
659 		this.assetTypes.clear();
660 		this.assetDependencies.clear();
661 		this.loaded = 0;
662 		this.toLoad = 0;
663 		this.loadQueue.clear();
664 		this.tasks.clear();
665 	}
666 
667 	/** @return the {@link Logger} used by the {@link AssetManager} */
getLogger()668 	public Logger getLogger () {
669 		return log;
670 	}
671 
setLogger(Logger logger)672 	public void setLogger (Logger logger) {
673 		log = logger;
674 	}
675 
676 	/** Returns the reference count of an asset.
677 	 * @param fileName */
getReferenceCount(String fileName)678 	public synchronized int getReferenceCount (String fileName) {
679 		Class type = assetTypes.get(fileName);
680 		if (type == null) throw new GdxRuntimeException("Asset not loaded: " + fileName);
681 		return assets.get(type).get(fileName).getRefCount();
682 	}
683 
684 	/** Sets the reference count of an asset.
685 	 * @param fileName */
setReferenceCount(String fileName, int refCount)686 	public synchronized void setReferenceCount (String fileName, int refCount) {
687 		Class type = assetTypes.get(fileName);
688 		if (type == null) throw new GdxRuntimeException("Asset not loaded: " + fileName);
689 		assets.get(type).get(fileName).setRefCount(refCount);
690 	}
691 
692 	/** @return a string containing ref count and dependency information for all assets. */
getDiagnostics()693 	public synchronized String getDiagnostics () {
694 		StringBuffer buffer = new StringBuffer();
695 		for (String fileName : assetTypes.keys()) {
696 			buffer.append(fileName);
697 			buffer.append(", ");
698 
699 			Class type = assetTypes.get(fileName);
700 			RefCountedContainer assetRef = assets.get(type).get(fileName);
701 			Array<String> dependencies = assetDependencies.get(fileName);
702 
703 			buffer.append(ClassReflection.getSimpleName(type));
704 
705 			buffer.append(", refs: ");
706 			buffer.append(assetRef.getRefCount());
707 
708 			if (dependencies != null) {
709 				buffer.append(", deps: [");
710 				for (String dep : dependencies) {
711 					buffer.append(dep);
712 					buffer.append(",");
713 				}
714 				buffer.append("]");
715 			}
716 			buffer.append("\n");
717 		}
718 		return buffer.toString();
719 	}
720 
721 	/** @return the file names of all loaded assets. */
getAssetNames()722 	public synchronized Array<String> getAssetNames () {
723 		return assetTypes.keys().toArray();
724 	}
725 
726 	/** @return the dependencies of an asset or null if the asset has no dependencies. */
getDependencies(String fileName)727 	public synchronized Array<String> getDependencies (String fileName) {
728 		return assetDependencies.get(fileName);
729 	}
730 
731 	/** @return the type of a loaded asset. */
getAssetType(String fileName)732 	public synchronized Class getAssetType (String fileName) {
733 		return assetTypes.get(fileName);
734 	}
735 
736 }
737