< 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:150
Uncovered lines:47
Coverable lines:197
Total lines:461
Line coverage:76.1% (150 of 197)
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.1711088.89%
LoadAvatar()0%26.2310045.45%
SetImpostor(...)0%5.44055.56%
PlayerPointerExit()0%6200%
PlayerPointerEnter()0%6200%
UpdatePlayerStatus(...)0%17.0617093.94%
Update()0%12300%
DisablePassport()0%2.062075%
EnablePassport()0%2.062075%
OnEntityTransformChanged(...)0%2100%
OnEntityTransformChanged(...)0%3.473062.5%
OnPoolRelease()0%2100%
OnPoolGet()0%110100%
ApplyHideAvatarModifier()0%110100%
RemoveHideAvatarModifier()0%110100%
Cleanup()0%8.018095.24%
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 AvatarOutlineOnHoverEvent outlineOnHover;
 61        [SerializeField] internal GameObject playerNameContainer;
 62
 63        internal IAvatarMovementController avatarMovementController;
 64        internal IPlayerName playerName;
 65        internal IAvatarReporterController avatarReporterController;
 66
 67        private StringVariable currentPlayerInfoCardId;
 68
 69        internal bool initializedPosition = false;
 70
 71        internal Player player = null;
 1972        internal BaseDictionary<string, Player> otherPlayers => DataStore.i.player.otherPlayers;
 73
 1174        private IAvatarAnchorPoints anchorPoints = new AvatarAnchorPoints();
 75        internal IAvatar avatar;
 76        internal CancellationTokenSource loadingCts;
 77        private ILazyTextureObserver currentLazyObserver;
 78        private IUserProfileBridge userProfileBridge;
 1179        private bool isGlobalSceneAvatar = true;
 80
 1781        public IPoolableObject poolableObject { get; set; }
 82        internal PBAvatarShape model;
 83        internal IDCLEntity entity;
 84
 85        private void Awake()
 86        {
 887            avatarMovementController = GetComponent<IAvatarMovementController>();
 88            // TODO: avoid instantiation, user profile bridge should be retrieved from the service locator
 889            userProfileBridge = new UserProfileWebInterfaceBridge();
 90
 891            currentPlayerInfoCardId = Resources.Load<StringVariable>(CURRENT_PLAYER_ID);
 892            Visibility visibility = new Visibility();
 893            avatarMovementController.SetAvatarTransform(transform);
 94
 95            // Right now the avatars that are not part of the global scene of avatar are not using LOD since
 96            // AvatarsLodController are no taking them into account. It needs product definition and a refactor to inclu
 897            LOD avatarLOD = new LOD(avatarContainer, visibility, avatarMovementController);
 898            AvatarAnimatorLegacy animator = GetComponentInChildren<AvatarAnimatorLegacy>();
 899            BaseAvatar baseAvatar = new BaseAvatar(avatarRevealContainer, armatureContainer, avatarLOD);
 8100            avatar = new AvatarWithHologram(
 101                baseAvatar,
 102                new AvatarCurator(new WearableItemResolver(), Environment.i.serviceLocator.Get<IEmotesCatalogService>())
 103                new Loader(new WearableLoaderFactory(), avatarContainer, new AvatarMeshCombinerHelper()),
 104                animator,
 105                visibility,
 106                avatarLOD,
 107                new SimpleGPUSkinning(),
 108                new GPUSkinningThrottler(),
 109                new EmoteAnimationEquipper(animator, DataStore.i.emotes));
 110
 8111            if (avatarReporterController == null)
 8112                avatarReporterController = new AvatarReporterController(Environment.i.world.state);
 113
 8114            onPointerDown.OnPointerDownReport += PlayerClicked;
 8115            onPointerDown.OnPointerEnterReport += PlayerPointerEnter;
 8116            onPointerDown.OnPointerExitReport += PlayerPointerExit;
 8117            outlineOnHover.OnPointerEnterReport += PlayerPointerEnter;
 8118            outlineOnHover.OnPointerExitReport += PlayerPointerExit;
 8119        }
 120
 121        public void Init()
 122        {
 123            // The avatars have an offset in the Y axis, so we set the offset after the avatar has been restored from th
 0124            transform.position = new UnityEngine.Vector3(transform.position.x, AVATAR_Y_AXIS_OFFSET, transform.position.
 0125            SetPlayerNameReference();
 0126        }
 127
 128        private void SetPlayerNameReference()
 129        {
 5130            if (playerName != null)
 0131                return;
 5132            playerName = GetComponentInChildren<IPlayerName>();
 5133            playerName?.Hide(true);
 5134        }
 135
 136        private void PlayerClicked()
 137        {
 0138            if (model == null)
 0139                return;
 0140            currentPlayerInfoCardId.Set(model.Id);
 0141        }
 142
 143        public void OnDestroy()
 144        {
 8145            Cleanup();
 146
 8147            if (poolableObject != null && poolableObject.isInsidePool)
 0148                poolableObject.RemoveFromPool();
 8149        }
 150
 151        public async void ApplyModel(IParcelScene scene, IDCLEntity entity, PBAvatarShape newModel)
 152        {
 4153            this.entity = entity;
 154
 4155            isGlobalSceneAvatar = scene.sceneData.sceneNumber == EnvironmentSettings.AVATAR_GLOBAL_SCENE_NUMBER;
 156
 4157            DisablePassport();
 158
 4159            bool needsLoading = !AvatarUtils.HaveSameWearablesAndColors(model,newModel);
 4160            model = newModel;
 161
 162#if UNITY_EDITOR
 4163            gameObject.name = $"Avatar Shape {model.GetName()}";
 164#endif
 165
 166            // To deal with the cases in which the entity transform was configured before the AvatarShape
 4167            if (!initializedPosition)
 168            {
 4169                OnEntityTransformChanged(entity.gameObject.transform.localPosition,
 170                    entity.gameObject.transform.localRotation, true);
 171            }
 172
 173            // NOTE: we subscribe here to transform changes since we might "lose" the message
 174            // if we subscribe after a any yield
 4175            entity.OnTransformChange -= OnEntityTransformChanged;
 4176            entity.OnTransformChange += OnEntityTransformChanged;
 177
 4178            var wearableItems = model.GetWereables().ToList();
 4179            wearableItems.Add(model.GetBodyShape());
 180
 181            // temporarily hardcoding the embedded emotes until the user profile provides the equipped ones
 4182            var embeddedEmotesSo = Resources.Load<EmbeddedEmotesSO>("EmbeddedEmotes");
 128183            wearableItems.AddRange(embeddedEmotesSo.emotes.Select(x => x.id));
 4184            HashSet<string> emotes = new HashSet<string>();
 128185            emotes.UnionWith(embeddedEmotesSo.emotes.Select(x => x.id));
 186
 4187            if (avatar.status != IAvatar.Status.Loaded || needsLoading)
 188            {
 4189                loadingCts?.Cancel();
 4190                loadingCts?.Dispose();
 4191                loadingCts = new CancellationTokenSource();
 192                try
 193                {
 4194                    await LoadAvatar(wearableItems,emotes);
 4195                }
 0196                catch (Exception e)
 197                {
 198                    // If the load of the avatar fails, we do it silently so the scene continue to operate.
 199                    // The LoadAvatar function will show the error in console already so in order to avoid noise, we jus
 0200                }
 201            }
 202
 203            // If the model contains a value for expressionTriggerId then we try it, if value doesn't exist, we skip
 4204            if(model.HasExpressionTriggerId)
 4205                avatar.PlayEmote(model.ExpressionTriggerId, model.GetExpressionTriggerTimestamp());
 206
 4207            UpdatePlayerStatus(entity, model);
 208
 4209            onPointerDown.Initialize(
 210                new OnPointerDown.Model()
 211                {
 212                    type = OnPointerDown.NAME,
 213                    button = WebInterface.ACTION_BUTTON.POINTER.ToString(),
 214                    hoverText = "View Profile"
 215                },
 216                entity, player
 217            );
 218
 4219            outlineOnHover.Initialize(new OnPointerDown.Model(), entity, player.avatar);
 220
 4221            avatarCollider.gameObject.SetActive(true);
 222
 4223            EnablePassport();
 224
 4225            onPointerDown.SetColliderEnabled(isGlobalSceneAvatar);
 4226            onPointerDown.SetOnClickReportEnabled(isGlobalSceneAvatar);
 4227        }
 228
 229        private async UniTask LoadAvatar(List<string> wearableItems, HashSet<string> emotes)
 230        {
 231            try
 232            {
 233                //TODO Add Collider to the AvatarSystem
 234                //TODO Without this the collider could get triggered disabling the avatar container,
 235                // this would stop the loading process due to the underlying coroutines of the AssetLoader not starting
 4236                avatarCollider.gameObject.SetActive(false);
 237
 4238                SetImpostor(model.Id);
 4239                UserProfile profile = userProfileBridge.Get(model.Id);
 4240                playerName.SetName(model.GetName(), profile?.hasClaimedName ?? false, profile?.isGuest ?? false);
 4241                playerName.Show(true);
 4242                await avatar.Load(wearableItems, emotes.ToList(),new AvatarSettings
 243                {
 244                    playerName = model.GetName(),
 245                    bodyshapeId = model.GetBodyShape(),
 246                    eyesColor = model.GetEyeColor().ToUnityColor(),
 247                    skinColor = model.GetSkinColor().ToUnityColor(),
 248                    hairColor = model.GetHairColor().ToUnityColor(),
 249                }, loadingCts.Token);
 4250            }
 0251            catch (OperationCanceledException)
 252            {
 0253                Cleanup();
 0254                throw;
 255            }
 0256            catch (Exception e)
 257            {
 0258                Cleanup();
 0259                Debug.Log($"Avatar.Load failed with wearables:[{string.Join(",", wearableItems)}] for bodyshape:{model.B
 0260                if (e.InnerException != null)
 0261                    ExceptionDispatchInfo.Capture(e.InnerException).Throw();
 262                else
 0263                    throw;
 0264            }
 265            finally
 266            {
 4267                loadingCts?.Dispose();
 4268                loadingCts = null;
 269            }
 4270        }
 271
 272        public void SetImpostor(string userId)
 273        {
 4274            currentLazyObserver?.RemoveListener(avatar.SetImpostorTexture);
 4275            if (string.IsNullOrEmpty(userId))
 0276                return;
 277
 4278            UserProfile userProfile = UserProfileController.GetProfileByUserId(userId);
 4279            if (userProfile == null)
 4280                return;
 281
 0282            currentLazyObserver = userProfile.bodySnapshotObserver;
 0283            currentLazyObserver.AddListener(avatar.SetImpostorTexture);
 0284        }
 285
 0286        private void PlayerPointerExit() { playerName?.SetForceShow(false); }
 287
 0288        private void PlayerPointerEnter() { playerName?.SetForceShow(true); }
 289
 290        internal void UpdatePlayerStatus(IDCLEntity entity, PBAvatarShape model)
 291        {
 292            // Remove the player status if the userId changes
 5293            if (player != null && (player.id != model.Id || player.name != model.GetName()))
 0294                otherPlayers.Remove(player.id);
 295
 5296            if (isGlobalSceneAvatar && string.IsNullOrEmpty(model?.GetName()))
 0297                return;
 298
 5299            bool isNew = player == null;
 5300            if (isNew)
 5301                player = new Player();
 302
 5303            bool isNameDirty = player.name != model.GetName();
 304
 5305            player.id = model.Id;
 5306            player.name = model.GetName();
 5307            player.isTalking = model.Talking;
 5308            player.worldPosition = entity.gameObject.transform.position;
 5309            player.avatar = avatar;
 5310            player.onPointerDownCollider = onPointerDown;
 5311            player.collider = avatarCollider;
 312
 5313            if (isNew)
 314            {
 5315                player.playerName = playerName;
 5316                player.playerName.Show();
 5317                player.anchorPoints = anchorPoints;
 5318                if (isGlobalSceneAvatar)
 319                {
 320                    // TODO: Note: This is having a problem, sometimes the users has been detected as new 2 times and it
 321                    // we should investigate this
 5322                    if (otherPlayers.ContainsKey(player.id))
 3323                        otherPlayers.Remove(player.id);
 5324                    otherPlayers.Add(player.id, player);
 325                }
 5326                avatarReporterController.ReportAvatarRemoved();
 327            }
 328
 5329            avatarReporterController.SetUp(entity.scene.sceneData.sceneNumber, player.id);
 330
 5331            float height = AvatarSystemUtils.AVATAR_Y_OFFSET + avatar.extents.y;
 332
 5333            anchorPoints.Prepare(avatarContainer.transform, avatar.GetBones(), height);
 334
 5335            player.playerName.SetIsTalking(model.Talking);
 5336            player.playerName.SetYOffset(Mathf.Max(MINIMUM_PLAYERNAME_HEIGHT, height));
 337
 5338            if (isNameDirty)
 339            {
 5340                UserProfile profile = userProfileBridge.Get(model.Id);
 5341                player.playerName.SetName(model.GetName(), profile?.hasClaimedName ?? false, profile?.isGuest ?? false);
 342            }
 5343        }
 344
 345        private void Update()
 346        {
 0347            if (player == null || entity == null)
 0348                return;
 349
 0350            player.worldPosition = entity.gameObject.transform.position;
 0351            player.forwardDirection = entity.gameObject.transform.forward;
 0352            avatarReporterController.ReportAvatarPosition(player.worldPosition);
 0353        }
 354
 355        public void DisablePassport()
 356        {
 4357            if (onPointerDown.collider == null)
 0358                return;
 359
 4360            onPointerDown.SetPassportEnabled(false);
 4361        }
 362
 363        public void EnablePassport()
 364        {
 4365            if (onPointerDown.collider == null)
 0366                return;
 367
 4368            onPointerDown.SetPassportEnabled(true);
 4369        }
 370
 371        private void OnEntityTransformChanged(object newModel)
 372        {
 0373            DCLTransform.Model newTransformModel = (DCLTransform.Model)newModel;
 0374            OnEntityTransformChanged(newTransformModel.position, newTransformModel.rotation, !initializedPosition);
 0375        }
 376
 377        private void OnEntityTransformChanged(in UnityEngine.Vector3 position, in Quaternion rotation, bool inmediate)
 378        {
 4379            if (entity == null)
 0380                return;
 381
 4382            if (isGlobalSceneAvatar)
 383            {
 4384                avatarMovementController.OnTransformChanged(position, rotation, inmediate);
 385            }
 386            else
 387            {
 0388                var scenePosition = DCL.Helpers.Utils.GridToWorldPosition(entity.scene.sceneData.basePosition.x, entity.
 0389                avatarMovementController.OnTransformChanged(scenePosition + position, rotation, inmediate);
 390            }
 4391            initializedPosition = true;
 4392        }
 393
 394        public void OnPoolRelease()
 395        {
 0396            Cleanup();
 0397        }
 398
 399        public void OnPoolGet()
 400        {
 5401            initializedPosition = false;
 5402            model = new PBAvatarShape();
 5403            player = null;
 5404            SetPlayerNameReference();
 5405        }
 406
 407        public void ApplyHideAvatarModifier()
 408        {
 1409            avatar.AddVisibilityConstraint(IN_HIDE_AREA);
 1410            onPointerDown.gameObject.SetActive(false);
 1411            playerNameContainer.SetActive(false);
 1412        }
 413
 414        public void RemoveHideAvatarModifier()
 415        {
 1416            avatar.RemoveVisibilityConstrain(IN_HIDE_AREA);
 1417            onPointerDown.gameObject.SetActive(true);
 1418            playerNameContainer.SetActive(true);
 1419        }
 420
 421        public void Cleanup()
 422        {
 9423            playerName?.Hide(true);
 9424            if (player != null)
 425            {
 5426                otherPlayers.Remove(player.id);
 5427                player = null;
 428            }
 429
 9430            loadingCts?.Cancel();
 9431            loadingCts?.Dispose();
 9432            loadingCts = null;
 433
 9434            currentLazyObserver?.RemoveListener(avatar.SetImpostorTexture);
 9435            avatar.Dispose();
 436
 9437            if (poolableObject != null)
 438            {
 0439                poolableObject.OnRelease -= Cleanup;
 440            }
 441
 9442            onPointerDown.OnPointerDownReport -= PlayerClicked;
 9443            onPointerDown.OnPointerEnterReport -= PlayerPointerEnter;
 9444            onPointerDown.OnPointerExitReport -= PlayerPointerExit;
 9445            outlineOnHover.OnPointerEnterReport -= PlayerPointerEnter;
 9446            outlineOnHover.OnPointerExitReport -= PlayerPointerExit;
 447
 9448            if (entity != null)
 449            {
 4450                entity.OnTransformChange = null;
 4451                entity = null;
 452            }
 453
 9454            avatarReporterController.ReportAvatarRemoved();
 9455        }
 456
 457        [ContextMenu("Print current profile")]
 0458        private void PrintCurrentProfile() { Debug.Log(JsonUtility.ToJson(model)); }
 459    }
 460}
 461