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

Methods/Properties

AssetPromiseKeeper()