< Summary

Class:DCL.AssetPromiseKeeper
Assembly:AssetPromiseKeeper
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Controllers/AssetManager/Common/AssetPromiseKeeper.cs
Covered lines:1
Uncovered lines:0
Coverable lines:1
Total lines:362
Line coverage:100% (1 of 1)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
AssetPromiseKeeper()0%110100%

File(s)

/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Controllers/AssetManager/Common/AssetPromiseKeeper.cs

#LineLine coverage
 1using System.Collections;
 2using System.Collections.Generic;
 3using UnityEngine;
 4
 5namespace DCL
 6{
 7    public class AssetPromiseKeeper
 8    {
 19        public static float PROCESS_PROMISES_TIME_BUDGET = 0.006f;
 10    }
 11
 12    /// <summary>
 13    /// The AssetPromiseKeeper is the user entry point interface.
 14    /// It manages stuff like requesting something that's already being loaded, etc.
 15    ///
 16    /// It also handles the special case of a promise that depends on another
 17    /// to be completed (blocked promise)
 18    /// </summary>
 19    /// <typeparam name="AssetType">Asset type to be handled</typeparam>
 20    /// <typeparam name="AssetLibraryType">Asset library type. It must handle the same type as AssetType.</typeparam>
 21    /// <typeparam name="AssetPromiseType">Asset promise type. It must handle the same type as AssetType.</typeparam>
 22    public class AssetPromiseKeeper<AssetType, AssetLibraryType, AssetPromiseType>
 23        where AssetType : Asset, new()
 24        where AssetLibraryType : AssetLibrary<AssetType>, new()
 25        where AssetPromiseType : AssetPromise<AssetType>
 26    {
 27        public AssetLibraryType library;
 28
 29        //NOTE(Brian): All waiting promises. Only used for cleanup and to keep count.
 30        HashSet<AssetPromiseType> waitingPromises = new HashSet<AssetPromiseType>();
 31        public int waitingPromisesCount => waitingPromises.Count;
 32
 33        //NOTE(Brian): List of promises waiting for assets not in library.
 34        protected Dictionary<object, AssetPromiseType> masterPromiseById = new Dictionary<object, AssetPromiseType>(100)
 35
 36        //NOTE(Brian): List of promises waiting for assets that are currently being loaded by another promise.
 37        HashSet<AssetPromiseType> blockedPromises = new HashSet<AssetPromiseType>();
 38
 39        //NOTE(Brian): Master promise id -> blocked promises HashSet
 40        Dictionary<object, HashSet<AssetPromiseType>> masterToBlockedPromises = new Dictionary<object, HashSet<AssetProm
 41
 42        public bool useTimeBudget => CommonScriptableObjects.rendererState.Get();
 43
 44        float startTime;
 45
 46        public bool IsBlocked(AssetPromiseType promise) { return blockedPromises.Contains(promise); }
 47
 48        public string GetMasterState(AssetPromiseType promise)
 49        {
 50            object promiseId = promise.GetId();
 51
 52            if (!masterToBlockedPromises.ContainsKey(promiseId))
 53                return "Master not found";
 54
 55            if (!masterToBlockedPromises[promiseId].Contains(promise))
 56                return "Promise is not blocked???";
 57
 58            if (!masterPromiseById.ContainsKey(promiseId))
 59                return "not registered as master?";
 60
 61            return $"master state = {masterPromiseById[promiseId].state}";
 62        }
 63
 64        public AssetPromiseKeeper(AssetLibraryType library)
 65        {
 66            this.library = library;
 67            CoroutineStarter.Start(ProcessBlockedPromisesQueue());
 68        }
 69
 70        public AssetPromiseType Keep(AssetPromiseType promise)
 71        {
 72            if (promise == null || promise.state != AssetPromiseState.IDLE_AND_EMPTY || waitingPromises.Contains(promise
 73                return promise;
 74
 75            object id = promise.GetId();
 76
 77            if (id == null)
 78            {
 79                Debug.LogError("ERROR: ID == null. Promise is not set up correctly.");
 80                return promise;
 81            }
 82
 83            promise.isDirty = true;
 84
 85            //NOTE(Brian): We already have a master promise for this id, add to blocked list.
 86            if (masterPromiseById.ContainsKey(id))
 87            {
 88                waitingPromises.Add(promise);
 89
 90                if (!masterToBlockedPromises.ContainsKey(id))
 91                    masterToBlockedPromises.Add(id, new HashSet<AssetPromiseType>());
 92
 93                masterToBlockedPromises[id].Add(promise);
 94
 95                blockedPromises.Add(promise);
 96                promise.SetWaitingState();
 97                return promise;
 98            }
 99
 100            // NOTE(Brian): Not in library, add to corresponding lists...
 101            if (!library.Contains(id))
 102            {
 103                waitingPromises.Add(promise);
 104                masterPromiseById.Add(id, promise);
 105            }
 106
 107            promise.library = library;
 108            promise.OnPreFinishEvent += OnRequestCompleted;
 109            promise.Load();
 110
 111            return promise;
 112        }
 113
 114        public AssetPromiseType Forget(AssetPromiseType promise)
 115        {
 116            if (promise == null)
 117                return null;
 118
 119            if (promise.state == AssetPromiseState.IDLE_AND_EMPTY || promise.state == AssetPromiseState.WAITING)
 120            {
 121                CleanPromise(promise);
 122                promise.OnForget();
 123                return promise;
 124            }
 125
 126            object id = promise.GetId();
 127
 128            bool isMasterPromise = masterPromiseById.ContainsKey(id) && masterPromiseById[id] == promise;
 129            bool hasBlockedPromises = masterToBlockedPromises.ContainsKey(id) && masterToBlockedPromises[id].Count > 0;
 130
 131            if (isMasterPromise && hasBlockedPromises)
 132            {
 133                //NOTE(Brian): Pending promises are waiting for this one.
 134                //             We clear the events because we shouldn't call them, as this promise is forgotten.
 135                OnSilentForget(promise);
 136                promise.OnForget();
 137                return promise;
 138            }
 139
 140            promise.Unload();
 141            CleanPromise(promise);
 142            promise.OnForget();
 143
 144            return promise;
 145        }
 146
 147        Queue<AssetPromiseType> toResolveBlockedPromisesQueue = new Queue<AssetPromiseType>();
 148
 149        private void OnRequestCompleted(AssetPromise<AssetType> loadedPromise)
 150        {
 151            object id = loadedPromise.GetId();
 152
 153            if (!masterToBlockedPromises.ContainsKey(id) || !masterPromiseById.ContainsKey(id))
 154            {
 155                CleanPromise(loadedPromise);
 156                return;
 157            }
 158
 159            toResolveBlockedPromisesQueue.Enqueue(loadedPromise as AssetPromiseType);
 160        }
 161
 162        IEnumerator ProcessBlockedPromisesQueue()
 163        {
 164            startTime = Time.unscaledTime;
 165
 166            while (true)
 167            {
 168                if (toResolveBlockedPromisesQueue.Count <= 0)
 169                {
 170                    yield return null;
 171                    continue;
 172                }
 173
 174                AssetPromiseType promise = toResolveBlockedPromisesQueue.Dequeue();
 175                yield return ProcessBlockedPromisesDeferred(promise);
 176                CleanPromise(promise);
 177
 178                if (promise.isForgotten)
 179                {
 180                    promise.Unload();
 181                }
 182
 183                var enumerator = SkipFrameIfOverBudget();
 184
 185                if (enumerator != null)
 186                    yield return enumerator;
 187            }
 188        }
 189
 190        private IEnumerator ProcessBlockedPromisesDeferred(AssetPromiseType loadedPromise)
 191        {
 192            object loadedPromiseId = loadedPromise.GetId();
 193
 194            if (!masterToBlockedPromises.ContainsKey(loadedPromiseId))
 195                yield break;
 196
 197            if (!masterPromiseById.ContainsKey(loadedPromiseId))
 198                yield break;
 199
 200            if (masterPromiseById[loadedPromiseId] != loadedPromise)
 201            {
 202                Debug.LogWarning($"Unexpected issue: masterPromiseById promise isn't the same as loaded promise? id: {lo
 203                yield break;
 204            }
 205
 206
 207            //NOTE(Brian): We have to keep checking to support the case in which
 208            //             new promises are enqueued while this promise ID is being
 209            //             resolved.
 210            while (masterToBlockedPromises.ContainsKey(loadedPromiseId) &&
 211                   masterToBlockedPromises[loadedPromiseId].Count > 0)
 212            {
 213                List<AssetPromiseType> promisesToLoadForId = GetBlockedPromisesToLoadForId(loadedPromiseId);
 214
 215                var enumerator = SkipFrameIfOverBudget();
 216
 217                if (enumerator != null)
 218                    yield return enumerator;
 219
 220                if (loadedPromise.state != AssetPromiseState.FINISHED)
 221                    yield return ForceFailPromiseList(promisesToLoadForId);
 222                else
 223                    yield return LoadPromisesList(promisesToLoadForId);
 224
 225                enumerator = SkipFrameIfOverBudget();
 226
 227                if (enumerator != null)
 228                    yield return enumerator;
 229            }
 230
 231            if (masterToBlockedPromises.ContainsKey(loadedPromiseId))
 232                masterToBlockedPromises.Remove(loadedPromiseId);
 233        }
 234
 235        private IEnumerator SkipFrameIfOverBudget()
 236        {
 237            if (useTimeBudget && Time.realtimeSinceStartup - startTime >= AssetPromiseKeeper.PROCESS_PROMISES_TIME_BUDGE
 238            {
 239                yield return null;
 240                startTime = Time.unscaledTime;
 241            }
 242        }
 243
 244        private List<AssetPromiseType> GetBlockedPromisesToLoadForId(object masterPromiseId)
 245        {
 246            var blockedPromisesToLoadAux = new List<AssetPromiseType>();
 247
 248            using (var iterator = masterToBlockedPromises[masterPromiseId].GetEnumerator())
 249            {
 250                while (iterator.MoveNext())
 251                {
 252                    var blockedPromise = iterator.Current;
 253
 254                    blockedPromises.Remove(blockedPromise);
 255
 256                    if (blockedPromise != null && !blockedPromise.isForgotten)
 257                    {
 258                        blockedPromisesToLoadAux.Add(blockedPromise);
 259                    }
 260                }
 261            }
 262
 263            return blockedPromisesToLoadAux;
 264        }
 265
 266        private IEnumerator ForceFailPromiseList(List<AssetPromiseType> promises)
 267        {
 268            int promisesCount = promises.Count;
 269
 270            for (int i = 0; i < promisesCount; i++)
 271            {
 272                var promise = promises[i];
 273                if (promise.isForgotten)
 274                    continue;
 275
 276                promise.ForceFail();
 277                Forget(promise);
 278                CleanPromise(promise);
 279
 280                IEnumerator enumerator = SkipFrameIfOverBudget();
 281
 282                if (enumerator != null)
 283                    yield return enumerator;
 284            }
 285        }
 286
 287        private IEnumerator LoadPromisesList(List<AssetPromiseType> promises)
 288        {
 289            int promisesCount = promises.Count;
 290
 291            for (int i = 0; i < promisesCount; i++)
 292            {
 293                AssetPromiseType promise = promises[i];
 294                if (promise.isForgotten)
 295                    continue;
 296
 297                promise.library = library;
 298                CleanPromise(promise);
 299                promise.Load();
 300
 301                var enumerator = SkipFrameIfOverBudget();
 302
 303                if (enumerator != null)
 304                    yield return enumerator;
 305            }
 306        }
 307
 308        void CleanPromise(AssetPromise<AssetType> promise)
 309        {
 310            if (!promise.isDirty)
 311                return;
 312
 313            promise.isDirty = false;
 314
 315            AssetPromiseType finalPromise = promise as AssetPromiseType;
 316
 317            object id = promise.GetId();
 318
 319            if (masterToBlockedPromises.ContainsKey(id))
 320            {
 321                if (masterToBlockedPromises[id].Contains(finalPromise))
 322                {
 323                    masterToBlockedPromises[id].Remove(finalPromise);
 324                }
 325            }
 326
 327            if (masterPromiseById.ContainsKey(id) && masterPromiseById[id] == promise)
 328            {
 329                masterPromiseById.Remove(id);
 330            }
 331
 332            if (blockedPromises.Contains(finalPromise))
 333                blockedPromises.Remove(finalPromise);
 334
 335            if (waitingPromises.Contains(finalPromise))
 336                waitingPromises.Remove(finalPromise);
 337        }
 338
 339        public void Cleanup()
 340        {
 341            blockedPromises = new HashSet<AssetPromiseType>();
 342            masterToBlockedPromises = new Dictionary<object, HashSet<AssetPromiseType>>();
 343
 344            using (var e = waitingPromises.GetEnumerator())
 345            {
 346                while (e.MoveNext())
 347                    e.Current?.Cleanup();
 348            }
 349
 350            foreach (var kvp in masterPromiseById)
 351            {
 352                kvp.Value.Cleanup();
 353            }
 354
 355            masterPromiseById = new Dictionary<object, AssetPromiseType>();
 356            waitingPromises = new HashSet<AssetPromiseType>();
 357            library.Cleanup();
 358        }
 359
 360        protected virtual void OnSilentForget(AssetPromiseType promise) { }
 361    }
 362}

Methods/Properties

AssetPromiseKeeper()