< Summary

Class:DCLServices.WearablesCatalogService.WebInterfaceWearablesCatalogService
Assembly:WearablesCatalogService
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/DCLServices/WearablesCatalogService/WebInterfaceWearablesCatalogService.cs
Covered lines:118
Uncovered lines:58
Coverable lines:176
Total lines:415
Line coverage:67% (118 of 176)
Covered branches:0
Total branches:0
Covered methods:23
Total methods:34
Method coverage:67.6% (23 of 34)

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
WebInterfaceWearablesCatalogService()0%110100%
Awake()0%110100%
Initialize()0%1.021075%
Initialize(...)0%110100%
Dispose()0%330100%
GetThirdPartyCollectionsAsync(...)0%2100%
RequestOwnedWearablesAsync(...)0%2100%
RequestOwnedWearablesAsync()0%3.583060%
RequestBaseWearablesAsync()0%5.673033.33%
RequestThirdPartyWearablesByCollectionAsync()0%3.583060%
RequestWearableAsync()0%6.016092.86%
RequestWearableFromBuilderAsync(...)0%2100%
RequestWearableCollection(...)0%2100%
RequestWearableCollectionInBuilder(...)0%2100%
RequestWearablesByContextAsync()0%6.016093.75%
AddWearablesToCatalog(...)0%3.433063.64%
WearablesRequestFailed(...)0%550100%
AddWearablesToCatalog(...)0%550100%
RemoveWearablesFromCatalog(...)0%12300%
RemoveWearableFromCatalog(...)0%2100%
RemoveWearablesInUse(...)0%30500%
AddEmbeddedWearablesToCatalog(...)0%20400%
Clear()0%12300%
IsValidWearable(...)0%6200%
CheckForSendingPendingRequestsAsync()0%14.2714088.89%
CheckForRequestsTimeOutsAsync()0%25.089041.67%
CheckForRequestsByContextTimeOutsAsync()0%129066.67%
ResolvePendingWearableById(...)0%3.023087.5%
ResolvePendingWearablesByContext(...)0%3.023087.5%
FilterWearablesByPage(...)0%550100%

File(s)

/tmp/workspace/unity-renderer/unity-renderer/Assets/DCLServices/WearablesCatalogService/WebInterfaceWearablesCatalogService.cs

