Unity Image Loader

repository·main·Indexed 42 days ago

https://github.com/ivanmurzak/unity-imageloader

An asynchronous image loading library for Unity version 7.0.4 that supports Web and Local paths. It features a dual-layer Memory and Disk cache system, a fluent API for automatically assigning images to components like Image, RawImage, Material, and SpriteRenderer, and a reference-counting system via Reference<T> to manage Texture2D memory.

Tokens
4.5K
Snippets
13
Records
15
Agent score
3%

What's inside Unity Image Loader

  1. Understand the `Future<Reference<T>>` pattern

    main

    The Future<Reference<T>> class is the core abstraction for managing asynchronous resource loading (like Texture2D or Sprite). It combines IFuture<T> (the asynchronous operation) with Reference<T> (the resource management) to provide a unified lifecycle.

    Key benefits include:

    • Asynchronous Loading: Loads resources without blocking the Unity main thread.
    • Lifecycle Management: Automatically releases resources to prevent memory leaks.
    • Event Handling: Allows registering callbacks for success, failure, and cancellation.
    • Component Binding: Automatically disposes of resources when the associated UnityEngine.Component is destroyed, ensuring memory is cleaned up when a GameObject is removed.
  2. Understand the `IFuture<T>` loading lifecycle

    main

    When you call ImageLoader.LoadSprite or ImageLoader.LoadTexture, it returns an IFuture<T> instance. This object represents the ongoing loading process and allows you to chain callbacks for various lifecycle stages.

    Lifecycle stages include:

    • Loading Cache/Source: LoadedFromMemoryCache, LoadingFromDiskCache, LoadedFromDiskCache, LoadingFromSource, LoadedFromSource.
    • Success: Loaded (on successful load), Consume (to apply the result to a component), and Then (to execute logic after success).
    • Negative: Canceled (if the process was stopped) and Failed (if an error occurred).
    • End of Lifecycle: Completed (triggers when the process reaches a final state: loaded, failed, or canceled).

    To avoid compilation warnings when not using await, use .Forget() at the end of the chain.

    ImageLoader.LoadSprite(imageURL)
        .LoadedFromMemoryCache(sprite => Debug.Log("Loaded from memory cache"))
        .LoadingFromDiskCache (()     => Debug.Log("Loading from disk cache"))
        .LoadedFromDiskCache  (sprite => Debug.Log("Loaded from disk cache"))
        .LoadingFromSource    (()     => Debug.Log("Loading from source"))
        .LoadedFromSource     (sprite => Debug.Log("Loaded from source"))
        .Loaded(sprite => Debug.Log("Loaded"))
        .Consume(sprite => Debug.Log("Consumed"))
        .Canceled(() => Debug.Log("Canceled"))
        .Failed(exception => Debug.LogException(exception))
        .Completed(isLoaded => Debug.Log($"Completed, isLoaded={isLoaded}"))
        .Forget();
  3. Manage Texture2D memory with Reference<T>

    main

    Because Texture2D objects consume significant memory, ImageLoader provides a reference-counting system to prevent OutOfMemory crashes. Instead of loading a raw Sprite, use LoadSpriteRef to obtain a Reference<Sprite>.

    How it works

    • Each Reference<T> instance increments a reference counter for the underlying texture.
    • When the last reference is disposed (via .Dispose() or Garbage Collection), the texture memory is released using UnityEngine.Object.DestroyImmediate(texture).
    • Warning: Releasing a texture while a Unity component is still using it can cause native or Editor crashes.

    Automatic Lifecycle Management

    To avoid manual disposal, you can bind the reference lifecycle to a Unity Component:

    • Consume(component): Automatically disposes the reference when the target Component is destroyed.
    • DisposeOnDestroy(component): Disposes the reference when the component's GameObject is destroyed.
    • DisposeOnDisable(component): Disposes the reference when the component is disabled.
    • DisposeOnEnable(component): Disposes the reference when the component is enabled.
    // Automatic binding to a component's lifecycle
    ImageLoader.LoadSpriteRef(imageURL)
        .Consume(imageComponent) 
        .Forget();
    
    // Manual lifecycle management
    var reference = await ImageLoader.LoadSpriteRef(imageURL);
    try {
        var sprite = reference.Value;
        // use sprite
    } finally {
        reference.Dispose();
    }
  4. Load and apply Sprites or Textures

    main

    You can load images and either await the result to get the asset or use .Consume() to automatically apply it to a Unity component.

    Loading Sprites into an Image component:

    • Use await ImageLoader.LoadSprite(url) to get the Sprite object.
    • Use .Consume(image) to set the sprite directly to an Image component.
    • Use .Consume(image1, image2) to set the same sprite to multiple Image components.

    Loading Textures into a Material:

    • Use await ImageLoader.LoadTexture(url) to get the Texture2D object.
    • Use .Consume(material) to set the texture directly to a Material.
    // Load Sprite and set to Image
    image.sprite = await ImageLoader.LoadSprite(imageURL);
    await ImageLoader.LoadSprite(imageURL).Consume(image);
    
    // Load Texture and set to Material
    material.mainTexture = await ImageLoader.LoadTexture(imageURL);
    await ImageLoader.LoadTexture(imageURL).Consume(material);
    
    // Load Sprite and set to multiple Images
    ImageLoader.LoadSprite(imageURL).Consume(image1, image2).Forget();
  5. Handle loading errors and placeholders

    main

    To improve user experience, you can set placeholder images that appear while loading or if a load fails. You can also use .Failed() to handle exceptions.

    Placeholders:

    • .SetPlaceholder(placeholder): Sets a placeholder for all loading conditions.
    • .SetPlaceholder(placeholder, trigger): Sets a placeholder for specific triggers like PlaceholderTrigger.LoadingFromSource or PlaceholderTrigger.FailedToLoad.

    Error Handling:

    • Use .Failed(exception => ...) to catch and handle errors.
    • Use .Then(...) and .Failed(...) together to perform conditional logic (e.g., activating/deactivating a GameObject based on success).
    // Using placeholders
    ImageLoader.LoadSprite(imageURL)
        .SetPlaceholder(placeholderAny)
        .SetPlaceholder(placeholderFailedToLoad, PlaceholderTrigger.FailedToLoad)
        .Consume(image)
        .Forget();
    
    // Error handling with logic
    ImageLoader.LoadSprite(imageURL)
        .Consume(image)
        .Then(sprite => image.gameObject.SetActive(true))
        .Failed(exception => image.gameObject.SetActive(false))
        .Forget();
  6. Install Unity Image Loader

    main

    You can install the package using one of the following three methods:

    Option 1: Installer

    1. Download the latest ImageLoader-Installer.unitypackage.
    2. Import the package into your Unity project by double-clicking the file or using Assets > Import Package > Custom Package in the Unity Editor.

    Option 2: OpenUPM-CLI

    If you use OpenUPM, run the following command in your Unity project folder:

    openupm add extensions.unity.imageloader

    Option 3: Asset Store

    You can find the package on the Unity Asset Store.

  7. Cancel image loading requests

    main

    Canceling requests saves network traffic, CPU, and RAM, especially when the consumer (like a UI element) is destroyed. You can cancel requests in several ways:

    1. Via MonoBehaviour lifecycle: Chain .CancelOnEnable(this), .CancelOnDisable(this), or .CancelOnDestroy(this) to automatically cancel the request when the component's lifecycle events trigger.

    2. Explicitly: Store the IFuture<T> returned by the loader and call .Cancel() on it.

    3. Via CancellationToken:

    • Pass a CancellationToken directly into LoadSprite(url, cancellationToken: token).
    • Or register an existing token using .Register(token) on the IFuture<T>.

    4. Via using block: Wrap the IFuture<T> in a using block; it will be canceled and disposed when the block exits.

    // Cancel based on MonoBehaviour lifecycle
    ImageLoader.LoadSprite(imageURL)
        .Consume(image)
        .CancelOnDestroy(this)
        .Forget();
    
    // Explicit cancellation
    var future = ImageLoader.LoadSprite(imageURL).Consume(image);
    future.Cancel();
    
    // Using CancellationToken
    var cts = new CancellationTokenSource();
    ImageLoader.LoadSprite(imageURL, cancellationToken: cts.Token)
        .Consume(image)
        .Forget();
    cts.Cancel();
  8. Load and set images using ImageLoader

    main

    The ImageLoader provides several ways to load images (from Web or Local paths) and apply them to Unity components. It supports both Sprite and Texture2D loading.

    Basic Async/Await usage

    To wait for the image to load before proceeding:

    image.sprite = await ImageLoader.LoadSprite(imageURL);

    Using the .Consume() pattern

    The .Consume() method allows you to load an image and automatically assign it to a component without manually handling the assignment logic. This is useful for fire-and-forget or callback-based workflows.

    Don't wait (Fire and Forget):

    ImageLoader.LoadSprite(imageURL).Consume(image).Forget();

    Wait for completion after setting via callback:

    await ImageLoader.LoadSprite(imageURL).Consume(image);
    // Wait for image get loaded then set:
    image.sprite = await ImageLoader.LoadSprite(imageURL);
    
    // Don't wait, use callback to set loaded image later:
    ImageLoader.LoadSprite(imageURL).Consume(image).Forget();
    
    // Use callback to set image and still wait for the completion:
    await ImageLoader.LoadSprite(imageURL).Consume(image);
  9. Initialize ImageLoader

    main

    Before using the ImageLoader API, you must call ImageLoader.Init() exactly once from the main thread at the start of your project. This initializes the static properties on the correct thread. Once initialized, you can call ImageLoader methods from any thread at any time.

    ImageLoader.Init(); // call once from the main thread
  10. Set loading timeouts

    main

    You can prevent requests from hanging indefinitely by setting timeouts. This triggers the IFuture<T> cancellation.

    • Global Timeout: Set via ImageLoader.settings.timeout using a TimeSpan.
    • Request-specific Timeout: Use the .Timeout(TimeSpan) method on an IFuture<T> instance.
    // Global timeout
    ImageLoader.settings.timeout = TimeSpan.FromSeconds(30);
    
    // Specific request timeout
    ImageLoader.LoadSprite(imageURL)
        .Consume(image)
        .Timeout(TimeSpan.FromSeconds(10))
        .Forget();
  11. Configure the Cache system

    main

    The cache system uses two layers: Memory cache and Disk cache. Both are enabled by default. Note that WebGL does not support Disk cache. You can enable/disable layers globally or override settings for specific loading tasks.

    Global Configuration

    Use ImageLoader.settings to manage global behavior:

    • ImageLoader.settings.useMemoryCache: Boolean (default true).
    • ImageLoader.settings.useDiskCache: Boolean (default true).
    • ImageLoader.settings.diskSaveLocation: String. The default is Application.persistentDataPath + "/ImageLoader".

    Task-Specific Overrides

    You can override global settings for a single request using the fluent API on the returned IFuture object.

    // Global setup
    ImageLoader.settings.useMemoryCache = true;
    ImageLoader.settings.useDiskCache = true;
    ImageLoader.settings.diskSaveLocation = Application.persistentDataPath + "/myCustomFolder";
    
    // Per-task override
    ImageLoader.LoadSprite(url)
        .SetUseDiskCache(false)
        .SetUseMemoryCache(true);
  12. Manually manage and check the cache

    main

    You can manually interact with the cache to save, retrieve, check existence, or clear specific items.

    Cache Operations

    • Check existence: Use CacheContains(url) for both layers, MemoryCacheContains(url), or DiskCacheContains(url).
    • Manual Read/Write: Use SaveToMemoryCache(url, sprite) to store an object, or LoadSpriteFromMemoryCache(url) to retrieve one.
    • Clearing Cache:
      • ClearCacheAll(): Clears both Memory and Disk caches for all images.
      • ClearCache(url): Clears both layers for a specific URL.
      • ClearMemoryCacheAll() / ClearDiskCacheAll(): Clears all items in a specific layer.
      • ClearMemoryCache(url) / ClearDiskCache(url): Clears a specific URL from a specific layer.
    // Check existence
    if (ImageLoader.CacheContains(url)) { /* ... */ }
    
    // Manual write/read
    ImageLoader.SaveToMemoryCache(url, sprite);
    var sprite = ImageLoader.LoadSpriteFromMemoryCache(url);
    
    // Clear specific image from all cache
    ImageLoader.ClearCache(url);