ConditionalProcessor.cs 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. using System.Collections.Generic;
  2. using System.Linq;
  3. using GraphProcessor;
  4. using Debug = UnityEngine.Debug;
  5. namespace NodeGraphProcessor.Examples
  6. {
  7. public class ConditionalProcessor : BaseGraphProcessor
  8. {
  9. List< BaseNode > processList;
  10. List< StartNode > startNodeList;
  11. Dictionary<BaseNode, List<BaseNode>> nonConditionalDependenciesCache = new Dictionary<BaseNode, List<BaseNode>>();
  12. public bool pause;
  13. public IEnumerator<BaseNode> currentGraphExecution { get; private set; } = null;
  14. // static readonly float maxExecutionTimeMS = 100; // 100 ms max execution time to avoid infinite loops
  15. /// <summary>
  16. /// Manage graph scheduling and processing
  17. /// </summary>
  18. /// <param name="graph">Graph to be processed</param>
  19. public ConditionalProcessor(BaseGraph graph) : base(graph) {}
  20. public override void UpdateComputeOrder()
  21. {
  22. // Gather start nodes:
  23. startNodeList = graph.nodes.Where(n => n is StartNode).Select(n => n as StartNode).ToList();
  24. // In case there is no start node, we process the graph like usual
  25. if (startNodeList.Count == 0)
  26. {
  27. processList = graph.nodes.OrderBy(n => n.computeOrder).ToList();
  28. }
  29. else
  30. {
  31. nonConditionalDependenciesCache.Clear();
  32. // Prepare the cache of non-conditional node execution
  33. }
  34. }
  35. public override void Run()
  36. {
  37. IEnumerator<BaseNode> enumerator;
  38. if(startNodeList.Count == 0)
  39. {
  40. enumerator = RunTheGraph();
  41. }
  42. else
  43. {
  44. Stack<BaseNode> nodeToExecute = new Stack<BaseNode>();
  45. // Add all the start nodes to the execution stack
  46. startNodeList.ForEach(s => nodeToExecute.Push(s));
  47. // Execute the whole graph:
  48. enumerator = RunTheGraph(nodeToExecute);
  49. }
  50. while(enumerator.MoveNext())
  51. ;
  52. }
  53. private void WaitedRun(Stack<BaseNode> nodesToRun)
  54. {
  55. // Execute the waitable node:
  56. var enumerator = RunTheGraph(nodesToRun);
  57. while(enumerator.MoveNext())
  58. ;
  59. }
  60. IEnumerable<BaseNode> GatherNonConditionalDependencies(BaseNode node)
  61. {
  62. Stack<BaseNode> dependencies = new Stack<BaseNode>();
  63. dependencies.Push(node);
  64. while (dependencies.Count > 0)
  65. {
  66. var dependency = dependencies.Pop();
  67. foreach (var d in dependency.GetInputNodes().Where(n => !(n is IConditionalNode)))
  68. dependencies.Push(d);
  69. if (dependency != node)
  70. yield return dependency;
  71. }
  72. }
  73. private IEnumerator<BaseNode> RunTheGraph()
  74. {
  75. int count = processList.Count;
  76. for(int i = 0; i < count; i++)
  77. {
  78. processList[i].OnProcess();
  79. yield return processList[i];
  80. }
  81. }
  82. private IEnumerator<BaseNode> RunTheGraph(Stack<BaseNode> nodeToExecute)
  83. {
  84. HashSet<BaseNode> nodeDependenciesGathered = new HashSet<BaseNode>();
  85. HashSet<BaseNode> skipConditionalHandling = new HashSet<BaseNode>();
  86. while(nodeToExecute.Count > 0)
  87. {
  88. var node = nodeToExecute.Pop();
  89. // TODO: maxExecutionTimeMS
  90. // In case the node is conditional, then we need to execute it's non-conditional dependencies first
  91. if(node is IConditionalNode && !skipConditionalHandling.Contains(node))
  92. {
  93. // Gather non-conditional deps: TODO, move to the cache:
  94. if(nodeDependenciesGathered.Contains(node))
  95. {
  96. // Execute the conditional node:
  97. node.OnProcess();
  98. yield return node;
  99. // And select the next nodes to execute:
  100. switch(node)
  101. {
  102. // special code path for the loop node as it will execute multiple times the same nodes
  103. case ForLoopNode forLoopNode:
  104. forLoopNode.index = forLoopNode.start - 1; // Initialize the start index
  105. foreach(var n in forLoopNode.GetExecutedNodesLoopCompleted())
  106. nodeToExecute.Push(n);
  107. for(int i = forLoopNode.start; i < forLoopNode.end; i++)
  108. {
  109. foreach(var n in forLoopNode.GetExecutedNodesLoopBody())
  110. nodeToExecute.Push(n);
  111. nodeToExecute.Push(node); // Increment the counter
  112. }
  113. skipConditionalHandling.Add(node);
  114. break;
  115. // another special case for waitable nodes, like "wait for a coroutine", wait x seconds", etc.
  116. case WaitableNode waitableNode:
  117. foreach(var n in waitableNode.GetExecutedNodes())
  118. nodeToExecute.Push(n);
  119. waitableNode.onProcessFinished += (waitedNode) =>
  120. {
  121. Stack<BaseNode> waitedNodes = new Stack<BaseNode>();
  122. foreach(var n in waitedNode.GetExecuteAfterNodes())
  123. waitedNodes.Push(n);
  124. WaitedRun(waitedNodes);
  125. waitableNode.onProcessFinished = null;
  126. };
  127. break;
  128. case IConditionalNode cNode:
  129. foreach(var n in cNode.GetExecutedNodes())
  130. nodeToExecute.Push(n);
  131. break;
  132. default:
  133. Debug.LogError($"Conditional node {node} not handled");
  134. break;
  135. }
  136. nodeDependenciesGathered.Remove(node);
  137. }
  138. else
  139. {
  140. nodeToExecute.Push(node);
  141. nodeDependenciesGathered.Add(node);
  142. foreach(var nonConditionalNode in GatherNonConditionalDependencies(node))
  143. {
  144. nodeToExecute.Push(nonConditionalNode);
  145. }
  146. }
  147. }
  148. else
  149. {
  150. node.OnProcess();
  151. yield return node;
  152. }
  153. }
  154. }
  155. // Advance the execution of the graph of one node, mostly for debug. Doesn't work for WaitableNode's executeAfter port.
  156. public void Step()
  157. {
  158. if (currentGraphExecution == null)
  159. {
  160. Stack<BaseNode> nodeToExecute = new Stack<BaseNode>();
  161. if(startNodeList.Count > 0)
  162. startNodeList.ForEach(s => nodeToExecute.Push(s));
  163. currentGraphExecution = startNodeList.Count == 0 ? RunTheGraph() : RunTheGraph(nodeToExecute);
  164. currentGraphExecution.MoveNext(); // Advance to the first node
  165. }
  166. else
  167. if (!currentGraphExecution.MoveNext())
  168. currentGraphExecution = null;
  169. }
  170. }
  171. }