< Summary

Class:DCL.DCLTexture
Assembly:DCL.Components.Texture
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Textures/DCLTexture.cs
Covered lines:103
Uncovered lines:20
Coverable lines:123
Total lines:303
Line coverage:83.7% (103 of 123)
Covered branches:0
Total branches:0
Covered methods:14
Total methods:16
Method coverage:87.5% (14 of 16)

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
Model()0%110100%
GetDataFromJSON(...)0%110100%
GetDataFromPb(...)0%30500%
DCLTexture()0%110100%
GetClassId()0%2100%
ApplyChanges()0%21.5120084.44%
AttachTo(...)0%110100%
DetachFrom(...)0%330100%
AddReference(...)0%330100%
RemoveReference(...)0%330100%
Dispose()0%330100%
DisposeTexture()0%440100%
Fetch()0%10.810080%
Cancel()0%220100%
Dispose()0%110100%

File(s)

/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Textures/DCLTexture.cs

#LineLine coverage
 1using Cysharp.Threading.Tasks;
 2using DCL.Components;
 3using DCL.Controllers;
 4using DCL.Helpers;
 5using DCL.Models;
 6using System;
 7using System.Collections;
 8using System.Collections.Generic;
 9using System.Linq;
 10using Decentraland.Sdk.Ecs6;
 11using System.Threading;
 12using UnityEngine;
 13using Object = UnityEngine.Object;
 14
 15namespace DCL
 16{
 17    public class DCLTexture : BaseDisposable
 18    {
 19        [Serializable]
 20        public class Model : BaseModel
 21        {
 22            public string src;
 23            public BabylonWrapMode wrap = BabylonWrapMode.CLAMP;
 25424            public FilterMode samplingMode = FilterMode.Bilinear;
 25
 26            public override BaseModel GetDataFromJSON(string json) =>
 7927                Utils.SafeFromJson<Model>(json);
 28
 29            public override BaseModel GetDataFromPb(ComponentBodyPayload pbModel)
 30            {
 031                if (pbModel.PayloadCase != ComponentBodyPayload.PayloadOneofCase.Texture)
 032                    return Utils.SafeUnimplemented<DCLTexture, Model>(expected: ComponentBodyPayload.PayloadOneofCase.Te
 33
 034                var pb = new Model();
 035                if (pbModel.Texture.HasSrc) pb.src = pbModel.Texture.Src;
 036                if (pbModel.Texture.HasWrap) pb.wrap = (BabylonWrapMode)pbModel.Texture.Wrap;
 037                if (pbModel.Texture.HasSamplingMode) pb.samplingMode = (FilterMode)pbModel.Texture.SamplingMode;
 38
 039                return pb;
 40            }
 41        }
 42
 43        public enum BabylonWrapMode
 44        {
 45            CLAMP,
 46            WRAP,
 47            MIRROR
 48        }
 49
 50        private AssetPromise_Texture texturePromise;
 51        private bool wasLoadedFromPromise;
 52
 9653        protected readonly Dictionary<ISharedComponent, HashSet<long>> attachedEntitiesByComponent =
 54            new Dictionary<ISharedComponent, HashSet<long>>();
 55
 56        public TextureWrapMode unityWrap;
 57        public FilterMode unitySamplingMode;
 58        public Texture2D texture;
 59
 60        protected bool isDisposed;
 61        protected bool textureDisposed;
 62
 4063        public float resizingFactor => texturePromise?.asset.resizingFactor ?? 1;
 64
 65        public override int GetClassId() =>
 066            (int)CLASS_ID.TEXTURE;
 67
 9668        public DCLTexture()
 69        {
 9670            model = new Model();
 9671        }
 72
 73        public override IEnumerator ApplyChanges(BaseModel newModel)
 74        {
 16075            yield return new WaitUntil(() => CommonScriptableObjects.rendererState.Get());
 76
 77            //If the scene creates and destroy the component before our renderer has been turned on bad things happen!
 78            //TODO: Analyze if we can catch this upstream and stop the IEnumerator
 8079            if (isDisposed)
 080                yield break;
 81
 8082            Model model = (Model)newModel;
 83
 8084            unitySamplingMode = model.samplingMode;
 85
 8086            switch (model.wrap)
 87            {
 88                case BabylonWrapMode.CLAMP:
 7989                    unityWrap = TextureWrapMode.Clamp;
 7990                    break;
 91                case BabylonWrapMode.WRAP:
 092                    unityWrap = TextureWrapMode.Repeat;
 093                    break;
 94                case BabylonWrapMode.MIRROR:
 195                    unityWrap = TextureWrapMode.Mirror;
 96                    break;
 97            }
 98
 8099            if (texture == null && !string.IsNullOrEmpty(model.src))
 100            {
 78101                bool isBase64 = model.src.Contains("image/png;base64");
 102
 78103                if (isBase64)
 104                {
 21105                    string base64Data = model.src.Substring(model.src.IndexOf(',') + 1);
 106
 21107                    wasLoadedFromPromise = false;
 108
 109                    // The used texture variable can't be null for the ImageConversion.LoadImage to work
 42110                    if (texture == null) { texture = new Texture2D(1, 1); }
 111
 21112                    if (!ImageConversion.LoadImage(texture, Convert.FromBase64String(base64Data))) { Debug.LogError($"DC
 113
 21114                    if (texture != null)
 115                    {
 21116                        texture.wrapMode = unityWrap;
 21117                        texture.filterMode = unitySamplingMode;
 118
 21119                        if (DataStore.i.textureConfig.runCompression.Get())
 0120                            texture.Compress(false);
 121
 21122                        texture.Apply(unitySamplingMode != FilterMode.Point, true);
 21123                        texture = TextureHelpers.ClampSize(texture, DataStore.i.textureConfig.generalMaxSize.Get());
 124                    }
 125                }
 126                else
 127                {
 57128                    string contentsUrl = string.Empty;
 57129                    bool isExternalURL = model.src.Contains("http://") || model.src.Contains("https://");
 130
 57131                    if (isExternalURL)
 0132                        contentsUrl = model.src;
 133                    else
 57134                        scene.contentProvider.TryGetContentsUrl(model.src, out contentsUrl);
 135
 57136                    var prevPromise = texturePromise;
 137
 57138                    if (!string.IsNullOrEmpty(contentsUrl))
 139                    {
 57140                        if (texturePromise != null)
 0141                            AssetPromiseKeeper_Texture.i.Forget(texturePromise);
 142
 57143                        wasLoadedFromPromise = true;
 57144                        texturePromise = new AssetPromise_Texture(contentsUrl, unityWrap, unitySamplingMode, storeDefaul
 113145                        texturePromise.OnSuccessEvent += (x) => texture = x.texture;
 57146                        texturePromise.OnFailEvent += (x, error) => { texture = null; };
 147
 57148                        AssetPromiseKeeper_Texture.i.Keep(texturePromise);
 57149                        yield return texturePromise;
 150                    }
 151
 57152                    AssetPromiseKeeper_Texture.i.Forget(prevPromise);
 57153                }
 154            }
 80155        }
 156
 157        public virtual void AttachTo(ISharedComponent component)
 158        {
 86159            AddReference(component);
 86160        }
 161
 162        public virtual void DetachFrom(ISharedComponent component)
 163        {
 62164            if (RemoveReference(component))
 165            {
 37166                if (attachedEntitiesByComponent.Count == 0)
 167                {
 26168                    DisposeTexture();
 169                }
 170            }
 62171        }
 172
 173        public void AddReference(ISharedComponent component)
 174        {
 99175            if (attachedEntitiesByComponent.ContainsKey(component))
 1176                return;
 177
 98178            attachedEntitiesByComponent.Add(component, new HashSet<long>());
 179
 362180            foreach (var entity in component.GetAttachedEntities())
 181            {
 83182                attachedEntitiesByComponent[component].Add(entity.entityId);
 83183                DataStore.i.sceneWorldObjects.AddTexture(scene.sceneData.sceneNumber, entity.entityId, texture);
 184            }
 98185        }
 186
 187        public bool RemoveReference(ISharedComponent component)
 188        {
 78189            if (!attachedEntitiesByComponent.ContainsKey(component))
 25190                return false;
 191
 259192            foreach (var entityId in attachedEntitiesByComponent[component]) { DataStore.i.sceneWorldObjects.RemoveTextu
 193
 53194            return attachedEntitiesByComponent.Remove(component);
 195        }
 196
 197        public override void Dispose()
 198        {
 52199            if (isDisposed)
 24200                return;
 201
 28202            isDisposed = true;
 203
 54204            while (attachedEntitiesByComponent.Count > 0) { RemoveReference(attachedEntitiesByComponent.First().Key); }
 205
 28206            DisposeTexture();
 207
 28208            base.Dispose();
 28209        }
 210
 211        protected virtual void DisposeTexture()
 212        {
 53213            textureDisposed = true;
 214
 53215            if (texturePromise != null)
 216            {
 28217                AssetPromiseKeeper_Texture.i.Forget(texturePromise);
 28218                texturePromise = null;
 219            }
 220            // only destroy textures created by this script
 221            // textures created by the asset promise should be cleaned up from the asset library
 25222            else if (!wasLoadedFromPromise)
 223            {
 14224                if (texture)
 14225                    Object.Destroy(texture);
 226            }
 25227        }
 228
 229        public class Fetcher : IDisposable
 230        {
 231            private CancellationTokenSource cancellationTokenSource;
 232
 233            public async UniTask Fetch(IParcelScene scene, string componentId,
 234                Func<DCLTexture, bool> attachCallback)
 235            {
 102236                Cancel();
 237
 102238                cancellationTokenSource = new CancellationTokenSource();
 102239                CancellationToken ct = cancellationTokenSource.Token;
 240
 102241                if (!scene.componentsManagerLegacy.HasSceneSharedComponent(componentId))
 242                {
 1243                    Debug.Log($"couldn't fetch texture, the DCLTexture component with id {componentId} doesn't exist");
 1244                    return;
 245                }
 246
 101247                DCLTexture textureComponent = scene.componentsManagerLegacy.GetSceneSharedComponent(componentId) as DCLT
 248
 101249                if (textureComponent == null)
 250                {
 0251                    Debug.Log($"couldn't fetch texture, the shared component with id {componentId} is NOT a DCLTexture")
 0252                    return;
 253                }
 254
 101255                bool textureWasReLoaded = false;
 256
 257                // If texture was previously disposed we load it again
 101258                if (textureComponent.textureDisposed)
 259                {
 2260                    textureComponent.textureDisposed = false;
 261
 262                    try
 263                    {
 4264                        await textureComponent.ApplyChanges(textureComponent.model).WithCancellation(ct);
 2265                        ct.ThrowIfCancellationRequested();
 2266                        textureWasReLoaded = true;
 2267                    }
 0268                    catch (OperationCanceledException _)
 269                    {
 0270                        textureComponent.DisposeTexture();
 0271                    }
 272                }
 273
 494274                await UniTask.WaitUntil(() => textureComponent.texture, PlayerLoopTiming.Update, ct);
 275
 276                // We dispose texture was re-loaded but attachment
 277                // was unsuccessful
 99278                if (!attachCallback(textureComponent) && textureWasReLoaded)
 279                {
 0280                    textureComponent.DisposeTexture();
 281                }
 282
 99283                cancellationTokenSource.Dispose();
 99284                cancellationTokenSource = null;
 100285            }
 286
 287            public void Cancel()
 288            {
 189289                if (cancellationTokenSource != null)
 290                {
 3291                    cancellationTokenSource.Cancel();
 3292                    cancellationTokenSource.Dispose();
 3293                    cancellationTokenSource = null;
 294                }
 189295            }
 296
 297            public void Dispose()
 298            {
 87299                Cancel();
 87300            }
 301        }
 302    }
 303}