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

Methods/Properties

AssetPromiseKeeper()