< 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:156
Uncovered lines:16
Coverable lines:172
Total lines:380
Line coverage:90.6% (156 of 172)
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%
EnsureProcessBlockerPromiseQueueCoroutine()0%220100%
Keep(...)0%8.048091.3%
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%8.028093.33%
Cleanup()0%550100%
OnSilentForget(...)0%110100%

File(s)

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

#LineLine coverage
 1using System;
 2using System.Collections;
 3using System.Collections.Generic;
 4using UnityEngine;
 5
 6namespace DCL
 7{
 8    public class AssetPromiseKeeper
 9    {
 10        public static float PROCESS_PROMISES_TIME_BUDGET = 0.006f;
 11    }
 12
 13    /// <summary>
 14    /// The AssetPromiseKeeper is the user entry point interface of the asset management system.
 15    /// It manages stuff like requesting something that's already being loaded, etc.
 16    ///
 17    /// It also handles the special cases of promises that depend on another 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.
 16430        HashSet<AssetPromiseType> waitingPromises = new HashSet<AssetPromiseType>();
 2931        public int waitingPromisesCount => waitingPromises.Count;
 32
 33        //NOTE(Brian): List of promises waiting for assets not in library.
 16434        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.
 16437        HashSet<AssetPromiseType> blockedPromises = new HashSet<AssetPromiseType>();
 38
 39        //NOTE(Brian): Master promise id -> blocked promises HashSet
 16440        protected Dictionary<object, HashSet<AssetPromiseType>> masterToBlockedPromises = new Dictionary<object, HashSet
 41
 16142        public bool useTimeBudget => CommonScriptableObjects.rendererState.Get();
 43
 44        float startTime;
 45
 46        Coroutine blockedPromiseSolverCoroutine;
 47
 048        public bool IsBlocked(AssetPromiseType promise) { return blockedPromises.Contains(promise); }
 49
 50        public string GetMasterState(AssetPromiseType promise)
 51        {
 052            object promiseId = promise.GetId();
 53
 054            if (!masterToBlockedPromises.ContainsKey(promiseId))
 055                return "Master not found";
 56
 057            if (!masterToBlockedPromises[promiseId].Contains(promise))
 058                return "Promise is not blocked???";
 59
 060            if (!masterPromiseById.ContainsKey(promiseId))
 061                return "not registered as master?";
 62
 063            return $"master state = {masterPromiseById[promiseId].state}";
 64        }
 65
 16466        public AssetPromiseKeeper(AssetLibraryType library)
 67        {
 16468            this.library = library;
 16469            EnsureProcessBlockerPromiseQueueCoroutine();
 16470        }
 71
 72        void EnsureProcessBlockerPromiseQueueCoroutine()
 73        {
 27074            if (blockedPromiseSolverCoroutine == null)
 75            {
 16676                blockedPromiseSolverCoroutine = CoroutineStarter.Start(ProcessBlockedPromisesQueue());
 77            }
 27078        }
 79
 80        public AssetPromiseType Keep(AssetPromiseType promise)
 81        {
 65182            if (promise == null || promise.state != AssetPromiseState.IDLE_AND_EMPTY || waitingPromises.Contains(promise
 183                return promise;
 84
 65085            object id = promise.GetId();
 86
 65087            if (id == null)
 88            {
 089                Debug.LogError("ERROR: ID == null. Promise is not set up correctly.");
 090                return promise;
 91            }
 92
 65093            promise.isDirty = true;
 94
 95            //NOTE(Brian): We already have a master promise for this id, add to blocked list.
 65096            if (masterPromiseById.ContainsKey(id))
 97            {
 98                // TODO(Brian): Remove once this class has a proper lifecycle and is on service locator.
 10699                EnsureProcessBlockerPromiseQueueCoroutine();
 106100                waitingPromises.Add(promise);
 101
 106102                if (!masterToBlockedPromises.ContainsKey(id))
 26103                    masterToBlockedPromises.Add(id, new HashSet<AssetPromiseType>());
 104
 106105                masterToBlockedPromises[id].Add(promise);
 106
 106107                blockedPromises.Add(promise);
 106108                promise.SetWaitingState();
 106109                return promise;
 110            }
 111
 112            // NOTE(Brian): Not in library, add to corresponding lists...
 544113            if (!library.Contains(id))
 114            {
 456115                waitingPromises.Add(promise);
 456116                masterPromiseById.Add(id, promise);
 117            }
 118
 544119            promise.library = library;
 544120            promise.OnPreFinishEvent += OnRequestCompleted;
 544121            promise.Load();
 122
 544123            return promise;
 124        }
 125
 126        public virtual AssetPromiseType Forget(AssetPromiseType promise)
 127        {
 625128            if (promise == null)
 197129                return null;
 130
 428131            if (promise.state == AssetPromiseState.IDLE_AND_EMPTY || promise.state == AssetPromiseState.WAITING)
 132            {
 110133                CleanPromise(promise);
 110134                promise.OnForget();
 110135                return promise;
 136            }
 137
 318138            object id = promise.GetId();
 139
 318140            bool isMasterPromise = masterPromiseById.ContainsKey(id) && masterPromiseById[id] == promise;
 318141            bool hasBlockedPromises = masterToBlockedPromises.ContainsKey(id) && masterToBlockedPromises[id].Count > 0;
 142
 318143            if (isMasterPromise && hasBlockedPromises)
 144            {
 145                //NOTE(Brian): Pending promises are waiting for this one.
 146                //             We clear the events because we shouldn't call them, as this promise is forgotten.
 8147                OnSilentForget(promise);
 8148                promise.OnForget();
 8149                return promise;
 150            }
 151
 310152            promise.Unload();
 310153            CleanPromise(promise);
 310154            promise.OnForget();
 155
 310156            return promise;
 157        }
 158
 164159        Queue<AssetPromiseType> toResolveBlockedPromisesQueue = new Queue<AssetPromiseType>();
 160
 161        private void OnRequestCompleted(AssetPromise<AssetType> loadedPromise)
 162        {
 515163            object id = loadedPromise.GetId();
 164
 515165            if (!masterToBlockedPromises.ContainsKey(id) || !masterPromiseById.ContainsKey(id))
 166            {
 491167                CleanPromise(loadedPromise);
 491168                return;
 169            }
 170
 24171            toResolveBlockedPromisesQueue.Enqueue(loadedPromise as AssetPromiseType);
 24172        }
 173
 174        IEnumerator ProcessBlockedPromisesQueue()
 175        {
 166176            startTime = Time.unscaledTime;
 177
 19178            while (true)
 179            {
 1250506180                if (toResolveBlockedPromisesQueue.Count <= 0)
 181                {
 1250482182                    yield return null;
 1250321183                    continue;
 184                }
 185
 24186                AssetPromiseType promise = toResolveBlockedPromisesQueue.Dequeue();
 24187                yield return ProcessBlockedPromisesDeferred(promise);
 19188                CleanPromise(promise);
 189
 19190                if (promise.isForgotten)
 191                {
 5192                    promise.Unload();
 193                }
 194
 19195                var enumerator = SkipFrameIfOverBudget();
 196
 19197                if (enumerator != null)
 19198                    yield return enumerator;
 19199            }
 200        }
 201
 202        private IEnumerator ProcessBlockedPromisesDeferred(AssetPromiseType loadedPromise)
 203        {
 24204            object loadedPromiseId = loadedPromise.GetId();
 205
 24206            if (!masterToBlockedPromises.ContainsKey(loadedPromiseId))
 0207                yield break;
 208
 24209            if (!masterPromiseById.ContainsKey(loadedPromiseId))
 0210                yield break;
 211
 24212            if (masterPromiseById[loadedPromiseId] != loadedPromise)
 213            {
 0214                Debug.LogWarning($"Unexpected issue: masterPromiseById promise isn't the same as loaded promise? id: {lo
 0215                yield break;
 216            }
 217
 218
 219            //NOTE(Brian): We have to keep checking to support the case in which
 220            //             new promises are enqueued while this promise ID is being
 221            //             resolved.
 45222            while (masterToBlockedPromises.ContainsKey(loadedPromiseId) &&
 223                   masterToBlockedPromises[loadedPromiseId].Count > 0)
 224            {
 26225                List<AssetPromiseType> promisesToLoadForId = GetBlockedPromisesToLoadForId(loadedPromiseId);
 226
 26227                var enumerator = SkipFrameIfOverBudget();
 228
 26229                if (enumerator != null)
 26230                    yield return enumerator;
 231
 26232                if (loadedPromise.state != AssetPromiseState.FINISHED)
 4233                    yield return ForceFailPromiseList(promisesToLoadForId);
 234                else
 22235                    yield return LoadPromisesList(promisesToLoadForId);
 236
 21237                enumerator = SkipFrameIfOverBudget();
 238
 21239                if (enumerator != null)
 21240                    yield return enumerator;
 21241            }
 242
 19243            if (masterToBlockedPromises.ContainsKey(loadedPromiseId))
 19244                masterToBlockedPromises.Remove(loadedPromiseId);
 19245        }
 246
 247        private IEnumerator SkipFrameIfOverBudget()
 248        {
 161249            if (useTimeBudget && Time.realtimeSinceStartup - startTime >= AssetPromiseKeeper.PROCESS_PROMISES_TIME_BUDGE
 250            {
 5251                yield return null;
 5252                startTime = Time.unscaledTime;
 253            }
 161254        }
 255
 256        private List<AssetPromiseType> GetBlockedPromisesToLoadForId(object masterPromiseId)
 257        {
 26258            var blockedPromisesToLoadAux = new List<AssetPromiseType>();
 259
 26260            using (var iterator = masterToBlockedPromises[masterPromiseId].GetEnumerator())
 261            {
 130262                while (iterator.MoveNext())
 263                {
 104264                    var blockedPromise = iterator.Current;
 265
 104266                    blockedPromises.Remove(blockedPromise);
 267
 104268                    if (blockedPromise != null && !blockedPromise.isForgotten)
 269                    {
 104270                        blockedPromisesToLoadAux.Add(blockedPromise);
 271                    }
 272                }
 26273            }
 274
 26275            return blockedPromisesToLoadAux;
 276        }
 277
 278        private IEnumerator ForceFailPromiseList(List<AssetPromiseType> promises)
 279        {
 4280            int promisesCount = promises.Count;
 281
 20282            for (int i = 0; i < promisesCount; i++)
 283            {
 8284                var promise = promises[i];
 8285                if (promise.isForgotten)
 286                    continue;
 287
 8288                promise.ForceFail(new Exception("Promise is forgotten"));
 8289                Forget(promise);
 8290                CleanPromise(promise);
 291
 8292                IEnumerator enumerator = SkipFrameIfOverBudget();
 293
 8294                if (enumerator != null)
 8295                    yield return enumerator;
 296            }
 2297        }
 298
 299        private IEnumerator LoadPromisesList(List<AssetPromiseType> promises)
 300        {
 22301            int promisesCount = promises.Count;
 302
 212303            for (int i = 0; i < promisesCount; i++)
 304            {
 87305                AssetPromiseType promise = promises[i];
 87306                if (promise.isForgotten)
 307                    continue;
 308
 87309                promise.library = library;
 87310                CleanPromise(promise);
 87311                promise.Load();
 312
 87313                var enumerator = SkipFrameIfOverBudget();
 314
 87315                if (enumerator != null)
 87316                    yield return enumerator;
 317            }
 19318        }
 319
 320        void CleanPromise(AssetPromise<AssetType> promise)
 321        {
 1025322            if (!promise.isDirty)
 384323                return;
 324
 641325            promise.isDirty = false;
 326
 641327            AssetPromiseType finalPromise = promise as AssetPromiseType;
 328
 641329            object id = promise.GetId();
 330
 641331            if (masterToBlockedPromises.ContainsKey(id))
 332            {
 104333                if (masterToBlockedPromises[id].Contains(finalPromise))
 334                {
 104335                    masterToBlockedPromises[id].Remove(finalPromise);
 336                }
 337            }
 338
 641339            if (masterPromiseById.ContainsKey(id) && masterPromiseById[id] == promise)
 340            {
 435341                masterPromiseById.Remove(id);
 342            }
 343
 641344            if (blockedPromises.Contains(finalPromise))
 0345                blockedPromises.Remove(finalPromise);
 346
 641347            if (waitingPromises.Contains(finalPromise))
 539348                waitingPromises.Remove(finalPromise);
 641349        }
 350
 351        public void Cleanup()
 352        {
 3293353            if (blockedPromiseSolverCoroutine != null)
 354            {
 103355                CoroutineStarter.Stop(blockedPromiseSolverCoroutine);
 103356                blockedPromiseSolverCoroutine = null;
 357            }
 358
 3293359            blockedPromises = new HashSet<AssetPromiseType>();
 3293360            masterToBlockedPromises = new Dictionary<object, HashSet<AssetPromiseType>>();
 361
 3293362            using (var e = waitingPromises.GetEnumerator())
 363            {
 3316364                while (e.MoveNext())
 23365                    e.Current?.Cleanup();
 3293366            }
 367
 6628368            foreach (var kvp in masterPromiseById)
 369            {
 21370                kvp.Value.Cleanup();
 371            }
 372
 3293373            masterPromiseById = new Dictionary<object, AssetPromiseType>();
 3293374            waitingPromises = new HashSet<AssetPromiseType>();
 3293375            library.Cleanup();
 3293376        }
 377
 4378        protected virtual void OnSilentForget(AssetPromiseType promise) { }
 379    }
 380}