< Summary

Class:AvatarSystem.Loader
Assembly:AvatarSystem
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/AvatarSystem/Loader/Loader.cs
Covered lines:98
Uncovered lines:8
Coverable lines:106
Total lines:223
Line coverage:92.4% (98 of 106)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
Loader(...)0%110100%
Load()0%17.0417094.87%
ConstructRequiredFailedWearablesList(...)0%330100%
LoadBodyshape()0%660100%
LoadWearables()0%4.074083.33%
GetNewLoaders(...)0%440100%
GetBones()0%12300%
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;
 131423        internal readonly Dictionary<string, IWearableLoader> loaders = new Dictionary<string, IWearableLoader>();
 24        private readonly IAvatarMeshCombinerHelper avatarMeshCombiner;
 25
 131426        public Loader(IWearableLoaderFactory wearableLoaderFactory, GameObject container, IAvatarMeshCombinerHelper avat
 27        {
 131428            this.wearableLoaderFactory = wearableLoaderFactory;
 131429            this.container = container;
 30
 131431            this.avatarMeshCombiner = avatarMeshCombiner;
 131432            avatarMeshCombiner.prepareMeshForGpuSkinning = true;
 131433            avatarMeshCombiner.uploadMeshToGpu = true;
 131434        }
 35
 36        public async UniTask Load(WearableItem bodyshape, WearableItem eyes, WearableItem eyebrows, WearableItem mouth, 
 37        {
 8038            ct.ThrowIfCancellationRequested();
 39
 8040            List<IWearableLoader> toCleanUp = new List<IWearableLoader>();
 41            try
 42            {
 8043                status = ILoader.Status.Loading;
 44
 23245                await LoadBodyshape(settings, bodyshape, eyes, eyebrows, mouth, toCleanUp, ct);
 46
 347                await LoadWearables(wearables, settings, toCleanUp, ct);
 48
 49                // Update Status accordingly
 350                status = ComposeStatus(loaders);
 351                if (status == ILoader.Status.Failed_Major)
 152                    throw new Exception($"Couldnt load (nor fallback) wearables with required category: {string.Join(", 
 53
 1254                AvatarSystemUtils.CopyBones(bodyshapeLoader.upperBodyRenderer, loaders.Values.SelectMany(x => x.renderea
 255                (bool headVisible, bool upperBodyVisible, bool lowerBodyVisible, bool feetVisible) = AvatarSystemUtils.G
 56
 657                combinedRenderer = await MergeAvatar(settings, wearables, headVisible, upperBodyVisible, lowerBodyVisibl
 58
 259                facialFeaturesRenderers = new List<Renderer>();
 60
 261                if (headVisible)
 62                {
 263                    if (eyes != null)
 264                        facialFeaturesRenderers.Add(bodyshapeLoader.eyesRenderer);
 265                    if (eyebrows != null)
 266                        facialFeaturesRenderers.Add(bodyshapeLoader.eyebrowsRenderer);
 267                    if (mouth != null)
 268                        facialFeaturesRenderers.Add(bodyshapeLoader.mouthRenderer);
 69                }
 270            }
 7671            catch (OperationCanceledException)
 72            {
 7673                Dispose();
 7674                throw;
 75            }
 276            catch
 77            {
 278                Dispose();
 279                Debug.Log("Failed Loading avatar");
 280                throw;
 81            }
 82            finally
 83            {
 28684                for (int i = 0; i < toCleanUp.Count; i++)
 85                {
 6386                    if (toCleanUp[i] == null)
 87                        continue;
 1488                    toCleanUp[i].Dispose();
 89                }
 90            }
 291        }
 92
 93        private static List<string> ConstructRequiredFailedWearablesList(IEnumerable<IWearableLoader> loaders)
 94        {
 695            return loaders.Where(x => x.status == IWearableLoader.Status.Failed && AvatarSystemUtils.IsCategoryRequired(
 296                          .Select(x => x.wearable.id)
 97                          .ToList();
 98        }
 99
 100        private async UniTask LoadBodyshape(AvatarSettings settings, WearableItem bodyshape, WearableItem eyes, Wearable
 101        {
 102            //We get a new loader if any of the subparts of the bodyshape changes
 80103            if (bodyshapeLoader == null || !bodyshapeLoader.IsValid(bodyshape, eyebrows, eyes, mouth))
 104            {
 63105                loadersToCleanUp.Add(bodyshapeLoader);
 63106                bodyshapeLoader = wearableLoaderFactory.GetBodyshapeLoader(bodyshape, eyes, eyebrows, mouth);
 107            }
 108
 232109            await bodyshapeLoader.Load(container, settings, ct);
 110
 4111            if (bodyshapeLoader.status == IWearableLoader.Status.Failed)
 112            {
 1113                status = ILoader.Status.Failed_Major;
 1114                throw new Exception($"Couldnt load bodyshape");
 115            }
 3116        }
 117
 118        private async UniTask LoadWearables(List<WearableItem> wearables, AvatarSettings settings, List<IWearableLoader>
 119        {
 3120            (List<IWearableLoader> notReusableLoaders, List<IWearableLoader> newLoaders) = GetNewLoaders(wearables, load
 3121            loadersToCleanUp.AddRange(notReusableLoaders);
 3122            loaders.Clear();
 36123            for (int i = 0; i < newLoaders.Count; i++)
 124            {
 15125                IWearableLoader loader = newLoaders[i];
 15126                loaders.Add(loader.wearable.data.category, loader);
 127            }
 128
 18129            await UniTask.WhenAll(loaders.Values.Select(x => x.Load(container, settings, ct)));
 3130        }
 131
 132        internal static (List<IWearableLoader> notReusableLoaders, List<IWearableLoader> newLoaders) GetNewLoaders(List<
 133        {
 134            // Initialize with all loaders and remove from cleaning-up the ones that can be reused
 6135            List<IWearableLoader> notReusableLoaders = new List<IWearableLoader>(currentLoaders.Values);
 6136            List<IWearableLoader> newLoaders = new List<IWearableLoader>();
 137
 66138            for (int i = 0; i < wearables.Count; i++)
 139            {
 27140                WearableItem wearable = wearables[i];
 141
 27142                if (currentLoaders.TryGetValue(wearable.data.category, out IWearableLoader loader))
 143                {
 144                    //We can reuse this loader
 4145                    if (loader.wearable.id == wearable.id)
 146                    {
 3147                        newLoaders.Add(loader);
 3148                        notReusableLoaders.Remove(loader);
 3149                        continue;
 150                    }
 151                }
 24152                newLoaders.Add(wearableLoaderFactory.GetWearableLoader(wearable));
 153            }
 154
 6155            return (notReusableLoaders, newLoaders);
 156        }
 157
 0158        public Transform[] GetBones() { return bodyshapeLoader?.upperBodyRenderer?.bones; }
 159
 160        private async UniTask<SkinnedMeshRenderer> MergeAvatar(AvatarSettings settings, List<WearableItem> wearables,
 161            bool headVisible, bool upperBodyVisible, bool lowerBodyVisible, bool feetVisible,
 162            CancellationToken ct)
 163        {
 2164            var activeBodyParts = AvatarSystemUtils.GetActiveBodyPartsRenderers(bodyshapeLoader, headVisible, upperBodyV
 12165            IEnumerable<SkinnedMeshRenderer> allRenderers = activeBodyParts.Union(loaders.Values.SelectMany(x => x.rende
 166
 167            // AvatarMeshCombiner is a bit buggy when performing the combine of the same meshes on the same frame,
 168            // once that's fixed we can remove this wait
 169            // AttachExternalCancellation is needed because cancellation will take a wait to trigger
 6170            await UniTask.WaitForEndOfFrame(ct).AttachExternalCancellation(ct);
 171
 2172            var featureFlags = DataStore.i.featureFlags.flags.Get();
 2173            avatarMeshCombiner.useCullOpaqueHeuristic = featureFlags.IsFeatureEnabled("cull-opaque-heuristic");
 2174            avatarMeshCombiner.enableCombinedMesh = false;
 175
 2176            bool success = avatarMeshCombiner.Combine(bodyshapeLoader.upperBodyRenderer, allRenderers.ToArray());
 2177            if (!success)
 178            {
 0179                status = ILoader.Status.Failed_Major;
 0180                throw new Exception("Couldnt merge avatar");
 181            }
 182
 2183            avatarMeshCombiner.container.transform.SetParent(container.transform, true);
 2184            avatarMeshCombiner.container.transform.localPosition = Vector3.zero;
 185
 2186            return avatarMeshCombiner.renderer;
 2187        }
 188
 189        internal static ILoader.Status ComposeStatus(Dictionary<string, IWearableLoader> loaders)
 190        {
 6191            ILoader.Status composedStatus = ILoader.Status.Succeeded;
 50192            foreach ((string category, IWearableLoader loader) in loaders)
 193            {
 20194                if (loader.status == IWearableLoader.Status.Defaulted)
 0195                    composedStatus = ILoader.Status.Failed_Minor;
 20196                else if (loader.status == IWearableLoader.Status.Failed)
 197                {
 6198                    if (AvatarSystemUtils.IsCategoryRequired(category))
 2199                        return ILoader.Status.Failed_Major;
 4200                    composedStatus = ILoader.Status.Failed_Minor;
 201                }
 202            }
 4203            return composedStatus;
 2204        }
 205
 206        private void ClearLoaders()
 207        {
 1493208            bodyshapeLoader?.Dispose();
 3016209            foreach (IWearableLoader wearableLoader in loaders.Values)
 210            {
 15211                wearableLoader.Dispose();
 212            }
 1493213            loaders.Clear();
 1493214        }
 215
 216        public void Dispose()
 217        {
 1493218            avatarMeshCombiner.Dispose();
 1493219            status = ILoader.Status.Idle;
 1493220            ClearLoaders();
 1493221        }
 222    }
 223}