using FishNet.Managing; using FishNet.Object; using System.Collections.Generic; using System.Runtime.CompilerServices; using UnityEngine; namespace FishNet.Component.Observing { public class GridEntry { /// /// Position on the grid. /// public Vector2Int Position; /// /// This grid entry as well those neighboring it. /// public HashSet NearbyEntries; public GridEntry() { } public GridEntry(HashSet nearby) { NearbyEntries = nearby; } public void SetValues(Vector2Int position, HashSet nearby) { Position = position; NearbyEntries = nearby; } public void SetValues(HashSet nearby) { NearbyEntries = nearby; } public void SetValues(Vector2Int position) { Position = position; } public void Reset() { Position = Vector2Int.zero; NearbyEntries.Clear(); } } public class HashGrid : MonoBehaviour { #region Types. public enum GridAxes : byte { XY = 0, YZ = 1, XZ = 2, } #endregion #region Internal. /// /// Value for when grid position is not set. /// internal static Vector2Int UnsetGridPosition = (Vector2Int.one * int.MaxValue); /// /// An empty grid entry. /// internal static GridEntry EmptyGridEntry = new(new()); #endregion #region Serialized. /// /// Axes of world space to base the grid on. /// [Tooltip("Axes of world space to base the grid on.")] [SerializeField] private GridAxes _gridAxes = GridAxes.XY; /// /// Accuracy of the grid. Objects will be considered nearby if they are within this number of units. Lower values may be more expensive. /// [Tooltip("Accuracy of the grid. Objects will be considered nearby if they are within this number of units. Lower values may be more expensive.")] [Range(1, ushort.MaxValue)] [SerializeField] private ushort _accuracy = 10; #endregion /// /// Half of accuracy. /// private int _halfAccuracy; /// /// Cache of List. /// private Stack> _gridEntryHashSetCache = new(); /// /// Cache of GridEntrys. /// private Stack _gridEntryCache = new(); /// /// All grid entries. /// private Dictionary _gridEntries = new(); /// /// NetworkManager this is used with. /// private NetworkManager _networkManager; private void Awake() { _networkManager = GetComponentInParent(); if (_networkManager == null) { _networkManager.LogError($"NetworkManager not found on object or within parent of {gameObject.name}. The {GetType().Name} must be placed on or beneath a NetworkManager."); return; } //Make sure there is only one per networkmanager. if (!_networkManager.HasInstance()) { _halfAccuracy = Mathf.CeilToInt((float)_accuracy / 2f); _networkManager.RegisterInstance(this); } else { Destroy(this); } } /// /// Sets out values to be used when creating a new GridEntry. /// private void OutputNewGridCollections(out GridEntry gridEntry, out HashSet gridEntries) { const int cacheCount = 100; if (!_gridEntryHashSetCache.TryPop(out gridEntries)) { BuildGridEntryHashSetCache(); gridEntries = new(); } if (!_gridEntryCache.TryPop(out gridEntry)) { BuildGridEntryCache(); gridEntry = new(); } void BuildGridEntryHashSetCache() { for (int i = 0; i < cacheCount; i++) _gridEntryHashSetCache.Push(new()); } void BuildGridEntryCache() { for (int i = 0; i < cacheCount; i++) _gridEntryCache.Push(new()); } } /// /// Creates a GridEntry for position and inserts it into GridEntries. /// private GridEntry CreateGridEntry(Vector2Int position) { //Make this into a stack that populates a number of entries when empty. also populate with some in awake. GridEntry newEntry; HashSet nearby; OutputNewGridCollections(out newEntry, out nearby); newEntry.SetValues(position, nearby); //Add to grid. _gridEntries[position] = newEntry; //Get neighbors. int endX = (position.x + 1); int endY = (position.y + 1); int iterations = 0; for (int x = (position.x - 1); x <= endX; x++) { for (int y = (position.y - 1); y <= endY; y++) { iterations++; if (_gridEntries.TryGetValue(new(x, y), out GridEntry foundEntry)) { nearby.Add(foundEntry); foundEntry.NearbyEntries.Add(newEntry); } } } return newEntry; } /// /// Gets grid positions and neighbors for a NetworkObject. /// internal void GetNearbyHashGridPositions(NetworkObject nob, ref HashSet collection) { Vector2Int position = GetHashGridPosition(nob); //Get neighbors. int endX = (position.x + 1); int endY = (position.y + 1); for (int x = (position.x - 1); x < endX; x++) { for (int y = (position.y - 1); y < endY; y++) collection.Add(new(x, y)); } } /// /// Gets the grid position to use for a NetworkObjects current position. /// internal Vector2Int GetHashGridPosition(NetworkObject nob) { Vector3 position = nob.transform.position; float fX; float fY; if (_gridAxes == GridAxes.XY) { fX = position.x; fY = position.y; } else if (_gridAxes == GridAxes.XZ) { fX = position.x; fY = position.z; } else if (_gridAxes == GridAxes.YZ) { fX = position.y; fY = position.z; } else { _networkManager.LogError($"GridAxes of {_gridAxes.ToString()} is not handled."); return default; } return new( (int)fX / _halfAccuracy , (int)fY / _halfAccuracy ); } /// /// Gets a GridEntry for a NetworkObject, creating the entry if needed. /// internal GridEntry GetGridEntry(NetworkObject nob) { Vector2Int pos = GetHashGridPosition(nob); return GetGridEntry(pos); } /// /// Gets a GridEntry for position, creating the entry if needed. /// internal GridEntry GetGridEntry(Vector2Int position) { GridEntry result; if (!_gridEntries.TryGetValue(position, out result)) result = CreateGridEntry(position); return result; } } }