< Summary

Class:DCLServices.MapRendererV2.Culling.MapCullingController
Assembly:MapRendererV2
File(s):File 1: /tmp/workspace/unity-renderer/unity-renderer/Assets/DCLServices/MapRendererV2/Culling/MapCullingController.cs
File 2: /tmp/workspace/unity-renderer/unity-renderer/Assets/DCLServices/MapRendererV2/Culling/TrackedState.cs
Covered lines:39
Uncovered lines:78
Coverable lines:117
Total lines:314
Line coverage:33.3% (39 of 117)
Covered branches:0
Total branches:0
Covered methods:8
Total methods:27
Method coverage:29.6% (8 of 27)

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
File 1: MapCullingController(...)0%110100%
File 1: SetCameraDirtyInternal(...)0%2100%
File 1: IsCameraDirty(...)0%110100%
File 1: OnCameraAdded(...)0%6200%
File 1: OnCameraRemoved(...)0%12300%
File 1: SetCameraDirty(...)0%6200%
File 1: SetTrackedObjectPositionDirty[T](...)0%6200%
File 1: SetTrackedStateDirty(...)0%6200%
File 1: StartTracking[T](...)0%6200%
File 1: StopTracking[T](...)0%5.673033.33%
File 1: ResolveDirtyCamerasRoutineAsync()0%440100%
File 1: ResolveDirtyCameras()0%7.545053.33%
File 1: ResolveDirtyObjectsRoutineAsync()0%440100%
File 1: ResolveDirtyObjects(...)0%4.123050%
File 1: ResolveDirtyObject(...)0%20400%
File 1: IsTrackedStateDirty(...)0%2100%
File 1: GetCameraIndex(...)0%12300%
File 1: Dispose()0%330100%
File 1: get_CameraStates()0%2100%
File 2: SetCameraFlag(...)0%2100%
File 2: SetCameraFlag(...)0%6200%
File 2: IsCameraDirty(...)0%2100%
File 2: SetVisibleFlag(...)0%2100%
File 2: SetVisibleFlag(...)0%6200%
File 2: get_TrackedObjects()0%2100%

File(s)

/tmp/workspace/unity-renderer/unity-renderer/Assets/DCLServices/MapRendererV2/Culling/MapCullingController.cs

