1
0

ConstructionGraphPrototype.cs 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. using System.Diagnostics.CodeAnalysis;
  2. using System.IO;
  3. using Robust.Shared.Prototypes;
  4. using Robust.Shared.Serialization;
  5. namespace Content.Shared.Construction.Prototypes
  6. {
  7. [Prototype]
  8. public sealed partial class ConstructionGraphPrototype : IPrototype, ISerializationHooks
  9. {
  10. private readonly Dictionary<string, ConstructionGraphNode> _nodes = new();
  11. private readonly Dictionary<(string, string), ConstructionGraphNode[]?> _paths = new();
  12. private readonly Dictionary<string, Dictionary<ConstructionGraphNode, ConstructionGraphNode?>> _pathfinding = new();
  13. [ViewVariables]
  14. [IdDataField]
  15. public string ID { get; private set; } = default!;
  16. [DataField("start")]
  17. public string? Start { get; private set; }
  18. [DataField("graph", priority: 0)]
  19. private List<ConstructionGraphNode> _graph = new();
  20. [ViewVariables]
  21. public IReadOnlyDictionary<string, ConstructionGraphNode> Nodes => _nodes;
  22. void ISerializationHooks.AfterDeserialization()
  23. {
  24. _nodes.Clear();
  25. foreach (var graphNode in _graph)
  26. {
  27. if (string.IsNullOrEmpty(graphNode.Name))
  28. {
  29. throw new InvalidDataException($"Name of graph node is null in construction graph {ID}!");
  30. }
  31. _nodes[graphNode.Name] = graphNode;
  32. }
  33. if (string.IsNullOrEmpty(Start) || !_nodes.ContainsKey(Start))
  34. throw new InvalidDataException($"Starting node for construction graph {ID} is null, empty or invalid!");
  35. }
  36. public ConstructionGraphEdge? Edge(string startNode, string nextNode)
  37. {
  38. var start = _nodes[startNode];
  39. return start.GetEdge(nextNode);
  40. }
  41. public bool TryPath(string startNode, string finishNode, [NotNullWhen(true)] out ConstructionGraphNode[]? path)
  42. {
  43. return (path = Path(startNode, finishNode)) != null;
  44. }
  45. public string[]? PathId(string startNode, string finishNode)
  46. {
  47. if (Path(startNode, finishNode) is not {} path)
  48. return null;
  49. var nodes = new string[path.Length];
  50. for (var i = 0; i < path.Length; i++)
  51. {
  52. nodes[i] = path[i].Name;
  53. }
  54. return nodes;
  55. }
  56. public ConstructionGraphNode[]? Path(string startNode, string finishNode)
  57. {
  58. var tuple = (startNode, finishNode);
  59. if (_paths.ContainsKey(tuple))
  60. return _paths[tuple];
  61. // Get graph given the current start.
  62. Dictionary<ConstructionGraphNode, ConstructionGraphNode?> pathfindingForStart;
  63. if (_pathfinding.ContainsKey(startNode))
  64. {
  65. pathfindingForStart = _pathfinding[startNode];
  66. }
  67. else
  68. {
  69. pathfindingForStart = _pathfinding[startNode] = PathsForStart(startNode);
  70. }
  71. // Follow the chain backwards.
  72. var start = _nodes[startNode];
  73. var finish = _nodes[finishNode];
  74. var current = finish;
  75. var path = new List<ConstructionGraphNode>();
  76. while (current != start)
  77. {
  78. // No path.
  79. if (current == null || !pathfindingForStart.ContainsKey(current))
  80. {
  81. // We remember this for next time.
  82. _paths[tuple] = null;
  83. return null;
  84. }
  85. path.Add(current);
  86. current = pathfindingForStart[current];
  87. }
  88. path.Reverse();
  89. return _paths[tuple] = path.ToArray();
  90. }
  91. /// <summary>
  92. /// Uses breadth first search for pathfinding.
  93. /// </summary>
  94. /// <param name="start"></param>
  95. private Dictionary<ConstructionGraphNode, ConstructionGraphNode?> PathsForStart(string start)
  96. {
  97. // TODO: Make this use A* or something, although it's not that important.
  98. var startNode = _nodes[start];
  99. var frontier = new Queue<ConstructionGraphNode>();
  100. var cameFrom = new Dictionary<ConstructionGraphNode, ConstructionGraphNode?>();
  101. frontier.Enqueue(startNode);
  102. cameFrom[startNode] = null;
  103. while (frontier.Count != 0)
  104. {
  105. var current = frontier.Dequeue();
  106. foreach (var edge in current.Edges)
  107. {
  108. var edgeNode = _nodes[edge.Target];
  109. if(cameFrom.ContainsKey(edgeNode)) continue;
  110. frontier.Enqueue(edgeNode);
  111. cameFrom[edgeNode] = current;
  112. }
  113. }
  114. return cameFrom;
  115. }
  116. }
  117. }