diff --git a/CHANGELOG.md b/CHANGELOG.md index 2db27e9..b3eb5e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,9 @@ Jump to version: ## 7.1.1 (In Development) -No code changes +### MLEM.Ui +Improvements +- Construct images in UiParser.ParseImage on the main thread to support usage with KNI ## 7.1.0 diff --git a/MLEM.Ui/Parsers/UiParser.cs b/MLEM.Ui/Parsers/UiParser.cs index f6f8e78..e5a4373 100644 --- a/MLEM.Ui/Parsers/UiParser.cs +++ b/MLEM.Ui/Parsers/UiParser.cs @@ -10,7 +10,6 @@ using MLEM.Ui.Style; #if NETSTANDARD2_0_OR_GREATER || NET6_0_OR_GREATER using System.Net.Http; - #else using System.Net; #endif @@ -139,43 +138,59 @@ namespace MLEM.Ui.Parsers { /// This method invokes an asynchronouns action, meaning the 's will likely not have loaded in when this method returns. /// /// The absolute, relative or web path to the image. - /// An action that is invoked with the loaded image once it is fetched. Note that this action will be invoked asynchronously. + /// An action that is invoked with the loaded image once it is fetched. Note that this action will be invoked synchronously. /// The loaded image. /// Thrown if is null, or if there is an loading the image and is unset. protected Image ParseImage(string path, Action onImageFetched = null) { if (this.GraphicsDevice == null) throw new NullReferenceException("A UI parser requires a GraphicsDevice for parsing images"); - var imageLock = new object(); + var bytesLock = new object(); + byte[] bytes = null; TextureRegion image = null; return new Image(Anchor.AutoLeft, Vector2.One, _ => { - lock (imageLock) - return image; + if (image == null) { + bool bytesNull; + lock (bytesLock) + bytesNull = bytes == null; + if (!bytesNull) { + Texture2D tex; + lock (bytesLock) { + using (var stream = new MemoryStream(bytes)) + tex = Texture2D.FromStream(this.GraphicsDevice, stream); + bytes = null; + } + image = new TextureRegion(tex); + onImageFetched?.Invoke(image); + } + } + return image; }) { SetHeightBasedOnAspect = true, OnAddedToUi = e => { - bool imageNull; - lock (imageLock) - imageNull = image == null; - if (imageNull) - LoadImageAsync(); + if (image == null) { + bool bytesNull; + lock (bytesLock) + bytesNull = bytes == null; + if (bytesNull) + LoadImageStream(); + } }, OnRemovedFromUi = e => { - lock (imageLock) { - image?.Texture.Dispose(); - image = null; - } + lock (bytesLock) + bytes = null; + image?.Texture.Dispose(); + image = null; } }; - async void LoadImageAsync() { + async void LoadImageStream() { // only apply the base path for relative files if (this.ImageBasePath != null && !path.StartsWith("http") && !Path.IsPathRooted(path)) path = $"{this.ImageBasePath}/{path}"; try { - Texture2D tex; + byte[] src; if (path.StartsWith("http")) { - byte[] src; #if NETSTANDARD2_0_OR_GREATER || NET6_0_OR_GREATER using (var client = new HttpClient()) src = await client.GetByteArrayAsync(path); @@ -183,18 +198,16 @@ namespace MLEM.Ui.Parsers { using (var client = new WebClient()) src = await client.DownloadDataTaskAsync(path); #endif - using (var memory = new MemoryStream(src)) - tex = Texture2D.FromStream(this.GraphicsDevice, memory); } else { - using (var stream = Path.IsPathRooted(path) ? File.OpenRead(path) : TitleContainer.OpenStream(path)) - tex = Texture2D.FromStream(this.GraphicsDevice, stream); - } - lock (imageLock) { - if (image == null) { - image = new TextureRegion(tex); - onImageFetched?.Invoke(image); + using (var fileStream = Path.IsPathRooted(path) ? File.OpenRead(path) : TitleContainer.OpenStream(path)) { + using (var memStream = new MemoryStream()) { + await fileStream.CopyToAsync(memStream); + src = memStream.ToArray(); + } } } + lock (bytesLock) + bytes = src; } catch (Exception e) { if (this.ImageExceptionHandler != null) { this.ImageExceptionHandler.Invoke(path, e);