< Summary

Class:AvatarSystem.Loader
Assembly:AvatarSystemLoaders
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/AvatarSystem/Loader/Loader.cs
Covered lines:20
Uncovered lines:95
Coverable lines:115
Total lines:248
Line coverage:17.3% (20 of 115)
Covered branches:0
Total branches:0
Covered methods:9
Total methods:22
Method coverage:40.9% (9 of 22)

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
Loader(...)0%110100%
Load()0%5522300%
ConstructRequiredFailedWearablesList(...)0%12300%
LoadBodyshape()0%30500%
LoadWearables()0%20400%
GetNewLoaders(...)0%20400%
GetBones()0%12300%
IsValidForBodyShape(...)0%6200%
MergeAvatar()0%20400%
ComposeStatus(...)0%30500%
ClearLoaders()0%3.213071.43%
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;
 52115        public SkinnedMeshRenderer combinedRenderer { get; private set; }
 016        public IReadOnlyList<SkinnedMeshRenderer> originalVisibleRenderers => originalVisibleRenderersValue;
 51817        public List<SkinnedMeshRenderer> originalVisibleRenderersValue { get; private set; } = new ();
 018        public List<Renderer> facialFeaturesRenderers { get; private set; }
 52119        public ILoader.Status status { get; private set; } = ILoader.Status.Idle;
 20
 21        private readonly IWearableLoaderFactory wearableLoaderFactory;
 22        private readonly GameObject container;
 23
 24        internal IBodyshapeLoader bodyshapeLoader;
 51825        internal readonly Dictionary<string, IWearableLoader> loaders = new ();
 26        private readonly IAvatarMeshCombinerHelper avatarMeshCombiner;
 27
 51828        public Loader(IWearableLoaderFactory wearableLoaderFactory, GameObject container, IAvatarMeshCombinerHelper avat
 29        {
 51830            this.wearableLoaderFactory = wearableLoaderFactory;
 51831            this.container = container;
 32
 51833            this.avatarMeshCombiner = avatarMeshCombiner;
 51834            avatarMeshCombiner.prepareMeshForGpuSkinning = true;
 51835            avatarMeshCombiner.uploadMeshToGpu = true;
 51836        }
 37
 38        public async UniTask Load(BodyWearables bodyWearables, List<WearableItem> wearables, AvatarSettings settings, Sk
 39        {
 040            cancellationToken.ThrowIfCancellationRequested();
 41
 042            List<IWearableLoader> toCleanUp = new List<IWearableLoader>();
 43
 44            try
 45            {
 046                status = ILoader.Status.Loading;
 047                await LoadBodyshape(settings, bodyWearables, toCleanUp, cancellationToken);
 048                await LoadWearables(wearables, settings, toCleanUp, cancellationToken);
 049                SkinnedMeshRenderer skinnedContainer = bonesContainer == null ? bodyshapeLoader.upperBodyRenderer : bone
 50
 51                // Update Status accordingly
 052                status = ComposeStatus(loaders);
 53
 054                if (status == ILoader.Status.Failed_Major)
 055                    throw new Exception($"Couldnt load (nor fallback) wearables with required category: {string.Join(", 
 56
 057                foreach (IWearableLoader wearableLoader in loaders.Values) { wearableLoader.SetBones(skinnedContainer.ro
 58
 059                if (bodyshapeLoader.rendereable != null) { bodyshapeLoader.SetBones(skinnedContainer.rootBone, skinnedCo
 60
 061                var activeBodyParts = AvatarSystemUtils.GetActiveBodyPartsRenderers(bodyshapeLoader, settings.bodyshapeI
 062                originalVisibleRenderersValue = activeBodyParts.Union(loaders.Values.SelectMany(x => x.rendereable.rende
 063                combinedRenderer = await MergeAvatar(originalVisibleRenderersValue, skinnedContainer, cancellationToken)
 064                facialFeaturesRenderers = new List<Renderer>();
 65
 066                if (activeBodyParts.Contains(bodyshapeLoader.headRenderer))
 67                {
 068                    if (bodyWearables.Eyes != null)
 69                    {
 070                        facialFeaturesRenderers.Add(bodyshapeLoader.eyesRenderer);
 071                        originalVisibleRenderersValue.Add(bodyshapeLoader.eyesRenderer);
 72                    }
 73
 074                    if (bodyWearables.Eyebrows != null)
 75                    {
 076                        facialFeaturesRenderers.Add(bodyshapeLoader.eyebrowsRenderer);
 077                        originalVisibleRenderersValue.Add(bodyshapeLoader.eyebrowsRenderer);
 78                    }
 79
 080                    if (bodyWearables.Mouth != null)
 81                    {
 082                        facialFeaturesRenderers.Add(bodyshapeLoader.mouthRenderer);
 083                        originalVisibleRenderersValue.Add(bodyshapeLoader.mouthRenderer);
 84                    }
 85                }
 86                else
 87                {
 088                    if (bodyshapeLoader != null)
 089                        bodyshapeLoader.DisableFacialRenderers();
 90                }
 091            }
 092            catch (OperationCanceledException)
 93            {
 094                Dispose();
 095                throw;
 96            }
 097            catch
 98            {
 099                Dispose();
 0100                Debug.Log("Failed Loading avatar");
 0101                throw;
 102            }
 103            finally
 104            {
 0105                for (int i = 0; i < toCleanUp.Count; i++)
 106                {
 0107                    if (toCleanUp[i] == null)
 108                        continue;
 109
 0110                    toCleanUp[i].Dispose();
 111                }
 112            }
 0113        }
 114
 115        private static List<string> ConstructRequiredFailedWearablesList(IEnumerable<IWearableLoader> loaders)
 116        {
 0117            return loaders.Where(x => x.status == IWearableLoader.Status.Failed && AvatarSystemUtils.IsCategoryRequired(
 0118                          .Select(x => x.bodyShape.id)
 119                          .ToList();
 120        }
 121
 122        private async UniTask LoadBodyshape(AvatarSettings settings, BodyWearables bodyWearables, List<IWearableLoader> 
 123        {
 124            //We get a new loader if any of the subparts of the bodyshape changes
 0125            if (!IsValidForBodyShape(bodyWearables))
 126            {
 0127                loadersToCleanUp.Add(bodyshapeLoader);
 0128                bodyshapeLoader = wearableLoaderFactory.GetBodyShapeLoader(bodyWearables);
 129            }
 130
 0131            await bodyshapeLoader.Load(container, settings, ct);
 132
 0133            if (bodyshapeLoader.status == IWearableLoader.Status.Failed)
 134            {
 0135                status = ILoader.Status.Failed_Major;
 0136                throw new Exception($"Couldnt load bodyshape");
 137            }
 0138        }
 139
 140        private async UniTask LoadWearables(List<WearableItem> wearables, AvatarSettings settings, List<IWearableLoader>
 141        {
 0142            (List<IWearableLoader> notReusableLoaders, List<IWearableLoader> newLoaders) = GetNewLoaders(wearables, load
 0143            loadersToCleanUp.AddRange(notReusableLoaders);
 0144            loaders.Clear();
 145
 0146            for (int i = 0; i < newLoaders.Count; i++)
 147            {
 0148                IWearableLoader loader = newLoaders[i];
 0149                loaders.Add(loader.bodyShape.data.category, loader);
 150            }
 151
 0152            await UniTask.WhenAll(loaders.Values.Select(x => x.Load(container, settings, ct)));
 0153        }
 154
 155        internal static (List<IWearableLoader> notReusableLoaders, List<IWearableLoader> newLoaders) GetNewLoaders(List<
 156        {
 157            // Initialize with all loaders and remove from cleaning-up the ones that can be reused
 0158            List<IWearableLoader> notReusableLoaders = new List<IWearableLoader>(currentLoaders.Values);
 0159            List<IWearableLoader> newLoaders = new List<IWearableLoader>();
 160
 0161            for (int i = 0; i < wearables.Count; i++)
 162            {
 0163                WearableItem wearable = wearables[i];
 164
 0165                if (currentLoaders.TryGetValue(wearable.data.category, out IWearableLoader loader))
 166                {
 167                    //We can reuse this loader
 0168                    if (loader.bodyShape.id == wearable.id)
 169                    {
 0170                        newLoaders.Add(loader);
 0171                        notReusableLoaders.Remove(loader);
 0172                        continue;
 173                    }
 174                }
 175
 0176                newLoaders.Add(wearableLoaderFactory.GetWearableLoader(wearable));
 177            }
 178
 0179            return (notReusableLoaders, newLoaders);
 180        }
 181
 182        public Transform[] GetBones() =>
 0183            bodyshapeLoader?.upperBodyRenderer?.bones;
 184
 185        public bool IsValidForBodyShape(BodyWearables bodyWearables) =>
 0186            bodyshapeLoader != null && bodyshapeLoader.IsValid(bodyWearables);
 187
 188        private async UniTask<SkinnedMeshRenderer> MergeAvatar(List<SkinnedMeshRenderer> renderers, SkinnedMeshRenderer 
 189            CancellationToken ct)
 190        {
 191            // AvatarMeshCombiner is a bit buggy when performing the combine of the same meshes on the same frame,
 192            // once that's fixed we can remove this wait
 193            // AttachExternalCancellation is needed because cancellation will take a wait to trigger
 0194            await UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate, ct).AttachExternalCancellation(ct);
 0195            avatarMeshCombiner.useCullOpaqueHeuristic = true;
 0196            avatarMeshCombiner.enableCombinedMesh = false;
 0197            bool success = avatarMeshCombiner.Combine(bonesContainer, renderers);
 198
 0199            if (!success)
 200            {
 0201                status = ILoader.Status.Failed_Major;
 0202                throw new Exception("Couldnt merge avatar");
 203            }
 204
 0205            avatarMeshCombiner.container.transform.SetParent(container.transform, true);
 0206            avatarMeshCombiner.container.transform.localPosition = Vector3.zero;
 0207            avatarMeshCombiner.container.transform.localScale = Vector3.one;
 0208            return avatarMeshCombiner.renderer;
 0209        }
 210
 211        internal static ILoader.Status ComposeStatus(Dictionary<string, IWearableLoader> loaders)
 212        {
 0213            ILoader.Status composedStatus = ILoader.Status.Succeeded;
 214
 0215            foreach ((string category, IWearableLoader loader) in loaders)
 216            {
 0217                if (loader.status == IWearableLoader.Status.Defaulted)
 0218                    composedStatus = ILoader.Status.Failed_Minor;
 0219                else if (loader.status == IWearableLoader.Status.Failed)
 220                {
 0221                    if (AvatarSystemUtils.IsCategoryRequired(category))
 0222                        return ILoader.Status.Failed_Major;
 223
 0224                    composedStatus = ILoader.Status.Failed_Minor;
 225                }
 226            }
 227
 0228            return composedStatus;
 0229        }
 230
 231        private void ClearLoaders()
 232        {
 521233            bodyshapeLoader?.Dispose();
 234
 1042235            foreach (IWearableLoader wearableLoader in loaders.Values) { wearableLoader.Dispose(); }
 236
 521237            loaders.Clear();
 521238        }
 239
 240        public void Dispose()
 241        {
 521242            avatarMeshCombiner.Dispose();
 521243            combinedRenderer = null;
 521244            status = ILoader.Status.Idle;
 521245            ClearLoaders();
 521246        }
 247    }
 248}