NodeGroupSystem.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. using System.Diagnostics;
  2. using System.Linq;
  3. using Content.Server.Administration.Managers;
  4. using Content.Server.NodeContainer.NodeGroups;
  5. using Content.Server.NodeContainer.Nodes;
  6. using Content.Shared.Administration;
  7. using Content.Shared.NodeContainer;
  8. using JetBrains.Annotations;
  9. using Robust.Server.Player;
  10. using Robust.Shared.Enums;
  11. using Robust.Shared.Map.Components;
  12. using Robust.Shared.Player;
  13. using Robust.Shared.Utility;
  14. namespace Content.Server.NodeContainer.EntitySystems
  15. {
  16. /// <summary>
  17. /// Entity system that manages <see cref="NodeGroupSystem"/> and <see cref="Node"/> updating.
  18. /// </summary>
  19. /// <seealso cref="NodeContainerSystem"/>
  20. [UsedImplicitly]
  21. public sealed class NodeGroupSystem : EntitySystem
  22. {
  23. [Dependency] private readonly IPlayerManager _playerManager = default!;
  24. [Dependency] private readonly IAdminManager _adminManager = default!;
  25. [Dependency] private readonly INodeGroupFactory _nodeGroupFactory = default!;
  26. [Dependency] private readonly ILogManager _logManager = default!;
  27. private readonly List<int> _visDeletes = new();
  28. private readonly List<BaseNodeGroup> _visSends = new();
  29. private readonly HashSet<ICommonSession> _visPlayers = new();
  30. private readonly HashSet<BaseNodeGroup> _toRemake = new();
  31. private readonly HashSet<BaseNodeGroup> _nodeGroups = new();
  32. private readonly HashSet<Node> _toRemove = new();
  33. private readonly List<Node> _toReflood = new();
  34. private ISawmill _sawmill = default!;
  35. private const float VisDataUpdateInterval = 1;
  36. private float _accumulatedFrameTime;
  37. public bool VisEnabled => _visPlayers.Count != 0;
  38. private int _gen = 1;
  39. private int _groupNetIdCounter = 1;
  40. /// <summary>
  41. /// If true, UpdateGrid() will not process grids.
  42. /// </summary>
  43. /// <remarks>
  44. /// Useful if something like a large explosion is in the process of shredding the grid, as it avoids uneccesary
  45. /// updating.
  46. /// </remarks>
  47. public bool PauseUpdating = false;
  48. public override void Initialize()
  49. {
  50. base.Initialize();
  51. _sawmill = _logManager.GetSawmill("nodegroup");
  52. _playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
  53. SubscribeNetworkEvent<NodeVis.MsgEnable>(HandleEnableMsg);
  54. }
  55. public override void Shutdown()
  56. {
  57. base.Shutdown();
  58. _playerManager.PlayerStatusChanged -= OnPlayerStatusChanged;
  59. }
  60. private void HandleEnableMsg(NodeVis.MsgEnable msg, EntitySessionEventArgs args)
  61. {
  62. var session = args.SenderSession;
  63. if (!_adminManager.HasAdminFlag(session, AdminFlags.Debug))
  64. return;
  65. if (msg.Enabled)
  66. {
  67. _visPlayers.Add(session);
  68. VisSendFullStateImmediate(session);
  69. }
  70. else
  71. {
  72. _visPlayers.Remove(session);
  73. }
  74. }
  75. private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
  76. {
  77. if (e.NewStatus == SessionStatus.Disconnected)
  78. _visPlayers.Remove(e.Session);
  79. }
  80. public void QueueRemakeGroup(BaseNodeGroup group)
  81. {
  82. if (group.Remaking)
  83. return;
  84. _toRemake.Add(group);
  85. group.Remaking = true;
  86. foreach (var node in group.Nodes)
  87. {
  88. QueueReflood(node);
  89. }
  90. if (group.NodeCount == 0)
  91. {
  92. _nodeGroups.Remove(group);
  93. }
  94. }
  95. public void QueueReflood(Node node)
  96. {
  97. if (node.FlaggedForFlood)
  98. return;
  99. _toReflood.Add(node);
  100. node.FlaggedForFlood = true;
  101. }
  102. public void QueueNodeRemove(Node node)
  103. {
  104. _toRemove.Add(node);
  105. }
  106. public void CreateSingleNetImmediate(Node node)
  107. {
  108. if (node.NodeGroup != null)
  109. return;
  110. QueueReflood(node);
  111. InitGroup(node, new List<Node> {node});
  112. }
  113. public override void Update(float frameTime)
  114. {
  115. base.Update(frameTime);
  116. if (!PauseUpdating)
  117. {
  118. DoGroupUpdates();
  119. VisDoUpdate(frameTime);
  120. }
  121. }
  122. // used to manually force an update for the groups
  123. // the VisDoUpdate will be done with the next scheduled update
  124. public void ForceUpdate()
  125. {
  126. DoGroupUpdates();
  127. }
  128. private void DoGroupUpdates()
  129. {
  130. // "Why is there a separate queue for group remakes and node refloods when they both cause eachother"
  131. // Future planning for the potential ability to do more intelligent group updating.
  132. if (_toRemake.Count == 0 && _toReflood.Count == 0 && _toRemove.Count == 0)
  133. return;
  134. var sw = Stopwatch.StartNew();
  135. var xformQuery = GetEntityQuery<TransformComponent>();
  136. var nodeQuery = GetEntityQuery<NodeContainerComponent>();
  137. foreach (var toRemove in _toRemove)
  138. {
  139. if (toRemove.NodeGroup == null)
  140. continue;
  141. var group = (BaseNodeGroup) toRemove.NodeGroup;
  142. group.RemoveNode(toRemove);
  143. toRemove.NodeGroup = null;
  144. QueueRemakeGroup(group);
  145. }
  146. // Break up all remaking groups.
  147. // Don't clear the list yet, we'll come back to these later.
  148. foreach (var toRemake in _toRemake)
  149. {
  150. QueueRemakeGroup(toRemake);
  151. }
  152. _gen += 1;
  153. // Go over all nodes to calculate reachable nodes and make an undirected graph out of them.
  154. // Node.GetReachableNodes() may return results asymmetrically,
  155. // i.e. node A may return B, but B may not return A.
  156. //
  157. // Must be for loop to allow concurrent modification from RemakeGroupImmediate.
  158. for (var i = 0; i < _toReflood.Count; i++)
  159. {
  160. var node = _toReflood[i];
  161. if (node.Deleting)
  162. continue;
  163. ClearReachableIfNecessary(node);
  164. if (node.NodeGroup?.Remaking == false)
  165. {
  166. QueueRemakeGroup((BaseNodeGroup) node.NodeGroup);
  167. }
  168. // GetCompatibleNodes will involve getting the transform & grid as most connection requirements are
  169. // based on position & anchored neighbours However, here more than one node could be attached to the
  170. // same parent. So there is probably a better way of doing this.
  171. foreach (var compatible in GetCompatibleNodes(node, xformQuery, nodeQuery))
  172. {
  173. ClearReachableIfNecessary(compatible);
  174. if (compatible.NodeGroup?.Remaking == false)
  175. {
  176. // We are expanding into an existing group,
  177. // remake it so that we can treat it uniformly.
  178. var group = (BaseNodeGroup) compatible.NodeGroup;
  179. QueueRemakeGroup(group);
  180. }
  181. node.ReachableNodes.Add(compatible);
  182. compatible.ReachableNodes.Add(node);
  183. }
  184. }
  185. var newGroups = new List<BaseNodeGroup>();
  186. // Flood fill over nodes. Every node will only be flood filled once.
  187. foreach (var node in _toReflood)
  188. {
  189. node.FlaggedForFlood = false;
  190. // Check if already flood filled.
  191. if (node.FloodGen == _gen || node.Deleting)
  192. continue;
  193. // Flood fill
  194. var groupNodes = FloodFillNode(node);
  195. var newGroup = InitGroup(node, groupNodes);
  196. newGroups.Add(newGroup);
  197. }
  198. // Go over dead groups that need to be cleaned up.
  199. // Tell them to push their data to new groups too.
  200. foreach (var oldGroup in _toRemake)
  201. {
  202. // Group by the NEW group.
  203. var newGrouped = oldGroup.Nodes.GroupBy(n => n.NodeGroup);
  204. oldGroup.Removed = true;
  205. oldGroup.AfterRemake(newGrouped);
  206. _nodeGroups.Remove(oldGroup);
  207. if (VisEnabled)
  208. _visDeletes.Add(oldGroup.NetId);
  209. }
  210. var refloodCount = _toReflood.Count;
  211. _toReflood.Clear();
  212. _toRemake.Clear();
  213. _toRemove.Clear();
  214. // notify entities that node groups have been updated, so they can do things like update their visuals.
  215. HashSet<EntityUid> entities = new();
  216. foreach (var group in newGroups)
  217. {
  218. foreach (var node in group.Nodes)
  219. {
  220. entities.Add(node.Owner);
  221. }
  222. }
  223. foreach (var uid in entities)
  224. {
  225. var ev = new NodeGroupsRebuilt(uid);
  226. RaiseLocalEvent(uid, ref ev, true);
  227. }
  228. _sawmill.Debug($"Updated node groups in {sw.Elapsed.TotalMilliseconds}ms. {newGroups.Count} new groups, {refloodCount} nodes processed.");
  229. }
  230. private void ClearReachableIfNecessary(Node node)
  231. {
  232. if (node.UndirectGen != _gen)
  233. {
  234. node.ReachableNodes.Clear();
  235. node.UndirectGen = _gen;
  236. }
  237. }
  238. private BaseNodeGroup InitGroup(Node node, List<Node> groupNodes)
  239. {
  240. var newGroup = (BaseNodeGroup) _nodeGroupFactory.MakeNodeGroup(node.NodeGroupID);
  241. newGroup.Initialize(node, EntityManager);
  242. newGroup.NetId = _groupNetIdCounter++;
  243. var netIdCounter = 0;
  244. foreach (var groupNode in groupNodes)
  245. {
  246. groupNode.NodeGroup = newGroup;
  247. groupNode.NetId = ++netIdCounter;
  248. }
  249. newGroup.LoadNodes(groupNodes);
  250. _nodeGroups.Add(newGroup);
  251. if (VisEnabled)
  252. _visSends.Add(newGroup);
  253. return newGroup;
  254. }
  255. private List<Node> FloodFillNode(Node rootNode)
  256. {
  257. // All nodes we're filling into that currently have NO network.
  258. var allNodes = new List<Node>();
  259. var stack = new Stack<Node>();
  260. stack.Push(rootNode);
  261. rootNode.FloodGen = _gen;
  262. while (stack.TryPop(out var node))
  263. {
  264. allNodes.Add(node);
  265. foreach (var reachable in node.ReachableNodes)
  266. {
  267. if (reachable.FloodGen == _gen)
  268. continue;
  269. reachable.FloodGen = _gen;
  270. stack.Push(reachable);
  271. }
  272. }
  273. return allNodes;
  274. }
  275. private IEnumerable<Node> GetCompatibleNodes(Node node, EntityQuery<TransformComponent> xformQuery, EntityQuery<NodeContainerComponent> nodeQuery)
  276. {
  277. var xform = xformQuery.GetComponent(node.Owner);
  278. TryComp<MapGridComponent>(xform.GridUid, out var grid);
  279. if (!node.Connectable(EntityManager, xform))
  280. yield break;
  281. foreach (var reachable in node.GetReachableNodes(xform, nodeQuery, xformQuery, grid, EntityManager))
  282. {
  283. DebugTools.Assert(reachable != node, "GetReachableNodes() should not include self.");
  284. if (reachable.NodeGroupID == node.NodeGroupID
  285. && reachable.Connectable(EntityManager, xformQuery.GetComponent(reachable.Owner)))
  286. {
  287. yield return reachable;
  288. }
  289. }
  290. }
  291. private void VisDoUpdate(float frametime)
  292. {
  293. if (_visPlayers.Count == 0)
  294. return;
  295. _accumulatedFrameTime += frametime;
  296. if (_accumulatedFrameTime < VisDataUpdateInterval
  297. && _visSends.Count == 0
  298. && _visDeletes.Count == 0)
  299. return;
  300. var msg = new NodeVis.MsgData();
  301. msg.GroupDeletions.AddRange(_visDeletes);
  302. msg.Groups.AddRange(_visSends.Select(VisMakeGroupState));
  303. if (_accumulatedFrameTime > VisDataUpdateInterval)
  304. {
  305. _accumulatedFrameTime -= VisDataUpdateInterval;
  306. foreach (var group in _nodeGroups)
  307. {
  308. if (_visSends.Contains(group))
  309. continue;
  310. msg.GroupDataUpdates.Add(group.NetId, group.GetDebugData());
  311. }
  312. }
  313. _visSends.Clear();
  314. _visDeletes.Clear();
  315. foreach (var player in _visPlayers)
  316. {
  317. RaiseNetworkEvent(msg, player.Channel);
  318. }
  319. }
  320. private void VisSendFullStateImmediate(ICommonSession player)
  321. {
  322. var msg = new NodeVis.MsgData();
  323. foreach (var network in _nodeGroups)
  324. {
  325. msg.Groups.Add(VisMakeGroupState(network));
  326. }
  327. RaiseNetworkEvent(msg, player.Channel);
  328. }
  329. private NodeVis.GroupData VisMakeGroupState(BaseNodeGroup group)
  330. {
  331. return new()
  332. {
  333. NetId = group.NetId,
  334. GroupId = group.GroupId.ToString(),
  335. Color = CalcNodeGroupColor(group),
  336. Nodes = group.Nodes.Select(n => new NodeVis.NodeDatum
  337. {
  338. Name = n.Name,
  339. NetId = n.NetId,
  340. Reachable = n.ReachableNodes.Select(r => r.NetId).ToArray(),
  341. Entity = GetNetEntity(n.Owner),
  342. Type = n.GetType().Name
  343. }).ToArray(),
  344. DebugData = group.GetDebugData()
  345. };
  346. }
  347. private static Color CalcNodeGroupColor(BaseNodeGroup group)
  348. {
  349. return group.GroupId switch
  350. {
  351. NodeGroupID.HVPower => Color.Orange,
  352. NodeGroupID.MVPower => Color.Yellow,
  353. NodeGroupID.Apc => Color.LimeGreen,
  354. NodeGroupID.AMEngine => Color.Purple,
  355. NodeGroupID.Pipe => Color.Blue,
  356. NodeGroupID.WireNet => Color.DarkMagenta,
  357. NodeGroupID.Teg => Color.Red,
  358. _ => Color.White
  359. };
  360. }
  361. }
  362. /// <summary>
  363. /// Event raised after node groups have been updated. Directed at any entity with a <see
  364. /// cref="NodeContainerComponent"/> that had a relevant node.
  365. /// </summary>
  366. [ByRefEvent]
  367. public readonly struct NodeGroupsRebuilt
  368. {
  369. public readonly EntityUid NodeOwner;
  370. public NodeGroupsRebuilt(EntityUid nodeOwner)
  371. {
  372. NodeOwner = nodeOwner;
  373. }
  374. }
  375. }