< Summary

Class:DCL.Helpers.LazyTextureObserver
Assembly:LazyTextureObserver
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Helpers/LazyTextureObserver/LazyTextureObserver.cs
Covered lines:75
Uncovered lines:3
Coverable lines:78
Total lines:231
Line coverage:96.1% (75 of 78)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
LazyTextureObserver(...)0%110100%
LazyTextureObserver()0%110100%
AddListener(...)0%550100%
RemoveListener(...)0%330100%
RefreshWithUri(...)0%330100%
RefreshWithTexture(...)0%440100%
TryToLoadAndDispatch()0%220100%
DispatchTextureByInjectedReference()0%330100%
DispatchLoadedTexture()0%220100%
Dispose()0%110100%
CreateWithUri(...)0%2100%
CreateWithTexture(...)0%110100%

File(s)

/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Helpers/LazyTextureObserver/LazyTextureObserver.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using UnityEngine;
 4using UnityEngine.Assertions;
 5
 6namespace DCL.Helpers
 7{
 8    public interface ILazyTextureObserver
 9    {
 10        /// <summary>
 11        /// Adds a listener for this LazyTextureObserver.
 12        ///
 13        /// The listener will be called when anyone reloads the texture by calling any of the refresh methods.
 14        /// Also, calling this method will ensure the texture starts loading lazily if it was not loaded yet.
 15        /// </summary>
 16        /// <param name="listener"></param>
 17        public void AddListener(Action<Texture2D> listener);
 18
 19        /// <summary>
 20        /// Remove the given listener. If no listeners want this texture anymore, it will be unloaded.
 21        ///
 22        /// Note that as this method uses the AssetPromiseKeeper_Texture service,
 23        /// if any other systems use the same texture it will not be unloaded.
 24        /// </summary>
 25        /// <param name="listener"></param>
 26        public void RemoveListener(Action<Texture2D> listener);
 27
 28        /// <summary>
 29        /// Set the texture to be loaded from the given URI when any listener is subscribed.
 30        /// If we already have listeners, the loading will start immediately.
 31        ///
 32        /// The uri method has precedence over the texture method.
 33        /// </summary>
 34        /// <param name="uri"></param>
 35        void RefreshWithUri(string uri);
 36
 37        /// <summary>
 38        /// Sets the listened texture as the passed texture object.
 39        ///
 40        /// If we already have listeners, they will be called immediately with the new texture.
 41        /// New listeners will receive the new texture.
 42        ///
 43        /// The uri method has precedence over the texture method.
 44        /// </summary>
 45        /// <param name="texture"></param>
 46        void RefreshWithTexture(Texture2D texture);
 47    }
 48
 49    /// <summary>
 50    /// The LazyTextureObserver class is a reactive texture loader wrapper.
 51    /// <br/> <br/>
 52    /// This class is useful for cases where a texture needs to be used, but it has to be lazy loaded,
 53    /// and can be changed by an external party.
 54    /// <br/> <br/>
 55    /// <ul>
 56    /// <li>Calling <b>RefreshWithUri</b> and <b>RefreshWithTexture</b> will only load the texture if someone is registe
 57    /// <li>If <b>AddListener</b> is called and the texture is not loaded, it will start being loaded.</li><br/>
 58    /// <li>If <b>RemoveListener</b> is called and no more listeners need this image, the image will be unloaded.</li><b
 59    /// </ul>
 60    /// All existing listeners will be keep informed of <b>RefreshWithX</b> calls to update the texture if it changes.
 61    /// </summary>
 62    public class LazyTextureObserver : ILazyTextureObserver
 63    {
 64        enum State
 65        {
 66            NONE,
 67            IN_PROGRESS,
 68            LOADED
 69        }
 70
 71        private State state = State.NONE;
 72        private Action<Texture2D> OnLoaded;
 73        private Action<Exception> OnFail;
 74
 143675        private HashSet<Action<Texture2D>> subscriptions = new HashSet<Action<Texture2D>>();
 76
 77        private Texture2D texture;
 78        private string uri;
 79        private ITextureLoader textureLoader;
 80
 285281        public LazyTextureObserver () : this(new TextureLoader()) { }
 82
 143683        internal LazyTextureObserver (ITextureLoader textureLoader)
 84        {
 143685            this.textureLoader = textureLoader;
 86
 143687            textureLoader.OnSuccess += x =>
 88            {
 789                state = State.LOADED;
 790                OnLoaded?.Invoke(x);
 791            };
 92
 143693            textureLoader.OnFail += error =>
 94            {
 195                state = State.NONE;
 196                uri = null;
 197                OnFail?.Invoke(error);
 198            };
 143699        }
 100
 101        public void AddListener(Action<Texture2D> listener)
 102        {
 244103            Assert.IsNotNull(listener, "Listener can't be null!");
 104
 243105            if (!subscriptions.Contains(listener))
 106            {
 233107                subscriptions.Add(listener);
 233108                this.OnLoaded += listener;
 234109                this.OnFail += e => listener.Invoke(null);
 110            }
 111
 112            // First, check if we did set a texture directly and return it if so.
 243113            if ( texture != null )
 114            {
 4115                listener.Invoke(texture);
 4116                return;
 117            }
 118
 119            // If not, we try to load the texture if not already loaded.
 239120            if ( state == State.NONE )
 121            {
 231122                TryToLoadAndDispatch();
 231123                return;
 124            }
 125
 126            // From now on we assume that the promise exists.
 127            // --
 128            // It must be in progress or available by now. If its available we just return it for this listener only.
 129            // If not, the texture promise flow will call all the listeners when it finishes.
 8130            if ( state == State.LOADED )
 131            {
 4132                listener.Invoke(textureLoader.GetTexture());
 133            }
 8134        }
 135
 136        public void RemoveListener(Action<Texture2D> listener)
 137        {
 953138            Assert.IsNotNull(listener, "Listener can't be null!");
 139
 140            // Not using assert here because this case is more probable, I want to fail silently in this case.
 952141            if (!subscriptions.Contains(listener))
 739142                return;
 143
 213144            this.OnLoaded -= listener;
 213145            subscriptions.Remove(listener);
 146
 213147            if ( subscriptions.Count == 0 )
 148            {
 207149                textureLoader.Unload();
 207150                state = State.NONE;
 151            }
 213152        }
 153
 154        public void RefreshWithUri(string uri)
 155        {
 17156            if (string.IsNullOrEmpty(uri))
 3157                return;
 158
 14159            this.uri = uri;
 14160            this.texture = null;
 161
 14162            if ( subscriptions.Count > 0 )
 4163                TryToLoadAndDispatch();
 14164        }
 165
 166        public void RefreshWithTexture(Texture2D texture)
 167        {
 8168            if (texture == null || texture == this.texture)
 1169                return;
 170
 7171            textureLoader.Unload();
 7172            state = State.NONE;
 173
 7174            this.uri = null;
 7175            this.texture = texture;
 176
 7177            if ( subscriptions.Count > 0 )
 2178                TryToLoadAndDispatch();
 7179        }
 180
 181        private void TryToLoadAndDispatch()
 182        {
 237183            if (DispatchTextureByInjectedReference())
 2184                return;
 185
 235186            DispatchLoadedTexture();
 235187        }
 188
 189        private bool DispatchTextureByInjectedReference()
 190        {
 237191            if (texture == null)
 235192                return false;
 193
 2194            state = State.LOADED;
 2195            OnLoaded?.Invoke(texture);
 2196            return true;
 197        }
 198
 199        private bool DispatchLoadedTexture()
 200        {
 235201            if (string.IsNullOrEmpty(uri))
 225202                return false;
 203
 10204            state = State.IN_PROGRESS;
 10205            textureLoader.Load(uri);
 10206            return true;
 207        }
 208
 209        public void Dispose()
 210        {
 12211            state = State.NONE;
 12212            textureLoader.Unload();
 12213            subscriptions.Clear();
 12214            OnLoaded = null;
 12215        }
 216
 217        public static LazyTextureObserver CreateWithUri(string uri)
 218        {
 0219            var result = new LazyTextureObserver();
 0220            result.RefreshWithUri(uri);
 0221            return result;
 222        }
 223
 224        public static LazyTextureObserver CreateWithTexture(Texture2D texture)
 225        {
 2226            var result = new LazyTextureObserver();
 2227            result.RefreshWithTexture(texture);
 2228            return result;
 229        }
 230    }
 231}