#LineLine coverage
 1using Cysharp.Threading.Tasks;
 2using JetBrains.Annotations;
 3using Newtonsoft.Json;
 4using System;
 5using System.Collections.Generic;
 6using System.Linq;
 7using System.Threading;
 8using UnityEngine;
 9
 10namespace DCLServices.WearablesCatalogService
 11{
 12    /// <summary>
 13    /// This service keeps the same logic of the old CatalogController (but managed with UniTasks instead of Promises)
 14    /// so all the wearables requests will pass through kernel.
 15    /// It will be deprecated once we move all the kernel's logic related to requesting wearables to Unity.
 16    /// </summary>
 17    [Obsolete("This service will be deprecated by LambdasWearablesCatalogService in the future.")]
 18    public class WebInterfaceWearablesCatalogService : MonoBehaviour, IWearablesCatalogService
 19    {
 20        private const string OWNED_WEARABLES_CONTEXT = "OwnedWearables";
 21        private const string BASE_WEARABLES_CONTEXT = "BaseWearables";
 22        private const string THIRD_PARTY_WEARABLES_CONTEXT = "ThirdPartyWearables";
 23        private const float REQUESTS_TIME_OUT_SECONDS = 45;
 24
 12425        public static WebInterfaceWearablesCatalogService Instance { get; private set; }
 4926        public BaseDictionary<string, WearableItem> WearablesCatalog { get; private set; }
 27
 28        private WearablesWebInterfaceBridge webInterfaceBridge;
 29        private CancellationTokenSource serviceCts;
 930        private readonly Dictionary<string, int> wearablesInUseCounters = new ();
 931        private readonly Dictionary<string, UniTaskCompletionSource<WearableItem[]>> awaitingWearablesByContextTasks = n
 932        private readonly Dictionary<string, float> pendingWearablesByContextRequestedTimes = new ();
 933        private readonly Dictionary<string, UniTaskCompletionSource<WearableItem>> awaitingWearableTasks = new ();
 934        private readonly Dictionary<string, float> pendingWearableRequestedTimes = new ();
 935        private readonly List<string> pendingWearablesToRequest = new ();
 36
 37        private void Awake()
 38        {
 939            Instance = this;
 940        }
 41
 42        public void Initialize()
 43        {
 944            serviceCts = new CancellationTokenSource();
 45
 46            try
 47            {
 48                // All the requests happened during the same frames interval are sent together
 949                CheckForSendingPendingRequestsAsync(serviceCts.Token).Forget();
 950                CheckForRequestsTimeOutsAsync(serviceCts.Token).Forget();
 951                CheckForRequestsByContextTimeOutsAsync(serviceCts.Token).Forget();
 952            }
 053            catch (OperationCanceledException) { }
 954        }
 55
 56        public void Initialize(
 57            WearablesWebInterfaceBridge wearablesWebInterfaceBridge,
 58            BaseDictionary<string, WearableItem> wearablesCatalog)
 59        {
 960            webInterfaceBridge = wearablesWebInterfaceBridge;
 961            WearablesCatalog = wearablesCatalog;
 962            Initialize();
 963        }
 64
 65        public void Dispose()
 66        {
 967            serviceCts?.Cancel();
 968            serviceCts?.Dispose();
 969            Destroy(this);
 970        }
 71
 72        public UniTask<WearableCollectionsAPIData.Collection[]> GetThirdPartyCollectionsAsync(CancellationToken cancella
 073            throw new NotImplementedException("Supported by LambdasWearablesCatalogService");
 74
 75        public UniTask<(IReadOnlyList<WearableItem> wearables, int totalAmount)> RequestOwnedWearablesAsync(string userI
 76            NftRarity rarity = NftRarity.None,
 77            NftCollectionType collectionTypeMask = NftCollectionType.All,
 78            ICollection<string> thirdPartyCollectionIds = null, string name = null,
 79            (NftOrderByOperation type, bool directionAscendent)? orderBy = null) =>
 080            throw new NotImplementedException("Supported by LambdasWearablesCatalogService");
 81
 82        public async UniTask<(IReadOnlyList<WearableItem> wearables, int totalAmount)> RequestOwnedWearablesAsync(string
 83        {
 284            var wearables = await RequestWearablesByContextAsync(userId, null, null, $"{OWNED_WEARABLES_CONTEXT}{userId}
 185            return (FilterWearablesByPage(wearables, pageNumber, pageSize), wearables.Count);
 86
 187        }
 88
 89        public async UniTask RequestBaseWearablesAsync(CancellationToken ct) =>
 290            await RequestWearablesByContextAsync(null, null, new[] { IWearablesCatalogService.BASE_WEARABLES_COLLECTION_
 91
 92        public async UniTask<(IReadOnlyList<WearableItem> wearables, int totalAmount)> RequestThirdPartyWearablesByColle
 93        {
 294            var wearables = await RequestWearablesByContextAsync(userId, null, new[] { collectionId }, $"{THIRD_PARTY_WE
 195            return (FilterWearablesByPage(wearables, pageNumber, pageSize), wearables.Count);
 196        }
 97
 98        public async UniTask<WearableItem> RequestWearableAsync(string wearableId, CancellationToken ct)
 99        {
 3100            if (WearablesCatalog.TryGetValue(wearableId, out WearableItem wearable))
 101            {
 1102                if (wearablesInUseCounters.ContainsKey(wearableId))
 1103                    wearablesInUseCounters[wearableId]++;
 104
 1105                return wearable;
 106            }
 107
 2108            ct.ThrowIfCancellationRequested();
 109
 110            UniTaskCompletionSource<WearableItem> taskResult;
 111
 2112            if (!awaitingWearableTasks.ContainsKey(wearableId))
 113            {
 2114                taskResult = new UniTaskCompletionSource<WearableItem>();
 2115                awaitingWearableTasks.Add(wearableId, taskResult);
 116
 117                // We accumulate all the requests during the same frames interval to send them all together
 2118                pendingWearablesToRequest.Add(wearableId);
 119            }
 120            else
 0121                taskResult = awaitingWearableTasks[wearableId];
 122
 6123            return await taskResult.Task.AttachExternalCancellation(ct);
 2124        }
 125
 126        public UniTask<WearableItem> RequestWearableFromBuilderAsync(string wearableId, CancellationToken ct) =>
 0127            throw new NotImplementedException("Supported by LambdasWearablesCatalogService");
 128
 129        public UniTask<IReadOnlyList<WearableItem>> RequestWearableCollection(IEnumerable<string> collectionIds,
 130            CancellationToken cancellationToken, List<WearableItem> wearableBuffer = null) =>
 0131            throw new NotImplementedException("Supported by LambdasWearablesCatalogService");
 132
 133        public UniTask<(IReadOnlyList<WearableItem> wearables, int totalAmount)> RequestWearableCollectionInBuilder(
 134            IEnumerable<string> collectionIds, CancellationToken cancellationToken,
 135            List<WearableItem> collectionBuffer = null, string nameFilter = null, int pageNumber = 1, int pageSize = 500
 0136            throw new NotImplementedException("Supported by LambdasWearablesCatalogService");
 137
 138        private async UniTask<IReadOnlyList<WearableItem>> RequestWearablesByContextAsync(
 139            string userId,
 140            string[] wearableIds,
 141            string[] collectionIds,
 142            string context,
 143            bool isThirdParty,
 144            CancellationToken ct)
 145        {
 8146            ct.ThrowIfCancellationRequested();
 147
 148            UniTaskCompletionSource<WearableItem[]> taskResult;
 149
 8150            if (!awaitingWearablesByContextTasks.ContainsKey(context))
 151            {
 8152                taskResult = new UniTaskCompletionSource<WearableItem[]>();
 8153                awaitingWearablesByContextTasks.Add(context, taskResult);
 154
 8155                if (!pendingWearablesByContextRequestedTimes.ContainsKey(context))
 8156                    pendingWearablesByContextRequestedTimes.Add(context, Time.realtimeSinceStartup);
 157
 8158                if (!isThirdParty)
 6159                    webInterfaceBridge.RequestWearables(userId, wearableIds, collectionIds, context);
 160                else
 2161                    webInterfaceBridge.RequestThirdPartyWearables(userId, collectionIds[0], context);
 162            }
 163            else
 0164                taskResult = awaitingWearablesByContextTasks[context];
 165
 10166            var wearablesResult = await taskResult.Task.AttachExternalCancellation(ct);
 4167            AddWearablesToCatalog(wearablesResult);
 168
 4169            return wearablesResult;
 4170        }
 171
 172        [PublicAPI]
 173        public void AddWearablesToCatalog(string payload)
 174        {
 4175            WearablesRequestResponse request = null;
 176
 177            try
 178            {
 179                // The new wearables paradigm is based on composing with optional field
 180                // i.e. the emotes will have an emoteDataV0 property with some values.
 181                // JsonUtility.FromJson doesn't allow null properties so we have to use Newtonsoft instead
 4182                request = JsonConvert.DeserializeObject<WearablesRequestResponse>(payload);
 4183            }
 0184            catch (Exception e) { Debug.LogError($"Fail to parse wearables json {e}"); }
 185
 4186            if (request == null)
 0187                return;
 188
 4189            if (!string.IsNullOrEmpty(request.context))
 4190                ResolvePendingWearablesByContext(request.context, request.wearables);
 4191        }
 192
 193        [PublicAPI]
 194        public void WearablesRequestFailed(string payload)
 195        {
 4196            WearablesRequestFailed requestFailedResponse = JsonUtility.FromJson<WearablesRequestFailed>(payload);
 197
 4198            if (requestFailedResponse.context == BASE_WEARABLES_CONTEXT ||
 199                requestFailedResponse.context.Contains(THIRD_PARTY_WEARABLES_CONTEXT) ||
 3200                requestFailedResponse.context.Contains(OWNED_WEARABLES_CONTEXT)) { ResolvePendingWearablesByContext(requ
 201            else
 202            {
 1203                string[] failedWearablesIds = requestFailedResponse.context.Split(',');
 204
 4205                foreach (string failedWearableId in failedWearablesIds)
 206                {
 1207                    ResolvePendingWearableById(
 208                        failedWearableId,
 209                        null,
 210                        $"The request for the wearable '{failedWearableId}' has failed: {requestFailedResponse.error}");
 211                }
 212            }
 1213        }
 214
 215        public void AddWearablesToCatalog(IEnumerable<WearableItem> wearableItems)
 216        {
 32217            foreach (WearableItem wearableItem in wearableItems)
 218            {
 11219                if (WearablesCatalog.ContainsKey(wearableItem.id))
 220                    continue;
 221
 11222                wearableItem.SanitizeHidesLists();
 11223                WearablesCatalog.Add(wearableItem.id, wearableItem);
 224
 11225                if (!wearablesInUseCounters.ContainsKey(wearableItem.id))
 11226                    wearablesInUseCounters.Add(wearableItem.id, 1);
 227            }
 5228        }
 229
 230        public void RemoveWearablesFromCatalog(IEnumerable<string> wearableIds)
 231        {
 0232            foreach (string wearableId in wearableIds)
 0233                RemoveWearableFromCatalog(wearableId);
 0234        }
 235
 236        public void RemoveWearableFromCatalog(string wearableId)
 237        {
 0238            WearablesCatalog.Remove(wearableId);
 0239            wearablesInUseCounters.Remove(wearableId);
 0240        }
 241
 242        public void RemoveWearablesInUse(IEnumerable<string> wearablesInUseToRemove)
 243        {
 0244            foreach (string wearableToRemove in wearablesInUseToRemove)
 245            {
 0246                if (!wearablesInUseCounters.ContainsKey(wearableToRemove))
 247                    continue;
 248
 0249                wearablesInUseCounters[wearableToRemove]--;
 250
 0251                if (wearablesInUseCounters[wearableToRemove] > 0)
 252                    continue;
 253
 0254                WearablesCatalog.Remove(wearableToRemove);
 0255                wearablesInUseCounters.Remove(wearableToRemove);
 256            }
 0257        }
 258
 259        public void AddEmbeddedWearablesToCatalog(IEnumerable<WearableItem> wearables)
 260        {
 0261            foreach (WearableItem wearableItem in wearables)
 262            {
 0263                WearablesCatalog[wearableItem.id] = wearableItem;
 264
 0265                if (wearablesInUseCounters.ContainsKey(wearableItem.id))
 0266                    wearablesInUseCounters[wearableItem.id] = 10000; //A high value to ensure they are not removed
 267            }
 0268        }
 269
 270        public void Clear()
 271        {
 0272            WearablesCatalog.Clear();
 0273            wearablesInUseCounters.Clear();
 0274            pendingWearablesToRequest.Clear();
 0275            pendingWearableRequestedTimes.Clear();
 0276            pendingWearablesByContextRequestedTimes.Clear();
 277
 0278            foreach (var awaitingTask in awaitingWearableTasks)
 0279                awaitingTask.Value.TrySetCanceled();
 280
 0281            awaitingWearableTasks.Clear();
 282
 0283            foreach (var awaitingTask in awaitingWearablesByContextTasks)
 0284                awaitingTask.Value.TrySetCanceled();
 285
 0286            awaitingWearablesByContextTasks.Clear();
 0287        }
 288
 289        public bool IsValidWearable(string wearableId)
 290        {
 0291            if (!WearablesCatalog.TryGetValue(wearableId, out var wearable))
 0292                return false;
 293
 0294            return wearable != null;
 295        }
 296
 297        private async UniTaskVoid CheckForSendingPendingRequestsAsync(CancellationToken ct)
 298        {
 26299            while (!ct.IsCancellationRequested)
 300            {
 78301                await UniTask.Yield(PlayerLoopTiming.PostLateUpdate, cancellationToken: ct);
 302
 18303                if (pendingWearablesToRequest.Count <= 0)
 304                    continue;
 305
 2306                string[] wearablesToRequest = pendingWearablesToRequest.ToArray();
 2307                pendingWearablesToRequest.Clear();
 308
 8309                foreach (string wearablesToRequestId in wearablesToRequest)
 310                {
 2311                    if (!pendingWearableRequestedTimes.ContainsKey(wearablesToRequestId))
 2312                        pendingWearableRequestedTimes.Add(wearablesToRequestId, Time.realtimeSinceStartup);
 313                }
 314
 4315                var requestedWearables = await RequestWearablesByContextAsync(null, wearablesToRequest, null, string.Joi
 1316                List<string> wearablesNotFound = wearablesToRequest.ToList();
 317
 4318                foreach (WearableItem wearable in requestedWearables)
 319                {
 1320                    wearablesNotFound.Remove(wearable.id);
 1321                    ResolvePendingWearableById(wearable.id, wearable);
 322                }
 323
 2324                foreach (string wearableNotFound in wearablesNotFound)
 0325                    ResolvePendingWearableById(wearableNotFound, null);
 1326            }
 0327        }
 328
 329        private async UniTaskVoid CheckForRequestsTimeOutsAsync(CancellationToken ct)
 330        {
 27331            while (!ct.IsCancellationRequested)
 332            {
 81333                await UniTask.Yield(PlayerLoopTiming.PostLateUpdate, cancellationToken: ct);
 334
 18335                if (pendingWearableRequestedTimes.Count <= 0)
 336                    continue;
 337
 0338                var expiredRequests = (from taskRequestedTime in pendingWearableRequestedTimes
 0339                    where Time.realtimeSinceStartup - taskRequestedTime.Value > REQUESTS_TIME_OUT_SECONDS
 0340                    select taskRequestedTime.Key).ToList();
 341
 0342                foreach (string expiredRequestId in expiredRequests)
 343                {
 0344                    pendingWearableRequestedTimes.Remove(expiredRequestId);
 345
 0346                    ResolvePendingWearableById(expiredRequestId, null,
 347                        $"The request for the wearable '{expiredRequestId}' has exceed the set timeout!");
 348                }
 349            }
 0350        }
 351
 352        private async UniTaskVoid CheckForRequestsByContextTimeOutsAsync(CancellationToken ct)
 353        {
 27354            while (!ct.IsCancellationRequested)
 355            {
 81356                await UniTask.Yield(PlayerLoopTiming.PostLateUpdate, cancellationToken: ct);
 357
 18358                if (pendingWearablesByContextRequestedTimes.Count <= 0)
 359                    continue;
 360
 1361                var expiredRequests = (from promiseByContextRequestedTime in pendingWearablesByContextRequestedTimes
 1362                    where Time.realtimeSinceStartup - promiseByContextRequestedTime.Value > REQUESTS_TIME_OUT_SECONDS
 0363                    select promiseByContextRequestedTime.Key).ToList();
 364
 2365                foreach (string expiredRequestToRemove in expiredRequests)
 366                {
 0367                    pendingWearablesByContextRequestedTimes.Remove(expiredRequestToRemove);
 368
 0369                    ResolvePendingWearablesByContext(expiredRequestToRemove, null,
 370                        $"The request for the wearable context '{expiredRequestToRemove}' has exceed the set timeout!");
 371                }
 372            }
 0373        }
 374
 375        private void ResolvePendingWearableById(string wearableId, WearableItem result, string errorMessage = "")
 376        {
 2377            if (!awaitingWearableTasks.TryGetValue(wearableId, out var task))
 0378                return;
 379
 2380            if (string.IsNullOrEmpty(errorMessage))
 1381                task.TrySetResult(result);
 382            else
 1383                task.TrySetException(new Exception(errorMessage));
 384
 2385            awaitingWearableTasks.Remove(wearableId);
 2386            pendingWearableRequestedTimes.Remove(wearableId);
 2387        }
 388
 389        private void ResolvePendingWearablesByContext(string context, WearableItem[] newWearablesAddedIntoCatalog = null
 390        {
 7391            if (!awaitingWearablesByContextTasks.TryGetValue(context, out var task))
 0392                return;
 393
 7394            if (string.IsNullOrEmpty(errorMessage))
 4395                task.TrySetResult(newWearablesAddedIntoCatalog);
 396            else
 3397                task.TrySetException(new Exception(errorMessage));
 398
 7399            awaitingWearablesByContextTasks.Remove(context);
 7400            pendingWearablesByContextRequestedTimes.Remove(context);
 7401        }
 402
 403        // As kernel doesn't have pagination available, we apply a "local" pagination over the returned results
 404        private static IReadOnlyList<WearableItem> FilterWearablesByPage(IReadOnlyCollection<WearableItem> wearables, in
 405        {
 2406            int paginationIndex = pageNumber * pageSize;
 2407            int skippedWearables = Math.Min(paginationIndex - pageSize, wearables.Count);
 2408            int takenWearables = paginationIndex > wearables.Count ? paginationIndex - (paginationIndex - wearables.Coun
 2409            return wearables
 410                  .Skip(skippedWearables)
 411                  .Take(skippedWearables < wearables.Count ? takenWearables : 0)
 412                  .ToArray();
 413        }
 414    }
 415}

Methods/Properties

Instance()
Instance(DCLServices.WearablesCatalogService.WebInterfaceWearablesCatalogService)
WearablesCatalog()
WearablesCatalog(.BaseDictionary[String,WearableItem])
WebInterfaceWearablesCatalogService()
Awake()
Initialize()
Initialize(DCLServices.WearablesCatalogService.WearablesWebInterfaceBridge, .BaseDictionary[String,WearableItem])
Dispose()
GetThirdPartyCollectionsAsync(System.Threading.CancellationToken)
RequestOwnedWearablesAsync(System.String, System.Int32, System.Int32, System.Threading.CancellationToken, System.String, DCLServices.WearablesCatalogService.NftRarity, DCLServices.WearablesCatalogService.NftCollectionType, System.Collections.Generic.ICollection[String], System.String, System.Nullable[ValueTuple`2])
RequestOwnedWearablesAsync()
RequestBaseWearablesAsync()
RequestThirdPartyWearablesByCollectionAsync()
RequestWearableAsync()
RequestWearableFromBuilderAsync(System.String, System.Threading.CancellationToken)
RequestWearableCollection(System.Collections.Generic.IEnumerable[String], System.Threading.CancellationToken, System.Collections.Generic.List[WearableItem])
RequestWearableCollectionInBuilder(System.Collections.Generic.IEnumerable[String], System.Threading.CancellationToken, System.Collections.Generic.List[WearableItem], System.String, System.Int32, System.Int32)
RequestWearablesByContextAsync()
AddWearablesToCatalog(System.String)
WearablesRequestFailed(System.String)
AddWearablesToCatalog(System.Collections.Generic.IEnumerable[WearableItem])
RemoveWearablesFromCatalog(System.Collections.Generic.IEnumerable[String])
RemoveWearableFromCatalog(System.String)
RemoveWearablesInUse(System.Collections.Generic.IEnumerable[String])
AddEmbeddedWearablesToCatalog(System.Collections.Generic.IEnumerable[WearableItem])
Clear()
IsValidWearable(System.String)
CheckForSendingPendingRequestsAsync()
CheckForRequestsTimeOutsAsync()
CheckForRequestsByContextTimeOutsAsync()
ResolvePendingWearableById(System.String, WearableItem, System.String)
ResolvePendingWearablesByContext(System.String, WearableItem[], System.String)
FilterWearablesByPage(System.Collections.Generic.IReadOnlyCollection[WearableItem], System.Int32, System.Int32)