< Summary

Class:DCL.ECSComponents.AvatarShape
Assembly:DCL.ECSComponents.AvatarShape
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/DCLPlugins/ECS7/ECSComponents/AvatarShape/AvatarShape.cs
Covered lines:141
Uncovered lines:49
Coverable lines:190
Total lines:446
Line coverage:74.2% (141 of 190)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
AvatarShape()0%110100%
Awake()0%220100%
Init()0%2100%
SetPlayerNameReference()0%3.073080%
PlayerClicked()0%6200%
OnDestroy()0%3.143075%
ApplyModel()0%11.1811088.57%
LoadAvatar()0%12.726042.86%
SetImpostor(...)0%5.44055.56%
PlayerPointerExit()0%6200%
PlayerPointerEnter()0%6200%
UpdatePlayerStatus(...)0%13.1413090.62%
Update()0%12300%
DisablePassport()0%2.062075%
EnablePassport()0%2.062075%
OnEntityTransformChanged(...)0%2100%
OnEntityTransformChanged(...)0%3.333066.67%
OnPoolRelease()0%2100%
OnPoolGet()0%110100%
ApplyHideAvatarModifier()0%110100%
RemoveHideAvatarModifier()0%110100%
Cleanup()0%8.018094.74%
PrintCurrentProfile()0%2100%

File(s)

