Grid2D.cs 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. // Grid2D from uMMORPG: get/set values of type T at any point
  2. // -> not named 'Grid' because Unity already has a Grid type. causes warnings.
  3. // -> struct to avoid memory indirection. it's accessed a lot.
  4. using System.Collections.Generic;
  5. using UnityEngine;
  6. namespace Mirror
  7. {
  8. // struct to avoid memory indirection. it's accessed a lot.
  9. public struct Grid2D<T>
  10. {
  11. // the grid
  12. // note that we never remove old keys.
  13. // => over time, HashSet<T>s will be allocated for every possible
  14. // grid position in the world
  15. // => Clear() doesn't clear them so we don't constantly reallocate the
  16. // entries when populating the grid in every Update() call
  17. // => makes the code a lot easier too
  18. // => this is FINE because in the worst case, every grid position in the
  19. // game world is filled with a player anyway!
  20. readonly Dictionary<Vector2Int, HashSet<T>> grid;
  21. // cache a 9 neighbor grid of vector2 offsets so we can use them more easily
  22. readonly Vector2Int[] neighbourOffsets;
  23. public Grid2D(int initialCapacity)
  24. {
  25. grid = new Dictionary<Vector2Int, HashSet<T>>(initialCapacity);
  26. neighbourOffsets = new[] {
  27. Vector2Int.up,
  28. Vector2Int.up + Vector2Int.left,
  29. Vector2Int.up + Vector2Int.right,
  30. Vector2Int.left,
  31. Vector2Int.zero,
  32. Vector2Int.right,
  33. Vector2Int.down,
  34. Vector2Int.down + Vector2Int.left,
  35. Vector2Int.down + Vector2Int.right
  36. };
  37. }
  38. // helper function so we can add an entry without worrying
  39. public void Add(Vector2Int position, T value)
  40. {
  41. // initialize set in grid if it's not in there yet
  42. if (!grid.TryGetValue(position, out HashSet<T> hashSet))
  43. {
  44. // each grid entry may hold hundreds of entities.
  45. // let's create the HashSet with a large initial capacity
  46. // in order to avoid resizing & allocations.
  47. #if !UNITY_2021_3_OR_NEWER
  48. // Unity 2019 doesn't have "new HashSet(capacity)" yet
  49. hashSet = new HashSet<T>();
  50. #else
  51. hashSet = new HashSet<T>(128);
  52. #endif
  53. grid[position] = hashSet;
  54. }
  55. // add to it
  56. hashSet.Add(value);
  57. }
  58. // helper function to get set at position without worrying
  59. // -> result is passed as parameter to avoid allocations
  60. // -> result is not cleared before. this allows us to pass the HashSet from
  61. // GetWithNeighbours and avoid .UnionWith which is very expensive.
  62. void GetAt(Vector2Int position, HashSet<T> result)
  63. {
  64. // return the set at position
  65. if (grid.TryGetValue(position, out HashSet<T> hashSet))
  66. {
  67. foreach (T entry in hashSet)
  68. result.Add(entry);
  69. }
  70. }
  71. // helper function to get at position and it's 8 neighbors without worrying
  72. // -> result is passed as parameter to avoid allocations
  73. public void GetWithNeighbours(Vector2Int position, HashSet<T> result)
  74. {
  75. // clear result first
  76. result.Clear();
  77. // add neighbours
  78. foreach (Vector2Int offset in neighbourOffsets)
  79. GetAt(position + offset, result);
  80. }
  81. // clear: clears the whole grid
  82. // IMPORTANT: we already allocated HashSet<T>s and don't want to do
  83. // reallocate every single update when we rebuild the grid.
  84. // => so simply remove each position's entries, but keep
  85. // every position in there
  86. // => see 'grid' comments above!
  87. // => named ClearNonAlloc to make it more obvious!
  88. public void ClearNonAlloc()
  89. {
  90. foreach (HashSet<T> hashSet in grid.Values)
  91. hashSet.Clear();
  92. }
  93. }
  94. }