1
0

PvsBenchmark.cs 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. #nullable enable
  2. using System;
  3. using System.Linq;
  4. using System.Threading.Tasks;
  5. using BenchmarkDotNet.Attributes;
  6. using Content.IntegrationTests;
  7. using Content.IntegrationTests.Pair;
  8. using Content.Server.Mind;
  9. using Content.Server.Warps;
  10. using Robust.Shared;
  11. using Robust.Shared.Analyzers;
  12. using Robust.Shared.EntitySerialization;
  13. using Robust.Shared.EntitySerialization.Systems;
  14. using Robust.Shared.GameObjects;
  15. using Robust.Shared.Map;
  16. using Robust.Shared.Player;
  17. using Robust.Shared.Random;
  18. using Robust.Shared.Utility;
  19. namespace Content.Benchmarks;
  20. // This benchmark probably benefits from some accidental cache locality. I,e. the order in which entities in a pvs
  21. // chunk are sent to players matches the order in which the entities were spawned.
  22. //
  23. // in a real mid-late game round, this is probably no longer the case.
  24. // One way to somewhat offset this is to update the NetEntity assignment to assign random (but still unique) NetEntity uids to entities.
  25. // This makes the benchmark run noticeably slower.
  26. [Virtual]
  27. public class PvsBenchmark
  28. {
  29. public const string Map = "Maps/box.yml";
  30. [Params(1, 8, 80)]
  31. public int PlayerCount { get; set; }
  32. private TestPair _pair = default!;
  33. private IEntityManager _entMan = default!;
  34. private ICommonSession[] _players = default!;
  35. private EntityCoordinates[] _spawns = default!;
  36. public int _cycleOffset = 0;
  37. private SharedTransformSystem _sys = default!;
  38. private EntityCoordinates[] _locations = default!;
  39. [GlobalSetup]
  40. public void Setup()
  41. {
  42. #if !DEBUG
  43. ProgramShared.PathOffset = "../../../../";
  44. #endif
  45. PoolManager.Startup();
  46. _pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
  47. _entMan = _pair.Server.ResolveDependency<IEntityManager>();
  48. _pair.Server.CfgMan.SetCVar(CVars.NetPVS, true);
  49. _pair.Server.CfgMan.SetCVar(CVars.ThreadParallelCount, 0);
  50. _pair.Server.CfgMan.SetCVar(CVars.NetPvsAsync, false);
  51. _sys = _entMan.System<SharedTransformSystem>();
  52. SetupAsync().Wait();
  53. }
  54. private async Task SetupAsync()
  55. {
  56. // Spawn the map
  57. _pair.Server.ResolveDependency<IRobustRandom>().SetSeed(42);
  58. await _pair.Server.WaitPost(() =>
  59. {
  60. var path = new ResPath(Map);
  61. var opts = DeserializationOptions.Default with {InitializeMaps = true};
  62. if (!_entMan.System<MapLoaderSystem>().TryLoadMap(path, out _, out _, opts))
  63. throw new Exception("Map load failed");
  64. });
  65. // Get list of ghost warp positions
  66. _spawns = _entMan.AllComponentsList<WarpPointComponent>()
  67. .OrderBy(x => x.Component.Location)
  68. .Select(x => _entMan.GetComponent<TransformComponent>(x.Uid).Coordinates)
  69. .ToArray();
  70. Array.Resize(ref _players, PlayerCount);
  71. // Spawn "Players"
  72. _players = await _pair.Server.AddDummySessions(PlayerCount);
  73. await _pair.Server.WaitPost(() =>
  74. {
  75. var mind = _pair.Server.System<MindSystem>();
  76. for (var i = 0; i < PlayerCount; i++)
  77. {
  78. var pos = _spawns[i % _spawns.Length];
  79. var uid =_entMan.SpawnEntity("MobHuman", pos);
  80. _pair.Server.ConsoleHost.ExecuteCommand($"setoutfit {_entMan.GetNetEntity(uid)} CaptainGear");
  81. mind.ControlMob(_players[i].UserId, uid);
  82. }
  83. });
  84. // Repeatedly move players around so that they "explore" the map and see lots of entities.
  85. // This will populate their PVS data with out-of-view entities.
  86. var rng = new Random(42);
  87. ShufflePlayers(rng, 100);
  88. _pair.Server.PvsTick(_players);
  89. _pair.Server.PvsTick(_players);
  90. var ents = _players.Select(x => x.AttachedEntity!.Value).ToArray();
  91. _locations = ents.Select(x => _entMan.GetComponent<TransformComponent>(x).Coordinates).ToArray();
  92. }
  93. private void ShufflePlayers(Random rng, int count)
  94. {
  95. while (count > 0)
  96. {
  97. ShufflePlayers(rng);
  98. count--;
  99. }
  100. }
  101. private void ShufflePlayers(Random rng)
  102. {
  103. _pair.Server.PvsTick(_players);
  104. var ents = _players.Select(x => x.AttachedEntity!.Value).ToArray();
  105. var locations = ents.Select(x => _entMan.GetComponent<TransformComponent>(x).Coordinates).ToArray();
  106. // Shuffle locations
  107. var n = locations.Length;
  108. while (n > 1)
  109. {
  110. n -= 1;
  111. var k = rng.Next(n + 1);
  112. (locations[k], locations[n]) = (locations[n], locations[k]);
  113. }
  114. _pair.Server.WaitPost(() =>
  115. {
  116. for (var i = 0; i < PlayerCount; i++)
  117. {
  118. _sys.SetCoordinates(ents[i], locations[i]);
  119. }
  120. }).Wait();
  121. _pair.Server.PvsTick(_players);
  122. }
  123. /// <summary>
  124. /// Basic benchmark for PVS in a static situation where nothing moves or gets dirtied..
  125. /// This effectively provides a lower bound on "real" pvs tick time, as it is missing:
  126. /// - PVS chunks getting dirtied and needing to be rebuilt
  127. /// - Fetching component states for dirty components
  128. /// - Compressing & sending network messages
  129. /// - Sending PVS leave messages
  130. /// </summary>
  131. [Benchmark]
  132. public void StaticTick()
  133. {
  134. _pair.Server.PvsTick(_players);
  135. }
  136. /// <summary>
  137. /// Basic benchmark for PVS in a situation where players are teleporting all over the place. This isn't very
  138. /// realistic, but unlike <see cref="StaticTick"/> this will actually also measure the speed of processing dirty
  139. /// chunks and sending PVS leave messages.
  140. /// </summary>
  141. [Benchmark]
  142. public void CycleTick()
  143. {
  144. _cycleOffset = (_cycleOffset + 1) % _players.Length;
  145. _pair.Server.WaitPost(() =>
  146. {
  147. for (var i = 0; i < PlayerCount; i++)
  148. {
  149. _sys.SetCoordinates(_players[i].AttachedEntity!.Value, _locations[(i + _cycleOffset) % _players.Length]);
  150. }
  151. }).Wait();
  152. _pair.Server.PvsTick(_players);
  153. }
  154. }