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

Methods/Properties

AssetPromiseKeeper()