Skip to content

Instantly share code, notes, and snippets.

@ericallam
Created September 2, 2020 10:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ericallam/2b53c5525e8a5e1645a7ad13495464fb to your computer and use it in GitHub Desktop.
Save ericallam/2b53c5525e8a5e1645a7ad13495464fb to your computer and use it in GitHub Desktop.
UnityWebRequest DownloadTexture leak
using System;
using System.Collections.Generic;
using MEC;
using RSG;
using UnityEngine;
using UnityEngine.Networking;
// This is the original implementation
namespace DefaultNamespace
{
public class TextureFromFile
{
private string FilePath { get; set; }
private UnityWebRequest _unityWebRequest;
private bool _isRunning;
public TextureFromFile(string filePath)
{
FilePath = filePath;
_isRunning = false;
}
public static TextureFromFile AtPath(string filePath)
{
return new TextureFromFile(filePath);
}
public IPromise<Texture2D> GetTexture2D(Func<bool> cancelEvaluator) {
if (FilePath == null)
{
return Promise<Texture2D>.Rejected(new Exception("FilePath is empty"));
}
Promise<Texture2D> promise = new Promise<Texture2D>();
Timing.RunCoroutine(TextureFromFilePathRoutine(promise).CancelWith(cancelEvaluator));
return promise;
}
public IPromise<Texture2D> GetTexture2D(GameObject activeGameObject) {
if (FilePath == null)
{
return Promise<Texture2D>.Rejected(new Exception("FilePath is empty"));
}
Promise<Texture2D> promise = new Promise<Texture2D>();
Timing.RunCoroutine(TextureFromFilePathRoutine(promise).CancelWith(activeGameObject));
return promise;
}
public IPromise<Texture2D> GetTexture2DWhileGameObjectExists(GameObject cancelGameObject)
{
return GetTexture2D(() => cancelGameObject != null);
}
public IPromise<Texture2D> GetTexture2DWhileGameObjectIsActive(GameObject cancelGameObject)
{
return GetTexture2D(() => cancelGameObject != null && cancelGameObject.activeInHierarchy);
}
public IPromise<Sprite> GetSprite(Func<bool> cancelEvaluator)
{
Promise<Sprite> promise = new Promise<Sprite>();
GetTexture2D(cancelEvaluator)
.Then(texture2D =>
{
Sprite sprite = Sprite.Create(texture2D, new Rect(0f, 0f, texture2D.width, texture2D.height), new Vector2(.5f, .5f), 200f, 1, SpriteMeshType.FullRect, Vector4.zero, false);
sprite.name = texture2D.name;
promise.Resolve(sprite);
})
.Catch(promise.Reject);
return promise;
}
public IPromise<Sprite> GetSprite(GameObject cancelGameObject)
{
Promise<Sprite> promise = new Promise<Sprite>();
GetTexture2D(cancelGameObject)
.Then(texture2D =>
{
Sprite sprite = Sprite.Create(texture2D, new Rect(0f, 0f, texture2D.width, texture2D.height), new Vector2(.5f, .5f), 200f, 1, SpriteMeshType.FullRect, Vector4.zero, false);
sprite.name = texture2D.name;
promise.Resolve(sprite);
})
.Catch(promise.Reject);
return promise;
}
public IPromise<Sprite> GetSpriteWhileGameObjectExists(GameObject cancelGameObject)
{
return GetSprite(() => cancelGameObject != null);
}
public IPromise<Sprite> GetSpriteWhileGameObjectIsActive(GameObject cancelGameObject)
{
return GetSprite(cancelGameObject);
}
public void Cancel()
{
if (!_isRunning) return;
_unityWebRequest.Abort();
}
private IEnumerator<float> TextureFromFilePathRoutine(IPendingPromise<Texture2D> promise) {
if (FilePath == null) {
promise.Reject(new Exception("FilePath is empty"));
yield break;
}
using (_unityWebRequest = UnityWebRequestTexture.GetTexture($"file://{FilePath}"))
{
UnityWebRequestAsyncOperation asyncOperation = _unityWebRequest.SendWebRequest();
_isRunning = true;
yield return Timing.WaitUntilDone(asyncOperation);
if (_unityWebRequest.isNetworkError || _unityWebRequest.isHttpError) {
promise.Reject(new Exception($"TextureFromFile.TextureFromFilePathRoutine() failed to load with error: {_unityWebRequest.error}"));
}
else {
Texture2D texture = DownloadHandlerTexture.GetContent(_unityWebRequest);
if (texture == null)
{
string errorMessage = $"Texture failed to create for {FilePath}: {_unityWebRequest.error}";
Exception exception = new Exception(errorMessage);
promise.Reject(exception);
yield break;
}
//name the texture (this is useful when debugging memory
Uri uri = new Uri(FilePath);
string textureName = uri.Segments[uri.Segments.Length - 1];
texture.name = textureName;
promise.Resolve(texture);
}
_isRunning = false;
}
}
}
}
using System;
using System.Collections.Generic;
using MEC;
using RSG;
using UnityEngine;
using UnityEngine.Networking;
// This one moves the `Texture2D texture = DownloadHandlerTexture.GetContent(_unityWebRequest);` line above the if
namespace DefaultNamespace
{
public class TextureFromFile
{
private string FilePath { get; set; }
private UnityWebRequest _unityWebRequest;
private bool _isRunning;
public TextureFromFile(string filePath)
{
FilePath = filePath;
_isRunning = false;
}
public static TextureFromFile AtPath(string filePath)
{
return new TextureFromFile(filePath);
}
public IPromise<Texture2D> GetTexture2D(Func<bool> cancelEvaluator) {
if (FilePath == null)
{
return Promise<Texture2D>.Rejected(new Exception("FilePath is empty"));
}
Promise<Texture2D> promise = new Promise<Texture2D>();
Timing.RunCoroutine(TextureFromFilePathRoutine(promise).CancelWith(cancelEvaluator));
return promise;
}
public IPromise<Texture2D> GetTexture2D(GameObject activeGameObject) {
if (FilePath == null)
{
return Promise<Texture2D>.Rejected(new Exception("FilePath is empty"));
}
Promise<Texture2D> promise = new Promise<Texture2D>();
Timing.RunCoroutine(TextureFromFilePathRoutine(promise).CancelWith(activeGameObject));
return promise;
}
public IPromise<Texture2D> GetTexture2DWhileGameObjectExists(GameObject cancelGameObject)
{
return GetTexture2D(() => cancelGameObject != null);
}
public IPromise<Texture2D> GetTexture2DWhileGameObjectIsActive(GameObject cancelGameObject)
{
return GetTexture2D(() => cancelGameObject != null && cancelGameObject.activeInHierarchy);
}
public IPromise<Sprite> GetSprite(Func<bool> cancelEvaluator)
{
Promise<Sprite> promise = new Promise<Sprite>();
GetTexture2D(cancelEvaluator)
.Then(texture2D =>
{
Sprite sprite = Sprite.Create(texture2D, new Rect(0f, 0f, texture2D.width, texture2D.height), new Vector2(.5f, .5f), 200f, 1, SpriteMeshType.FullRect, Vector4.zero, false);
sprite.name = texture2D.name;
promise.Resolve(sprite);
})
.Catch(promise.Reject);
return promise;
}
public IPromise<Sprite> GetSprite(GameObject cancelGameObject)
{
Promise<Sprite> promise = new Promise<Sprite>();
GetTexture2D(cancelGameObject)
.Then(texture2D =>
{
Sprite sprite = Sprite.Create(texture2D, new Rect(0f, 0f, texture2D.width, texture2D.height), new Vector2(.5f, .5f), 200f, 1, SpriteMeshType.FullRect, Vector4.zero, false);
sprite.name = texture2D.name;
promise.Resolve(sprite);
})
.Catch(promise.Reject);
return promise;
}
public IPromise<Sprite> GetSpriteWhileGameObjectExists(GameObject cancelGameObject)
{
return GetSprite(() => cancelGameObject != null);
}
public IPromise<Sprite> GetSpriteWhileGameObjectIsActive(GameObject cancelGameObject)
{
return GetSprite(cancelGameObject);
}
public void Cancel()
{
if (!_isRunning) return;
_unityWebRequest.Abort();
}
private IEnumerator<float> TextureFromFilePathRoutine(IPendingPromise<Texture2D> promise) {
if (FilePath == null) {
promise.Reject(new Exception("FilePath is empty"));
yield break;
}
using (_unityWebRequest = UnityWebRequestTexture.GetTexture($"file://{FilePath}"))
{
UnityWebRequestAsyncOperation asyncOperation = _unityWebRequest.SendWebRequest();
_isRunning = true;
yield return Timing.WaitUntilDone(asyncOperation);
Texture2D texture = DownloadHandlerTexture.GetContent(_unityWebRequest);
if (_unityWebRequest.isNetworkError || _unityWebRequest.isHttpError) {
promise.Reject(new Exception($"TextureFromFile.TextureFromFilePathRoutine() failed to load with error: {_unityWebRequest.error}"));
}
else {
if (texture == null)
{
string errorMessage = $"Texture failed to create for {FilePath}: {_unityWebRequest.error}";
Exception exception = new Exception(errorMessage);
promise.Reject(exception);
yield break;
}
//name the texture (this is useful when debugging memory
Uri uri = new Uri(FilePath);
string textureName = uri.Segments[uri.Segments.Length - 1];
texture.name = textureName;
promise.Resolve(texture);
}
_isRunning = false;
}
}
}
}
using System;
using System.Collections.Generic;
using MEC;
using RSG;
using UnityEngine;
using UnityEngine.Networking;
// Removes unityWebRequest.Abort() but the DownloadHandlerTexture.GetContent call is only in the success path
namespace DefaultNamespace
{
public class TextureFromFile
{
private string FilePath { get; set; }
private UnityWebRequest _unityWebRequest;
private bool _isRunning;
private bool _isCancelled;
public TextureFromFile(string filePath)
{
FilePath = filePath;
_isRunning = false;
_isCancelled = false;
}
public static TextureFromFile AtPath(string filePath)
{
return new TextureFromFile(filePath);
}
public IPromise<Texture2D> GetTexture2D(Func<bool> cancelEvaluator) {
if (FilePath == null)
{
return Promise<Texture2D>.Rejected(new Exception("FilePath is empty"));
}
Promise<Texture2D> promise = new Promise<Texture2D>();
Timing.RunCoroutine(TextureFromFilePathRoutine(promise).CancelWith(cancelEvaluator));
return promise;
}
public IPromise<Texture2D> GetTexture2D(GameObject activeGameObject) {
if (FilePath == null)
{
return Promise<Texture2D>.Rejected(new Exception("FilePath is empty"));
}
Promise<Texture2D> promise = new Promise<Texture2D>();
Timing.RunCoroutine(TextureFromFilePathRoutine(promise).CancelWith(activeGameObject));
return promise;
}
public IPromise<Texture2D> GetTexture2DWhileGameObjectExists(GameObject cancelGameObject)
{
return GetTexture2D(() => cancelGameObject != null);
}
public IPromise<Texture2D> GetTexture2DWhileGameObjectIsActive(GameObject cancelGameObject)
{
return GetTexture2D(() => cancelGameObject != null && cancelGameObject.activeInHierarchy);
}
public IPromise<Sprite> GetSprite(Func<bool> cancelEvaluator)
{
Promise<Sprite> promise = new Promise<Sprite>();
GetTexture2D(cancelEvaluator)
.Then(texture2D =>
{
Sprite sprite = Sprite.Create(texture2D, new Rect(0f, 0f, texture2D.width, texture2D.height), new Vector2(.5f, .5f), 200f, 1, SpriteMeshType.FullRect, Vector4.zero, false);
sprite.name = texture2D.name;
promise.Resolve(sprite);
})
.Catch(promise.Reject);
return promise;
}
public IPromise<Sprite> GetSprite(GameObject cancelGameObject)
{
Promise<Sprite> promise = new Promise<Sprite>();
GetTexture2D(cancelGameObject)
.Then(texture2D =>
{
Sprite sprite = Sprite.Create(texture2D, new Rect(0f, 0f, texture2D.width, texture2D.height), new Vector2(.5f, .5f), 200f, 1, SpriteMeshType.FullRect, Vector4.zero, false);
sprite.name = texture2D.name;
promise.Resolve(sprite);
})
.Catch(promise.Reject);
return promise;
}
public IPromise<Sprite> GetSpriteWhileGameObjectExists(GameObject cancelGameObject)
{
return GetSprite(() => cancelGameObject != null);
}
public IPromise<Sprite> GetSpriteWhileGameObjectIsActive(GameObject cancelGameObject)
{
return GetSprite(cancelGameObject);
}
public void Cancel()
{
if (!_isRunning) return;
_isCancelled = true;
}
private IEnumerator<float> TextureFromFilePathRoutine(IPendingPromise<Texture2D> promise) {
if (FilePath == null) {
promise.Reject(new Exception("FilePath is empty"));
yield break;
}
using (_unityWebRequest = UnityWebRequestTexture.GetTexture($"file://{FilePath}"))
{
UnityWebRequestAsyncOperation asyncOperation = _unityWebRequest.SendWebRequest();
_isRunning = true;
yield return Timing.WaitUntilDone(asyncOperation);
if (_isCancelled)
{
promise.Reject(new Exception($"TextureFromFile.TextureFromFilePathRoutine() cancelled"));
}
else if (_unityWebRequest.isNetworkError || _unityWebRequest.isHttpError) {
promise.Reject(new Exception($"TextureFromFile.TextureFromFilePathRoutine() failed to load with error: {_unityWebRequest.error}"));
}
else {
Texture2D texture = DownloadHandlerTexture.GetContent(_unityWebRequest);
if (texture == null)
{
string errorMessage = $"Texture failed to create for {FilePath}: {_unityWebRequest.error}";
Exception exception = new Exception(errorMessage);
promise.Reject(exception);
yield break;
}
//name the texture (this is useful when debugging memory
Uri uri = new Uri(FilePath);
string textureName = uri.Segments[uri.Segments.Length - 1];
texture.name = textureName;
promise.Resolve(texture);
}
_isRunning = false;
}
}
}
}
using System;
using System.Collections.Generic;
using MEC;
using RSG;
using UnityEngine;
using UnityEngine.Networking;
// Removes unityWebRequest.Abort() and moves the DownloadHandlerTexture.GetContent call above the if
namespace DefaultNamespace
{
public class TextureFromFile
{
private string FilePath { get; set; }
private UnityWebRequest _unityWebRequest;
private bool _isRunning;
private bool _isCancelled;
public TextureFromFile(string filePath)
{
FilePath = filePath;
_isRunning = false;
_isCancelled = false;
}
public static TextureFromFile AtPath(string filePath)
{
return new TextureFromFile(filePath);
}
public IPromise<Texture2D> GetTexture2D(Func<bool> cancelEvaluator) {
if (FilePath == null)
{
return Promise<Texture2D>.Rejected(new Exception("FilePath is empty"));
}
Promise<Texture2D> promise = new Promise<Texture2D>();
Timing.RunCoroutine(TextureFromFilePathRoutine(promise).CancelWith(cancelEvaluator));
return promise;
}
public IPromise<Texture2D> GetTexture2D(GameObject activeGameObject) {
if (FilePath == null)
{
return Promise<Texture2D>.Rejected(new Exception("FilePath is empty"));
}
Promise<Texture2D> promise = new Promise<Texture2D>();
Timing.RunCoroutine(TextureFromFilePathRoutine(promise).CancelWith(activeGameObject));
return promise;
}
public IPromise<Texture2D> GetTexture2DWhileGameObjectExists(GameObject cancelGameObject)
{
return GetTexture2D(() => cancelGameObject != null);
}
public IPromise<Texture2D> GetTexture2DWhileGameObjectIsActive(GameObject cancelGameObject)
{
return GetTexture2D(() => cancelGameObject != null && cancelGameObject.activeInHierarchy);
}
public IPromise<Sprite> GetSprite(Func<bool> cancelEvaluator)
{
Promise<Sprite> promise = new Promise<Sprite>();
GetTexture2D(cancelEvaluator)
.Then(texture2D =>
{
Sprite sprite = Sprite.Create(texture2D, new Rect(0f, 0f, texture2D.width, texture2D.height), new Vector2(.5f, .5f), 200f, 1, SpriteMeshType.FullRect, Vector4.zero, false);
sprite.name = texture2D.name;
promise.Resolve(sprite);
})
.Catch(promise.Reject);
return promise;
}
public IPromise<Sprite> GetSprite(GameObject cancelGameObject)
{
Promise<Sprite> promise = new Promise<Sprite>();
GetTexture2D(cancelGameObject)
.Then(texture2D =>
{
Sprite sprite = Sprite.Create(texture2D, new Rect(0f, 0f, texture2D.width, texture2D.height), new Vector2(.5f, .5f), 200f, 1, SpriteMeshType.FullRect, Vector4.zero, false);
sprite.name = texture2D.name;
promise.Resolve(sprite);
})
.Catch(promise.Reject);
return promise;
}
public IPromise<Sprite> GetSpriteWhileGameObjectExists(GameObject cancelGameObject)
{
return GetSprite(() => cancelGameObject != null);
}
public IPromise<Sprite> GetSpriteWhileGameObjectIsActive(GameObject cancelGameObject)
{
return GetSprite(cancelGameObject);
}
public void Cancel()
{
if (!_isRunning) return;
_isCancelled = true;
}
private IEnumerator<float> TextureFromFilePathRoutine(IPendingPromise<Texture2D> promise) {
if (FilePath == null) {
promise.Reject(new Exception("FilePath is empty"));
yield break;
}
using (_unityWebRequest = UnityWebRequestTexture.GetTexture($"file://{FilePath}"))
{
UnityWebRequestAsyncOperation asyncOperation = _unityWebRequest.SendWebRequest();
_isRunning = true;
yield return Timing.WaitUntilDone(asyncOperation);
Texture2D texture = DownloadHandlerTexture.GetContent(_unityWebRequest);
if (_isCancelled)
{
promise.Reject(new Exception($"TextureFromFile.TextureFromFilePathRoutine() cancelled"));
}
else if (_unityWebRequest.isNetworkError || _unityWebRequest.isHttpError) {
promise.Reject(new Exception($"TextureFromFile.TextureFromFilePathRoutine() failed to load with error: {_unityWebRequest.error}"));
}
else {
if (texture == null)
{
string errorMessage = $"Texture failed to create for {FilePath}: {_unityWebRequest.error}";
Exception exception = new Exception(errorMessage);
promise.Reject(exception);
yield break;
}
//name the texture (this is useful when debugging memory
Uri uri = new Uri(FilePath);
string textureName = uri.Segments[uri.Segments.Length - 1];
texture.name = textureName;
promise.Resolve(texture);
}
_isRunning = false;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment