< 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:115
Uncovered lines:11
Coverable lines:126
Total lines:446
Line coverage:91.2% (115 of 126)
Covered branches:0
Total branches:0
Covered methods:13
Total methods:15
Method coverage:86.6% (13 of 15)

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
ECSPointerInputSystem()0%110100%
ECSPointerInputSystem(...)0%110100%
Update()0%20.0120096.77%
IsColliderDifferent(...)0%440100%
IsColliderMissing(...)0%220100%
IsColliderAvailable(...)0%220100%
HandleColliderChanged(...)0%220100%
HandleMissingCollider(...)0%6200%
HandleAvailableCollider(...)0%110100%
BroadcastInputResultEvent(...)0%330100%
AddInputResultEvent(...)0%440100%
GetEntityWithCollider(...)0%3.023087.5%
ShowHoverTooltips(...)0%18.0118096.77%
IsValidInputForUnlockingUiPrompts(...)0%56700%
HasInputEvent(...)0%770100%

File(s)

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

#LineLine coverage
 1using DCL;
 2using DCL.Controllers;
 3using DCL.ECS7.InternalComponents;
 4using DCL.ECSComponents;
 5using DCL.ECSRuntime;
 6using DCL.Interface;
 7using System;
 8using System.Collections.Generic;
 9using UnityEngine;
 10using RaycastHit = DCL.ECSComponents.RaycastHit;
 11
 12namespace ECSSystems.PointerInputSystem
 13{
 14    public class ECSPointerInputSystem
 15    {
 116        private static readonly InputAction[] INPUT_ACTION_ENUM = (InputAction[])Enum.GetValues(typeof(WebInterface.ACTI
 17
 18        private readonly IInternalECSComponent<InternalColliders> pointerColliderComponent;
 19        private readonly IInternalECSComponent<InternalInputEventResults> inputResultComponent;
 20        private readonly IInternalECSComponent<InternalPointerEvents> pointerEvents;
 21        private readonly DataStore_ECS7 dataStoreEcs7;
 22        private readonly EntityInput lastHoverFeedback;
 23        private readonly IWorldState worldState;
 24        private readonly IECSInteractionHoverCanvas interactionHoverCanvas;
 25        private readonly bool[] inputActionState;
 26
 27        private class EntityInput
 28        {
 29            public long entityId;
 30            public IParcelScene scene;
 31            public int sceneNumber;
 32            public bool hasValue;
 33            public IReadOnlyList<InternalPointerEvents.Entry> pointerEvents;
 34        }
 35
 3136        public ECSPointerInputSystem(
 37            IInternalECSComponent<InternalColliders> pointerColliderComponent,
 38            IInternalECSComponent<InternalInputEventResults> inputResultComponent,
 39            IInternalECSComponent<InternalPointerEvents> pointerEvents,
 40            IECSInteractionHoverCanvas interactionHoverCanvas,
 41            IWorldState worldState,
 42            DataStore_ECS7 dataStoreEcs)
 43        {
 3144            this.pointerColliderComponent = pointerColliderComponent;
 3145            this.inputResultComponent = inputResultComponent;
 3146            this.pointerEvents = pointerEvents;
 3147            this.worldState = worldState;
 3148            this.interactionHoverCanvas = interactionHoverCanvas;
 3149            this.dataStoreEcs7 = dataStoreEcs;
 3150            this.lastHoverFeedback = new EntityInput() { hasValue = false };
 3151            this.inputActionState = new bool[INPUT_ACTION_ENUM.Length];
 3152        }
 53
 54        public void Update()
 55        {
 56            // Retrieve the last raycast hit
 4357            bool doesRaycastHit = dataStoreEcs7.lastPointerRayHit.hasValue && dataStoreEcs7.lastPointerRayHit.didHit;
 4358            DataStore_ECS7.RaycastEvent raycastEvent = dataStoreEcs7.lastPointerRayHit;
 4359            DataStore_ECS7.RaycastEvent.Hit raycastHit = raycastEvent.hit;
 4360            Ray raycastRay = raycastEvent.ray;
 4361            IReadOnlyList<IParcelScene> loadedScenes = dataStoreEcs7.scenes;
 62
 63            // Get the collider that the raycast hit
 4364            ECSComponentData<InternalColliders>? colliderData = doesRaycastHit
 65                ? GetEntityWithCollider(pointerColliderComponent, raycastHit.collider)
 66                : null;
 67
 4368            IParcelScene colliderScene = colliderData?.scene;
 69
 4370            IReadOnlyList<InternalPointerEvents.Entry> entityPointerEvents = colliderData != null
 71                ? pointerEvents.GetFor(colliderData.Value.scene, colliderData.Value.entity)?.model.PointerEvents
 72                : null;
 73
 4374            bool isAnyButtonDown = false;
 75
 76            // Emit command for button states
 4377            bool[] curState = dataStoreEcs7.inputActionState;
 4378            bool[] prevState = inputActionState;
 79
 129080            for (int i = 0; i < curState.Length; i++)
 81            {
 60282                isAnyButtonDown |= curState[i];
 83
 60284                if (curState[i] != prevState[i])
 85                {
 3186                    PointerEventType pointerEventType = curState[i] ? PointerEventType.PetDown : PointerEventType.PetUp;
 3187                    InputAction inputAction = INPUT_ACTION_ENUM[i];
 88
 3189                    if (colliderData != null)
 90                    {
 3191                        AddInputResultEvent(
 92                            inputResultComponent,
 93                            inputAction,
 94                            colliderData.Value.scene,
 95                            colliderData.Value.entity.entityId,
 96                            raycastRay,
 97                            raycastHit,
 98                            pointerEventType,
 99                            entityPointerEvents
 100                        );
 101                    }
 102
 31103                    BroadcastInputResultEvent(
 104                        inputResultComponent,
 105                        loadedScenes,
 106                        inputAction,
 107                        raycastRay,
 108                        raycastHit,
 109                        pointerEventType,
 110                        colliderScene
 111                    );
 112
 113                    // update
 31114                    prevState[i] = curState[i];
 115                }
 116            }
 117
 118            // Check if the hovered entity has changed with three options:
 119            // 1) We were hitting a collider A and now we're hitting a collider B
 43120            if (IsColliderDifferent(lastHoverFeedback, colliderData))
 121            {
 5122                HandleColliderChanged(colliderData.Value, lastHoverFeedback, raycastEvent, interactionHoverCanvas, input
 123            }
 124
 125            // 2) We were hitting a collider A and now we're not hitting anything
 38126            else if (IsColliderMissing(lastHoverFeedback, colliderData))
 127            {
 0128                HandleMissingCollider(lastHoverFeedback, raycastEvent, interactionHoverCanvas, inputResultComponent, wor
 129            }
 130
 131            // 3) We were not hitting anything and now we're hitting collider A
 38132            else if (IsColliderAvailable(lastHoverFeedback, colliderData))
 133            {
 29134                HandleAvailableCollider(colliderData.Value, lastHoverFeedback, raycastEvent, inputResultComponent, entit
 135            }
 136
 43137            if (entityPointerEvents != null)
 138            {
 30139                ShowHoverTooltips(interactionHoverCanvas, entityPointerEvents, curState, raycastHit.distance, isAnyButto
 140            }
 43141        }
 142
 143        private static bool IsColliderDifferent(EntityInput lastHoverFeedback, ECSComponentData<InternalColliders>? coll
 144        {
 43145            return colliderData != null && // current collider
 146                   lastHoverFeedback.hasValue && // previous collider
 147                   (lastHoverFeedback.entityId != colliderData.Value.entity.entityId ||
 148                    lastHoverFeedback.sceneNumber != colliderData.Value.scene.sceneData.sceneNumber);
 149        }
 150
 151        private static bool IsColliderMissing(EntityInput lastHoverFeedback, ECSComponentData<InternalColliders>? collid
 152        {
 38153            return colliderData == null && lastHoverFeedback.hasValue;
 154        }
 155
 156        private static bool IsColliderAvailable(EntityInput lastHoverFeedback, ECSComponentData<InternalColliders>? coll
 157        {
 38158            return colliderData != null && !lastHoverFeedback.hasValue;
 159        }
 160
 161        private static void HandleColliderChanged(
 162            ECSComponentData<InternalColliders> colliderData,
 163            EntityInput lastHoverFeedback,
 164            DataStore_ECS7.RaycastEvent lastPointerRayHit,
 165            IECSInteractionHoverCanvas interactionHoverCanvas,
 166            IInternalECSComponent<InternalInputEventResults> inputResultComponent,
 167            IWorldState worldState,
 168            IReadOnlyList<InternalPointerEvents.Entry> entityEvents)
 169        {
 5170            DataStore_ECS7.RaycastEvent.Hit raycastHit = lastPointerRayHit.hit;
 5171            Ray raycastRay = lastPointerRayHit.ray;
 172
 5173            if (worldState.ContainsScene(lastHoverFeedback.sceneNumber))
 174            {
 5175                AddInputResultEvent(
 176                    inputResultComponent,
 177                    InputAction.IaPointer,
 178                    lastHoverFeedback.scene,
 179                    lastHoverFeedback.entityId,
 180                    raycastRay,
 181                    raycastHit,
 182                    PointerEventType.PetHoverLeave,
 183                    lastHoverFeedback.pointerEvents
 184                );
 185            }
 186
 5187            AddInputResultEvent(
 188                inputResultComponent,
 189                InputAction.IaPointer,
 190                colliderData.scene,
 191                colliderData.entity.entityId,
 192                raycastRay,
 193                raycastHit,
 194                PointerEventType.PetHoverEnter,
 195                entityEvents
 196            );
 197
 5198            interactionHoverCanvas.Hide();
 199
 5200            lastHoverFeedback.hasValue = true;
 5201            lastHoverFeedback.sceneNumber = colliderData.scene.sceneData.sceneNumber;
 5202            lastHoverFeedback.scene = colliderData.scene;
 5203            lastHoverFeedback.entityId = colliderData.entity.entityId;
 5204            lastHoverFeedback.pointerEvents = entityEvents;
 5205        }
 206
 207        private static void HandleMissingCollider(
 208            EntityInput lastHoverFeedback,
 209            DataStore_ECS7.RaycastEvent lastPointerRayHit,
 210            IECSInteractionHoverCanvas interactionHoverCanvas,
 211            IInternalECSComponent<InternalInputEventResults> inputResultComponent,
 212            IWorldState worldState)
 213        {
 0214            DataStore_ECS7.RaycastEvent.Hit raycastHit = lastPointerRayHit.hit;
 0215            Ray raycastRay = lastPointerRayHit.ray;
 216
 0217            if (worldState.ContainsScene(lastHoverFeedback.sceneNumber))
 218            {
 0219                AddInputResultEvent(
 220                    inputResultComponent,
 221                    InputAction.IaPointer,
 222                    lastHoverFeedback.scene,
 223                    lastHoverFeedback.entityId,
 224                    raycastRay,
 225                    raycastHit,
 226                    PointerEventType.PetHoverLeave,
 227                    lastHoverFeedback.pointerEvents
 228                );
 229            }
 230
 0231            interactionHoverCanvas.Hide();
 0232            lastHoverFeedback.hasValue = false;
 0233        }
 234
 235        private static void HandleAvailableCollider(
 236            ECSComponentData<InternalColliders> colliderData,
 237            EntityInput lastHoverFeedback,
 238            DataStore_ECS7.RaycastEvent lastPointerRayHit,
 239            IInternalECSComponent<InternalInputEventResults> inputResultComponent,
 240            IReadOnlyList<InternalPointerEvents.Entry> entityEvents)
 241        {
 29242            DataStore_ECS7.RaycastEvent.Hit raycastHit = lastPointerRayHit.hit;
 29243            Ray raycastRay = lastPointerRayHit.ray;
 244
 29245            AddInputResultEvent(
 246                inputResultComponent,
 247                InputAction.IaPointer,
 248                colliderData.scene,
 249                colliderData.entity.entityId,
 250                raycastRay,
 251                raycastHit,
 252                PointerEventType.PetHoverEnter,
 253                entityEvents
 254            );
 255
 29256            lastHoverFeedback.hasValue = true;
 29257            lastHoverFeedback.sceneNumber = colliderData.scene.sceneData.sceneNumber;
 29258            lastHoverFeedback.scene = colliderData.scene;
 29259            lastHoverFeedback.entityId = colliderData.entity.entityId;
 29260            lastHoverFeedback.pointerEvents = entityEvents;
 29261        }
 262
 263        // Sent input result to other scenes
 264        private static void BroadcastInputResultEvent(
 265            IInternalECSComponent<InternalInputEventResults> inputResultComponent,
 266            IReadOnlyList<IParcelScene> scenes,
 267            InputAction buttonId,
 268            Ray ray,
 269            DataStore_ECS7.RaycastEvent.Hit raycastHit,
 270            PointerEventType pointerEventType,
 271            IParcelScene skipScene = null)
 272        {
 146273            for (int i = 0; i < scenes.Count; i++)
 274            {
 42275                if (scenes[i] != skipScene)
 276                {
 13277                    AddInputResultEvent(inputResultComponent, buttonId, scenes[i], -1, ray, raycastHit, pointerEventType
 278                }
 279            }
 31280        }
 281
 282        private static void AddInputResultEvent(
 283            IInternalECSComponent<InternalInputEventResults> inputResultComponent,
 284            InputAction buttonId,
 285            IParcelScene scene,
 286            long entityId,
 287            Ray ray,
 288            DataStore_ECS7.RaycastEvent.Hit raycastHit,
 289            PointerEventType pointerEventType,
 290            IReadOnlyList<InternalPointerEvents.Entry> entityEvents)
 291        {
 83292            RaycastHit hitInfo = null;
 293
 294            // If entity has pointer event component for this `pointerEventType` we setup the `hit` data
 295            // otherwise we leave it empty (global input)
 83296            if (HasInputEvent(entityEvents, pointerEventType, buttonId, raycastHit.distance))
 297            {
 15298                ray.origin = WorldStateUtils.ConvertUnityToScenePosition(ray.origin, scene);
 299
 15300                hitInfo = ProtoConvertUtils.ToPBRaycasHit(
 301                    entityId,
 302                    null,
 303                    ray,
 304                    raycastHit.distance,
 305                    WorldStateUtils.ConvertUnityToScenePosition(raycastHit.point, scene),
 306                    raycastHit.normal);
 307            }
 308
 309            // If entity does not have pointer event component for this `pointerEventType` we ignore the event
 310            // so it's not send to the scene
 68311            else if (pointerEventType == PointerEventType.PetHoverEnter || pointerEventType == PointerEventType.PetHover
 312            {
 37313                return;
 314            }
 315
 46316            inputResultComponent.AddEvent(scene, new InternalInputEventResults.EventData()
 317            {
 318                button = buttonId,
 319                hit = hitInfo,
 320                type = pointerEventType
 321            });
 46322        }
 323
 324        private static ECSComponentData<InternalColliders>? GetEntityWithCollider(
 325            IInternalECSComponent<InternalColliders> pointerColliderComponent,
 326            Collider collider)
 327        {
 43328            var collidersData = pointerColliderComponent.GetForAll();
 329
 104330            for (int i = 0; i < collidersData.Count; i++)
 331            {
 52332                var colliderData = collidersData[i].value;
 333
 52334                if (colliderData.model.colliders.ContainsKey(collider))
 335                {
 43336                    return colliderData;
 337                }
 338            }
 339
 0340            return null;
 341        }
 342
 343        private static void ShowHoverTooltips(IECSInteractionHoverCanvas canvas,
 344            IReadOnlyList<InternalPointerEvents.Entry> entityEvents, bool[] buttonState, float distance, bool isAnyButto
 345        {
 30346            if (entityEvents is null)
 0347                return;
 348
 30349            int tooltipIndex = -1;
 30350            bool shouldAdd = false;
 351            InternalPointerEvents.Entry pointerEvent;
 352
 134353            for (int eventIndex = 0; eventIndex < entityEvents.Count; eventIndex++)
 354            {
 37355                shouldAdd = false;
 37356                pointerEvent = entityEvents[eventIndex];
 357
 37358                if (!pointerEvent.EventInfo.ShowFeedback || distance > pointerEvent.EventInfo.MaxDistance)
 359                    continue;
 360
 9361                int buttonId = (int)pointerEvent.EventInfo.Button;
 362
 9363                if (buttonId == (int)InputAction.IaAny)
 364                {
 2365                    if (
 366                        (isAnyButtonDown && pointerEvent.EventType == PointerEventType.PetUp) ||
 367                        (!isAnyButtonDown && pointerEvent.EventType == PointerEventType.PetDown))
 368                    {
 1369                        shouldAdd = true;
 370                    }
 371                }
 7372                else if (buttonId >= 0 && buttonId < buttonState.Length)
 373                {
 7374                    bool buttonIsDown = buttonState[buttonId];
 375
 7376                    if ((pointerEvent.EventType == PointerEventType.PetDown && !buttonIsDown)
 377                        || (pointerEvent.EventType == PointerEventType.PetUp && buttonIsDown))
 378                    {
 4379                        shouldAdd = true;
 380                    }
 381                }
 382
 9383                if (shouldAdd)
 384                {
 5385                    tooltipIndex++;
 5386                    canvas.SetTooltipText(tooltipIndex, pointerEvent.EventInfo.HoverText);
 5387                    canvas.SetTooltipInput(tooltipIndex, pointerEvent.EventInfo.Button);
 5388                    canvas.SetTooltipActive(tooltipIndex, true);
 389                }
 390            }
 391
 392            // first tooltip free
 86393            for (int i = tooltipIndex + 1; i < canvas.tooltipsCount; i++)
 394            {
 13395                canvas.SetTooltipActive(i, false);
 396            }
 397
 30398            if (tooltipIndex != -1)
 399            {
 5400                canvas.Show();
 401            }
 402            else
 403            {
 25404                canvas.Hide();
 405            }
 25406        }
 407
 408        private static bool IsValidInputForUnlockingUiPrompts(InputAction inputAction)
 409        {
 0410            return inputAction == InputAction.IaPointer
 411                   || inputAction == InputAction.IaPrimary
 412                   || inputAction == InputAction.IaSecondary
 413                   || inputAction == InputAction.IaAction3
 414                   || inputAction == InputAction.IaAction4
 415                   || inputAction == InputAction.IaAction5
 416                   || inputAction == InputAction.IaAction6;
 417        }
 418
 419        private static bool HasInputEvent(
 420            IReadOnlyList<InternalPointerEvents.Entry> entityEvents,
 421            PointerEventType pointerEventType,
 422            InputAction actionButton,
 423            float distance)
 424        {
 83425            if (entityEvents == null)
 35426                return false;
 427
 178428            for (int i = 0; i < entityEvents.Count; i++)
 429            {
 56430                var inputEventEntry = entityEvents[i];
 431
 56432                if (inputEventEntry.EventInfo.Button != actionButton
 433                    && inputEventEntry.EventInfo.Button != InputAction.IaAny)
 434                    continue;
 435
 33436                if (inputEventEntry.EventType != pointerEventType)
 437                    continue;
 438
 20439                if (distance <= inputEventEntry.EventInfo.MaxDistance)
 15440                    return true;
 441            }
 442
 33443            return false;
 444        }
 445    }
 446}

Methods/Properties

ECSPointerInputSystem()
ECSPointerInputSystem(.IInternalECSComponent[InternalColliders], .IInternalECSComponent[InternalInputEventResults], .IInternalECSComponent[InternalPointerEvents], IECSInteractionHoverCanvas, DCL.IWorldState, DCL.DataStore_ECS7)
Update()
IsColliderDifferent(ECSSystems.PointerInputSystem.ECSPointerInputSystem/EntityInput, System.Nullable[ECSComponentData`1])
IsColliderMissing(ECSSystems.PointerInputSystem.ECSPointerInputSystem/EntityInput, System.Nullable[ECSComponentData`1])
IsColliderAvailable(ECSSystems.PointerInputSystem.ECSPointerInputSystem/EntityInput, System.Nullable[ECSComponentData`1])
HandleColliderChanged(DCL.ECSRuntime.ECSComponentData[InternalColliders], ECSSystems.PointerInputSystem.ECSPointerInputSystem/EntityInput, DCL.DataStore_ECS7/RaycastEvent, IECSInteractionHoverCanvas, .IInternalECSComponent[InternalInputEventResults], DCL.IWorldState, System.Collections.Generic.IReadOnlyList[Entry])
HandleMissingCollider(ECSSystems.PointerInputSystem.ECSPointerInputSystem/EntityInput, DCL.DataStore_ECS7/RaycastEvent, IECSInteractionHoverCanvas, .IInternalECSComponent[InternalInputEventResults], DCL.IWorldState)
HandleAvailableCollider(DCL.ECSRuntime.ECSComponentData[InternalColliders], ECSSystems.PointerInputSystem.ECSPointerInputSystem/EntityInput, DCL.DataStore_ECS7/RaycastEvent, .IInternalECSComponent[InternalInputEventResults], System.Collections.Generic.IReadOnlyList[Entry])
BroadcastInputResultEvent(.IInternalECSComponent[InternalInputEventResults], System.Collections.Generic.IReadOnlyList[IParcelScene], DCL.ECSComponents.InputAction, UnityEngine.Ray, DCL.DataStore_ECS7/RaycastEvent/Hit, DCL.ECSComponents.PointerEventType, DCL.Controllers.IParcelScene)
AddInputResultEvent(.IInternalECSComponent[InternalInputEventResults], DCL.ECSComponents.InputAction, DCL.Controllers.IParcelScene, System.Int64, UnityEngine.Ray, DCL.DataStore_ECS7/RaycastEvent/Hit, DCL.ECSComponents.PointerEventType, System.Collections.Generic.IReadOnlyList[Entry])
GetEntityWithCollider(.IInternalECSComponent[InternalColliders], UnityEngine.Collider)
ShowHoverTooltips(IECSInteractionHoverCanvas, System.Collections.Generic.IReadOnlyList[Entry], System.Boolean[], System.Single, System.Boolean)
IsValidInputForUnlockingUiPrompts(DCL.ECSComponents.InputAction)
HasInputEvent(System.Collections.Generic.IReadOnlyList[Entry], DCL.ECSComponents.PointerEventType, DCL.ECSComponents.InputAction, System.Single)