Skip to content

Commit 025a763

Browse files
authored
perf(Spatial Hash IM): Refresh Grid on Interval (#4015)
- Substantial performance increase at scale - Notes added to explain why this is acceptable - Default visRange set to more likely real world value - Tests updated
1 parent 66cd289 commit 025a763

3 files changed

Lines changed: 56 additions & 36 deletions

File tree

Assets/Mirror/Components/InterestManagement/SpatialHashing/SpatialHashing3DInterestManagement.cs

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,19 @@ namespace Mirror
1111
[AddComponentMenu("Network/ Interest Management/ Spatial Hash/Grid Spatial Hash (3D)")]
1212
public class SpatialHashing3DInterestManagement : InterestManagement
1313
{
14-
[Tooltip("The maximum range that objects will be visible at.")]
15-
public int visRange = 30;
14+
[Tooltip("The maximum range that objects will be visible.\nSet to 10-20% larger than camera far clip plane")]
15+
public int visRange = 1200;
1616

1717
// we use a 9 neighbour grid.
1818
// so we always see in a distance of 2 grids.
1919
// for example, our own grid and then one on top / below / left / right.
2020
//
2121
// this means that grid resolution needs to be distance / 2.
22-
// so for example, for distance = 30 we see 2 cells = 15 * 2 distance.
22+
// so for example, for distance = 1200 we see 2 cells = 600 * 2 distance.
2323
//
2424
// on first sight, it seems we need distance / 3 (we see left/us/right).
2525
// but that's not the case.
26-
// resolution would be 10, and we only see 1 cell far, so 10+10=20.
26+
// resolution would be 400, and we only see 1 cell far, so 400+400=800.
2727
public int resolution => visRange / 2; // same as XY because if XY is rotated 90 degree for 3D, it's still the same distance
2828

2929
[Tooltip("Rebuild all every 'rebuildInterval' seconds.")]
@@ -77,22 +77,41 @@ internal void Update()
7777
// NOTE: unlike Scene/MatchInterestManagement, this rebuilds ALL
7878
// entities every INTERVAL. consider the other approach later.
7979

80-
// IMPORTANT: refresh grid every update!
80+
// Old Notes: refresh grid every update!
8181
// => newly spawned entities get observers assigned via
8282
// OnCheckObservers. this can happen any time and we don't want
8383
// them broadcast to old (moved or destroyed) connections.
8484
// => players do move all the time. we want them to always be in the
8585
// correct grid position.
8686
// => note that the actual 'rebuildall' doesn't need to happen all
8787
// the time.
88-
// NOTE: consider refreshing grid only every 'interval' too. but not
89-
// for now. stability & correctness matter.
88+
89+
// Updated Notes: refresh grid and RebuildAll every interval.
90+
// Real world application would have visRange larger than camera
91+
// far clip plane, e.g. 1200, and a player movement speed of ~10m/sec
92+
// so they typically won't cross cell boundaries quickly. If users notice
93+
// flickering or mis-positioning, they can decrease the interval.
9094

9195
// clear old grid results before we update everyone's position.
9296
// (this way we get rid of destroyed connections automatically)
9397
//
9498
// NOTE: keeps allocated HashSets internally.
9599
// clearing & populating every frame works without allocations
100+
101+
// rebuild all spawned entities' observers every 'interval'
102+
// this will call OnRebuildObservers which then returns the
103+
// observers at grid[position] for each entity.
104+
if (NetworkTime.localTime >= lastRebuildTime + rebuildInterval)
105+
{
106+
RefreshGrid();
107+
RebuildAll();
108+
lastRebuildTime = NetworkTime.localTime;
109+
}
110+
}
111+
112+
// (internal so we can update from tests)
113+
internal void RefreshGrid()
114+
{
96115
grid.ClearNonAlloc();
97116

98117
// put every connection into the grid at it's main player's position
@@ -109,15 +128,6 @@ internal void Update()
109128
grid.Add(position, connection);
110129
}
111130
}
112-
113-
// rebuild all spawned entities' observers every 'interval'
114-
// this will call OnRebuildObservers which then returns the
115-
// observers at grid[position] for each entity.
116-
if (NetworkTime.localTime >= lastRebuildTime + rebuildInterval)
117-
{
118-
RebuildAll();
119-
lastRebuildTime = NetworkTime.localTime;
120-
}
121131
}
122132

123133
#if UNITY_EDITOR || (!UNITY_SERVER && DEBUG)

