-
-
Notifications
You must be signed in to change notification settings - Fork 65
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
perf(Visibility): adding SpatialHash components
- Loading branch information
1 parent
a6384f5
commit 3d46d9f
Showing
5 changed files
with
309 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
238 changes: 238 additions & 0 deletions
238
Assets/Mirage/Components/Visibility/SpatialHash/SpatialHashSystem.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,238 @@ | ||
using System.Collections.Generic; | ||
using System.Runtime.CompilerServices; | ||
using UnityEngine; | ||
|
||
namespace Mirage.Visibility.SpatialHash | ||
{ | ||
internal static class SpatialHashExtensions | ||
{ | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
internal static Vector2 ToXZ(this Vector3 v) => new Vector2(v.x, v.z); | ||
} | ||
|
||
public class SpatialHashSystem : MonoBehaviour | ||
{ | ||
public NetworkServer Server; | ||
|
||
/// <summary> | ||
/// How often (in seconds) that this object should update the list of observers that can see it. | ||
/// </summary> | ||
[Tooltip("How often (in seconds) that this object should update the list of observers that can see it.")] | ||
public float VisibilityUpdateInterval = 1; | ||
|
||
[Tooltip("height and width of 1 box in grid")] | ||
public float gridSize = 10; | ||
|
||
public Vector2 Centre = new Vector2(0, 0); | ||
|
||
[Tooltip("Bounds of the map used to calculate visibility. Objects out side of grid will not be visibility")] | ||
public Vector2 Size = new Vector2(100, 100); | ||
|
||
// todo is list vs hashset better? Set would be better for remove objects, list would be better for looping | ||
List<SpatialHashVisibility> all = new List<SpatialHashVisibility>(); | ||
public GridHolder<INetworkPlayer> Grid; | ||
|
||
|
||
public void Awake() | ||
{ | ||
Server.Started.AddListener(() => | ||
{ | ||
Server.World.onSpawn += World_onSpawn; | ||
Server.World.onUnspawn += World_onUnspawn; | ||
// skip first invoke, list will be empty | ||
InvokeRepeating(nameof(RebuildObservers), VisibilityUpdateInterval, VisibilityUpdateInterval); | ||
Grid = new GridHolder<INetworkPlayer>(gridSize, Centre, Size); | ||
}); | ||
|
||
Server.Stopped.AddListener(() => | ||
{ | ||
CancelInvoke(nameof(RebuildObservers)); | ||
Grid = null; | ||
}); | ||
} | ||
|
||
private void World_onSpawn(NetworkIdentity identity) | ||
{ | ||
NetworkVisibility visibility = identity.Visibility; | ||
if (visibility is SpatialHashVisibility obj) | ||
{ | ||
obj.System = this; | ||
all.Add(obj); | ||
} | ||
} | ||
private void World_onUnspawn(NetworkIdentity identity) | ||
{ | ||
NetworkVisibility visibility = identity.Visibility; | ||
if (visibility is SpatialHashVisibility obj) | ||
{ | ||
all.Remove(obj); | ||
} | ||
} | ||
|
||
void RebuildObservers() | ||
{ | ||
ClearGrid(); | ||
AddPlayersToGrid(); | ||
|
||
foreach (SpatialHashVisibility obj in all) | ||
{ | ||
obj.Identity.RebuildObservers(false); | ||
} | ||
} | ||
|
||
private void ClearGrid() | ||
{ | ||
for (int i = 0; i < Grid.Width; i++) | ||
{ | ||
for (int j = 0; j < Grid.Width; j++) | ||
{ | ||
HashSet<INetworkPlayer> set = Grid.GetObjects(i, j); | ||
if (set != null) | ||
{ | ||
set.Clear(); | ||
} | ||
} | ||
} | ||
} | ||
|
||
private void AddPlayersToGrid() | ||
{ | ||
foreach (INetworkPlayer player in Server.Players) | ||
{ | ||
if (!player.HasCharacter) | ||
continue; | ||
|
||
Vector2 position = player.Identity.transform.position.ToXZ(); | ||
Grid.AddObject(position, player); | ||
} | ||
} | ||
|
||
|
||
public class GridHolder<T> | ||
{ | ||
public readonly int Width; | ||
public readonly int Height; | ||
public readonly float GridSize; | ||
public readonly Vector2 Centre; | ||
public readonly Vector2 Size; | ||
|
||
public readonly GridPoint[] Points; | ||
|
||
public GridHolder(float gridSize, Vector2 centre, Vector2 size) | ||
{ | ||
Centre = centre; | ||
Size = size; | ||
Width = Mathf.CeilToInt(size.x / gridSize); | ||
Height = Mathf.CeilToInt(size.y / gridSize); | ||
GridSize = gridSize; | ||
|
||
Points = new GridPoint[Width * Height]; | ||
} | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public void AddObject(Vector2 position, T obj) | ||
{ | ||
ToGridIndex(position, out int x, out int y); | ||
AddObject(x, y, obj); | ||
} | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public void AddObject(int i, int j, T obj) | ||
{ | ||
int index = i + j * Width; | ||
if (Points[index].objects == null) | ||
{ | ||
Points[index].objects = new HashSet<T>(); | ||
} | ||
|
||
Points[index].objects.Add(obj); | ||
} | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public HashSet<T> GetObjects(int i, int j) | ||
{ | ||
return Points[i + j * Width].objects; | ||
} | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public void CreateSet(int i, int j) | ||
{ | ||
Points[i + j * Width].objects = new HashSet<T>(); | ||
} | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool InBounds(Vector2 position) | ||
{ | ||
float x = position.x - Centre.x; | ||
float y = position.y - Centre.y; | ||
|
||
return (0 < x && x < Size.x) | ||
&& (0 < y && y < Size.y); | ||
} | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool InBounds(int x, int y) | ||
{ | ||
return (0 < x && x < Width) | ||
&& (0 < y && y < Height); | ||
} | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool IsVisible(Vector2 target, Vector2 player, int range) | ||
{ | ||
// if either is out of bounds, not visible | ||
if (!InBounds(target) || !InBounds(player)) return false; | ||
|
||
ToGridIndex(target, out int xt, out int yt); | ||
ToGridIndex(player, out int xp, out int yp); | ||
|
||
return AreClose(xt, xp, range) && AreClose(yt, yp, range); | ||
} | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
bool AreClose(int a, int b, int range) | ||
{ | ||
int min = a - range; | ||
int max = a + range; | ||
|
||
return max <= b && b <= min; | ||
} | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public void ToGridIndex(Vector2 position, out int x, out int y) | ||
{ | ||
float fx = position.x - Centre.x; | ||
float fy = position.y - Centre.y; | ||
|
||
x = Mathf.RoundToInt(fx / GridSize); | ||
y = Mathf.RoundToInt(fy / GridSize); | ||
} | ||
|
||
public void BuildObservers(HashSet<T> observers, Vector2 position, int range) | ||
{ | ||
// not visible if not in range | ||
if (!InBounds(position)) | ||
return; | ||
|
||
ToGridIndex(position - Centre, out int x, out int y); | ||
|
||
for (int i = x - range; i <= x + range; i++) | ||
{ | ||
for (int j = y - range; j <= y + range; j++) | ||
{ | ||
if (InBounds(i, j)) | ||
{ | ||
observers.UnionWith(GetObjects(i, j)); | ||
} | ||
} | ||
} | ||
} | ||
|
||
public struct GridPoint | ||
{ | ||
public HashSet<T> objects; | ||
} | ||
} | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
Assets/Mirage/Components/Visibility/SpatialHash/SpatialHashSystem.cs.meta
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
41 changes: 41 additions & 0 deletions
41
Assets/Mirage/Components/Visibility/SpatialHash/SpatialHashVisibility.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
using System.Collections.Generic; | ||
using Mirage.Logging; | ||
using UnityEngine; | ||
|
||
namespace Mirage.Visibility.SpatialHash | ||
{ | ||
public class SpatialHashVisibility : NetworkVisibility | ||
{ | ||
static readonly ILogger logger = LogFactory.GetLogger(typeof(SpatialHashVisibility)); | ||
|
||
[Tooltip("How many grid away the player can be to see this object. Real distance is this mutlipled by SpatialHashSystem")] | ||
public int GridVisibleRange = 1; | ||
|
||
public SpatialHashSystem System; | ||
|
||
/// <param name="player">Network connection of a player.</param> | ||
/// <returns>True if the player can see this object.</returns> | ||
public override bool OnCheckObserver(INetworkPlayer player) | ||
{ | ||
if (player.Identity == null) | ||
return false; | ||
|
||
|
||
Vector2 thisPosition = transform.position.ToXZ(); | ||
Vector2 playerPosition = player.Identity.transform.position.ToXZ(); | ||
|
||
return System.Grid.IsVisible(thisPosition, playerPosition, GridVisibleRange); | ||
} | ||
|
||
/// <summary> | ||
/// Callback used by the visibility system to (re)construct the set of observers that can see this object. | ||
/// <para>Implementations of this callback should add network connections of players that can see this object to the observers set.</para> | ||
/// </summary> | ||
/// <param name="observers">The new set of observers for this object.</param> | ||
/// <param name="initialize">True if the set of observers is being built for the first time.</param> | ||
public override void OnRebuildObservers(HashSet<INetworkPlayer> observers, bool initialize) | ||
{ | ||
System.Grid.BuildObservers(observers, transform.position.ToXZ(), GridVisibleRange); | ||
} | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
Assets/Mirage/Components/Visibility/SpatialHash/SpatialHashVisibility.cs.meta
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.