< Summary

Class:AvatarSystem.Loader
Assembly:AvatarSystem
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/AvatarSystem/Loader/Loader.cs
Covered lines:102
Uncovered lines:14
Coverable lines:116
Total lines:240
Line coverage:87.9% (102 of 116)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
Loader(...)0%110100%
Load()0%12300%
Load()0%24.2422083.33%
ConstructRequiredFailedWearablesList(...)0%330100%
LoadBodyshape()0%5.25080%
LoadWearables()0%4.074083.33%
GetNewLoaders(...)0%440100%
GetBones()0%12300%
IsValidForBodyShape(...)0%220100%
MergeAvatar()0%5.055087.5%
ComposeStatus(...)0%5.015091.67%
ClearLoaders()0%330100%
Dispose()0%110100%

File(s)

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

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Linq;
 4using System.Threading;
 5using Cysharp.Threading.Tasks;
 6using DCL;
 7using DCL.Helpers;
 8using UnityEngine;
 9
 10namespace AvatarSystem
 11{
 12    public class Loader : ILoader
 13    {
 014        public GameObject bodyshapeContainer => bodyshapeLoader?.rendereable?.container;
 015        public SkinnedMeshRenderer combinedRenderer { get; private set; }
 016        public List<Renderer> facialFeaturesRenderers { get; private set; }
 017        public ILoader.Status status { get; private set; } = ILoader.Status.Idle;
 18
 19        private readonly IWearableLoaderFactory wearableLoaderFactory;
 20        private readonly GameObject container;
 21
 22        internal IBodyshapeLoader bodyshapeLoader;
 102823        internal readonly Dictionary<string, IWearableLoader> loaders = new Dictionary<string, IWearableLoader>();
 24        private readonly IAvatarMeshCombinerHelper avatarMeshCombiner;
 25
 102826        public Loader(IWearableLoaderFactory wearableLoaderFactory, GameObject container, IAvatarMeshCombinerHelper avat
 27        {
 102828            this.wearableLoaderFactory = wearableLoaderFactory;
 102829            this.container = container;
 30
 102831            this.avatarMeshCombiner = avatarMeshCombiner;
 102832            avatarMeshCombiner.prepareMeshForGpuSkinning = true;
 102833            avatarMeshCombiner.uploadMeshToGpu = true;
 102834        }
 35
 36        public async UniTask Load(WearableItem bodyshape, WearableItem eyes, WearableItem eyebrows, WearableItem mouth, 
 37        {
 038            await Load(bodyshape, eyes, eyebrows, mouth, wearables, settings, null, ct);
 039        }
 40
 41        public async UniTask Load(WearableItem bodyshape, WearableItem eyes, WearableItem eyebrows, WearableItem mouth, 
 42        {
 743            ct.ThrowIfCancellationRequested();
 44
 745            List<IWearableLoader> toCleanUp = new List<IWearableLoader>();
 46            try
 47            {
 748                status = ILoader.Status.Loading;
 749                await LoadBodyshape(settings, bodyshape, eyes, eyebrows, mouth, toCleanUp, ct);
 650                await LoadWearables(wearables, settings, toCleanUp, ct);
 651                SkinnedMeshRenderer skinnedContainer = bonesContainer == null ? bodyshapeLoader.upperBodyRenderer : bone
 52                // Update Status accordingly
 653                status = ComposeStatus(loaders);
 654                if (status == ILoader.Status.Failed_Major)
 155                    throw new Exception($"Couldnt load (nor fallback) wearables with required category: {string.Join(", 
 56
 57
 5258                foreach (IWearableLoader wearableLoader in loaders.Values)
 59                {
 2160                    wearableLoader.SetBones(skinnedContainer.rootBone, skinnedContainer.bones);
 61                }
 62
 563                if (bodyshapeLoader.rendereable != null)
 64                {
 465                    bodyshapeLoader.SetBones(skinnedContainer.rootBone, skinnedContainer.bones);
 66                }
 67
 568                (bool headVisible, bool upperBodyVisible, bool lowerBodyVisible, bool feetVisible) = AvatarSystemUtils.G
 69
 1570                combinedRenderer = await MergeAvatar(settings, wearables, headVisible, upperBodyVisible, lowerBodyVisibl
 571                facialFeaturesRenderers = new List<Renderer>();
 572                if (headVisible)
 73                {
 474                    if (eyes != null)
 475                        facialFeaturesRenderers.Add(bodyshapeLoader.eyesRenderer);
 476                    if (eyebrows != null)
 477                        facialFeaturesRenderers.Add(bodyshapeLoader.eyebrowsRenderer);
 478                    if (mouth != null)
 479                        facialFeaturesRenderers.Add(bodyshapeLoader.mouthRenderer);
 480                }
 81                else
 82                {
 183                    if(bodyshapeLoader != null)
 184                        bodyshapeLoader.DisableFacialRenderers();
 85                }
 586            }
 087            catch (OperationCanceledException)
 88            {
 089                Dispose();
 090                throw;
 91            }
 292            catch
 93            {
 294                Dispose();
 295                Debug.Log("Failed Loading avatar");
 296                throw;
 97            }
 98            finally
 99            {
 28100                for (int i = 0; i < toCleanUp.Count; i++)
 101                {
 7102                    if (toCleanUp[i] == null)
 103                        continue;
 0104                    toCleanUp[i].Dispose();
 105                }
 106            }
 5107        }
 108
 109        private static List<string> ConstructRequiredFailedWearablesList(IEnumerable<IWearableLoader> loaders)
 110        {
 6111            return loaders.Where(x => x.status == IWearableLoader.Status.Failed && AvatarSystemUtils.IsCategoryRequired(
 2112                          .Select(x => x.wearable.id)
 113                          .ToList();
 114        }
 115
 116        private async UniTask LoadBodyshape(AvatarSettings settings, WearableItem bodyshape, WearableItem eyes, Wearable
 117        {
 118            //We get a new loader if any of the subparts of the bodyshape changes
 7119            if (!IsValidForBodyShape(bodyshape, eyes, eyebrows, mouth))
 120            {
 7121                loadersToCleanUp.Add(bodyshapeLoader);
 7122                bodyshapeLoader = wearableLoaderFactory.GetBodyshapeLoader(bodyshape, eyes, eyebrows, mouth);
 123            }
 124
 7125            await bodyshapeLoader.Load(container, settings, ct);
 126
 7127            if (bodyshapeLoader.status == IWearableLoader.Status.Failed)
 128            {
 1129                status = ILoader.Status.Failed_Major;
 1130                throw new Exception($"Couldnt load bodyshape");
 131            }
 6132        }
 133
 134        private async UniTask LoadWearables(List<WearableItem> wearables, AvatarSettings settings, List<IWearableLoader>
 135        {
 6136            (List<IWearableLoader> notReusableLoaders, List<IWearableLoader> newLoaders) = GetNewLoaders(wearables, load
 6137            loadersToCleanUp.AddRange(notReusableLoaders);
 6138            loaders.Clear();
 64139            for (int i = 0; i < newLoaders.Count; i++)
 140            {
 26141                IWearableLoader loader = newLoaders[i];
 26142                loaders.Add(loader.wearable.data.category, loader);
 143            }
 144
 32145            await UniTask.WhenAll(loaders.Values.Select(x => x.Load(container, settings, ct)));
 6146        }
 147
 148        internal static (List<IWearableLoader> notReusableLoaders, List<IWearableLoader> newLoaders) GetNewLoaders(List<
 149        {
 150            // Initialize with all loaders and remove from cleaning-up the ones that can be reused
 9151            List<IWearableLoader> notReusableLoaders = new List<IWearableLoader>(currentLoaders.Values);
 9152            List<IWearableLoader> newLoaders = new List<IWearableLoader>();
 153
 94154            for (int i = 0; i < wearables.Count; i++)
 155            {
 38156                WearableItem wearable = wearables[i];
 157
 38158                if (currentLoaders.TryGetValue(wearable.data.category, out IWearableLoader loader))
 159                {
 160                    //We can reuse this loader
 4161                    if (loader.wearable.id == wearable.id)
 162                    {
 3163                        newLoaders.Add(loader);
 3164                        notReusableLoaders.Remove(loader);
 3165                        continue;
 166                    }
 167                }
 35168                newLoaders.Add(wearableLoaderFactory.GetWearableLoader(wearable));
 169            }
 170
 9171            return (notReusableLoaders, newLoaders);
 172        }
 173
 0174        public Transform[] GetBones() { return bodyshapeLoader?.upperBodyRenderer?.bones; }
 175
 176        public bool IsValidForBodyShape(WearableItem bodyshape, WearableItem eyes, WearableItem eyebrows, WearableItem m
 177        {
 7178            return bodyshapeLoader != null && bodyshapeLoader.IsValid(bodyshape, eyebrows, eyes, mouth);
 179        }
 180
 181        private async UniTask<SkinnedMeshRenderer> MergeAvatar(AvatarSettings settings, List<WearableItem> wearables,
 182            bool headVisible, bool upperBodyVisible, bool lowerBodyVisible, bool feetVisible, SkinnedMeshRenderer bonesC
 183            CancellationToken ct)
 184        {
 5185            var activeBodyParts = AvatarSystemUtils.GetActiveBodyPartsRenderers(bodyshapeLoader, headVisible, upperBodyV
 26186            IEnumerable<SkinnedMeshRenderer> allRenderers = activeBodyParts.Union(loaders.Values.SelectMany(x => x.rende
 187
 188            // AvatarMeshCombiner is a bit buggy when performing the combine of the same meshes on the same frame,
 189            // once that's fixed we can remove this wait
 190            // AttachExternalCancellation is needed because cancellation will take a wait to trigger
 15191            await UniTask.WaitForEndOfFrame(ct).AttachExternalCancellation(ct);
 5192            var featureFlags = DataStore.i.featureFlags.flags.Get();
 5193            avatarMeshCombiner.useCullOpaqueHeuristic = true;
 5194            avatarMeshCombiner.enableCombinedMesh = false;
 5195            bool success = avatarMeshCombiner.Combine(bonesContainer, allRenderers.ToArray());
 5196            if (!success)
 197            {
 0198                status = ILoader.Status.Failed_Major;
 0199                throw new Exception("Couldnt merge avatar");
 200            }
 5201            avatarMeshCombiner.container.transform.SetParent(container.transform, true);
 5202            avatarMeshCombiner.container.transform.localPosition = Vector3.zero;
 5203            return avatarMeshCombiner.renderer;
 5204        }
 205
 206        internal static ILoader.Status ComposeStatus(Dictionary<string, IWearableLoader> loaders)
 207        {
 9208            ILoader.Status composedStatus = ILoader.Status.Succeeded;
 78209            foreach ((string category, IWearableLoader loader) in loaders)
 210            {
 31211                if (loader.status == IWearableLoader.Status.Defaulted)
 0212                    composedStatus = ILoader.Status.Failed_Minor;
 31213                else if (loader.status == IWearableLoader.Status.Failed)
 214                {
 6215                    if (AvatarSystemUtils.IsCategoryRequired(category))
 2216                        return ILoader.Status.Failed_Major;
 4217                    composedStatus = ILoader.Status.Failed_Minor;
 218                }
 219            }
 7220            return composedStatus;
 2221        }
 222
 223        private void ClearLoaders()
 224        {
 1047225            bodyshapeLoader?.Dispose();
 2146226            foreach (IWearableLoader wearableLoader in loaders.Values)
 227            {
 26228                wearableLoader.Dispose();
 229            }
 1047230            loaders.Clear();
 1047231        }
 232
 233        public void Dispose()
 234        {
 1047235            avatarMeshCombiner.Dispose();
 1047236            status = ILoader.Status.Idle;
 1047237            ClearLoaders();
 1047238        }
 239    }
 240}