Assets/Mirror/Components/InterestManagement/SpatialHashing/SpatialHashingInterestManagement.cs

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,19 @@ namespace Mirror
1111
[AddComponentMenu("Network/ Interest Management/ Spatial Hash/Grid Spatial Hash (2D)")]
1212
public class SpatialHashingInterestManagement : InterestManagement
1313
{
14-
[Tooltip("The maximum range that objects will be visible at.")]
15-
public int visRange = 30;
14+
[Tooltip("The maximum range that objects will be visible.\nSet to 10-20% larger than camera far clip plane")]
15+
public int visRange = 1200;
1616

1717
// we use a 9 neighbour grid.
1818
// so we always see in a distance of 2 grids.
1919
// for example, our own grid and then one on top / below / left / right.
2020
//
2121
// this means that grid resolution needs to be distance / 2.
22-
// so for example, for distance = 30 we see 2 cells = 15 * 2 distance.
22+
// so for example, for distance = 1200 we see 2 cells = 600 * 2 distance.
2323
//
2424
// on first sight, it seems we need distance / 3 (we see left/us/right).
2525
// but that's not the case.
26-
// resolution would be 10, and we only see 1 cell far, so 10+10=20.
26+
// resolution would be 400, and we only see 1 cell far, so 400+400=800.
2727
public int resolution => visRange / 2;
2828

2929
[Tooltip("Rebuild all every 'rebuildInterval' seconds.")]
@@ -87,22 +87,41 @@ internal void Update()
8787
// NOTE: unlike Scene/MatchInterestManagement, this rebuilds ALL
8888
// entities every INTERVAL. consider the other approach later.
8989

90-
// IMPORTANT: refresh grid every update!
90+
// Old Notes: refresh grid every update!
9191
// => newly spawned entities get observers assigned via
9292
// OnCheckObservers. this can happen any time and we don't want
9393
// them broadcast to old (moved or destroyed) connections.
9494
// => players do move all the time. we want them to always be in the
9595
// correct grid position.
9696
// => note that the actual 'rebuildall' doesn't need to happen all
9797
// the time.
98-
// NOTE: consider refreshing grid only every 'interval' too. but not
99-
// for now. stability & correctness matter.
98+
99+
// Updated Notes: refresh grid and RebuildAll every interval.
100+
// Real world application would have visRange larger than camera
101+
// far clip plane, e.g. 1200, and a player movement speed of ~10m/sec
102+
// so they typically won't cross cell boundaries quickly. If users notice
103+
// flickering or mis-positioning, they can decrease the interval.
100104

101105
// clear old grid results before we update everyone's position.
102106
// (this way we get rid of destroyed connections automatically)
103107
//
104108
// NOTE: keeps allocated HashSets internally.
105109
// clearing & populating every frame works without allocations
110+
111+
// rebuild all spawned entities' observers every 'interval'
112+
// this will call OnRebuildObservers which then returns the
113+
// observers at grid[position] for each entity.
114+
if (NetworkTime.localTime >= lastRebuildTime + rebuildInterval)
115+
{
116+
RefreshGrid();
117+
RebuildAll();
118+
lastRebuildTime = NetworkTime.localTime;
119+
}
120+
}
121+
122+
// (internal so we can update from tests)
123+
internal void RefreshGrid()
124+
{
106125
grid.ClearNonAlloc();
107126

108127
// put every connection into the grid at it's main player's position
@@ -119,15 +138,6 @@ internal void Update()
119138
grid.Add(position, connection);
120139
}
121140
}
122-
123-
// rebuild all spawned entities' observers every 'interval'
124-
// this will call OnRebuildObservers which then returns the
125-
// observers at grid[position] for each entity.
126-
if (NetworkTime.localTime >= lastRebuildTime + rebuildInterval)
127-
{
128-
RebuildAll();
129-
lastRebuildTime = NetworkTime.localTime;
130-
}
131141
}
132142

133143
#if UNITY_EDITOR || (!UNITY_SERVER && DEBUG)

Assets/Mirror/Tests/Editor/InterestManagement/InterestManagementTests_SpatialHashing.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,8 @@ public void OutOfRange_Initial()
117117
// A and B are too far from each other
118118
identityB.transform.position = Vector3.right * (aoi.visRange + 1);
119119

120-
// update grid now that positions were changed
121-
aoi.Update();
120+
// Refresh the grid
121+
aoi.RefreshGrid();
122122

123123
// rebuild for boths
124124
NetworkServer.RebuildObservers(identityA, true);
@@ -137,8 +137,8 @@ public void OutOfRange_NotInitial()
137137
// A and B are too far from each other
138138
identityB.transform.position = Vector3.right * (aoi.visRange + 1);
139139

140-
// update grid now that positions were changed
141-
aoi.Update();
140+
// Refresh the grid
141+
aoi.RefreshGrid();
142142

143143
// rebuild for boths
144144
NetworkServer.RebuildObservers(identityA, false);

0 commit comments

Comments
 (0)