Skip to content

Commit c6c8ccb

Browse files
committed
Enhance IFuture interface to implement IDisposable; add DisposeOn methods for reference management and update sample scripts for improved usage examples
1 parent 30922f2 commit c6c8ccb

File tree

10 files changed

+202
-26
lines changed

10 files changed

+202
-26
lines changed

Assets/_PackageRoot/Runtime/Future/CancelComponent/ComponentCancelOn.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,15 @@ internal void Register<T>(IFuture<T> future)
2222
}
2323
onTrigger += future.Cancel;
2424
}
25+
26+
internal void Register<T>(Reference<T> future)
27+
{
28+
if (isTriggered)
29+
{
30+
future.Dispose();
31+
return;
32+
}
33+
onTrigger += future.Dispose;
34+
}
2535
}
2636
}

Assets/_PackageRoot/Runtime/Future/Future.WebRequest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
namespace Extensions.Unity.ImageLoader
66
{
7-
public partial class Future<T> : IFuture, IDisposable
7+
public partial class Future<T>
88
{
99
internal Future<T> SetWebRequest(UnityWebRequest webRequest)
1010
{

Assets/_PackageRoot/Runtime/Future/Future.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ internal static class FutureMetadata
1010
{
1111
public static volatile uint idCounter = 0;
1212
}
13-
public partial class Future<T> : IFuture, IFuture<T>, IFutureInternal<T>, IDisposable
13+
public partial class Future<T> : IFuture<T>, IFuture, IFutureInternal<T>, IDisposable
1414
{
1515
public string Url { get; }
1616

Assets/_PackageRoot/Runtime/Future/IFuture.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
namespace Extensions.Unity.ImageLoader
99
{
10-
public interface IFuture
10+
public interface IFuture : IDisposable
1111
{
1212
uint Id { get; }
1313
string Url { get; }

Assets/_PackageRoot/Runtime/ImageLoader.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public static Task ClearCache(string url)
9595
/// <param name="texture">Texture for creation Sprite</param>
9696
/// <param name="pixelDensity">Pixel density of the Sprite</param>
9797
/// <returns>Returns sprite</returns>
98-
public static Sprite ToSprite(Texture2D texture, float pixelDensity = 100f)
98+
public static Sprite ToSprite(this Texture2D texture, float pixelDensity = 100f)
9999
=> Sprite.Create(texture, new Rect(0.0f, 0.0f, texture.width, texture.height), new Vector2(0.5f, 0.5f), pixelDensity);
100100

101101
/// <summary>
@@ -105,7 +105,14 @@ public static Sprite ToSprite(Texture2D texture, float pixelDensity = 100f)
105105
/// <param name="pivot">Pivot of created Sprite</param>
106106
/// <param name="pixelDensity">Pixel density of the Sprite</param>
107107
/// <returns>Returns sprite</returns>
108-
public static Sprite ToSprite(Texture2D texture, Vector2 pivot, float pixelDensity = 100f)
108+
public static Sprite ToSprite(this Texture2D texture, Vector2 pivot, float pixelDensity = 100f)
109109
=> Sprite.Create(texture, new Rect(0.0f, 0.0f, texture.width, texture.height), pivot, pixelDensity);
110+
111+
/// <summary>
112+
/// Get reference count of the image
113+
/// </summary>
114+
/// <param name="url">URL to the image, web or local</param>
115+
/// <returns>Returns reference count</returns>
116+
public static int GetReferenceCount(string url) => Reference<Texture2D>.Counter(url);
110117
}
111118
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
using UnityEngine;
2+
3+
namespace Extensions.Unity.ImageLoader
4+
{
5+
public static partial class ReferenceEx
6+
{
7+
/// <summary>
8+
/// Subscribe on any event of GameObject, cancel the Reference at this event.
9+
/// You may need to create a class extended from ComponentCancelOn to subscribe on a custom event.
10+
/// Or use other DisposeOn function version with already implemented list of events.
11+
/// </summary>
12+
/// <param name="component">target component to subscribe on it's gameObject</param>
13+
/// <returns>Returns async Reference</returns>
14+
public static Reference<T> DisposeOn<T, K>(this Reference<T> future, Component component) where K : ComponentCancelOn
15+
{
16+
return DisposeOn<T, K>(future, component.gameObject);
17+
}
18+
19+
/// <summary>
20+
/// Subscribe on any event of GameObject, cancel the Reference at this event.
21+
/// You may need to create a class extended from ComponentCancelOn to subscribe on a custom event.
22+
/// Or use other DisposeOn function version with already implemented list of events.
23+
/// </summary>
24+
/// <param name="gameObject">target component to subscribe on it</param>
25+
/// <returns>Returns async Reference</returns>
26+
public static Reference<T> DisposeOn<T, K>(this Reference<T> future, GameObject gameObject) where K : ComponentCancelOn
27+
{
28+
var cancellation = gameObject.GetComponent<K>() ?? gameObject.AddComponent<K>();
29+
cancellation.Register(future);
30+
return future;
31+
}
32+
33+
34+
/// <summary>
35+
/// Subscribe on OnDestroy event of GameObject, cancel the Reference at this event.
36+
/// </summary>
37+
/// <param name="component">target component to subscribe on it's gameObject</param>
38+
/// <returns>Returns async Reference</returns>
39+
public static Reference<T> DisposeOnDestroy<T>(this Reference<T> reference, Component component) => DisposeOn<T, ComponentCancelOnDestroy>(reference, component);
40+
41+
/// <summary>
42+
/// Subscribe on OnDestroy event of GameObject, cancel the Reference at this event.
43+
/// </summary>
44+
/// <param name="gameObject">target component to subscribe on it</param>
45+
/// <returns>Returns async Reference</returns>
46+
public static Reference<T> DisposeOnDestroy<T>(this Reference<T> reference, GameObject gameObject) => DisposeOn<T, ComponentCancelOnDestroy>(reference, gameObject);
47+
48+
49+
/// <summary>
50+
/// Subscribe on OnDisable event of GameObject, cancel the Reference at this event.
51+
/// </summary>
52+
/// <param name="component">target component to subscribe on it's gameObject</param>
53+
/// <returns>Returns async Reference</returns>
54+
public static Reference<T> DisposeOnDisable<T>(this Reference<T> reference, Component component) => DisposeOn<T, ComponentCancelOnDisable>(reference, component);
55+
56+
/// <summary>
57+
/// Subscribe on OnDisable event of GameObject, cancel the Reference at this event.
58+
/// </summary>
59+
/// <param name="gameObject">target component to subscribe on it</param>
60+
/// <returns>Returns async Reference</returns>
61+
public static Reference<T> DisposeOnDisable<T>(this Reference<T> reference, GameObject gameObject) => DisposeOn<T, ComponentCancelOnDisable>(reference, gameObject);
62+
63+
64+
/// <summary>
65+
/// Subscribe on OnEnable event of GameObject, cancel the Reference at this event.
66+
/// </summary>
67+
/// <param name="component">target component to subscribe on it's gameObject</param>
68+
/// <returns>Returns async Reference</returns>
69+
public static Reference<T> DisposeOnEnable<T>(this Reference<T> reference, Component component) => DisposeOn<T, ComponentCancelOnEnable>(reference, component);
70+
71+
/// <summary>
72+
/// Subscribe on OnEnable event of GameObject, cancel the Reference at this event.
73+
/// </summary>
74+
/// <param name="gameObject">target component to subscribe on it</param>
75+
/// <returns>Returns async Reference</returns>
76+
public static Reference<T> DisposeOnEnable<T>(this Reference<T> reference, GameObject gameObject) => DisposeOn<T, ComponentCancelOnEnable>(reference, gameObject);
77+
}
78+
}

Assets/_PackageRoot/Runtime/Reference/Reference.DisposeOn.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Assets/_PackageRoot/Samples/SampleCache.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ static void ChangeDiskCacheFolder()
1010
{
1111
ImageLoader.settings.diskSaveLocation = Application.persistentDataPath + "/myCustomFolder";
1212
}
13+
static void SetupCacheGlobally()
14+
{
15+
ImageLoader.settings.useMemoryCache = true;
16+
ImageLoader.settings.useDiskCache = true;
17+
}
1318
static void OverrideCache()
1419
{
1520
// Override Memory cache for specific image
@@ -18,6 +23,12 @@ static void OverrideCache()
1823
// Take from Memory cache for specific image if exists
1924
ImageLoader.LoadSpriteFromMemoryCache(url);
2025
}
26+
static void OverrideCacheForSingleTask()
27+
{
28+
ImageLoader.LoadSprite(url)
29+
.SetUseDiskCache(false)
30+
.SetUseMemoryCache(true);
31+
}
2132
static void DoesCacheContainImage()
2233
{
2334
// Check if any cache contains specific image
@@ -31,9 +42,12 @@ static void DoesCacheContainImage()
3142
}
3243
static void ClearImage()
3344
{
34-
// Clear memory Memory and Disk cache
45+
// Clear memory Memory and Disk cache for all images
3546
ImageLoader.ClearCacheAll();
3647

48+
// Clear only Memory and Disk cache for specific image
49+
ImageLoader.ClearCache(url);
50+
3751
// Clear only Memory cache for all images
3852
ImageLoader.ClearMemoryCacheAll();
3953

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using Extensions.Unity.ImageLoader;
2-
using System;
32
using UnityEngine;
43
using UnityEngine.UI;
54

@@ -10,9 +9,19 @@ public class SampleReferences : MonoBehaviour
109

1110
void Start()
1211
{
13-
ImageLoader.LoadSprite(imageURL) // load sprite
14-
.ThenSet(image) // if success set sprite into image
15-
.Timeout(TimeSpan.FromSeconds(10)) // set timeout duration 10 seconds
12+
ImageLoader.LoadSpriteRef(imageURL) // load sprite using Reference
13+
.ThenSet(image) // if success set sprite into image, also creates binding to `image`
1614
.Forget();
15+
16+
ImageLoader.LoadSpriteRef(imageURL) // load sprite using Reference
17+
.Then(reference => reference.DisposeOnDestroy(this))
18+
.Then(reference =>
19+
{
20+
var sprite = reference.Value;
21+
// use sprite
22+
})
23+
.Forget();
24+
25+
var count = ImageLoader.GetReferenceCount(imageURL); // get count of references
1726
}
1827
}

README.md

Lines changed: 63 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,16 @@ await ImageLoader.LoadSprite(imageURL).ThenSet(image);
6565
- [Cache](#cache)
6666
- [Setup Cache](#setup-cache)
6767
- [Change Disk cache folder](#change-disk-cache-folder)
68+
- [Override for a specific loading task](#override-for-a-specific-loading-task)
6869
- [Manually read / write into cache](#manually-read--write-into-cache)
6970
- [Check cache existence](#check-cache-existence)
7071
- [Clear cache](#clear-cache)
7172
- [Texture Memory Management](#texture-memory-management)
72-
- [\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_](#____________________)
73-
- [\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_](#____________________-1)
74-
- [\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_](#____________________-2)
73+
- [Manual Memory cache cleaning](#manual-memory-cache-cleaning)
74+
- [Automatic Memory cache cleaning](#automatic-memory-cache-cleaning)
75+
- [Load Reference](#load-reference)
76+
- [Dispose `Reference<T>` on `Component` destroy event](#dispose-referencet-on-component-destroy-event)
77+
- [Get references count](#get-references-count)
7578
- [Other](#other)
7679
- [Understanding `IFuture<T>`](#understanding-ifuturet)
7780
- [Understanding `Reference<T>`](#understanding-referencet)
@@ -320,6 +323,16 @@ By default it uses `Application.persistentDataPath + "/ImageLoader"`
320323
ImageLoader.settings.diskSaveLocation = Application.persistentDataPath + "/myCustomFolder";
321324
```
322325

326+
#### Override for a specific loading task
327+
328+
It overrides global `ImageLoader.settings`
329+
330+
``` C#
331+
ImageLoader.LoadSprite(url)
332+
.SetUseDiskCache(false)
333+
.SetUseMemoryCache(true);
334+
```
335+
323336
> [Full sample source code](https://github.com/IvanMurzak/Unity-ImageLoader/blob/master/Assets/_PackageRoot/Samples/SampleCache.cs)
324337
325338
### Manually read / write into cache
@@ -348,9 +361,12 @@ ImageLoader.DiskCacheContains(url);
348361
### Clear cache
349362

350363
``` C#
351-
// Clear memory Memory and Disk cache
364+
// Clear memory Memory and Disk cache for all images
352365
ImageLoader.ClearCacheAll();
353366

367+
// Clear only Memory and Disk cache for specific image
368+
ImageLoader.ClearCache(url);
369+
354370
// Clear only Memory cache for all images
355371
ImageLoader.ClearMemoryCacheAll();
356372

@@ -368,40 +384,71 @@ Memory cache could be cleared automatically if to use `Reference<T>` and the hea
368384

369385
## Texture Memory Management
370386

371-
ImageLoader can manager memory usage of loaded textures. To use it need to call `ImageLoader.LoadSpriteRef` instead of `ImageLoader.LoadSprite`. It will return `Reference<Sprite>` object which contains `Sprite` and `Url` objects. When `Reference<Sprite>` object is not needed anymore, call `Dispose` method to release memory, or just don't save the reference on it. It is `IDisposable` and it will clean itself automatically. Each new instance of `Reference<Sprite>` increments reference counter of the texture. When the last reference is disposed, the texture will be unloaded from memory. Also the all related References will be automatically disposed if you call `ImageLoader.ClearMemoryCache` or `ImageLoader.ClearCache`.
387+
Texture2D objects consume a lot of memory. Ignoring it may impact performance or even trigger `OutOfMemory` crash by operation system. To avoid it, let's dig deeper into tools the package provides. We worry less about Disk cache, because it doesn't impact game performance directly. Let's focus on the Memory cache.
388+
389+
### Manual Memory cache cleaning
390+
391+
It is simple, just executing this line of code would release memory of a single Texture2D in the case if no other `Reference` pointing on it exists. Before doing that please make sure that no Unity component is using the texture.
392+
393+
``` C#
394+
ImageLoader.ClearMemoryCache(url);
395+
```
396+
397+
Under the hood it calls `UnityEngine.Object.DestroyImmediate(texture)`.
398+
399+
> :warning: Releasing `Texture2D` from memory while any Unity's component uses it may trigger native app crash or even Unity Editor crash
400+
401+
### Automatic Memory cache cleaning
402+
403+
ImageLoader can manager memory releasing of loaded textures. To use it need to call `ImageLoader.LoadSpriteRef` instead of `ImageLoader.LoadSprite`. It returns `Reference<Sprite>` object which contains `Sprite` and `Url`. When `Reference<Sprite>` object is not needed anymore, call `reference.Dispose()` method to release memory, or just don't save the reference on it. It is `IDisposable` and it will be disposed by Garbage Collector. Each new instance of `Reference<Sprite>` increments reference counter of the texture. When the last reference is disposed, the texture memory releases. Also, if any reference is alive, calling `ImageLoader.ClearMemoryCache` or `ImageLoader.ClearCache` would have zero effect for only referenced textures. It prints warning messages about it.
372404

373405
``` C#
374406
// Load sprite image and get reference to it
375-
await ImageLoader.LoadSpriteRef(imageURL);
407+
var reference = await ImageLoader.LoadSpriteRef(imageURL);
376408

377409
// Take from Memory cache reference for specific image if exists
378-
ImageLoader.LoadSpriteRefFromMemoryCache(url);
410+
var reference = ImageLoader.LoadSpriteRefFromMemoryCache(url);
411+
412+
// Dispose `reference` when you don't need the texture anymore
413+
reference.Dispose();
414+
415+
// You may also nullify the reference to let Garbage Collector at some point to Dispose it for you
416+
reference = null;
379417
```
380418

381419
> :warning: Releasing `Texture2D` from memory while any Unity's component uses it may trigger native app crash or even Unity Editor crash. Please pay enough attention to manage `Reference<T>` instances in a proper way. Or do not use them.
382420
383-
### ____________________
421+
#### Load Reference
384422

385423
> [Full sample source code](https://github.com/IvanMurzak/Unity-ImageLoader/blob/master/Assets/_PackageRoot/Samples/SampleReferences.cs)
386424
425+
`Reference<T>.ThenSet` has a unique feature to attach the reference to the target consumer if consumer is `UnityEngine.Component`. The reference would be disposed as only the consumer gets destroyed.
426+
387427
``` C#
388-
____________________________
428+
ImageLoader.LoadSpriteRef(imageURL) // load sprite using Reference
429+
.ThenSet(image) // if success set sprite into image, also creates binding to `image`
430+
.Forget();
389431
```
390432

391-
### ____________________
433+
#### Dispose `Reference<T>` on `Component` destroy event
392434

393-
> [Full sample source code](https://github.com/IvanMurzak/Unity-ImageLoader/blob/master/Assets/_PackageRoot/Samples/______________________.cs)
435+
It automatically dispose the reference as only `this.gameObject` gets `OnDestroy` callback.
394436

395437
``` C#
396-
____________________________
438+
ImageLoader.LoadSpriteRef(imageURL) // load sprite using Reference
439+
.Then(reference => reference.DisposeOnDestroy(this))
440+
.Then(reference =>
441+
{
442+
var sprite = reference.Value;
443+
// use sprite
444+
})
445+
.Forget();
397446
```
398447

399-
### ____________________
400-
401-
> [Full sample source code](https://github.com/IvanMurzak/Unity-ImageLoader/blob/master/Assets/_PackageRoot/Samples/______________________.cs)
448+
#### Get references count
402449

403450
``` C#
404-
____________________________
451+
var count = ImageLoader.GetReferenceCount(imageURL); // get count of references
405452
```
406453

407454
# Other

0 commit comments

Comments
 (0)