< Summary

Class:AvatarSystem.Loader
Assembly:AvatarSystem
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/AvatarSystem/Loader/Loader.cs
Covered lines:105
Uncovered lines:11
Coverable lines:116
Total lines:242
Line coverage:90.5% (105 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.3922082.98%
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;
 69015        public SkinnedMeshRenderer combinedRenderer { get; private set; }
 1716        public List<Renderer> facialFeaturesRenderers { get; private set; }
 70517        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;
 67523        internal readonly Dictionary<string, IWearableLoader> loaders = new Dictionary<string, IWearableLoader>();
 24        private readonly IAvatarMeshCombinerHelper avatarMeshCombiner;
 25
 67526        public Loader(IWearableLoaderFactory wearableLoaderFactory, GameObject container, IAvatarMeshCombinerHelper avat
 27        {
 67528            this.wearableLoaderFactory = wearableLoaderFactory;
 67529            this.container = container;
 30
 67531            this.avatarMeshCombiner = avatarMeshCombiner;
 67532            avatarMeshCombiner.prepareMeshForGpuSkinning = true;
 67533            avatarMeshCombiner.uploadMeshToGpu = true;
 67534        }
 35
 36        public async UniTask Load(WearableItem bodyshape, WearableItem eyes, WearableItem eyebrows, WearableItem mouth,
 37            List<WearableItem> wearables, AvatarSettings settings, CancellationToken ct = default)
 38        {
 039            await Load(bodyshape, eyes, eyebrows, mouth, wearables, settings, null, ct);
 040        }
 41
 42        public async UniTask Load(WearableItem bodyshape, WearableItem eyes, WearableItem eyebrows, WearableItem mouth, 
 43        {
 744            ct.ThrowIfCancellationRequested();
 45
 746            List<IWearableLoader> toCleanUp = new List<IWearableLoader>();
 47            try
 48            {
 749                status = ILoader.Status.Loading;
 750                await LoadBodyshape(settings, bodyshape, eyes, eyebrows, mouth, toCleanUp, ct);
 651                await LoadWearables(wearables, settings, toCleanUp, ct);
 652                SkinnedMeshRenderer skinnedContainer = bonesContainer == null ? bodyshapeLoader.upperBodyRenderer : bone
 53                // Update Status accordingly
 654                status = ComposeStatus(loaders);
 655                if (status == ILoader.Status.Failed_Major)
 156                    throw new Exception($"Couldnt load (nor fallback) wearables with required category: {string.Join(", 
 57
 58
 5259                foreach (IWearableLoader wearableLoader in loaders.Values)
 60                {
 2161                    wearableLoader.SetBones(skinnedContainer.rootBone, skinnedContainer.bones);
 62                }
 63
 564                if (bodyshapeLoader.rendereable != null)
 65                {
 466                    bodyshapeLoader.SetBones(skinnedContainer.rootBone, skinnedContainer.bones);
 67                }
 68
 569                (bool headVisible, bool upperBodyVisible, bool lowerBodyVisible, bool feetVisible) = AvatarSystemUtils.G
 70
 1571                combinedRenderer = await MergeAvatar(settings, wearables, headVisible, upperBodyVisible, lowerBodyVisibl
 572                facialFeaturesRenderers = new List<Renderer>();
 573                if (headVisible)
 74                {
 475                    if (eyes != null)
 476                        facialFeaturesRenderers.Add(bodyshapeLoader.eyesRenderer);
 477                    if (eyebrows != null)
 478                        facialFeaturesRenderers.Add(bodyshapeLoader.eyebrowsRenderer);
 479                    if (mouth != null)
 480                        facialFeaturesRenderers.Add(bodyshapeLoader.mouthRenderer);
 81                }
 82                else
 83                {
 184                    if(bodyshapeLoader != null)
 185                        bodyshapeLoader.DisableFacialRenderers();
 86                }
 587            }
 088            catch (OperationCanceledException)
 89            {
 090                Dispose();
 091                throw;
 92            }
 293            catch
 94            {
 295                Dispose();
 296                Debug.Log("Failed Loading avatar");
 297                throw;
 98            }
 99            finally
 100            {
 28101                for (int i = 0; i < toCleanUp.Count; i++)
 102                {
 7103                    if (toCleanUp[i] == null)
 104                        continue;
 0105                    toCleanUp[i].Dispose();
 106                }
 107            }
 5108        }
 109
 110        private static List<string> ConstructRequiredFailedWearablesList(IEnumerable<IWearableLoader> loaders)
 111        {
 6112            return loaders.Where(x => x.status == IWearableLoader.Status.Failed && AvatarSystemUtils.IsCategoryRequired(
 2113                          .Select(x => x.wearable.id)
 114                          .ToList();
 115        }
 116
 117        private async UniTask LoadBodyshape(AvatarSettings settings, WearableItem bodyshape, WearableItem eyes, Wearable
 118        {
 119            //We get a new loader if any of the subparts of the bodyshape changes
 7120            if (!IsValidForBodyShape(bodyshape, eyes, eyebrows, mouth))
 121            {
 7122                loadersToCleanUp.Add(bodyshapeLoader);
 7123                bodyshapeLoader = wearableLoaderFactory.GetBodyshapeLoader(bodyshape, eyes, eyebrows, mouth);
 124            }
 125
 7126            await bodyshapeLoader.Load(container, settings, ct);
 127
 7128            if (bodyshapeLoader.status == IWearableLoader.Status.Failed)
 129            {
 1130                status = ILoader.Status.Failed_Major;
 1131                throw new Exception($"Couldnt load bodyshape");
 132            }
 6133        }
 134
 135        private async UniTask LoadWearables(List<WearableItem> wearables, AvatarSettings settings, List<IWearableLoader>
 136        {
 6137            (List<IWearableLoader> notReusableLoaders, List<IWearableLoader> newLoaders) = GetNewLoaders(wearables, load
 6138            loadersToCleanUp.AddRange(notReusableLoaders);
 6139            loaders.Clear();
 64140            for (int i = 0; i < newLoaders.Count; i++)
 141            {
 26142                IWearableLoader loader = newLoaders[i];
 26143                loaders.Add(loader.wearable.data.category, loader);
 144            }
 145
 32146            await UniTask.WhenAll(loaders.Values.Select(x => x.Load(container, settings, ct)));
 6147        }
 148
 149        internal static (List<IWearableLoader> notReusableLoaders, List<IWearableLoader> newLoaders) GetNewLoaders(List<
 150        {
 151            // Initialize with all loaders and remove from cleaning-up the ones that can be reused
 9152            List<IWearableLoader> notReusableLoaders = new List<IWearableLoader>(currentLoaders.Values);
 9153            List<IWearableLoader> newLoaders = new List<IWearableLoader>();
 154
 94155            for (int i = 0; i < wearables.Count; i++)
 156            {
 38157                WearableItem wearable = wearables[i];
 158
 38159                if (currentLoaders.TryGetValue(wearable.data.category, out IWearableLoader loader))
 160                {
 161                    //We can reuse this loader
 4162                    if (loader.wearable.id == wearable.id)
 163                    {
 3164                        newLoaders.Add(loader);
 3165                        notReusableLoaders.Remove(loader);
 3166                        continue;
 167                    }
 168                }
 35169                newLoaders.Add(wearableLoaderFactory.GetWearableLoader(wearable));
 170            }
 171
 9172            return (notReusableLoaders, newLoaders);
 173        }
 174
 0175        public Transform[] GetBones() { return bodyshapeLoader?.upperBodyRenderer?.bones; }
 176
 177        public bool IsValidForBodyShape(WearableItem bodyshape, WearableItem eyes, WearableItem eyebrows, WearableItem m
 178        {
 7179            return bodyshapeLoader != null && bodyshapeLoader.IsValid(bodyshape, eyebrows, eyes, mouth);
 180        }
 181
 182        private async UniTask<SkinnedMeshRenderer> MergeAvatar(AvatarSettings settings, List<WearableItem> wearables,
 183            bool headVisible, bool upperBodyVisible, bool lowerBodyVisible, bool feetVisible, SkinnedMeshRenderer bonesC
 184            CancellationToken ct)
 185        {
 5186            var activeBodyParts = AvatarSystemUtils.GetActiveBodyPartsRenderers(bodyshapeLoader, headVisible, upperBodyV
 26187            IEnumerable<SkinnedMeshRenderer> allRenderers = activeBodyParts.Union(loaders.Values.SelectMany(x => x.rende
 188
 189            // AvatarMeshCombiner is a bit buggy when performing the combine of the same meshes on the same frame,
 190            // once that's fixed we can remove this wait
 191            // AttachExternalCancellation is needed because cancellation will take a wait to trigger
 15192            await UniTask.WaitForEndOfFrame(ct).AttachExternalCancellation(ct);
 5193            var featureFlags = DataStore.i.featureFlags.flags.Get();
 5194            avatarMeshCombiner.useCullOpaqueHeuristic = true;
 5195            avatarMeshCombiner.enableCombinedMesh = false;
 5196            bool success = avatarMeshCombiner.Combine(bonesContainer, allRenderers.ToArray());
 5197            if (!success)
 198            {
 0199                status = ILoader.Status.Failed_Major;
 0200                throw new Exception("Couldnt merge avatar");
 201            }
 5202            avatarMeshCombiner.container.transform.SetParent(container.transform, true);
 5203            avatarMeshCombiner.container.transform.localPosition = Vector3.zero;
 5204            return avatarMeshCombiner.renderer;
 5205        }
 206
 207        internal static ILoader.Status ComposeStatus(Dictionary<string, IWearableLoader> loaders)
 208        {
 9209            ILoader.Status composedStatus = ILoader.Status.Succeeded;
 78210            foreach ((string category, IWearableLoader loader) in loaders)
 211            {
 31212                if (loader.status == IWearableLoader.Status.Defaulted)
 0213                    composedStatus = ILoader.Status.Failed_Minor;
 31214                else if (loader.status == IWearableLoader.Status.Failed)
 215                {
 6216                    if (AvatarSystemUtils.IsCategoryRequired(category))
 2217                        return ILoader.Status.Failed_Major;
 4218                    composedStatus = ILoader.Status.Failed_Minor;
 219                }
 220            }
 7221            return composedStatus;
 2222        }
 223
 224        private void ClearLoaders()
 225        {
 683226            bodyshapeLoader?.Dispose();
 1418227            foreach (IWearableLoader wearableLoader in loaders.Values)
 228            {
 26229                wearableLoader.Dispose();
 230            }
 683231            loaders.Clear();
 683232        }
 233
 234        public void Dispose()
 235        {
 683236            avatarMeshCombiner.Dispose();
 683237            combinedRenderer = null;
 683238            status = ILoader.Status.Idle;
 683239            ClearLoaders();
 683240        }
 241    }
 242}