| | 1 | | using Cysharp.Threading.Tasks; |
| | 2 | | using DCLServices.MapRendererV2.MapCameraController; |
| | 3 | | using System; |
| | 4 | | using System.Collections.Generic; |
| | 5 | | using System.Threading; |
| | 6 | | using UnityEngine.Profiling; |
| | 7 | |
|
| | 8 | | namespace 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; |
| 97 | 16 | | private readonly List<CameraState> cameraStates = new (); |
| 97 | 17 | | private readonly LinkedList<TrackedState> dirtyObjects = new (); |
| 97 | 18 | | private readonly Dictionary<IMapPositionProvider, TrackedState> trackedObjs = new (); |
| | 19 | |
|
| | 20 | | private int dirtyCamerasFlag = 0; |
| 97 | 21 | | private CancellationTokenSource disposingCts = new (); |
| | 22 | |
|
| 97 | 23 | | internal MapCullingController(IMapCullingVisibilityChecker cullingVisibilityChecker) |
| | 24 | | { |
| 97 | 25 | | this.cullingVisibilityChecker = cullingVisibilityChecker; |
| 97 | 26 | | ResolveDirtyCamerasRoutineAsync(disposingCts.Token).Forget(); |
| 97 | 27 | | ResolveDirtyObjectsRoutineAsync(disposingCts.Token).Forget(); |
| 97 | 28 | | } |
| | 29 | |
|
| | 30 | | private void SetCameraDirtyInternal(int index) |
| | 31 | | { |
| 0 | 32 | | dirtyCamerasFlag |= (1 << index); |
| 0 | 33 | | } |
| | 34 | |
|
| | 35 | | private bool IsCameraDirty(int index) => |
| 102688384 | 36 | | (dirtyCamerasFlag & (1 << index)) > 0; |
| | 37 | |
|
| | 38 | | void IMapCullingController.OnCameraAdded(IMapCameraControllerInternal cameraController) |
| | 39 | | { |
| 0 | 40 | | if (cameraStates.Count == MAX_CAMERAS_COUNT) |
| 0 | 41 | | throw new ArgumentOutOfRangeException(nameof(cameraController), "Camera out of range"); |
| | 42 | |
|
| 0 | 43 | | SetCameraDirtyInternal(cameraStates.Count); |
| | 44 | |
|
| 0 | 45 | | var cameraState = new CameraState |
| | 46 | | { |
| | 47 | | CameraController = cameraController, |
| | 48 | | Rect = cameraController.GetCameraRect(), |
| | 49 | | }; |
| | 50 | |
|
| 0 | 51 | | cameraStates.Add(cameraState); |
| 0 | 52 | | } |
| | 53 | |
|
| | 54 | | void IMapCullingController.OnCameraRemoved(IMapCameraControllerInternal cameraController) |
| | 55 | | { |
| 0 | 56 | | int index = GetCameraIndex(cameraController); |
| | 57 | |
|
| 0 | 58 | | if (index == -1) |
| 0 | 59 | | return; |
| | 60 | |
|
| 0 | 61 | | for (int i = index; i < cameraStates.Count; i++) |
| | 62 | | { |
| 0 | 63 | | SetCameraDirtyInternal(i); //We set dirty all the cameras onwards due to index shifting |
| | 64 | | } |
| | 65 | |
|
| 0 | 66 | | cameraStates.RemoveAt(index); |
| 0 | 67 | | } |
| | 68 | |
|
| | 69 | | void IMapCullingController.SetCameraDirty(IMapCameraControllerInternal cameraController) |
| | 70 | | { |
| 0 | 71 | | int index = GetCameraIndex(cameraController); |
| | 72 | |
|
| 0 | 73 | | if (index == -1) |
| 0 | 74 | | throw new Exception($"Tried to set not tracked camera dirty"); |
| | 75 | |
|
| 0 | 76 | | SetCameraDirtyInternal(index); |
| 0 | 77 | | cameraStates[index].Rect = cameraController.GetCameraRect(); |
| 0 | 78 | | } |
| | 79 | |
|
| | 80 | | public void SetTrackedObjectPositionDirty<T>(T obj) where T: IMapPositionProvider |
| | 81 | | { |
| 0 | 82 | | if (!trackedObjs.TryGetValue(obj, out TrackedState state)) |
| 0 | 83 | | throw new Exception("Tried to set not tracked object dirty"); |
| | 84 | |
|
| 0 | 85 | | SetTrackedStateDirty(state); |
| 0 | 86 | | } |
| | 87 | |
|
| | 88 | | private void SetTrackedStateDirty(TrackedState state) |
| | 89 | | { |
| | 90 | | // shifting to the right will add zeroes on the left |
| 0 | 91 | | state.SetCameraFlag((-1 >> (cameraStates.Count - 1))); |
| | 92 | |
|
| 0 | 93 | | if (IsTrackedStateDirty(state)) |
| 0 | 94 | | return; |
| | 95 | |
|
| 0 | 96 | | dirtyObjects.AddLast(state.nodeInQueue); |
| 0 | 97 | | } |
| | 98 | |
|
| | 99 | | void IMapCullingController.StartTracking<T>(T obj, IMapCullingListener<T> listener) |
| | 100 | | { |
| 0 | 101 | | if (trackedObjs.ContainsKey(obj)) |
| 0 | 102 | | return; |
| | 103 | |
|
| 0 | 104 | | TrackedState<T> state = new TrackedState<T>(obj, listener); |
| 0 | 105 | | trackedObjs.Add(obj, state); |
| 0 | 106 | | SetTrackedStateDirty(state); |
| 0 | 107 | | } |
| | 108 | |
|
| | 109 | | public void StopTracking<T>(T obj) where T: IMapPositionProvider |
| | 110 | | { |
| 5760 | 111 | | if (!trackedObjs.TryGetValue(obj, out var state)) |
| 5760 | 112 | | return; |
| | 113 | |
|
| 0 | 114 | | if (IsTrackedStateDirty(state)) |
| 0 | 115 | | dirtyObjects.Remove(state.nodeInQueue); |
| 0 | 116 | | trackedObjs.Remove(obj); |
| 0 | 117 | | } |
| | 118 | |
|
| | 119 | | private async UniTaskVoid ResolveDirtyCamerasRoutineAsync(CancellationToken ct) |
| | 120 | | { |
| 3208921 | 121 | | while (true) |
| | 122 | | { |
| 3209018 | 123 | | if (ct.IsCancellationRequested) |
| 6 | 124 | | return; |
| | 125 | |
|
| 3209012 | 126 | | ResolveDirtyCameras(); |
| 9626945 | 127 | | await UniTask.Yield(PlayerLoopTiming.PostLateUpdate); |
| | 128 | | } |
| 6 | 129 | | } |
| | 130 | |
|
| | 131 | | private void ResolveDirtyCameras() |
| | 132 | | { |
| 3209012 | 133 | | Profiler.BeginSample(nameof(ResolveDirtyCameras)); |
| | 134 | |
|
| 211794792 | 135 | | for (var i = 0; i < MAX_CAMERAS_COUNT; i++) |
| | 136 | | { |
| 102688384 | 137 | | if (!IsCameraDirty(i)) |
| | 138 | | continue; |
| | 139 | |
|
| 0 | 140 | | 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 |
| 0 | 143 | | bool visible = i < cameraStates.Count && cullingVisibilityChecker.IsVisible(obj, cameraStates[i]); |
| 0 | 144 | | cullable.SetVisibleFlag(i, visible); |
| 0 | 145 | | cullable.SetCameraFlag(i, false); |
| 0 | 146 | | cullable.CallListener(); |
| | 147 | | } |
| | 148 | | } |
| | 149 | |
|
| 3209012 | 150 | | dirtyCamerasFlag = 0; |
| | 151 | |
|
| 3209012 | 152 | | Profiler.EndSample(); |
| 3209012 | 153 | | } |
| | 154 | |
|
| | 155 | | private async UniTaskVoid ResolveDirtyObjectsRoutineAsync(CancellationToken ct) |
| | 156 | | { |
| 3208921 | 157 | | while (true) |
| | 158 | | { |
| 3209018 | 159 | | if (ct.IsCancellationRequested) |
| 6 | 160 | | return; |
| | 161 | |
|
| 3209012 | 162 | | ResolveDirtyObjects(MAX_DIRTY_OBJECTS_PER_FRAME); |
| | 163 | |
|
| 9626945 | 164 | | await UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate); |
| | 165 | | } |
| 6 | 166 | | } |
| | 167 | |
|
| | 168 | | private void ResolveDirtyObjects(int count) |
| | 169 | | { |
| 3209012 | 170 | | Profiler.BeginSample(nameof(ResolveDirtyObjects)); |
| | 171 | |
|
| 3209012 | 172 | | var stateNode = dirtyObjects.First; |
| | 173 | |
|
| 3209012 | 174 | | while (count > 0 && stateNode != null) |
| | 175 | | { |
| 0 | 176 | | ResolveDirtyObject(stateNode.Value); |
| | 177 | |
|
| 0 | 178 | | var processedNode = stateNode; |
| 0 | 179 | | stateNode = stateNode.Next; |
| | 180 | |
|
| | 181 | | // Remove the processed node |
| | 182 | | // it will nullify `List` reference |
| 0 | 183 | | dirtyObjects.Remove(processedNode); |
| | 184 | |
|
| 0 | 185 | | count--; |
| | 186 | | } |
| | 187 | |
|
| 3209012 | 188 | | Profiler.EndSample(); |
| 3209012 | 189 | | } |
| | 190 | |
|
| | 191 | | private void ResolveDirtyObject(TrackedState state) |
| | 192 | | { |
| 0 | 193 | | for (int i = 0; i < MAX_CAMERAS_COUNT; i++) |
| | 194 | | { |
| 0 | 195 | | 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 |
| 0 | 199 | | bool visible = i < cameraStates.Count && cullingVisibilityChecker.IsVisible(state.Obj, cameraStates[i]); |
| 0 | 200 | | state.SetVisibleFlag(i, visible); |
| 0 | 201 | | state.SetCameraFlag(i, false); |
| 0 | 202 | | state.CallListener(); |
| | 203 | | } |
| 0 | 204 | | } |
| | 205 | |
|
| | 206 | | private bool IsTrackedStateDirty(TrackedState state) => |
| 0 | 207 | | state.nodeInQueue.List != null; |
| | 208 | |
|
| | 209 | | private int GetCameraIndex(IMapCameraControllerInternal cameraController) |
| | 210 | | { |
| 0 | 211 | | for (var i = 0; i < cameraStates.Count; i++) |
| | 212 | | { |
| 0 | 213 | | if (cameraStates[i].CameraController == cameraController) |
| 0 | 214 | | return i; |
| | 215 | | } |
| | 216 | |
|
| 0 | 217 | | return -1; |
| | 218 | | } |
| | 219 | |
|
| | 220 | | public void Dispose() |
| | 221 | | { |
| 6 | 222 | | disposingCts?.Cancel(); |
| 6 | 223 | | disposingCts?.Dispose(); |
| 6 | 224 | | disposingCts = null; |
| 6 | 225 | | } |
| | 226 | |
|
| 0 | 227 | | IReadOnlyList<CameraState> IMapCullingController.CameraStates => cameraStates; |
| | 228 | | } |
| | 229 | | } |