#LineLine coverage
 1using Cysharp.Threading.Tasks;
 2using DCLServices.MapRendererV2.MapCameraController;
 3using System;
 4using System.Collections.Generic;
 5using System.Threading;
 6using UnityEngine.Profiling;
 7
 8namespace DCLServices.MapRendererV2.Culling
 9{
 10    public partial class MapCullingController : IMapCullingController
 11    {
 12        private const int MAX_DIRTY_OBJECTS_PER_FRAME = 10;
 13        private const int MAX_CAMERAS_COUNT = sizeof(int) * 8;
 14
 15        private readonly IMapCullingVisibilityChecker cullingVisibilityChecker;
 9716        private readonly List<CameraState> cameraStates = new ();
 9717        private readonly LinkedList<TrackedState> dirtyObjects = new ();
 9718        private readonly Dictionary<IMapPositionProvider, TrackedState> trackedObjs = new ();
 19
 20        private int dirtyCamerasFlag = 0;
 9721        private CancellationTokenSource disposingCts = new ();
 22
 9723        internal MapCullingController(IMapCullingVisibilityChecker cullingVisibilityChecker)
 24        {
 9725            this.cullingVisibilityChecker = cullingVisibilityChecker;
 9726            ResolveDirtyCamerasRoutineAsync(disposingCts.Token).Forget();
 9727            ResolveDirtyObjectsRoutineAsync(disposingCts.Token).Forget();
 9728        }
 29
 30        private void SetCameraDirtyInternal(int index)
 31        {
 032            dirtyCamerasFlag |= (1 << index);
 033        }
 34
 35        private bool IsCameraDirty(int index) =>
 10268838436            (dirtyCamerasFlag & (1 << index)) > 0;
 37
 38        void IMapCullingController.OnCameraAdded(IMapCameraControllerInternal cameraController)
 39        {
 040            if (cameraStates.Count == MAX_CAMERAS_COUNT)
 041                throw new ArgumentOutOfRangeException(nameof(cameraController), "Camera out of range");
 42
 043            SetCameraDirtyInternal(cameraStates.Count);
 44
 045            var cameraState = new CameraState
 46            {
 47                CameraController = cameraController,
 48                Rect = cameraController.GetCameraRect(),
 49            };
 50
 051            cameraStates.Add(cameraState);
 052        }
 53
 54        void IMapCullingController.OnCameraRemoved(IMapCameraControllerInternal cameraController)
 55        {
 056            int index = GetCameraIndex(cameraController);
 57
 058            if (index == -1)
 059                return;
 60
 061            for (int i = index; i < cameraStates.Count; i++)
 62            {
 063                SetCameraDirtyInternal(i); //We set dirty all the cameras onwards due to index shifting
 64            }
 65
 066            cameraStates.RemoveAt(index);
 067        }
 68
 69        void IMapCullingController.SetCameraDirty(IMapCameraControllerInternal cameraController)
 70        {
 071            int index = GetCameraIndex(cameraController);
 72
 073            if (index == -1)
 074                throw new Exception($"Tried to set not tracked camera dirty");
 75
 076            SetCameraDirtyInternal(index);
 077            cameraStates[index].Rect = cameraController.GetCameraRect();
 078        }
 79
 80        public void SetTrackedObjectPositionDirty<T>(T obj) where T: IMapPositionProvider
 81        {
 082            if (!trackedObjs.TryGetValue(obj, out TrackedState state))
 083                throw new Exception("Tried to set not tracked object dirty");
 84
 085            SetTrackedStateDirty(state);
 086        }
 87
 88        private void SetTrackedStateDirty(TrackedState state)
 89        {
 90            // shifting to the right will add zeroes on the left
 091            state.SetCameraFlag((-1 >> (cameraStates.Count - 1)));
 92
 093            if (IsTrackedStateDirty(state))
 094                return;
 95
 096            dirtyObjects.AddLast(state.nodeInQueue);
 097        }
 98
 99        void IMapCullingController.StartTracking<T>(T obj, IMapCullingListener<T> listener)
 100        {
 0101            if (trackedObjs.ContainsKey(obj))
 0102                return;
 103
 0104            TrackedState<T> state = new TrackedState<T>(obj, listener);
 0105            trackedObjs.Add(obj, state);
 0106            SetTrackedStateDirty(state);
 0107        }
 108
 109        public void StopTracking<T>(T obj) where T: IMapPositionProvider
 110        {
 5760111            if (!trackedObjs.TryGetValue(obj, out var state))
 5760112                return;
 113
 0114            if (IsTrackedStateDirty(state))
 0115                dirtyObjects.Remove(state.nodeInQueue);
 0116            trackedObjs.Remove(obj);
 0117        }
 118
 119        private async UniTaskVoid ResolveDirtyCamerasRoutineAsync(CancellationToken ct)
 120        {
 3208921121            while (true)
 122            {
 3209018123                if (ct.IsCancellationRequested)
 6124                    return;
 125
 3209012126                ResolveDirtyCameras();
 9626945127                await UniTask.Yield(PlayerLoopTiming.PostLateUpdate);
 128            }
 6129        }
 130
 131        private void ResolveDirtyCameras()
 132        {
 3209012133            Profiler.BeginSample(nameof(ResolveDirtyCameras));
 134
 211794792135            for (var i = 0; i < MAX_CAMERAS_COUNT; i++)
 136            {
 102688384137                if (!IsCameraDirty(i))
 138                    continue;
 139
 0140                foreach ((IMapPositionProvider obj, TrackedState cullable) in trackedObjs)
 141                {
 142                    //If index is higher than camera count we have a dirty flag for a no longer tracked camera, we ignor
 0143                    bool visible = i < cameraStates.Count && cullingVisibilityChecker.IsVisible(obj, cameraStates[i]);
 0144                    cullable.SetVisibleFlag(i, visible);
 0145                    cullable.SetCameraFlag(i, false);
 0146                    cullable.CallListener();
 147                }
 148            }
 149
 3209012150            dirtyCamerasFlag = 0;
 151
 3209012152            Profiler.EndSample();
 3209012153        }
 154
 155        private async UniTaskVoid ResolveDirtyObjectsRoutineAsync(CancellationToken ct)
 156        {
 3208921157            while (true)
 158            {
 3209018159                if (ct.IsCancellationRequested)
 6160                    return;
 161
 3209012162                ResolveDirtyObjects(MAX_DIRTY_OBJECTS_PER_FRAME);
 163
 9626945164                await UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate);
 165            }
 6166        }
 167
 168        private void ResolveDirtyObjects(int count)
 169        {
 3209012170            Profiler.BeginSample(nameof(ResolveDirtyObjects));
 171
 3209012172            var stateNode = dirtyObjects.First;
 173
 3209012174            while (count > 0 && stateNode != null)
 175            {
 0176                ResolveDirtyObject(stateNode.Value);
 177
 0178                var processedNode = stateNode;
 0179                stateNode = stateNode.Next;
 180
 181                // Remove the processed node
 182                // it will nullify `List` reference
 0183                dirtyObjects.Remove(processedNode);
 184
 0185                count--;
 186            }
 187
 3209012188            Profiler.EndSample();
 3209012189        }
 190
 191        private void ResolveDirtyObject(TrackedState state)
 192        {
 0193            for (int i = 0; i < MAX_CAMERAS_COUNT; i++)
 194            {
 0195                if (!state.IsCameraDirty(i))
 196                    continue;
 197
 198                //If index is higher than camera count we have a dirty flag for a no longer tracked camera, we ignore it
 0199                bool visible = i < cameraStates.Count && cullingVisibilityChecker.IsVisible(state.Obj, cameraStates[i]);
 0200                state.SetVisibleFlag(i, visible);
 0201                state.SetCameraFlag(i, false);
 0202                state.CallListener();
 203            }
 0204        }
 205
 206        private bool IsTrackedStateDirty(TrackedState state) =>
 0207            state.nodeInQueue.List != null;
 208
 209        private int GetCameraIndex(IMapCameraControllerInternal cameraController)
 210        {
 0211            for (var i = 0; i < cameraStates.Count; i++)
 212            {
 0213                if (cameraStates[i].CameraController == cameraController)
 0214                    return i;
 215            }
 216
 0217            return -1;
 218        }
 219
 220        public void Dispose()
 221        {
 6222            disposingCts?.Cancel();
 6223            disposingCts?.Dispose();
 6224            disposingCts = null;
 6225        }
 226
 0227        IReadOnlyList<CameraState> IMapCullingController.CameraStates => cameraStates;
 228    }
 229}

