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