< Summary

Class:AvatarSystem.AvatarCurator
Assembly:AvatarSystem
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/AvatarSystem/AvatarCurator.cs
Covered lines:7
Uncovered lines:78
Coverable lines:85
Total lines:226
Line coverage:8.2% (7 of 85)
Covered branches:0
Total branches:0
Covered methods:2
Total methods:4
Method coverage:50% (2 of 4)

Metrics

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

File(s)

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

#LineLine coverage
 1using Cysharp.Threading.Tasks;
 2using DCL.Emotes;
 3using DCLServices.WearablesCatalogService;
 4using System;
 5using System.Collections.Generic;
 6using System.Linq;
 7using System.Threading;
 8using UnityEngine;
 9using UnityEngine.Assertions;
 10using UnityEngine.Pool;
 11
 12namespace AvatarSystem
 13{
 14    public class AvatarCurator : IAvatarCurator
 15    {
 16        private readonly IWearableItemResolver wearableItemResolver;
 17        private readonly IEmotesCatalogService emotesCatalog;
 18        private EmbeddedEmotesSO embedEmotes;
 19        private string[] embedEmotesId;
 20
 51821        public AvatarCurator(IWearableItemResolver wearableItemResolver, IEmotesCatalogService emotesCatalog)
 22        {
 51823            Assert.IsNotNull(wearableItemResolver);
 51824            this.wearableItemResolver = wearableItemResolver;
 51825            this.emotesCatalog = emotesCatalog;
 26
 51827        }
 28
 29        /// <summary>
 30        /// Curate a flattened into IDs set of wearables.
 31        /// Bear in mind that the bodyshape must be part of the list of wearables
 32        /// </summary>
 33        /// <param name="settings"></param>
 34        /// <param name="wearablesId"></param>
 35        /// <param name="emoteIds"></param>
 36        /// <param name="ct"></param>
 37        /// <returns></returns>
 38        /// <exception cref="Exception"></exception>
 39        public async UniTask<(
 40            BodyWearables bodyWearables,
 41            List<WearableItem> wearables,
 42            List<WearableItem> emotes
 43            )> Curate(AvatarSettings settings, IEnumerable<string> wearablesId, IEnumerable<string> emoteIds, Cancellati
 44        {
 045            ct.ThrowIfCancellationRequested();
 46
 47            try
 48            {
 49                //Old flow contains emotes among the wearablesIds
 050                (List<WearableItem> wearableItems, List<WearableItem> emotes) = await wearableItemResolver.ResolveAndSpl
 51
 052                HashSet<string> hiddenCategories = WearableItem.ComposeHiddenCategoriesOrdered(settings.bodyshapeId, set
 53
 54                //New emotes flow use the emotes catalog
 055                if (emoteIds != null)
 56                {
 057                    embedEmotes ??= await emotesCatalog.GetEmbeddedEmotes();
 058                    embedEmotesId ??= embedEmotes.GetAllIds();
 59
 060                    DateTime startLoadTime = DateTime.Now;
 61
 062                    var emoteIdsList = emoteIds.Select(ExtendedUrnParser.GetShortenedUrn).ToList();
 063                    IReadOnlyList<WearableItem> resolvedEmotes = await emotesCatalog.RequestEmotesAsync(emoteIdsList, ct
 064                    List<WearableItem> nonPublishedEmotes = ListPool<WearableItem>.Get();
 65
 066                    foreach (string nonPublishedEmoteId in emoteIdsList)
 67                    {
 068                        if (nonPublishedEmoteId.StartsWith("urn")) continue;
 069                        bool wasResolved = resolvedEmotes?.Any(item => item?.id == nonPublishedEmoteId) ?? false;
 070                        if (wasResolved) continue;
 071                        bool isEmbedded = embedEmotesId.Contains(nonPublishedEmoteId);
 072                        if (isEmbedded) continue;
 073                        WearableItem nonPublishedEmote = await emotesCatalog.RequestEmoteFromBuilderAsync(nonPublishedEm
 074                        if (nonPublishedEmote != null)
 075                            nonPublishedEmotes.Add(nonPublishedEmote);
 76                    }
 77
 078                    var loadTimeDelta = DateTime.Now - startLoadTime;
 79
 080                    if (loadTimeDelta.TotalSeconds > 5)
 81                    {
 82                        //This error is good to have to detect too long load times early
 083                        Debug.LogError("Curate: emotes load time is too high: " + (DateTime.Now - startLoadTime));
 84                    }
 85
 86                    //this filter is needed to make sure there will be no duplicates coming from two sources of emotes
 087                    var loadedEmotesFilter = new HashSet<string>();
 088                    emotes.ForEach(e => loadedEmotesFilter.Add(e.id));
 89
 090                    if (resolvedEmotes != null)
 91                    {
 092                        foreach (var otherEmote in resolvedEmotes)
 093                            if (otherEmote != null)
 94                            {
 095                                if (loadedEmotesFilter.Contains(otherEmote.id))
 96                                    continue;
 97
 098                                emotes.Add(otherEmote);
 99                            }
 100                    }
 101
 0102                    foreach (WearableItem emote in nonPublishedEmotes)
 103                    {
 0104                        if (emote == null) continue;
 0105                        if (loadedEmotesFilter.Contains(emote.id)) continue;
 0106                        emotes.Add(emote);
 107                    }
 108
 0109                    ListPool<WearableItem>.Release(nonPublishedEmotes);
 0110                }
 111
 0112                Dictionary<string, WearableItem> wearablesByCategory = new Dictionary<string, WearableItem>();
 113
 0114                for (int i = 0; i < wearableItems.Count; i++)
 115                {
 0116                    WearableItem wearableItem = wearableItems[i];
 117
 118                    // Ignore hidden categories
 0119                    if (hiddenCategories.Contains(wearableItem.data.category))
 120                        continue;
 121
 122                    // Avoid having two items with the same category.
 0123                    if (wearableItem == null || wearablesByCategory.ContainsKey(wearableItem.data.category))
 124                        continue;
 125
 126                    // Filter wearables without representation for the bodyshape
 0127                    if (!wearableItem.TryGetRepresentation(settings.bodyshapeId, out var representation))
 128                        continue;
 129
 0130                    wearablesByCategory.Add(wearableItem.data.category, wearableItem);
 131                }
 132
 0133                if (!wearablesByCategory.ContainsKey(WearableLiterals.Categories.BODY_SHAPE))
 0134                    throw new Exception("Set of wearables doesn't contain a bodyshape (or couldn't be resolved)");
 135
 0136                WearableItem[] fallbackWearables = await GetFallbackForMissingNeededCategories(settings.bodyshapeId, wea
 137
 0138                for (int i = 0; i < fallbackWearables.Length; i++)
 139                {
 0140                    WearableItem wearableItem = fallbackWearables[i];
 141
 0142                    if (wearableItem == null)
 0143                        throw new Exception($"Fallback wearable is null");
 144
 0145                    if (!wearableItem.TryGetRepresentation(settings.bodyshapeId, out var representation))
 0146                        throw new Exception($"Fallback wearable {wearableItem} doesn't contain a representation for {set
 147
 0148                    if (wearablesByCategory.ContainsKey(wearableItem.data.category))
 0149                        throw new Exception($"A wearable in category {wearableItem.data.category} already exists trying 
 150
 0151                    wearablesByCategory.Add(wearableItem.data.category, wearableItem);
 152                }
 153
 154                // Wearables that are not bodyshape or facialFeatures
 0155                List<WearableItem> wearables = wearablesByCategory.Where(
 156                                                                       x =>
 0157                                                                           x.Key != WearableLiterals.Categories.BODY_SHA
 158                                                                           x.Key != WearableLiterals.Categories.EYES &&
 159                                                                           x.Key != WearableLiterals.Categories.EYEBROWS
 160                                                                           x.Key != WearableLiterals.Categories.MOUTH)
 0161                                                                  .Select(x => x.Value)
 162                                                                  .ToList();
 163
 0164                var bodyWearables = new BodyWearables
 165                (
 166                    wearablesByCategory[WearableLiterals.Categories.BODY_SHAPE],
 167                    wearablesByCategory.TryGetValue(WearableLiterals.Categories.EYES, out WearableItem eyes) ? eyes : nu
 168                    wearablesByCategory.TryGetValue(WearableLiterals.Categories.EYEBROWS, out WearableItem eyebrows) ? e
 169                    wearablesByCategory.TryGetValue(WearableLiterals.Categories.MOUTH, out WearableItem mouth) ? mouth :
 170                );
 171
 0172                return (bodyWearables, wearables, emotes.ToList());
 173            }
 0174            catch (OperationCanceledException)
 175            {
 176                //No Disposing required
 0177                throw;
 178            }
 0179            catch (Exception e)
 180            {
 0181                Debug.Log("Failed curating avatar wearables");
 0182                throw;
 183            }
 0184        }
 185
 186        private async UniTask<WearableItem[]> GetFallbackForMissingNeededCategories(string bodyshapeId, Dictionary<strin
 187        {
 0188            ct.ThrowIfCancellationRequested();
 189
 190            try
 191            {
 0192                List<UniTask<WearableItem>> neededWearablesTasks = new List<UniTask<WearableItem>>();
 193
 0194                foreach (string neededCategory in WearableLiterals.Categories.REQUIRED_CATEGORIES)
 195                {
 196                    // If a needed category is hidden we dont need to fallback, we skipped it on purpose
 0197                    if (hiddenCategories.Contains(neededCategory))
 198                        continue;
 199
 200                    // The needed category is present
 0201                    if (wearablesByCategory.ContainsKey(neededCategory))
 202                        continue;
 203
 0204                    string fallbackWearableId = WearableLiterals.DefaultWearables.GetDefaultWearable(bodyshapeId, needed
 205
 0206                    if (fallbackWearableId == null)
 0207                        throw new Exception($"Couldn't find a fallback wearable for bodyshape: {bodyshapeId} and categor
 208
 0209                    neededWearablesTasks.Add(wearableItemResolver.Resolve(fallbackWearableId, ct));
 210                }
 211
 0212                return await UniTask.WhenAll(neededWearablesTasks).AttachExternalCancellation(ct);
 213            }
 0214            catch (OperationCanceledException)
 215            {
 216                //No disposing required
 0217                throw;
 218            }
 0219        }
 220
 221        public void Dispose()
 222        {
 521223            wearableItemResolver.Dispose();
 521224        }
 225    }
 226}