< Summary

Class:AvatarSystem.AvatarCurator
Assembly:AvatarSystem
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/AvatarSystem/AvatarCurator.cs
Covered lines:45
Uncovered lines:19
Coverable lines:64
Total lines:187
Line coverage:70.3% (45 of 64)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
AvatarCurator(...)0%110100%
Curate()0%117.132056.36%
GetFallbackForMissingNeededCategories()0%12.5410070.59%
Dispose()0%110100%

File(s)

/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/AvatarSystem/AvatarCurator.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Linq;
 4using System.Runtime.ExceptionServices;
 5using System.Threading;
 6using Cysharp.Threading.Tasks;
 7using DCL;
 8using UnityEngine;
 9using UnityEngine.Assertions;
 10
 11namespace AvatarSystem
 12{
 13    public class AvatarCurator : IAvatarCurator
 14    {
 15        private readonly IWearableItemResolver wearableItemResolver;
 16        private readonly IEmotesCatalogService emotesCatalog;
 17
 66618        public AvatarCurator(IWearableItemResolver wearableItemResolver, IEmotesCatalogService emotesCatalog)
 19        {
 66620            Assert.IsNotNull(wearableItemResolver);
 66621            this.wearableItemResolver = wearableItemResolver;
 66622            this.emotesCatalog = emotesCatalog;
 66623        }
 24
 25        /// <summary>
 26        /// Curate a flattened into IDs set of wearables.
 27        /// Bear in mind that the bodyshape must be part of the list of wearables
 28        /// </summary>
 29        /// <param name="settings"></param>
 30        /// <param name="wearablesId"></param>
 31        /// <param name="ct"></param>
 32        /// <returns></returns>
 33        /// <exception cref="Exception"></exception>
 34        public async UniTask<(
 35            WearableItem bodyshape,
 36            WearableItem eyes,
 37            WearableItem eyebrows,
 38            WearableItem mouth,
 39            List<WearableItem> wearables,
 40            List<WearableItem> emotes
 41            )> Curate(AvatarSettings settings, IEnumerable<string> wearablesId, IEnumerable<string> emoteIds, Cancellati
 42        {
 343            ct.ThrowIfCancellationRequested();
 44
 45            try
 46            {
 47                //Old flow contains emotes among the wearablesIds
 248                (List<WearableItem> wearableItems, List<WearableItem> emotes) =  await wearableItemResolver.ResolveAndSp
 49
 250                HashSet<string> hiddenCategories = WearableItem.ComposeHiddenCategories(settings.bodyshapeId, wearableIt
 51
 52                //New emotes flow use the emotes catalog
 253                if (emoteIds != null)
 54                {
 255                    DateTime startLoadTime = DateTime.Now;
 56
 257                    var moreEmotes = await emotesCatalog.RequestEmotesAsync(emoteIds.ToList(), ct);
 58
 259                    var loadTimeDelta = DateTime.Now - startLoadTime;
 260                    if (loadTimeDelta.TotalSeconds > 5)
 61                    {
 62                        //This error is good to have to detect too long load times early
 063                        Debug.LogError("Curate: emotes load time is too high: " + (DateTime.Now - startLoadTime));
 64                    }
 65
 266                    if (moreEmotes != null)
 67                    {
 68                        //this filter is needed to make sure there will be no duplicates coming from two sources of emot
 069                        var loadedEmotesFilter = new HashSet<string>();
 070                        emotes.ForEach(e => loadedEmotesFilter.Add(e.id));
 71
 072                        foreach(var otherEmote in moreEmotes)
 073                            if (otherEmote != null)
 74                            {
 075                                if (loadedEmotesFilter.Contains(otherEmote.id))
 76                                    continue;
 77
 078                                emotes.Add(otherEmote);
 79                            }
 80                    }
 81                }
 82
 283                Dictionary<string, WearableItem> wearablesByCategory = new Dictionary<string, WearableItem>();
 2284                for (int i = 0; i < wearableItems.Count; i++)
 85                {
 986                    WearableItem wearableItem = wearableItems[i];
 87
 88                    // Ignore hidden categories
 989                    if (hiddenCategories.Contains(wearableItem.data.category))
 90                        continue;
 91
 92                    // Avoid having two items with the same category.
 993                    if (wearableItem == null || wearablesByCategory.ContainsKey(wearableItem.data.category) )
 94                        continue;
 95
 96                    // Filter wearables without representation for the bodyshape
 997                    if (!wearableItem.TryGetRepresentation(settings.bodyshapeId, out var representation))
 98                        continue;
 99
 9100                    wearablesByCategory.Add(wearableItem.data.category, wearableItem);
 101                }
 102
 2103                if (!wearablesByCategory.ContainsKey(WearableLiterals.Categories.BODY_SHAPE))
 0104                    throw new Exception("Set of wearables doesn't contain a bodyshape (or couldn't be resolved)");
 105
 2106                WearableItem[] fallbackWearables = await GetFallbackForMissingNeededCategories(settings.bodyshapeId, wea
 107
 14108                for (int i = 0; i < fallbackWearables.Length; i++)
 109                {
 5110                    WearableItem wearableItem = fallbackWearables[i];
 5111                    if (wearableItem == null)
 0112                        throw new Exception($"Fallback wearable is null");
 5113                    if (!wearableItem.TryGetRepresentation(settings.bodyshapeId, out var representation))
 0114                        throw new Exception($"Fallback wearable {wearableItem} doesn't contain a representation for {set
 5115                    if (wearablesByCategory.ContainsKey(wearableItem.data.category))
 0116                        throw new Exception($"A wearable in category {wearableItem.data.category} already exists trying 
 5117                    wearablesByCategory.Add(wearableItem.data.category, wearableItem);
 118                }
 119
 120                // Wearables that are not bodyshape or facialFeatures
 2121                List<WearableItem> wearables = wearablesByCategory.Where(
 122                                                                      x =>
 14123                                                                          x.Key != WearableLiterals.Categories.BODY_SHAP
 124                                                                          x.Key != WearableLiterals.Categories.EYES &&
 125                                                                          x.Key != WearableLiterals.Categories.EYEBROWS 
 126                                                                          x.Key != WearableLiterals.Categories.MOUTH)
 6127                                                                  .Select(x => x.Value)
 128                                                                  .ToList();
 129
 2130                return (
 131                    wearablesByCategory[WearableLiterals.Categories.BODY_SHAPE],
 132                    wearablesByCategory.ContainsKey(WearableLiterals.Categories.EYES) ? wearablesByCategory[WearableLite
 133                    wearablesByCategory.ContainsKey(WearableLiterals.Categories.EYEBROWS) ? wearablesByCategory[Wearable
 134                    wearablesByCategory.ContainsKey(WearableLiterals.Categories.MOUTH) ? wearablesByCategory[WearableLit
 135                    wearables,
 136                    emotes.ToList()
 137                );
 138            }
 0139            catch (OperationCanceledException)
 140            {
 141                //No Disposing required
 0142                throw;
 143            }
 144            catch (Exception e)
 145            {
 0146                Debug.Log("Failed curating avatar wearables");
 0147                ExceptionDispatchInfo.Capture(e).Throw();
 0148                throw;
 149            }
 2150        }
 151
 152        private async UniTask<WearableItem[]> GetFallbackForMissingNeededCategories(string bodyshapeId, Dictionary<strin
 153        {
 2154            ct.ThrowIfCancellationRequested();
 155
 156            try
 157            {
 2158                List<UniTask<WearableItem>> neededWearablesTasks = new List<UniTask<WearableItem>>();
 24159                foreach (string neededCategory in WearableLiterals.Categories.REQUIRED_CATEGORIES)
 160                {
 161                    // If a needed category is hidden we dont need to fallback, we skipped it on purpose
 10162                    if (hiddenCategories.Contains(neededCategory))
 163                        continue;
 164
 165                    // The needed category is present
 10166                    if (wearablesByCategory.ContainsKey(neededCategory))
 167                        continue;
 168
 5169                    string fallbackWearableId = WearableLiterals.DefaultWearables.GetDefaultWearable(bodyshapeId, needed
 170
 5171                    if (fallbackWearableId == null)
 0172                        throw new Exception($"Couldn't find a fallback wearable for bodyshape: {bodyshapeId} and categor
 173
 5174                    neededWearablesTasks.Add(wearableItemResolver.Resolve(fallbackWearableId, ct));
 175                }
 2176                return await UniTask.WhenAll(neededWearablesTasks).AttachExternalCancellation(ct);
 177            }
 0178            catch (OperationCanceledException)
 179            {
 180                //No disposing required
 0181                throw;
 182            }
 2183        }
 184
 1346185        public void Dispose() { wearableItemResolver.Dispose(); }
 186    }
 187}