< Summary

Class:DCL.AvatarRenderer
Assembly:AvatarShape
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Avatar/AvatarRenderer.cs
Covered lines:62
Uncovered lines:180
Coverable lines:242
Total lines:507
Line coverage:25.6% (62 of 242)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
AvatarRenderer()0%110100%
Awake()0%110100%
ApplyModel(...)0%7.327081.25%
StopLoadingCoroutines()0%220100%
CleanupAvatar()0%77095.45%
CleanUpUnusedItems()0%30500%
LoadAvatar()0%2722.715605.26%
OnWearableLoadingSuccess(...)0%12300%
OnBodyShapeLoadingFail(...)0%42600%
OnWearableLoadingFail(...)0%56700%
SetWearableBones()0%6200%
UpdateExpressions(...)0%110100%
AddWearableController(...)0%56700%
UpdateWearableController(...)0%56700%
CopyFrom(...)0%2100%
SetVisibility(...)0%220100%
HideAll()0%6200%
OnDestroy()0%110100%

File(s)

/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Avatar/AvatarRenderer.cs

#LineLine coverage
 1using System;
 2using System.Collections;
 3using System.Collections.Generic;
 4using System.Linq;
 5using UnityEngine;
 6using static WearableLiterals;
 7
 8namespace DCL
 9{
 10    public class AvatarRenderer : MonoBehaviour
 11    {
 12        public enum VisualCue
 13        {
 14            CleanedUp,
 15            Loaded
 16        }
 17
 18        private const int MAX_RETRIES = 5;
 19
 20        public Material defaultMaterial;
 21        public Material eyeMaterial;
 22        public Material eyebrowMaterial;
 23        public Material mouthMaterial;
 24
 25        private AvatarModel model;
 26
 27        public event Action<VisualCue> OnVisualCue;
 28        public event Action OnSuccessEvent;
 29        public event Action<bool> OnFailEvent;
 30
 31        internal BodyShapeController bodyShapeController;
 89232        internal Dictionary<WearableItem, WearableController> wearableControllers = new Dictionary<WearableItem, Wearabl
 33        internal FacialFeatureController eyesController;
 34        internal FacialFeatureController eyebrowsController;
 35        internal FacialFeatureController mouthController;
 36        internal AvatarAnimatorLegacy animator;
 37        internal StickersController stickersController;
 38
 89239        private long lastStickerTimestamp = -1;
 40
 41        internal bool isLoading = false;
 42
 43        private Coroutine loadCoroutine;
 89244        private List<string> wearablesInUse = new List<string>();
 45
 46        private void Awake()
 47        {
 16748            animator = GetComponent<AvatarAnimatorLegacy>();
 16749            stickersController = GetComponent<StickersController>();
 16750        }
 51
 52        public void ApplyModel(AvatarModel model, Action onSuccess, Action onFail)
 53        {
 12354            if (this.model != null && model != null && this.model.Equals(model))
 55            {
 4556                onSuccess?.Invoke();
 3357                return;
 58            }
 59
 7860            this.model = new AvatarModel();
 7861            this.model.CopyFrom(model);
 62
 63            void onSuccessWrapper()
 64            {
 065                onSuccess?.Invoke();
 066                this.OnSuccessEvent -= onSuccessWrapper;
 067            }
 68
 7869            this.OnSuccessEvent += onSuccessWrapper;
 70
 71            void onFailWrapper(bool isFatalError)
 72            {
 573                onFail?.Invoke();
 574                this.OnFailEvent -= onFailWrapper;
 575            }
 76
 7877            this.OnFailEvent += onFailWrapper;
 78
 7879            isLoading = false;
 80
 7881            if (model == null)
 82            {
 083                CleanupAvatar();
 084                this.OnSuccessEvent?.Invoke();
 085                return;
 86            }
 87
 7888            StopLoadingCoroutines();
 7889            isLoading = true;
 7890            loadCoroutine = CoroutineStarter.Start(LoadAvatar());
 7891        }
 92
 93        void StopLoadingCoroutines()
 94        {
 24795            if (loadCoroutine != null)
 7796                CoroutineStarter.Stop(loadCoroutine);
 97
 24798            loadCoroutine = null;
 24799        }
 100
 101        public void CleanupAvatar()
 102        {
 169103            StopLoadingCoroutines();
 104
 169105            eyebrowsController?.CleanUp();
 169106            eyebrowsController = null;
 107
 169108            eyesController?.CleanUp();
 169109            eyesController = null;
 110
 169111            mouthController?.CleanUp();
 169112            mouthController = null;
 113
 169114            bodyShapeController?.CleanUp();
 169115            bodyShapeController = null;
 116
 169117            using (var iterator = wearableControllers.GetEnumerator())
 118            {
 169119                while (iterator.MoveNext())
 120                {
 0121                    iterator.Current.Value.CleanUp();
 122                }
 169123            }
 124
 169125            wearableControllers.Clear();
 169126            model = null;
 169127            isLoading = false;
 169128            OnFailEvent = null;
 169129            OnSuccessEvent = null;
 130
 169131            CatalogController.RemoveWearablesInUse(wearablesInUse);
 169132            wearablesInUse.Clear();
 169133            OnVisualCue?.Invoke(VisualCue.CleanedUp);
 125134        }
 135
 136        void CleanUpUnusedItems()
 137        {
 0138            if (model.wearables == null)
 0139                return;
 140
 0141            var ids = wearableControllers.Keys.ToArray();
 142
 0143            for (var i = 0; i < ids.Length; i++)
 144            {
 0145                var currentId = ids[i];
 0146                var wearable = wearableControllers[currentId];
 147
 0148                if (!model.wearables.Contains(wearable.id) || !wearable.IsLoadedForBodyShape(model.bodyShape))
 149                {
 0150                    wearable.CleanUp();
 0151                    wearableControllers.Remove(currentId);
 152                }
 153            }
 0154        }
 155
 156        private IEnumerator LoadAvatar()
 157        {
 156158            yield return new WaitUntil(() => gameObject.activeSelf);
 159
 5160            bool loadSoftFailed = false;
 161
 5162            WearableItem resolvedBody = null;
 5163            Helpers.Promise<WearableItem> avatarBodyPromise = null;
 5164            if (!string.IsNullOrEmpty(model.bodyShape))
 165            {
 0166                avatarBodyPromise = CatalogController.RequestWearable(model.bodyShape);
 0167            }
 168            else
 169            {
 5170                OnFailEvent?.Invoke(true);
 5171                yield break;
 172            }
 173
 0174            List<WearableItem> resolvedWearables = new List<WearableItem>();
 0175            List<Helpers.Promise<WearableItem>> avatarWearablePromises = new List<Helpers.Promise<WearableItem>>();
 0176            if (model.wearables != null)
 177            {
 0178                for (int i = 0; i < model.wearables.Count; i++)
 179                {
 0180                    avatarWearablePromises.Add(CatalogController.RequestWearable(model.wearables[i]));
 181                }
 182            }
 183
 184            // In this point, all the requests related to the avatar's wearables have been collected and sent to the Cat
 185            // From here we wait for the response of the requested wearables and process them.
 0186            if (avatarBodyPromise != null)
 187            {
 0188                yield return avatarBodyPromise;
 189
 0190                if (!string.IsNullOrEmpty(avatarBodyPromise.error))
 191                {
 0192                    Debug.LogError(avatarBodyPromise.error);
 0193                    loadSoftFailed = true;
 0194                }
 195                else
 196                {
 0197                    resolvedBody = avatarBodyPromise.value;
 0198                    wearablesInUse.Add(avatarBodyPromise.value.id);
 199                }
 200            }
 201
 0202            if (resolvedBody == null)
 203            {
 0204                isLoading = false;
 0205                OnFailEvent?.Invoke(true);
 0206                yield break;
 207            }
 208
 0209            List<Helpers.Promise<WearableItem>> replacementPromises = new List<Helpers.Promise<WearableItem>>();
 0210            foreach (var avatarWearablePromise in avatarWearablePromises)
 211            {
 0212                yield return avatarWearablePromise;
 213
 0214                if (!string.IsNullOrEmpty(avatarWearablePromise.error))
 215                {
 0216                    Debug.LogError(avatarWearablePromise.error);
 0217                    loadSoftFailed = true;
 0218                }
 219                else
 220                {
 0221                    WearableItem wearableItem = avatarWearablePromise.value;
 0222                    wearablesInUse.Add(wearableItem.id);
 0223                    if (wearableItem.GetRepresentation(model.bodyShape) != null)
 0224                        resolvedWearables.Add(wearableItem);
 225                    else
 226                    {
 0227                        model.wearables.Remove(wearableItem.id);
 0228                        string defaultReplacement = DefaultWearables.GetDefaultWearable(model.bodyShape, wearableItem.da
 0229                        if (!string.IsNullOrEmpty(defaultReplacement))
 230                        {
 0231                            model.wearables.Add(defaultReplacement);
 0232                            replacementPromises.Add(CatalogController.RequestWearable(defaultReplacement));
 233                        }
 234                    }
 235                }
 0236            }
 237
 0238            foreach (var wearablePromise in replacementPromises)
 239            {
 0240                yield return wearablePromise;
 241
 0242                if (!string.IsNullOrEmpty(wearablePromise.error))
 243                {
 0244                    Debug.LogError(wearablePromise.error);
 0245                    loadSoftFailed = true;
 0246                }
 247                else
 248                {
 0249                    WearableItem wearableItem = wearablePromise.value;
 0250                    wearablesInUse.Add(wearableItem.id);
 0251                    resolvedWearables.Add(wearableItem);
 252                }
 0253            }
 254
 0255            bool bodyIsDirty = false;
 0256            if (bodyShapeController != null && bodyShapeController.id != model?.bodyShape)
 257            {
 0258                bodyShapeController.CleanUp();
 0259                bodyShapeController = null;
 0260                bodyIsDirty = true;
 261            }
 262
 0263            if (bodyShapeController == null)
 264            {
 0265                HideAll();
 0266                bodyShapeController = new BodyShapeController(resolvedBody);
 0267                eyesController = FacialFeatureController.CreateDefaultFacialFeature(bodyShapeController.bodyShapeId, Cat
 0268                eyebrowsController = FacialFeatureController.CreateDefaultFacialFeature(bodyShapeController.bodyShapeId,
 0269                mouthController = FacialFeatureController.CreateDefaultFacialFeature(bodyShapeController.bodyShapeId, Ca
 0270            }
 271            else
 272            {
 273                //If bodyShape is downloading will call OnWearableLoadingSuccess (and therefore SetupDefaultMaterial) on
 0274                if (bodyShapeController.isReady)
 0275                    bodyShapeController.SetupDefaultMaterial(defaultMaterial, model.skinColor, model.hairColor);
 276            }
 277
 0278            bool wearablesIsDirty = false;
 0279            HashSet<string> unusedCategories = new HashSet<string>(Categories.ALL);
 0280            int wearableCount = resolvedWearables.Count;
 0281            for (int index = 0; index < wearableCount; index++)
 282            {
 0283                WearableItem wearable = resolvedWearables[index];
 0284                if (wearable == null)
 285                    continue;
 286
 0287                unusedCategories.Remove(wearable.data.category);
 0288                if (wearableControllers.ContainsKey(wearable))
 289                {
 0290                    if (wearableControllers[wearable].IsLoadedForBodyShape(bodyShapeController.bodyShapeId))
 0291                        UpdateWearableController(wearable);
 292                    else
 0293                        wearableControllers[wearable].CleanUp();
 0294                }
 295                else
 296                {
 0297                    AddWearableController(wearable);
 0298                    if (wearable.data.category != Categories.EYES && wearable.data.category != Categories.MOUTH && weara
 0299                        wearablesIsDirty = true;
 300                }
 301            }
 302
 0303            foreach (var category in unusedCategories)
 304            {
 305                switch (category)
 306                {
 307                    case Categories.EYES:
 0308                        eyesController = FacialFeatureController.CreateDefaultFacialFeature(bodyShapeController.bodyShap
 0309                        break;
 310                    case Categories.MOUTH:
 0311                        mouthController = FacialFeatureController.CreateDefaultFacialFeature(bodyShapeController.bodySha
 0312                        break;
 313                    case Categories.EYEBROWS:
 0314                        eyebrowsController = FacialFeatureController.CreateDefaultFacialFeature(bodyShapeController.body
 315                        break;
 316                }
 317            }
 318
 319
 0320            HashSet<string> hiddenList = WearableItem.CompoundHidesList(bodyShapeController.bodyShapeId, resolvedWearabl
 0321            if (!bodyShapeController.isReady)
 322            {
 0323                bodyShapeController.Load(bodyShapeController.bodyShapeId, transform, OnWearableLoadingSuccess, OnBodySha
 324            }
 325
 0326            foreach (WearableController wearable in wearableControllers.Values)
 327            {
 0328                if (bodyIsDirty)
 0329                    wearable.boneRetargetingDirty = true;
 330
 0331                wearable.Load(bodyShapeController.bodyShapeId, transform, OnWearableLoadingSuccess, x => OnWearableLoadi
 0332                yield return null;
 333            }
 334
 0335            yield return new WaitUntil(() => bodyShapeController.isReady && wearableControllers.Values.All(x => x.isRead
 336
 0337            eyesController?.Load(bodyShapeController, model.eyeColor);
 0338            eyebrowsController?.Load(bodyShapeController, model.hairColor);
 0339            mouthController?.Load(bodyShapeController, model.skinColor);
 340
 0341            yield return new WaitUntil(() =>
 0342                (eyebrowsController == null || (eyebrowsController != null && eyebrowsController.isReady)) &&
 343                (eyesController == null || (eyesController != null && eyesController.isReady)) &&
 344                (mouthController == null || (mouthController != null && mouthController.isReady)));
 345
 0346            if (bodyIsDirty || wearablesIsDirty)
 347            {
 0348                OnVisualCue?.Invoke(VisualCue.Loaded);
 349            }
 350
 0351            bodyShapeController.SetActiveParts(unusedCategories.Contains(Categories.LOWER_BODY), unusedCategories.Contai
 0352            bodyShapeController.UpdateVisibility(hiddenList);
 0353            foreach (WearableController wearableController in wearableControllers.Values)
 354            {
 0355                wearableController.UpdateVisibility(hiddenList);
 356            }
 357
 0358            CleanUpUnusedItems();
 359
 0360            isLoading = false;
 361
 0362            SetWearableBones();
 0363            UpdateExpressions(model.expressionTriggerId, model.expressionTriggerTimestamp);
 0364            if (lastStickerTimestamp != model.stickerTriggerTimestamp && model.stickerTriggerId != null)
 365            {
 0366                lastStickerTimestamp = model.stickerTriggerTimestamp;
 0367                stickersController?.PlayEmote(model.stickerTriggerId);
 368            }
 369
 0370            if (loadSoftFailed)
 371            {
 0372                OnFailEvent?.Invoke(false);
 0373            }
 374            else
 375            {
 0376                OnSuccessEvent?.Invoke();
 377            }
 0378        }
 379
 380        void OnWearableLoadingSuccess(WearableController wearableController)
 381        {
 0382            if (wearableController == null || model == null)
 383            {
 0384                Debug.LogWarning($"WearableSuccess was called wrongly: IsWearableControllerNull=>{wearableController == 
 0385                OnWearableLoadingFail(wearableController, 0);
 0386                return;
 387            }
 388
 0389            wearableController.SetupDefaultMaterial(defaultMaterial, model.skinColor, model.hairColor);
 0390        }
 391
 392        void OnBodyShapeLoadingFail(WearableController wearableController)
 393        {
 0394            Debug.LogError($"Avatar: {model?.name}  -  Failed loading bodyshape: {wearableController?.id}");
 0395            CleanupAvatar();
 0396            OnFailEvent?.Invoke(true);
 0397        }
 398
 399        void OnWearableLoadingFail(WearableController wearableController, int retriesCount = MAX_RETRIES)
 400        {
 0401            if (retriesCount <= 0)
 402            {
 0403                Debug.LogError($"Avatar: {model?.name}  -  Failed loading wearable: {wearableController?.id}");
 0404                CleanupAvatar();
 0405                OnFailEvent?.Invoke(false);
 0406                return;
 407            }
 408
 0409            wearableController.Load(bodyShapeController.id, transform, OnWearableLoadingSuccess, x => OnWearableLoadingF
 0410        }
 411
 412        private void SetWearableBones()
 413        {
 414            //NOTE(Brian): Set bones/rootBone of all wearables to be the same of the baseBody,
 415            //             so all of them are animated together.
 0416            var mainSkinnedRenderer = bodyShapeController.skinnedMeshRenderer;
 417
 0418            using (var enumerator = wearableControllers.GetEnumerator())
 419            {
 0420                while (enumerator.MoveNext())
 421                {
 0422                    enumerator.Current.Value.SetAnimatorBones(mainSkinnedRenderer);
 423                }
 0424            }
 0425        }
 426
 427        public void UpdateExpressions(string id, long timestamp)
 428        {
 2429            model.expressionTriggerId = id;
 2430            model.expressionTriggerTimestamp = timestamp;
 2431            animator.SetExpressionValues(id, timestamp);
 2432        }
 433
 434        private void AddWearableController(WearableItem wearable)
 435        {
 0436            if (wearable == null)
 0437                return;
 0438            switch (wearable.data.category)
 439            {
 440                case Categories.EYES:
 0441                    eyesController = new FacialFeatureController(wearable, eyeMaterial);
 0442                    break;
 443                case Categories.EYEBROWS:
 0444                    eyebrowsController = new FacialFeatureController(wearable, eyebrowMaterial);
 0445                    break;
 446                case Categories.MOUTH:
 0447                    mouthController = new FacialFeatureController(wearable, mouthMaterial);
 0448                    break;
 449                case Categories.BODY_SHAPE:
 450                    break;
 451
 452                default:
 0453                    var wearableController = new WearableController(wearable);
 0454                    wearableControllers.Add(wearable, wearableController);
 455                    break;
 456            }
 0457        }
 458
 459        private void UpdateWearableController(WearableItem wearable)
 460        {
 0461            var wearableController = wearableControllers[wearable];
 0462            switch (wearableController.category)
 463            {
 464                case Categories.EYES:
 465                case Categories.EYEBROWS:
 466                case Categories.MOUTH:
 467                case Categories.BODY_SHAPE:
 468                    break;
 469                default:
 470                    //If wearable is downloading will call OnWearableLoadingSuccess(and therefore SetupDefaultMaterial) 
 0471                    if (wearableController.isReady)
 0472                        wearableController.SetupDefaultMaterial(defaultMaterial, model.skinColor, model.hairColor);
 473                    break;
 474            }
 0475        }
 476
 477        //TODO: Remove/replace once the class is easily mockable.
 478        protected void CopyFrom(AvatarRenderer original)
 479        {
 0480            this.wearableControllers = original.wearableControllers;
 0481            this.mouthController = original.mouthController;
 0482            this.bodyShapeController = original.bodyShapeController;
 0483            this.eyebrowsController = original.eyebrowsController;
 0484            this.eyesController = original.eyesController;
 0485        }
 486
 487        public void SetVisibility(bool newVisibility)
 488        {
 489            //NOTE(Brian): Avatar being loaded needs the renderer.enabled as false until the loading finishes.
 490            //             So we can' manipulate the values because it'd show an incomplete avatar. Its easier to just d
 7491            if (gameObject.activeSelf != newVisibility)
 7492                gameObject.SetActive(newVisibility);
 7493        }
 494
 495        private void HideAll()
 496        {
 0497            Renderer[] renderers = gameObject.GetComponentsInChildren<Renderer>();
 498
 0499            for (int i = 0; i < renderers.Length; i++)
 500            {
 0501                renderers[i].enabled = false;
 502            }
 0503        }
 504
 332505        protected virtual void OnDestroy() { CleanupAvatar(); }
 506    }
 507}