/tmp/workspace/unity-renderer/unity-renderer/Assets/DCLServices/MapRendererV2/Culling/TrackedState.cs

#LineLine coverage
 1using System.Collections.Generic;
 2
 3namespace DCLServices.MapRendererV2.Culling
 4{
 5    public partial class MapCullingController
 6    {
 7        internal partial class TrackedState<T> : TrackedState where T: IMapPositionProvider
 8        {
 9            private readonly T obj;
 10            public override IMapPositionProvider Obj => obj;
 11
 12            private readonly IMapCullingListener<T> listener;
 13            private bool? lastCalledWasVisible = null;
 14
 15            public TrackedState(T obj, IMapCullingListener<T> listener)
 16            {
 17                nodeInQueue = new LinkedListNode<TrackedState>(this);
 18                this.obj = obj;
 19                this.listener = listener;
 20            }
 21
 22            public override void CallListener(bool forceCall = false)
 23            {
 24                bool currentVisible = visibleFlag > 0;
 25
 26                if (!forceCall)
 27                {
 28                    if (currentVisible == lastCalledWasVisible)
 29                        return;
 30                }
 31
 32                lastCalledWasVisible = currentVisible;
 33
 34                if (currentVisible)
 35                    listener.OnMapObjectBecameVisible(obj);
 36                else
 37                    listener.OnMapObjectCulled(obj);
 38            }
 39        }
 40
 41        internal abstract class TrackedState
 42        {
 43            public abstract IMapPositionProvider Obj { get; }
 44
 45            // Constant memory footprint => we don't add and remove State but keep a single instance of Node instead
 046            public LinkedListNode<TrackedState> nodeInQueue { get; protected set; }
 47
 48            protected int visibleFlag;
 49            protected int dirtyCamerasFlag;
 50
 51            public abstract void CallListener(bool forceCall = false);
 52
 53            public void SetCameraFlag(int value)
 54            {
 055                dirtyCamerasFlag = value;
 056            }
 57
 58            public void SetCameraFlag(int index, bool value)
 59            {
 060                if (value)
 061                    dirtyCamerasFlag |= (1 << index);
 62                else
 063                    dirtyCamerasFlag &= ~(1 << index);
 064            }
 65
 66            public bool IsCameraDirty(int index) =>
 067                (dirtyCamerasFlag & (1 << index)) > 0;
 68
 69            public void SetVisibleFlag(int value)
 70            {
 071                visibleFlag = value;
 072            }
 73
 74            public void SetVisibleFlag(int index, bool value)
 75            {
 076                if (value)
 077                    visibleFlag |= (1 << index);
 78                else
 079                    visibleFlag &= ~(1 << index);
 080            }
 81        }
 82
 083        IReadOnlyDictionary<IMapPositionProvider, TrackedState> IMapCullingController.TrackedObjects => trackedObjs;
 84    }
 85}