< Summary

Class:DCL.AssetPromiseKeeper[AssetType,AssetLibraryType,AssetPromiseType]
Assembly:AssetPromiseKeeper
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Controllers/AssetManager/Common/AssetPromiseKeeper.cs
Covered lines:149
Uncovered lines:16
Coverable lines:165
Total lines:361
Line coverage:90.3% (149 of 165)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
AssetPromiseKeeper(...)0%110100%
IsBlocked(...)0%2100%
GetMasterState(...)0%20400%
Keep(...)0%8.058090.91%
Forget(...)0%770100%
OnRequestCompleted(...)0%330100%
ProcessBlockedPromisesQueue()0%880100%
ProcessBlockedPromisesDeferred()0%16.1815082.61%
SkipFrameIfOverBudget()0%550100%
GetBlockedPromisesToLoadForId(...)0%440100%
ForceFailPromiseList()0%660100%
LoadPromisesList()0%660100%
CleanPromise(...)0%880100%
Cleanup()0%440100%
OnSilentForget(...)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    {
 9        public static float PROCESS_PROMISES_TIME_BUDGET = 0.006f;
 10    }
 11
 12    /// <summary>
 13    /// The AssetPromiseKeeper is the user entry point interface of the asset management system.
 14    /// It manages stuff like requesting something that's already being loaded, etc.
 15    ///
 16    /// It also handles the special cases of promises that depend on another to be completed (blocked promise)
 17    /// </summary>
 18    /// <typeparam name="AssetType">Asset type to be handled</typeparam>
 19    /// <typeparam name="AssetLibraryType">Asset library type. It must handle the same type as AssetType.</typeparam>
 20    /// <typeparam name="AssetPromiseType">Asset promise type. It must handle the same type as AssetType.</typeparam>
 21    public class AssetPromiseKeeper<AssetType, AssetLibraryType, AssetPromiseType>
 22        where AssetType : Asset, new()
 23        where AssetLibraryType : AssetLibrary<AssetType>, new()
 24        where AssetPromiseType : AssetPromise<AssetType>
 25    {
 26        public AssetLibraryType library;
 27
 28        //NOTE(Brian): All waiting promises. Only used for cleanup and to keep count.
 8229        HashSet<AssetPromiseType> waitingPromises = new HashSet<AssetPromiseType>();
 030        public int waitingPromisesCount => waitingPromises.Count;
 31
 32        //NOTE(Brian): List of promises waiting for assets not in library.
 8233        protected Dictionary<object, AssetPromiseType> masterPromiseById = new Dictionary<object, AssetPromiseType>(100)
 34
 35        //NOTE(Brian): List of promises waiting for assets that are currently being loaded by another promise.
 8236        HashSet<AssetPromiseType> blockedPromises = new HashSet<AssetPromiseType>();
 37
 38        //NOTE(Brian): Master promise id -> blocked promises HashSet
 8239        protected Dictionary<object, HashSet<AssetPromiseType>> masterToBlockedPromises = new Dictionary<object, HashSet
 40
 15941        public bool useTimeBudget => CommonScriptableObjects.rendererState.Get();
 42
 43        float startTime;
 44
 045        public bool IsBlocked(AssetPromiseType promise) { return blockedPromises.Contains(promise); }
 46
 47        public string GetMasterState(AssetPromiseType promise)
 48        {
 049            object promiseId = promise.GetId();
 50
 051            if (!masterToBlockedPromises.ContainsKey(promiseId))
 052                return "Master not found";
 53
 054            if (!masterToBlockedPromises[promiseId].Contains(promise))
 055                return "Promise is not blocked???";
 56
 057            if (!masterPromiseById.ContainsKey(promiseId))
 058                return "not registered as master?";
 59
 060            return $"master state = {masterPromiseById[promiseId].state}";
 61        }
 62
 8263        public AssetPromiseKeeper(AssetLibraryType library)
 64        {
 8265            this.library = library;
 8266            CoroutineStarter.Start(ProcessBlockedPromisesQueue());
 8267        }
 68
 69        public AssetPromiseType Keep(AssetPromiseType promise)
 70        {
 70871            if (promise == null || promise.state != AssetPromiseState.IDLE_AND_EMPTY || waitingPromises.Contains(promise
 572                return promise;
 73
 70374            object id = promise.GetId();
 75
 70376            if (id == null)
 77            {
 078                Debug.LogError("ERROR: ID == null. Promise is not set up correctly.");
 079                return promise;
 80            }
 81
 70382            promise.isDirty = true;
 83
 84            //NOTE(Brian): We already have a master promise for this id, add to blocked list.
 70385            if (masterPromiseById.ContainsKey(id))
 86            {
 35987                waitingPromises.Add(promise);
 88
 35989                if (!masterToBlockedPromises.ContainsKey(id))
 3790                    masterToBlockedPromises.Add(id, new HashSet<AssetPromiseType>());
 91
 35992                masterToBlockedPromises[id].Add(promise);
 93
 35994                blockedPromises.Add(promise);
 35995                promise.SetWaitingState();
 35996                return promise;
 97            }
 98
 99            // NOTE(Brian): Not in library, add to corresponding lists...
 344100            if (!library.Contains(id))
 101            {
 288102                waitingPromises.Add(promise);
 288103                masterPromiseById.Add(id, promise);
 104            }
 105
 344106            promise.library = library;
 344107            promise.OnPreFinishEvent += OnRequestCompleted;
 344108            promise.Load();
 109
 344110            return promise;
 111        }
 112
 113        public virtual AssetPromiseType Forget(AssetPromiseType promise)
 114        {
 601115            if (promise == null)
 43116                return null;
 117
 558118            if (promise.state == AssetPromiseState.IDLE_AND_EMPTY || promise.state == AssetPromiseState.WAITING)
 119            {
 291120                CleanPromise(promise);
 291121                promise.OnForget();
 291122                return promise;
 123            }
 124
 267125            object id = promise.GetId();
 126
 267127            bool isMasterPromise = masterPromiseById.ContainsKey(id) && masterPromiseById[id] == promise;
 267128            bool hasBlockedPromises = masterToBlockedPromises.ContainsKey(id) && masterToBlockedPromises[id].Count > 0;
 129
 267130            if (isMasterPromise && hasBlockedPromises)
 131            {
 132                //NOTE(Brian): Pending promises are waiting for this one.
 133                //             We clear the events because we shouldn't call them, as this promise is forgotten.
 23134                OnSilentForget(promise);
 23135                promise.OnForget();
 23136                return promise;
 137            }
 138
 244139            promise.Unload();
 244140            CleanPromise(promise);
 244141            promise.OnForget();
 142
 244143            return promise;
 144        }
 145
 82146        Queue<AssetPromiseType> toResolveBlockedPromisesQueue = new Queue<AssetPromiseType>();
 147
 148        private void OnRequestCompleted(AssetPromise<AssetType> loadedPromise)
 149        {
 308150            object id = loadedPromise.GetId();
 151
 308152            if (!masterToBlockedPromises.ContainsKey(id) || !masterPromiseById.ContainsKey(id))
 153            {
 273154                CleanPromise(loadedPromise);
 273155                return;
 156            }
 157
 35158            toResolveBlockedPromisesQueue.Enqueue(loadedPromise as AssetPromiseType);
 35159        }
 160
 161        IEnumerator ProcessBlockedPromisesQueue()
 162        {
 82163            startTime = Time.unscaledTime;
 164
 33165            while (true)
 166            {
 125669167                if (toResolveBlockedPromisesQueue.Count <= 0)
 168                {
 125636169                    yield return null;
 125554170                    continue;
 171                }
 172
 33173                AssetPromiseType promise = toResolveBlockedPromisesQueue.Dequeue();
 33174                yield return ProcessBlockedPromisesDeferred(promise);
 33175                CleanPromise(promise);
 176
 33177                if (promise.isForgotten)
 178                {
 20179                    promise.Unload();
 180                }
 181
 33182                var enumerator = SkipFrameIfOverBudget();
 183
 33184                if (enumerator != null)
 33185                    yield return enumerator;
 33186            }
 187        }
 188
 189        private IEnumerator ProcessBlockedPromisesDeferred(AssetPromiseType loadedPromise)
 190        {
 33191            object loadedPromiseId = loadedPromise.GetId();
 192
 33193            if (!masterToBlockedPromises.ContainsKey(loadedPromiseId))
 0194                yield break;
 195
 33196            if (!masterPromiseById.ContainsKey(loadedPromiseId))
 0197                yield break;
 198
 33199            if (masterPromiseById[loadedPromiseId] != loadedPromise)
 200            {
 0201                Debug.LogWarning($"Unexpected issue: masterPromiseById promise isn't the same as loaded promise? id: {lo
 0202                yield break;
 203            }
 204
 205
 206            //NOTE(Brian): We have to keep checking to support the case in which
 207            //             new promises are enqueued while this promise ID is being
 208            //             resolved.
 54209            while (masterToBlockedPromises.ContainsKey(loadedPromiseId) &&
 210                   masterToBlockedPromises[loadedPromiseId].Count > 0)
 211            {
 21212                List<AssetPromiseType> promisesToLoadForId = GetBlockedPromisesToLoadForId(loadedPromiseId);
 213
 21214                var enumerator = SkipFrameIfOverBudget();
 215
 21216                if (enumerator != null)
 21217                    yield return enumerator;
 218
 21219                if (loadedPromise.state != AssetPromiseState.FINISHED)
 5220                    yield return ForceFailPromiseList(promisesToLoadForId);
 221                else
 16222                    yield return LoadPromisesList(promisesToLoadForId);
 223
 21224                enumerator = SkipFrameIfOverBudget();
 225
 21226                if (enumerator != null)
 21227                    yield return enumerator;
 21228            }
 229
 33230            if (masterToBlockedPromises.ContainsKey(loadedPromiseId))
 29231                masterToBlockedPromises.Remove(loadedPromiseId);
 33232        }
 233
 234        private IEnumerator SkipFrameIfOverBudget()
 235        {
 159236            if (useTimeBudget && Time.realtimeSinceStartup - startTime >= AssetPromiseKeeper.PROCESS_PROMISES_TIME_BUDGE
 237            {
 71238                yield return null;
 71239                startTime = Time.unscaledTime;
 240            }
 159241        }
 242
 243        private List<AssetPromiseType> GetBlockedPromisesToLoadForId(object masterPromiseId)
 244        {
 21245            var blockedPromisesToLoadAux = new List<AssetPromiseType>();
 246
 21247            using (var iterator = masterToBlockedPromises[masterPromiseId].GetEnumerator())
 248            {
 115249                while (iterator.MoveNext())
 250                {
 94251                    var blockedPromise = iterator.Current;
 252
 94253                    blockedPromises.Remove(blockedPromise);
 254
 94255                    if (blockedPromise != null && !blockedPromise.isForgotten)
 256                    {
 94257                        blockedPromisesToLoadAux.Add(blockedPromise);
 258                    }
 259                }
 21260            }
 261
 21262            return blockedPromisesToLoadAux;
 263        }
 264
 265        private IEnumerator ForceFailPromiseList(List<AssetPromiseType> promises)
 266        {
 5267            int promisesCount = promises.Count;
 268
 46269            for (int i = 0; i < promisesCount; i++)
 270            {
 18271                var promise = promises[i];
 18272                if (promise.isForgotten)
 273                    continue;
 274
 8275                promise.ForceFail();
 8276                Forget(promise);
 8277                CleanPromise(promise);
 278
 8279                IEnumerator enumerator = SkipFrameIfOverBudget();
 280
 8281                if (enumerator != null)
 8282                    yield return enumerator;
 283            }
 5284        }
 285
 286        private IEnumerator LoadPromisesList(List<AssetPromiseType> promises)
 287        {
 16288            int promisesCount = promises.Count;
 289
 184290            for (int i = 0; i < promisesCount; i++)
 291            {
 76292                AssetPromiseType promise = promises[i];
 76293                if (promise.isForgotten)
 294                    continue;
 295
 76296                promise.library = library;
 76297                CleanPromise(promise);
 76298                promise.Load();
 299
 76300                var enumerator = SkipFrameIfOverBudget();
 301
 76302                if (enumerator != null)
 76303                    yield return enumerator;
 304            }
 16305        }
 306
 307        void CleanPromise(AssetPromise<AssetType> promise)
 308        {
 925309            if (!promise.isDirty)
 230310                return;
 311
 695312            promise.isDirty = false;
 313
 695314            AssetPromiseType finalPromise = promise as AssetPromiseType;
 315
 695316            object id = promise.GetId();
 317
 695318            if (masterToBlockedPromises.ContainsKey(id))
 319            {
 358320                if (masterToBlockedPromises[id].Contains(finalPromise))
 321                {
 358322                    masterToBlockedPromises[id].Remove(finalPromise);
 323                }
 324            }
 325
 695326            if (masterPromiseById.ContainsKey(id) && masterPromiseById[id] == promise)
 327            {
 277328                masterPromiseById.Remove(id);
 329            }
 330
 695331            if (blockedPromises.Contains(finalPromise))
 264332                blockedPromises.Remove(finalPromise);
 333
 695334            if (waitingPromises.Contains(finalPromise))
 635335                waitingPromises.Remove(finalPromise);
 695336        }
 337
 338        public void Cleanup()
 339        {
 2027340            blockedPromises = new HashSet<AssetPromiseType>();
 2027341            masterToBlockedPromises = new Dictionary<object, HashSet<AssetPromiseType>>();
 342
 2027343            using (var e = waitingPromises.GetEnumerator())
 344            {
 2035345                while (e.MoveNext())
 8346                    e.Current?.Cleanup();
 2027347            }
 348
 4068349            foreach (var kvp in masterPromiseById)
 350            {
 7351                kvp.Value.Cleanup();
 352            }
 353
 2027354            masterPromiseById = new Dictionary<object, AssetPromiseType>();
 2027355            waitingPromises = new HashSet<AssetPromiseType>();
 2027356            library.Cleanup();
 2027357        }
 358
 16359        protected virtual void OnSilentForget(AssetPromiseType promise) { }
 360    }
 361}