diff --git a/.github/workflows/2019.4.40f1_build.yml b/.github/workflows/2019.4.40f1_build.yml new file mode 100644 index 0000000..79c0100 --- /dev/null +++ b/.github/workflows/2019.4.40f1_build.yml @@ -0,0 +1,18 @@ +name: 2019.4.40f1-Build + +on: + pull_request: + branches: + - main_disabled + push: + branches: + - main_disabled + +jobs: + editor-tests: + uses: ./.github/workflows/main.yml + with: + projectPath: './' + unityVersion: '2019.4.40f1' + testMode: 'standalone' + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/2019.4.40f1_editor.yml b/.github/workflows/2019.4.40f1_editor.yml index 67b3d2b..45e1f11 100644 --- a/.github/workflows/2019.4.40f1_editor.yml +++ b/.github/workflows/2019.4.40f1_editor.yml @@ -3,10 +3,10 @@ name: 2019.4.40f1-Editor on: pull_request: branches: - - master + - main push: branches: - - master + - main jobs: editor-tests: diff --git a/.github/workflows/2019.4.40f1_player.yml b/.github/workflows/2019.4.40f1_player.yml new file mode 100644 index 0000000..d06f183 --- /dev/null +++ b/.github/workflows/2019.4.40f1_player.yml @@ -0,0 +1,18 @@ +name: 2019.4.40f1-Player + +on: + pull_request: + branches: + - main_disabled + push: + branches: + - main_disabled + +jobs: + editor-tests: + uses: ./.github/workflows/main.yml + with: + projectPath: './' + unityVersion: '2019.4.40f1' + testMode: 'playmode' + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/2020.3.40f1_build.yml b/.github/workflows/2020.3.40f1_build.yml new file mode 100644 index 0000000..91ff3b7 --- /dev/null +++ b/.github/workflows/2020.3.40f1_build.yml @@ -0,0 +1,18 @@ +name: 2020.3.40f1-Build + +on: + pull_request: + branches: + - main_disabled + push: + branches: + - main_disabled + +jobs: + editor-tests: + uses: ./.github/workflows/main.yml + with: + projectPath: './' + unityVersion: '2020.3.40f1' + testMode: 'standalone' + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/2020.3.40f1_editor.yml b/.github/workflows/2020.3.40f1_editor.yml index ecac8de..b839aa1 100644 --- a/.github/workflows/2020.3.40f1_editor.yml +++ b/.github/workflows/2020.3.40f1_editor.yml @@ -3,10 +3,10 @@ name: 2020.3.40f1-Editor on: pull_request: branches: - - master + - main push: branches: - - master + - main jobs: editor-tests: diff --git a/.github/workflows/2020.3.40f1_player.yml b/.github/workflows/2020.3.40f1_player.yml new file mode 100644 index 0000000..632f6a1 --- /dev/null +++ b/.github/workflows/2020.3.40f1_player.yml @@ -0,0 +1,18 @@ +name: 2020.3.40f1-Player + +on: + pull_request: + branches: + - main_disabled + push: + branches: + - main_disabled + +jobs: + editor-tests: + uses: ./.github/workflows/main.yml + with: + projectPath: './' + unityVersion: '2020.3.40f1' + testMode: 'playmode' + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/2021.3.45f1_build.yml b/.github/workflows/2021.3.45f1_build.yml new file mode 100644 index 0000000..0e9cb07 --- /dev/null +++ b/.github/workflows/2021.3.45f1_build.yml @@ -0,0 +1,18 @@ +name: 2021.3.45f1-Build + +on: + pull_request: + branches: + - main_disabled + push: + branches: + - main_disabled + +jobs: + editor-tests: + uses: ./.github/workflows/main.yml + with: + projectPath: './' + unityVersion: '2021.3.45f1' + testMode: 'standalone' + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/2021.3.45f1_editor.yml b/.github/workflows/2021.3.45f1_editor.yml index a2894b5..411c5f8 100644 --- a/.github/workflows/2021.3.45f1_editor.yml +++ b/.github/workflows/2021.3.45f1_editor.yml @@ -3,10 +3,10 @@ name: 2021.3.45f1-Editor on: pull_request: branches: - - master + - main push: branches: - - master + - main jobs: editor-tests: diff --git a/.github/workflows/2021.3.45f1_player.yml b/.github/workflows/2021.3.45f1_player.yml new file mode 100644 index 0000000..a90b84d --- /dev/null +++ b/.github/workflows/2021.3.45f1_player.yml @@ -0,0 +1,18 @@ +name: 2021.3.45f1-Player + +on: + pull_request: + branches: + - main_disabled + push: + branches: + - main_disabled + +jobs: + editor-tests: + uses: ./.github/workflows/main.yml + with: + projectPath: './' + unityVersion: '2021.3.45f1' + testMode: 'playmode' + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/2022.3.57f1_build.yml b/.github/workflows/2022.3.57f1_build.yml new file mode 100644 index 0000000..26abe34 --- /dev/null +++ b/.github/workflows/2022.3.57f1_build.yml @@ -0,0 +1,18 @@ +name: 2022.3.57f1-Build + +on: + pull_request: + branches: + - main_disabled + push: + branches: + - main_disabled + +jobs: + editor-tests: + uses: ./.github/workflows/main.yml + with: + projectPath: './' + unityVersion: '2022.3.57f1' + testMode: 'standalone' + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/2022.3.57f1_editor.yml b/.github/workflows/2022.3.57f1_editor.yml index 7ad30ea..eaa76bc 100644 --- a/.github/workflows/2022.3.57f1_editor.yml +++ b/.github/workflows/2022.3.57f1_editor.yml @@ -3,10 +3,10 @@ name: 2022.3.57f1-Editor on: pull_request: branches: - - master + - main push: branches: - - master + - main jobs: editor-tests: diff --git a/.github/workflows/2022.3.57f1_player.yml b/.github/workflows/2022.3.57f1_player.yml new file mode 100644 index 0000000..ae44fc3 --- /dev/null +++ b/.github/workflows/2022.3.57f1_player.yml @@ -0,0 +1,18 @@ +name: 2022.3.57f1-Player + +on: + pull_request: + branches: + - main_disabled + push: + branches: + - main_disabled + +jobs: + editor-tests: + uses: ./.github/workflows/main.yml + with: + projectPath: './' + unityVersion: '2022.3.57f1' + testMode: 'playmode' + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/2023.1.20f1_build.yml b/.github/workflows/2023.1.20f1_build.yml new file mode 100644 index 0000000..4c85706 --- /dev/null +++ b/.github/workflows/2023.1.20f1_build.yml @@ -0,0 +1,18 @@ +name: 2023.1.20f1-Build + +on: + pull_request: + branches: + - main_disabled + push: + branches: + - main_disabled + +jobs: + editor-tests: + uses: ./.github/workflows/main.yml + with: + projectPath: './' + unityVersion: '2023.1.20f1' + testMode: 'standalone' + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/2023.1.20f1_editor.yml b/.github/workflows/2023.1.20f1_editor.yml index b51dabe..cb70fdc 100644 --- a/.github/workflows/2023.1.20f1_editor.yml +++ b/.github/workflows/2023.1.20f1_editor.yml @@ -3,10 +3,10 @@ name: 2023.1.20f1-Editor on: pull_request: branches: - - master + - main push: branches: - - master + - main jobs: editor-tests: diff --git a/.github/workflows/2023.1.20f1_player.yml b/.github/workflows/2023.1.20f1_player.yml new file mode 100644 index 0000000..e076ad5 --- /dev/null +++ b/.github/workflows/2023.1.20f1_player.yml @@ -0,0 +1,18 @@ +name: 2023.1.20f1-Player + +on: + pull_request: + branches: + - main_disabled + push: + branches: + - main_disabled + +jobs: + editor-tests: + uses: ./.github/workflows/main.yml + with: + projectPath: './' + unityVersion: '2023.1.20f1' + testMode: 'playmode' + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/2023.2.20f1_build.yml b/.github/workflows/2023.2.20f1_build.yml new file mode 100644 index 0000000..8226e6f --- /dev/null +++ b/.github/workflows/2023.2.20f1_build.yml @@ -0,0 +1,18 @@ +name: 2023.2.20f1-Build + +on: + pull_request: + branches: + - main_disabled + push: + branches: + - main_disabled + +jobs: + editor-tests: + uses: ./.github/workflows/main.yml + with: + projectPath: './' + unityVersion: '2023.2.20f1' + testMode: 'standalone' + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/2023.2.20f1_editor.yml b/.github/workflows/2023.2.20f1_editor.yml index 1b83144..729c4ed 100644 --- a/.github/workflows/2023.2.20f1_editor.yml +++ b/.github/workflows/2023.2.20f1_editor.yml @@ -3,10 +3,10 @@ name: 2023.2.20f1-Editor on: pull_request: branches: - - master + - main push: branches: - - master + - main jobs: editor-tests: diff --git a/.github/workflows/2023.2.20f1_player.yml b/.github/workflows/2023.2.20f1_player.yml new file mode 100644 index 0000000..60dad76 --- /dev/null +++ b/.github/workflows/2023.2.20f1_player.yml @@ -0,0 +1,18 @@ +name: 2023.2.20f1-Player + +on: + pull_request: + branches: + - main_disabled + push: + branches: + - main_disabled + +jobs: + editor-tests: + uses: ./.github/workflows/main.yml + with: + projectPath: './' + unityVersion: '2023.2.20f1' + testMode: 'playmode' + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/6000.0.37f1_build.yml b/.github/workflows/6000.0.37f1_build.yml new file mode 100644 index 0000000..59f9d12 --- /dev/null +++ b/.github/workflows/6000.0.37f1_build.yml @@ -0,0 +1,18 @@ +name: 6000.0.37f1-Build + +on: + pull_request: + branches: + - main_disabled + push: + branches: + - main_disabled + +jobs: + editor-tests: + uses: ./.github/workflows/main.yml + with: + projectPath: './' + unityVersion: '6000.0.37f1' + testMode: 'standalone' + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/6000.0.37f1_editor.yml b/.github/workflows/6000.0.37f1_editor.yml index b1f06c3..11994ab 100644 --- a/.github/workflows/6000.0.37f1_editor.yml +++ b/.github/workflows/6000.0.37f1_editor.yml @@ -3,10 +3,10 @@ name: 6000.0.37f1-Editor on: pull_request: branches: - - master + - main push: branches: - - master + - main jobs: editor-tests: diff --git a/.github/workflows/6000.0.37f1_player.yml b/.github/workflows/6000.0.37f1_player.yml new file mode 100644 index 0000000..3e20ef7 --- /dev/null +++ b/.github/workflows/6000.0.37f1_player.yml @@ -0,0 +1,18 @@ +name: 6000.0.37f1-Player + +on: + pull_request: + branches: + - main_disabled + push: + branches: + - main_disabled + +jobs: + editor-tests: + uses: ./.github/workflows/main.yml + with: + projectPath: './' + unityVersion: '6000.0.37f1' + testMode: 'playmode' + secrets: inherit \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 3890346..2c7bb13 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -60,6 +60,7 @@ "imageloader", "Murzak", "openupm", + "playmode", "ugui", "unitask", "WEBGL" diff --git a/Assets/_PackageRoot/Documentation~/README.md b/Assets/_PackageRoot/Documentation~/README.md index 0bf57d3..f6cb8e4 100644 --- a/Assets/_PackageRoot/Documentation~/README.md +++ b/Assets/_PackageRoot/Documentation~/README.md @@ -15,13 +15,13 @@ image.sprite = await ImageLoader.LoadSprite(imageURL); Don't wait, use callback to set loaded image later: ```csharp -ImageLoader.LoadSprite(imageURL).ThenSet(image).Forget(); +ImageLoader.LoadSprite(imageURL).Consume(image).Forget(); ``` Use callback to set image and still wait for the completion: ```csharp -await ImageLoader.LoadSprite(imageURL).ThenSet(image); +await ImageLoader.LoadSprite(imageURL).Consume(image); ``` ## Features @@ -33,10 +33,10 @@ await ImageLoader.LoadSprite(imageURL).ThenSet(image); - ✔️ Avoids loading same image multiple times simultaneously, a new load task waits for completion of existed task - ✔️ Uses `UnityWebRequest` to load data which works smooth across all platforms including `WebGL` - ✔️ Cache supported on at `WebGL`. Memory cache works, Disk cache isn't allowed by the platform -- ✔️ Set into Image `ImageLoader.LoadSprite(imageURL).ThenSet(image);` -- ✔️ Set into RawImage `ImageLoader.LoadSprite(imageURL).ThenSet(rawImage);` -- ✔️ Set into Material `ImageLoader.LoadSprite(imageURL).ThenSet("_MainTex", material);` -- ✔️ Set into SpriteRenderer `ImageLoader.LoadSprite(imageURL).ThenSet(spriteRenderer);` +- ✔️ Set into Image `ImageLoader.LoadSprite(imageURL).Consume(image);` +- ✔️ Set into RawImage `ImageLoader.LoadSprite(imageURL).Consume(rawImage);` +- ✔️ Set into Material `ImageLoader.LoadSprite(imageURL).Consume("_MainTex", material);` +- ✔️ Set into SpriteRenderer `ImageLoader.LoadSprite(imageURL).Consume(spriteRenderer);` - ✔️ [Set into anything](#cancellation) - ✔️ Cancellation `ImageLoader.LoadSprite(imageURL).Cancel();` - ✔️ Cancellation callback `ImageLoader.LoadSprite(imageURL).Cancelled(() => ...);` @@ -57,6 +57,7 @@ await ImageLoader.LoadSprite(imageURL).ThenSet(image); - [Load `Sprite` then set into multiple `Image`](#load-sprite-then-set-into-multiple-image) - [Error handling](#error-handling) - [Async `await` and `Forget`](#async-await-and-forget) + - [Placeholder](#placeholder) - [Cancellation](#cancellation) - [Cancel by MonoBehaviour events](#cancel-by-monobehaviour-events) - [Explicit cancellation](#explicit-cancellation) @@ -126,6 +127,17 @@ ImageLoader.LoadSprite(imageURL) // loading process started .LoadedFromSource (sprite => Debug.Log("Loaded from source")) // on loaded from source │ // ────────────────────────────────────────────────────────────────────────────────────────────────────┘ + // ┌──────────────────────────┬───────────────────────────────────────────┐ + // │ Success lifecycle events │ │ + // └──────────────────────────┘ │ + .Loaded(sprite => Debug.Log("Loaded")) // on successfully loaded │ + // ┌────────────────────────────────────────────────────────┤ + // │ Set/Consume sprite [placeholder, successfully loaded] │ + // └────────────────────────────────────────────────────────┤ + .Consume(sprite => Debug.Log("Consumed")) // │ + .Consume(image) // │ + // ───────────────────────────────────────────────────────────────────────┘ + // ┌───────────────────────────┬──────────────────────────────────────────┐ // │ Negative lifecycle events │ │ // └───────────────────────────┘ │ @@ -133,13 +145,6 @@ ImageLoader.LoadSprite(imageURL) // loading process started .Failed(exception => Debug.LogException(exception)) // on failed to load │ // ───────────────────────────────────────────────────────────────────────┘ - // ┌──────────────────────────────────────┬──────────────────────────────┐ - // │ Successfully loaded lifecycle events │ │ - // └──────────────────────────────────────┘ │ - .Then(sprite => Debug.Log("Loaded")) // on loaded │ - .ThenSet(image) // on loaded set sprite into image │ - // ──────────────────────────────────────────────────────────────────────┘ - // ┌──────────────────────┬──────────────────────────────────────────────────────────────────────────┐ // │ The end of lifecycle │ │ // └──────────────────────┘ │ @@ -152,34 +157,34 @@ ImageLoader.LoadSprite(imageURL) // loading process started ## Load `Sprite` then set into `Image` -> [Full sample source code](https://github.com/IvanMurzak/Unity-ImageLoader/blob/master/Assets/_PackageRoot/Samples/SampleLoadSpriteThenSetImage.cs) +> [Full sample source code](https://github.com/IvanMurzak/Unity-ImageLoader/blob/master/Assets/_PackageRoot/Samples/SampleLoadSpriteConsumeImage.cs) ```csharp // Load a sprite from the web and cache it for faster loading next time image.sprite = await ImageLoader.LoadSprite(imageURL); // Load a sprite from the web and set it directly to the Image component -await ImageLoader.LoadSprite(imageURL).ThenSet(image); +await ImageLoader.LoadSprite(imageURL).Consume(image); ``` ## Load `Texture2D` then set into `Material` -> [Full sample source code](https://github.com/IvanMurzak/Unity-ImageLoader/blob/master/Assets/_PackageRoot/Samples/SampleLoadTextureThenSetMaterial.cs) +> [Full sample source code](https://github.com/IvanMurzak/Unity-ImageLoader/blob/master/Assets/_PackageRoot/Samples/SampleLoadTextureConsumeMaterial.cs) ```csharp // Load a Texture2D from the web and cache it for faster loading next time material.mainTexture = await ImageLoader.LoadTexture(imageURL); // Load a Texture2D from the web and set it directly to the Material -await ImageLoader.LoadTexture(imageURL).ThenSet(material); +await ImageLoader.LoadTexture(imageURL).Consume(material); ``` ## Load `Sprite` then set into multiple `Image` -> [Full sample source code](https://github.com/IvanMurzak/Unity-ImageLoader/blob/master/Assets/_PackageRoot/Samples/SampleLoadSpriteThenSetIntoMultipleImages.cs) +> [Full sample source code](https://github.com/IvanMurzak/Unity-ImageLoader/blob/master/Assets/_PackageRoot/Samples/SampleLoadSpriteConsumeIntoMultipleImages.cs) ```csharp -ImageLoader.LoadSprite(imageURL).ThenSet(image1, image2).Forget(); +ImageLoader.LoadSprite(imageURL).Consume(image1, image2).Forget(); ``` ## Error handling @@ -188,12 +193,12 @@ ImageLoader.LoadSprite(imageURL).ThenSet(image1, image2).Forget(); ```csharp ImageLoader.LoadSprite(imageURL) // Attempt to load a sprite - .ThenSet(image) // If successful, set the sprite to the Image component + .Consume(image) // If successful, set the sprite to the Image component .Failed(exception => Debug.LogException(exception)) // If an error occurs, log the exception .Forget(); // Forget the task to avoid compilation warning ImageLoader.LoadSprite(imageURL) // Attempt to load a sprite - .ThenSet(image) // If successful, set the sprite to the Image component + .Consume(image) // If successful, set the sprite to the Image component .Then(sprite => image.gameObject.SetActive(true)) // If successful, activate the GameObject .Failed(exception => image.gameObject.SetActive(false)) // If an error occurs, deactivate the GameObject .Forget(); // Forget the task to avoid compilation warning @@ -208,12 +213,32 @@ ImageLoader.LoadSprite(imageURL) // Attempt to load a sprite await ImageLoader.LoadSprite(imageURL); // Load image, set image and wait -await ImageLoader.LoadSprite(imageURL).ThenSet(image); +await ImageLoader.LoadSprite(imageURL).Consume(image); // Skip waiting for completion. // To do that we can simply remove 'await' from the start. // To avoid compilation warning need to add '.Forget()'. -ImageLoader.LoadSprite(imageURL).ThenSet(image).Forget(); +ImageLoader.LoadSprite(imageURL).Consume(image).Forget(); +``` + +## Placeholder + +While the target image is loading it would be a good idea to set placeholder image. Also, it works well for setting image if loading fails. + +> [Full sample source code](https://github.com/IvanMurzak/Unity-ImageLoader/blob/master/Assets/_PackageRoot/Samples/SamplePlaceholder.cs) + +```csharp +ImageLoader.LoadSprite(imageURL) + // set placeholder in all conditions + .SetPlaceholder(placeholderAny) + + // set placeholder in a specific conditions + .SetPlaceholder(placeholderLoadingFromSource, PlaceholderTrigger.LoadingFromSource) + .SetPlaceholder(placeholderFailedToLoad, PlaceholderTrigger.FailedToLoad) + + // set consumer + .Consume(image) + .Forget(); ``` ## Cancellation @@ -226,7 +251,7 @@ Cancellation is helpful if target image consumer doesn't exist anymore. For exam ```csharp ImageLoader.LoadSprite(imageURL) - .ThenSet(image) + .Consume(image) .CancelOnEnable(this) // cancel on OnEnable event of current MonoBehaviour .CancelOnDisable(this) // cancel on OnDisable event of current MonoBehaviour .CancelOnDestroy(this); // cancel on OnDestroy event of current MonoBehaviour @@ -235,7 +260,7 @@ ImageLoader.LoadSprite(imageURL) ### Explicit cancellation ```csharp -var future = ImageLoader.LoadSprite(imageURL).ThenSet(image); +var future = ImageLoader.LoadSprite(imageURL).Consume(image); future.Cancel(); ``` @@ -246,7 +271,7 @@ var cancellationTokenSource = new CancellationTokenSource(); // loading with attached cancellation token ImageLoader.LoadSprite(imageURL, cancellationToken: cancellationTokenSource.Token) - .ThenSet(image) + .Consume(image) .Forget(); cancellationTokenSource.Cancel(); // canceling @@ -256,7 +281,7 @@ cancellationTokenSource.Cancel(); // canceling var cancellationTokenSource = new CancellationTokenSource(); ImageLoader.LoadSprite(imageURL) - .ThenSet(image) + .Consume(image) .Register(cancellationTokenSource.Token) // registering cancellation token .Forget(); @@ -266,7 +291,7 @@ cancellationTokenSource.Cancel(); // canceling ### Cancellation by `using` ```csharp -using (var future = ImageLoader.LoadSprite(imageURL).ThenSet(image)) +using (var future = ImageLoader.LoadSprite(imageURL).Consume(image)) { // future would be canceled and disposed outside of the brackets } @@ -274,18 +299,18 @@ using (var future = ImageLoader.LoadSprite(imageURL).ThenSet(image)) ```csharp ImageLoader.LoadSprite(imageURL) // load sprite - .ThenSet(image) // if success set sprite into image + .Consume(image) // if success set sprite into image .CancelOnDestroy(this) // cancel OnDestroy event of current gameObject .Forget(); ImageLoader.LoadSprite(imageURL) // load sprite - .ThenSet(image) // if success set sprite into image + .Consume(image) // if success set sprite into image .Failed(exception => Debug.LogException(exception)) // if fail print exception .CancelOnDestroy(this) // cancel OnDestroy event of current gameObject .Forget(); ImageLoader.LoadSprite(imageURL) // load sprite - .ThenSet(image) // if success set sprite into image + .Consume(image) // if success set sprite into image .Then(sprite => image.gameObject.SetActive(true)) // if success activate gameObject .Failed(exception => image.gameObject.SetActive(false)) // if fail deactivate gameObject .Canceled(() => Debug.Log("ImageLoading canceled")) // if cancelled @@ -309,7 +334,7 @@ Set timeout for a specific loading request (`IFuture`): ```csharp ImageLoader.LoadSprite(imageURL) // load sprite - .ThenSet(image) // if success set sprite into image + .Consume(image) // if success set sprite into image .Timeout(TimeSpan.FromSeconds(10)) // set timeout duration 10 seconds .Forget(); ``` @@ -430,11 +455,11 @@ reference = null; > [Full sample source code](https://github.com/IvanMurzak/Unity-ImageLoader/blob/master/Assets/_PackageRoot/Samples/SampleReferences.cs) -`Reference.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. +`Future>.Consume` 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. ```csharp ImageLoader.LoadSpriteRef(imageURL) // load sprite using Reference - .ThenSet(image) // if success set sprite into image, also creates binding to `image` + .Consume(image) // if success set sprite into image, also creates binding to `image` .Forget(); ``` diff --git a/Assets/_PackageRoot/README.md b/Assets/_PackageRoot/README.md index 0bf57d3..f6cb8e4 100644 --- a/Assets/_PackageRoot/README.md +++ b/Assets/_PackageRoot/README.md @@ -15,13 +15,13 @@ image.sprite = await ImageLoader.LoadSprite(imageURL); Don't wait, use callback to set loaded image later: ```csharp -ImageLoader.LoadSprite(imageURL).ThenSet(image).Forget(); +ImageLoader.LoadSprite(imageURL).Consume(image).Forget(); ``` Use callback to set image and still wait for the completion: ```csharp -await ImageLoader.LoadSprite(imageURL).ThenSet(image); +await ImageLoader.LoadSprite(imageURL).Consume(image); ``` ## Features @@ -33,10 +33,10 @@ await ImageLoader.LoadSprite(imageURL).ThenSet(image); - ✔️ Avoids loading same image multiple times simultaneously, a new load task waits for completion of existed task - ✔️ Uses `UnityWebRequest` to load data which works smooth across all platforms including `WebGL` - ✔️ Cache supported on at `WebGL`. Memory cache works, Disk cache isn't allowed by the platform -- ✔️ Set into Image `ImageLoader.LoadSprite(imageURL).ThenSet(image);` -- ✔️ Set into RawImage `ImageLoader.LoadSprite(imageURL).ThenSet(rawImage);` -- ✔️ Set into Material `ImageLoader.LoadSprite(imageURL).ThenSet("_MainTex", material);` -- ✔️ Set into SpriteRenderer `ImageLoader.LoadSprite(imageURL).ThenSet(spriteRenderer);` +- ✔️ Set into Image `ImageLoader.LoadSprite(imageURL).Consume(image);` +- ✔️ Set into RawImage `ImageLoader.LoadSprite(imageURL).Consume(rawImage);` +- ✔️ Set into Material `ImageLoader.LoadSprite(imageURL).Consume("_MainTex", material);` +- ✔️ Set into SpriteRenderer `ImageLoader.LoadSprite(imageURL).Consume(spriteRenderer);` - ✔️ [Set into anything](#cancellation) - ✔️ Cancellation `ImageLoader.LoadSprite(imageURL).Cancel();` - ✔️ Cancellation callback `ImageLoader.LoadSprite(imageURL).Cancelled(() => ...);` @@ -57,6 +57,7 @@ await ImageLoader.LoadSprite(imageURL).ThenSet(image); - [Load `Sprite` then set into multiple `Image`](#load-sprite-then-set-into-multiple-image) - [Error handling](#error-handling) - [Async `await` and `Forget`](#async-await-and-forget) + - [Placeholder](#placeholder) - [Cancellation](#cancellation) - [Cancel by MonoBehaviour events](#cancel-by-monobehaviour-events) - [Explicit cancellation](#explicit-cancellation) @@ -126,6 +127,17 @@ ImageLoader.LoadSprite(imageURL) // loading process started .LoadedFromSource (sprite => Debug.Log("Loaded from source")) // on loaded from source │ // ────────────────────────────────────────────────────────────────────────────────────────────────────┘ + // ┌──────────────────────────┬───────────────────────────────────────────┐ + // │ Success lifecycle events │ │ + // └──────────────────────────┘ │ + .Loaded(sprite => Debug.Log("Loaded")) // on successfully loaded │ + // ┌────────────────────────────────────────────────────────┤ + // │ Set/Consume sprite [placeholder, successfully loaded] │ + // └────────────────────────────────────────────────────────┤ + .Consume(sprite => Debug.Log("Consumed")) // │ + .Consume(image) // │ + // ───────────────────────────────────────────────────────────────────────┘ + // ┌───────────────────────────┬──────────────────────────────────────────┐ // │ Negative lifecycle events │ │ // └───────────────────────────┘ │ @@ -133,13 +145,6 @@ ImageLoader.LoadSprite(imageURL) // loading process started .Failed(exception => Debug.LogException(exception)) // on failed to load │ // ───────────────────────────────────────────────────────────────────────┘ - // ┌──────────────────────────────────────┬──────────────────────────────┐ - // │ Successfully loaded lifecycle events │ │ - // └──────────────────────────────────────┘ │ - .Then(sprite => Debug.Log("Loaded")) // on loaded │ - .ThenSet(image) // on loaded set sprite into image │ - // ──────────────────────────────────────────────────────────────────────┘ - // ┌──────────────────────┬──────────────────────────────────────────────────────────────────────────┐ // │ The end of lifecycle │ │ // └──────────────────────┘ │ @@ -152,34 +157,34 @@ ImageLoader.LoadSprite(imageURL) // loading process started ## Load `Sprite` then set into `Image` -> [Full sample source code](https://github.com/IvanMurzak/Unity-ImageLoader/blob/master/Assets/_PackageRoot/Samples/SampleLoadSpriteThenSetImage.cs) +> [Full sample source code](https://github.com/IvanMurzak/Unity-ImageLoader/blob/master/Assets/_PackageRoot/Samples/SampleLoadSpriteConsumeImage.cs) ```csharp // Load a sprite from the web and cache it for faster loading next time image.sprite = await ImageLoader.LoadSprite(imageURL); // Load a sprite from the web and set it directly to the Image component -await ImageLoader.LoadSprite(imageURL).ThenSet(image); +await ImageLoader.LoadSprite(imageURL).Consume(image); ``` ## Load `Texture2D` then set into `Material` -> [Full sample source code](https://github.com/IvanMurzak/Unity-ImageLoader/blob/master/Assets/_PackageRoot/Samples/SampleLoadTextureThenSetMaterial.cs) +> [Full sample source code](https://github.com/IvanMurzak/Unity-ImageLoader/blob/master/Assets/_PackageRoot/Samples/SampleLoadTextureConsumeMaterial.cs) ```csharp // Load a Texture2D from the web and cache it for faster loading next time material.mainTexture = await ImageLoader.LoadTexture(imageURL); // Load a Texture2D from the web and set it directly to the Material -await ImageLoader.LoadTexture(imageURL).ThenSet(material); +await ImageLoader.LoadTexture(imageURL).Consume(material); ``` ## Load `Sprite` then set into multiple `Image` -> [Full sample source code](https://github.com/IvanMurzak/Unity-ImageLoader/blob/master/Assets/_PackageRoot/Samples/SampleLoadSpriteThenSetIntoMultipleImages.cs) +> [Full sample source code](https://github.com/IvanMurzak/Unity-ImageLoader/blob/master/Assets/_PackageRoot/Samples/SampleLoadSpriteConsumeIntoMultipleImages.cs) ```csharp -ImageLoader.LoadSprite(imageURL).ThenSet(image1, image2).Forget(); +ImageLoader.LoadSprite(imageURL).Consume(image1, image2).Forget(); ``` ## Error handling @@ -188,12 +193,12 @@ ImageLoader.LoadSprite(imageURL).ThenSet(image1, image2).Forget(); ```csharp ImageLoader.LoadSprite(imageURL) // Attempt to load a sprite - .ThenSet(image) // If successful, set the sprite to the Image component + .Consume(image) // If successful, set the sprite to the Image component .Failed(exception => Debug.LogException(exception)) // If an error occurs, log the exception .Forget(); // Forget the task to avoid compilation warning ImageLoader.LoadSprite(imageURL) // Attempt to load a sprite - .ThenSet(image) // If successful, set the sprite to the Image component + .Consume(image) // If successful, set the sprite to the Image component .Then(sprite => image.gameObject.SetActive(true)) // If successful, activate the GameObject .Failed(exception => image.gameObject.SetActive(false)) // If an error occurs, deactivate the GameObject .Forget(); // Forget the task to avoid compilation warning @@ -208,12 +213,32 @@ ImageLoader.LoadSprite(imageURL) // Attempt to load a sprite await ImageLoader.LoadSprite(imageURL); // Load image, set image and wait -await ImageLoader.LoadSprite(imageURL).ThenSet(image); +await ImageLoader.LoadSprite(imageURL).Consume(image); // Skip waiting for completion. // To do that we can simply remove 'await' from the start. // To avoid compilation warning need to add '.Forget()'. -ImageLoader.LoadSprite(imageURL).ThenSet(image).Forget(); +ImageLoader.LoadSprite(imageURL).Consume(image).Forget(); +``` + +## Placeholder + +While the target image is loading it would be a good idea to set placeholder image. Also, it works well for setting image if loading fails. + +> [Full sample source code](https://github.com/IvanMurzak/Unity-ImageLoader/blob/master/Assets/_PackageRoot/Samples/SamplePlaceholder.cs) + +```csharp +ImageLoader.LoadSprite(imageURL) + // set placeholder in all conditions + .SetPlaceholder(placeholderAny) + + // set placeholder in a specific conditions + .SetPlaceholder(placeholderLoadingFromSource, PlaceholderTrigger.LoadingFromSource) + .SetPlaceholder(placeholderFailedToLoad, PlaceholderTrigger.FailedToLoad) + + // set consumer + .Consume(image) + .Forget(); ``` ## Cancellation @@ -226,7 +251,7 @@ Cancellation is helpful if target image consumer doesn't exist anymore. For exam ```csharp ImageLoader.LoadSprite(imageURL) - .ThenSet(image) + .Consume(image) .CancelOnEnable(this) // cancel on OnEnable event of current MonoBehaviour .CancelOnDisable(this) // cancel on OnDisable event of current MonoBehaviour .CancelOnDestroy(this); // cancel on OnDestroy event of current MonoBehaviour @@ -235,7 +260,7 @@ ImageLoader.LoadSprite(imageURL) ### Explicit cancellation ```csharp -var future = ImageLoader.LoadSprite(imageURL).ThenSet(image); +var future = ImageLoader.LoadSprite(imageURL).Consume(image); future.Cancel(); ``` @@ -246,7 +271,7 @@ var cancellationTokenSource = new CancellationTokenSource(); // loading with attached cancellation token ImageLoader.LoadSprite(imageURL, cancellationToken: cancellationTokenSource.Token) - .ThenSet(image) + .Consume(image) .Forget(); cancellationTokenSource.Cancel(); // canceling @@ -256,7 +281,7 @@ cancellationTokenSource.Cancel(); // canceling var cancellationTokenSource = new CancellationTokenSource(); ImageLoader.LoadSprite(imageURL) - .ThenSet(image) + .Consume(image) .Register(cancellationTokenSource.Token) // registering cancellation token .Forget(); @@ -266,7 +291,7 @@ cancellationTokenSource.Cancel(); // canceling ### Cancellation by `using` ```csharp -using (var future = ImageLoader.LoadSprite(imageURL).ThenSet(image)) +using (var future = ImageLoader.LoadSprite(imageURL).Consume(image)) { // future would be canceled and disposed outside of the brackets } @@ -274,18 +299,18 @@ using (var future = ImageLoader.LoadSprite(imageURL).ThenSet(image)) ```csharp ImageLoader.LoadSprite(imageURL) // load sprite - .ThenSet(image) // if success set sprite into image + .Consume(image) // if success set sprite into image .CancelOnDestroy(this) // cancel OnDestroy event of current gameObject .Forget(); ImageLoader.LoadSprite(imageURL) // load sprite - .ThenSet(image) // if success set sprite into image + .Consume(image) // if success set sprite into image .Failed(exception => Debug.LogException(exception)) // if fail print exception .CancelOnDestroy(this) // cancel OnDestroy event of current gameObject .Forget(); ImageLoader.LoadSprite(imageURL) // load sprite - .ThenSet(image) // if success set sprite into image + .Consume(image) // if success set sprite into image .Then(sprite => image.gameObject.SetActive(true)) // if success activate gameObject .Failed(exception => image.gameObject.SetActive(false)) // if fail deactivate gameObject .Canceled(() => Debug.Log("ImageLoading canceled")) // if cancelled @@ -309,7 +334,7 @@ Set timeout for a specific loading request (`IFuture`): ```csharp ImageLoader.LoadSprite(imageURL) // load sprite - .ThenSet(image) // if success set sprite into image + .Consume(image) // if success set sprite into image .Timeout(TimeSpan.FromSeconds(10)) // set timeout duration 10 seconds .Forget(); ``` @@ -430,11 +455,11 @@ reference = null; > [Full sample source code](https://github.com/IvanMurzak/Unity-ImageLoader/blob/master/Assets/_PackageRoot/Samples/SampleReferences.cs) -`Reference.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. +`Future>.Consume` 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. ```csharp ImageLoader.LoadSpriteRef(imageURL) // load sprite using Reference - .ThenSet(image) // if success set sprite into image, also creates binding to `image` + .Consume(image) // if success set sprite into image, also creates binding to `image` .Forget(); ``` diff --git a/Assets/_PackageRoot/Runtime/Future/Extensions.meta b/Assets/_PackageRoot/Runtime/Future/Extensions.meta new file mode 100644 index 0000000..ff27b8f --- /dev/null +++ b/Assets/_PackageRoot/Runtime/Future/Extensions.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 04fbd9d8ff04b2d4397b3008b80044d3 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_PackageRoot/Runtime/Future/Future.CancelOn.cs b/Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.CancelOn.cs similarity index 100% rename from Assets/_PackageRoot/Runtime/Future/Future.CancelOn.cs rename to Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.CancelOn.cs diff --git a/Assets/_PackageRoot/Runtime/Future/Future.CancelOn.cs.meta b/Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.CancelOn.cs.meta similarity index 100% rename from Assets/_PackageRoot/Runtime/Future/Future.CancelOn.cs.meta rename to Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.CancelOn.cs.meta diff --git a/Assets/_PackageRoot/Runtime/Future/Future.ThenSet.cs b/Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.Consume.cs similarity index 66% rename from Assets/_PackageRoot/Runtime/Future/Future.ThenSet.cs rename to Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.Consume.cs index 7baa3c8..677dde0 100644 --- a/Assets/_PackageRoot/Runtime/Future/Future.ThenSet.cs +++ b/Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.Consume.cs @@ -13,28 +13,19 @@ public static partial class FutureEx /// Setter function that gets Consumer and loaded object, it should set the object into Consumer /// Array of consumers for injecting /// Returns async Future - public static IFuture ThenSet(this IFuture future, Action setter, params C[] consumers) => future.Then(obj => + public static IFuture Consume(this IFuture future, Action setter, params C[] consumers) => future.Consume(obj => { UniTask.Post(() => // using only MainThread to set any images to any targets { - foreach (var target in consumers) + foreach (var consumer in consumers) { - if (target == null) + if (ReferenceEquals(consumer, null) || consumer == null) { if (future.LogLevel.IsActive(DebugLevel.Warning)) Debug.LogWarning($"[ImageLoader] Future[id={future.Id}] The target is null. Can't set image into it. Skipping."); continue; } - if (target is UnityEngine.Object unityObject) - { - if (unityObject.IsNull()) - { - if (future.LogLevel.IsActive(DebugLevel.Warning)) - Debug.LogWarning($"The target ({typeof(T).Name}) is destroyed. Can't set image into it. Skipping."); - continue; - } - } - setter?.Invoke(target, obj); + Safe.Run(setter, consumer, obj, future.LogLevel); } }); }); @@ -44,40 +35,40 @@ public static IFuture ThenSet(this IFuture future, Action sett /// /// Array of Images /// Returns async Future - public static IFuture ThenSet(this IFuture future, params Image[] images) - => future.ThenSet((consumer, sprite) => consumer.sprite = sprite, images); + public static IFuture Consume(this IFuture future, params Image[] images) + => future.Consume((consumer, sprite) => consumer.sprite = sprite, images); /// /// Set image into array of RawImages /// /// Array of RawImages /// Returns async Future - public static IFuture ThenSet(this IFuture future, params RawImage[] rawImages) - => future.ThenSet((consumer, sprite) => consumer.texture = sprite?.texture, rawImages); + public static IFuture Consume(this IFuture future, params RawImage[] rawImages) + => future.Consume((consumer, sprite) => consumer.texture = sprite?.texture, rawImages); /// /// Set image into array of RawImages /// /// Array of RawImages /// Returns async Future - public static IFuture ThenSet(this IFuture future, params RawImage[] rawImages) - => future.ThenSet((consumer, texture) => consumer.texture = texture, rawImages); + public static IFuture Consume(this IFuture future, params RawImage[] rawImages) + => future.Consume((consumer, texture) => consumer.texture = texture, rawImages); /// /// Set image into array of SpriteRenderers /// /// Array of SpriteRenderers /// Returns async Future - public static IFuture ThenSet(this IFuture future, params SpriteRenderer[] spriteRenderers) - => future.ThenSet((consumer, sprite) => consumer.sprite = sprite, spriteRenderers); + public static IFuture Consume(this IFuture future, params SpriteRenderer[] spriteRenderers) + => future.Consume((consumer, sprite) => consumer.sprite = sprite, spriteRenderers); /// /// Set image into array of Materials /// /// Array of Materials /// Returns async Future - public static IFuture ThenSet(this IFuture future, params Material[] materials) - => future.ThenSet("_MainTex", materials); + public static IFuture Consume(this IFuture future, params Material[] materials) + => future.Consume("_MainTex", materials); /// /// Set image into array of Materials @@ -85,16 +76,16 @@ public static IFuture ThenSet(this IFuture future, params Materi /// Property name to set the texture /// Array of Materials /// Returns async Future - public static IFuture ThenSet(this IFuture future, string propertyName = "_MainTex", params Material[] materials) - => future.ThenSet((consumer, sprite) => consumer.SetTexture(propertyName, sprite?.texture), materials); + public static IFuture Consume(this IFuture future, string propertyName = "_MainTex", params Material[] materials) + => future.Consume((consumer, sprite) => consumer.SetTexture(propertyName, sprite?.texture), materials); /// /// Set image into array of Materials /// /// Array of Materials /// Returns async Future - public static IFuture ThenSet(this IFuture future, params Material[] materials) - => future.ThenSet("_MainTex", materials); + public static IFuture Consume(this IFuture future, params Material[] materials) + => future.Consume("_MainTex", materials); /// /// Set image into array of Materials @@ -102,7 +93,7 @@ public static IFuture ThenSet(this IFuture future, params /// Property name to set the texture /// Array of Materials /// Returns async Future - public static IFuture ThenSet(this IFuture future, string propertyName = "_MainTex", params Material[] materials) - => future.ThenSet((consumer, texture) => consumer.SetTexture(propertyName, texture), materials); + public static IFuture Consume(this IFuture future, string propertyName = "_MainTex", params Material[] materials) + => future.Consume((consumer, texture) => consumer.SetTexture(propertyName, texture), materials); } } diff --git a/Assets/_PackageRoot/Runtime/Future/Future.ThenSet.cs.meta b/Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.Consume.cs.meta similarity index 100% rename from Assets/_PackageRoot/Runtime/Future/Future.ThenSet.cs.meta rename to Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.Consume.cs.meta diff --git a/Assets/_PackageRoot/Runtime/Future/Future.ThenSetRef.cs b/Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.ConsumeRef.cs similarity index 74% rename from Assets/_PackageRoot/Runtime/Future/Future.ThenSetRef.cs rename to Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.ConsumeRef.cs index 231d3e4..02a011a 100644 --- a/Assets/_PackageRoot/Runtime/Future/Future.ThenSetRef.cs +++ b/Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.ConsumeRef.cs @@ -13,29 +13,20 @@ public static partial class FutureEx /// Setter function that gets Consumer and loaded object, it should set the object into Consumer /// Array of consumers for injecting /// Returns async Future - public static IFuture> ThenSet(this IFuture> future, Action> setter, params C[] consumers) => future.Then(reference => + public static IFuture> Consume(this IFuture> future, Action> setter, params C[] consumers) => future.Consume(reference => { UniTask.Post(() => // using only MainThread to set any images to any targets { foreach (var consumer in consumers) { - if (consumer == null) + if (ReferenceEquals(consumer, null) || consumer == null) { if (future.LogLevel.IsActive(DebugLevel.Warning)) Debug.LogWarning($"[ImageLoader] Future[id={future.Id}] The target is null. Can't set image into it. Skipping."); continue; } - if (consumer is UnityEngine.Object unityObject) - { - if (unityObject.IsNull()) - { - if (future.LogLevel.IsActive(DebugLevel.Warning)) - Debug.LogWarning($"The target ({typeof(T).Name}) is destroyed. Can't set image into it. Skipping."); - continue; - } - } - setter?.Invoke(consumer, reference); + Safe.Run(setter, consumer, reference, future.LogLevel); // ┌────────────────────────┬─────────────────────────────────────────────────────┐ // │ Memory leak protection │ Connection Reference to the Component │ @@ -53,40 +44,40 @@ public static IFuture> ThenSet(this IFuture> fut /// /// Array of Images /// Returns async Future - public static IFuture> ThenSet(this IFuture> future, params Image[] images) - => future.ThenSet((consumer, reference) => consumer.sprite = reference?.Value, images); + public static IFuture> Consume(this IFuture> future, params Image[] images) + => future.Consume((consumer, reference) => consumer.sprite = reference?.Value, images); /// /// Set image into array of RawImages /// /// Array of RawImages /// Returns async Future - public static IFuture> ThenSet(this IFuture> future, params RawImage[] rawImages) - => future.ThenSet((consumer, reference) => consumer.texture = reference?.Value?.texture, rawImages); + public static IFuture> Consume(this IFuture> future, params RawImage[] rawImages) + => future.Consume((consumer, reference) => consumer.texture = reference?.Value?.texture, rawImages); /// /// Set image into array of RawImages /// /// Array of RawImages /// Returns async Future - public static IFuture> ThenSet(this IFuture> future, params RawImage[] rawImages) - => future.ThenSet((consumer, reference) => consumer.texture = reference?.Value, rawImages); + public static IFuture> Consume(this IFuture> future, params RawImage[] rawImages) + => future.Consume((consumer, reference) => consumer.texture = reference?.Value, rawImages); /// /// Set image into array of SpriteRenderers /// /// Array of SpriteRenderers /// Returns async Future - public static IFuture> ThenSet(this IFuture> future, params SpriteRenderer[] spriteRenderers) - => future.ThenSet((consumer, reference) => consumer.sprite = reference?.Value, spriteRenderers); + public static IFuture> Consume(this IFuture> future, params SpriteRenderer[] spriteRenderers) + => future.Consume((consumer, reference) => consumer.sprite = reference?.Value, spriteRenderers); /// /// Set image into array of Materials /// /// Array of Materials /// Returns async Future - public static IFuture> ThenSet(this IFuture> future, params Material[] materials) - => future.ThenSet("_MainTex", materials); + public static IFuture> Consume(this IFuture> future, params Material[] materials) + => future.Consume("_MainTex", materials); /// /// Set image into array of Materials @@ -94,16 +85,16 @@ public static IFuture> ThenSet(this IFuture> /// Property name to set the texture /// Array of Materials /// Returns async Future - public static IFuture> ThenSet(this IFuture> future, string propertyName = "_MainTex", params Material[] materials) - => future.ThenSet((consumer, reference) => consumer.SetTexture(propertyName, reference?.Value?.texture), materials); + public static IFuture> Consume(this IFuture> future, string propertyName = "_MainTex", params Material[] materials) + => future.Consume((consumer, reference) => consumer.SetTexture(propertyName, reference?.Value?.texture), materials); /// /// Set image into array of Materials /// /// Array of Materials /// Returns async Future - public static IFuture> ThenSet(this IFuture> future, params Material[] materials) - => future.ThenSet("_MainTex", materials); + public static IFuture> Consume(this IFuture> future, params Material[] materials) + => future.Consume("_MainTex", materials); /// /// Set image into array of Materials @@ -111,7 +102,7 @@ public static IFuture> ThenSet(this IFutureProperty name to set the texture /// Array of Materials /// Returns async Future - public static IFuture> ThenSet(this IFuture> future, string propertyName = "_MainTex", params Material[] materials) - => future.ThenSet((consumer, reference) => consumer.SetTexture(propertyName, reference?.Value), materials); + public static IFuture> Consume(this IFuture> future, string propertyName = "_MainTex", params Material[] materials) + => future.Consume((consumer, reference) => consumer.SetTexture(propertyName, reference?.Value), materials); } } diff --git a/Assets/_PackageRoot/Runtime/Future/Future.ThenSetRef.cs.meta b/Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.ConsumeRef.cs.meta similarity index 100% rename from Assets/_PackageRoot/Runtime/Future/Future.ThenSetRef.cs.meta rename to Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.ConsumeRef.cs.meta diff --git a/Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.Placeholder.Color.cs b/Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.Placeholder.Color.cs new file mode 100644 index 0000000..116679c --- /dev/null +++ b/Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.Placeholder.Color.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Concurrent; +using UnityEngine; + +namespace Extensions.Unity.ImageLoader +{ + public static partial class FutureEx + { + /// + /// Set a placeholder in all conditions for this Future instance + /// + /// new placeholder + /// Returns the Future instance + public static IFuture SetPlaceholder(this IFuture future, Color color) => future.SetPlaceholder(color, + PlaceholderTrigger.LoadingFromDiskCache, + PlaceholderTrigger.LoadingFromSource, + PlaceholderTrigger.FailedToLoad, + PlaceholderTrigger.Canceled); + + /// + /// Set a placeholder in specified conditions for this Future instance + /// + /// hex color to fill solid color + /// Returns the Future instance + public static IFuture SetPlaceholder(this IFuture future, Color color, params PlaceholderTrigger[] triggers) + => future.SetPlaceholder(color.ToHexRGBA(), triggers); + + /// + /// Set a placeholder in all conditions for this Future instance + /// + /// new placeholder + /// Returns the Future instance + public static IFuture SetPlaceholder(this IFuture future, Color color) => future.SetPlaceholder(color, + PlaceholderTrigger.LoadingFromDiskCache, + PlaceholderTrigger.LoadingFromSource, + PlaceholderTrigger.FailedToLoad, + PlaceholderTrigger.Canceled); + + /// + /// Set a placeholder in specified conditions for this Future instance + /// + /// hex color to fill solid color + /// Returns the Future instance + public static IFuture SetPlaceholder(this IFuture future, Color color, params PlaceholderTrigger[] triggers) + => future.SetPlaceholder(color.ToHexRGBA(), triggers); + + /// + /// Set a placeholder in all conditions for this Future instance + /// + /// new placeholder + /// Returns the Future instance + public static IFuture> SetPlaceholder(this IFuture> future, Color color) => future.SetPlaceholder(color, + PlaceholderTrigger.LoadingFromDiskCache, + PlaceholderTrigger.LoadingFromSource, + PlaceholderTrigger.FailedToLoad, + PlaceholderTrigger.Canceled); + + /// + /// Set a placeholder in specified conditions for this Future instance + /// + /// hex color to fill solid color + /// Returns the Future instance + public static IFuture> SetPlaceholder(this IFuture> future, Color color, params PlaceholderTrigger[] triggers) + => future.SetPlaceholder(color.ToHexRGBA(), triggers); + + /// + /// Set a placeholder in all conditions for this Future instance + /// + /// new placeholder + /// Returns the Future instance + public static IFuture> SetPlaceholder(this IFuture> future, Color color) => future.SetPlaceholder(color, + PlaceholderTrigger.LoadingFromDiskCache, + PlaceholderTrigger.LoadingFromSource, + PlaceholderTrigger.FailedToLoad, + PlaceholderTrigger.Canceled); + + /// + /// Set a placeholder in specified conditions for this Future instance + /// + /// hex color to fill solid color + /// Returns the Future instance + public static IFuture> SetPlaceholder(this IFuture> future, Color color, params PlaceholderTrigger[] triggers) + => future.SetPlaceholder(color.ToHexRGBA(), triggers); + } +} diff --git a/Assets/_PackageRoot/Runtime/Future/Future.Setter.cs.meta b/Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.Placeholder.Color.cs.meta similarity index 83% rename from Assets/_PackageRoot/Runtime/Future/Future.Setter.cs.meta rename to Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.Placeholder.Color.cs.meta index 87a056a..c4fda6e 100644 --- a/Assets/_PackageRoot/Runtime/Future/Future.Setter.cs.meta +++ b/Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.Placeholder.Color.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 79d466ddba9aff945b8b29cc2a155a85 +guid: 9956fc41dbd96e345b77b3d39e81b365 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.Placeholder.ColorHex.cs b/Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.Placeholder.ColorHex.cs new file mode 100644 index 0000000..6160384 --- /dev/null +++ b/Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.Placeholder.ColorHex.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Concurrent; +using UnityEngine; + +namespace Extensions.Unity.ImageLoader +{ + public static partial class FutureEx + { + /// + /// Set a placeholder in all conditions for this Future instance + /// + /// new placeholder + /// Returns the Future instance + public static IFuture SetPlaceholder(this IFuture future, string hexColor) => future.SetPlaceholder(hexColor, + PlaceholderTrigger.LoadingFromDiskCache, + PlaceholderTrigger.LoadingFromSource, + PlaceholderTrigger.FailedToLoad, + PlaceholderTrigger.Canceled); + + /// + /// Set a placeholder in specified conditions for this Future instance + /// + /// hex color to fill solid color + /// Returns the Future instance + public static IFuture SetPlaceholder(this IFuture future, string hexColor, params PlaceholderTrigger[] triggers) + => future.SetPlaceholder(GetOrCreate(hexColor, color + => CreateFillTexture(color)), triggers); + + /// + /// Set a placeholder in all conditions for this Future instance + /// + /// new placeholder + /// Returns the Future instance + public static IFuture SetPlaceholder(this IFuture future, string hexColor) => future.SetPlaceholder(hexColor, + PlaceholderTrigger.LoadingFromDiskCache, + PlaceholderTrigger.LoadingFromSource, + PlaceholderTrigger.FailedToLoad, + PlaceholderTrigger.Canceled); + + + /// + /// Set a placeholder in specified conditions for this Future instance + /// + /// hex color to fill solid color + /// Returns the Future instance + public static IFuture SetPlaceholder(this IFuture future, string hexColor, params PlaceholderTrigger[] triggers) + => future.SetPlaceholder(GetOrCreate(hexColor, color + => GetOrCreate(hexColor, color2 + => CreateFillTexture(color2)) + .ToSprite()), triggers); + + /// + /// Set a placeholder in all conditions for this Future instance + /// + /// new placeholder + /// Returns the Future instance + public static IFuture> SetPlaceholder(this IFuture> future, string hexColor) => future.SetPlaceholder(hexColor, + PlaceholderTrigger.LoadingFromDiskCache, + PlaceholderTrigger.LoadingFromSource, + PlaceholderTrigger.FailedToLoad, + PlaceholderTrigger.Canceled); + /// + /// Set a placeholder in specified conditions for this Future instance + /// + /// hex color to fill solid color + /// Returns the Future instance + public static IFuture> SetPlaceholder(this IFuture> future, string hexColor, params PlaceholderTrigger[] triggers) + => future.SetPlaceholder( + GetOrCreate(hexColor, color => new Reference(hexColor, + GetOrCreate(hexColor, color2 + => CreateFillTexture(color2)))) + .SetKeep(), triggers); + + /// + /// Set a placeholder in all conditions for this Future instance + /// + /// new placeholder + /// Returns the Future instance + public static IFuture> SetPlaceholder(this IFuture> future, string hexColor) => future.SetPlaceholder(hexColor, + PlaceholderTrigger.LoadingFromDiskCache, + PlaceholderTrigger.LoadingFromSource, + PlaceholderTrigger.FailedToLoad, + PlaceholderTrigger.Canceled); + + /// + /// Set a placeholder in specified conditions for this Future instance + /// + /// hex color to fill solid color + /// Returns the Future instance + public static IFuture> SetPlaceholder(this IFuture> future, string hexColor, params PlaceholderTrigger[] triggers) + => future.SetPlaceholder( + GetOrCreate(hexColor, color => new Reference(hexColor, + GetOrCreate(hexColor, color2 => // sprite + GetOrCreate(hexColor, color3 => // texture + CreateFillTexture(color3)) + .ToSprite()))) + .SetKeep(), triggers); + + public static T GetOrCreate(string hexColor, Func create) + { + var type = typeof(T); + if (!colors.ContainsKey(type)) + colors[type] = new ConcurrentDictionary(); + + if (!colors[type].ContainsKey(hexColor)) + { + if (ColorUtility.TryParseHtmlString(hexColor, out var color)) + { + colors[type][hexColor] = create(color); + } + else + { + Debug.LogWarning($"[ImageLoader] FutureEx.SetPlaceholder: Invalid hex color '{hexColor}'"); + colors[type][hexColor] = create(Color.magenta); + } + } + + return (T)colors[type][hexColor]; + } + } +} diff --git a/Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.Placeholder.ColorHex.cs.meta b/Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.Placeholder.ColorHex.cs.meta new file mode 100644 index 0000000..531a07a --- /dev/null +++ b/Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.Placeholder.ColorHex.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1dfd6496938bff140b68e9420e44a0ed +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.Placeholder.cs b/Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.Placeholder.cs new file mode 100644 index 0000000..70b88a0 --- /dev/null +++ b/Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.Placeholder.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Concurrent; +using System.Linq; +using UnityEngine; + +namespace Extensions.Unity.ImageLoader +{ + public static partial class FutureEx + { + const int PlaceholderSolidColorTextureSize = 2; + static ConcurrentDictionary> colors = new ConcurrentDictionary>(); + + public static Texture2D CreateFillTexture(this Color color, int width = PlaceholderSolidColorTextureSize, int height = PlaceholderSolidColorTextureSize) + { + var texture = new Texture2D(width, height); + texture.SetPixels(Enumerable.Repeat(color, width * height).ToArray()); + texture.Apply(); + return texture; + } + + /// + /// Set a placeholder in all conditions for this Future instance + /// + /// new placeholder + /// Returns the Future instance + public static IFuture SetPlaceholder(this IFuture future, T placeholder) => future.SetPlaceholder(placeholder, + PlaceholderTrigger.LoadingFromDiskCache, + PlaceholderTrigger.LoadingFromSource, + PlaceholderTrigger.FailedToLoad, + PlaceholderTrigger.Canceled); + } +} diff --git a/Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.Placeholder.cs.meta b/Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.Placeholder.cs.meta new file mode 100644 index 0000000..ca1d310 --- /dev/null +++ b/Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.Placeholder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4aeccdcda9945a64fa430418911813c5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_PackageRoot/Runtime/Future/Future.Register.cs b/Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.Register.cs similarity index 100% rename from Assets/_PackageRoot/Runtime/Future/Future.Register.cs rename to Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.Register.cs diff --git a/Assets/_PackageRoot/Runtime/Future/Future.Register.cs.meta b/Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.Register.cs.meta similarity index 100% rename from Assets/_PackageRoot/Runtime/Future/Future.Register.cs.meta rename to Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.Register.cs.meta diff --git a/Assets/_PackageRoot/Runtime/Future/Future.Timeout.cs b/Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.Timeout.cs similarity index 100% rename from Assets/_PackageRoot/Runtime/Future/Future.Timeout.cs rename to Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.Timeout.cs diff --git a/Assets/_PackageRoot/Runtime/Future/Future.Timeout.cs.meta b/Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.Timeout.cs.meta similarity index 100% rename from Assets/_PackageRoot/Runtime/Future/Future.Timeout.cs.meta rename to Assets/_PackageRoot/Runtime/Future/Extensions/FutureEx.Timeout.cs.meta diff --git a/Assets/_PackageRoot/Runtime/Future/Future.API.cs b/Assets/_PackageRoot/Runtime/Future/Future.API.cs index 6c4eb11..e6e82f2 100644 --- a/Assets/_PackageRoot/Runtime/Future/Future.API.cs +++ b/Assets/_PackageRoot/Runtime/Future/Future.API.cs @@ -9,15 +9,51 @@ namespace Extensions.Unity.ImageLoader public partial class Future : IDisposable { /// - /// When the image is loaded successfully from any source + /// When the it is time to set data into any consumer. Such as setting placeholder or final loaded subject + /// + /// Consumer (setter) function + /// If true, clear all existed consumer and replace them with this new one + /// Returns the Future instance + public IFuture Consume(Action consumer, bool replace = false) + { + if (IsLoaded) + { + Safe.Run(consumer, value, LogLevel); + return this; + } + else if (Status == FutureStatus.LoadingFromDiskCache || Status == FutureStatus.LoadingFromSource) + { + lock (placeholders) + if (placeholders.TryGetValue(Status, out var placeholder)) + Safe.Run(consumer, placeholder, LogLevel); + } + else if (Status == FutureStatus.FailedToLoad || Status == FutureStatus.Canceled) + { + lock (placeholders) + if (placeholders.TryGetValue(Status, out var placeholder)) + Safe.Run(consumer, placeholder, LogLevel); + return this; + } + + lock (consumers) + { + if (replace) + consumers.Clear(); + consumers.Add(consumer); + } + return this; + } + + /// + /// When is loaded successfully from any source /// /// action to execute on the event /// Returns the Future instance - public IFuture Then(Action action) + public IFuture Loaded(Action action) { if (IsLoaded) { - action(value); + Safe.Run(action, value, LogLevel); return this; } OnLoaded += action; @@ -33,7 +69,7 @@ public IFuture Failed(Action action) { if (Status == FutureStatus.FailedToLoad) { - action(exception); + Safe.Run(action, exception, LogLevel); return this; } OnFailedToLoad += action; @@ -207,6 +243,7 @@ public virtual void Cancel() if (Safe.RunCancel(cts, LogLevel)) { Safe.Run(OnCanceled, LogLevel); + ActivatePlaceholder(Status); Safe.Run(OnCompleted, IsLoaded, LogLevel); } Clear(); @@ -225,7 +262,7 @@ public IFuture> AsReference(DebugLevel logLevel = DebugLevel.Trace) LoadedFromMemoryCache(obj => { if (weakReference.TryGetTarget(out var future)) - future.Loaded(new Reference(url, obj), FutureLoadedFrom.MemoryCache); + future.SetLoaded(new Reference(url, obj), FutureLoadedFrom.MemoryCache); }); LoadingFromDiskCache(() => { @@ -235,7 +272,7 @@ public IFuture> AsReference(DebugLevel logLevel = DebugLevel.Trace) LoadedFromDiskCache(obj => { if (weakReference.TryGetTarget(out var future)) - future.Loaded(new Reference(url, obj), FutureLoadedFrom.DiskCache); + future.SetLoaded(new Reference(url, obj), FutureLoadedFrom.DiskCache); }); LoadingFromSource(() => { @@ -245,7 +282,7 @@ public IFuture> AsReference(DebugLevel logLevel = DebugLevel.Trace) LoadedFromSource(obj => { if (weakReference.TryGetTarget(out var future)) - future.Loaded(new Reference(url, obj), FutureLoadedFrom.Source); + future.SetLoaded(new Reference(url, obj), FutureLoadedFrom.Source); }); Failed(e => { @@ -367,7 +404,7 @@ public UniTask AsUniTask() var taskCompletionSource = new UniTaskCompletionSource(); - Then(value => taskCompletionSource.TrySetResult(value)); + Loaded(value => taskCompletionSource.TrySetResult(value)); Failed(exception => taskCompletionSource.TrySetException(exception)); Canceled(() => taskCompletionSource.TrySetCanceled()); @@ -380,7 +417,7 @@ public Task AsTask() var taskCompletionSource = new TaskCompletionSource(); - Then(taskCompletionSource.SetResult); + Loaded(taskCompletionSource.SetResult); Failed(taskCompletionSource.SetException); Canceled(taskCompletionSource.SetCanceled); diff --git a/Assets/_PackageRoot/Runtime/Future/Future.Empty.cs b/Assets/_PackageRoot/Runtime/Future/Future.Empty.cs index 1337181..1ff5104 100644 --- a/Assets/_PackageRoot/Runtime/Future/Future.Empty.cs +++ b/Assets/_PackageRoot/Runtime/Future/Future.Empty.cs @@ -20,7 +20,7 @@ public static IFuture EmptyLoading(string url = null, FutureLoadingFrom from public static IFuture EmptyLoaded(string url = null, FutureLoadedFrom from = FutureLoadedFrom.Source, T value = default) { var future = new FutureEmpty(url).SetLogLevel(DebugLevel.None); - ((IFutureInternal)future).Loaded(value, from); + ((IFutureInternal)future).SetLoaded(value, from); return future; } diff --git a/Assets/_PackageRoot/Runtime/Future/Future.Loading.cs b/Assets/_PackageRoot/Runtime/Future/Future.Loading.cs index a9ebb88..25a3b44 100644 --- a/Assets/_PackageRoot/Runtime/Future/Future.Loading.cs +++ b/Assets/_PackageRoot/Runtime/Future/Future.Loading.cs @@ -35,7 +35,7 @@ internal async UniTask InternalLoading(bool ignoreImageNotFoundError = false) var cachedObj = LoadFromMemoryCache(Url); if (cachedObj != null) { - ((IFutureInternal)this).Loaded(cachedObj, FutureLoadedFrom.MemoryCache); + ((IFutureInternal)this).SetLoaded(cachedObj, FutureLoadedFrom.MemoryCache); return; } } @@ -89,7 +89,7 @@ internal async UniTask InternalLoading(bool ignoreImageNotFoundError = false) RemoveLoading(); // LOADING REMOVED if (IsCancelled || Status == FutureStatus.FailedToLoad) return; - ((IFutureInternal)this).Loaded(loadedObj, FutureLoadedFrom.DiskCache); + ((IFutureInternal)this).SetLoaded(loadedObj, FutureLoadedFrom.DiskCache); return; } } @@ -214,7 +214,7 @@ internal async UniTask InternalLoading(bool ignoreImageNotFoundError = false) SaveToMemoryCache(downloadedObj, replace: true); RemoveLoading(); // LOADING REMOVED - ((IFutureInternal)this).Loaded(downloadedObj, FutureLoadedFrom.Source); + ((IFutureInternal)this).SetLoaded(downloadedObj, FutureLoadedFrom.Source); } protected virtual bool RegisterLoading(out Future anotherLoadingFuture) diff --git a/Assets/_PackageRoot/Runtime/Future/Future.Placeholder.cs b/Assets/_PackageRoot/Runtime/Future/Future.Placeholder.cs index 17a6e8a..daf2b5a 100644 --- a/Assets/_PackageRoot/Runtime/Future/Future.Placeholder.cs +++ b/Assets/_PackageRoot/Runtime/Future/Future.Placeholder.cs @@ -1,46 +1,50 @@ -using System; -using System.Linq; -using System.Threading; -using UnityEngine; +using UnityEngine; namespace Extensions.Unity.ImageLoader { public partial class Future { /// - /// Create and return empty Future instance with loading status + /// Set a placeholder in a specific condition for this Future instance /// - // public Future SetPlaceholder(Texture placeholder, params FutureLoadingFrom[] from) - // { - // if (cleared || IsCancelled) - // { - // if (LogLevel.IsActive(DebugLevel.Error)) - // Debug.Log($"[ImageLoader] Future[id={Id}] SetPlaceholder: is impossible because the future is cleared or canceled\n{Url}"); - // return this; - // } - // if (IsInProgress) - // { - // // TODO: set placeholder + /// new placeholder + /// triggers for setting the placeholder + /// Returns the Future instance + public IFuture SetPlaceholder(T placeholder, params PlaceholderTrigger[] triggers) + { + if (cleared || IsCancelled) + { + if (LogLevel.IsActive(DebugLevel.Error)) + Debug.Log($"[ImageLoader] Future[id={Id}] SetPlaceholder: is impossible because the future is cleared or canceled\n{Url}"); + return this; + } - // return this; - // } + if (triggers == null || triggers.Length == 0) + { + if (LogLevel.IsActive(DebugLevel.Error)) + Debug.LogError($"[ImageLoader] Future[id={Id}] SetPlaceholder: triggers are not specified\n{Url}"); + return this; + } - // if (from.Any(x => x == FutureLoadingFrom.DiskCache)) - // { - // LoadingFromDiskCache(() => - // { - // // TODO: set placeholder - // }); - // } - // if (from.Any(x => x == FutureLoadingFrom.Source)) - // { - // LoadingFromSource(() => - // { - // // TODO: set placeholder - // }); - // } + if (LogLevel.IsActive(DebugLevel.Trace)) + Debug.Log($"[ImageLoader] Future[id={Id}] SetPlaceholder\n{Url}"); - // return this; - // } + foreach (var trigger in triggers) + { + if (trigger.IsEqual(Status)) + { + lock (consumers) + { + foreach (var setter in consumers) + Safe.Run(setter, placeholder, LogLevel); + } + // continue; + } + lock (placeholders) + placeholders[trigger.AsFutureStatus()] = placeholder; + } + + return this; + } } } diff --git a/Assets/_PackageRoot/Runtime/Future/Future.Setter.cs b/Assets/_PackageRoot/Runtime/Future/Future.Setter.cs deleted file mode 100644 index 96fa44f..0000000 --- a/Assets/_PackageRoot/Runtime/Future/Future.Setter.cs +++ /dev/null @@ -1,67 +0,0 @@ -using Cysharp.Threading.Tasks; -using System; -using UnityEngine; -using UnityEngine.EventSystems; -using UnityEngine.UI; - -namespace Extensions.Unity.ImageLoader -{ - public static partial class FutureEx - { - // /// - // /// Set image into array of the generic target instances - // /// - // /// Setter function that gets Target instance and Sprite instance, it should set the Sprite value into Target instance - // /// Array of generic Target instances - // /// Returns async Future - // public static IFuture Setter(this IFuture future, Action setter, params T[] targets) - // => future.Then(sprite => - // { - // UniTask.Post(() => // using only MainThread to set any images to any targets - // { - // foreach (var target in targets) - // { - // if (ReferenceEquals(target, null) || target == null || (target is UIBehaviour uiBehaviour && IsDestroyed(uiBehaviour))) - // { - // if (future.LogLevel.IsActive(DebugLevel.Warning)) - // Debug.LogWarning($"[ImageLoader] Future[id={future.Id}] The target is null. Can't set image into it. Skipping."); - // continue; - // } - // Safe.Run(setter, target, sprite, future.LogLevel); - // } - // }); - // }); - - // /// - // /// Set image into array of Images - // /// - // /// Array of Images - // /// Returns async Future - // public static IFuture Setter(this IFuture future, params Image[] images) - // => future.Setter((target, sprite) => target.sprite = sprite, images); - - // /// - // /// Set image into array of RawImages - // /// - // /// Array of RawImages - // /// Returns async Future - // public static IFuture Setter(this IFuture future, params RawImage[] rawImages) - // => future.Setter((target, sprite) => target.texture = sprite?.texture, rawImages); - - // /// - // /// Set image into array of SpriteRenderers - // /// - // /// Array of SpriteRenderers - // /// Returns async Future - // public static IFuture Setter(this IFuture future, params SpriteRenderer[] spriteRenderers) - // => future.Setter((target, sprite) => target.sprite = sprite, spriteRenderers); - - // /// - // /// Set image into array of Materials - // /// - // /// Array of Materials - // /// Returns async Future - // public static IFuture Setter(this IFuture future, string propertyName = "_MainTex", params Material[] materials) - // => future.Setter((target, sprite) => target.SetTexture(propertyName, sprite?.texture), materials); - } -} diff --git a/Assets/_PackageRoot/Runtime/Future/Future.cs b/Assets/_PackageRoot/Runtime/Future/Future.cs index 20b3c35..5f2e0a1 100644 --- a/Assets/_PackageRoot/Runtime/Future/Future.cs +++ b/Assets/_PackageRoot/Runtime/Future/Future.cs @@ -35,8 +35,9 @@ public partial class Future : IFuture, IFuture, IFutureInternal, IDispo public uint Id { get; } = FutureMetadata.idCounter++; + protected readonly List> consumers = new List>(); + protected readonly Dictionary placeholders = new Dictionary(); protected TimeSpan timeout; - protected List> setters = new List>(); protected bool cleared = false; protected bool disposeValue = false; protected T value = default; @@ -100,11 +101,11 @@ public IFuture PassEvents(IFutureInternal to, bool passCancelled = true) if (LogLevel.IsActive(DebugLevel.Trace)) Debug.Log($"[ImageLoader] Future[id={Id}] -> Future[id={to.Id}] Subscribe on events\n{Url}"); - LoadedFromMemoryCache((v) => to.Loaded(v, FutureLoadedFrom.MemoryCache)); + LoadedFromMemoryCache((v) => to.SetLoaded(v, FutureLoadedFrom.MemoryCache)); LoadingFromDiskCache(( ) => to.Loading(FutureLoadingFrom.DiskCache)); - LoadedFromDiskCache((v) => to.Loaded(v, FutureLoadedFrom.DiskCache)); + LoadedFromDiskCache((v) => to.SetLoaded(v, FutureLoadedFrom.DiskCache)); LoadingFromSource(( ) => to.Loading(FutureLoadingFrom.Source)); - LoadedFromSource((v) => to.Loaded(v, FutureLoadedFrom.Source)); + LoadedFromSource((v) => to.SetLoaded(v, FutureLoadedFrom.Source)); Failed(to.FailToLoad); if (passCancelled) @@ -120,11 +121,11 @@ public IFuture PassEvents(IFutureInternal to, Func convert, bo if (LogLevel.IsActive(DebugLevel.Log)) Debug.Log($"[ImageLoader] Future[id={Id}] -> Future[id={to.Id}] Subscribe on events (${typeof(T).Name} -> ${typeof(T2).Name})\n{Url}"); - LoadedFromMemoryCache((v) => to.Loaded(convert(v), FutureLoadedFrom.MemoryCache)); + LoadedFromMemoryCache((v) => to.SetLoaded(convert(v), FutureLoadedFrom.MemoryCache)); LoadingFromDiskCache(( ) => to.Loading(FutureLoadingFrom.DiskCache)); - LoadedFromDiskCache((v) => to.Loaded(convert(v), FutureLoadedFrom.DiskCache)); + LoadedFromDiskCache((v) => to.SetLoaded(convert(v), FutureLoadedFrom.DiskCache)); LoadingFromSource(( ) => to.Loading(FutureLoadingFrom.Source)); - LoadedFromSource((v) => to.Loaded(convert(v), FutureLoadedFrom.Source)); + LoadedFromSource((v) => to.SetLoaded(convert(v), FutureLoadedFrom.Source)); Failed(to.FailToLoad); if (passCancelled) @@ -135,35 +136,6 @@ public IFuture PassEvents(IFutureInternal to, Func convert, bo return this; } - internal void Placeholder(Sprite placeholder) - { - if (cleared || IsCancelled) return; - - if (LogLevel.IsActive(DebugLevel.Log)) - Debug.Log($"[ImageLoader] Future[id={Id}] Placeholder\n{Url}"); - - // UniTask.ReturnToMainThread() - // if (UnityMainThreadDispatcher.IsMainThread) - // { - // OnLoadedFromMemoryCache?.Invoke(placeholder); - // OnLoadedFromDiskCache?.Invoke(placeholder); - // OnLoadedFromSource?.Invoke(placeholder); - // OnLoaded?.Invoke(placeholder); - // OnCompleted?.Invoke(true); - // Clear(); - // } - // else - // { - // UniTask.SwitchToMainThread(); - // Placeholder(placeholder); - // } - - OnLoadedFromMemoryCache += (v) => { }; - OnLoadingFromDiskCache += ( ) => { }; - OnLoadedFromDiskCache += (v) => { }; - OnLoadingFromSource += ( ) => { }; - OnLoadedFromSource += (v) => { }; - } void IFutureInternal.Loading(FutureLoadingFrom loadingFrom) { if (cleared || IsCancelled) return; @@ -188,8 +160,9 @@ void IFutureInternal.Loading(FutureLoadingFrom loadingFrom) this.loadingFrom = loadingFrom; Safe.Run(onLoadingEvent, LogLevel); + ActivatePlaceholder(Status); } - void IFutureInternal.Loaded(T value, FutureLoadedFrom loadedFrom) + void IFutureInternal.SetLoaded(T value, FutureLoadedFrom loadedFrom) { if (cleared || IsCancelled) return; @@ -219,6 +192,7 @@ void IFutureInternal.Loaded(T value, FutureLoadedFrom loadedFrom) Safe.Run(onLoadedEvent, this.value, LogLevel); Safe.Run(OnLoaded, this.value, LogLevel); + FeedConsumers(this.value); Safe.Run(OnCompleted, true, LogLevel); Clear(); } @@ -233,10 +207,31 @@ void IFutureInternal.FailToLoad(Exception exception) Debug.LogError(exception.Message); Safe.Run(OnFailedToLoad, exception, LogLevel); + ActivatePlaceholder(Status); Safe.Run(OnCompleted, false, LogLevel); Clear(); } void IFutureInternal.SetTimeout(TimeSpan duration) => timeout = duration; + void ActivatePlaceholder(FutureStatus status) + { + lock (placeholders) + { + if (placeholders.TryGetValue(status, out var placeholder)) + FeedConsumers(placeholder); + } + } + void FeedConsumers(T value) + { + lock (consumers) + { + foreach (var setter in consumers) + { + if (LogLevel.IsActive(DebugLevel.Trace)) + Debug.Log($"[ImageLoader] Future[id={Id}] Feed consumer\n{Url}"); + Safe.Run(setter, value, LogLevel); + } + } + } protected virtual void Clear() { @@ -244,6 +239,8 @@ protected virtual void Clear() Debug.Log($"[ImageLoader] Future[id={Id}] Cleared\n{Url}"); cleared = true; + // lock (placeholders) + // placeholders.Clear(); OnLoadedFromMemoryCache = null; OnLoadingFromDiskCache = null; OnLoadedFromDiskCache = null; diff --git a/Assets/_PackageRoot/Runtime/Future/FutureEnums.cs b/Assets/_PackageRoot/Runtime/Future/FutureEnums.cs deleted file mode 100644 index bcdc55a..0000000 --- a/Assets/_PackageRoot/Runtime/Future/FutureEnums.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Extensions.Unity.ImageLoader -{ - public enum FutureStatus - { - Initialized, - LoadedFromMemoryCache, - LoadingFromDiskCache, - LoadedFromDiskCache, - LoadingFromSource, - LoadedFromSource, - FailedToLoad, - Canceled, - Disposed - } - public enum FutureLoadedFrom - { - MemoryCache, DiskCache, Source, FailedToLoad - } - public enum FutureLoadingFrom - { - DiskCache, Source - } -} diff --git a/Assets/_PackageRoot/Runtime/Future/IFuture.cs b/Assets/_PackageRoot/Runtime/Future/IFuture.cs index a5b60ab..e1e74f3 100644 --- a/Assets/_PackageRoot/Runtime/Future/IFuture.cs +++ b/Assets/_PackageRoot/Runtime/Future/IFuture.cs @@ -25,7 +25,7 @@ public partial interface IFuture : IFuture, IDisposable T Value { get; } DebugLevel LogLevel { get; } UniTask StartLoading(bool ignoreImageNotFoundError = false); - IFuture Then(Action onCompleted); + IFuture Loaded(Action onCompleted); IFuture Failed(Action action); IFuture Completed(Action action); IFuture LoadedFromMemoryCache(Action action); @@ -33,11 +33,15 @@ public partial interface IFuture : IFuture, IDisposable IFuture LoadedFromDiskCache(Action action); IFuture LoadingFromSource(Action action, bool ignoreWhenLoaded = false); IFuture LoadedFromSource(Action action); + + IFuture Consume(Action setter, bool replace = false); + IFuture Canceled(Action action); IFuture SetUseDiskCache(bool value = true); IFuture SetUseMemoryCache(bool value = true); IFuture SetLogLevel(DebugLevel value); + IFuture SetPlaceholder(T placeholder, params PlaceholderTrigger[] triggers); IFuture PassEvents(IFutureInternal to, bool passCancelled = true); IFuture PassEvents(IFutureInternal to, Func convert, bool passCancelled = true); @@ -55,7 +59,7 @@ public interface IFutureInternal : IFuture UnityWebRequest WebRequest { get; } void FailToLoad(Exception exception); void Loading(FutureLoadingFrom loadingFrom); - void Loaded(T value, FutureLoadedFrom loadedFrom); + void SetLoaded(T value, FutureLoadedFrom loadedFrom); void SetTimeout(TimeSpan duration); } } \ No newline at end of file diff --git a/Assets/_PackageRoot/Runtime/Future/Utils.meta b/Assets/_PackageRoot/Runtime/Future/Utils.meta new file mode 100644 index 0000000..47bbb78 --- /dev/null +++ b/Assets/_PackageRoot/Runtime/Future/Utils.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c15aa2758e1abd746beb83376e6f3a15 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_PackageRoot/Runtime/Future/FutureAwaiter.cs b/Assets/_PackageRoot/Runtime/Future/Utils/FutureAwaiter.cs similarity index 100% rename from Assets/_PackageRoot/Runtime/Future/FutureAwaiter.cs rename to Assets/_PackageRoot/Runtime/Future/Utils/FutureAwaiter.cs diff --git a/Assets/_PackageRoot/Runtime/Future/FutureAwaiter.cs.meta b/Assets/_PackageRoot/Runtime/Future/Utils/FutureAwaiter.cs.meta similarity index 100% rename from Assets/_PackageRoot/Runtime/Future/FutureAwaiter.cs.meta rename to Assets/_PackageRoot/Runtime/Future/Utils/FutureAwaiter.cs.meta diff --git a/Assets/_PackageRoot/Runtime/Future/Utils/FutureEnums.cs b/Assets/_PackageRoot/Runtime/Future/Utils/FutureEnums.cs new file mode 100644 index 0000000..02e2ce7 --- /dev/null +++ b/Assets/_PackageRoot/Runtime/Future/Utils/FutureEnums.cs @@ -0,0 +1,34 @@ +namespace Extensions.Unity.ImageLoader +{ + public enum FutureStatus + { + Initialized = 0, + LoadedFromMemoryCache = 1, + LoadingFromDiskCache = 2, + LoadedFromDiskCache = 3, + LoadingFromSource = 4, + LoadedFromSource = 5, + FailedToLoad = 6, + Canceled = 7, + Disposed = 8 + } + public enum FutureLoadedFrom + { + MemoryCache = 1, + DiskCache = 3, + Source = 5, + FailedToLoad = 6 + } + public enum FutureLoadingFrom + { + DiskCache = 2, + Source = 4 + } + public enum PlaceholderTrigger + { + LoadingFromDiskCache = 2, + LoadingFromSource = 4, + FailedToLoad = 6, + Canceled = 7, + } +} diff --git a/Assets/_PackageRoot/Runtime/Future/FutureEnums.cs.meta b/Assets/_PackageRoot/Runtime/Future/Utils/FutureEnums.cs.meta similarity index 100% rename from Assets/_PackageRoot/Runtime/Future/FutureEnums.cs.meta rename to Assets/_PackageRoot/Runtime/Future/Utils/FutureEnums.cs.meta diff --git a/Assets/_PackageRoot/Runtime/Future/Utils/FutureEnumsEx.cs b/Assets/_PackageRoot/Runtime/Future/Utils/FutureEnumsEx.cs new file mode 100644 index 0000000..9c93e7f --- /dev/null +++ b/Assets/_PackageRoot/Runtime/Future/Utils/FutureEnumsEx.cs @@ -0,0 +1,38 @@ +namespace Extensions.Unity.ImageLoader +{ + public static class FutureEnumEx + { + public static FutureStatus AsFutureStatus(this FutureLoadedFrom value) => (FutureStatus)value; + public static FutureStatus AsFutureStatus(this FutureLoadingFrom value) => (FutureStatus)value; + public static FutureStatus AsFutureStatus(this PlaceholderTrigger value) => (FutureStatus)value; + + public static FutureLoadedFrom AsFutureLoadedFrom(this FutureStatus value) => (FutureLoadedFrom)value; + public static FutureLoadedFrom AsFutureLoadedFrom(this FutureLoadingFrom value) => (FutureLoadedFrom)value; + public static FutureLoadedFrom AsFutureLoadedFrom(this PlaceholderTrigger value) => (FutureLoadedFrom)value; + + public static FutureLoadingFrom AsFutureLoadingFrom(this FutureStatus value) => (FutureLoadingFrom)value; + public static FutureLoadingFrom AsFutureLoadingFrom(this FutureLoadedFrom value) => (FutureLoadingFrom)value; + public static FutureLoadingFrom AsFutureLoadingFrom(this PlaceholderTrigger value) => (FutureLoadingFrom)value; + + public static PlaceholderTrigger AsPlaceholderTrigger(this FutureStatus value) => (PlaceholderTrigger)value; + public static PlaceholderTrigger AsPlaceholderTrigger(this FutureLoadedFrom value) => (PlaceholderTrigger)value; + public static PlaceholderTrigger AsPlaceholderTrigger(this FutureLoadingFrom value) => (PlaceholderTrigger)value; + + + public static bool IsEqual(this FutureStatus value1, FutureLoadedFrom value2) => (int)value1 == (int)value2; + public static bool IsEqual(this FutureStatus value1, FutureLoadingFrom value2) => (int)value1 == (int)value2; + public static bool IsEqual(this FutureStatus value1, PlaceholderTrigger value2) => (int)value1 == (int)value2; + + public static bool IsEqual(this FutureLoadedFrom value1, FutureStatus value2) => (int)value1 == (int)value2; + public static bool IsEqual(this FutureLoadedFrom value1, FutureLoadingFrom value2) => (int)value1 == (int)value2; + public static bool IsEqual(this FutureLoadedFrom value1, PlaceholderTrigger value2) => (int)value1 == (int)value2; + + public static bool IsEqual(this FutureLoadingFrom value1, FutureStatus value2) => (int)value1 == (int)value2; + public static bool IsEqual(this FutureLoadingFrom value1, FutureLoadedFrom value2) => (int)value1 == (int)value2; + public static bool IsEqual(this FutureLoadingFrom value1, PlaceholderTrigger value2) => (int)value1 == (int)value2; + + public static bool IsEqual(this PlaceholderTrigger value1, FutureStatus value2) => (int)value1 == (int)value2; + public static bool IsEqual(this PlaceholderTrigger value1, FutureLoadedFrom value2) => (int)value1 == (int)value2; + public static bool IsEqual(this PlaceholderTrigger value1, FutureLoadingFrom value2) => (int)value1 == (int)value2; + } +} diff --git a/Assets/_PackageRoot/Runtime/Future/Utils/FutureEnumsEx.cs.meta b/Assets/_PackageRoot/Runtime/Future/Utils/FutureEnumsEx.cs.meta new file mode 100644 index 0000000..93e496f --- /dev/null +++ b/Assets/_PackageRoot/Runtime/Future/Utils/FutureEnumsEx.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 64fc0c3e418e4d14b9b9d5ee7a06d30a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_PackageRoot/Runtime/Utils/ColorExtensions.cs b/Assets/_PackageRoot/Runtime/Utils/ColorExtensions.cs new file mode 100644 index 0000000..e601c8d --- /dev/null +++ b/Assets/_PackageRoot/Runtime/Utils/ColorExtensions.cs @@ -0,0 +1,48 @@ +using UnityEngine; + +namespace Extensions.Unity.ImageLoader +{ + public static class ColorExtensions + { + public static string ToHexRGB(this Color color, bool hexSymbol = true) => hexSymbol + ? $"#{ColorUtility.ToHtmlStringRGB(color)}" + : ColorUtility.ToHtmlStringRGB(color); + + public static string ToHexRGBA(this Color color, bool hexSymbol = true) => hexSymbol + ? $"#{ColorUtility.ToHtmlStringRGBA(color)}" + : ColorUtility.ToHtmlStringRGBA(color); + + public static Color SetR(this Color color, float r) + { + color.r = r; + return color; + } + public static Color SetG(this Color color, float g) + { + color.g = g; + return color; + } + public static Color SetB(this Color color, float b) + { + color.b = b; + return color; + } + public static Color SetA(this Color color, float a) + { + color.a = a; + return color; + } + public static bool HexToColor(this string hex, out Color color) + => ColorUtility.TryParseHtmlString(hex, out color); + + public static Color HexToColor(this string hex) + { + if (!ColorUtility.TryParseHtmlString(hex, out var color)) + { + if (ImageLoader.settings.debugLevel.IsActive(DebugLevel.Error)) + Debug.LogError($"[ImageLoader] Invalid hex color: {hex}"); + } + return color; + } + } +} \ No newline at end of file diff --git a/Assets/_PackageRoot/Runtime/Utils/ColorExtensions.cs.meta b/Assets/_PackageRoot/Runtime/Utils/ColorExtensions.cs.meta new file mode 100644 index 0000000..96a3856 --- /dev/null +++ b/Assets/_PackageRoot/Runtime/Utils/ColorExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f7886578b617d9042a504ea6ece6445b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_PackageRoot/Samples/SampleAwaitAndForget.cs b/Assets/_PackageRoot/Samples/SampleAwaitAndForget.cs index 774f8b1..af18d01 100644 --- a/Assets/_PackageRoot/Samples/SampleAwaitAndForget.cs +++ b/Assets/_PackageRoot/Samples/SampleAwaitAndForget.cs @@ -13,11 +13,11 @@ async void Start() await ImageLoader.LoadSprite(imageURL); // Load image, set image and wait - await ImageLoader.LoadSprite(imageURL).ThenSet(image); + await ImageLoader.LoadSprite(imageURL).Consume(image); // Skip waiting for completion. // To do that we can simply remove 'await' from the start. // To avoid compilation warning need to add '.Forget()'. - ImageLoader.LoadSprite(imageURL).ThenSet(image).Forget(); + ImageLoader.LoadSprite(imageURL).Consume(image).Forget(); } } \ No newline at end of file diff --git a/Assets/_PackageRoot/Samples/SampleCancellation.cs b/Assets/_PackageRoot/Samples/SampleCancellation.cs index eb8ff0b..78e245b 100644 --- a/Assets/_PackageRoot/Samples/SampleCancellation.cs +++ b/Assets/_PackageRoot/Samples/SampleCancellation.cs @@ -12,19 +12,19 @@ public class SampleCancellation : MonoBehaviour void Start() { ImageLoader.LoadSprite(imageURL) // load sprite - .ThenSet(image) // if success set sprite into image + .Consume(image) // if success set sprite into image .CancelOnDestroy(this) // cancel OnDestroy event of current gameObject .Forget(); ImageLoader.LoadSprite(imageURL) // load sprite - .ThenSet(image) // if success set sprite into image + .Consume(image) // if success set sprite into image .Failed(exception => Debug.LogException(exception)) // if fail print exception .CancelOnDestroy(this) // cancel OnDestroy event of current gameObject .Forget(); ImageLoader.LoadSprite(imageURL) // load sprite - .ThenSet(image) // if success set sprite into image - .Then(sprite => image.gameObject.SetActive(true)) // if success activate gameObject + .Consume(image) // if success set sprite into image + .Loaded(sprite => image.gameObject.SetActive(true)) // if success activate gameObject .Failed(exception => image.gameObject.SetActive(false)) // if fail deactivate gameObject .Canceled(() => Debug.Log("ImageLoading canceled")) // if cancelled .CancelOnDisable(this) // cancel OnDisable event of current gameObject @@ -34,7 +34,7 @@ void Start() void DestroyWithMonoBehaviour() { ImageLoader.LoadSprite(imageURL) - .ThenSet(image) + .Consume(image) .CancelOnEnable(this) // cancel on OnEnable event of current MonoBehaviour .CancelOnDisable(this) // cancel on OnDisable event of current MonoBehaviour .CancelOnDestroy(this); // cancel on OnDestroy event of current MonoBehaviour @@ -42,7 +42,7 @@ void DestroyWithMonoBehaviour() void SimpleCancellation() { - var future = ImageLoader.LoadSprite(imageURL).ThenSet(image); + var future = ImageLoader.LoadSprite(imageURL).Consume(image); future.Cancel(); } @@ -52,7 +52,7 @@ void CancellationTokenSample1() // loading with attached cancellation token ImageLoader.LoadSprite(imageURL, cancellationToken: cancellationTokenSource.Token) - .ThenSet(image) + .Consume(image) .Forget(); cancellationTokenSource.Cancel(); // canceling @@ -63,7 +63,7 @@ void CancellationTokenSample2() var cancellationTokenSource = new CancellationTokenSource(); ImageLoader.LoadSprite(imageURL) - .ThenSet(image) + .Consume(image) .Register(cancellationTokenSource.Token) // registering cancellation token .Forget(); @@ -72,7 +72,7 @@ void CancellationTokenSample2() void DisposeSample() { - using (var future = ImageLoader.LoadSprite(imageURL).ThenSet(image)) + using (var future = ImageLoader.LoadSprite(imageURL).Consume(image)) { // future would be canceled and disposed outside of the brackets } diff --git a/Assets/_PackageRoot/Samples/SampleErrorHandle.cs b/Assets/_PackageRoot/Samples/SampleErrorHandle.cs index e759f5a..3aaac28 100644 --- a/Assets/_PackageRoot/Samples/SampleErrorHandle.cs +++ b/Assets/_PackageRoot/Samples/SampleErrorHandle.cs @@ -10,13 +10,13 @@ public class SampleErrorHandle : MonoBehaviour void Start() { ImageLoader.LoadSprite(imageURL) // Attempt to load a sprite - .ThenSet(image) // If successful, set the sprite to the Image component + .Consume(image) // If successful, set the sprite to the Image component .Failed(exception => Debug.LogException(exception)) // If an error occurs, log the exception .Forget(); // Forget the task to avoid compilation warning ImageLoader.LoadSprite(imageURL) // Attempt to load a sprite - .ThenSet(image) // If successful, set the sprite to the Image component - .Then(sprite => image.gameObject.SetActive(true)) // If successful, activate the GameObject + .Consume(image) // If successful, set the sprite to the Image component + .Loaded(sprite => image.gameObject.SetActive(true)) // If successful, activate the GameObject .Failed(exception => image.gameObject.SetActive(false)) // If an error occurs, deactivate the GameObject .Forget(); // Forget the task to avoid compilation warning } diff --git a/Assets/_PackageRoot/Samples/SampleLifecycle.cs b/Assets/_PackageRoot/Samples/SampleLifecycle.cs index 18a6adc..c31b4c2 100644 --- a/Assets/_PackageRoot/Samples/SampleLifecycle.cs +++ b/Assets/_PackageRoot/Samples/SampleLifecycle.cs @@ -20,6 +20,17 @@ void Start() .LoadedFromSource (sprite => Debug.Log("Loaded from source")) // on loaded from source │ // ────────────────────────────────────────────────────────────────────────────────────────────────────┘ + // ┌──────────────────────────┬───────────────────────────────────────────┐ + // │ Success lifecycle events │ │ + // └──────────────────────────┘ │ + .Loaded(sprite => Debug.Log("Loaded")) // on successfully loaded │ + // ┌────────────────────────────────────────────────────────┤ + // │ Set/Consume sprite [placeholder, successfully loaded] │ + // └────────────────────────────────────────────────────────┤ + .Consume(sprite => Debug.Log("Consumed")) // │ + .Consume(image) // │ + // ───────────────────────────────────────────────────────────────────────┘ + // ┌───────────────────────────┬──────────────────────────────────────────┐ // │ Negative lifecycle events │ │ // └───────────────────────────┘ │ @@ -27,13 +38,6 @@ void Start() .Failed(exception => Debug.LogException(exception)) // on failed to load │ // ───────────────────────────────────────────────────────────────────────┘ - // ┌──────────────────────────────────────┬──────────────────────────────┐ - // │ Successfully loaded lifecycle events │ │ - // └──────────────────────────────────────┘ │ - .Then(sprite => Debug.Log("Loaded")) // on loaded │ - .ThenSet(image) // on loaded set sprite into image │ - // ──────────────────────────────────────────────────────────────────────┘ - // ┌──────────────────────┬──────────────────────────────────────────────────────────────────────────┐ // │ The end of lifecycle │ │ // └──────────────────────┘ │ diff --git a/Assets/_PackageRoot/Samples/SampleLoadSpriteThenSetImage.cs b/Assets/_PackageRoot/Samples/SampleLoadSpriteConsumeImage.cs similarity index 89% rename from Assets/_PackageRoot/Samples/SampleLoadSpriteThenSetImage.cs rename to Assets/_PackageRoot/Samples/SampleLoadSpriteConsumeImage.cs index 96c4346..065b89e 100644 --- a/Assets/_PackageRoot/Samples/SampleLoadSpriteThenSetImage.cs +++ b/Assets/_PackageRoot/Samples/SampleLoadSpriteConsumeImage.cs @@ -13,6 +13,6 @@ async void Start() image.sprite = await ImageLoader.LoadSprite(imageURL); // Load a sprite from the web and set it directly to the Image component - await ImageLoader.LoadSprite(imageURL).ThenSet(image); + await ImageLoader.LoadSprite(imageURL).Consume(image); } } \ No newline at end of file diff --git a/Assets/_PackageRoot/Samples/SampleLoadSpriteThenSetImage.cs.meta b/Assets/_PackageRoot/Samples/SampleLoadSpriteConsumeImage.cs.meta similarity index 100% rename from Assets/_PackageRoot/Samples/SampleLoadSpriteThenSetImage.cs.meta rename to Assets/_PackageRoot/Samples/SampleLoadSpriteConsumeImage.cs.meta diff --git a/Assets/_PackageRoot/Samples/SampleLoadSpriteThenSetIntoMultipleImages.cs b/Assets/_PackageRoot/Samples/SampleLoadSpriteConsumeIntoMultipleImages.cs similarity index 88% rename from Assets/_PackageRoot/Samples/SampleLoadSpriteThenSetIntoMultipleImages.cs rename to Assets/_PackageRoot/Samples/SampleLoadSpriteConsumeIntoMultipleImages.cs index a27a7ec..74888ce 100644 --- a/Assets/_PackageRoot/Samples/SampleLoadSpriteThenSetIntoMultipleImages.cs +++ b/Assets/_PackageRoot/Samples/SampleLoadSpriteConsumeIntoMultipleImages.cs @@ -11,6 +11,6 @@ public class SampleLoadSpriteThenSetIntoMultipleImages : MonoBehaviour void Start() { // Loading with auto set to multiple images - ImageLoader.LoadSprite(imageURL).ThenSet(image1, image2).Forget(); + ImageLoader.LoadSprite(imageURL).Consume(image1, image2).Forget(); } } \ No newline at end of file diff --git a/Assets/_PackageRoot/Samples/SampleLoadSpriteThenSetIntoMultipleImages.cs.meta b/Assets/_PackageRoot/Samples/SampleLoadSpriteConsumeIntoMultipleImages.cs.meta similarity index 100% rename from Assets/_PackageRoot/Samples/SampleLoadSpriteThenSetIntoMultipleImages.cs.meta rename to Assets/_PackageRoot/Samples/SampleLoadSpriteConsumeIntoMultipleImages.cs.meta diff --git a/Assets/_PackageRoot/Samples/SampleLoadTextureThenSetMaterial.cs b/Assets/_PackageRoot/Samples/SampleLoadTextureConsumeMaterial.cs similarity index 89% rename from Assets/_PackageRoot/Samples/SampleLoadTextureThenSetMaterial.cs rename to Assets/_PackageRoot/Samples/SampleLoadTextureConsumeMaterial.cs index 9bcbe80..f62e9d8 100644 --- a/Assets/_PackageRoot/Samples/SampleLoadTextureThenSetMaterial.cs +++ b/Assets/_PackageRoot/Samples/SampleLoadTextureConsumeMaterial.cs @@ -12,6 +12,6 @@ async void Start() material.mainTexture = await ImageLoader.LoadTexture(imageURL); // Load a Texture2D from the web and set it directly to the Material - await ImageLoader.LoadTexture(imageURL).ThenSet(material); + await ImageLoader.LoadTexture(imageURL).Consume(material); } } \ No newline at end of file diff --git a/Assets/_PackageRoot/Samples/SampleLoadTextureThenSetMaterial.cs.meta b/Assets/_PackageRoot/Samples/SampleLoadTextureConsumeMaterial.cs.meta similarity index 100% rename from Assets/_PackageRoot/Samples/SampleLoadTextureThenSetMaterial.cs.meta rename to Assets/_PackageRoot/Samples/SampleLoadTextureConsumeMaterial.cs.meta diff --git a/Assets/_PackageRoot/Samples/SamplePlaceholder.cs b/Assets/_PackageRoot/Samples/SamplePlaceholder.cs new file mode 100644 index 0000000..131905a --- /dev/null +++ b/Assets/_PackageRoot/Samples/SamplePlaceholder.cs @@ -0,0 +1,27 @@ +using Extensions.Unity.ImageLoader; +using UnityEngine; +using UnityEngine.UI; + +public class SamplePlaceholder : MonoBehaviour +{ + [SerializeField] string imageURL; // URL of the image to be loaded + [SerializeField] Image image; // UI Image component to display the loaded sprite + [SerializeField] Sprite placeholderAny; + [SerializeField] Sprite placeholderLoadingFromSource; + [SerializeField] Color placeholderFailedToLoad = Color.red; + + void Start() + { + ImageLoader.LoadSprite(imageURL) + // set placeholder in all conditions + .SetPlaceholder(placeholderAny) + + // set placeholder in a specific conditions + .SetPlaceholder(placeholderLoadingFromSource, PlaceholderTrigger.LoadingFromSource) + .SetPlaceholder(placeholderFailedToLoad, PlaceholderTrigger.FailedToLoad) + + // set consumer + .Consume(image) + .Forget(); + } +} \ No newline at end of file diff --git a/Assets/_PackageRoot/Samples/SamplePlaceholder.cs.meta b/Assets/_PackageRoot/Samples/SamplePlaceholder.cs.meta new file mode 100644 index 0000000..8d4f3fe --- /dev/null +++ b/Assets/_PackageRoot/Samples/SamplePlaceholder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b1d60cfba4bd2d64ea9b2b3999e87a01 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_PackageRoot/Samples/SampleReferences.cs b/Assets/_PackageRoot/Samples/SampleReferences.cs index c40a902..d62af51 100644 --- a/Assets/_PackageRoot/Samples/SampleReferences.cs +++ b/Assets/_PackageRoot/Samples/SampleReferences.cs @@ -10,12 +10,12 @@ public class SampleReferences : MonoBehaviour void Start() { ImageLoader.LoadSpriteRef(imageURL) // load sprite using Reference - .ThenSet(image) // if success set sprite into image, also creates binding to `image` + .Consume(image) // if success set sprite into image, also creates binding to `image` .Forget(); ImageLoader.LoadSpriteRef(imageURL) // load sprite using Reference - .Then(reference => reference.DisposeOnDestroy(this)) - .Then(reference => + .Loaded(reference => reference.DisposeOnDestroy(this)) + .Loaded(reference => { var sprite = reference.Value; // use sprite diff --git a/Assets/_PackageRoot/Samples/SampleTimeout.cs b/Assets/_PackageRoot/Samples/SampleTimeout.cs index 353dd68..b1d54cf 100644 --- a/Assets/_PackageRoot/Samples/SampleTimeout.cs +++ b/Assets/_PackageRoot/Samples/SampleTimeout.cs @@ -11,7 +11,7 @@ public class SampleTimeout : MonoBehaviour void Start() { ImageLoader.LoadSprite(imageURL) // load sprite - .ThenSet(image) // if success set sprite into image + .Consume(image) // if success set sprite into image .Timeout(TimeSpan.FromSeconds(10)) // set timeout duration 10 seconds .Forget(); } diff --git a/Assets/_PackageRoot/Tests/Base.meta b/Assets/_PackageRoot/Tests/Base.meta new file mode 100644 index 0000000..879d123 --- /dev/null +++ b/Assets/_PackageRoot/Tests/Base.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a659c4cee9358f0459cc7b10e413a932 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_PackageRoot/Tests/Editor/Extensions.Unity.ImageLoader.Editor.Tests.asmdef b/Assets/_PackageRoot/Tests/Base/Extensions.Unity.ImageLoader.Tests.asmdef similarity index 76% rename from Assets/_PackageRoot/Tests/Editor/Extensions.Unity.ImageLoader.Editor.Tests.asmdef rename to Assets/_PackageRoot/Tests/Base/Extensions.Unity.ImageLoader.Tests.asmdef index 7e425cc..dcaeb90 100644 --- a/Assets/_PackageRoot/Tests/Editor/Extensions.Unity.ImageLoader.Editor.Tests.asmdef +++ b/Assets/_PackageRoot/Tests/Base/Extensions.Unity.ImageLoader.Tests.asmdef @@ -1,12 +1,11 @@ { - "name": "Extensions.Unity.ImageLoader.Editor.Tests", + "name": "Extensions.Unity.ImageLoader.Tests", "references": [ "Extensions.Unity.ImageLoader", + "UnityEngine.TestRunner", "UniTask" ], - "includePlatforms": [ - "Editor" - ], + "includePlatforms": [], "excludePlatforms": [], "allowUnsafeCode": false, "overrideReferences": false, diff --git a/Assets/_PackageRoot/Tests/Base/Extensions.Unity.ImageLoader.Tests.asmdef.meta b/Assets/_PackageRoot/Tests/Base/Extensions.Unity.ImageLoader.Tests.asmdef.meta new file mode 100644 index 0000000..51db954 --- /dev/null +++ b/Assets/_PackageRoot/Tests/Base/Extensions.Unity.ImageLoader.Tests.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 46caf33cc8e42f348839004db69bcbee +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_PackageRoot/Tests/Editor/Utils.meta b/Assets/_PackageRoot/Tests/Base/Utils.meta similarity index 100% rename from Assets/_PackageRoot/Tests/Editor/Utils.meta rename to Assets/_PackageRoot/Tests/Base/Utils.meta diff --git a/Assets/_PackageRoot/Tests/Editor/Utils/EventData.cs b/Assets/_PackageRoot/Tests/Base/Utils/EventData.cs similarity index 100% rename from Assets/_PackageRoot/Tests/Editor/Utils/EventData.cs rename to Assets/_PackageRoot/Tests/Base/Utils/EventData.cs diff --git a/Assets/_PackageRoot/Tests/Editor/Utils/EventData.cs.meta b/Assets/_PackageRoot/Tests/Base/Utils/EventData.cs.meta similarity index 100% rename from Assets/_PackageRoot/Tests/Editor/Utils/EventData.cs.meta rename to Assets/_PackageRoot/Tests/Base/Utils/EventData.cs.meta diff --git a/Assets/_PackageRoot/Tests/Editor/Utils/EventName.cs b/Assets/_PackageRoot/Tests/Base/Utils/EventName.cs similarity index 97% rename from Assets/_PackageRoot/Tests/Editor/Utils/EventName.cs rename to Assets/_PackageRoot/Tests/Base/Utils/EventName.cs index 97ffd36..7483e8d 100644 --- a/Assets/_PackageRoot/Tests/Editor/Utils/EventName.cs +++ b/Assets/_PackageRoot/Tests/Base/Utils/EventName.cs @@ -10,7 +10,8 @@ public enum EventName LoadedFromDiskCache, LoadingFromSource, LoadedFromSource, - Then, + Loaded, + Consumed, Failed, Completed, Canceled diff --git a/Assets/_PackageRoot/Tests/Editor/Utils/EventName.cs.meta b/Assets/_PackageRoot/Tests/Base/Utils/EventName.cs.meta similarity index 100% rename from Assets/_PackageRoot/Tests/Editor/Utils/EventName.cs.meta rename to Assets/_PackageRoot/Tests/Base/Utils/EventName.cs.meta diff --git a/Assets/_PackageRoot/Tests/Editor/Utils/FakeConsumer.cs b/Assets/_PackageRoot/Tests/Base/Utils/FakeConsumer.cs similarity index 100% rename from Assets/_PackageRoot/Tests/Editor/Utils/FakeConsumer.cs rename to Assets/_PackageRoot/Tests/Base/Utils/FakeConsumer.cs diff --git a/Assets/_PackageRoot/Tests/Editor/Utils/FakeConsumer.cs.meta b/Assets/_PackageRoot/Tests/Base/Utils/FakeConsumer.cs.meta similarity index 100% rename from Assets/_PackageRoot/Tests/Editor/Utils/FakeConsumer.cs.meta rename to Assets/_PackageRoot/Tests/Base/Utils/FakeConsumer.cs.meta diff --git a/Assets/_PackageRoot/Tests/Editor/Utils/FakeFuture.Implementation.IFuture.cs b/Assets/_PackageRoot/Tests/Base/Utils/FakeFuture.Implementation.IFuture.cs similarity index 90% rename from Assets/_PackageRoot/Tests/Editor/Utils/FakeFuture.Implementation.IFuture.cs rename to Assets/_PackageRoot/Tests/Base/Utils/FakeFuture.Implementation.IFuture.cs index f03d6e2..315de94 100644 --- a/Assets/_PackageRoot/Tests/Editor/Utils/FakeFuture.Implementation.IFuture.cs +++ b/Assets/_PackageRoot/Tests/Base/Utils/FakeFuture.Implementation.IFuture.cs @@ -51,12 +51,14 @@ public void Cancel() public IFuture LoadedFromSource(Action action) => throw new NotImplementedException(); public IFuture LoadingFromDiskCache(Action action, bool ignoreLoaded = false) => throw new NotImplementedException(); public IFuture LoadingFromSource(Action action, bool ignoreLoaded = false) => throw new NotImplementedException(); + public IFuture Consume(Action setter, bool replace = false) => throw new NotImplementedException(); public IFuture PassEvents(IFutureInternal to, bool passCancelled = true) => throw new NotImplementedException(); public IFuture PassEvents(IFutureInternal to, Func convert, bool passCancelled = true) => throw new NotImplementedException(); public IFuture SetLogLevel(DebugLevel value) => throw new NotImplementedException(); public IFuture SetUseDiskCache(bool value = true) => throw new NotImplementedException(); public IFuture SetUseMemoryCache(bool value = true) => throw new NotImplementedException(); + public IFuture SetPlaceholder(T placeholder, params PlaceholderTrigger[] triggers) => throw new NotImplementedException(); public UniTask StartLoading(bool ignoreImageNotFoundError = false) => throw new NotImplementedException(); - public IFuture Then(Action onCompleted) => throw new NotImplementedException(); + public IFuture Loaded(Action onCompleted) => throw new NotImplementedException(); } } \ No newline at end of file diff --git a/Assets/_PackageRoot/Tests/Editor/Utils/FakeFuture.Implementation.IFuture.cs.meta b/Assets/_PackageRoot/Tests/Base/Utils/FakeFuture.Implementation.IFuture.cs.meta similarity index 100% rename from Assets/_PackageRoot/Tests/Editor/Utils/FakeFuture.Implementation.IFuture.cs.meta rename to Assets/_PackageRoot/Tests/Base/Utils/FakeFuture.Implementation.IFuture.cs.meta diff --git a/Assets/_PackageRoot/Tests/Editor/Utils/FakeFuture.Implementation.IFutureInternal.cs b/Assets/_PackageRoot/Tests/Base/Utils/FakeFuture.Implementation.IFutureInternal.cs similarity index 95% rename from Assets/_PackageRoot/Tests/Editor/Utils/FakeFuture.Implementation.IFutureInternal.cs rename to Assets/_PackageRoot/Tests/Base/Utils/FakeFuture.Implementation.IFutureInternal.cs index 7cdcf8c..27836c7 100644 --- a/Assets/_PackageRoot/Tests/Editor/Utils/FakeFuture.Implementation.IFutureInternal.cs +++ b/Assets/_PackageRoot/Tests/Base/Utils/FakeFuture.Implementation.IFutureInternal.cs @@ -17,7 +17,7 @@ public void Loading(FutureLoadingFrom loadingFrom) events.Add(new EventData { name = eventName }); } - public void Loaded(T value, FutureLoadedFrom loadedFrom) + public void SetLoaded(T value, FutureLoadedFrom loadedFrom) { if (LogLevel.IsActive(DebugLevel.Trace)) UnityEngine.Debug.Log($"FakeFuture[id={Id}] Loaded from {loadedFrom}"); diff --git a/Assets/_PackageRoot/Tests/Editor/Utils/FakeFuture.Implementation.IFutureInternal.cs.meta b/Assets/_PackageRoot/Tests/Base/Utils/FakeFuture.Implementation.IFutureInternal.cs.meta similarity index 100% rename from Assets/_PackageRoot/Tests/Editor/Utils/FakeFuture.Implementation.IFutureInternal.cs.meta rename to Assets/_PackageRoot/Tests/Base/Utils/FakeFuture.Implementation.IFutureInternal.cs.meta diff --git a/Assets/_PackageRoot/Tests/Editor/Utils/FakeFuture.Subscription.cs b/Assets/_PackageRoot/Tests/Base/Utils/FakeFuture.Subscription.cs similarity index 100% rename from Assets/_PackageRoot/Tests/Editor/Utils/FakeFuture.Subscription.cs rename to Assets/_PackageRoot/Tests/Base/Utils/FakeFuture.Subscription.cs diff --git a/Assets/_PackageRoot/Tests/Editor/Utils/FakeFuture.Subscription.cs.meta b/Assets/_PackageRoot/Tests/Base/Utils/FakeFuture.Subscription.cs.meta similarity index 100% rename from Assets/_PackageRoot/Tests/Editor/Utils/FakeFuture.Subscription.cs.meta rename to Assets/_PackageRoot/Tests/Base/Utils/FakeFuture.Subscription.cs.meta diff --git a/Assets/_PackageRoot/Tests/Editor/Utils/FakeFuture.cs b/Assets/_PackageRoot/Tests/Base/Utils/FakeFuture.cs similarity index 100% rename from Assets/_PackageRoot/Tests/Editor/Utils/FakeFuture.cs rename to Assets/_PackageRoot/Tests/Base/Utils/FakeFuture.cs diff --git a/Assets/_PackageRoot/Tests/Editor/Utils/FakeFuture.cs.meta b/Assets/_PackageRoot/Tests/Base/Utils/FakeFuture.cs.meta similarity index 100% rename from Assets/_PackageRoot/Tests/Editor/Utils/FakeFuture.cs.meta rename to Assets/_PackageRoot/Tests/Base/Utils/FakeFuture.cs.meta diff --git a/Assets/_PackageRoot/Tests/Editor/Utils/FutureEx.cs b/Assets/_PackageRoot/Tests/Base/Utils/FutureEx.cs similarity index 58% rename from Assets/_PackageRoot/Tests/Editor/Utils/FutureEx.cs rename to Assets/_PackageRoot/Tests/Base/Utils/FutureEx.cs index 5e6223c..95935c8 100644 --- a/Assets/_PackageRoot/Tests/Editor/Utils/FutureEx.cs +++ b/Assets/_PackageRoot/Tests/Base/Utils/FutureEx.cs @@ -2,7 +2,7 @@ namespace Extensions.Unity.ImageLoader.Tests.Utils { public static class FutureEx { - public static FutureListener ToFutureListener(this IFuture future, bool ignoreLoadingWhenLoaded = false) - => new FutureListener(future, ignoreLoadingWhenLoaded: ignoreLoadingWhenLoaded); + public static FutureListener ToFutureListener(this IFuture future, bool ignoreLoadingWhenLoaded = false, bool ignorePlaceholder = true) + => new FutureListener(future, ignoreLoadingWhenLoaded: ignoreLoadingWhenLoaded, ignorePlaceholder: ignorePlaceholder); } } \ No newline at end of file diff --git a/Assets/_PackageRoot/Tests/Editor/Utils/FutureEx.cs.meta b/Assets/_PackageRoot/Tests/Base/Utils/FutureEx.cs.meta similarity index 100% rename from Assets/_PackageRoot/Tests/Editor/Utils/FutureEx.cs.meta rename to Assets/_PackageRoot/Tests/Base/Utils/FutureEx.cs.meta diff --git a/Assets/_PackageRoot/Tests/Editor/Utils/FutureListener.Assert.cs b/Assets/_PackageRoot/Tests/Base/Utils/FutureListener.Assert.cs similarity index 100% rename from Assets/_PackageRoot/Tests/Editor/Utils/FutureListener.Assert.cs rename to Assets/_PackageRoot/Tests/Base/Utils/FutureListener.Assert.cs diff --git a/Assets/_PackageRoot/Tests/Editor/Utils/FutureListener.Assert.cs.meta b/Assets/_PackageRoot/Tests/Base/Utils/FutureListener.Assert.cs.meta similarity index 100% rename from Assets/_PackageRoot/Tests/Editor/Utils/FutureListener.Assert.cs.meta rename to Assets/_PackageRoot/Tests/Base/Utils/FutureListener.Assert.cs.meta diff --git a/Assets/_PackageRoot/Tests/Editor/Utils/FutureListener.cs b/Assets/_PackageRoot/Tests/Base/Utils/FutureListener.cs similarity index 64% rename from Assets/_PackageRoot/Tests/Editor/Utils/FutureListener.cs rename to Assets/_PackageRoot/Tests/Base/Utils/FutureListener.cs index e6ef717..4556c25 100644 --- a/Assets/_PackageRoot/Tests/Editor/Utils/FutureListener.cs +++ b/Assets/_PackageRoot/Tests/Base/Utils/FutureListener.cs @@ -6,6 +6,9 @@ namespace Extensions.Unity.ImageLoader.Tests.Utils { public partial class FutureListener { + static uint idCounter = 0; + + uint id = idCounter++; List events = new List(); public IReadOnlyList Events @@ -17,59 +20,59 @@ public IReadOnlyList Events } } - public FutureListener(IFuture future, bool ignoreLoadingWhenLoaded = false, DebugLevel? logLevel = null) + public FutureListener(IFuture future, bool ignoreLoadingWhenLoaded = false, bool ignorePlaceholder = true, DebugLevel? logLevel = null) { if (logLevel == null) logLevel = ImageLoader.settings.debugLevel; if (logLevel.Value.IsActive(DebugLevel.Trace)) - Debug.Log($"[FutureListener] Future[id={future.Id}] Created"); + Debug.Log($"[FutureListener id={id}] Future[id={future.Id}] Created. FutureStatus: {future.Status}"); future.LoadedFromMemoryCache(value => { if (logLevel.Value.IsActive(DebugLevel.Trace)) - Debug.Log($"[FutureListener] Future[id={future.Id}] LoadedFromMemoryCache: {value}"); + Debug.Log($"[FutureListener id={id}] Future[id={future.Id}] LoadedFromMemoryCache: {value}"); lock (events) events.Add(new EventData { name = EventName.LoadedFromMemoryCache, value = value }); }); future.LoadingFromDiskCache(() => { if (logLevel.Value.IsActive(DebugLevel.Trace)) - Debug.Log($"[FutureListener] Future[id={future.Id}] LoadingFromDiskCache"); + Debug.Log($"[FutureListener id={id}] Future[id={future.Id}] LoadingFromDiskCache"); lock (events) events.Add(new EventData { name = EventName.LoadingFromDiskCache }); }, ignoreWhenLoaded: ignoreLoadingWhenLoaded); future.LoadedFromDiskCache(value => { if (logLevel.Value.IsActive(DebugLevel.Trace)) - Debug.Log($"[FutureListener] Future[id={future.Id}] LoadedFromDiskCache: {value}"); + Debug.Log($"[FutureListener id={id}] Future[id={future.Id}] LoadedFromDiskCache: {value}"); lock (events) events.Add(new EventData { name = EventName.LoadedFromDiskCache, value = value }); }); future.LoadingFromSource(() => { if (logLevel.Value.IsActive(DebugLevel.Trace)) - Debug.Log($"[FutureListener] Future[id={future.Id}] LoadingFromSource"); + Debug.Log($"[FutureListener id={id}] Future[id={future.Id}] LoadingFromSource"); lock (events) events.Add(new EventData { name = EventName.LoadingFromSource }); }, ignoreWhenLoaded: ignoreLoadingWhenLoaded); future.LoadedFromSource(value => { if (logLevel.Value.IsActive(DebugLevel.Trace)) - Debug.Log($"[FutureListener] Future[id={future.Id}] LoadedFromSource: {value}"); + Debug.Log($"[FutureListener id={id}] Future[id={future.Id}] LoadedFromSource: {value}"); lock (events) events.Add(new EventData { name = EventName.LoadedFromSource, value = value }); }); - future.Then(value => + future.Loaded(value => { if (logLevel.Value.IsActive(DebugLevel.Trace)) - Debug.Log($"[FutureListener] Future[id={future.Id}] Then: {value}"); + Debug.Log($"[FutureListener id={id}] Future[id={future.Id}] Loaded: {value}"); lock (events) - events.Add(new EventData { name = EventName.Then, value = value }); + events.Add(new EventData { name = EventName.Loaded, value = value }); }); future.Failed(exception => { if (logLevel.Value.IsActive(DebugLevel.Trace)) - Debug.Log($"[FutureListener] Future[id={future.Id}] Failed: {exception}"); + Debug.Log($"[FutureListener id={id}] Future[id={future.Id}] Failed: {exception}"); lock (events) events.Add(new EventData { name = EventName.Failed, value = exception }); }); @@ -78,15 +81,25 @@ public FutureListener(IFuture future, bool ignoreLoadingWhenLoaded = false, D future.Canceled(() => { if (logLevel.Value.IsActive(DebugLevel.Trace)) - Debug.Log($"[FutureListener] Future[id={future.Id}] Canceled"); + Debug.Log($"[FutureListener id={id}] Future[id={future.Id}] Canceled"); lock (events) events.Add(new EventData { name = EventName.Canceled }); }); } + if (!ignorePlaceholder) + { + future.Consume(value => + { + if (logLevel.Value.IsActive(DebugLevel.Trace)) + Debug.Log($"[FutureListener id={id}] Future[id={future.Id}] Consume: {value}"); + lock (events) + events.Add(new EventData { name = EventName.Consumed, value = value }); + }); + } future.Completed(value => { if (logLevel.Value.IsActive(DebugLevel.Trace)) - Debug.Log($"[FutureListener] Future[id={future.Id}] Completed: {value}"); + Debug.Log($"[FutureListener id={id}] Future[id={future.Id}] Completed: {value}"); lock (events) events.Add(new EventData { name = EventName.Completed, value = value }); }); diff --git a/Assets/_PackageRoot/Tests/Editor/Utils/FutureListener.cs.meta b/Assets/_PackageRoot/Tests/Base/Utils/FutureListener.cs.meta similarity index 100% rename from Assets/_PackageRoot/Tests/Editor/Utils/FutureListener.cs.meta rename to Assets/_PackageRoot/Tests/Base/Utils/FutureListener.cs.meta diff --git a/Assets/_PackageRoot/Tests/Editor/Utils/TestUtils.Async.cs b/Assets/_PackageRoot/Tests/Base/Utils/TestUtils.Async.cs similarity index 97% rename from Assets/_PackageRoot/Tests/Editor/Utils/TestUtils.Async.cs rename to Assets/_PackageRoot/Tests/Base/Utils/TestUtils.Async.cs index 0852525..00f2cc1 100644 --- a/Assets/_PackageRoot/Tests/Editor/Utils/TestUtils.Async.cs +++ b/Assets/_PackageRoot/Tests/Base/Utils/TestUtils.Async.cs @@ -6,7 +6,7 @@ namespace Extensions.Unity.ImageLoader.Tests.Utils { - internal static partial class TestUtils + public static partial class TestUtils { public static IEnumerator WaitTicks(int ticks = 1) { diff --git a/Assets/_PackageRoot/Tests/Editor/Utils/TestUtils.Async.cs.meta b/Assets/_PackageRoot/Tests/Base/Utils/TestUtils.Async.cs.meta similarity index 100% rename from Assets/_PackageRoot/Tests/Editor/Utils/TestUtils.Async.cs.meta rename to Assets/_PackageRoot/Tests/Base/Utils/TestUtils.Async.cs.meta diff --git a/Assets/_PackageRoot/Tests/Base/Utils/TestUtils.Load.cs b/Assets/_PackageRoot/Tests/Base/Utils/TestUtils.Load.cs new file mode 100644 index 0000000..bf66e2c --- /dev/null +++ b/Assets/_PackageRoot/Tests/Base/Utils/TestUtils.Load.cs @@ -0,0 +1,212 @@ +using System; +using System.Collections; +using System.Linq; +using Cysharp.Threading.Tasks; +using NUnit.Framework; + +namespace Extensions.Unity.ImageLoader.Tests.Utils +{ + public static partial class TestUtils + { + public static IEnumerator LoadFromMemoryCache(string url, bool usePlaceholder = false) => Load(url, null, FutureLoadedFrom.MemoryCache, usePlaceholder); + public static IEnumerator Load(string url, FutureLoadingFrom? expectedLoadingFrom, FutureLoadedFrom expectedLoadedFrom, bool usePlaceholder = false) + { + var future = ImageLoader.LoadSprite(url); + var futureListener = future.ToFutureListener(ignorePlaceholder: !usePlaceholder); + + if (usePlaceholder) + { + future.SetPlaceholder(placeholderSprites[PlaceholderTrigger.LoadingFromDiskCache], PlaceholderTrigger.LoadingFromDiskCache); + future.SetPlaceholder(placeholderSprites[PlaceholderTrigger.LoadingFromSource], PlaceholderTrigger.LoadingFromSource); + future.SetPlaceholder(placeholderSprites[PlaceholderTrigger.FailedToLoad], PlaceholderTrigger.FailedToLoad); + future.SetPlaceholder(placeholderSprites[PlaceholderTrigger.Canceled], PlaceholderTrigger.Canceled); + } + + if (expectedLoadingFrom.HasValue) + futureListener.Assert_Events_Contains(expectedLoadingFrom.Value.ToEventName()); + + var task1 = future.AsTask(); + yield return future.TimeoutCoroutine(TimeSpan.FromSeconds(10)); + var task2 = future.AsTask(); + + futureListener.Assert_Events_NotContains(EventName.Canceled); + + var events = expectedLoadingFrom.HasValue + ? usePlaceholder + ? new [] { expectedLoadingFrom.Value.ToEventName(), EventName.Consumed, expectedLoadedFrom.ToEventName(), EventName.Loaded, EventName.Consumed, EventName.Completed } + : new [] { expectedLoadingFrom.Value.ToEventName(), expectedLoadedFrom.ToEventName(), EventName.Loaded, EventName.Completed } + : usePlaceholder + ? new [] { expectedLoadedFrom.ToEventName(), EventName.Loaded, EventName.Consumed, EventName.Completed } + : new [] { expectedLoadedFrom.ToEventName(), EventName.Loaded, EventName.Completed }; + + futureListener.Assert_Events_Equals(events); + futureListener.Assert_Events_Value(EventName.Completed, success => success == true); + + Assert.IsTrue(task1.IsCompleted, "Task was not cancelled but Future was cancelled"); + Assert.IsTrue(task2.IsCompleted, "Task was not cancelled but Future was cancelled"); + + yield return UniTask.Yield(); + + futureListener.Assert_Events_Equals(events); + futureListener.Assert_Events_Value(EventName.Completed, success => success == true); + + Assert.AreEqual(future.Status, expectedLoadedFrom.AsFutureStatus()); + + future.ToFutureListener(ignoreLoadingWhenLoaded: true, ignorePlaceholder: true) + .Assert_Events_Equals(expectedLoadedFrom.ToEventName(), EventName.Loaded, EventName.Completed) + .Assert_Events_Value(EventName.Completed, success => success == true); + + future.ToFutureListener(ignoreLoadingWhenLoaded: true, ignorePlaceholder: false) + .Assert_Events_Equals(expectedLoadedFrom.ToEventName(), EventName.Loaded, EventName.Consumed, EventName.Completed) + .Assert_Events_Value(EventName.Completed, success => success == true); + + if (expectedLoadingFrom.HasValue) + future.ToFutureListener() + .Assert_Events_Equals(expectedLoadingFrom.Value.ToEventName(), expectedLoadedFrom.ToEventName(), EventName.Loaded, EventName.Completed) + .Assert_Events_Value(EventName.Completed, success => success == true); + + future.Dispose(); + yield return UniTask.Yield(); + } + public static IEnumerator LoadFromMemoryCacheThenCancel(string url, bool useGC, bool usePlaceholder = false) + => LoadThenCancel(url, null, FutureLoadedFrom.MemoryCache, useGC, usePlaceholder); + public static IEnumerator LoadThenCancel(string url, FutureLoadingFrom? expectedLoadingFrom, FutureLoadedFrom expectedLoadedFrom, bool useGC, bool usePlaceholder = false) + { + var future = ImageLoader.LoadSprite(url); + var futureListener = future.ToFutureListener(ignorePlaceholder: !usePlaceholder); + + if (usePlaceholder) + { + future.SetPlaceholder(placeholderSprites[PlaceholderTrigger.LoadingFromDiskCache], PlaceholderTrigger.LoadingFromDiskCache); + future.SetPlaceholder(placeholderSprites[PlaceholderTrigger.LoadingFromSource], PlaceholderTrigger.LoadingFromSource); + future.SetPlaceholder(placeholderSprites[PlaceholderTrigger.FailedToLoad], PlaceholderTrigger.FailedToLoad); + future.SetPlaceholder(placeholderSprites[PlaceholderTrigger.Canceled], PlaceholderTrigger.Canceled); + } + + if (expectedLoadingFrom.HasValue) + futureListener.Assert_Events_Contains(expectedLoadingFrom.Value.ToEventName()); + + var task1 = future.AsTask(); + yield return future.TimeoutCoroutine(TimeSpan.FromSeconds(10)); + var task2 = future.AsTask(); + + futureListener.Assert_Events_NotContains(EventName.Canceled); + + if (useGC) + TestUtils.WaitForGCFast(); + + future.Cancel(); + + var events = expectedLoadingFrom.HasValue + ? usePlaceholder + ? new [] { expectedLoadingFrom.Value.ToEventName(), EventName.Consumed, expectedLoadedFrom.ToEventName(), EventName.Loaded, EventName.Consumed, EventName.Completed } + : new [] { expectedLoadingFrom.Value.ToEventName(), expectedLoadedFrom.ToEventName(), EventName.Loaded, EventName.Completed } + : usePlaceholder + ? new [] { expectedLoadedFrom.ToEventName(), EventName.Loaded, EventName.Consumed, EventName.Completed } + : new [] { expectedLoadedFrom.ToEventName(), EventName.Loaded, EventName.Completed }; + + futureListener.Assert_Events_Equals(events); + futureListener.Assert_Events_Value(EventName.Completed, success => success == true); + + Assert.IsTrue(task1.IsCompleted, "Task was not cancelled but Future was cancelled. Probably the OnCancel subscription was cleaned up too early."); + Assert.IsTrue(task2.IsCompleted, "Task was not cancelled but Future was cancelled. Probably the OnCancel subscription was cleaned up too early."); + + yield return UniTask.Yield(); + + futureListener.Assert_Events_Equals(events); + futureListener.Assert_Events_Value(EventName.Completed, success => success == true); + + Assert.AreEqual(future.Status, expectedLoadedFrom.AsFutureStatus()); + + future.ToFutureListener(ignoreLoadingWhenLoaded: true, ignorePlaceholder: true) + .Assert_Events_Equals(expectedLoadedFrom.ToEventName(), EventName.Loaded, EventName.Completed) + .Assert_Events_Value(EventName.Completed, success => success == true); + + future.ToFutureListener(ignoreLoadingWhenLoaded: true, ignorePlaceholder: false) + .Assert_Events_Equals(expectedLoadedFrom.ToEventName(), EventName.Loaded, EventName.Consumed, EventName.Completed) + .Assert_Events_Value(EventName.Completed, success => success == true); + + if (expectedLoadingFrom.HasValue) + future.ToFutureListener() + .Assert_Events_Equals(expectedLoadingFrom.Value.ToEventName(), expectedLoadedFrom.ToEventName(), EventName.Loaded, EventName.Completed) + .Assert_Events_Value(EventName.Completed, success => success == true); + + future.Dispose(); + yield return UniTask.Yield(); + } + public static IEnumerator LoadFromMemoryCacheAndCancel(string url, bool usePlaceholder = false) => LoadAndCancel(url, null, usePlaceholder); + public static IEnumerator LoadAndCancel(string url, FutureLoadingFrom? expectedLoadingFrom, bool usePlaceholder = false) + { + yield return LoadAndCancel(url, expectedLoadingFrom, useGC: true, usePlaceholder); + yield return LoadAndCancel(url, expectedLoadingFrom, useGC: false, usePlaceholder); + } + public static IEnumerator LoadAndCancel(string url, FutureLoadingFrom? expectedLoadingFrom, bool useGC, bool usePlaceholder = false) + { + var future = ImageLoader.LoadSprite(url); + var futureListener = future.ToFutureListener(ignorePlaceholder: !usePlaceholder); + var shouldLoadFromMemoryCache = !expectedLoadingFrom.HasValue; + + if (usePlaceholder) + { + future.SetPlaceholder(placeholderSprites[PlaceholderTrigger.LoadingFromDiskCache], PlaceholderTrigger.LoadingFromDiskCache); + future.SetPlaceholder(placeholderSprites[PlaceholderTrigger.LoadingFromSource], PlaceholderTrigger.LoadingFromSource); + future.SetPlaceholder(placeholderSprites[PlaceholderTrigger.FailedToLoad], PlaceholderTrigger.FailedToLoad); + future.SetPlaceholder(placeholderSprites[PlaceholderTrigger.Canceled], PlaceholderTrigger.Canceled); + } + + futureListener.Assert_Events_Contains(expectedLoadingFrom.HasValue + ? expectedLoadingFrom.Value.ToEventName() + : EventName.LoadedFromMemoryCache); + + if (useGC) + TestUtils.WaitForGCFast(); + + var task1 = future.AsTask(); + future.Cancel(); + var task2 = future.AsTask(); + + var events = shouldLoadFromMemoryCache + ? usePlaceholder + ? new [] { EventName.LoadedFromMemoryCache, EventName.Loaded, EventName.Consumed, EventName.Completed } + : new [] { EventName.LoadedFromMemoryCache, EventName.Loaded, EventName.Completed } + : usePlaceholder + ? new [] { expectedLoadingFrom.Value.ToEventName(), EventName.Consumed, EventName.Canceled, EventName.Consumed, EventName.Completed } + : new [] { expectedLoadingFrom.Value.ToEventName(), EventName.Canceled, EventName.Completed }; + + futureListener.Assert_Events_Equals(events); + futureListener.Assert_Events_Value(EventName.Completed, success => success == shouldLoadFromMemoryCache); + + Assert.IsTrue(task1.IsCompleted, "Task was not cancelled but Future was cancelled. Probably the OnCancel subscription was cleaned up too early."); + Assert.IsTrue(task2.IsCompleted, "Task was not cancelled but Future was cancelled. Probably the OnCancel subscription was cleaned up too early."); + + yield return UniTask.Yield(); + + futureListener.Assert_Events_Equals(events); + futureListener.Assert_Events_Value(EventName.Completed, success => success == shouldLoadFromMemoryCache); + + Assert.AreEqual(future.Status, shouldLoadFromMemoryCache + ? FutureStatus.LoadedFromMemoryCache + : FutureStatus.Canceled); + + var lateEvents = shouldLoadFromMemoryCache + ? usePlaceholder + ? new[] { EventName.LoadedFromMemoryCache, EventName.Loaded, EventName.Consumed, EventName.Completed } + : new[] { EventName.LoadedFromMemoryCache, EventName.Loaded, EventName.Completed } + : usePlaceholder + ? new[] { expectedLoadingFrom.Value.ToEventName(), EventName.Canceled, EventName.Consumed, EventName.Completed } + : new[] { expectedLoadingFrom.Value.ToEventName(), EventName.Canceled, EventName.Completed }; + + var lateFutureListener = future.ToFutureListener(ignorePlaceholder: !usePlaceholder) + .Assert_Events_Equals(lateEvents) + .Assert_Events_Value(EventName.Completed, success => success == shouldLoadFromMemoryCache); + + if (expectedLoadingFrom.HasValue && future.IsLoaded) + future.ToFutureListener(ignoreLoadingWhenLoaded: true, ignorePlaceholder: !usePlaceholder) + .Assert_Events_Equals(lateEvents.Except(new [] { expectedLoadingFrom.Value.ToEventName() })) + .Assert_Events_Value(EventName.Completed, success => success == shouldLoadFromMemoryCache); + + future.Dispose(); + yield return UniTask.Yield(); + } + } +} \ No newline at end of file diff --git a/Assets/_PackageRoot/Tests/Editor/Utils/TestUtils.Load.cs.meta b/Assets/_PackageRoot/Tests/Base/Utils/TestUtils.Load.cs.meta similarity index 100% rename from Assets/_PackageRoot/Tests/Editor/Utils/TestUtils.Load.cs.meta rename to Assets/_PackageRoot/Tests/Base/Utils/TestUtils.Load.cs.meta diff --git a/Assets/_PackageRoot/Tests/Base/Utils/TestUtils.LoadFail.cs b/Assets/_PackageRoot/Tests/Base/Utils/TestUtils.LoadFail.cs new file mode 100644 index 0000000..8076c04 --- /dev/null +++ b/Assets/_PackageRoot/Tests/Base/Utils/TestUtils.LoadFail.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections; +using Cysharp.Threading.Tasks; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Extensions.Unity.ImageLoader.Tests.Utils +{ + public static partial class TestUtils + { + public static IEnumerator LoadFailFromMemoryCache(string url, bool usePlaceholder = false) => LoadFail(url, null, usePlaceholder); + public static IEnumerator LoadFail(string url, FutureLoadingFrom? expectedLoadingFrom, bool usePlaceholder = false) + { + var timeout = TimeSpan.FromMilliseconds(100); + var future = ImageLoader.LoadSprite(url).Timeout(timeout); + var futureListener = future.ToFutureListener(ignorePlaceholder: !usePlaceholder); + + if (usePlaceholder) + { + future.SetPlaceholder(placeholderSprites[PlaceholderTrigger.LoadingFromDiskCache], PlaceholderTrigger.LoadingFromDiskCache); + future.SetPlaceholder(placeholderSprites[PlaceholderTrigger.LoadingFromSource], PlaceholderTrigger.LoadingFromSource); + future.SetPlaceholder(placeholderSprites[PlaceholderTrigger.FailedToLoad], PlaceholderTrigger.FailedToLoad); + future.SetPlaceholder(placeholderSprites[PlaceholderTrigger.Canceled], PlaceholderTrigger.Canceled); + } + + if (expectedLoadingFrom.HasValue) + futureListener.Assert_Events_Contains(expectedLoadingFrom.Value.ToEventName()); + + var task1 = future.AsTask(); + if (expectedLoadingFrom.HasValue && expectedLoadingFrom.Value == FutureLoadingFrom.Source) // exception should be thrown only if ONLY loading from Source + LogAssert.Expect(LogType.Error, $"[ImageLoader] Future[id={future.Id}] Timeout ({timeout}): {url}"); + yield return TestUtils.WaitWhile(() => future.IsInProgress, TimeSpan.FromSeconds(10)); + var task2 = future.AsTask(); + + var events = expectedLoadingFrom.HasValue + ? usePlaceholder + ? new[] { expectedLoadingFrom.Value.ToEventName(), EventName.Consumed, EventName.Failed, EventName.Consumed, EventName.Completed } + : new[] { expectedLoadingFrom.Value.ToEventName(), EventName.Failed, EventName.Completed } + : usePlaceholder + ? new[] { EventName.Failed, EventName.Consumed, EventName.Completed} + : new[] { EventName.Failed, EventName.Completed}; + + futureListener.Assert_Events_Equals(events); + futureListener.Assert_Events_Value(EventName.Completed, success => success == false); + + Assert.IsTrue(task1.IsCompleted, "Task was not cancelled but Future was cancelled"); + Assert.IsTrue(task2.IsCompleted, "Task was not cancelled but Future was cancelled"); + + yield return UniTask.Yield(); + + futureListener.Assert_Events_Equals(events); + futureListener.Assert_Events_Value(EventName.Completed, success => success == false); + + Assert.AreEqual(future.Status, FutureStatus.FailedToLoad); + + var lateEvents = expectedLoadingFrom.HasValue + ? usePlaceholder + ? new[] { expectedLoadingFrom.Value.ToEventName(), EventName.Failed, EventName.Consumed, EventName.Completed } + : new[] { expectedLoadingFrom.Value.ToEventName(), EventName.Failed, EventName.Completed } + : usePlaceholder + ? new[] { EventName.Failed, EventName.Consumed, EventName.Completed} + : new[] { EventName.Failed, EventName.Completed}; + + future.ToFutureListener(ignoreLoadingWhenLoaded: true, ignorePlaceholder: !usePlaceholder) + .Assert_Events_Equals(lateEvents) + .Assert_Events_Value(EventName.Completed, success => success == false); + + if (expectedLoadingFrom.HasValue) + future.ToFutureListener(ignorePlaceholder: !usePlaceholder) + .Assert_Events_Equals(lateEvents) + .Assert_Events_Value(EventName.Completed, success => success == false); + + future.Dispose(); + yield return UniTask.Yield(); + } + } +} \ No newline at end of file diff --git a/Assets/_PackageRoot/Tests/Editor/Utils/TestUtils.LoadFail.cs.meta b/Assets/_PackageRoot/Tests/Base/Utils/TestUtils.LoadFail.cs.meta similarity index 100% rename from Assets/_PackageRoot/Tests/Editor/Utils/TestUtils.LoadFail.cs.meta rename to Assets/_PackageRoot/Tests/Base/Utils/TestUtils.LoadFail.cs.meta diff --git a/Assets/_PackageRoot/Tests/Editor/Utils/TestUtils.cs b/Assets/_PackageRoot/Tests/Base/Utils/TestUtils.cs similarity index 71% rename from Assets/_PackageRoot/Tests/Editor/Utils/TestUtils.cs rename to Assets/_PackageRoot/Tests/Base/Utils/TestUtils.cs index c1eb451..7ffd901 100644 --- a/Assets/_PackageRoot/Tests/Editor/Utils/TestUtils.cs +++ b/Assets/_PackageRoot/Tests/Base/Utils/TestUtils.cs @@ -8,18 +8,26 @@ namespace Extensions.Unity.ImageLoader.Tests.Utils { - internal static partial class TestUtils + public static partial class TestUtils { public static readonly string[] ImageURLs = { - "https://github.com/IvanMurzak/Unity-ImageLoader/raw/master/Test%20Images/ImageA.jpg", - "https://github.com/IvanMurzak/Unity-ImageLoader/raw/master/Test%20Images/ImageB.png", - "https://github.com/IvanMurzak/Unity-ImageLoader/raw/master/Test%20Images/ImageC.png" + "https://github.com/IvanMurzak/Unity-ImageLoader/raw/main/Test%20Images/ImageA.jpg", + "https://github.com/IvanMurzak/Unity-ImageLoader/raw/main/Test%20Images/ImageB.png", + "https://github.com/IvanMurzak/Unity-ImageLoader/raw/main/Test%20Images/ImageC.png" }; public static string IncorrectImageURL => $"https://doesntexist.com/{Guid.NewGuid()}.png"; public static IEnumerable IncorrectImageURLs(int count = 3) => Enumerable.Range(0, count).Select(_ => IncorrectImageURL); public static readonly byte[] CorruptedTextureBytes = new byte[] { 0 }; + public static readonly Dictionary placeholderSprites = new Dictionary + { + { PlaceholderTrigger.LoadingFromDiskCache, Texture2D.whiteTexture.ToSprite() }, + { PlaceholderTrigger.LoadingFromSource, Texture2D.blackTexture.ToSprite() }, + { PlaceholderTrigger.FailedToLoad, Texture2D.redTexture.ToSprite() }, + { PlaceholderTrigger.Canceled, Texture2D.grayTexture.ToSprite() } + }; + public static IEnumerator ClearEverything(string message) { if (message != null) @@ -49,5 +57,10 @@ public static IEnumerator RunNoLogs(Func test) ImageLoader.settings.debugLevel = DebugLevel.Error; yield return test(); } + public static void RunNoLogs(Action test) + { + ImageLoader.settings.debugLevel = DebugLevel.Error; + test(); + } } } \ No newline at end of file diff --git a/Assets/_PackageRoot/Tests/Editor/Utils/TestUtils.cs.meta b/Assets/_PackageRoot/Tests/Base/Utils/TestUtils.cs.meta similarity index 100% rename from Assets/_PackageRoot/Tests/Editor/Utils/TestUtils.cs.meta rename to Assets/_PackageRoot/Tests/Base/Utils/TestUtils.cs.meta diff --git a/Assets/_PackageRoot/Tests/Editor/Extensions.Unity.ImageLoader.Tests.Editor.asmdef b/Assets/_PackageRoot/Tests/Editor/Extensions.Unity.ImageLoader.Tests.Editor.asmdef new file mode 100644 index 0000000..0cb286a --- /dev/null +++ b/Assets/_PackageRoot/Tests/Editor/Extensions.Unity.ImageLoader.Tests.Editor.asmdef @@ -0,0 +1,23 @@ +{ + "name": "Extensions.Unity.ImageLoader.Tests.Editor", + "references": [ + "Extensions.Unity.ImageLoader.Tests", + "Extensions.Unity.ImageLoader", + "UnityEngine.TestRunner", + "UnityEditor.TestRunner", + "UniTask" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [ + "NSubstitute.dll" + ], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Assets/_PackageRoot/Tests/Editor/Extensions.Unity.ImageLoader.Editor.Tests.asmdef.meta b/Assets/_PackageRoot/Tests/Editor/Extensions.Unity.ImageLoader.Tests.Editor.asmdef.meta similarity index 100% rename from Assets/_PackageRoot/Tests/Editor/Extensions.Unity.ImageLoader.Editor.Tests.asmdef.meta rename to Assets/_PackageRoot/Tests/Editor/Extensions.Unity.ImageLoader.Tests.Editor.asmdef.meta diff --git a/Assets/_PackageRoot/Tests/Editor/TestFuture.Load.AndCancel.cs b/Assets/_PackageRoot/Tests/Editor/TestFuture.Load.AndCancel.cs index e34fdba..e918937 100644 --- a/Assets/_PackageRoot/Tests/Editor/TestFuture.Load.AndCancel.cs +++ b/Assets/_PackageRoot/Tests/Editor/TestFuture.Load.AndCancel.cs @@ -9,12 +9,17 @@ public partial class TestFuture [UnityTest] public IEnumerator LoadFrom_Source_AndCancel_NoLogs() => TestUtils.RunNoLogs(LoadFrom_Source_AndCancel); [UnityTest] public IEnumerator LoadFrom_Source_AndCancel() { - yield return LoadFrom_Source_AndCancel(useDiskCache: true, useMemoryCache: true); - yield return LoadFrom_Source_AndCancel(useDiskCache: true, useMemoryCache: false); - yield return LoadFrom_Source_AndCancel(useDiskCache: false, useMemoryCache: true); - yield return LoadFrom_Source_AndCancel(useDiskCache: false, useMemoryCache: false); + yield return LoadFrom_Source_AndCancel(useDiskCache: true, useMemoryCache: true, usePlaceholder: false); + yield return LoadFrom_Source_AndCancel(useDiskCache: true, useMemoryCache: false, usePlaceholder: false); + yield return LoadFrom_Source_AndCancel(useDiskCache: false, useMemoryCache: true, usePlaceholder: false); + yield return LoadFrom_Source_AndCancel(useDiskCache: false, useMemoryCache: false, usePlaceholder: false); + + yield return LoadFrom_Source_AndCancel(useDiskCache: true, useMemoryCache: true, usePlaceholder: true); + yield return LoadFrom_Source_AndCancel(useDiskCache: true, useMemoryCache: false, usePlaceholder: true); + yield return LoadFrom_Source_AndCancel(useDiskCache: false, useMemoryCache: true, usePlaceholder: true); + yield return LoadFrom_Source_AndCancel(useDiskCache: false, useMemoryCache: false, usePlaceholder: true); } - IEnumerator LoadFrom_Source_AndCancel(bool useDiskCache, bool useMemoryCache) + IEnumerator LoadFrom_Source_AndCancel(bool useDiskCache, bool useMemoryCache, bool usePlaceholder) { ImageLoader.settings.useDiskCache = useDiskCache; ImageLoader.settings.useMemoryCache = useMemoryCache; @@ -24,17 +29,32 @@ IEnumerator LoadFrom_Source_AndCancel(bool useDiskCache, bool useMemoryCache) yield return TestUtils.ClearEverything(message: null); } + IEnumerator LoadFrom_MemoryCache_AndCancel(bool usePlaceholder) + { + foreach (var url in TestUtils.ImageURLs) + { + yield return ImageLoader.LoadSprite(url).AsCoroutine(); + yield return TestUtils.LoadFromMemoryCacheAndCancel(url, usePlaceholder: usePlaceholder); + } + yield return TestUtils.ClearEverything(message: null); + } + IEnumerator LoadFrom_DiskCache_AndCancel(bool usePlaceholder) + { + foreach (var url in TestUtils.ImageURLs) + { + yield return ImageLoader.LoadSprite(url).AsCoroutine(); + yield return TestUtils.LoadAndCancel(url, FutureLoadingFrom.DiskCache, usePlaceholder: usePlaceholder); + } + yield return TestUtils.ClearEverything(message: null); + } [UnityTest] public IEnumerator LoadFrom_MemoryCache_AndCancel_NoLogs() => TestUtils.RunNoLogs(LoadFrom_MemoryCache_AndCancel); [UnityTest] public IEnumerator LoadFrom_MemoryCache_AndCancel() { ImageLoader.settings.useDiskCache = true; ImageLoader.settings.useMemoryCache = true; - foreach (var url in TestUtils.ImageURLs) - { - yield return ImageLoader.LoadSprite(url).AsCoroutine(); - yield return TestUtils.LoadFromMemoryCacheAndCancel(url); - } + yield return LoadFrom_MemoryCache_AndCancel(usePlaceholder: false); + yield return LoadFrom_MemoryCache_AndCancel(usePlaceholder: true); } [UnityTest] public IEnumerator LoadFrom_DiskCache_AndCancel_NoLogs() => TestUtils.RunNoLogs(LoadFrom_DiskCache_AndCancel); @@ -43,11 +63,8 @@ [UnityTest] public IEnumerator LoadFrom_DiskCache_AndCancel() ImageLoader.settings.useDiskCache = true; ImageLoader.settings.useMemoryCache = false; - foreach (var url in TestUtils.ImageURLs) - { - yield return ImageLoader.LoadSprite(url).AsCoroutine(); - yield return TestUtils.LoadAndCancel(url, FutureLoadingFrom.DiskCache); - } + yield return LoadFrom_DiskCache_AndCancel(usePlaceholder: false); + yield return LoadFrom_DiskCache_AndCancel(usePlaceholder: true); } } } \ No newline at end of file diff --git a/Assets/_PackageRoot/Tests/Editor/TestFuture.Load.ThenCancel.cs b/Assets/_PackageRoot/Tests/Editor/TestFuture.Load.ThenCancel.cs index f48868b..430703d 100644 --- a/Assets/_PackageRoot/Tests/Editor/TestFuture.Load.ThenCancel.cs +++ b/Assets/_PackageRoot/Tests/Editor/TestFuture.Load.ThenCancel.cs @@ -9,45 +9,66 @@ public partial class TestFuture [UnityTest] public IEnumerator LoadFrom_Source_ThenCancel_NoLogs() => TestUtils.RunNoLogs(LoadFrom_Source_ThenCancel); [UnityTest] public IEnumerator LoadFrom_Source_ThenCancel() { - yield return LoadFrom_Source_ThenCancel(useDiskCache: true, useMemoryCache: true, useGC: true); - yield return LoadFrom_Source_ThenCancel(useDiskCache: true, useMemoryCache: false, useGC: true); - yield return LoadFrom_Source_ThenCancel(useDiskCache: false, useMemoryCache: true, useGC: true); - yield return LoadFrom_Source_ThenCancel(useDiskCache: false, useMemoryCache: false, useGC: true); + yield return LoadFrom_Source_ThenCancel(useDiskCache: true, useMemoryCache: true, useGC: true, usePlaceholder: false); + yield return LoadFrom_Source_ThenCancel(useDiskCache: true, useMemoryCache: false, useGC: true, usePlaceholder: false); + yield return LoadFrom_Source_ThenCancel(useDiskCache: false, useMemoryCache: true, useGC: true, usePlaceholder: false); + yield return LoadFrom_Source_ThenCancel(useDiskCache: false, useMemoryCache: false, useGC: true, usePlaceholder: false); - yield return LoadFrom_Source_ThenCancel(useDiskCache: true, useMemoryCache: true, useGC: false); - yield return LoadFrom_Source_ThenCancel(useDiskCache: true, useMemoryCache: false, useGC: false); - yield return LoadFrom_Source_ThenCancel(useDiskCache: false, useMemoryCache: true, useGC: false); - yield return LoadFrom_Source_ThenCancel(useDiskCache: false, useMemoryCache: false, useGC: false); + yield return LoadFrom_Source_ThenCancel(useDiskCache: true, useMemoryCache: true, useGC: false, usePlaceholder: false); + yield return LoadFrom_Source_ThenCancel(useDiskCache: true, useMemoryCache: false, useGC: false, usePlaceholder: false); + yield return LoadFrom_Source_ThenCancel(useDiskCache: false, useMemoryCache: true, useGC: false, usePlaceholder: false); + yield return LoadFrom_Source_ThenCancel(useDiskCache: false, useMemoryCache: false, useGC: false, usePlaceholder: false); + + yield return LoadFrom_Source_ThenCancel(useDiskCache: true, useMemoryCache: true, useGC: true, usePlaceholder: true); + yield return LoadFrom_Source_ThenCancel(useDiskCache: true, useMemoryCache: false, useGC: true, usePlaceholder: true); + yield return LoadFrom_Source_ThenCancel(useDiskCache: false, useMemoryCache: true, useGC: true, usePlaceholder: true); + yield return LoadFrom_Source_ThenCancel(useDiskCache: false, useMemoryCache: false, useGC: true, usePlaceholder: true); + + yield return LoadFrom_Source_ThenCancel(useDiskCache: true, useMemoryCache: true, useGC: false, usePlaceholder: true); + yield return LoadFrom_Source_ThenCancel(useDiskCache: true, useMemoryCache: false, useGC: false, usePlaceholder: true); + yield return LoadFrom_Source_ThenCancel(useDiskCache: false, useMemoryCache: true, useGC: false, usePlaceholder: true); + yield return LoadFrom_Source_ThenCancel(useDiskCache: false, useMemoryCache: false, useGC: false, usePlaceholder: true); } - IEnumerator LoadFrom_Source_ThenCancel(bool useDiskCache, bool useMemoryCache, bool useGC) + IEnumerator LoadFrom_Source_ThenCancel(bool useDiskCache, bool useMemoryCache, bool useGC, bool usePlaceholder) { ImageLoader.settings.useDiskCache = useDiskCache; ImageLoader.settings.useMemoryCache = useMemoryCache; foreach (var url in TestUtils.ImageURLs) - yield return TestUtils.LoadThenCancel(url, FutureLoadingFrom.Source, FutureLoadedFrom.Source, useGC); + yield return TestUtils.LoadThenCancel(url, FutureLoadingFrom.Source, FutureLoadedFrom.Source, useGC: useGC, usePlaceholder: usePlaceholder); yield return TestUtils.ClearEverything(message: null); } - - [UnityTest] public IEnumerator LoadFrom_MemoryCache_ThenCancel_NoLogs() => TestUtils.RunNoLogs(LoadFrom_MemoryCache_ThenCancel); - [UnityTest] public IEnumerator LoadFrom_MemoryCache_ThenCancel() + IEnumerator LoadFrom_MemoryCache_ThenCancel(bool useGC, bool usePlaceholder) { - ImageLoader.settings.useDiskCache = true; - ImageLoader.settings.useMemoryCache = true; - foreach (var url in TestUtils.ImageURLs) { yield return ImageLoader.LoadSprite(url).AsCoroutine(); - yield return TestUtils.LoadFromMemoryCacheThenCancel(url, useGC: true); + yield return TestUtils.LoadFromMemoryCacheThenCancel(url, useGC: useGC, usePlaceholder: usePlaceholder); } - // TODO: remove code duplicate yield return TestUtils.ClearEverything(message: null); + } + IEnumerator LoadFrom_DiskCache_ThenCancel(bool useGC, bool usePlaceholder) + { foreach (var url in TestUtils.ImageURLs) { yield return ImageLoader.LoadSprite(url).AsCoroutine(); - yield return TestUtils.LoadFromMemoryCacheThenCancel(url, useGC: false); + yield return TestUtils.LoadThenCancel(url, FutureLoadingFrom.DiskCache, FutureLoadedFrom.DiskCache, useGC: useGC, usePlaceholder: usePlaceholder); } + yield return TestUtils.ClearEverything(message: null); + } + + [UnityTest] public IEnumerator LoadFrom_MemoryCache_ThenCancel_NoLogs() => TestUtils.RunNoLogs(LoadFrom_MemoryCache_ThenCancel); + [UnityTest] public IEnumerator LoadFrom_MemoryCache_ThenCancel() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = true; + + yield return LoadFrom_MemoryCache_ThenCancel(useGC: true, usePlaceholder: false); + yield return LoadFrom_MemoryCache_ThenCancel(useGC: false, usePlaceholder: false); + + yield return LoadFrom_MemoryCache_ThenCancel(useGC: true, usePlaceholder: true); + yield return LoadFrom_MemoryCache_ThenCancel(useGC: false, usePlaceholder: true); } [UnityTest] public IEnumerator LoadFrom_DiskCache_ThenCancel_NoLogs() => TestUtils.RunNoLogs(LoadFrom_DiskCache_ThenCancel); @@ -56,18 +77,11 @@ [UnityTest] public IEnumerator LoadFrom_DiskCache_ThenCancel() ImageLoader.settings.useDiskCache = true; ImageLoader.settings.useMemoryCache = false; - foreach (var url in TestUtils.ImageURLs) - { - yield return ImageLoader.LoadSprite(url).AsCoroutine(); - yield return TestUtils.LoadThenCancel(url, FutureLoadingFrom.DiskCache, FutureLoadedFrom.DiskCache, useGC: true); - } - // TODO: remove code duplicate - yield return TestUtils.ClearEverything(message: null); - foreach (var url in TestUtils.ImageURLs) - { - yield return ImageLoader.LoadSprite(url).AsCoroutine(); - yield return TestUtils.LoadThenCancel(url, FutureLoadingFrom.DiskCache, FutureLoadedFrom.DiskCache, useGC: false); - } + yield return LoadFrom_DiskCache_ThenCancel(useGC: true, usePlaceholder: false); + yield return LoadFrom_DiskCache_ThenCancel(useGC: false, usePlaceholder: false); + + yield return LoadFrom_DiskCache_ThenCancel(useGC: true, usePlaceholder: true); + yield return LoadFrom_DiskCache_ThenCancel(useGC: false, usePlaceholder: true); } } } \ No newline at end of file diff --git a/Assets/_PackageRoot/Tests/Editor/TestFuture.Load.cs b/Assets/_PackageRoot/Tests/Editor/TestFuture.Load.cs index e7b11dd..512ae70 100644 --- a/Assets/_PackageRoot/Tests/Editor/TestFuture.Load.cs +++ b/Assets/_PackageRoot/Tests/Editor/TestFuture.Load.cs @@ -9,21 +9,44 @@ public partial class TestFuture [UnityTest] public IEnumerator LoadFrom_Source_NoLogs() => TestUtils.RunNoLogs(LoadFrom_Source); [UnityTest] public IEnumerator LoadFrom_Source() { - yield return LoadFrom_Source(useDiskCache: true, useMemoryCache: true); - yield return LoadFrom_Source(useDiskCache: true, useMemoryCache: false); - yield return LoadFrom_Source(useDiskCache: false, useMemoryCache: true); - yield return LoadFrom_Source(useDiskCache: false, useMemoryCache: false); + yield return LoadFrom_Source(useDiskCache: true, useMemoryCache: true, usePlaceholder: false); + yield return LoadFrom_Source(useDiskCache: true, useMemoryCache: false, usePlaceholder: false); + yield return LoadFrom_Source(useDiskCache: false, useMemoryCache: true, usePlaceholder: false); + yield return LoadFrom_Source(useDiskCache: false, useMemoryCache: false, usePlaceholder: false); + + yield return LoadFrom_Source(useDiskCache: true, useMemoryCache: true, usePlaceholder: true); + yield return LoadFrom_Source(useDiskCache: true, useMemoryCache: false, usePlaceholder: true); + yield return LoadFrom_Source(useDiskCache: false, useMemoryCache: true, usePlaceholder: true); + yield return LoadFrom_Source(useDiskCache: false, useMemoryCache: false, usePlaceholder: true); } - IEnumerator LoadFrom_Source(bool useDiskCache, bool useMemoryCache) + IEnumerator LoadFrom_Source(bool useDiskCache, bool useMemoryCache, bool usePlaceholder) { ImageLoader.settings.useDiskCache = useDiskCache; ImageLoader.settings.useMemoryCache = useMemoryCache; foreach (var url in TestUtils.ImageURLs) - yield return TestUtils.Load(url, FutureLoadingFrom.Source, FutureLoadedFrom.Source); + yield return TestUtils.Load(url, FutureLoadingFrom.Source, FutureLoadedFrom.Source, usePlaceholder: usePlaceholder); yield return TestUtils.ClearEverything(message: null); } + IEnumerator LoadFrom_DiskCache(bool usePlaceholder) + { + foreach (var url in TestUtils.ImageURLs) + { + yield return ImageLoader.LoadSprite(url).AsCoroutine(); + yield return TestUtils.Load(url, FutureLoadingFrom.DiskCache, FutureLoadedFrom.DiskCache, usePlaceholder: usePlaceholder); + } + yield return TestUtils.ClearEverything(message: null); + } + IEnumerator LoadFrom_MemoryCache(bool usePlaceholder) + { + foreach (var url in TestUtils.ImageURLs) + { + yield return ImageLoader.LoadSprite(url).AsCoroutine(); + yield return TestUtils.LoadFromMemoryCache(url, usePlaceholder: usePlaceholder); + } + yield return TestUtils.ClearEverything(message: null); + } [UnityTest] public IEnumerator LoadFrom_DiskCache_NoLogs() => TestUtils.RunNoLogs(LoadFrom_DiskCache); [UnityTest] public IEnumerator LoadFrom_DiskCache() @@ -31,11 +54,8 @@ [UnityTest] public IEnumerator LoadFrom_DiskCache() ImageLoader.settings.useDiskCache = true; ImageLoader.settings.useMemoryCache = false; - foreach (var url in TestUtils.ImageURLs) - { - yield return ImageLoader.LoadSprite(url).AsCoroutine(); - yield return TestUtils.Load(url, FutureLoadingFrom.DiskCache, FutureLoadedFrom.DiskCache); - } + yield return LoadFrom_DiskCache(usePlaceholder: false); + yield return LoadFrom_DiskCache(usePlaceholder: true); } [UnityTest] public IEnumerator LoadFrom_MemoryCache_NoLogs() => TestUtils.RunNoLogs(LoadFrom_MemoryCache); @@ -44,11 +64,8 @@ [UnityTest] public IEnumerator LoadFrom_MemoryCache() ImageLoader.settings.useDiskCache = true; ImageLoader.settings.useMemoryCache = true; - foreach (var url in TestUtils.ImageURLs) - { - yield return ImageLoader.LoadSprite(url).AsCoroutine(); - yield return TestUtils.LoadFromMemoryCache(url); - } + yield return LoadFrom_MemoryCache(usePlaceholder: false); + yield return LoadFrom_MemoryCache(usePlaceholder: true); } } } \ No newline at end of file diff --git a/Assets/_PackageRoot/Tests/Editor/TestFuture.LoadFail.cs b/Assets/_PackageRoot/Tests/Editor/TestFuture.LoadFail.cs index a37b605..4e79aa2 100644 --- a/Assets/_PackageRoot/Tests/Editor/TestFuture.LoadFail.cs +++ b/Assets/_PackageRoot/Tests/Editor/TestFuture.LoadFail.cs @@ -9,18 +9,23 @@ public partial class TestFuture [UnityTest] public IEnumerator LoadFailFrom_Source_NoLogs() => TestUtils.RunNoLogs(LoadFailFrom_Source); [UnityTest] public IEnumerator LoadFailFrom_Source() { - yield return LoadFailFrom_Source(useDiskCache: true, useMemoryCache: true); - yield return LoadFailFrom_Source(useDiskCache: true, useMemoryCache: false); - yield return LoadFailFrom_Source(useDiskCache: false, useMemoryCache: true); - yield return LoadFailFrom_Source(useDiskCache: false, useMemoryCache: false); + yield return LoadFailFrom_Source(useDiskCache: true, useMemoryCache: true, usePlaceholder: false); + yield return LoadFailFrom_Source(useDiskCache: true, useMemoryCache: false, usePlaceholder: false); + yield return LoadFailFrom_Source(useDiskCache: false, useMemoryCache: true, usePlaceholder: false); + yield return LoadFailFrom_Source(useDiskCache: false, useMemoryCache: false, usePlaceholder: false); + + yield return LoadFailFrom_Source(useDiskCache: true, useMemoryCache: true, usePlaceholder: true); + yield return LoadFailFrom_Source(useDiskCache: true, useMemoryCache: false, usePlaceholder: true); + yield return LoadFailFrom_Source(useDiskCache: false, useMemoryCache: true, usePlaceholder: true); + yield return LoadFailFrom_Source(useDiskCache: false, useMemoryCache: false, usePlaceholder: true); } - IEnumerator LoadFailFrom_Source(bool useDiskCache, bool useMemoryCache) + IEnumerator LoadFailFrom_Source(bool useDiskCache, bool useMemoryCache, bool usePlaceholder) { ImageLoader.settings.useDiskCache = useDiskCache; ImageLoader.settings.useMemoryCache = useMemoryCache; foreach (var url in TestUtils.IncorrectImageURLs()) - yield return TestUtils.LoadFail(url, FutureLoadingFrom.Source); + yield return TestUtils.LoadFail(url, FutureLoadingFrom.Source, usePlaceholder: usePlaceholder); yield return TestUtils.ClearEverything(message: null); } diff --git a/Assets/_PackageRoot/Tests/Editor/TestFuture.Placeholder.cs b/Assets/_PackageRoot/Tests/Editor/TestFuture.Placeholder.cs new file mode 100644 index 0000000..151e016 --- /dev/null +++ b/Assets/_PackageRoot/Tests/Editor/TestFuture.Placeholder.cs @@ -0,0 +1,54 @@ +using UnityEngine.TestTools; +using System.Collections; +using Extensions.Unity.ImageLoader.Tests.Utils; + +namespace Extensions.Unity.ImageLoader.Tests +{ + public partial class TestFuture + { + // [UnityTest] public IEnumerator Placeholder_Source_NoLogs() => TestUtils.RunNoLogs(Placeholder_Source); + // [UnityTest] public IEnumerator Placeholder_Source() + // { + // yield return Placeholder_Source(useDiskCache: true, useMemoryCache: true); + // yield return Placeholder_Source(useDiskCache: true, useMemoryCache: false); + // yield return Placeholder_Source(useDiskCache: false, useMemoryCache: true); + // yield return Placeholder_Source(useDiskCache: false, useMemoryCache: false); + // } + // IEnumerator Placeholder_Source(bool useDiskCache, bool useMemoryCache) + // { + // ImageLoader.settings.useDiskCache = useDiskCache; + // ImageLoader.settings.useMemoryCache = useMemoryCache; + + // foreach (var url in TestUtils.ImageURLs) + // yield return TestUtils.Placeholder(url, FutureLoadingFrom.Source, FutureLoadedFrom.Source); + + // yield return TestUtils.ClearEverything(message: null); + // } + + // [UnityTest] public IEnumerator Placeholder_DiskCache_NoLogs() => TestUtils.RunNoLogs(Placeholder_DiskCache); + // [UnityTest] public IEnumerator Placeholder_DiskCache() + // { + // ImageLoader.settings.useDiskCache = true; + // ImageLoader.settings.useMemoryCache = false; + + // foreach (var url in TestUtils.ImageURLs) + // { + // yield return ImageLoader.LoadSprite(url).AsCoroutine(); + // yield return TestUtils.Placeholder(url, FutureLoadingFrom.DiskCache, FutureLoadedFrom.DiskCache); + // } + // } + + // [UnityTest] public IEnumerator Placeholder_MemoryCache_NoLogs() => TestUtils.RunNoLogs(Placeholder_MemoryCache); + // [UnityTest] public IEnumerator Placeholder_MemoryCache() + // { + // ImageLoader.settings.useDiskCache = true; + // ImageLoader.settings.useMemoryCache = true; + + // foreach (var url in TestUtils.ImageURLs) + // { + // yield return ImageLoader.LoadSprite(url).AsCoroutine(); + // yield return TestUtils.LoadFromMemoryCache(url); + // } + // } + } +} \ No newline at end of file diff --git a/Assets/_PackageRoot/Tests/Editor/TestFuture.Placeholder.cs.meta b/Assets/_PackageRoot/Tests/Editor/TestFuture.Placeholder.cs.meta new file mode 100644 index 0000000..e73f8fb --- /dev/null +++ b/Assets/_PackageRoot/Tests/Editor/TestFuture.Placeholder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 65c503c3992353b40ae71d1c7c675a03 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_PackageRoot/Tests/Editor/TestFutureOrder.cs b/Assets/_PackageRoot/Tests/Editor/TestFutureOrder.cs index 356230a..f9e401a 100644 --- a/Assets/_PackageRoot/Tests/Editor/TestFutureOrder.cs +++ b/Assets/_PackageRoot/Tests/Editor/TestFutureOrder.cs @@ -32,11 +32,11 @@ [UnityTest] public IEnumerator EventsLoadedWhenClear() { EventName.LoadingFromSource, EventName.LoadedFromSource, - EventName.Then, + EventName.Loaded, EventName.Completed }); futureListener.Assert_Events_Value(EventName.LoadedFromSource, sprite => sprite != null); - futureListener.Assert_Events_Value(EventName.Then, sprite => sprite != null); + futureListener.Assert_Events_Value(EventName.Loaded, sprite => sprite != null); futureListener.Assert_Events_Value(EventName.Completed, success => ((bool)success) == true); futureListener.Assert_Events_NotContains(EventName.Canceled); diff --git a/Assets/_PackageRoot/Tests/Editor/Utils/TestUtils.Load.cs b/Assets/_PackageRoot/Tests/Editor/Utils/TestUtils.Load.cs deleted file mode 100644 index c2c6a63..0000000 --- a/Assets/_PackageRoot/Tests/Editor/Utils/TestUtils.Load.cs +++ /dev/null @@ -1,165 +0,0 @@ -using System; -using System.Collections; -using System.Linq; -using Cysharp.Threading.Tasks; -using NUnit.Framework; - -namespace Extensions.Unity.ImageLoader.Tests.Utils -{ - internal static partial class TestUtils - { - public static IEnumerator LoadFromMemoryCache(string url) => Load(url, null, FutureLoadedFrom.MemoryCache); - public static IEnumerator Load(string url, FutureLoadingFrom? expectedLoadingFrom, FutureLoadedFrom expectedLoadedFrom) - { - var future = ImageLoader.LoadSprite(url); - var futureListener = future.ToFutureListener(); - - if (expectedLoadingFrom.HasValue) - futureListener.Assert_Events_Contains(expectedLoadingFrom.Value.ToEventName()); - - var task1 = future.AsTask(); - yield return future.TimeoutCoroutine(TimeSpan.FromSeconds(10)); - var task2 = future.AsTask(); - - futureListener.Assert_Events_NotContains(EventName.Canceled); - - if (expectedLoadingFrom.HasValue) - futureListener.Assert_Events_Equals(expectedLoadingFrom.Value.ToEventName(), expectedLoadedFrom.ToEventName(), EventName.Then, EventName.Completed); - else - futureListener.Assert_Events_Equals(expectedLoadedFrom.ToEventName(), EventName.Then, EventName.Completed); - - futureListener.Assert_Events_Value(EventName.Completed, success => success == true); - - Assert.IsTrue(task1.IsCompleted, "Task was not cancelled but Future was cancelled"); - Assert.IsTrue(task2.IsCompleted, "Task was not cancelled but Future was cancelled"); - - yield return UniTask.Yield(); - - if (expectedLoadingFrom.HasValue) - futureListener.Assert_Events_Equals(expectedLoadingFrom.Value.ToEventName(), expectedLoadedFrom.ToEventName(), EventName.Then, EventName.Completed); - else - futureListener.Assert_Events_Equals(expectedLoadedFrom.ToEventName(), EventName.Then, EventName.Completed); - - futureListener.Assert_Events_Value(EventName.Completed, success => success == true); - - future.ToFutureListener(ignoreLoadingWhenLoaded: true) - .Assert_Events_Equals(expectedLoadedFrom.ToEventName(), EventName.Then, EventName.Completed) - .Assert_Events_Value(EventName.Completed, success => success == true); - - if (expectedLoadingFrom.HasValue) - future.ToFutureListener() - .Assert_Events_Equals(expectedLoadingFrom.Value.ToEventName(), expectedLoadedFrom.ToEventName(), EventName.Then, EventName.Completed) - .Assert_Events_Value(EventName.Completed, success => success == true); - - future.Dispose(); - yield return UniTask.Yield(); - } - public static IEnumerator LoadFromMemoryCacheThenCancel(string url, bool useGC) => LoadThenCancel(url, null, FutureLoadedFrom.MemoryCache, useGC); - - // public static IEnumerator LoadThenCancel(string url, FutureLoadingFrom? expectedLoadingFrom, FutureLoadedFrom expectedLoadedFrom) - // { - // yield return LoadThenCancel(url, expectedLoadingFrom, expectedLoadedFrom, useGC: true); - // yield return LoadThenCancel(url, expectedLoadingFrom, expectedLoadedFrom, useGC: false); - // } - public static IEnumerator LoadThenCancel(string url, FutureLoadingFrom? expectedLoadingFrom, FutureLoadedFrom expectedLoadedFrom, bool useGC) - { - var future = ImageLoader.LoadSprite(url); - var futureListener = future.ToFutureListener(); - - if (expectedLoadingFrom.HasValue) - futureListener.Assert_Events_Contains(expectedLoadingFrom.Value.ToEventName()); - - var task1 = future.AsTask(); - yield return future.TimeoutCoroutine(TimeSpan.FromSeconds(10)); - var task2 = future.AsTask(); - - futureListener.Assert_Events_NotContains(EventName.Canceled); - - if (useGC) - TestUtils.WaitForGCFast(); - - future.Cancel(); - - if (expectedLoadingFrom.HasValue) - futureListener.Assert_Events_Equals(expectedLoadingFrom.Value.ToEventName(), expectedLoadedFrom.ToEventName(), EventName.Then, EventName.Completed); - else - futureListener.Assert_Events_Equals(expectedLoadedFrom.ToEventName(), EventName.Then, EventName.Completed); - - futureListener.Assert_Events_Value(EventName.Completed, success => success == true); - - Assert.IsTrue(task1.IsCompleted, "Task was not cancelled but Future was cancelled. Probably the OnCancel subscription was cleaned up too early."); - Assert.IsTrue(task2.IsCompleted, "Task was not cancelled but Future was cancelled. Probably the OnCancel subscription was cleaned up too early."); - - yield return UniTask.Yield(); - - if (expectedLoadingFrom.HasValue) - futureListener.Assert_Events_Equals(expectedLoadingFrom.Value.ToEventName(), expectedLoadedFrom.ToEventName(), EventName.Then, EventName.Completed); - else - futureListener.Assert_Events_Equals(expectedLoadedFrom.ToEventName(), EventName.Then, EventName.Completed); - - futureListener.Assert_Events_Value(EventName.Completed, success => success == true); - - future.ToFutureListener(ignoreLoadingWhenLoaded: true) - .Assert_Events_Equals(expectedLoadedFrom.ToEventName(), EventName.Then, EventName.Completed) - .Assert_Events_Value(EventName.Completed, success => success == true); - - if (expectedLoadingFrom.HasValue) - future.ToFutureListener() - .Assert_Events_Equals(expectedLoadingFrom.Value.ToEventName(), expectedLoadedFrom.ToEventName(), EventName.Then, EventName.Completed) - .Assert_Events_Value(EventName.Completed, success => success == true); - - future.Dispose(); - yield return UniTask.Yield(); - } - public static IEnumerator LoadFromMemoryCacheAndCancel(string url) => LoadAndCancel(url, null); - public static IEnumerator LoadAndCancel(string url, FutureLoadingFrom? expectedLoadingFrom) - { - yield return LoadAndCancel(url, expectedLoadingFrom, useGC: true); - yield return LoadAndCancel(url, expectedLoadingFrom, useGC: false); - } - public static IEnumerator LoadAndCancel(string url, FutureLoadingFrom? expectedLoadingFrom, bool useGC) - { - var future = ImageLoader.LoadSprite(url); - var futureListener = future.ToFutureListener(); - var shouldLoadFromMemoryCache = !expectedLoadingFrom.HasValue; - - futureListener.Assert_Events_Contains(expectedLoadingFrom.HasValue - ? expectedLoadingFrom.Value.ToEventName() - : EventName.LoadedFromMemoryCache); - - if (useGC) - TestUtils.WaitForGCFast(); - - var task1 = future.AsTask(); - future.Cancel(); - var task2 = future.AsTask(); - - var events = shouldLoadFromMemoryCache - ? new [] { EventName.LoadedFromMemoryCache, EventName.Then, EventName.Completed } - : new [] { expectedLoadingFrom.Value.ToEventName(), EventName.Canceled, EventName.Completed }; - - futureListener.Assert_Events_Equals(events); - futureListener.Assert_Events_Value(EventName.Completed, success => success == shouldLoadFromMemoryCache); - - Assert.IsTrue(task1.IsCompleted, "Task was not cancelled but Future was cancelled. Probably the OnCancel subscription was cleaned up too early."); - Assert.IsTrue(task2.IsCompleted, "Task was not cancelled but Future was cancelled. Probably the OnCancel subscription was cleaned up too early."); - - yield return UniTask.Yield(); - - futureListener.Assert_Events_Equals(events); - futureListener.Assert_Events_Value(EventName.Completed, success => success == shouldLoadFromMemoryCache); - - future.ToFutureListener() - .Assert_Events_Equals(events) - .Assert_Events_Value(EventName.Completed, success => success == shouldLoadFromMemoryCache); - - if (expectedLoadingFrom.HasValue && future.IsLoaded) - future.ToFutureListener(ignoreLoadingWhenLoaded: true) - .Assert_Events_Equals(events.Except(new [] { expectedLoadingFrom.Value.ToEventName() })) - .Assert_Events_Value(EventName.Completed, success => success == shouldLoadFromMemoryCache); - - future.Dispose(); - yield return UniTask.Yield(); - } - } -} \ No newline at end of file diff --git a/Assets/_PackageRoot/Tests/Editor/Utils/TestUtils.LoadFail.cs b/Assets/_PackageRoot/Tests/Editor/Utils/TestUtils.LoadFail.cs deleted file mode 100644 index 0173d7c..0000000 --- a/Assets/_PackageRoot/Tests/Editor/Utils/TestUtils.LoadFail.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Collections; -using Cysharp.Threading.Tasks; -using NUnit.Framework; -using UnityEngine; -using UnityEngine.TestTools; - -namespace Extensions.Unity.ImageLoader.Tests.Utils -{ - internal static partial class TestUtils - { - public static IEnumerator LoadFailFromMemoryCache(string url) => LoadFail(url, null); - public static IEnumerator LoadFail(string url, FutureLoadingFrom? expectedLoadingFrom) - { - var timeout = TimeSpan.FromMilliseconds(100); - var future = ImageLoader.LoadSprite(url).Timeout(timeout); - var futureListener = future.ToFutureListener(); - - if (expectedLoadingFrom.HasValue) - futureListener.Assert_Events_Contains(expectedLoadingFrom.Value.ToEventName()); - - var task1 = future.AsTask(); - if (expectedLoadingFrom.HasValue && expectedLoadingFrom.Value == FutureLoadingFrom.Source) // exception should be thrown only if ONLY loading from Source - LogAssert.Expect(LogType.Error, $"[ImageLoader] Future[id={future.Id}] Timeout ({timeout}): {url}"); - yield return TestUtils.WaitWhile(() => future.IsInProgress, TimeSpan.FromSeconds(10)); - var task2 = future.AsTask(); - - var events = expectedLoadingFrom.HasValue - ? new[] { expectedLoadingFrom.Value.ToEventName(), EventName.Failed, EventName.Completed } - : new[] { EventName.Failed, EventName.Completed}; - - futureListener.Assert_Events_Equals(events); - futureListener.Assert_Events_Value(EventName.Completed, success => success == false); - - Assert.IsTrue(task1.IsCompleted, "Task was not cancelled but Future was cancelled"); - Assert.IsTrue(task2.IsCompleted, "Task was not cancelled but Future was cancelled"); - - yield return UniTask.Yield(); - - futureListener.Assert_Events_Equals(events); - futureListener.Assert_Events_Value(EventName.Completed, success => success == false); - - future.ToFutureListener(ignoreLoadingWhenLoaded: true) - .Assert_Events_Equals(events) - .Assert_Events_Value(EventName.Completed, success => success == false); - - if (expectedLoadingFrom.HasValue) - future.ToFutureListener() - .Assert_Events_Equals(events) - .Assert_Events_Value(EventName.Completed, success => success == false); - - future.Dispose(); - yield return UniTask.Yield(); - } - } -} \ No newline at end of file diff --git a/Assets/_PackageRoot/Tests/Runtime/Extensions.Unity.ImageLoader.Tests.Runtime.asmdef b/Assets/_PackageRoot/Tests/Runtime/Extensions.Unity.ImageLoader.Tests.Runtime.asmdef new file mode 100644 index 0000000..91b1c32 --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/Extensions.Unity.ImageLoader.Tests.Runtime.asmdef @@ -0,0 +1,20 @@ +{ + "name": "Extensions.Unity.ImageLoader.Tests.Runtime", + "references": [ + "Extensions.Unity.ImageLoader.Tests", + "Extensions.Unity.ImageLoader", + "UnityEngine.TestRunner", + "UniTask" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [ + "NSubstitute.dll" + ], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Assets/_PackageRoot/Tests/Runtime/Extensions.Unity.ImageLoader.Tests.Runtime.asmdef.meta b/Assets/_PackageRoot/Tests/Runtime/Extensions.Unity.ImageLoader.Tests.Runtime.asmdef.meta new file mode 100644 index 0000000..e396ffc --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/Extensions.Unity.ImageLoader.Tests.Runtime.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: b61a7e38a135d044baa1959a8002e39d +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_PackageRoot/Tests/Runtime/Test.cs b/Assets/_PackageRoot/Tests/Runtime/Test.cs new file mode 100644 index 0000000..6d6270a --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/Test.cs @@ -0,0 +1,21 @@ +using System.Collections; +using Extensions.Unity.ImageLoader.Tests.Utils; +using System; + +namespace Extensions.Unity.ImageLoader.Tests +{ + public class Test + { + public virtual IEnumerator SetUp() + { + yield return TestUtils.ClearEverything("Test Start "); + ImageLoader.settings.debugLevel = DebugLevel.Trace; + } + public virtual IEnumerator TearDown() + { + yield return TestUtils.WaitWhile(() => ImageLoader.GetLoadingSpriteFutures().Count > 0, TimeSpan.FromSeconds(10)); + yield return TestUtils.WaitWhile(() => ImageLoader.GetLoadingTextureFutures().Count > 0, TimeSpan.FromSeconds(10)); + yield return TestUtils.ClearEverything("Test End "); + } + } +} \ No newline at end of file diff --git a/Assets/_PackageRoot/Tests/Runtime/Test.cs.meta b/Assets/_PackageRoot/Tests/Runtime/Test.cs.meta new file mode 100644 index 0000000..6678538 --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/Test.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 12ce6cb65d862ce4ca4c64675c18c481 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_PackageRoot/Tests/Runtime/TestCache.cs b/Assets/_PackageRoot/Tests/Runtime/TestCache.cs new file mode 100644 index 0000000..8caa41d --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/TestCache.cs @@ -0,0 +1,146 @@ +using NUnit.Framework; +using Cysharp.Threading.Tasks; +using UnityEngine.TestTools; +using System.Collections; +using Extensions.Unity.ImageLoader.Tests.Utils; +using System; + +namespace Extensions.Unity.ImageLoader.Tests +{ + public class TestCache : Test + { + [UnitySetUp] public override IEnumerator SetUp() => base.SetUp(); + [UnityTearDown] public override IEnumerator TearDown() => base.TearDown(); + + public async UniTask LoadSprite(string url) + { + var sprite = await ImageLoader.LoadSprite(url); + Assert.IsNotNull(sprite); + } + + [UnityTest] public IEnumerator LoadingFromMemoryCache_NoLogs() => TestUtils.RunNoLogs(LoadingFromMemoryCache); + [UnityTest] public IEnumerator LoadingFromMemoryCache() + { + ImageLoader.settings.useMemoryCache = true; + ImageLoader.settings.useDiskCache = false; + + foreach (var imageURL in TestUtils.ImageURLs) + { + Assert.IsFalse(ImageLoader.MemoryCacheContains(imageURL)); + yield return LoadSprite(imageURL).TimeoutCoroutine(TimeSpan.FromSeconds(10)); + Assert.IsTrue(ImageLoader.MemoryCacheContains(imageURL)); + } + } + [UnityTest] public IEnumerator LoadingFromDiskCache_NoLogs() => TestUtils.RunNoLogs(LoadingFromDiskCache); + [UnityTest] public IEnumerator LoadingFromDiskCache() + { + ImageLoader.settings.useMemoryCache = false; + ImageLoader.settings.useDiskCache = true; + + foreach (var imageURL in TestUtils.ImageURLs) + { + Assert.IsFalse(ImageLoader.DiskCacheContains(imageURL)); + yield return LoadSprite(imageURL).TimeoutCoroutine(TimeSpan.FromSeconds(10)); + Assert.IsTrue(ImageLoader.DiskCacheContains(imageURL)); + } + } + [UnityTest] public IEnumerator DiskCacheEnable_NoLogs() => TestUtils.RunNoLogs(DiskCacheEnable); + [UnityTest] public IEnumerator DiskCacheEnable() + { + ImageLoader.settings.useDiskCache = true; + + foreach (var imageURL in TestUtils.ImageURLs) + { + yield return LoadSprite(imageURL).TimeoutCoroutine(TimeSpan.FromSeconds(10)); + Assert.IsTrue(ImageLoader.DiskCacheContains(imageURL)); + } + } + [UnityTest] public IEnumerator DiskCacheDisable_NoLogs() => TestUtils.RunNoLogs(DiskCacheDisable); + [UnityTest] public IEnumerator DiskCacheDisable() + { + ImageLoader.settings.useDiskCache = false; + + foreach (var imageURL in TestUtils.ImageURLs) + { + yield return LoadSprite(imageURL).TimeoutCoroutine(TimeSpan.FromSeconds(10)); + Assert.IsFalse(ImageLoader.DiskCacheContains(imageURL)); + } + } + [UnityTest] public IEnumerator MemoryCacheEnabled_NoLogs() => TestUtils.RunNoLogs(MemoryCacheEnabled); + [UnityTest] public IEnumerator MemoryCacheEnabled() + { + ImageLoader.settings.useMemoryCache = true; + + foreach (var imageURL in TestUtils.ImageURLs) + { + yield return LoadSprite(imageURL).TimeoutCoroutine(TimeSpan.FromSeconds(10)); + Assert.IsTrue(ImageLoader.MemoryCacheContains(imageURL)); + } + } + [UnityTest] public IEnumerator MemoryCacheDisabled_NoLogs() => TestUtils.RunNoLogs(MemoryCacheDisabled); + [UnityTest] public IEnumerator MemoryCacheDisabled() + { + ImageLoader.settings.useMemoryCache = false; + + foreach (var imageURL in TestUtils.ImageURLs) + { + yield return LoadSprite(imageURL).TimeoutCoroutine(TimeSpan.FromSeconds(10)); + Assert.IsFalse(ImageLoader.MemoryCacheContains(imageURL)); + } + } + [UnityTest] public IEnumerator ClearDiskCache_NoLogs() => TestUtils.RunNoLogs(ClearDiskCache); + [UnityTest] public IEnumerator ClearDiskCache() + { + ImageLoader.settings.useDiskCache = true; + + foreach (var imageURL in TestUtils.ImageURLs) + { + yield return LoadSprite(imageURL).TimeoutCoroutine(TimeSpan.FromSeconds(10)); + Assert.IsTrue(ImageLoader.DiskCacheContains(imageURL)); + yield return ImageLoader.ClearDiskCacheAll().TimeoutCoroutine(TimeSpan.FromSeconds(10)); + Assert.IsFalse(ImageLoader.DiskCacheContains(imageURL)); + } + } + [UnityTest] public IEnumerator ClearMemoryCache_NoLogs() => TestUtils.RunNoLogs(ClearMemoryCache); + [UnityTest] public IEnumerator ClearMemoryCache() + { + ImageLoader.settings.useMemoryCache = true; + + foreach (var imageURL in TestUtils.ImageURLs) + { + yield return LoadSprite(imageURL).TimeoutCoroutine(TimeSpan.FromSeconds(10)); + Assert.IsTrue(ImageLoader.MemoryCacheContains(imageURL)); + ImageLoader.ClearMemoryCache(imageURL); + Assert.IsFalse(ImageLoader.MemoryCacheContains(imageURL)); + } + } + [UnityTest] public IEnumerator ClearDiskCacheAll_NoLogs() => TestUtils.RunNoLogs(ClearDiskCacheAll); + [UnityTest] public IEnumerator ClearDiskCacheAll() + { + ImageLoader.settings.useDiskCache = true; + + foreach (var imageURL in TestUtils.ImageURLs) + { + yield return LoadSprite(imageURL).TimeoutCoroutine(TimeSpan.FromSeconds(10)); + Assert.IsTrue(ImageLoader.DiskCacheContains(imageURL)); + } + yield return ImageLoader.ClearDiskCacheAll().TimeoutCoroutine(TimeSpan.FromSeconds(10)); + foreach (var imageURL in TestUtils.ImageURLs) + Assert.IsFalse(ImageLoader.DiskCacheContains(imageURL)); + } + [UnityTest] public IEnumerator ClearMemoryCacheAll_NoLogs() => TestUtils.RunNoLogs(ClearMemoryCacheAll); + [UnityTest] public IEnumerator ClearMemoryCacheAll() + { + ImageLoader.settings.useMemoryCache = true; + + foreach (var imageURL in TestUtils.ImageURLs) + { + yield return LoadSprite(imageURL).TimeoutCoroutine(TimeSpan.FromSeconds(10)); + Assert.IsTrue(ImageLoader.MemoryCacheContains(imageURL)); + } + ImageLoader.ClearMemoryCacheAll(); + foreach (var imageURL in TestUtils.ImageURLs) + Assert.IsFalse(ImageLoader.MemoryCacheContains(imageURL)); + } + } +} \ No newline at end of file diff --git a/Assets/_PackageRoot/Tests/Runtime/TestCache.cs.meta b/Assets/_PackageRoot/Tests/Runtime/TestCache.cs.meta new file mode 100644 index 0000000..8986a3d --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/TestCache.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6b5110b05db07284291eda199b280ab6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_PackageRoot/Tests/Runtime/TestConcurrency.cs b/Assets/_PackageRoot/Tests/Runtime/TestConcurrency.cs new file mode 100644 index 0000000..546d16a --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/TestConcurrency.cs @@ -0,0 +1,121 @@ +using System; +using System.Linq; +using System.Collections; +using NUnit.Framework; +using Cysharp.Threading.Tasks; +using UnityEngine.TestTools; +using UnityEngine; +using System.Threading.Tasks; +using Extensions.Unity.ImageLoader.Tests.Utils; + +namespace Extensions.Unity.ImageLoader.Tests +{ + public class TestConcurrency : Test + { + [UnitySetUp] public override IEnumerator SetUp() => base.SetUp(); + [UnityTearDown] public override IEnumerator TearDown() => base.TearDown(); + + [UnityTest] public IEnumerator Load____1_ReferencesInParallelLateDispose_NoLogs() => TestUtils.RunNoLogs(Load____1_ReferencesInParallelLateDispose); + [UnityTest] public IEnumerator Load____1_ReferencesInParallelLateDispose() + { + yield return Load_X_ReferencesInParallelLateDispose(1); + } + [UnityTest] public IEnumerator Load____2_ReferencesInParallelLateDispose_NoLogs() => TestUtils.RunNoLogs(Load____2_ReferencesInParallelLateDispose); + [UnityTest] public IEnumerator Load____2_ReferencesInParallelLateDispose() + { + yield return Load_X_ReferencesInParallelLateDispose(2); + } + [UnityTest] public IEnumerator Load____5_ReferencesInParallelLateDispose_NoLogs() => TestUtils.RunNoLogs(Load____5_ReferencesInParallelLateDispose); + [UnityTest] public IEnumerator Load____5_ReferencesInParallelLateDispose() + { + yield return Load_X_ReferencesInParallelLateDispose(5); + } + [UnityTest] public IEnumerator Load_1000_ReferencesInParallelLateDispose_NoLogs() => TestUtils.RunNoLogs(Load_1000_ReferencesInParallelLateDispose); + [UnityTest] public IEnumerator Load_1000_ReferencesInParallelLateDispose() + { + yield return Load_X_ReferencesInParallelLateDispose(1000); + } + public IEnumerator Load_X_ReferencesInParallelLateDispose(int count, bool futureDispose = false) + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = true; + + var url = TestUtils.ImageURLs[0]; + + var cts = new System.Threading.CancellationTokenSource(); + cts.CancelAfterSlim(TimeSpan.FromSeconds(25) + TimeSpan.FromMilliseconds(5 * count)); + + var tasks = Enumerable.Range(0, count) + .Select(i => Task.Run(async () => + { + var futureRef = ImageLoader.LoadSpriteRef(url); + Assert.NotNull(futureRef); + var result = await futureRef; + if (futureDispose) + futureRef.Dispose(); + return result; + })) + .ToArray(); + + var waitTask = Task.WhenAll(tasks); + + while (!waitTask.IsCompleted && !cts.Token.IsCancellationRequested) + yield return UniTask.Yield(); + + Assert.False(cts.Token.IsCancellationRequested, "Timeout"); + Assert.AreEqual(count, Reference.Counter(url)); + + foreach (var reference in tasks.Select(task => task.Result)) + reference.Dispose(); + Assert.AreEqual(0, Reference.Counter(url)); + } + + [UnityTest] public IEnumerator LoadOneMake____5_ReferencesInParallelLateDispose_NoLogs() => TestUtils.RunNoLogs(LoadOneMake____5_ReferencesInParallelLateDispose); + [UnityTest] public IEnumerator LoadOneMake____5_ReferencesInParallelLateDispose() + { + yield return LoadOneMake_X_ReferencesInParallelLateDispose(5); + } + [UnityTest] public IEnumerator LoadOneMake_1000_ReferencesInParallelLateDispose_NoLogs() => TestUtils.RunNoLogs(LoadOneMake_1000_ReferencesInParallelLateDispose); + [UnityTest] public IEnumerator LoadOneMake_1000_ReferencesInParallelLateDispose() + { + yield return LoadOneMake_X_ReferencesInParallelLateDispose(1000); + } + public IEnumerator LoadOneMake_X_ReferencesInParallelLateDispose(int count) + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = true; + + var url = TestUtils.ImageURLs[0]; + + var future = ImageLoader.LoadSpriteRef(url); + var task1 = future.AsTask(); + + yield return task1.TimeoutCoroutine(TimeSpan.FromSeconds(25)); + + Assert.AreEqual(1, Reference.Counter(url)); + + var ref1 = task1.Result; + Assert.NotNull(ref1); + + var references = new Reference[count]; + var tasks = Enumerable.Range(0, count) + .Select(i => Task.Run(() => + { + var ref0 = ImageLoader.LoadSpriteRefFromMemoryCache(url); + Assert.NotNull(ref0); + return ref0; + })) + .ToArray(); + + yield return Task.WhenAll(tasks).TimeoutCoroutine(TimeSpan.FromSeconds(10)); + Assert.AreEqual(count + 1, Reference.Counter(url)); + + foreach (var reference in tasks.Select(task => task.Result)) + reference.Dispose(); + Assert.AreEqual(1, Reference.Counter(url)); + + ref1.Dispose(); + Assert.AreEqual(0, Reference.Counter(url)); + } + } +} \ No newline at end of file diff --git a/Assets/_PackageRoot/Tests/Runtime/TestConcurrency.cs.meta b/Assets/_PackageRoot/Tests/Runtime/TestConcurrency.cs.meta new file mode 100644 index 0000000..79436cd --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/TestConcurrency.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 77b7564380bed854d9016c956c23fc38 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_PackageRoot/Tests/Runtime/TestFuture.Load.AndCancel.cs b/Assets/_PackageRoot/Tests/Runtime/TestFuture.Load.AndCancel.cs new file mode 100644 index 0000000..e918937 --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/TestFuture.Load.AndCancel.cs @@ -0,0 +1,70 @@ +using UnityEngine.TestTools; +using System.Collections; +using Extensions.Unity.ImageLoader.Tests.Utils; + +namespace Extensions.Unity.ImageLoader.Tests +{ + public partial class TestFuture + { + [UnityTest] public IEnumerator LoadFrom_Source_AndCancel_NoLogs() => TestUtils.RunNoLogs(LoadFrom_Source_AndCancel); + [UnityTest] public IEnumerator LoadFrom_Source_AndCancel() + { + yield return LoadFrom_Source_AndCancel(useDiskCache: true, useMemoryCache: true, usePlaceholder: false); + yield return LoadFrom_Source_AndCancel(useDiskCache: true, useMemoryCache: false, usePlaceholder: false); + yield return LoadFrom_Source_AndCancel(useDiskCache: false, useMemoryCache: true, usePlaceholder: false); + yield return LoadFrom_Source_AndCancel(useDiskCache: false, useMemoryCache: false, usePlaceholder: false); + + yield return LoadFrom_Source_AndCancel(useDiskCache: true, useMemoryCache: true, usePlaceholder: true); + yield return LoadFrom_Source_AndCancel(useDiskCache: true, useMemoryCache: false, usePlaceholder: true); + yield return LoadFrom_Source_AndCancel(useDiskCache: false, useMemoryCache: true, usePlaceholder: true); + yield return LoadFrom_Source_AndCancel(useDiskCache: false, useMemoryCache: false, usePlaceholder: true); + } + IEnumerator LoadFrom_Source_AndCancel(bool useDiskCache, bool useMemoryCache, bool usePlaceholder) + { + ImageLoader.settings.useDiskCache = useDiskCache; + ImageLoader.settings.useMemoryCache = useMemoryCache; + + foreach (var url in TestUtils.ImageURLs) + yield return TestUtils.LoadAndCancel(url, FutureLoadingFrom.Source); + + yield return TestUtils.ClearEverything(message: null); + } + IEnumerator LoadFrom_MemoryCache_AndCancel(bool usePlaceholder) + { + foreach (var url in TestUtils.ImageURLs) + { + yield return ImageLoader.LoadSprite(url).AsCoroutine(); + yield return TestUtils.LoadFromMemoryCacheAndCancel(url, usePlaceholder: usePlaceholder); + } + yield return TestUtils.ClearEverything(message: null); + } + IEnumerator LoadFrom_DiskCache_AndCancel(bool usePlaceholder) + { + foreach (var url in TestUtils.ImageURLs) + { + yield return ImageLoader.LoadSprite(url).AsCoroutine(); + yield return TestUtils.LoadAndCancel(url, FutureLoadingFrom.DiskCache, usePlaceholder: usePlaceholder); + } + yield return TestUtils.ClearEverything(message: null); + } + [UnityTest] public IEnumerator LoadFrom_MemoryCache_AndCancel_NoLogs() => TestUtils.RunNoLogs(LoadFrom_MemoryCache_AndCancel); + [UnityTest] public IEnumerator LoadFrom_MemoryCache_AndCancel() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = true; + + yield return LoadFrom_MemoryCache_AndCancel(usePlaceholder: false); + yield return LoadFrom_MemoryCache_AndCancel(usePlaceholder: true); + } + + [UnityTest] public IEnumerator LoadFrom_DiskCache_AndCancel_NoLogs() => TestUtils.RunNoLogs(LoadFrom_DiskCache_AndCancel); + [UnityTest] public IEnumerator LoadFrom_DiskCache_AndCancel() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = false; + + yield return LoadFrom_DiskCache_AndCancel(usePlaceholder: false); + yield return LoadFrom_DiskCache_AndCancel(usePlaceholder: true); + } + } +} \ No newline at end of file diff --git a/Assets/_PackageRoot/Tests/Runtime/TestFuture.Load.AndCancel.cs.meta b/Assets/_PackageRoot/Tests/Runtime/TestFuture.Load.AndCancel.cs.meta new file mode 100644 index 0000000..cf5f315 --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/TestFuture.Load.AndCancel.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 04ab48db728d9c545b09c12b0858767d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_PackageRoot/Tests/Runtime/TestFuture.Load.ThenCancel.cs b/Assets/_PackageRoot/Tests/Runtime/TestFuture.Load.ThenCancel.cs new file mode 100644 index 0000000..430703d --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/TestFuture.Load.ThenCancel.cs @@ -0,0 +1,87 @@ +using UnityEngine.TestTools; +using System.Collections; +using Extensions.Unity.ImageLoader.Tests.Utils; + +namespace Extensions.Unity.ImageLoader.Tests +{ + public partial class TestFuture + { + [UnityTest] public IEnumerator LoadFrom_Source_ThenCancel_NoLogs() => TestUtils.RunNoLogs(LoadFrom_Source_ThenCancel); + [UnityTest] public IEnumerator LoadFrom_Source_ThenCancel() + { + yield return LoadFrom_Source_ThenCancel(useDiskCache: true, useMemoryCache: true, useGC: true, usePlaceholder: false); + yield return LoadFrom_Source_ThenCancel(useDiskCache: true, useMemoryCache: false, useGC: true, usePlaceholder: false); + yield return LoadFrom_Source_ThenCancel(useDiskCache: false, useMemoryCache: true, useGC: true, usePlaceholder: false); + yield return LoadFrom_Source_ThenCancel(useDiskCache: false, useMemoryCache: false, useGC: true, usePlaceholder: false); + + yield return LoadFrom_Source_ThenCancel(useDiskCache: true, useMemoryCache: true, useGC: false, usePlaceholder: false); + yield return LoadFrom_Source_ThenCancel(useDiskCache: true, useMemoryCache: false, useGC: false, usePlaceholder: false); + yield return LoadFrom_Source_ThenCancel(useDiskCache: false, useMemoryCache: true, useGC: false, usePlaceholder: false); + yield return LoadFrom_Source_ThenCancel(useDiskCache: false, useMemoryCache: false, useGC: false, usePlaceholder: false); + + yield return LoadFrom_Source_ThenCancel(useDiskCache: true, useMemoryCache: true, useGC: true, usePlaceholder: true); + yield return LoadFrom_Source_ThenCancel(useDiskCache: true, useMemoryCache: false, useGC: true, usePlaceholder: true); + yield return LoadFrom_Source_ThenCancel(useDiskCache: false, useMemoryCache: true, useGC: true, usePlaceholder: true); + yield return LoadFrom_Source_ThenCancel(useDiskCache: false, useMemoryCache: false, useGC: true, usePlaceholder: true); + + yield return LoadFrom_Source_ThenCancel(useDiskCache: true, useMemoryCache: true, useGC: false, usePlaceholder: true); + yield return LoadFrom_Source_ThenCancel(useDiskCache: true, useMemoryCache: false, useGC: false, usePlaceholder: true); + yield return LoadFrom_Source_ThenCancel(useDiskCache: false, useMemoryCache: true, useGC: false, usePlaceholder: true); + yield return LoadFrom_Source_ThenCancel(useDiskCache: false, useMemoryCache: false, useGC: false, usePlaceholder: true); + } + IEnumerator LoadFrom_Source_ThenCancel(bool useDiskCache, bool useMemoryCache, bool useGC, bool usePlaceholder) + { + ImageLoader.settings.useDiskCache = useDiskCache; + ImageLoader.settings.useMemoryCache = useMemoryCache; + + foreach (var url in TestUtils.ImageURLs) + yield return TestUtils.LoadThenCancel(url, FutureLoadingFrom.Source, FutureLoadedFrom.Source, useGC: useGC, usePlaceholder: usePlaceholder); + + yield return TestUtils.ClearEverything(message: null); + } + IEnumerator LoadFrom_MemoryCache_ThenCancel(bool useGC, bool usePlaceholder) + { + foreach (var url in TestUtils.ImageURLs) + { + yield return ImageLoader.LoadSprite(url).AsCoroutine(); + yield return TestUtils.LoadFromMemoryCacheThenCancel(url, useGC: useGC, usePlaceholder: usePlaceholder); + } + yield return TestUtils.ClearEverything(message: null); + } + IEnumerator LoadFrom_DiskCache_ThenCancel(bool useGC, bool usePlaceholder) + { + foreach (var url in TestUtils.ImageURLs) + { + yield return ImageLoader.LoadSprite(url).AsCoroutine(); + yield return TestUtils.LoadThenCancel(url, FutureLoadingFrom.DiskCache, FutureLoadedFrom.DiskCache, useGC: useGC, usePlaceholder: usePlaceholder); + } + yield return TestUtils.ClearEverything(message: null); + } + + [UnityTest] public IEnumerator LoadFrom_MemoryCache_ThenCancel_NoLogs() => TestUtils.RunNoLogs(LoadFrom_MemoryCache_ThenCancel); + [UnityTest] public IEnumerator LoadFrom_MemoryCache_ThenCancel() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = true; + + yield return LoadFrom_MemoryCache_ThenCancel(useGC: true, usePlaceholder: false); + yield return LoadFrom_MemoryCache_ThenCancel(useGC: false, usePlaceholder: false); + + yield return LoadFrom_MemoryCache_ThenCancel(useGC: true, usePlaceholder: true); + yield return LoadFrom_MemoryCache_ThenCancel(useGC: false, usePlaceholder: true); + } + + [UnityTest] public IEnumerator LoadFrom_DiskCache_ThenCancel_NoLogs() => TestUtils.RunNoLogs(LoadFrom_DiskCache_ThenCancel); + [UnityTest] public IEnumerator LoadFrom_DiskCache_ThenCancel() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = false; + + yield return LoadFrom_DiskCache_ThenCancel(useGC: true, usePlaceholder: false); + yield return LoadFrom_DiskCache_ThenCancel(useGC: false, usePlaceholder: false); + + yield return LoadFrom_DiskCache_ThenCancel(useGC: true, usePlaceholder: true); + yield return LoadFrom_DiskCache_ThenCancel(useGC: false, usePlaceholder: true); + } + } +} \ No newline at end of file diff --git a/Assets/_PackageRoot/Tests/Runtime/TestFuture.Load.ThenCancel.cs.meta b/Assets/_PackageRoot/Tests/Runtime/TestFuture.Load.ThenCancel.cs.meta new file mode 100644 index 0000000..acd9c4a --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/TestFuture.Load.ThenCancel.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 68dc40a8db6573f4a9f13deddbd391fe +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_PackageRoot/Tests/Runtime/TestFuture.Load.cs b/Assets/_PackageRoot/Tests/Runtime/TestFuture.Load.cs new file mode 100644 index 0000000..512ae70 --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/TestFuture.Load.cs @@ -0,0 +1,71 @@ +using UnityEngine.TestTools; +using System.Collections; +using Extensions.Unity.ImageLoader.Tests.Utils; + +namespace Extensions.Unity.ImageLoader.Tests +{ + public partial class TestFuture + { + [UnityTest] public IEnumerator LoadFrom_Source_NoLogs() => TestUtils.RunNoLogs(LoadFrom_Source); + [UnityTest] public IEnumerator LoadFrom_Source() + { + yield return LoadFrom_Source(useDiskCache: true, useMemoryCache: true, usePlaceholder: false); + yield return LoadFrom_Source(useDiskCache: true, useMemoryCache: false, usePlaceholder: false); + yield return LoadFrom_Source(useDiskCache: false, useMemoryCache: true, usePlaceholder: false); + yield return LoadFrom_Source(useDiskCache: false, useMemoryCache: false, usePlaceholder: false); + + yield return LoadFrom_Source(useDiskCache: true, useMemoryCache: true, usePlaceholder: true); + yield return LoadFrom_Source(useDiskCache: true, useMemoryCache: false, usePlaceholder: true); + yield return LoadFrom_Source(useDiskCache: false, useMemoryCache: true, usePlaceholder: true); + yield return LoadFrom_Source(useDiskCache: false, useMemoryCache: false, usePlaceholder: true); + } + IEnumerator LoadFrom_Source(bool useDiskCache, bool useMemoryCache, bool usePlaceholder) + { + ImageLoader.settings.useDiskCache = useDiskCache; + ImageLoader.settings.useMemoryCache = useMemoryCache; + + foreach (var url in TestUtils.ImageURLs) + yield return TestUtils.Load(url, FutureLoadingFrom.Source, FutureLoadedFrom.Source, usePlaceholder: usePlaceholder); + + yield return TestUtils.ClearEverything(message: null); + } + IEnumerator LoadFrom_DiskCache(bool usePlaceholder) + { + foreach (var url in TestUtils.ImageURLs) + { + yield return ImageLoader.LoadSprite(url).AsCoroutine(); + yield return TestUtils.Load(url, FutureLoadingFrom.DiskCache, FutureLoadedFrom.DiskCache, usePlaceholder: usePlaceholder); + } + yield return TestUtils.ClearEverything(message: null); + } + IEnumerator LoadFrom_MemoryCache(bool usePlaceholder) + { + foreach (var url in TestUtils.ImageURLs) + { + yield return ImageLoader.LoadSprite(url).AsCoroutine(); + yield return TestUtils.LoadFromMemoryCache(url, usePlaceholder: usePlaceholder); + } + yield return TestUtils.ClearEverything(message: null); + } + + [UnityTest] public IEnumerator LoadFrom_DiskCache_NoLogs() => TestUtils.RunNoLogs(LoadFrom_DiskCache); + [UnityTest] public IEnumerator LoadFrom_DiskCache() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = false; + + yield return LoadFrom_DiskCache(usePlaceholder: false); + yield return LoadFrom_DiskCache(usePlaceholder: true); + } + + [UnityTest] public IEnumerator LoadFrom_MemoryCache_NoLogs() => TestUtils.RunNoLogs(LoadFrom_MemoryCache); + [UnityTest] public IEnumerator LoadFrom_MemoryCache() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = true; + + yield return LoadFrom_MemoryCache(usePlaceholder: false); + yield return LoadFrom_MemoryCache(usePlaceholder: true); + } + } +} \ No newline at end of file diff --git a/Assets/_PackageRoot/Tests/Runtime/TestFuture.Load.cs.meta b/Assets/_PackageRoot/Tests/Runtime/TestFuture.Load.cs.meta new file mode 100644 index 0000000..2d51398 --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/TestFuture.Load.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0946b807bf14d1e40a33bc952b23ef90 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_PackageRoot/Tests/Runtime/TestFuture.LoadFail.cs b/Assets/_PackageRoot/Tests/Runtime/TestFuture.LoadFail.cs new file mode 100644 index 0000000..4e79aa2 --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/TestFuture.LoadFail.cs @@ -0,0 +1,33 @@ +using UnityEngine.TestTools; +using System.Collections; +using Extensions.Unity.ImageLoader.Tests.Utils; + +namespace Extensions.Unity.ImageLoader.Tests +{ + public partial class TestFuture + { + [UnityTest] public IEnumerator LoadFailFrom_Source_NoLogs() => TestUtils.RunNoLogs(LoadFailFrom_Source); + [UnityTest] public IEnumerator LoadFailFrom_Source() + { + yield return LoadFailFrom_Source(useDiskCache: true, useMemoryCache: true, usePlaceholder: false); + yield return LoadFailFrom_Source(useDiskCache: true, useMemoryCache: false, usePlaceholder: false); + yield return LoadFailFrom_Source(useDiskCache: false, useMemoryCache: true, usePlaceholder: false); + yield return LoadFailFrom_Source(useDiskCache: false, useMemoryCache: false, usePlaceholder: false); + + yield return LoadFailFrom_Source(useDiskCache: true, useMemoryCache: true, usePlaceholder: true); + yield return LoadFailFrom_Source(useDiskCache: true, useMemoryCache: false, usePlaceholder: true); + yield return LoadFailFrom_Source(useDiskCache: false, useMemoryCache: true, usePlaceholder: true); + yield return LoadFailFrom_Source(useDiskCache: false, useMemoryCache: false, usePlaceholder: true); + } + IEnumerator LoadFailFrom_Source(bool useDiskCache, bool useMemoryCache, bool usePlaceholder) + { + ImageLoader.settings.useDiskCache = useDiskCache; + ImageLoader.settings.useMemoryCache = useMemoryCache; + + foreach (var url in TestUtils.IncorrectImageURLs()) + yield return TestUtils.LoadFail(url, FutureLoadingFrom.Source, usePlaceholder: usePlaceholder); + + yield return TestUtils.ClearEverything(message: null); + } + } +} \ No newline at end of file diff --git a/Assets/_PackageRoot/Tests/Runtime/TestFuture.LoadFail.cs.meta b/Assets/_PackageRoot/Tests/Runtime/TestFuture.LoadFail.cs.meta new file mode 100644 index 0000000..cc6c5d7 --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/TestFuture.LoadFail.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0bb6788ffce20d5479763082e87a724d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_PackageRoot/Tests/Runtime/TestFuture.Placeholder.cs b/Assets/_PackageRoot/Tests/Runtime/TestFuture.Placeholder.cs new file mode 100644 index 0000000..151e016 --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/TestFuture.Placeholder.cs @@ -0,0 +1,54 @@ +using UnityEngine.TestTools; +using System.Collections; +using Extensions.Unity.ImageLoader.Tests.Utils; + +namespace Extensions.Unity.ImageLoader.Tests +{ + public partial class TestFuture + { + // [UnityTest] public IEnumerator Placeholder_Source_NoLogs() => TestUtils.RunNoLogs(Placeholder_Source); + // [UnityTest] public IEnumerator Placeholder_Source() + // { + // yield return Placeholder_Source(useDiskCache: true, useMemoryCache: true); + // yield return Placeholder_Source(useDiskCache: true, useMemoryCache: false); + // yield return Placeholder_Source(useDiskCache: false, useMemoryCache: true); + // yield return Placeholder_Source(useDiskCache: false, useMemoryCache: false); + // } + // IEnumerator Placeholder_Source(bool useDiskCache, bool useMemoryCache) + // { + // ImageLoader.settings.useDiskCache = useDiskCache; + // ImageLoader.settings.useMemoryCache = useMemoryCache; + + // foreach (var url in TestUtils.ImageURLs) + // yield return TestUtils.Placeholder(url, FutureLoadingFrom.Source, FutureLoadedFrom.Source); + + // yield return TestUtils.ClearEverything(message: null); + // } + + // [UnityTest] public IEnumerator Placeholder_DiskCache_NoLogs() => TestUtils.RunNoLogs(Placeholder_DiskCache); + // [UnityTest] public IEnumerator Placeholder_DiskCache() + // { + // ImageLoader.settings.useDiskCache = true; + // ImageLoader.settings.useMemoryCache = false; + + // foreach (var url in TestUtils.ImageURLs) + // { + // yield return ImageLoader.LoadSprite(url).AsCoroutine(); + // yield return TestUtils.Placeholder(url, FutureLoadingFrom.DiskCache, FutureLoadedFrom.DiskCache); + // } + // } + + // [UnityTest] public IEnumerator Placeholder_MemoryCache_NoLogs() => TestUtils.RunNoLogs(Placeholder_MemoryCache); + // [UnityTest] public IEnumerator Placeholder_MemoryCache() + // { + // ImageLoader.settings.useDiskCache = true; + // ImageLoader.settings.useMemoryCache = true; + + // foreach (var url in TestUtils.ImageURLs) + // { + // yield return ImageLoader.LoadSprite(url).AsCoroutine(); + // yield return TestUtils.LoadFromMemoryCache(url); + // } + // } + } +} \ No newline at end of file diff --git a/Assets/_PackageRoot/Tests/Runtime/TestFuture.Placeholder.cs.meta b/Assets/_PackageRoot/Tests/Runtime/TestFuture.Placeholder.cs.meta new file mode 100644 index 0000000..cbe4a1c --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/TestFuture.Placeholder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 598cc762ff08ae843a93e2a01eda6290 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_PackageRoot/Tests/Runtime/TestFuture.cs b/Assets/_PackageRoot/Tests/Runtime/TestFuture.cs new file mode 100644 index 0000000..16b0c53 --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/TestFuture.cs @@ -0,0 +1,301 @@ +using NUnit.Framework; +using Cysharp.Threading.Tasks; +using UnityEngine.TestTools; +using System.Collections; +using UnityEngine; +using System; +using Extensions.Unity.ImageLoader.Tests.Utils; +using System.Threading.Tasks; + +namespace Extensions.Unity.ImageLoader.Tests +{ + public partial class TestFuture : Test + { + [UnitySetUp] public override IEnumerator SetUp() => base.SetUp(); + [UnityTearDown] public override IEnumerator TearDown() => base.TearDown(); + + [UnityTest] public IEnumerator GetAllLoadingFutures_NoLogs() => TestUtils.RunNoLogs(GetAllLoadingFutures); + [UnityTest] public IEnumerator GetAllLoadingFutures() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = true; + + var loadingSpriteFutures = ImageLoader.GetLoadingSpriteFutures(); + Assert.NotNull(loadingSpriteFutures); + Debug.Log($"Loading Future count={loadingSpriteFutures.Count}"); + foreach (var loadingFuture in loadingSpriteFutures) + { + Debug.Log($"Loading Future: {loadingFuture.Url}, Status={loadingFuture.Status}"); + } + Assert.Zero(loadingSpriteFutures.Count); + yield return UniTask.Yield(); + + var loadingTextureFutures = ImageLoader.GetLoadingTextureFutures(); + Assert.NotNull(loadingTextureFutures); + Debug.Log($"Loading Future count={loadingTextureFutures.Count}"); + foreach (var loadingFuture in loadingTextureFutures) + { + Debug.Log($"Loading Future: {loadingFuture.Url}, Status={loadingFuture.Status}"); + } + Assert.Zero(loadingTextureFutures.Count); + } + [UnityTest] public IEnumerator LoadingRefAndWaiting_NoLogs() => TestUtils.RunNoLogs(LoadingRefAndWaiting); + [UnityTest] public IEnumerator LoadingRefAndWaiting() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = true; + + var url = TestUtils.ImageURLs[0]; + + var task1 = ImageLoader.LoadSpriteRef(url).AsTask(); + yield return task1.TimeoutCoroutine(TimeSpan.FromSeconds(25)); + + var ref0 = task1.Result; + Assert.IsNotNull(ref0); + Assert.AreEqual(1, Reference.Counter(url)); + + Assert.Throws(() => ImageLoader.ClearMemoryCache(url)); + + ref0.Dispose(); + Assert.IsNull(ref0.Value); + Assert.AreEqual(0, Reference.Counter(url)); + } + [UnityTest] public IEnumerator Loading2RefAndCancelFirst_NoLogs() => TestUtils.RunNoLogs(Loading2RefAndCancelFirst); + [UnityTest] public IEnumerator Loading2RefAndCancelFirst() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = true; + + var url = TestUtils.ImageURLs[0]; + + var future1 = ImageLoader.LoadSpriteRef(url); + var future2 = ImageLoader.LoadSpriteRef(url); + + future1.Cancel(); + + var task1 = future2.AsTask(); + yield return task1.TimeoutCoroutine(TimeSpan.FromSeconds(25)); + + var ref2 = task1.Result; + Assert.IsNotNull(ref2); + Assert.IsNotNull(ref2.Value); + Assert.AreEqual(1, Reference.Counter(url)); + future1.Dispose(); + future2.Dispose(); + ref2.Dispose(); + } + [UnityTest] public IEnumerator Loading2RefAndWait_NoLogs() => TestUtils.RunNoLogs(Loading2RefAndWait); + [UnityTest] public IEnumerator Loading2RefAndWait() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = true; + + var url = TestUtils.ImageURLs[0]; + + var task1 = ImageLoader.LoadSpriteRef(url).AsTask(); + var task2 = ImageLoader.LoadSpriteRef(url).AsTask(); + + yield return Task.WhenAll(task1, task2).TimeoutCoroutine(TimeSpan.FromSeconds(25)); + + var ref0 = task1.Result; + Assert.IsNotNull(ref0); + var ref1 = task2.Result; + Assert.IsNotNull(ref1); + Assert.AreEqual(2, Reference.Counter(url)); + + Assert.Throws(() => ImageLoader.ClearMemoryCache(url)); + + ref0.Dispose(); + Assert.IsNull(ref0.Value); + Assert.AreEqual(1, Reference.Counter(url)); + ref1.Dispose(); + Assert.IsNull(ref1.Value); + Assert.AreEqual(0, Reference.Counter(url)); + + var sprite = ImageLoader.LoadSpriteFromMemoryCache(url); + Assert.IsNull(sprite); + + ImageLoader.ClearMemoryCache(url); + Assert.AreEqual(0, Reference.Counter(url)); + } + [UnityTest] public IEnumerator Loading2RefAndDisposeAll_NoLogs() => TestUtils.RunNoLogs(Loading2RefAndDisposeAll); + [UnityTest] public IEnumerator Loading2RefAndDisposeAll() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = true; + + var url = TestUtils.ImageURLs[0]; + + var future1 = ImageLoader.LoadSpriteRef(url); + Assert.IsNotNull(future1); + var future2 = ImageLoader.LoadSpriteRef(url); + Assert.IsNotNull(future2); + + Assert.AreEqual(0, Reference.Counter(url)); + + future1.Cancel(); + future2.Cancel(); + + future1.Dispose(); + future2.Dispose(); + + Assert.AreEqual(0, Reference.Counter(url)); + yield return TestUtils.Wait(TimeSpan.FromSeconds(1)); + Assert.AreEqual(0, Reference.Counter(url)); + } + [UnityTest] public IEnumerator DisposeOnOutDisposingBlock_NoLogs() => TestUtils.RunNoLogs(DisposeOnOutDisposingBlock); + [UnityTest] public IEnumerator DisposeOnOutDisposingBlock() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = true; + + foreach (var url in TestUtils.ImageURLs) + { + var future1 = ImageLoader.LoadSpriteRef(url); + var task1 = future1.AsTask(); + Assert.AreEqual(0, Reference.Counter(url)); + yield return task1.TimeoutCoroutine(TimeSpan.FromSeconds(25)); + + using (var ref1 = task1.Result) + { + Assert.AreEqual(1, Reference.Counter(url)); + } + Assert.AreEqual(0, Reference.Counter(url)); + } + foreach (var url in TestUtils.ImageURLs) + { + Assert.AreEqual(0, Reference.Counter(url), $"Should be zero references to URL={url}"); + } + } + [UnityTest] public IEnumerator DisposeOnOutDisposingBlock2_NoLogs() => TestUtils.RunNoLogs(DisposeOnOutDisposingBlock2); + [UnityTest] public IEnumerator DisposeOnOutDisposingBlock2() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = true; + + foreach (var url in TestUtils.ImageURLs) + { + var future1 = ImageLoader.LoadSpriteRef(url); + var task1 = future1.AsTask(); + yield return task1.TimeoutCoroutine(TimeSpan.FromSeconds(25)); + + Assert.AreEqual(1, Reference.Counter(url)); + + var ref1 = future1.Value; + ref1.Dispose(); + Assert.AreEqual(0, Reference.Counter(url)); + } + foreach (var url in TestUtils.ImageURLs) + { + Assert.AreEqual(0, Reference.Counter(url), $"Should be zero references to URL={url}"); + } + } + [UnityTest] public IEnumerator DisposeOnOutDisposingBlock3_NoLogs() => TestUtils.RunNoLogs(DisposeOnOutDisposingBlock3); + [UnityTest] public IEnumerator DisposeOnOutDisposingBlock3() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = true; + + foreach (var url in TestUtils.ImageURLs) + { + var future1 = ImageLoader.LoadSpriteRef(url); + var task1 = future1.AsTask(); + yield return task1.TimeoutCoroutine(TimeSpan.FromSeconds(25)); + + Assert.AreEqual(1, Reference.Counter(url)); + + future1.Value.Dispose(); + Assert.AreEqual(0, Reference.Counter(url)); + + var future2 = ImageLoader.LoadSpriteRef(url); + var task2 = future2.AsTask(); + yield return task2.TimeoutCoroutine(TimeSpan.FromSeconds(25)); + + using (var ref2 = task2.Result) + { + Assert.AreEqual(1, Reference.Counter(url)); + } + Assert.AreEqual(0, Reference.Counter(url)); + } + foreach (var url in TestUtils.ImageURLs) + { + Assert.AreEqual(0, Reference.Counter(url), $"Should be zero references to URL={url}"); + } + } + + [UnityTest] public IEnumerator EventFailedWithIncorrectUrlAndTimeout_NoLogs() => TestUtils.RunNoLogs(EventFailedWithIncorrectUrlAndTimeout); + [UnityTest] public IEnumerator EventFailedWithIncorrectUrlAndTimeout() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = true; + + var url = TestUtils.IncorrectImageURL; + var exception = default(Exception); + var startTime = DateTime.Now; + var future1 = ImageLoader.LoadSprite(url) + .Timeout(TimeSpan.FromSeconds(0.1f)) + .Failed(e => exception = e); + + Assert.IsNull(exception); + + LogAssert.ignoreFailingMessages = true; + yield return TestUtils.Wait(TimeSpan.FromSeconds(2)); + var task1 = future1.AsTask(); + Assert.IsTrue(task1.IsCompleted); + Assert.IsNotNull(exception); + + future1.Cancel(); // expected warning + LogAssert.ignoreFailingMessages = false; + yield return TestUtils.Wait(TimeSpan.FromSeconds(2)); + future1.Dispose(); + } + [UnityTest] public IEnumerator EventFailedWithIncorrectUrlNotCalledBecauseOfCancel_NoLogs() => TestUtils.RunNoLogs(EventFailedWithIncorrectUrlNotCalledBecauseOfCancel); + [UnityTest] public IEnumerator EventFailedWithIncorrectUrlNotCalledBecauseOfCancel() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = true; + + var url = TestUtils.IncorrectImageURL; + var exception = default(Exception); + var startTime = DateTime.Now; + var future1 = ImageLoader.LoadSprite(url) + .Failed(e => exception = e); + + Assert.IsNull(exception); + + var task1 = future1.AsTask(); + future1.Cancel(); + + yield return task1.TimeoutCoroutine(TimeSpan.FromSeconds(25)); + yield return TestUtils.Wait(TimeSpan.FromSeconds(1)); + Assert.IsNull(exception); + future1.Dispose(); + } + [UnityTest] public IEnumerator AsyncOperationCompletion_NoLogs() => TestUtils.RunNoLogs(AsyncOperationCompletion); + [UnityTest] public IEnumerator AsyncOperationCompletion() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = true; + + foreach (var url in TestUtils.ImageURLs) + { + var completed = false; + yield return ImageLoader.LoadSprite(url) + .Completed(success => completed = true) + .TimeoutCoroutine(TimeSpan.FromSeconds(10)); + + Assert.IsTrue(completed); + } + yield return TestUtils.Wait(TimeSpan.FromSeconds(1)); + } + [UnityTest] public IEnumerator AsyncOperationCompletionAfterCancel_NoLogs() => TestUtils.RunNoLogs(AsyncOperationCompletionAfterCancel); + [UnityTest] public IEnumerator AsyncOperationCompletionAfterCancel() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = true; + + foreach (var url in TestUtils.ImageURLs) + yield return TestUtils.LoadAndCancel(url, FutureLoadingFrom.Source); + } + } +} \ No newline at end of file diff --git a/Assets/_PackageRoot/Tests/Runtime/TestFuture.cs.meta b/Assets/_PackageRoot/Tests/Runtime/TestFuture.cs.meta new file mode 100644 index 0000000..85b73f4 --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/TestFuture.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 894b315b837b07e498ee88f02ff15a86 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_PackageRoot/Tests/Runtime/TestFutureOrder.cs b/Assets/_PackageRoot/Tests/Runtime/TestFutureOrder.cs new file mode 100644 index 0000000..f9e401a --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/TestFutureOrder.cs @@ -0,0 +1,94 @@ +using NUnit.Framework; +using Cysharp.Threading.Tasks; +using UnityEngine.TestTools; +using System.Collections; +using UnityEngine; +using System; +using Extensions.Unity.ImageLoader.Tests.Utils; +using System.Collections.Generic; + +namespace Extensions.Unity.ImageLoader.Tests +{ + public class TestFutureOrder : Test + { + [UnitySetUp] public override IEnumerator SetUp() => base.SetUp(); + [UnityTearDown] public override IEnumerator TearDown() => base.TearDown(); + + [UnityTest] public IEnumerator EventsLoadedWhenClear_NoLogs() => TestUtils.RunNoLogs(EventsLoadedWhenClear); + [UnityTest] public IEnumerator EventsLoadedWhenClear() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = true; + + var url = TestUtils.ImageURLs[0]; + var startTime = DateTime.Now; + + var future = new FutureSprite(url); + var futureListener = new FutureListener(future); + var task = future.StartLoading().AsTask(); + yield return task.TimeoutCoroutine(TimeSpan.FromSeconds(10)); + + futureListener.Assert_Events_Equals(new List + { + EventName.LoadingFromSource, + EventName.LoadedFromSource, + EventName.Loaded, + EventName.Completed + }); + futureListener.Assert_Events_Value(EventName.LoadedFromSource, sprite => sprite != null); + futureListener.Assert_Events_Value(EventName.Loaded, sprite => sprite != null); + futureListener.Assert_Events_Value(EventName.Completed, success => ((bool)success) == true); + futureListener.Assert_Events_NotContains(EventName.Canceled); + + var task1 = future.AsTask(); + Assert.True(task1.IsCompleted); + Assert.AreEqual(FutureStatus.LoadedFromSource, future.Status); + + future.Cancel(); + Assert.AreEqual(FutureStatus.LoadedFromSource, future.Status); + + Assert.True(future.AsTask().IsCompleted); + Assert.AreEqual(FutureStatus.LoadedFromSource, future.Status); + + future.Dispose(); + } + [UnityTest] public IEnumerator EventsFailedWhenClear_NoLogs() => TestUtils.RunNoLogs(EventsFailedWhenClear); + [UnityTest] public IEnumerator EventsFailedWhenClear() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = true; + + var url = TestUtils.IncorrectImageURL; + var startTime = DateTime.Now; + + var future = new FutureSprite(url); + var futureListener = new FutureListener(future); + future.Timeout(TimeSpan.FromMilliseconds(100)); + + LogAssert.ignoreFailingMessages = true; + yield return future.StartLoading().TimeoutCoroutine(TimeSpan.FromSeconds(10)); + LogAssert.ignoreFailingMessages = false; + + futureListener.Assert_Events_Equals(new List + { + EventName.LoadingFromSource, + EventName.Failed, + EventName.Completed + }); + futureListener.Assert_Events_Value(EventName.Completed, success => ((bool)success) == false); + futureListener.Assert_Events_NotContains(EventName.Canceled); + + var task1 = future.AsTask(); + Assert.True(task1.IsCompleted); + Assert.AreEqual(FutureStatus.FailedToLoad, future.Status); + + future.Cancel(); + Assert.AreEqual(FutureStatus.FailedToLoad, future.Status); + + Assert.True(future.AsTask().IsCompleted); + Assert.AreEqual(FutureStatus.FailedToLoad, future.Status); + + future.Dispose(); + } + } +} \ No newline at end of file diff --git a/Assets/_PackageRoot/Tests/Runtime/TestFutureOrder.cs.meta b/Assets/_PackageRoot/Tests/Runtime/TestFutureOrder.cs.meta new file mode 100644 index 0000000..e51d0e8 --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/TestFutureOrder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: af3b53b3d23b9694bb437e1678c8aa52 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_PackageRoot/Tests/Runtime/TestFutureWaitingAnotherFuture.Load.AndCancel.cs b/Assets/_PackageRoot/Tests/Runtime/TestFutureWaitingAnotherFuture.Load.AndCancel.cs new file mode 100644 index 0000000..e3b9a6a --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/TestFutureWaitingAnotherFuture.Load.AndCancel.cs @@ -0,0 +1,62 @@ +using System.Linq; +using UnityEngine.TestTools; +using System.Collections; +using Extensions.Unity.ImageLoader.Tests.Utils; +using UnityEngine; + +namespace Extensions.Unity.ImageLoader.Tests +{ + public partial class TestFutureWaitingAnotherFuture + { + [UnityTest] public IEnumerator LoadFrom_Source_AndCancel_NoLogs() => TestUtils.RunNoLogs(LoadFrom_Source_AndCancel); + [UnityTest] public IEnumerator LoadFrom_Source_AndCancel() + { + yield return LoadFrom_Source_AndCancel(useDiskCache: true, useMemoryCache: true); + yield return LoadFrom_Source_AndCancel(useDiskCache: true, useMemoryCache: false); + yield return LoadFrom_Source_AndCancel(useDiskCache: false, useMemoryCache: true); + yield return LoadFrom_Source_AndCancel(useDiskCache: false, useMemoryCache: false); + } + IEnumerator LoadFrom_Source_AndCancel(bool useDiskCache, bool useMemoryCache) + { + ImageLoader.settings.useDiskCache = useDiskCache; + ImageLoader.settings.useMemoryCache = useMemoryCache; + + foreach (var url in TestUtils.ImageURLs) + { + var future = ImageLoader.LoadSprite(url); + yield return TestUtils.LoadAndCancel(url, FutureLoadingFrom.Source); + future.Dispose(); + } + + yield return TestUtils.ClearEverything(message: null); + } + [UnityTest] public IEnumerator LoadFrom_MemoryCache_AndCancel_NoLogs() => TestUtils.RunNoLogs(LoadFrom_MemoryCache_AndCancel); + [UnityTest] public IEnumerator LoadFrom_MemoryCache_AndCancel() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = true; + + foreach (var url in TestUtils.ImageURLs) + { + yield return ImageLoader.LoadSprite(url).AsCoroutine(); + ImageLoader.LoadSprite(url); + yield return TestUtils.LoadFromMemoryCacheAndCancel(url); + } + } + + [UnityTest] public IEnumerator LoadFrom_DiskCache_AndCancel_NoLogs() => TestUtils.RunNoLogs(LoadFrom_DiskCache_AndCancel); + [UnityTest] public IEnumerator LoadFrom_DiskCache_AndCancel() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = false; + + // foreach (var url in TestUtils.ImageURLs.SelectMany(x => Enumerable.Repeat(x, 100)).OrderBy(x => Random.value)) + foreach (var url in TestUtils.ImageURLs) + { + yield return ImageLoader.LoadSprite(url).AsCoroutine(); + ImageLoader.LoadSprite(url); + yield return TestUtils.LoadAndCancel(url, FutureLoadingFrom.DiskCache); + } + } + } +} \ No newline at end of file diff --git a/Assets/_PackageRoot/Tests/Runtime/TestFutureWaitingAnotherFuture.Load.AndCancel.cs.meta b/Assets/_PackageRoot/Tests/Runtime/TestFutureWaitingAnotherFuture.Load.AndCancel.cs.meta new file mode 100644 index 0000000..f5d6929 --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/TestFutureWaitingAnotherFuture.Load.AndCancel.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7ee81f6ced8d94e45aa31cc3a94be220 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_PackageRoot/Tests/Runtime/TestFutureWaitingAnotherFuture.Load.ThenCancel.cs b/Assets/_PackageRoot/Tests/Runtime/TestFutureWaitingAnotherFuture.Load.ThenCancel.cs new file mode 100644 index 0000000..45ceefc --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/TestFutureWaitingAnotherFuture.Load.ThenCancel.cs @@ -0,0 +1,80 @@ +using UnityEngine.TestTools; +using System.Collections; +using Extensions.Unity.ImageLoader.Tests.Utils; + +namespace Extensions.Unity.ImageLoader.Tests +{ + public partial class TestFutureWaitingAnotherFuture + { + [UnityTest] public IEnumerator LoadFrom_Source_ThenCancel_NoLogs() => TestUtils.RunNoLogs(LoadFrom_Source_ThenCancel); + [UnityTest] public IEnumerator LoadFrom_Source_ThenCancel() + { + yield return LoadFrom_Source_ThenCancel(useDiskCache: true, useMemoryCache: true, useGC: true); + yield return LoadFrom_Source_ThenCancel(useDiskCache: true, useMemoryCache: false, useGC: true); + yield return LoadFrom_Source_ThenCancel(useDiskCache: false, useMemoryCache: true, useGC: true); + yield return LoadFrom_Source_ThenCancel(useDiskCache: false, useMemoryCache: false, useGC: true); + + yield return LoadFrom_Source_ThenCancel(useDiskCache: true, useMemoryCache: true, useGC: false); + yield return LoadFrom_Source_ThenCancel(useDiskCache: true, useMemoryCache: false, useGC: false); + yield return LoadFrom_Source_ThenCancel(useDiskCache: false, useMemoryCache: true, useGC: false); + yield return LoadFrom_Source_ThenCancel(useDiskCache: false, useMemoryCache: false, useGC: false); + } + IEnumerator LoadFrom_Source_ThenCancel(bool useDiskCache, bool useMemoryCache, bool useGC) + { + ImageLoader.settings.useDiskCache = useDiskCache; + ImageLoader.settings.useMemoryCache = useMemoryCache; + + foreach (var url in TestUtils.ImageURLs) + { + ImageLoader.LoadSprite(url); + yield return TestUtils.LoadThenCancel(url, FutureLoadingFrom.Source, FutureLoadedFrom.Source, useGC); + } + + yield return TestUtils.ClearEverything(message: null); + } + + [UnityTest] public IEnumerator LoadFrom_MemoryCache_ThenCancel_NoLogs() => TestUtils.RunNoLogs(LoadFrom_MemoryCache_ThenCancel); + [UnityTest] public IEnumerator LoadFrom_MemoryCache_ThenCancel() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = true; + + foreach (var url in TestUtils.ImageURLs) + { + yield return ImageLoader.LoadSprite(url).AsCoroutine(); + ImageLoader.LoadSprite(url); + yield return TestUtils.LoadFromMemoryCacheThenCancel(url, useGC: true); + } + // TODO: remove code duplicate + yield return TestUtils.ClearEverything(message: null); + foreach (var url in TestUtils.ImageURLs) + { + yield return ImageLoader.LoadSprite(url).AsCoroutine(); + ImageLoader.LoadSprite(url); + yield return TestUtils.LoadFromMemoryCacheThenCancel(url, useGC: false); + } + } + + [UnityTest] public IEnumerator LoadFrom_DiskCache_ThenCancel_NoLogs() => TestUtils.RunNoLogs(LoadFrom_DiskCache_ThenCancel); + [UnityTest] public IEnumerator LoadFrom_DiskCache_ThenCancel() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = false; + + foreach (var url in TestUtils.ImageURLs) + { + yield return ImageLoader.LoadSprite(url).AsCoroutine(); + ImageLoader.LoadSprite(url); + yield return TestUtils.LoadThenCancel(url, FutureLoadingFrom.DiskCache, FutureLoadedFrom.DiskCache, useGC: true); + } + // TODO: remove code duplicate + yield return TestUtils.ClearEverything(message: null); + foreach (var url in TestUtils.ImageURLs) + { + yield return ImageLoader.LoadSprite(url).AsCoroutine(); + ImageLoader.LoadSprite(url); + yield return TestUtils.LoadThenCancel(url, FutureLoadingFrom.DiskCache, FutureLoadedFrom.DiskCache, useGC: false); + } + } + } +} \ No newline at end of file diff --git a/Assets/_PackageRoot/Tests/Runtime/TestFutureWaitingAnotherFuture.Load.ThenCancel.cs.meta b/Assets/_PackageRoot/Tests/Runtime/TestFutureWaitingAnotherFuture.Load.ThenCancel.cs.meta new file mode 100644 index 0000000..61ffb12 --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/TestFutureWaitingAnotherFuture.Load.ThenCancel.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d7867e9e8d17bdd449365c699ed09690 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_PackageRoot/Tests/Runtime/TestFutureWaitingAnotherFuture.Load.cs b/Assets/_PackageRoot/Tests/Runtime/TestFutureWaitingAnotherFuture.Load.cs new file mode 100644 index 0000000..d2a112a --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/TestFutureWaitingAnotherFuture.Load.cs @@ -0,0 +1,59 @@ +using UnityEngine.TestTools; +using System.Collections; +using Extensions.Unity.ImageLoader.Tests.Utils; + +namespace Extensions.Unity.ImageLoader.Tests +{ + public partial class TestFutureWaitingAnotherFuture + { + [UnityTest] public IEnumerator LoadFrom_Source_NoLogs() => TestUtils.RunNoLogs(LoadFrom_Source); + [UnityTest] public IEnumerator LoadFrom_Source() + { + yield return LoadFrom_Source(useDiskCache: true, useMemoryCache: true); + yield return LoadFrom_Source(useDiskCache: true, useMemoryCache: false); + yield return LoadFrom_Source(useDiskCache: false, useMemoryCache: true); + yield return LoadFrom_Source(useDiskCache: false, useMemoryCache: false); + } + IEnumerator LoadFrom_Source(bool useDiskCache, bool useMemoryCache) + { + ImageLoader.settings.useDiskCache = useDiskCache; + ImageLoader.settings.useMemoryCache = useMemoryCache; + + foreach (var url in TestUtils.ImageURLs) + { + ImageLoader.LoadSprite(url); + yield return TestUtils.Load(url, FutureLoadingFrom.Source, FutureLoadedFrom.Source); + } + + yield return TestUtils.ClearEverything(message: null); + } + + [UnityTest] public IEnumerator LoadFrom_DiskCache_NoLogs() => TestUtils.RunNoLogs(LoadFrom_DiskCache); + [UnityTest] public IEnumerator LoadFrom_DiskCache() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = false; + + foreach (var url in TestUtils.ImageURLs) + { + yield return ImageLoader.LoadSprite(url).AsCoroutine(); + ImageLoader.LoadSprite(url); + yield return TestUtils.Load(url, FutureLoadingFrom.DiskCache, FutureLoadedFrom.DiskCache); + } + } + + [UnityTest] public IEnumerator LoadFrom_MemoryCache_NoLogs() => TestUtils.RunNoLogs(LoadFrom_MemoryCache); + [UnityTest] public IEnumerator LoadFrom_MemoryCache() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = true; + + foreach (var url in TestUtils.ImageURLs) + { + yield return ImageLoader.LoadSprite(url).AsCoroutine(); + ImageLoader.LoadSprite(url); + yield return TestUtils.LoadFromMemoryCache(url); + } + } + } +} \ No newline at end of file diff --git a/Assets/_PackageRoot/Tests/Runtime/TestFutureWaitingAnotherFuture.Load.cs.meta b/Assets/_PackageRoot/Tests/Runtime/TestFutureWaitingAnotherFuture.Load.cs.meta new file mode 100644 index 0000000..6287b94 --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/TestFutureWaitingAnotherFuture.Load.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7351803d1c78dc94aaa73c57093f3ff3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_PackageRoot/Tests/Runtime/TestFutureWaitingAnotherFuture.LoadFail.cs b/Assets/_PackageRoot/Tests/Runtime/TestFutureWaitingAnotherFuture.LoadFail.cs new file mode 100644 index 0000000..3c7fa84 --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/TestFutureWaitingAnotherFuture.LoadFail.cs @@ -0,0 +1,32 @@ +using UnityEngine.TestTools; +using System.Collections; +using Extensions.Unity.ImageLoader.Tests.Utils; + +namespace Extensions.Unity.ImageLoader.Tests +{ + public partial class TestFutureWaitingAnotherFuture + { + [UnityTest] public IEnumerator LoadFailFrom_Source_NoLogs() => TestUtils.RunNoLogs(LoadFailFrom_Source); + [UnityTest] public IEnumerator LoadFailFrom_Source() + { + yield return LoadFailFrom_Source(useDiskCache: true, useMemoryCache: true); + yield return LoadFailFrom_Source(useDiskCache: true, useMemoryCache: false); + yield return LoadFailFrom_Source(useDiskCache: false, useMemoryCache: true); + yield return LoadFailFrom_Source(useDiskCache: false, useMemoryCache: false); + } + IEnumerator LoadFailFrom_Source(bool useDiskCache, bool useMemoryCache) + { + ImageLoader.settings.useDiskCache = useDiskCache; + ImageLoader.settings.useMemoryCache = useMemoryCache; + + foreach (var url in TestUtils.IncorrectImageURLs()) + { + var future = ImageLoader.LoadSprite(url); + yield return TestUtils.LoadFail(url, FutureLoadingFrom.Source); + future.Dispose(); + } + + yield return TestUtils.ClearEverything(message: null); + } + } +} \ No newline at end of file diff --git a/Assets/_PackageRoot/Tests/Runtime/TestFutureWaitingAnotherFuture.LoadFail.cs.meta b/Assets/_PackageRoot/Tests/Runtime/TestFutureWaitingAnotherFuture.LoadFail.cs.meta new file mode 100644 index 0000000..98e3e98 --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/TestFutureWaitingAnotherFuture.LoadFail.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0696139d5fea42b4c92741767da1d855 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_PackageRoot/Tests/Runtime/TestFutureWaitingAnotherFuture.cs b/Assets/_PackageRoot/Tests/Runtime/TestFutureWaitingAnotherFuture.cs new file mode 100644 index 0000000..f5117cc --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/TestFutureWaitingAnotherFuture.cs @@ -0,0 +1,11 @@ +using UnityEngine.TestTools; +using System.Collections; + +namespace Extensions.Unity.ImageLoader.Tests +{ + public partial class TestFutureWaitingAnotherFuture : Test + { + [UnitySetUp] public override IEnumerator SetUp() => base.SetUp(); + [UnityTearDown] public override IEnumerator TearDown() => base.TearDown(); + } +} \ No newline at end of file diff --git a/Assets/_PackageRoot/Tests/Runtime/TestFutureWaitingAnotherFuture.cs.meta b/Assets/_PackageRoot/Tests/Runtime/TestFutureWaitingAnotherFuture.cs.meta new file mode 100644 index 0000000..7bc434d --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/TestFutureWaitingAnotherFuture.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0415ebd124cd7e34b9530d71fa9b0f1c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_PackageRoot/Tests/Runtime/TestLoading.Sprite.cs b/Assets/_PackageRoot/Tests/Runtime/TestLoading.Sprite.cs new file mode 100644 index 0000000..6b5a8d7 --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/TestLoading.Sprite.cs @@ -0,0 +1,55 @@ +using NUnit.Framework; +using Cysharp.Threading.Tasks; +using UnityEngine.TestTools; +using System.Collections; +using Extensions.Unity.ImageLoader.Tests.Utils; +using System; + +namespace Extensions.Unity.ImageLoader.Tests +{ + public partial class TestLoading : Test + { + public async UniTask LoadSprite(string url) + { + var sprite = await ImageLoader.LoadSprite(url); + Assert.IsNotNull(sprite); + } + + [UnityTest] public IEnumerator LoadSpritesCacheMemoryDisk_NoLogs() => TestUtils.RunNoLogs(LoadSpritesCacheMemoryDisk); + [UnityTest] public IEnumerator LoadSpritesCacheMemoryDisk() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = true; + + foreach (var imageURL in TestUtils.ImageURLs) + yield return LoadSprite(imageURL).TimeoutCoroutine(TimeSpan.FromSeconds(10)); + } + [UnityTest] public IEnumerator LoadSpritesCacheMemory_NoLogs() => TestUtils.RunNoLogs(LoadSpritesCacheMemory); + [UnityTest] public IEnumerator LoadSpritesCacheMemory() + { + ImageLoader.settings.useDiskCache = false; + ImageLoader.settings.useMemoryCache = true; + + foreach (var imageURL in TestUtils.ImageURLs) + yield return LoadSprite(imageURL).TimeoutCoroutine(TimeSpan.FromSeconds(10)); + } + [UnityTest] public IEnumerator LoadSpritesCacheDisk_NoLogs() => TestUtils.RunNoLogs(LoadSpritesCacheDisk); + [UnityTest] public IEnumerator LoadSpritesCacheDisk() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = false; + + foreach (var imageURL in TestUtils.ImageURLs) + yield return LoadSprite(imageURL).TimeoutCoroutine(TimeSpan.FromSeconds(10)); + } + [UnityTest] public IEnumerator LoadSpritesNoCache_NoLogs() => TestUtils.RunNoLogs(LoadSpritesNoCache); + [UnityTest] public IEnumerator LoadSpritesNoCache() + { + ImageLoader.settings.useDiskCache = false; + ImageLoader.settings.useMemoryCache = false; + + foreach (var imageURL in TestUtils.ImageURLs) + yield return LoadSprite(imageURL).TimeoutCoroutine(TimeSpan.FromSeconds(10)); + } + } +} \ No newline at end of file diff --git a/Assets/_PackageRoot/Tests/Runtime/TestLoading.Sprite.cs.meta b/Assets/_PackageRoot/Tests/Runtime/TestLoading.Sprite.cs.meta new file mode 100644 index 0000000..5eaa24b --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/TestLoading.Sprite.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fd1a54ed7db82824b9370ddebfc18c01 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_PackageRoot/Tests/Runtime/TestLoading.Texture.cs b/Assets/_PackageRoot/Tests/Runtime/TestLoading.Texture.cs new file mode 100644 index 0000000..0031930 --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/TestLoading.Texture.cs @@ -0,0 +1,55 @@ +using NUnit.Framework; +using Cysharp.Threading.Tasks; +using UnityEngine.TestTools; +using System.Collections; +using Extensions.Unity.ImageLoader.Tests.Utils; +using System; + +namespace Extensions.Unity.ImageLoader.Tests +{ + public partial class TestLoading : Test + { + public async UniTask LoadTextureTexture(string url) + { + var texture = await ImageLoader.LoadTexture(url); + Assert.IsNotNull(texture); + } + + [UnityTest] public IEnumerator LoadTextureTexturesCacheMemoryDisk_NoLogs() => TestUtils.RunNoLogs(LoadTextureTexturesCacheMemoryDisk); + [UnityTest] public IEnumerator LoadTextureTexturesCacheMemoryDisk() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = true; + + foreach (var imageURL in TestUtils.ImageURLs) + yield return LoadTextureTexture(imageURL).TimeoutCoroutine(TimeSpan.FromSeconds(10)); + } + [UnityTest] public IEnumerator LoadTextureTexturesCacheMemory_NoLogs() => TestUtils.RunNoLogs(LoadTextureTexturesCacheMemory); + [UnityTest] public IEnumerator LoadTextureTexturesCacheMemory() + { + ImageLoader.settings.useDiskCache = false; + ImageLoader.settings.useMemoryCache = true; + + foreach (var imageURL in TestUtils.ImageURLs) + yield return LoadTextureTexture(imageURL).TimeoutCoroutine(TimeSpan.FromSeconds(10)); + } + [UnityTest] public IEnumerator LoadTextureTexturesCacheDisk_NoLogs() => TestUtils.RunNoLogs(LoadTextureTexturesCacheDisk); + [UnityTest] public IEnumerator LoadTextureTexturesCacheDisk() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = false; + + foreach (var imageURL in TestUtils.ImageURLs) + yield return LoadTextureTexture(imageURL).TimeoutCoroutine(TimeSpan.FromSeconds(10)); + } + [UnityTest] public IEnumerator LoadTextureTexturesNoCache_NoLogs() => TestUtils.RunNoLogs(LoadTextureTexturesNoCache); + [UnityTest] public IEnumerator LoadTextureTexturesNoCache() + { + ImageLoader.settings.useDiskCache = false; + ImageLoader.settings.useMemoryCache = false; + + foreach (var imageURL in TestUtils.ImageURLs) + yield return LoadTextureTexture(imageURL).TimeoutCoroutine(TimeSpan.FromSeconds(10)); + } + } +} \ No newline at end of file diff --git a/Assets/_PackageRoot/Tests/Runtime/TestLoading.Texture.cs.meta b/Assets/_PackageRoot/Tests/Runtime/TestLoading.Texture.cs.meta new file mode 100644 index 0000000..69e4b93 --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/TestLoading.Texture.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 431d370810c79d1488e0f1cb51a5e40a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_PackageRoot/Tests/Runtime/TestLoading.cs b/Assets/_PackageRoot/Tests/Runtime/TestLoading.cs new file mode 100644 index 0000000..d33926a --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/TestLoading.cs @@ -0,0 +1,11 @@ +using UnityEngine.TestTools; +using System.Collections; + +namespace Extensions.Unity.ImageLoader.Tests +{ + public partial class TestLoading : Test + { + [UnitySetUp] public override IEnumerator SetUp() => base.SetUp(); + [UnityTearDown] public override IEnumerator TearDown() => base.TearDown(); + } +} \ No newline at end of file diff --git a/Assets/_PackageRoot/Tests/Runtime/TestLoading.cs.meta b/Assets/_PackageRoot/Tests/Runtime/TestLoading.cs.meta new file mode 100644 index 0000000..f9e74a2 --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/TestLoading.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d8480b3de3fda094087dcd71048c609f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_PackageRoot/Tests/Runtime/TestReference.cs b/Assets/_PackageRoot/Tests/Runtime/TestReference.cs new file mode 100644 index 0000000..07e8a54 --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/TestReference.cs @@ -0,0 +1,415 @@ +using System; +using System.Linq; +using System.Collections; +using NUnit.Framework; +using Cysharp.Threading.Tasks; +using UnityEngine.TestTools; +using UnityEngine; +using System.Threading.Tasks; +using Extensions.Unity.ImageLoader.Tests.Utils; + +namespace Extensions.Unity.ImageLoader.Tests +{ + public class TestReference : Test + { + [UnitySetUp] public override IEnumerator SetUp() => base.SetUp(); + [UnityTearDown] public override IEnumerator TearDown() => base.TearDown(); + + [UnityTest] public IEnumerator CleanMemoryCache_NoLogs() => TestUtils.RunNoLogs(CleanMemoryCache); + [UnityTest] public IEnumerator CleanMemoryCache() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = true; + + var url = TestUtils.ImageURLs[0]; + + var task1 = ImageLoader.LoadSpriteRef(url).AsTask(); + yield return task1.TimeoutCoroutine(TimeSpan.FromSeconds(25)); + + var ref0 = task1.Result; + Assert.AreEqual(1, Reference.Counter(url)); + + Assert.Throws(() => ImageLoader.ClearMemoryCache(url)); + Assert.IsNotNull(ref0.Value); + Assert.AreEqual(1, Reference.Counter(url)); + + ref0.Dispose(); + Assert.IsNull(ref0.Value); + Assert.AreEqual(0, Reference.Counter(url)); + } + + [UnityTest] public IEnumerator DisposeOnOutOfScope_NoLogs() => TestUtils.RunNoLogs(DisposeOnOutOfScope); + [UnityTest] public IEnumerator DisposeOnOutOfScope() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = true; + + var url = TestUtils.ImageURLs[0]; + { // scope + var task = ImageLoader.LoadSpriteRef(url).AsTask(); + yield return task.TimeoutCoroutine(TimeSpan.FromSeconds(25)); + + var reference = task.Result; + Assert.NotNull(reference); + Assert.AreEqual(1, Reference.Counter(url)); + } // end of scope + + yield return TestUtils.WaitForGC(); + yield return TestUtils.WaitForGC(); + yield return TestUtils.WaitForGC(); + Assert.AreEqual(0, Reference.Counter(url)); + } + + [UnityTest] public IEnumerator DisposeOnOutOfScope2() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = true; + + var url = TestUtils.ImageURLs[0]; + + var futureRef = ImageLoader.LoadSpriteRef(url); + while (futureRef.IsInProgress) + yield return UniTask.Yield(); + + var reference = futureRef.Value; + Assert.NotNull(reference); + Assert.AreEqual(1, Reference.Counter(url)); + + futureRef = null; + reference = null; + + yield return TestUtils.WaitForGC(); + yield return TestUtils.WaitForGC(); + yield return TestUtils.WaitForGC(); + Assert.AreEqual(0, Reference.Counter(url)); + } + + [UnityTest] public IEnumerator DisposeOnOutOfScopeAll_NoLogs() => TestUtils.RunNoLogs(DisposeOnOutOfScopeAll); + [UnityTest] public IEnumerator DisposeOnOutOfScopeAll() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = true; + + foreach (var url in TestUtils.ImageURLs) + { + var future = ImageLoader.LoadSpriteRef(url); + yield return future.TimeoutCoroutine(TimeSpan.FromSeconds(10)); + + Assert.True(future.IsLoaded); + Assert.True(future.IsCompleted); + var reference = future.Value; + Assert.NotNull(reference); + Assert.NotNull(reference.Value); + Assert.AreEqual(1, Reference.Counter(url)); + } + + yield return TestUtils.WaitForGC(); + yield return TestUtils.WaitForGC(); + yield return TestUtils.WaitForGC(); + + foreach (var url in TestUtils.ImageURLs) + Assert.AreEqual(0, Reference.Counter(url)); + } + [UnityTest] public IEnumerator DisposeOnOutDisposingBlock_NoLogs() => TestUtils.RunNoLogs(DisposeOnOutDisposingBlock); + [UnityTest] public IEnumerator DisposeOnOutDisposingBlock() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = true; + + foreach (var url in TestUtils.ImageURLs) + { + var future = ImageLoader.LoadSpriteRef(url); + yield return future.TimeoutCoroutine(TimeSpan.FromSeconds(10)); + + using (var reference = future.Value) + { + Assert.AreEqual(1, Reference.Counter(url)); + } + Assert.AreEqual(0, Reference.Counter(url)); + } + foreach (var url in TestUtils.ImageURLs) + { + Assert.AreEqual(0, Reference.Counter(url)); + } + } + + [UnityTest] public IEnumerator CleanMemoryCacheAll_NoLogs() => TestUtils.RunNoLogs(CleanMemoryCacheAll); + [UnityTest] public IEnumerator CleanMemoryCacheAll() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = true; + + var url = TestUtils.ImageURLs[0]; + + var future = ImageLoader.LoadSpriteRef(url); + yield return future.TimeoutCoroutine(TimeSpan.FromSeconds(10)); + + var ref0 = future.Value; + Assert.AreEqual(1, Reference.Counter(url)); + + LogAssert.Expect(LogType.Error, $"[ImageLoader] There are 1 references to the object, clear them first. URL={url}"); + ImageLoader.ClearMemoryCacheAll(); + + Assert.IsNotNull(ref0.Value); + Assert.AreEqual(1, Reference.Counter(url)); + + ref0.Dispose(); + Assert.IsNull(ref0.Value); + Assert.AreEqual(0, Reference.Counter(url)); + } + + [UnityTest] public IEnumerator LoadOneMake1000ReferencesLaterDispose_NoLogs() => TestUtils.RunNoLogs(LoadOneMake1000ReferencesLaterDispose); + [UnityTest] public IEnumerator LoadOneMake1000ReferencesLaterDispose() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = true; + + var url = TestUtils.ImageURLs[0]; + + var task1 = ImageLoader.LoadSpriteRef(url).AsTask(); + yield return task1.TimeoutCoroutine(TimeSpan.FromSeconds(25)); + + var ref1_1 = task1.Result; + Assert.NotNull(ref1_1); + Assert.AreEqual(1, Reference.Counter(url)); + + var count = 1000; + var references = new Reference[count]; + for (var i = 0; i < count; i++) + { + var reference = ImageLoader.LoadSpriteRefFromMemoryCache(url); + Assert.NotNull(reference); + Assert.AreEqual(i + 2, Reference.Counter(url)); + references[i] = reference; + } + + ref1_1.Dispose(); + Assert.AreEqual(count, Reference.Counter(url)); + + for (var i = 0; i < count; i++) + { + references[i].Dispose(); + Assert.AreEqual(count - i - 1, Reference.Counter(url)); + } + } + + [UnityTest] public IEnumerator KeepReferenceButDisposeFuture_NoLogs() => TestUtils.RunNoLogs(KeepReferenceButDisposeFuture); + [UnityTest] public IEnumerator KeepReferenceButDisposeFuture() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = true; + + var url = TestUtils.ImageURLs[0]; + + var future = ImageLoader.LoadSpriteRef(url); + var task1 = future.AsTask(); + yield return task1.TimeoutCoroutine(TimeSpan.FromSeconds(25)); + + Assert.AreEqual(1, Reference.Counter(url)); + + var ref1 = task1.Result; + Assert.NotNull(ref1); + Assert.AreEqual(1, Reference.Counter(url)); + + future.Dispose(); + Assert.AreEqual(1, Reference.Counter(url)); + + ref1.Dispose(); + Assert.AreEqual(0, Reference.Counter(url)); + } + + [UnityTest] public IEnumerator LoadOneMake1000ReferencesImmediateDispose_NoLogs() => TestUtils.RunNoLogs(LoadOneMake1000ReferencesImmediateDispose); + [UnityTest] public IEnumerator LoadOneMake1000ReferencesImmediateDispose() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = true; + + var url = TestUtils.ImageURLs[0]; + + var future = ImageLoader.LoadSpriteRef(url); + var task1 = future.AsTask(); + yield return task1.TimeoutCoroutine(TimeSpan.FromSeconds(25)); + + var ref1_1 = task1.Result; + Assert.NotNull(ref1_1); + Assert.AreEqual(1, Reference.Counter(url)); + + var count = 1000; + for (var i = 0; i < count; i++) + { + Assert.AreEqual(1, Reference.Counter(url), $"ref[{i}] going to create."); + + var reference = ImageLoader.LoadSpriteRefFromMemoryCache(url); + Assert.NotNull(reference); + Assert.AreEqual(2, Reference.Counter(url), $"ref[{i}] is created, but reference counter is wrong."); + + reference.Dispose(); + Assert.AreEqual(1, Reference.Counter(url), $"ref[{i}] is disposed, but it should be still in memory cache because of another ref."); + } + Assert.AreEqual(1, Reference.Counter(url)); + + future.Dispose(); + Assert.AreEqual(1, Reference.Counter(url)); + + ref1_1.Dispose(); + Assert.AreEqual(0, Reference.Counter(url)); + } + + [UnityTest] public IEnumerator LoadOneMake1000ReferencesInParallelLateDispose_NoLogs() => TestUtils.RunNoLogs(LoadOneMake1000ReferencesInParallelLateDispose); + [UnityTest] public IEnumerator LoadOneMake1000ReferencesInParallelLateDispose() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = true; + + var url = TestUtils.ImageURLs[0]; + + var task1 = ImageLoader.LoadSpriteRef(url).AsTask(); + yield return task1.TimeoutCoroutine(TimeSpan.FromSeconds(25)); + + var ref1_1 = task1.Result; + Assert.NotNull(ref1_1); + Assert.AreEqual(1, Reference.Counter(url)); + + var count = 1000; + var references = new Reference[count]; + var tasks = Enumerable.Range(0, count) + .Select(i => Task.Run(() => + { + var reference = ImageLoader.LoadSpriteRefFromMemoryCache(url); + Assert.NotNull(reference); + references[i] = reference; + return reference; + })) + .ToArray(); + + yield return Task.WhenAll(tasks).TimeoutCoroutine(TimeSpan.FromSeconds(10)); + Assert.AreEqual(count + 1, Reference.Counter(url)); + + foreach (var reference in references) + reference.Dispose(); + Assert.AreEqual(1, Reference.Counter(url)); + + ref1_1.Dispose(); + Assert.AreEqual(0, Reference.Counter(url)); + } + + [UnityTest] public IEnumerator Load1Sprite2TimesAnd2TimesFromCache_NoLogs() => TestUtils.RunNoLogs(Load1Sprite2TimesAnd2TimesFromCache); + [UnityTest] public IEnumerator Load1Sprite2TimesAnd2TimesFromCache() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = true; + + var url = TestUtils.ImageURLs[0]; + + var task1 = ImageLoader.LoadSpriteRef(url).AsTask(); + yield return task1.TimeoutCoroutine(TimeSpan.FromSeconds(25)); + + var ref0 = task1.Result; + Assert.AreEqual(1, Reference.Counter(url)); + + var task2 = ImageLoader.LoadSpriteRef(url).AsTask(); + yield return task2.TimeoutCoroutine(TimeSpan.FromSeconds(25)); + + var ref1 = task2.Result; + Assert.AreEqual(2, Reference.Counter(url)); + + var ref2 = ImageLoader.LoadSpriteRefFromMemoryCache(url); + Assert.AreEqual(3, Reference.Counter(url)); + + var ref3 = ImageLoader.LoadSpriteRefFromMemoryCache(url); + Assert.AreEqual(4, Reference.Counter(url)); + + ref0.Dispose(); + Assert.AreEqual(3, Reference.Counter(url)); + Assert.IsNull(ref0.Value); + Assert.IsNotNull(ref1.Value); + Assert.IsNotNull(ref2.Value); + Assert.IsNotNull(ref3.Value); + + ref1.Dispose(); + Assert.AreEqual(2, Reference.Counter(url)); + Assert.IsNull(ref0.Value); + Assert.IsNull(ref1.Value); + Assert.IsNotNull(ref2.Value); + Assert.IsNotNull(ref3.Value); + + ref2.Dispose(); + Assert.AreEqual(1, Reference.Counter(url)); + Assert.IsNull(ref0.Value); + Assert.IsNull(ref1.Value); + Assert.IsNull(ref2.Value); + Assert.IsNotNull(ref3.Value); + + ref3.Dispose(); + Assert.AreEqual(0, Reference.Counter(url)); + Assert.IsNull(ref0.Value); + Assert.IsNull(ref1.Value); + Assert.IsNull(ref2.Value); + Assert.IsNull(ref3.Value); + } + + [UnityTest] public IEnumerator Load2SpritesTimesAnd2TimesFromCache_NoLogs() => TestUtils.RunNoLogs(Load2SpritesTimesAnd2TimesFromCache); + [UnityTest] public IEnumerator Load2SpritesTimesAnd2TimesFromCache() + { + ImageLoader.settings.useDiskCache = true; + ImageLoader.settings.useMemoryCache = true; + + var url = TestUtils.ImageURLs[0]; + var url2 = TestUtils.ImageURLs[1]; + + var task1 = ImageLoader.LoadSpriteRef(url).AsTask(); + yield return task1.TimeoutCoroutine(TimeSpan.FromSeconds(25)); + + var ref1_1 = task1.Result; + Assert.NotNull(ref1_1); + Assert.AreEqual(1, Reference.Counter(url)); + + var task2 = ImageLoader.LoadSpriteRef(url2).AsTask(); + yield return task2.TimeoutCoroutine(TimeSpan.FromSeconds(25)); + + var ref2_1 = task2.Result; + Assert.NotNull(ref2_1); + Assert.AreEqual(1, Reference.Counter(url2)); + + var ref1_2 = ImageLoader.LoadSpriteRefFromMemoryCache(url); + Assert.NotNull(ref1_2); + Assert.AreEqual(2, Reference.Counter(url)); + + var ref2_2 = ImageLoader.LoadSpriteRefFromMemoryCache(url2); + Assert.NotNull(ref2_2); + Assert.AreEqual(2, Reference.Counter(url2)); + + ref1_1.Dispose(); + Assert.AreEqual(1, Reference.Counter(url)); + Assert.AreEqual(2, Reference.Counter(url2)); + Assert.IsNull(ref1_1.Value); + Assert.IsNotNull(ref2_1.Value); + Assert.IsNotNull(ref1_2.Value); + Assert.IsNotNull(ref2_2.Value); + + ref2_1.Dispose(); + Assert.AreEqual(1, Reference.Counter(url)); + Assert.AreEqual(1, Reference.Counter(url2)); + Assert.IsNull(ref1_1.Value); + Assert.IsNull(ref2_1.Value); + Assert.IsNotNull(ref1_2.Value); + Assert.IsNotNull(ref2_2.Value); + + ref1_2.Dispose(); + Assert.AreEqual(0, Reference.Counter(url)); + Assert.AreEqual(1, Reference.Counter(url2)); + Assert.IsNull(ref1_1.Value); + Assert.IsNull(ref2_1.Value); + Assert.IsNull(ref1_2.Value); + Assert.IsNotNull(ref2_2.Value); + + ref2_2.Dispose(); + Assert.AreEqual(0, Reference.Counter(url)); + Assert.AreEqual(0, Reference.Counter(url2)); + Assert.IsNull(ref1_1.Value); + Assert.IsNull(ref2_1.Value); + Assert.IsNull(ref1_2.Value); + Assert.IsNull(ref2_2.Value); + } + } +} \ No newline at end of file diff --git a/Assets/_PackageRoot/Tests/Runtime/TestReference.cs.meta b/Assets/_PackageRoot/Tests/Runtime/TestReference.cs.meta new file mode 100644 index 0000000..2163208 --- /dev/null +++ b/Assets/_PackageRoot/Tests/Runtime/TestReference.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2395d12b483238641b2509bfc17f9bdd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_PackageRoot/package.json b/Assets/_PackageRoot/package.json index fe9dec7..dc9ee8a 100644 --- a/Assets/_PackageRoot/package.json +++ b/Assets/_PackageRoot/package.json @@ -7,7 +7,7 @@ "url": "https://github.com/IvanMurzak" }, "license": "MIT", - "version": "6.0.2", + "version": "7.0.0", "unity": "2019.4", "description": "Asynchronous image loading from remote or local destination. It has two layers of configurable Memory and Disk cache systems.", "keywords": [ diff --git a/README.md b/README.md index 0bf57d3..f6cb8e4 100644 --- a/README.md +++ b/README.md @@ -15,13 +15,13 @@ image.sprite = await ImageLoader.LoadSprite(imageURL); Don't wait, use callback to set loaded image later: ```csharp -ImageLoader.LoadSprite(imageURL).ThenSet(image).Forget(); +ImageLoader.LoadSprite(imageURL).Consume(image).Forget(); ``` Use callback to set image and still wait for the completion: ```csharp -await ImageLoader.LoadSprite(imageURL).ThenSet(image); +await ImageLoader.LoadSprite(imageURL).Consume(image); ``` ## Features @@ -33,10 +33,10 @@ await ImageLoader.LoadSprite(imageURL).ThenSet(image); - ✔️ Avoids loading same image multiple times simultaneously, a new load task waits for completion of existed task - ✔️ Uses `UnityWebRequest` to load data which works smooth across all platforms including `WebGL` - ✔️ Cache supported on at `WebGL`. Memory cache works, Disk cache isn't allowed by the platform -- ✔️ Set into Image `ImageLoader.LoadSprite(imageURL).ThenSet(image);` -- ✔️ Set into RawImage `ImageLoader.LoadSprite(imageURL).ThenSet(rawImage);` -- ✔️ Set into Material `ImageLoader.LoadSprite(imageURL).ThenSet("_MainTex", material);` -- ✔️ Set into SpriteRenderer `ImageLoader.LoadSprite(imageURL).ThenSet(spriteRenderer);` +- ✔️ Set into Image `ImageLoader.LoadSprite(imageURL).Consume(image);` +- ✔️ Set into RawImage `ImageLoader.LoadSprite(imageURL).Consume(rawImage);` +- ✔️ Set into Material `ImageLoader.LoadSprite(imageURL).Consume("_MainTex", material);` +- ✔️ Set into SpriteRenderer `ImageLoader.LoadSprite(imageURL).Consume(spriteRenderer);` - ✔️ [Set into anything](#cancellation) - ✔️ Cancellation `ImageLoader.LoadSprite(imageURL).Cancel();` - ✔️ Cancellation callback `ImageLoader.LoadSprite(imageURL).Cancelled(() => ...);` @@ -57,6 +57,7 @@ await ImageLoader.LoadSprite(imageURL).ThenSet(image); - [Load `Sprite` then set into multiple `Image`](#load-sprite-then-set-into-multiple-image) - [Error handling](#error-handling) - [Async `await` and `Forget`](#async-await-and-forget) + - [Placeholder](#placeholder) - [Cancellation](#cancellation) - [Cancel by MonoBehaviour events](#cancel-by-monobehaviour-events) - [Explicit cancellation](#explicit-cancellation) @@ -126,6 +127,17 @@ ImageLoader.LoadSprite(imageURL) // loading process started .LoadedFromSource (sprite => Debug.Log("Loaded from source")) // on loaded from source │ // ────────────────────────────────────────────────────────────────────────────────────────────────────┘ + // ┌──────────────────────────┬───────────────────────────────────────────┐ + // │ Success lifecycle events │ │ + // └──────────────────────────┘ │ + .Loaded(sprite => Debug.Log("Loaded")) // on successfully loaded │ + // ┌────────────────────────────────────────────────────────┤ + // │ Set/Consume sprite [placeholder, successfully loaded] │ + // └────────────────────────────────────────────────────────┤ + .Consume(sprite => Debug.Log("Consumed")) // │ + .Consume(image) // │ + // ───────────────────────────────────────────────────────────────────────┘ + // ┌───────────────────────────┬──────────────────────────────────────────┐ // │ Negative lifecycle events │ │ // └───────────────────────────┘ │ @@ -133,13 +145,6 @@ ImageLoader.LoadSprite(imageURL) // loading process started .Failed(exception => Debug.LogException(exception)) // on failed to load │ // ───────────────────────────────────────────────────────────────────────┘ - // ┌──────────────────────────────────────┬──────────────────────────────┐ - // │ Successfully loaded lifecycle events │ │ - // └──────────────────────────────────────┘ │ - .Then(sprite => Debug.Log("Loaded")) // on loaded │ - .ThenSet(image) // on loaded set sprite into image │ - // ──────────────────────────────────────────────────────────────────────┘ - // ┌──────────────────────┬──────────────────────────────────────────────────────────────────────────┐ // │ The end of lifecycle │ │ // └──────────────────────┘ │ @@ -152,34 +157,34 @@ ImageLoader.LoadSprite(imageURL) // loading process started ## Load `Sprite` then set into `Image` -> [Full sample source code](https://github.com/IvanMurzak/Unity-ImageLoader/blob/master/Assets/_PackageRoot/Samples/SampleLoadSpriteThenSetImage.cs) +> [Full sample source code](https://github.com/IvanMurzak/Unity-ImageLoader/blob/master/Assets/_PackageRoot/Samples/SampleLoadSpriteConsumeImage.cs) ```csharp // Load a sprite from the web and cache it for faster loading next time image.sprite = await ImageLoader.LoadSprite(imageURL); // Load a sprite from the web and set it directly to the Image component -await ImageLoader.LoadSprite(imageURL).ThenSet(image); +await ImageLoader.LoadSprite(imageURL).Consume(image); ``` ## Load `Texture2D` then set into `Material` -> [Full sample source code](https://github.com/IvanMurzak/Unity-ImageLoader/blob/master/Assets/_PackageRoot/Samples/SampleLoadTextureThenSetMaterial.cs) +> [Full sample source code](https://github.com/IvanMurzak/Unity-ImageLoader/blob/master/Assets/_PackageRoot/Samples/SampleLoadTextureConsumeMaterial.cs) ```csharp // Load a Texture2D from the web and cache it for faster loading next time material.mainTexture = await ImageLoader.LoadTexture(imageURL); // Load a Texture2D from the web and set it directly to the Material -await ImageLoader.LoadTexture(imageURL).ThenSet(material); +await ImageLoader.LoadTexture(imageURL).Consume(material); ``` ## Load `Sprite` then set into multiple `Image` -> [Full sample source code](https://github.com/IvanMurzak/Unity-ImageLoader/blob/master/Assets/_PackageRoot/Samples/SampleLoadSpriteThenSetIntoMultipleImages.cs) +> [Full sample source code](https://github.com/IvanMurzak/Unity-ImageLoader/blob/master/Assets/_PackageRoot/Samples/SampleLoadSpriteConsumeIntoMultipleImages.cs) ```csharp -ImageLoader.LoadSprite(imageURL).ThenSet(image1, image2).Forget(); +ImageLoader.LoadSprite(imageURL).Consume(image1, image2).Forget(); ``` ## Error handling @@ -188,12 +193,12 @@ ImageLoader.LoadSprite(imageURL).ThenSet(image1, image2).Forget(); ```csharp ImageLoader.LoadSprite(imageURL) // Attempt to load a sprite - .ThenSet(image) // If successful, set the sprite to the Image component + .Consume(image) // If successful, set the sprite to the Image component .Failed(exception => Debug.LogException(exception)) // If an error occurs, log the exception .Forget(); // Forget the task to avoid compilation warning ImageLoader.LoadSprite(imageURL) // Attempt to load a sprite - .ThenSet(image) // If successful, set the sprite to the Image component + .Consume(image) // If successful, set the sprite to the Image component .Then(sprite => image.gameObject.SetActive(true)) // If successful, activate the GameObject .Failed(exception => image.gameObject.SetActive(false)) // If an error occurs, deactivate the GameObject .Forget(); // Forget the task to avoid compilation warning @@ -208,12 +213,32 @@ ImageLoader.LoadSprite(imageURL) // Attempt to load a sprite await ImageLoader.LoadSprite(imageURL); // Load image, set image and wait -await ImageLoader.LoadSprite(imageURL).ThenSet(image); +await ImageLoader.LoadSprite(imageURL).Consume(image); // Skip waiting for completion. // To do that we can simply remove 'await' from the start. // To avoid compilation warning need to add '.Forget()'. -ImageLoader.LoadSprite(imageURL).ThenSet(image).Forget(); +ImageLoader.LoadSprite(imageURL).Consume(image).Forget(); +``` + +## Placeholder + +While the target image is loading it would be a good idea to set placeholder image. Also, it works well for setting image if loading fails. + +> [Full sample source code](https://github.com/IvanMurzak/Unity-ImageLoader/blob/master/Assets/_PackageRoot/Samples/SamplePlaceholder.cs) + +```csharp +ImageLoader.LoadSprite(imageURL) + // set placeholder in all conditions + .SetPlaceholder(placeholderAny) + + // set placeholder in a specific conditions + .SetPlaceholder(placeholderLoadingFromSource, PlaceholderTrigger.LoadingFromSource) + .SetPlaceholder(placeholderFailedToLoad, PlaceholderTrigger.FailedToLoad) + + // set consumer + .Consume(image) + .Forget(); ``` ## Cancellation @@ -226,7 +251,7 @@ Cancellation is helpful if target image consumer doesn't exist anymore. For exam ```csharp ImageLoader.LoadSprite(imageURL) - .ThenSet(image) + .Consume(image) .CancelOnEnable(this) // cancel on OnEnable event of current MonoBehaviour .CancelOnDisable(this) // cancel on OnDisable event of current MonoBehaviour .CancelOnDestroy(this); // cancel on OnDestroy event of current MonoBehaviour @@ -235,7 +260,7 @@ ImageLoader.LoadSprite(imageURL) ### Explicit cancellation ```csharp -var future = ImageLoader.LoadSprite(imageURL).ThenSet(image); +var future = ImageLoader.LoadSprite(imageURL).Consume(image); future.Cancel(); ``` @@ -246,7 +271,7 @@ var cancellationTokenSource = new CancellationTokenSource(); // loading with attached cancellation token ImageLoader.LoadSprite(imageURL, cancellationToken: cancellationTokenSource.Token) - .ThenSet(image) + .Consume(image) .Forget(); cancellationTokenSource.Cancel(); // canceling @@ -256,7 +281,7 @@ cancellationTokenSource.Cancel(); // canceling var cancellationTokenSource = new CancellationTokenSource(); ImageLoader.LoadSprite(imageURL) - .ThenSet(image) + .Consume(image) .Register(cancellationTokenSource.Token) // registering cancellation token .Forget(); @@ -266,7 +291,7 @@ cancellationTokenSource.Cancel(); // canceling ### Cancellation by `using` ```csharp -using (var future = ImageLoader.LoadSprite(imageURL).ThenSet(image)) +using (var future = ImageLoader.LoadSprite(imageURL).Consume(image)) { // future would be canceled and disposed outside of the brackets } @@ -274,18 +299,18 @@ using (var future = ImageLoader.LoadSprite(imageURL).ThenSet(image)) ```csharp ImageLoader.LoadSprite(imageURL) // load sprite - .ThenSet(image) // if success set sprite into image + .Consume(image) // if success set sprite into image .CancelOnDestroy(this) // cancel OnDestroy event of current gameObject .Forget(); ImageLoader.LoadSprite(imageURL) // load sprite - .ThenSet(image) // if success set sprite into image + .Consume(image) // if success set sprite into image .Failed(exception => Debug.LogException(exception)) // if fail print exception .CancelOnDestroy(this) // cancel OnDestroy event of current gameObject .Forget(); ImageLoader.LoadSprite(imageURL) // load sprite - .ThenSet(image) // if success set sprite into image + .Consume(image) // if success set sprite into image .Then(sprite => image.gameObject.SetActive(true)) // if success activate gameObject .Failed(exception => image.gameObject.SetActive(false)) // if fail deactivate gameObject .Canceled(() => Debug.Log("ImageLoading canceled")) // if cancelled @@ -309,7 +334,7 @@ Set timeout for a specific loading request (`IFuture`): ```csharp ImageLoader.LoadSprite(imageURL) // load sprite - .ThenSet(image) // if success set sprite into image + .Consume(image) // if success set sprite into image .Timeout(TimeSpan.FromSeconds(10)) // set timeout duration 10 seconds .Forget(); ``` @@ -430,11 +455,11 @@ reference = null; > [Full sample source code](https://github.com/IvanMurzak/Unity-ImageLoader/blob/master/Assets/_PackageRoot/Samples/SampleReferences.cs) -`Reference.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. +`Future>.Consume` 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. ```csharp ImageLoader.LoadSpriteRef(imageURL) // load sprite using Reference - .ThenSet(image) // if success set sprite into image, also creates binding to `image` + .Consume(image) // if success set sprite into image, also creates binding to `image` .Forget(); ```