< Summary

Class:ECSSystems.PointerInputSystem.ECSPointerInputSystem
Assembly:ECS7Plugin.Systems.PointerInput
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/DCLPlugins/ECS7/Systems/PointerInputSystem/ECSPointerInputSystem.cs
Covered lines:112
Uncovered lines:4
Coverable lines:116
Total lines:387
Line coverage:96.5% (112 of 116)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
CreateSystem(...)0%110100%
Update(...)0%55.0655097.33%
AddInputResultEvent(...)0%110100%
GetEntityWithCollider(...)0%3.023087.5%
GetPointerEventsForEntity(...)0%220100%
ShowPointerDownHover(...)0%110100%
ShowPointerUpHover(...)0%110100%
ShowHoverTooltips(...)0%77095.83%

File(s)

/tmp/workspace/unity-renderer/unity-renderer/Assets/DCLPlugins/ECS7/Systems/PointerInputSystem/ECSPointerInputSystem.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using DCL;
 4using DCL.Controllers;
 5using DCL.ECS7.InternalComponents;
 6using DCL.ECSComponents;
 7using DCL.ECSRuntime;
 8using DCL.Models;
 9using UnityEngine;
 10using Ray = UnityEngine.Ray;
 11
 12namespace ECSSystems.PointerInputSystem
 13{
 14    public static class ECSPointerInputSystem
 15    {
 16        private class State
 17        {
 18            public IInternalECSComponent<InternalColliders> pointerColliderComponent;
 19            public IInternalECSComponent<InternalInputEventResults> inputResultComponent;
 20            public ECSComponent<PBPointerHoverFeedback> pointerEvents;
 21            public DataStore_ECS7 dataStoreEcs7;
 22            public bool isLastInputPointerDown;
 23            public IWorldState worldState;
 24            public EntityInput lastInputDown;
 25            public EntityInput lastInputHover;
 26            public EntityInput lastHoverFeedback;
 27            public IECSInteractionHoverCanvas interactionHoverCanvas;
 28        }
 29
 30        private class EntityInput
 31        {
 32            public IDCLEntity entity;
 33            public IParcelScene scene;
 34            public int sceneNumber;
 35            public bool hasValue;
 36        }
 37
 38        private enum InputHitType
 39        {
 40            None,
 41            PointerDown,
 42            PointerUp,
 43            PointerHover
 44        }
 45
 46        public static Action CreateSystem(
 47            IInternalECSComponent<InternalColliders> pointerColliderComponent,
 48            IInternalECSComponent<InternalInputEventResults> inputResultComponent,
 49            ECSComponent<PBPointerHoverFeedback> pointerEvents,
 50            IECSInteractionHoverCanvas interactionHoverCanvas,
 51            IWorldState worldState,
 52            DataStore_ECS7 dataStoreEcs)
 53        {
 1654            var state = new State()
 55            {
 56                pointerColliderComponent = pointerColliderComponent,
 57                inputResultComponent = inputResultComponent,
 58                pointerEvents = pointerEvents,
 59                interactionHoverCanvas = interactionHoverCanvas,
 60                isLastInputPointerDown = false,
 61                dataStoreEcs7 = dataStoreEcs,
 62                worldState = worldState,
 63                lastInputDown = new EntityInput() { hasValue = false },
 64                lastInputHover = new EntityInput() { hasValue = false },
 65                lastHoverFeedback = new EntityInput() { hasValue = false },
 66            };
 3967            return () => Update(state);
 68        }
 69
 70        private static void Update(State state)
 71        {
 2372            DataStore_ECS7.PointerEvent currentPointerInput = state.dataStoreEcs7.lastPointerInputEvent;
 73
 2374            bool isHit = state.dataStoreEcs7.lastPointerRayHit.hasValue && state.dataStoreEcs7.lastPointerRayHit.didHit;
 2375            bool isPointerDown = currentPointerInput.hasValue && currentPointerInput.isButtonDown;
 2376            bool isPointerUp = state.isLastInputPointerDown && currentPointerInput.hasValue && !currentPointerInput.isBu
 77
 2378            DataStore_ECS7.RaycastEvent.Hit raycastHit = state.dataStoreEcs7.lastPointerRayHit.hit;
 2379            Ray raycastRay = state.dataStoreEcs7.lastPointerRayHit.ray;
 80
 2381            IECSReadOnlyComponentData<InternalColliders> colliderData = isHit
 82                ? state.pointerColliderComponent
 83                       .GetEntityWithCollider(raycastHit.collider)
 84                : null;
 85
 2386            bool isRaycastHitValidEntity = colliderData != null;
 2387            bool isHoveringInput = !isPointerDown && !isPointerUp && !state.isLastInputPointerDown;
 2388            bool isHoveringExit = !isRaycastHitValidEntity && state.lastInputHover.hasValue;
 89
 2390            IList<PBPointerHoverFeedback.Types.Entry> hoverEvents = isRaycastHitValidEntity
 91                ? state.pointerEvents.GetPointerEventsForEntity(colliderData.scene, colliderData.entity)
 92                : null;
 93
 94            // show hover tooltip for pointer down
 2395            if (hoverEvents != null && isHoveringInput)
 96            {
 397                if (!state.lastHoverFeedback.hasValue || state.lastHoverFeedback.entity != colliderData.entity)
 98                {
 399                    state.interactionHoverCanvas.ShowPointerDownHover(hoverEvents, raycastHit.distance);
 3100                    state.lastHoverFeedback.hasValue = true;
 3101                    state.lastHoverFeedback.entity = colliderData.entity;
 3102                    state.lastHoverFeedback.scene = colliderData.scene;
 3103                    state.lastHoverFeedback.sceneNumber = colliderData.scene.sceneData.sceneNumber;
 104                }
 105            }
 106            // show hover tooltip for pointer up
 20107            else if (hoverEvents != null && state.isLastInputPointerDown && !isPointerUp)
 108            {
 3109                if (!state.lastHoverFeedback.hasValue && state.lastInputDown.entity == colliderData.entity)
 110                {
 3111                    state.interactionHoverCanvas.ShowPointerUpHover(hoverEvents, raycastHit.distance, (InputAction)curre
 3112                    state.lastHoverFeedback.hasValue = true;
 3113                    state.lastHoverFeedback.entity = colliderData.entity;
 3114                    state.lastHoverFeedback.scene = colliderData.scene;
 3115                    state.lastHoverFeedback.sceneNumber = colliderData.scene.sceneData.sceneNumber;
 116                }
 117            }
 118            // hide hover tooltip
 17119            else if (state.lastHoverFeedback.hasValue)
 120            {
 0121                state.interactionHoverCanvas.Hide();
 0122                state.lastHoverFeedback.hasValue = false;
 123            }
 124
 23125            if (isRaycastHitValidEntity)
 126            {
 21127                InputHitType inputHitType = isPointerDown ? InputHitType.PointerDown
 128                    : isPointerUp ? InputHitType.PointerUp
 129                    : isHoveringInput ? InputHitType.PointerHover
 130                    : InputHitType.None;
 131
 132                // process entity hit with input
 133                switch (inputHitType)
 134                {
 135                    case InputHitType.PointerDown:
 9136                        state.lastInputDown.entity = colliderData.entity;
 9137                        state.lastInputDown.scene = colliderData.scene;
 9138                        state.lastInputDown.sceneNumber = colliderData.scene.sceneData.sceneNumber;
 9139                        state.lastInputDown.hasValue = true;
 140
 9141                        AddInputResultEvent(
 142                            state,
 143                            currentPointerInput,
 144                            colliderData.scene,
 145                            colliderData.entity.entityId,
 146                            raycastRay,
 147                            raycastHit,
 148                            PointerEventType.PetDown
 149                        );
 9150                        break;
 151
 152                    case InputHitType.PointerUp:
 2153                        bool validInputDownExist = state.lastInputDown.hasValue;
 2154                        EntityInput lastInputDownData = state.lastInputDown;
 155
 156                        // did it hit same entity as pointer down hit?
 2157                        if (validInputDownExist && colliderData.entity.entityId == lastInputDownData.entity.entityId
 158                                                && colliderData.scene.sceneData.sceneNumber == lastInputDownData.sceneNu
 159                        {
 1160                            AddInputResultEvent(
 161                                state,
 162                                currentPointerInput,
 163                                colliderData.scene,
 164                                colliderData.entity.entityId,
 165                                raycastRay,
 166                                raycastHit,
 167                                PointerEventType.PetUp
 168                            );
 169                        }
 1170                        else if (validInputDownExist) // did it hit different entity as pointer down hit?
 171                        {
 1172                            bool isEntityFromSameScene = colliderData.scene.sceneData.sceneNumber == lastInputDownData.s
 1173                            bool isValidScene = isEntityFromSameScene || state.worldState.ContainsScene(lastInputDownDat
 174
 1175                            if (isValidScene)
 176                            {
 1177                                AddInputResultEvent(
 178                                    state,
 179                                    currentPointerInput,
 180                                    lastInputDownData.scene,
 181                                    -1,
 182                                    raycastRay,
 183                                    raycastHit,
 184                                    PointerEventType.PetUp
 185                                );
 186                            }
 187                        }
 2188                        state.lastInputDown.hasValue = false;
 2189                        break;
 190
 191                    case InputHitType.PointerHover:
 7192                        bool isPreviouslyHoveredEntity = state.lastInputHover.hasValue;
 7193                        bool isHoveringNewEntity = !isPreviouslyHoveredEntity
 194                                                   || state.lastInputHover.entity.entityId != colliderData.entity.entity
 195                                                   || state.lastInputHover.sceneNumber != colliderData.scene.sceneData.s
 196
 197                        // was other entity previously hovered?
 7198                        if (isPreviouslyHoveredEntity && isHoveringNewEntity)
 199                        {
 1200                            bool isValidScene = colliderData.scene.sceneData.sceneNumber == state.lastInputHover.sceneNu
 201                                                || state.worldState.ContainsScene(state.lastInputHover.sceneNumber);
 202
 1203                            if (isValidScene)
 204                            {
 1205                                AddInputResultEvent(
 206                                    state,
 207                                    currentPointerInput,
 208                                    state.lastInputHover.scene,
 209                                    state.lastInputHover.entity.entityId,
 210                                    raycastRay,
 211                                    raycastHit,
 212                                    PointerEventType.PetHoverLeave
 213                                );
 214                            }
 215                        }
 216
 217                        // hover enter
 7218                        if (isHoveringNewEntity)
 219                        {
 7220                            state.lastInputHover.entity = colliderData.entity;
 7221                            state.lastInputHover.scene = colliderData.scene;
 7222                            state.lastInputHover.sceneNumber = colliderData.scene.sceneData.sceneNumber;
 7223                            state.lastInputHover.hasValue = true;
 224
 7225                            AddInputResultEvent(
 226                                state,
 227                                currentPointerInput,
 228                                colliderData.scene,
 229                                colliderData.entity.entityId,
 230                                raycastRay,
 231                                raycastHit,
 232                                PointerEventType.PetHoverEnter
 233                            );
 234                        }
 7235                        break;
 236                }
 237            }
 238            else // no entity hit
 239            {
 2240                if (isPointerUp)
 241                {
 1242                    if (state.lastInputDown.hasValue)
 243                    {
 244                        // input up without hit but with valid input down
 1245                        bool isValidScene = state.worldState.ContainsScene(state.lastInputDown.sceneNumber);
 1246                        if (isValidScene)
 247                        {
 1248                            AddInputResultEvent(
 249                                state,
 250                                currentPointerInput,
 251                                state.lastInputDown.scene,
 252                                -1,
 253                                raycastRay,
 254                                raycastHit,
 255                                PointerEventType.PetUp
 256                            );
 257                        }
 258                    }
 1259                    state.lastInputDown.hasValue = false;
 260                }
 261
 2262                if (isHoveringExit)
 263                {
 1264                    bool isValidScene = state.worldState.ContainsScene(state.lastInputHover.sceneNumber);
 1265                    if (isValidScene)
 266                    {
 1267                        AddInputResultEvent(
 268                            state,
 269                            currentPointerInput,
 270                            state.lastInputHover.scene,
 271                            state.lastInputHover.entity.entityId,
 272                            raycastRay,
 273                            raycastHit,
 274                            PointerEventType.PetHoverLeave
 275                        );
 276                    }
 1277                    state.lastInputHover.hasValue = false;
 278                }
 279            }
 280
 23281            state.dataStoreEcs7.lastPointerInputEvent.hasValue = false;
 23282            state.dataStoreEcs7.lastPointerRayHit.hasValue = false;
 23283            state.isLastInputPointerDown = isPointerDown || (!isPointerUp && state.isLastInputPointerDown);
 23284        }
 285
 286        private static void AddInputResultEvent(State state, DataStore_ECS7.PointerEvent pointerEvent, IParcelScene scen
 287            long entityId, Ray ray, DataStore_ECS7.RaycastEvent.Hit raycastHit, PointerEventType pointerEventType)
 288        {
 21289            raycastHit.point = WorldStateUtils.ConvertUnityToScenePosition(raycastHit.point, scene);
 21290            ray.origin = WorldStateUtils.ConvertUnityToScenePosition(ray.origin, scene);
 291
 21292            state.inputResultComponent.AddEvent(scene, new InternalInputEventResults.EventData()
 293            {
 294                analog = 1,
 295                button = (InputAction)pointerEvent.buttonId,
 296                hit = ProtoConvertUtils.ToPBRaycasHit(entityId, null,
 297                    ray, raycastHit.distance, raycastHit.point, raycastHit.normal, entityId != -1),
 298                type = pointerEventType
 299            });
 21300        }
 301
 302        private static IECSReadOnlyComponentData<InternalColliders> GetEntityWithCollider(
 303            this IInternalECSComponent<InternalColliders> pointerColliderComponent,
 304            Collider collider)
 305        {
 21306            var collidersData = pointerColliderComponent.GetForAll();
 54307            for (int i = 0; i < collidersData.Count; i++)
 308            {
 27309                var colliderData = collidersData[i].value;
 27310                if (colliderData.model.colliders.Contains(collider))
 311                {
 21312                    return colliderData;
 313                }
 314            }
 0315            return null;
 316        }
 317
 318        private static IList<PBPointerHoverFeedback.Types.Entry> GetPointerEventsForEntity(this ECSComponent<PBPointerHo
 319            IParcelScene scene, IDCLEntity entity)
 320        {
 21321            var componentData = component.Get(scene, entity);
 21322            return componentData?.model.PointerEvents;
 323        }
 324
 325        private static void ShowPointerDownHover(this IECSInteractionHoverCanvas canvas,
 326            IList<PBPointerHoverFeedback.Types.Entry> entityEvents, float distance)
 327        {
 3328            canvas.ShowHoverTooltips(entityEvents, (pointerEvent) =>
 4329                pointerEvent.EventType == PointerEventType.PetDown
 330                && pointerEvent.EventInfo.GetShowFeedback()
 331                && distance <= pointerEvent.EventInfo.GetMaxDistance()
 332            );
 3333        }
 334
 335        private static void ShowPointerUpHover(this IECSInteractionHoverCanvas canvas,
 336            IList<PBPointerHoverFeedback.Types.Entry> entityEvents, float distance, InputAction expectedButton)
 337        {
 3338            canvas.ShowHoverTooltips(entityEvents, (pointerEvent) =>
 3339                pointerEvent.EventType == PointerEventType.PetUp
 340                && pointerEvent.EventInfo.GetShowFeedback()
 341                && distance <= pointerEvent.EventInfo.GetMaxDistance()
 342                && (pointerEvent.EventInfo.GetButton() == expectedButton || pointerEvent.EventInfo.GetButton() == InputA
 343            );
 3344        }
 345
 346        private static void ShowHoverTooltips(this IECSInteractionHoverCanvas canvas,
 347            IList<PBPointerHoverFeedback.Types.Entry> entityEvents, Func<PBPointerHoverFeedback.Types.Entry, bool> filte
 348        {
 6349            if (entityEvents is null)
 0350                return;
 351
 6352            bool anyTooltipAdded = false;
 6353            int eventIndex = 0;
 36354            for (int i = 0; i < canvas.tooltipsCount; i++)
 355            {
 12356                PBPointerHoverFeedback.Types.Entry pointerEvent = null;
 18357                for (; eventIndex < entityEvents.Count; eventIndex++)
 358                {
 7359                    pointerEvent = entityEvents[eventIndex];
 7360                    if (filter(pointerEvent))
 361                    {
 4362                        eventIndex++;
 4363                        break;
 364                    }
 3365                    pointerEvent = null;
 366                }
 367
 12368                if (!(pointerEvent is null))
 369                {
 4370                    anyTooltipAdded = true;
 4371                    canvas.SetTooltipText(i, pointerEvent.EventInfo.GetHoverText());
 4372                    canvas.SetTooltipInput(i, pointerEvent.EventInfo.GetButton());
 4373                    canvas.SetTooltipActive(i, true);
 374                }
 375                else
 376                {
 8377                    canvas.SetTooltipActive(i, false);
 378                }
 379            }
 380
 6381            if (anyTooltipAdded)
 382            {
 3383                canvas.Show();
 384            }
 6385        }
 386    }
 387}