//============= Copyright (c) Ludic GmbH, All rights reserved. ============== // // Purpose: Part of the My Behaviour Tree Code // //============================================================================= using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Xml.Serialization; #if UNITY_EDITOR using UnityEditor; #endif using UnityEngine; namespace MyBT { //public class CondictionNode : TaskNode { // Task conditionTask; // Task trueTask; // Task falseTask; //} // used in HandleExecute public enum NodeExecute { // start or restart a node Run, // abort a running node Abort } public enum NodeState { // the node is not initialized NotRunning, // the Node has not been called before FirstRun, // state after FirstRun Running, // this node has to stop in this exec, also all children need to stop Aborting } public enum NodeResult { Undefined, Continue, Succeeded, Failed, Error } [XmlInclude(typeof(Node))] public class Node : ScriptableObject { protected const int recursionLimit = 200; #if UNITY_EDITOR // used in editor inspector [SerializeField] public bool isFolded = true; #endif [SerializeField] public TaskController taskController; // associated with code [SerializeField] protected Token[] tokens = null; [SerializeField] public Token[] closingTokens = null; // associated with this or other nodes [XmlIgnore] public Node parentNode; [SerializeField] public NodeList children = new NodeList(); [SerializeField] public int nodeDepth; [SerializeField] public List> lastResults = new List>(); public float lastResultsTime = 0; // runtime data [SerializeField] public List nodeRuntimeDataList = new List(); [SerializeField] private bool _logStringDisplay = false; public bool logStringDisplay { get { if (taskController != null) { return _logStringDisplay || taskController.overrideLogStringDisplay; } Debug.Log(NodeLogger("logStringDisplay", $"taskController is null, regenerate the nodes!")); return _logStringDisplay; } set { _logStringDisplay = value; } } [SerializeField] private bool _debugChangesActive = false; public bool debugChangesActive { get { if (taskController != null) { return _debugChangesActive || taskController.overrideDebugChangesActive; } Debug.Log(NodeLogger("debugChangesActive", $"taskController is null, regenerate the nodes!")); return _debugChangesActive; } set { _debugChangesActive = value; } } [SerializeField] private bool _debugInternalActive = false; public bool debugInternalActive { get { if (taskController != null) { return _debugInternalActive || taskController.overrideDebugInternalActive; } Debug.Log(NodeLogger("debugInternalActive", $"taskController is null, regenerate the nodes!")); return _debugInternalActive; } set { _debugInternalActive = value; } } public bool hasChildren { get { return ((children != null) && (children.Count > 0)); } } public bool parentFolded { get { if (parentNode != null) { #if UNITY_EDITOR return parentNode.parentFolded || parentNode.isFolded; #else return false; #endif } // top node cannot be folded return false; } } public enum TaskNodeType { TaskNode, TreeNode, CompositeNode, DecoratorNode, ActionNode, RunTreeNode, CommentNode } #region node and task labels, icons, description and colors public static readonly Dictionary NodeTypeDict = new Dictionary { {typeof(Node), TaskNodeType.TaskNode}, {typeof(TreeNode),TaskNodeType.TaskNode}, {typeof(CompositeNode), TaskNodeType.CompositeNode}, {typeof(DecoratorNode), TaskNodeType.DecoratorNode}, {typeof(ActionNode), TaskNodeType.ActionNode}, {typeof(RunTreeNode), TaskNodeType.RunTreeNode}, }; #endregion public virtual void Init(int _nodeDepth, Token[] _tokens) { //, int[] _tokenIndexes) { taskController = null; tokens = _tokens; nodeDepth = _nodeDepth; } public void SetTaskController (TaskController _taskController) { taskController = _taskController; } public virtual bool Add(Node n) { if (children == null) { children = new NodeList(); } // can only have 1 child node if ((this.GetType() == typeof(DecoratorNode)) || (this.GetType() == typeof(TreeNode))) { if (children.Count > 0) { // Debug.LogError($"Node.Add: Error in '{codeLine}' {lineNumber}:{colNumber}\n{this.GetType().Name} can only have 1 sub-element cannot add {n.codeLine}"); return false; } } // add new child to this children.Add(n); // set parent in child n.parentNode = this; return true; } /// /// reset the whole behaviour tree structure /// public virtual void Destroy() { if (debugInternalActive) Debug.Log($"Node.Destroy cl:{codeLine} pr:{parentNode} cc:{children.Count} tl:{tokens?.Length} {(tokens!=null?(tokens.Length>0?tokens[0].location.line:-1):0)}"); if (debugInternalActive) { Debug.Log(NodeLogger("Reset", "")); } tokens = null; closingTokens = null; parentNode = null; children = new NodeList(); for (int i= nodeRuntimeDataList.Count-1; i>-1; i--) { nodeRuntimeDataList[i].Destroy(); } nodeRuntimeDataList = new List(); } public IEnumerable getAllNodesRecursively(bool traverseRunTree) { // Return the parent before its children yield return this; // on a standard node, run the childrens if (!(this is RunTreeNode)) { // Debug.Log("Node.getAllNodesRecursively: Node"); foreach (Node node in this.children) { foreach (Node n in node.getAllNodesRecursively(traverseRunTree)) { yield return n; } } } // on runtree nodes else { // Debug.Log("Node.getAllNodesRecursively: RunTreeNode"); // run if (traverseRunTree) { RunTreeNode node = this as RunTreeNode; if (node.runThisNode != null) { foreach (Node n in node.runThisNode.getAllNodesRecursively(traverseRunTree)) { yield return n; } } else { if (debugInternalActive) Debug.LogError(NodeLogger("Node.getAllNodesRecursively", "cannot traverse RunTreeNode, not bound")); } } // othervise ignore } } public TreeNode GetParentTreeNode () { Node currentNode = this; while (currentNode.GetType() != typeof(TreeNode)) { if (currentNode.parentNode != null) { currentNode = currentNode.parentNode; } } return currentNode as TreeNode; } public int GetNodeTreeDepth () { TreeNode currentNode = GetParentTreeNode(); return currentNode.nodeDepth; } /// /// return wether the state has changed /// public virtual bool PreTick (NodeExecute nodeExecute, NodeRuntimeData nodeRuntimeData) { NodeState oldNodeState = nodeRuntimeData.nodeState; switch (nodeExecute) { case NodeExecute.Run: switch (nodeRuntimeData.nodeState) { case NodeState.NotRunning: nodeRuntimeData.nodeState = NodeState.FirstRun; nodeRuntimeData.nodeResult = NodeResult.Continue; break; case NodeState.FirstRun: nodeRuntimeData.nodeState = NodeState.Running; nodeRuntimeData.nodeResult = NodeResult.Continue; break; case NodeState.Running: nodeRuntimeData.nodeState = NodeState.Running; nodeRuntimeData.nodeResult = NodeResult.Continue; break; case NodeState.Aborting: nodeRuntimeData.nodeState = NodeState.Aborting; nodeRuntimeData.nodeResult = NodeResult.Undefined; break; } break; case NodeExecute.Abort: nodeRuntimeData.nodeState = NodeState.Aborting; nodeRuntimeData.nodeResult = NodeResult.Undefined; break; } return (nodeRuntimeData.nodeState != oldNodeState); } public virtual IEnumerable Tick(NodeRuntimeData parentNodeRuntimeData, int recursions) { yield break; } public virtual void Execute(NodeRuntimeData myNodeRuntimeData, NodeExecute nodeExecute) { } /// /// return wether the state has changed /// public virtual bool PostTick(NodeExecute nodeExecute, NodeRuntimeData nodeRuntimeData) { NodeState oldNodeState = nodeRuntimeData.nodeState; NodeResult oldNodeResult = nodeRuntimeData.nodeResult; switch (nodeExecute) { case NodeExecute.Run: switch (nodeRuntimeData.nodeResult) { case NodeResult.Undefined: Debug.Log(NodeLogger("PostTick", "in undefined result state")); break; case NodeResult.Continue: nodeRuntimeData.nodeState = NodeState.Running; break; case NodeResult.Succeeded: nodeRuntimeData.nodeState = NodeState.NotRunning; break; case NodeResult.Failed: nodeRuntimeData.nodeState = NodeState.NotRunning; break; case NodeResult.Error: nodeRuntimeData.nodeState = NodeState.Aborting; break; } break; case NodeExecute.Abort: nodeRuntimeData.nodeState = NodeState.NotRunning; nodeRuntimeData.nodeResult = NodeResult.Undefined; break; } // copy result to lastResults if ((nodeExecute != NodeExecute.Abort) && (lastResults != null) && (nodeRuntimeDataList != null)) { // clear contents if (lastResultsTime != TaskController.currentUiTime) { foreach (List nrl in lastResults) { nrl.Clear(); } lastResultsTime = TaskController.currentUiTime; } int myIndex = nodeRuntimeDataList.IndexOf(nodeRuntimeData); if (debugChangesActive) Debug.Log(NodeLogger("PostTick", $"frame {Time.time} index {myIndex} result {nodeRuntimeData.nodeResult}")); if (myIndex != -1) { while (lastResults.Count < myIndex+1) { //nodeRuntimeDataList.Count) { lastResults.Add(new List()); } lastResults[myIndex].Add(nodeRuntimeDataList[myIndex].nodeResult); } } return (nodeRuntimeData.nodeState != oldNodeState) || (nodeRuntimeData.nodeResult != oldNodeResult); } public void DetachChildren() { if (children != null) { foreach (Node child in children) { child.DetachChildren(); child.parentNode = null; } children = null; } } public string NodeLogger(string functionName, string appendix, NodeRuntimeData runtimeData=null, bool includeDetails =false) { StringBuilder sb = new StringBuilder(); sb.Append($"{this.GetType().Name.ToString().Trim()}."); if (!string.IsNullOrEmpty(functionName.Trim())) { sb.Append($"{functionName.Trim()} "); } sb.Append($"({this.lineNumber}:{ this.colNumber}): "); RecursionIndention(ref sb, nodeDepth); sb.Append($"{appendix} "); if (includeDetails && (runtimeData != null)) { sb.Append($"State/Result: {MyBtResources.NodeStateLabels[runtimeData.nodeState]}, {MyBtResources.NodeResultLabels[runtimeData.nodeResult]} "); } if ((runtimeData is ActionNodeRuntimeData) && (runtimeData != null)) { ActionNodeRuntimeData actionNodeRuntimeData = (ActionNodeRuntimeData)runtimeData; if (includeDetails) { sb.Append($"(Task: {MyBtResources.NodeStateLabels[actionNodeRuntimeData.nodeState]}, {MyBtResources.NodeResultLabels[actionNodeRuntimeData.nodeResult]}) "); if (this is ActionNode) sb.Append($"(bnd: {((ActionNode)this).isBound} int: {debugInternalActive} chg: {debugChangesActive}) "); } string debugDataString = (actionNodeRuntimeData.logString!=null)? actionNodeRuntimeData.logString.ToString():"undefined"; // string debugDataString = (an.debugData!=null)?an.debugData.ToString():"undefined"; sb.Append($"dbgDt: {debugDataString} "); string userDataString = (actionNodeRuntimeData.userData != null) ? actionNodeRuntimeData.userData.ToString() : "undefined"; sb.Append($"usrDt: {userDataString} "); } return sb.ToString(); } public void RecursionIndention(ref StringBuilder sb, int recursion) { if (recursion != -1) sb.Insert(0, $"{string.Concat(Enumerable.Repeat("|--", recursion+1))} "); } [SerializeField] private int _lineNumberCache; [SerializeField] private int _colNumberCache; [SerializeField] private int _lastLineNumberCache; [SerializeField] private string _codeLineCache; [SerializeField] private string _spacedTextCache; [SerializeField] private string _nodeStringCache; public void GenerateNodeDataCaches () { if ((tokens != null) && (tokens.Length > 0) && (tokens[0] != null) && (tokens[0].location != null)) { _lineNumberCache = tokens[0].location.line; } else { // USING THE NODELOGGER WILL CAUSE A RECURSIVE ERROR IN HERE!!! //Debug.LogError(NodeLogger("lineNumber", $"tokens.Length: {(tokens != null ? tokens.Length : 0)}")); Debug.LogError("Error in lineNumber"); } if ((tokens != null) && (tokens.Length > 0) && (tokens[0] != null) && (tokens[0].location != null)) { _colNumberCache = tokens[0].location.col; } else { Debug.LogError("Error in colNumber"); //Debug.LogError(NodeLogger("colNumber", $"tokens.Length: {(tokens != null ? tokens.Length : 0)}")); } if ((closingTokens != null) && (closingTokens.Length > 0) && (closingTokens[0] != null) && (closingTokens[0].location != null)) { _lastLineNumberCache = closingTokens[0].location.line; } else { if (debugChangesActive) Debug.LogError(NodeLogger("lastLineNumber", $"tokens.Length: {(closingTokens != null ? closingTokens.Length : 0)}")); } StringBuilder sb1 = new StringBuilder(); if ((tokens != null) && (tokens.Length > 0)) { for (int i = 0; i < tokens.Length; i++) { if ((tokens[i] != null) && (tokens[i].location != null)) { sb1.Append(tokens[i].location.code); } else { if (tokens[i] == null) { Debug.LogError("Token undefined!"); } else if (tokens[i].location != null) { Debug.LogError("Token.location undefined!"); } _codeLineCache = "undefined"; } } } else { Debug.LogError("Error in codeLine"); //Debug.LogError(NodeLogger("codeLine", $"{(tokens != null ? tokens.Length : 0)}")); } _codeLineCache = sb1.ToString(); StringBuilder sb2 = new StringBuilder(); // string tokenString = ""; if ((tokens != null) && (tokens.Length > 0)) { for (int i = 0; i < tokens.Length; i++) { // tokenString += " " + Regex.Replace(tokens[i].location.code, @"\t|\n|\r", " "); sb2.Append($" {Regex.Replace(tokens[i].location.code, @"\t|\n|\r", " ")}"); } } // return tokenString; _spacedTextCache = sb2.ToString(); _nodeStringCache = $"{this.GetType().Name.ToString().Trim()} ({this.lineNumber}:{this.colNumber})"; } public int lineNumber { get { return _lineNumberCache; } } public int colNumber { get { return _colNumberCache; } } public int lastLineNumber { get { return _lastLineNumberCache; } } public string codeLine { get { return _codeLineCache; } } public string spacedText { get { return _spacedTextCache; } } public string nodeString { get { return _nodeStringCache; } } public string DebugTree(int curDepth = 0) { string output = new string('-', curDepth) + ToString() + " " + codeLine + "\n"; if (children != null) { foreach (Node taskNode in children) { output += taskNode.DebugTree(curDepth + 1); } } return output; } public override string ToString () { return nodeString; } /// /// TODO!!! /// public Node DuplicateRecursive (int recursion = 0, Node parent=null) { Debug.LogWarning($"DuplicateRecursive {this}"); // TreeNode newRootNode = rootNode.Duplicate(); Node newCurrentNode = Duplicate(); if (parent != null) { parent.Add(newCurrentNode); } if (recursion > 10) { Debug.LogError("Abort Recursion"); return null; } foreach (Node child in children) { Node newChild = child.DuplicateRecursive(recursion + 1, newCurrentNode); // if (parent != null) { // newCurrentNode.Add(newChild); // } } return newCurrentNode; } public virtual Node Duplicate () { Debug.LogError("not implemented"); return null; } public void BaseDuplicate (Node n) { n.parentNode = parentNode; // cannot be copyed, is generated by recursion! // n.children = new NodeList(); n.tokens = tokens; if (closingTokens != null) { n.closingTokens = closingTokens; } else { // Debug.LogWarning("BaseDuplicate closingTokens is null!"); } n.nodeDepth = nodeDepth; //n.nodeResult = nodeResult; n.logStringDisplay = false; //n.logString = logString; //n.nodeState = nodeState; n.debugInternalActive = false; n.debugChangesActive = false; // return n; } } }