/tmp/workspace/unity-renderer/unity-renderer/Assets/DCLPlugins/ECS7/ECSComponents/AvatarShape/AvatarShape.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Linq;
 4using System.Runtime.ExceptionServices;
 5using System.Threading;
 6using AvatarSystem;
 7using Cysharp.Threading.Tasks;
 8using DCL.Components;
 9using DCL.Configuration;
 10using DCL.Controllers;
 11using DCL.Emotes;
 12using DCL.Helpers;
 13using DCL.Interface;
 14using DCL.Models;
 15using GPUSkinning;
 16using UnityEngine;
 17using LOD = AvatarSystem.LOD;
 18
 19namespace DCL.ECSComponents
 20{
 21    public interface IAvatarShape
 22    {
 23        /// <summary>
 24        /// This will initialize the AvatarShape and set
 25        /// </summary>
 26        void Init();
 27
 28        /// <summary>
 29        /// Clean up the avatar shape so we can reutilizate it using the pool
 30        /// </summary>
 31        void Cleanup();
 32
 33        /// <summary>
 34        /// Apply the model of the avatar, it will reload if necessary
 35        /// </summary>
 36        /// <param name="scene"></param>
 37        /// <param name="entity"></param>
 38        /// <param name="newModel"></param>
 39        void ApplyModel(IParcelScene scene, IDCLEntity entity, PBAvatarShape newModel);
 40
 41        /// <summary>
 42        /// Get the transform of the avatar shape
 43        /// </summary>
 44        Transform transform { get; }
 45    }
 46
 47    public class AvatarShape : MonoBehaviour, IHideAvatarAreaHandler, IPoolableObjectContainer, IAvatarShape, IPoolLifec
 48    {
 49        private const float AVATAR_Y_AXIS_OFFSET = -0.72f;
 50        private const float MINIMUM_PLAYERNAME_HEIGHT = 2.7f;
 51        private const string CURRENT_PLAYER_ID = "CurrentPlayerInfoCardId";
 52        internal const string IN_HIDE_AREA = "IN_HIDE_AREA";
 53
 54        [SerializeField] private GameObject avatarContainer;
 55        [SerializeField] internal Collider avatarCollider;
 56        [SerializeField] private Transform avatarRevealContainer;
 57        [SerializeField] private GameObject armatureContainer;
 58
 59        [SerializeField] internal AvatarOnPointerDown onPointerDown;
 60        [SerializeField] internal GameObject playerNameContainer;
 61
 62        internal IAvatarMovementController avatarMovementController;
 63        internal IPlayerName playerName;
 64        internal IAvatarReporterController avatarReporterController;
 65
 66        private StringVariable currentPlayerInfoCardId;
 67
 68        internal bool initializedPosition = false;
 69
 70        internal Player player = null;
 871        internal BaseDictionary<string, Player> otherPlayers => DataStore.i.player.otherPlayers;
 72
 2073        private IAvatarAnchorPoints anchorPoints = new AvatarAnchorPoints();
 74        internal IAvatar avatar;
 75        internal CancellationTokenSource loadingCts;
 76        private ILazyTextureObserver currentLazyObserver;
 2077        private bool isGlobalSceneAvatar = true;
 78
 079        public IPoolableObject poolableObject { get; set; }
 80        internal PBAvatarShape model;
 81        internal IDCLEntity entity;
 82
 83        private void Awake()
 84        {
 1885            avatarMovementController = GetComponent<IAvatarMovementController>();
 86
 1887            currentPlayerInfoCardId = Resources.Load<StringVariable>(CURRENT_PLAYER_ID);
 1888            Visibility visibility = new Visibility();
 1889            avatarMovementController.SetAvatarTransform(transform);
 90
 91            // Right now the avatars that are not part of the global scene of avatar are not using LOD since
 92            // AvatarsLodController are no taking them into account. It needs product definition and a refactor to inclu
 1893            LOD avatarLOD = new LOD(avatarContainer, visibility, avatarMovementController);
 1894            AvatarAnimatorLegacy animator = GetComponentInChildren<AvatarAnimatorLegacy>();
 1895            BaseAvatar baseAvatar = new BaseAvatar(avatarRevealContainer, armatureContainer, avatarLOD);
 1896            avatar = new AvatarWithHologram(
 97                baseAvatar,
 98                new AvatarCurator(new WearableItemResolver(), Environment.i.serviceLocator.Get<IEmotesCatalogService>())
 99                new Loader(new WearableLoaderFactory(), avatarContainer, new AvatarMeshCombinerHelper()),
 100                animator,
 101                visibility,
 102                avatarLOD,
 103                new SimpleGPUSkinning(),
 104                new GPUSkinningThrottler(),
 105                new EmoteAnimationEquipper(animator, DataStore.i.emotes));
 106
 18107            if (avatarReporterController == null)
 18108                avatarReporterController = new AvatarReporterController(Environment.i.world.state);
 109
 18110            onPointerDown.OnPointerDownReport += PlayerClicked;
 18111            onPointerDown.OnPointerEnterReport += PlayerPointerEnter;
 18112            onPointerDown.OnPointerExitReport += PlayerPointerExit;
 18113        }
 114
 115        public void Init()
 116        {
 117            // The avatars have an offset in the Y axis, so we set the offset after the avatar has been restored from th
 0118            transform.position = new UnityEngine.Vector3(transform.position.x, AVATAR_Y_AXIS_OFFSET, transform.position.
 0119            SetPlayerNameReference();
 0120        }
 121
 122        private void SetPlayerNameReference()
 123        {
 5124            if (playerName != null)
 0125                return;
 5126            playerName = GetComponentInChildren<IPlayerName>();
 5127            playerName?.Hide(true);
 5128        }
 129
 130        private void PlayerClicked()
 131        {
 0132            if (model == null)
 0133                return;
 0134            currentPlayerInfoCardId.Set(model.Id);
 0135        }
 136
 137        public void OnDestroy()
 138        {
 18139            Cleanup();
 140
 18141            if (poolableObject != null && poolableObject.isInsidePool)
 0142                poolableObject.RemoveFromPool();
 18143        }
 144
 145        public async void ApplyModel(IParcelScene scene, IDCLEntity entity, PBAvatarShape newModel)
 146        {
 4147            this.entity = entity;
 148
 4149            isGlobalSceneAvatar = scene.sceneData.id == EnvironmentSettings.AVATAR_GLOBAL_SCENE_ID;
 150
 4151            DisablePassport();
 152
 4153            bool needsLoading = !AvatarUtils.HaveSameWearablesAndColors(model,newModel);
 4154            model = newModel;
 155
 156#if UNITY_EDITOR
 4157            gameObject.name = $"Avatar Shape {model.GetName()}";
 158#endif
 159
 160            // To deal with the cases in which the entity transform was configured before the AvatarShape
 4161            if (!initializedPosition)
 162            {
 4163                OnEntityTransformChanged(entity.gameObject.transform.localPosition,
 164                    entity.gameObject.transform.localRotation, true);
 165            }
 166
 167            // NOTE: we subscribe here to transform changes since we might "lose" the message
 168            // if we subscribe after a any yield
 4169            entity.OnTransformChange -= OnEntityTransformChanged;
 4170            entity.OnTransformChange += OnEntityTransformChanged;
 171
 4172            var wearableItems = model.GetWereables().ToList();
 4173            wearableItems.Add(model.GetBodyShape());
 174
 175            // temporarily hardcoding the embedded emotes until the user profile provides the equipped ones
 4176            var embeddedEmotesSo = Resources.Load<EmbeddedEmotesSO>("EmbeddedEmotes");
 80177            wearableItems.AddRange(embeddedEmotesSo.emotes.Select(x => x.id));
 4178            HashSet<string> emotes = new HashSet<string>();
 80179            emotes.UnionWith(embeddedEmotesSo.emotes.Select(x => x.id));
 180
 4181            if (avatar.status != IAvatar.Status.Loaded || needsLoading)
 182            {
 4183                loadingCts?.Cancel();
 4184                loadingCts?.Dispose();
 4185                loadingCts = new CancellationTokenSource();
 186                try
 187                {
 4188                    await LoadAvatar(wearableItems,emotes);
 4189                }
 0190                catch (Exception e)
 191                {
 192                    // If the load of the avatar fails, we do it silently so the scene continue to operate.
 193                    // The LoadAvatar function will show the error in console already so in order to avoid noise, we jus
 0194                }
 195            }
 196
 197            // If the model contains a value for expressionTriggerId then we try it, if value doesn't exist, we skip
 4198            if(model.HasExpressionTriggerId)
 4199                avatar.PlayEmote(model.ExpressionTriggerId, model.GetExpressionTriggerTimestamp());
 200
 4201            UpdatePlayerStatus(entity, model);
 202
 4203            onPointerDown.Initialize(
 204                new OnPointerDown.Model()
 205                {
 206                    type = OnPointerDown.NAME,
 207                    button = WebInterface.ACTION_BUTTON.POINTER.ToString(),
 208                    hoverText = "view profile"
 209                },
 210                entity, player
 211            );
 212
 4213            avatarCollider.gameObject.SetActive(true);
 214
 4215            EnablePassport();
 216
 4217            onPointerDown.SetColliderEnabled(isGlobalSceneAvatar);
 4218            onPointerDown.SetOnClickReportEnabled(isGlobalSceneAvatar);
 4219        }
 220
 221        private async UniTask LoadAvatar(List<string> wearableItems, HashSet<string> emotes)
 222        {
 223            try
 224            {
 225                //TODO Add Collider to the AvatarSystem
 226                //TODO Without this the collider could get triggered disabling the avatar container,
 227                // this would stop the loading process due to the underlying coroutines of the AssetLoader not starting
 4228                avatarCollider.gameObject.SetActive(false);
 229
 4230                SetImpostor(model.Id);
 4231                playerName.SetName(model.GetName());
 4232                playerName.Show(true);
 4233                await avatar.Load(wearableItems, emotes.ToList(),new AvatarSettings
 234                {
 235                    playerName = model.GetName(),
 236                    bodyshapeId = model.GetBodyShape(),
 237                    eyesColor = model.GetEyeColor().ToUnityColor(),
 238                    skinColor = model.GetSkinColor().ToUnityColor(),
 239                    hairColor = model.GetHairColor().ToUnityColor(),
 240                }, loadingCts.Token);
 4241            }
 0242            catch (OperationCanceledException)
 243            {
 0244                Cleanup();
 0245                throw;
 246            }
 0247            catch (Exception e)
 248            {
 0249                Cleanup();
 0250                Debug.Log($"Avatar.Load failed with wearables:[{string.Join(",", wearableItems)}] for bodyshape:{model.B
 0251                if (e.InnerException != null)
 0252                    ExceptionDispatchInfo.Capture(e.InnerException).Throw();
 253                else
 0254                    throw;
 0255            }
 256            finally
 257            {
 4258                loadingCts?.Dispose();
 4259                loadingCts = null;
 260            }
 4261        }
 262
 263        public void SetImpostor(string userId)
 264        {
 4265            currentLazyObserver?.RemoveListener(avatar.SetImpostorTexture);
 4266            if (string.IsNullOrEmpty(userId))
 0267                return;
 268
 4269            UserProfile userProfile = UserProfileController.GetProfileByUserId(userId);
 4270            if (userProfile == null)
 4271                return;
 272
 0273            currentLazyObserver = userProfile.bodySnapshotObserver;
 0274            currentLazyObserver.AddListener(avatar.SetImpostorTexture);
 0275        }
 276
 0277        private void PlayerPointerExit() { playerName?.SetForceShow(false); }
 278
 0279        private void PlayerPointerEnter() { playerName?.SetForceShow(true); }
 280
 281        internal void UpdatePlayerStatus(IDCLEntity entity, PBAvatarShape model)
 282        {
 283            // Remove the player status if the userId changes
 5284            if (player != null && (player.id != model.Id || player.name != model.GetName()))
 0285                otherPlayers.Remove(player.id);
 286
 5287            if (isGlobalSceneAvatar && string.IsNullOrEmpty(model?.GetName()))
 0288                return;
 289
 5290            bool isNew = player == null;
 5291            if (isNew)
 5292                player = new Player();
 293
 5294            bool isNameDirty = player.name != model.GetName();
 295
 5296            player.id = model.Id;
 5297            player.name = model.GetName();
 5298            player.isTalking = model.Talking;
 5299            player.worldPosition = entity.gameObject.transform.position;
 5300            player.avatar = avatar;
 5301            player.onPointerDownCollider = onPointerDown;
 5302            player.collider = avatarCollider;
 303
 5304            if (isNew)
 305            {
 5306                player.playerName = playerName;
 5307                player.playerName.Show();
 5308                player.anchorPoints = anchorPoints;
 5309                if (isGlobalSceneAvatar)
 310                {
 311                    // TODO: Note: This is having a problem, sometimes the users has been detected as new 2 times and it
 312                    // we should investigate this
 1313                    if (otherPlayers.ContainsKey(player.id))
 0314                        otherPlayers.Remove(player.id);
 1315                    otherPlayers.Add(player.id, player);
 316                }
 5317                avatarReporterController.ReportAvatarRemoved();
 318            }
 319
 5320            avatarReporterController.SetUp(entity.scene.sceneData.id, player.id);
 321
 5322            float height = AvatarSystemUtils.AVATAR_Y_OFFSET + avatar.extents.y;
 323
 5324            anchorPoints.Prepare(avatarContainer.transform, avatar.GetBones(), height);
 325
 5326            player.playerName.SetIsTalking(model.Talking);
 5327            player.playerName.SetYOffset(Mathf.Max(MINIMUM_PLAYERNAME_HEIGHT, height));
 5328            if (isNameDirty)
 5329                player.playerName.SetName(model.GetName());
 5330        }
 331
 332        private void Update()
 333        {
 0334            if (player == null || entity == null)
 0335                return;
 336
 0337            player.worldPosition = entity.gameObject.transform.position;
 0338            player.forwardDirection = entity.gameObject.transform.forward;
 0339            avatarReporterController.ReportAvatarPosition(player.worldPosition);
 0340        }
 341
 342        public void DisablePassport()
 343        {
 4344            if (onPointerDown.collider == null)
 0345                return;
 346
 4347            onPointerDown.SetPassportEnabled(false);
 4348        }
 349
 350        public void EnablePassport()
 351        {
 4352            if (onPointerDown.collider == null)
 0353                return;
 354
 4355            onPointerDown.SetPassportEnabled(true);
 4356        }
 357
 358        private void OnEntityTransformChanged(object newModel)
 359        {
 0360            DCLTransform.Model newTransformModel = (DCLTransform.Model)newModel;
 0361            OnEntityTransformChanged(newTransformModel.position, newTransformModel.rotation, !initializedPosition);
 0362        }
 363
 364        private void OnEntityTransformChanged(in UnityEngine.Vector3 position, in Quaternion rotation, bool inmediate)
 365        {
 4366            if (entity == null)
 0367                return;
 368
 4369            if (isGlobalSceneAvatar)
 370            {
 0371                avatarMovementController.OnTransformChanged(position, rotation, inmediate);
 0372            }
 373            else
 374            {
 4375                var scenePosition = DCL.Helpers.Utils.GridToWorldPosition(entity.scene.sceneData.basePosition.x, entity.
 4376                avatarMovementController.OnTransformChanged(scenePosition + position, rotation, inmediate);
 377            }
 4378            initializedPosition = true;
 4379        }
 380
 381        public void OnPoolRelease()
 382        {
 0383            Cleanup();
 0384        }
 385
 386        public void OnPoolGet()
 387        {
 5388            initializedPosition = false;
 5389            model = new PBAvatarShape();
 5390            player = null;
 5391            SetPlayerNameReference();
 5392        }
 393
 394        public void ApplyHideAvatarModifier()
 395        {
 1396            avatar.AddVisibilityConstrain(IN_HIDE_AREA);
 1397            onPointerDown.gameObject.SetActive(false);
 1398            playerNameContainer.SetActive(false);
 1399        }
 400
 401        public void RemoveHideAvatarModifier()
 402        {
 1403            avatar.RemoveVisibilityConstrain(IN_HIDE_AREA);
 1404            onPointerDown.gameObject.SetActive(true);
 1405            playerNameContainer.SetActive(true);
 1406        }
 407
 408        public void Cleanup()
 409        {
 19410            playerName?.Hide(true);
 19411            if (player != null)
 412            {
 5413                otherPlayers.Remove(player.id);
 5414                player = null;
 415            }
 416
 19417            loadingCts?.Cancel();
 19418            loadingCts?.Dispose();
 19419            loadingCts = null;
 420
 19421            currentLazyObserver?.RemoveListener(avatar.SetImpostorTexture);
 19422            avatar.Dispose();
 423
 19424            if (poolableObject != null)
 425            {
 0426                poolableObject.OnRelease -= Cleanup;
 427            }
 428
 19429            onPointerDown.OnPointerDownReport -= PlayerClicked;
 19430            onPointerDown.OnPointerEnterReport -= PlayerPointerEnter;
 19431            onPointerDown.OnPointerExitReport -= PlayerPointerExit;
 432
 19433            if (entity != null)
 434            {
 4435                entity.OnTransformChange = null;
 4436                entity = null;
 437            }
 438
 19439            avatarReporterController.ReportAvatarRemoved();
 19440        }
 441
 442        [ContextMenu("Print current profile")]
 0443        private void PrintCurrentProfile() { Debug.Log(JsonUtility.ToJson(model)); }
 444    }
 445}
 446