diff --git a/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Materials/BasicMaterial.cs b/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Materials/BasicMaterial.cs index c8a0d75aa5..a6b87836ff 100644 --- a/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Materials/BasicMaterial.cs +++ b/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Materials/BasicMaterial.cs @@ -1,9 +1,11 @@ +using Cysharp.Threading.Tasks; using DCL.Controllers; using DCL.Helpers; using DCL.Models; using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Threading; using UnityEngine; using UnityEngine.Rendering; @@ -34,6 +36,9 @@ public class Model : BaseModel private static readonly int _Cutoff = Shader.PropertyToID("_Cutoff"); private static readonly int _ZWrite = Shader.PropertyToID("_ZWrite"); + private readonly DCLTexture.Fetcher dclTextureFetcher = new DCLTexture.Fetcher(); + private bool isDisposed; + public BasicMaterial() { material = new Material(Utils.EnsureResourcesMaterial("Materials/BasicShapeMaterial")); @@ -78,19 +83,25 @@ public override IEnumerator ApplyChanges(BaseModel newModel) { if (dclTexture == null || dclTexture.id != model.texture) { - yield return DCLTexture.FetchTextureComponent(scene, model.texture, - (downloadedTexture) => + yield return dclTextureFetcher.Fetch( + scene, + model.texture, + texture => { - if ( dclTexture != null ) + if (isDisposed) + return false; + + if (dclTexture != null) { dclTexture.DetachFrom(this); } - material.SetTexture(_BaseMap, downloadedTexture.texture); - dclTexture = downloadedTexture; + material.SetTexture(_BaseMap, texture.texture); + dclTexture = texture; dclTexture.AttachTo(this); - } - ); + return true; + }).ToCoroutine(); + // using `ToCoroutine()` since using Task directly arise some component lifecycle issues } } else @@ -181,6 +192,8 @@ void OnMaterialDetached(IDCLEntity entity) public override void Dispose() { + isDisposed = true; + dclTexture?.DetachFrom(this); while (attachedEntities != null && attachedEntities.Count > 0 ) @@ -189,6 +202,8 @@ public override void Dispose() } Object.Destroy(material); + + dclTextureFetcher.Dispose(); base.Dispose(); } } diff --git a/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Materials/DCL.Components.Materials.asmdef b/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Materials/DCL.Components.Materials.asmdef index 7e4625511f..d291138382 100644 --- a/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Materials/DCL.Components.Materials.asmdef +++ b/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Materials/DCL.Components.Materials.asmdef @@ -11,9 +11,9 @@ "GUID:f070f44586498a44c9e6d2ea3e166379", "GUID:760a1d365aad58044916992b072cf2a6", "GUID:08b2edf8f14cdd3408988fd7746b749c", - "GUID:f62ee1cb37ad7324797360ac42f7a4d9", "GUID:28f74c468a54948bfa9f625c5d428f56", - "GUID:0b3e983b6c2fed54ebecf9d146c251ba" + "GUID:0b3e983b6c2fed54ebecf9d146c251ba", + "GUID:f51ebe6a0ceec4240a699833d6309b23" ], "includePlatforms": [], "excludePlatforms": [], diff --git a/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Materials/PBRMaterial.cs b/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Materials/PBRMaterial.cs index 590d90ae61..12aeac32bd 100644 --- a/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Materials/PBRMaterial.cs +++ b/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Materials/PBRMaterial.cs @@ -1,3 +1,4 @@ +using Cysharp.Threading.Tasks; using DCL.Helpers; using DCL.Models; using System.Collections; @@ -48,18 +49,29 @@ enum TransparencyMode AUTO } - public Material material { get; set; } + private enum TextureType + { + Albedo, + Alpha, + Emissive, + Bump + } + + public Material material { get; private set; } private string currentMaterialResourcesFilename; const string MATERIAL_RESOURCES_PATH = "Materials/"; const string PBR_MATERIAL_NAME = "ShapeMaterial"; - DCLTexture albedoDCLTexture = null; - DCLTexture alphaDCLTexture = null; - DCLTexture emissiveDCLTexture = null; - DCLTexture bumpDCLTexture = null; + private readonly DCLTexture[] textures = new DCLTexture[4]; - private List textureFetchCoroutines = new List(); + private readonly DCLTexture.Fetcher[] dclTextureFetcher = new DCLTexture.Fetcher[] + { + new DCLTexture.Fetcher(), + new DCLTexture.Fetcher(), + new DCLTexture.Fetcher(), + new DCLTexture.Fetcher() + }; public PBRMaterial() { @@ -110,24 +122,16 @@ public override IEnumerator ApplyChanges(BaseModel newModel) // FETCH AND LOAD EMISSIVE TEXTURE - var fetchEmission = FetchTexture(ShaderUtils.EmissionMap, model.emissiveTexture, emissiveDCLTexture); + var fetchEmission = FetchTexture(ShaderUtils.EmissionMap, model.emissiveTexture, (int)TextureType.Emissive); SetupTransparencyMode(); // FETCH AND LOAD TEXTURES - var fetchBaseMap = FetchTexture(ShaderUtils.BaseMap, model.albedoTexture, albedoDCLTexture); - var fetchAlpha = FetchTexture(ShaderUtils.AlphaTexture, model.alphaTexture, alphaDCLTexture); - var fetchBump = FetchTexture(ShaderUtils.BumpMap, model.bumpTexture, bumpDCLTexture); + var fetchBaseMap = FetchTexture(ShaderUtils.BaseMap, model.albedoTexture, (int)TextureType.Albedo); + var fetchAlpha = FetchTexture(ShaderUtils.AlphaTexture, model.alphaTexture, (int)TextureType.Alpha); + var fetchBump = FetchTexture(ShaderUtils.BumpMap, model.bumpTexture, (int)TextureType.Bump); - textureFetchCoroutines.Add(CoroutineStarter.Start(fetchEmission)); - textureFetchCoroutines.Add(CoroutineStarter.Start(fetchBaseMap)); - textureFetchCoroutines.Add(CoroutineStarter.Start(fetchAlpha)); - textureFetchCoroutines.Add(CoroutineStarter.Start(fetchBump)); - - yield return fetchBaseMap; - yield return fetchAlpha; - yield return fetchBump; - yield return fetchEmission; + yield return UniTask.WhenAll(fetchEmission, fetchBaseMap, fetchAlpha, fetchBump).ToCoroutine(); foreach (IDCLEntity entity in attachedEntities) InitMaterial(entity); @@ -272,50 +276,63 @@ private void OnMaterialDetached(IDCLEntity entity) DataStore.i.sceneWorldObjects.RemoveMaterial(scene.sceneData.sceneNumber, entity.entityId, material); } - IEnumerator FetchTexture(int materialPropertyId, string textureComponentId, DCLTexture cachedDCLTexture) + private UniTask FetchTexture(int materialPropertyId, string textureComponentId, int textureType) { if (!string.IsNullOrEmpty(textureComponentId)) { - if (!AreSameTextureComponent(cachedDCLTexture, textureComponentId)) + if (!AreSameTextureComponent(textureType, textureComponentId)) { - yield return DCLTexture.FetchTextureComponent(scene, textureComponentId, - (fetchedDCLTexture) => - { - if (material == null) - return; - - material.SetTexture(materialPropertyId, fetchedDCLTexture.texture); - SwitchTextureComponent(cachedDCLTexture, fetchedDCLTexture); - }); + return dclTextureFetcher[textureType] + .Fetch(scene, textureComponentId, + fetchedDCLTexture => + { + if (material == null) + return false; + + material.SetTexture(materialPropertyId, fetchedDCLTexture.texture); + SwitchTextureComponent(textureType, fetchedDCLTexture); + return true; + }); } } else { material.SetTexture(materialPropertyId, null); - cachedDCLTexture?.DetachFrom(this); + textures[textureType]?.DetachFrom(this); + textures[textureType] = null; } + + return new UniTask(); } - bool AreSameTextureComponent(DCLTexture dclTexture, string textureId) + bool AreSameTextureComponent(int textureType, string textureId) { + DCLTexture dclTexture = textures[textureType]; if (dclTexture == null) return false; return dclTexture.id == textureId; } - void SwitchTextureComponent(DCLTexture cachedTexture, DCLTexture newTexture) + void SwitchTextureComponent(int textureType, DCLTexture newTexture) { - cachedTexture?.DetachFrom(this); - cachedTexture = newTexture; - cachedTexture.AttachTo(this); + DCLTexture dclTexture = textures[textureType]; + dclTexture?.DetachFrom(this); + textures[textureType] = newTexture; + newTexture.AttachTo(this); } public override void Dispose() { - albedoDCLTexture?.DetachFrom(this); - alphaDCLTexture?.DetachFrom(this); - emissiveDCLTexture?.DetachFrom(this); - bumpDCLTexture?.DetachFrom(this); + for (int i = 0; i < dclTextureFetcher.Length; i++) + { + dclTextureFetcher[i].Dispose(); + } + + for (int i = 0; i < textures.Length; i++) + { + textures[i]?.DetachFrom(this); + textures[i] = null; + } if (material != null) { @@ -328,19 +345,6 @@ public override void Dispose() Utils.SafeDestroy(material); } - for (int i = 0; i < textureFetchCoroutines.Count; i++) - { - var coroutine = textureFetchCoroutines[i]; - - if ( coroutine != null ) - CoroutineStarter.Stop(coroutine); - } - - albedoDCLTexture = null; - alphaDCLTexture = null; - emissiveDCLTexture = null; - bumpDCLTexture = null; - base.Dispose(); } } diff --git a/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Textures/DCL.Components.Texture.asmdef b/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Textures/DCL.Components.Texture.asmdef index cfc16b8ad0..23bc888ed1 100644 --- a/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Textures/DCL.Components.Texture.asmdef +++ b/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Textures/DCL.Components.Texture.asmdef @@ -20,7 +20,8 @@ "GUID:a4bf61c057a098f4ca05b539a6d8c0fe", "GUID:0b3e983b6c2fed54ebecf9d146c251ba", "GUID:8f18b4ef38b9d4637b8190231eb24e38", - "GUID:2995626b54c60644988f134a69a77450" + "GUID:2995626b54c60644988f134a69a77450", + "GUID:f51ebe6a0ceec4240a699833d6309b23" ], "includePlatforms": [], "excludePlatforms": [], diff --git a/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Textures/DCLTexture.cs b/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Textures/DCLTexture.cs index c70a9b21c5..531341fd4e 100644 --- a/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Textures/DCLTexture.cs +++ b/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Textures/DCLTexture.cs @@ -1,18 +1,21 @@ +using Cysharp.Threading.Tasks; using DCL.Components; using DCL.Controllers; +using DCL.Helpers; using DCL.Models; using System; using System.Collections; -using DCL.Helpers; -using UnityEngine; using System.Collections.Generic; using System.Linq; +using System.Threading; +using UnityEngine; +using Object = UnityEngine.Object; namespace DCL { public class DCLTexture : BaseDisposable { - [System.Serializable] + [Serializable] public class Model : BaseModel { public string src; @@ -20,7 +23,10 @@ public class Model : BaseModel public FilterMode samplingMode = FilterMode.Bilinear; public bool hasAlpha = false; - public override BaseModel GetDataFromJSON(string json) { return Utils.SafeFromJson(json); } + public override BaseModel GetDataFromJSON(string json) + { + return Utils.SafeFromJson(json); + } } public enum BabylonWrapMode @@ -32,46 +38,26 @@ public enum BabylonWrapMode AssetPromise_Texture texturePromise = null; - private Dictionary> attachedEntitiesByComponent = + protected Dictionary> attachedEntitiesByComponent = new Dictionary>(); public TextureWrapMode unityWrap; public FilterMode unitySamplingMode; public Texture2D texture; - - protected bool isDisposed; - public float resizingFactor => texturePromise?.asset.resizingFactor ?? 1; - public override int GetClassId() { return (int) CLASS_ID.TEXTURE; } + protected bool isDisposed; + protected bool textureDisposed; - public DCLTexture() { model = new Model(); } + public float resizingFactor => texturePromise?.asset.resizingFactor ?? 1; - public static IEnumerator FetchFromComponent(IParcelScene scene, string componentId, - System.Action OnFinish) + public override int GetClassId() { - yield return FetchTextureComponent(scene, componentId, (dclTexture) => { OnFinish?.Invoke(dclTexture.texture); }); + return (int)CLASS_ID.TEXTURE; } - public static IEnumerator FetchTextureComponent(IParcelScene scene, string componentId, - System.Action OnFinish) + public DCLTexture() { - if (!scene.componentsManagerLegacy.HasSceneSharedComponent(componentId)) - { - Debug.Log($"couldn't fetch texture, the DCLTexture component with id {componentId} doesn't exist"); - yield break; - } - - DCLTexture textureComponent = scene.componentsManagerLegacy.GetSceneSharedComponent(componentId) as DCLTexture; - - if (textureComponent == null) - { - Debug.Log($"couldn't fetch texture, the shared component with id {componentId} is NOT a DCLTexture"); - yield break; - } - - yield return new WaitUntil(() => textureComponent.texture != null); - - OnFinish.Invoke(textureComponent); + model = new Model(); } public override IEnumerator ApplyChanges(BaseModel newModel) @@ -83,7 +69,7 @@ public override IEnumerator ApplyChanges(BaseModel newModel) if (isDisposed) yield break; - Model model = (Model) newModel; + Model model = (Model)newModel; unitySamplingMode = model.samplingMode; @@ -123,10 +109,10 @@ public override IEnumerator ApplyChanges(BaseModel newModel) { texture.wrapMode = unityWrap; texture.filterMode = unitySamplingMode; - + if (DataStore.i.textureConfig.runCompression.Get()) texture.Compress(false); - + texture.Apply(unitySamplingMode != FilterMode.Point, true); texture = TextureHelpers.ClampSize(texture, DataStore.i.textureConfig.generalMaxSize.Get()); } @@ -141,6 +127,8 @@ public override IEnumerator ApplyChanges(BaseModel newModel) else scene.contentProvider.TryGetContentsUrl(model.src, out contentsUrl); + var prevPromise = texturePromise; + if (!string.IsNullOrEmpty(contentsUrl)) { if (texturePromise != null) @@ -153,6 +141,8 @@ public override IEnumerator ApplyChanges(BaseModel newModel) AssetPromiseKeeper_Texture.i.Keep(texturePromise); yield return texturePromise; } + + AssetPromiseKeeper_Texture.i.Forget(prevPromise); } } } @@ -164,7 +154,13 @@ public virtual void AttachTo(ISharedComponent component) public virtual void DetachFrom(ISharedComponent component) { - RemoveReference(component); + if (RemoveReference(component)) + { + if (attachedEntitiesByComponent.Count == 0) + { + DisposeTexture(); + } + } } public void AddReference(ISharedComponent component) @@ -181,24 +177,24 @@ public void AddReference(ISharedComponent component) } } - public void RemoveReference(ISharedComponent component) + public bool RemoveReference(ISharedComponent component) { if (!attachedEntitiesByComponent.ContainsKey(component)) - return; + return false; foreach (var entityId in attachedEntitiesByComponent[component]) { DataStore.i.sceneWorldObjects.RemoveTexture(scene.sceneData.sceneNumber, entityId, texture); } - attachedEntitiesByComponent.Remove(component); + return attachedEntitiesByComponent.Remove(component); } public override void Dispose() { if (isDisposed) return; - + isDisposed = true; while (attachedEntitiesByComponent.Count > 0) @@ -206,13 +202,98 @@ public override void Dispose() RemoveReference(attachedEntitiesByComponent.First().Key); } + DisposeTexture(); + + base.Dispose(); + } + + protected virtual void DisposeTexture() + { + textureDisposed = true; + if (texturePromise != null) { AssetPromiseKeeper_Texture.i.Forget(texturePromise); texturePromise = null; } + else if (texture) + { + Object.Destroy(texture); + } + } - base.Dispose(); + public class Fetcher : IDisposable + { + private CancellationTokenSource cancellationTokenSource; + + public async UniTask Fetch(IParcelScene scene, string componentId, + Func attachCallback) + { + Cancel(); + + cancellationTokenSource = new CancellationTokenSource(); + CancellationToken ct = cancellationTokenSource.Token; + + if (!scene.componentsManagerLegacy.HasSceneSharedComponent(componentId)) + { + Debug.Log($"couldn't fetch texture, the DCLTexture component with id {componentId} doesn't exist"); + return; + } + + DCLTexture textureComponent = scene.componentsManagerLegacy.GetSceneSharedComponent(componentId) as DCLTexture; + + if (textureComponent == null) + { + Debug.Log($"couldn't fetch texture, the shared component with id {componentId} is NOT a DCLTexture"); + return; + } + + bool textureWasReLoaded = false; + + // If texture was previously disposed we load it again + if (textureComponent.textureDisposed) + { + textureComponent.textureDisposed = false; + + try + { + await textureComponent.ApplyChanges(textureComponent.model).WithCancellation(ct); + ct.ThrowIfCancellationRequested(); + textureWasReLoaded = true; + } + catch (OperationCanceledException _) + { + textureComponent.DisposeTexture(); + } + } + + await UniTask.WaitUntil(() => textureComponent.texture, PlayerLoopTiming.Update, ct); + + // We dispose texture was re-loaded but attachment + // was unsuccessful + if (!attachCallback(textureComponent) && textureWasReLoaded) + { + textureComponent.DisposeTexture(); + } + + cancellationTokenSource.Dispose(); + cancellationTokenSource = null; + } + + public void Cancel() + { + if (cancellationTokenSource != null) + { + cancellationTokenSource.Cancel(); + cancellationTokenSource.Dispose(); + cancellationTokenSource = null; + } + } + + public void Dispose() + { + Cancel(); + } } } -} \ No newline at end of file +} diff --git a/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Textures/Tests/DCL.Components.Texture.Tests.asmdef b/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Textures/Tests/DCL.Components.Texture.Tests.asmdef index aca3cd189e..8f71374f90 100644 --- a/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Textures/Tests/DCL.Components.Texture.Tests.asmdef +++ b/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Textures/Tests/DCL.Components.Texture.Tests.asmdef @@ -1,30 +1,36 @@ { - "name": "DCL.Components.Texture.Tests", - "rootNamespace": "", - "references": [ - "GUID:b1087c5731ff68448a0a9c625bb7e52d", - "GUID:ef84d7ce90ba34f07be26ace428e9bc8", - "GUID:27619889b8ba8c24980f49ee34dbb44a", - "GUID:0acc523941302664db1f4e527237feb3", - "GUID:a881b57670b1d2747a6d7f9e32b63230", - "GUID:b308ae946900fa04080222d7daf47d9a", - "GUID:97d8897529779cb49bebd400c7f402a6", - "GUID:4720e174f2805c74bb7aa94cc8bb5bf8", - "GUID:3ac909a9e663fbd4fbcffe071a761ac6", - "GUID:9a16a75a9de4ba2458b50ed374f1b28f", - "GUID:44a9db8f655ab05409802e5476cf9dd3" - ], - "includePlatforms": [], - "excludePlatforms": [], - "allowUnsafeCode": false, - "overrideReferences": true, - "precompiledReferences": [ - "nunit.framework.dll" - ], - "autoReferenced": false, - "defineConstraints": [ - "UNITY_INCLUDE_TESTS" - ], - "versionDefines": [], - "noEngineReferences": false + "name": "DCL.Components.Texture.Tests", + "rootNamespace": "", + "references": [ + "GUID:b1087c5731ff68448a0a9c625bb7e52d", + "GUID:ef84d7ce90ba34f07be26ace428e9bc8", + "GUID:27619889b8ba8c24980f49ee34dbb44a", + "GUID:0acc523941302664db1f4e527237feb3", + "GUID:a881b57670b1d2747a6d7f9e32b63230", + "GUID:b308ae946900fa04080222d7daf47d9a", + "GUID:97d8897529779cb49bebd400c7f402a6", + "GUID:4720e174f2805c74bb7aa94cc8bb5bf8", + "GUID:3ac909a9e663fbd4fbcffe071a761ac6", + "GUID:9a16a75a9de4ba2458b50ed374f1b28f", + "GUID:44a9db8f655ab05409802e5476cf9dd3", + "GUID:3b61bfe638c557d44815e0e09f59786d", + "GUID:a78c5d8dad39efe45ab1b21901f8db0f", + "GUID:1dd0780aa6be12b428c8005d0bee46b8", + "GUID:08b2edf8f14cdd3408988fd7746b749c", + "GUID:6e9be8e4a71701b48b60c8881e27a0e3", + "GUID:e8e8b57e56da94749a3e4bbb6c38524c" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "nunit.framework.dll" + ], + "autoReferenced": false, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS" + ], + "versionDefines": [], + "noEngineReferences": false } \ No newline at end of file diff --git a/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Textures/Tests/TexturesTests.cs b/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Textures/Tests/TexturesTests.cs index 59eb6fd106..625d3dd1f4 100644 --- a/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Textures/Tests/TexturesTests.cs +++ b/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Textures/Tests/TexturesTests.cs @@ -1,9 +1,11 @@ using DCL; +using DCL.Components; using DCL.Helpers; using DCL.Models; using NUnit.Framework; using System.Collections; using DCL.Controllers; +using DCL.Shaders; using UnityEngine; using UnityEngine.TestTools; @@ -13,16 +15,22 @@ public class TexturesTests : IntegrationTestSuite_Legacy { private ParcelScene scene; private CoreComponentsPlugin coreComponentsPlugin; + private UIComponentsPlugin uiComponentsPlugin; + + const string BASE64_TEXTURE = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAApgAAAKYB3X3/OAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAANCSURBVEiJtZZPbBtFFMZ/M7ubXdtdb1xSFyeilBapySVU8h8OoFaooFSqiihIVIpQBKci6KEg9Q6H9kovIHoCIVQJJCKE1ENFjnAgcaSGC6rEnxBwA04Tx43t2FnvDAfjkNibxgHxnWb2e/u992bee7tCa00YFsffekFY+nUzFtjW0LrvjRXrCDIAaPLlW0nHL0SsZtVoaF98mLrx3pdhOqLtYPHChahZcYYO7KvPFxvRl5XPp1sN3adWiD1ZAqD6XYK1b/dvE5IWryTt2udLFedwc1+9kLp+vbbpoDh+6TklxBeAi9TL0taeWpdmZzQDry0AcO+jQ12RyohqqoYoo8RDwJrU+qXkjWtfi8Xxt58BdQuwQs9qC/afLwCw8tnQbqYAPsgxE1S6F3EAIXux2oQFKm0ihMsOF71dHYx+f3NND68ghCu1YIoePPQN1pGRABkJ6Bus96CutRZMydTl+TvuiRW1m3n0eDl0vRPcEysqdXn+jsQPsrHMquGeXEaY4Yk4wxWcY5V/9scqOMOVUFthatyTy8QyqwZ+kDURKoMWxNKr2EeqVKcTNOajqKoBgOE28U4tdQl5p5bwCw7BWquaZSzAPlwjlithJtp3pTImSqQRrb2Z8PHGigD4RZuNX6JYj6wj7O4TFLbCO/Mn/m8R+h6rYSUb3ekokRY6f/YukArN979jcW+V/S8g0eT/N3VN3kTqWbQ428m9/8k0P/1aIhF36PccEl6EhOcAUCrXKZXXWS3XKd2vc/TRBG9O5ELC17MmWubD2nKhUKZa26Ba2+D3P+4/MNCFwg59oWVeYhkzgN/JDR8deKBoD7Y+ljEjGZ0sosXVTvbc6RHirr2reNy1OXd6pJsQ+gqjk8VWFYmHrwBzW/n+uMPFiRwHB2I7ih8ciHFxIkd/3Omk5tCDV1t+2nNu5sxxpDFNx+huNhVT3/zMDz8usXC3ddaHBj1GHj/As08fwTS7Kt1HBTmyN29vdwAw+/wbwLVOJ3uAD1wi/dUH7Qei66PfyuRj4Ik9is+hglfbkbfR3cnZm7chlUWLdwmprtCohX4HUtlOcQjLYCu+fzGJH2QRKvP3UNz8bWk1qMxjGTOMThZ3kvgLI5AzFfo379UAAAAASUVORK5CYII="; + protected override IEnumerator SetUp() { yield return base.SetUp(); coreComponentsPlugin = new CoreComponentsPlugin(); + uiComponentsPlugin = new UIComponentsPlugin(); scene = TestUtils.CreateTestScene(); } protected override IEnumerator TearDown() { coreComponentsPlugin.Dispose(); + uiComponentsPlugin.Dispose(); yield return base.TearDown(); } @@ -88,5 +96,305 @@ public IEnumerator Texture_OnReadyAfterLoadingInstantlyCalled() dclTexture.CallWhenReady((x) => { isOnReady = true; }); Assert.IsTrue(isOnReady); } + + [UnityTest] + public IEnumerator Texture_Disposed_BasicMaterialUpdated() + { + IDCLEntity entity = TestUtils.CreateSceneEntity(scene); + DCLTexture texture = TestUtils.CreateDCLTexture(scene, BASE64_TEXTURE); + + BasicMaterial basicMaterial = TestUtils.SharedComponentCreate( + scene, + DCL.Models.CLASS_ID.BASIC_MATERIAL, + new BasicMaterial.Model() + { + texture = texture.id + }); + + TestUtils.SharedComponentAttach(basicMaterial, entity); + TestUtils.SharedComponentAttach(texture, entity); + + yield return basicMaterial.routine; + + Texture mainTex = basicMaterial.material.GetTexture(ShaderUtils.BaseMap); + + // texture should be created + Assert.IsTrue(mainTex); + + TestUtils.SharedComponentUpdate(basicMaterial, new BasicMaterial.Model() + { + texture = TestUtils.CreateDCLTexture(scene, BASE64_TEXTURE).id + }); + + yield return basicMaterial.routine; + yield return null; + + // texture should have being disposed + Assert.IsFalse(mainTex); + } + + [UnityTest] + public IEnumerator Texture_Disposed_PBRMaterialUpdated() + { + IDCLEntity entity = TestUtils.CreateSceneEntity(scene); + DCLTexture dclTextureA = TestUtils.CreateDCLTexture(scene, BASE64_TEXTURE); + DCLTexture dclTextureB = TestUtils.CreateDCLTexture(scene, BASE64_TEXTURE); + DCLTexture dclTextureC = TestUtils.CreateDCLTexture(scene, BASE64_TEXTURE); + DCLTexture dclTextureD = TestUtils.CreateDCLTexture(scene, BASE64_TEXTURE); + + PBRMaterial pbrMaterial = TestUtils.SharedComponentCreate( + scene, + DCL.Models.CLASS_ID.PBR_MATERIAL, + new PBRMaterial.Model() + { + albedoTexture = dclTextureA.id, + alphaTexture = dclTextureB.id, + bumpTexture = dclTextureC.id, + emissiveTexture = dclTextureD.id + }); + + TestUtils.SharedComponentAttach(pbrMaterial, entity); + TestUtils.SharedComponentAttach(dclTextureA, entity); + TestUtils.SharedComponentAttach(dclTextureB, entity); + TestUtils.SharedComponentAttach(dclTextureC, entity); + TestUtils.SharedComponentAttach(dclTextureD, entity); + + yield return pbrMaterial.routine; + + Texture textureA = pbrMaterial.material.GetTexture(ShaderUtils.EmissionMap); + Texture textureB = pbrMaterial.material.GetTexture(ShaderUtils.BaseMap); + Texture textureC = pbrMaterial.material.GetTexture(ShaderUtils.AlphaTexture); + Texture textureD = pbrMaterial.material.GetTexture(ShaderUtils.BumpMap); + + // textures should be created + Assert.IsTrue(textureA); + Assert.IsTrue(textureB); + Assert.IsTrue(textureC); + Assert.IsTrue(textureD); + + TestUtils.SharedComponentUpdate(pbrMaterial, new PBRMaterial.Model() + { + albedoTexture = TestUtils.CreateDCLTexture(scene, BASE64_TEXTURE).id, + alphaTexture = TestUtils.CreateDCLTexture(scene, BASE64_TEXTURE).id, + bumpTexture = TestUtils.CreateDCLTexture(scene, BASE64_TEXTURE).id, + emissiveTexture = TestUtils.CreateDCLTexture(scene, BASE64_TEXTURE).id + }); + + yield return pbrMaterial.routine; + yield return null; + + // texture should have being disposed + Assert.IsFalse(textureA); + Assert.IsFalse(textureB); + Assert.IsFalse(textureC); + Assert.IsFalse(textureD); + } + + [UnityTest] + public IEnumerator Texture_Disposed_UIImageUpdated() + { + // UIScreenSpace needed for UIImage + UIScreenSpace screenSpaceShape = + TestUtils.SharedComponentCreate(scene, + CLASS_ID.UI_SCREEN_SPACE_SHAPE); + yield return screenSpaceShape.routine; + + UIImage uiImage = TestUtils.SharedComponentCreate( + scene, + DCL.Models.CLASS_ID.UI_IMAGE_SHAPE, + new UIImage.Model() + { + source = TestUtils.CreateDCLTexture(scene, BASE64_TEXTURE).id + }); + + // we cant `yield return uiImage.routine` since UIImage does not yield + // so we just try to guess the number of frames to skip + yield return null; + yield return null; + + Texture imageTexture = uiImage.referencesContainer.image.texture; + + // texture should be created + Assert.IsTrue(imageTexture); + + TestUtils.SharedComponentUpdate(uiImage, new UIImage.Model() + { + source = TestUtils.CreateDCLTexture(scene, BASE64_TEXTURE).id + }); + + // we cant `yield return uiImage.routine` since UIImage does not yield + // so we just try to guess the number of frames to skip + yield return null; + yield return null; + + // texture should have being disposed + Assert.IsFalse(imageTexture); + } + + [UnityTest] + public IEnumerator Texture_Disposed_BasicMaterialRemoved() + { + IDCLEntity entity = TestUtils.CreateSceneEntity(scene); + DCLTexture texture = TestUtils.CreateDCLTexture(scene, BASE64_TEXTURE); + + BasicMaterial basicMaterial = TestUtils.SharedComponentCreate( + scene, + DCL.Models.CLASS_ID.BASIC_MATERIAL, + new BasicMaterial.Model() + { + texture = texture.id + }); + + TestUtils.SharedComponentAttach(basicMaterial, entity); + TestUtils.SharedComponentAttach(texture, entity); + + yield return basicMaterial.routine; + + Texture mainTex = basicMaterial.material.GetTexture(ShaderUtils.BaseMap); + + // texture should be created + Assert.IsTrue(mainTex); + + TestUtils.SharedComponentDispose(basicMaterial); + + // frame skip + yield return null; + + // texture should have being disposed + Assert.IsFalse(mainTex); + } + + [UnityTest] + public IEnumerator Texture_Disposed_PBRMaterialRemoved() + { + IDCLEntity entity = TestUtils.CreateSceneEntity(scene); + DCLTexture dclTextureA = TestUtils.CreateDCLTexture(scene, BASE64_TEXTURE); + DCLTexture dclTextureB = TestUtils.CreateDCLTexture(scene, BASE64_TEXTURE); + DCLTexture dclTextureC = TestUtils.CreateDCLTexture(scene, BASE64_TEXTURE); + DCLTexture dclTextureD = TestUtils.CreateDCLTexture(scene, BASE64_TEXTURE); + + PBRMaterial pbrMaterial = TestUtils.SharedComponentCreate( + scene, + DCL.Models.CLASS_ID.PBR_MATERIAL, + new PBRMaterial.Model() + { + albedoTexture = dclTextureA.id, + alphaTexture = dclTextureB.id, + bumpTexture = dclTextureC.id, + emissiveTexture = dclTextureD.id + }); + + TestUtils.SharedComponentAttach(pbrMaterial, entity); + TestUtils.SharedComponentAttach(dclTextureA, entity); + TestUtils.SharedComponentAttach(dclTextureB, entity); + TestUtils.SharedComponentAttach(dclTextureC, entity); + TestUtils.SharedComponentAttach(dclTextureD, entity); + + yield return pbrMaterial.routine; + + Texture textureA = pbrMaterial.material.GetTexture(ShaderUtils.EmissionMap); + Texture textureB = pbrMaterial.material.GetTexture(ShaderUtils.BaseMap); + Texture textureC = pbrMaterial.material.GetTexture(ShaderUtils.AlphaTexture); + Texture textureD = pbrMaterial.material.GetTexture(ShaderUtils.BumpMap); + + // textures should be created + Assert.IsTrue(textureA); + Assert.IsTrue(textureB); + Assert.IsTrue(textureC); + Assert.IsTrue(textureD); + + TestUtils.SharedComponentDispose(pbrMaterial); + + // frame skip + yield return null; + + // texture should have being disposed + Assert.IsFalse(textureA); + Assert.IsFalse(textureB); + Assert.IsFalse(textureC); + Assert.IsFalse(textureD); + } + + [UnityTest] + public IEnumerator Texture_Disposed_UIImageRemoved() + { + // UIScreenSpace needed for UIImage + UIScreenSpace screenSpaceShape = + TestUtils.SharedComponentCreate(scene, + CLASS_ID.UI_SCREEN_SPACE_SHAPE); + yield return screenSpaceShape.routine; + + UIImage uiImage = TestUtils.SharedComponentCreate( + scene, + DCL.Models.CLASS_ID.UI_IMAGE_SHAPE, + new UIImage.Model() + { + source = TestUtils.CreateDCLTexture(scene, BASE64_TEXTURE).id + }); + + // we cant `yield return uiImage.routine` since UIImage does not yield + // so we just try to guess the number of frames to skip + yield return null; + yield return null; + + Texture imageTexture = uiImage.referencesContainer.image.texture; + + // texture should be created + Assert.IsTrue(imageTexture); + + TestUtils.SharedComponentDispose(uiImage); + + // frame skip + yield return null; + + // texture should have being disposed + Assert.IsFalse(imageTexture); + } + + [UnityTest] + public IEnumerator Texture_ReloadsCorrectly() + { + IDCLEntity entity = TestUtils.CreateSceneEntity(scene); + DCLTexture texture = TestUtils.CreateDCLTexture(scene, BASE64_TEXTURE); + + BasicMaterial basicMaterial = TestUtils.SharedComponentCreate( + scene, + DCL.Models.CLASS_ID.BASIC_MATERIAL, + new BasicMaterial.Model() + { + texture = texture.id + }); + + TestUtils.SharedComponentAttach(basicMaterial, entity); + TestUtils.SharedComponentAttach(texture, entity); + + yield return basicMaterial.routine; + + Texture mainTex = basicMaterial.material.GetTexture(ShaderUtils.BaseMap); + + // texture should be created + Assert.IsTrue(mainTex); + + TestUtils.SharedComponentUpdate(basicMaterial, new BasicMaterial.Model() + { + texture = TestUtils.CreateDCLTexture(scene, BASE64_TEXTURE).id + }); + + yield return basicMaterial.routine; + yield return null; + + // texture should have being disposed + Assert.IsFalse(mainTex); + + TestUtils.SharedComponentUpdate(basicMaterial, new BasicMaterial.Model() + { + texture = texture.id + }); + + yield return basicMaterial.routine; + + // texture should have being reloaded + Assert.IsTrue(basicMaterial.material.GetTexture(ShaderUtils.BaseMap)); + } } -} \ No newline at end of file +} diff --git a/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/UI/DCL.Components.UI.asmdef b/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/UI/DCL.Components.UI.asmdef index 9ebf9aa68d..50c7ec566a 100644 --- a/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/UI/DCL.Components.UI.asmdef +++ b/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/UI/DCL.Components.UI.asmdef @@ -22,7 +22,8 @@ "GUID:fbcc413e192ef9048811d47ab0aca0c0", "GUID:9857fe4bae4e30441bc3a577c5de790a", "GUID:3b80b0b562b1cbc489513f09fc1b8f69", - "GUID:0b167d3fa6c88454ea3eb69cbabe4eb7" + "GUID:0b167d3fa6c88454ea3eb69cbabe4eb7", + "GUID:f51ebe6a0ceec4240a699833d6309b23" ], "includePlatforms": [], "excludePlatforms": [], diff --git a/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/UI/UIImage/UIImage.cs b/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/UI/UIImage/UIImage.cs index 5bf56ca876..dc49aac994 100644 --- a/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/UI/UIImage/UIImage.cs +++ b/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/UI/UIImage/UIImage.cs @@ -1,3 +1,4 @@ +using Cysharp.Threading.Tasks; using DCL.Controllers; using DCL.Helpers; using DCL.Models; @@ -30,6 +31,8 @@ public class UIImage : UIShape public override string referencesContainerPrefabName => "UIImage"; DCLTexture dclTexture = null; + private readonly DCLTexture.Fetcher dclTextureFetcher = new DCLTexture.Fetcher(); + private bool isDisposed; public UIImage() { @@ -42,8 +45,6 @@ public UIImage() public override void DetachFrom(IDCLEntity entity, System.Type overridenAttachedType = null) { } - Coroutine fetchRoutine; - public override IEnumerator ApplyChanges(BaseModel newModel) { // Fetch texture @@ -51,24 +52,22 @@ public override IEnumerator ApplyChanges(BaseModel newModel) { if (dclTexture == null || (dclTexture != null && dclTexture.id != model.source)) { - if (fetchRoutine != null) - { - CoroutineStarter.Stop(fetchRoutine); - fetchRoutine = null; - } - - IEnumerator fetchIEnum = DCLTexture.FetchTextureComponent(scene, model.source, (downloadedTexture) => - { - referencesContainer.image.texture = downloadedTexture.texture; - fetchRoutine = null; - dclTexture?.DetachFrom(this); - dclTexture = downloadedTexture; - dclTexture.AttachTo(this); - - ConfigureImage(); - }); - - fetchRoutine = CoroutineStarter.Start(fetchIEnum); + dclTextureFetcher.Fetch(scene, model.source, + fetchedDCLTexture => + { + if (isDisposed) + return false; + + referencesContainer.image.texture = fetchedDCLTexture.texture; + dclTexture?.DetachFrom(this); + dclTexture = fetchedDCLTexture; + dclTexture.AttachTo(this); + + ConfigureImage(); + return true; + }) + .Forget(); + return null; } } @@ -127,11 +126,9 @@ private void ConfigureUVRect(RectTransform parentRecTransform, float resizingFac public override void Dispose() { - if (fetchRoutine != null) - { - CoroutineStarter.Stop(fetchRoutine); - fetchRoutine = null; - } + isDisposed = true; + + dclTextureFetcher.Dispose(); dclTexture?.DetachFrom(this); diff --git a/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Video/DCLVideoTexture.cs b/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Video/DCLVideoTexture.cs index a109998cc6..307e0332ef 100644 --- a/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Video/DCLVideoTexture.cs +++ b/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Video/DCLVideoTexture.cs @@ -345,8 +345,13 @@ public override void DetachFrom(ISharedComponent component) component.OnDetach -= SetPlayStateDirty; DCLVideoTextureUtils.UnsubscribeToEntityShapeUpdate(component, SetPlayStateDirty); - RemoveReference(component); SetPlayStateDirty(); + + if (RemoveReference(component)) + { + if (attachedEntitiesByComponent.Count == 0) + DisposeTexture(); + } } private void SetPlayStateDirty(IDCLEntity entity = null) => @@ -358,9 +363,17 @@ private void OnAudioSettingsChanged(AudioSettings settings) => public override void Dispose() { DataStore.i.virtualAudioMixer.sceneSFXVolume.OnChange -= OnVirtualAudioMixerChangedValue; - Settings.i.audioSettings.OnChanged -= OnAudioSettingsChanged; + base.Dispose(); + } + + protected override void DisposeTexture() + { + isInitialized = false; + textureDisposed = true; + CommonScriptableObjects.playerCoords.OnChange -= OnPlayerCoordsChanged; CommonScriptableObjects.sceneNumber.OnChange -= OnSceneNumberChanged; + Settings.i.audioSettings.OnChanged -= OnAudioSettingsChanged; if (scene != null) scene.OnEntityRemoved -= SetPlayStateDirty; @@ -378,7 +391,6 @@ public override void Dispose() } Utils.SafeDestroy(texture); - base.Dispose(); } } } diff --git a/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Video/Tests/DCL.Components.Video.Tests.asmdef b/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Video/Tests/DCL.Components.Video.Tests.asmdef index 2baf30fa8c..5879e054e5 100644 --- a/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Video/Tests/DCL.Components.Video.Tests.asmdef +++ b/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Video/Tests/DCL.Components.Video.Tests.asmdef @@ -26,7 +26,8 @@ "GUID:3b61bfe638c557d44815e0e09f59786d", "GUID:69fd9bb431ff24c448dfb96a4dd9365f", "GUID:9a16a75a9de4ba2458b50ed374f1b28f", - "GUID:44a9db8f655ab05409802e5476cf9dd3" + "GUID:44a9db8f655ab05409802e5476cf9dd3", + "GUID:08b2edf8f14cdd3408988fd7746b749c" ], "includePlatforms": [], "excludePlatforms": [], diff --git a/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Video/Tests/VideoTextureShould.cs b/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Video/Tests/VideoTextureShould.cs index 98e8075610..beaea6c7ad 100644 --- a/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Video/Tests/VideoTextureShould.cs +++ b/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Video/Tests/VideoTextureShould.cs @@ -10,6 +10,7 @@ using DCL.Controllers; using DCL.Interface; using DCL.SettingsCommon; +using DCL.Shaders; using Assert = UnityEngine.Assertions.Assert; using AudioSettings = DCL.SettingsCommon.AudioSettings; @@ -29,10 +30,9 @@ protected override IEnumerator SetUp() coreComponentsPlugin = new CoreComponentsPlugin(); scene = TestUtils.CreateTestScene() as ParcelScene; CommonScriptableObjects.sceneNumber.Set(scene.sceneData.sceneNumber); - - IVideoPluginWrapper pluginWrapper = new VideoPluginWrapper_Mock(); + originalVideoPluginBuilder = DCLVideoTexture.videoPluginWrapperBuilder; - DCLVideoTexture.videoPluginWrapperBuilder = () => pluginWrapper; + DCLVideoTexture.videoPluginWrapperBuilder = () => new VideoPluginWrapper_Mock(); } protected override IEnumerator TearDown() @@ -331,7 +331,7 @@ public IEnumerator UpdateTexturePlayerVolumeWhenAudioSettingsChange() var component = CreateDCLVideoTextureWithCustomTextureModel(scene, model); yield return component.routine; - + Assert.AreApproximatelyEqual(1f, component.texturePlayer.volume, 0.01f); AudioSettings settings = Settings.i.audioSettings.Data; @@ -448,6 +448,51 @@ public IEnumerator VolumeIsUnmutedWhenUserEntersScene() Assert.AreEqual(videoTexture.GetVolume(), videoTexture.texturePlayer.volume); } + [UnityTest] + public IEnumerator VideoTextureIsDisposedAndReloadedCorrectly() + { + IDCLEntity entity = TestUtils.CreateSceneEntity(scene); + DCLVideoTexture texture = CreateDCLVideoTexture(scene, "it-wont-load-during-test"); + + BasicMaterial basicMaterial = TestUtils.SharedComponentCreate( + scene, + DCL.Models.CLASS_ID.BASIC_MATERIAL, + new BasicMaterial.Model() + { + texture = texture.id + }); + + TestUtils.SharedComponentAttach(basicMaterial, entity); + TestUtils.SharedComponentAttach(texture, entity); + + yield return basicMaterial.routine; + + Texture mainTex = basicMaterial.material.GetTexture(ShaderUtils.BaseMap); + + // texture should be created + Assert.IsTrue(mainTex); + + TestUtils.SharedComponentUpdate(basicMaterial, new BasicMaterial.Model() + { + texture = CreateDCLVideoTexture(scene, "it-wont-load-during-test2").id + }); + + yield return basicMaterial.routine; + + // texture should have being disposed + Assert.IsFalse(mainTex); + + TestUtils.SharedComponentUpdate(basicMaterial, new BasicMaterial.Model() + { + texture = texture.id + }); + + yield return basicMaterial.routine; + + // texture should have being reloaded + Assert.IsTrue(basicMaterial.material.GetTexture(ShaderUtils.BaseMap)); + } + static DCLVideoClip CreateDCLVideoClip(ParcelScene scn, string url) { return TestUtils.SharedComponentCreate @@ -487,4 +532,4 @@ static DCLVideoTexture CreateDCLVideoTextureWithModel(ParcelScene scn, DCLVideoT static DCLVideoTexture CreateDCLVideoTexture(ParcelScene scn, string url) { return CreateDCLVideoTexture(scn, CreateDCLVideoClip(scn, "http://" + url)); } static DCLVideoTexture CreateDCLVideoTextureWithCustomTextureModel(ParcelScene scn, DCLVideoTexture.Model model) { return CreateDCLVideoTextureWithModel(scn, model); } } -} \ No newline at end of file +}