//============= Copyright (c) Ludic GmbH, All rights reserved. ============== // // Purpose: Part of the My Behaviour Tree Code // //============================================================================= using System; using System.Collections.Generic; using System.Linq; using System.Xml.Serialization; using UnityEngine; namespace MyBT { public enum CompositeParameter { Undefined, Sequence, Selector, Race, Marathon, } // [XmlInclude(typeof(CompositeNode))] public class CompositeNode : Node { [SerializeField] public List parameters; [SerializeField] private CompositeParameter compositeType = CompositeParameter.Undefined; public bool Has(CompositeParameter compositeParameter) { return parameters.Contains(compositeParameter); } public void Init(int _nodeDepth, Token[] _tokens, List stringParameter) { base.Init(_nodeDepth, _tokens); parameters = new List(); for (int i = 0; i < stringParameter.Count; i++) { CompositeParameter compositeParameter = CompositeParameter.Undefined; if (Enum.TryParse(stringParameter[i], out compositeParameter)) { parameters.Add(compositeParameter); } else { UnityEngine.Debug.LogError("CompositeNode." + this + " cannot convert parameter " + stringParameter[i]); //throw new Exception("CompositeNode." + this + " cannot convert parameter " + stringParameter[i]); } } if (Has(CompositeParameter.Race)) { compositeType = CompositeParameter.Race; } else if (Has(CompositeParameter.Marathon)) { compositeType = CompositeParameter.Marathon; } else if (Has(CompositeParameter.Sequence)) { compositeType = CompositeParameter.Sequence; } else if (Has(CompositeParameter.Selector)) { compositeType = CompositeParameter.Selector; } } /// /// reset the whole behaviour tree structure /// public override void Destroy() { base.Destroy(); parameters = null; //activeNodeId = 0; } public override IEnumerable Tick(NodeRuntimeData parentNodeRuntimeData, int recursions) { if (debugInternalActive) { Debug.Log(NodeLogger($"Tick", $"")); } // only work with 1 child && failsave in case we have an infinite loop if (recursions < recursionLimit) { using (NodeRuntimeData myNodeRuntimeData = new NodeRuntimeData(this, parentNodeRuntimeData)) { myNodeRuntimeData.taskHasChanges = false; if (debugInternalActive) Debug.Log(NodeLogger($"Tick.OnStart", $"{myNodeRuntimeData} nodeResult {myNodeRuntimeData.nodeResult} nodeState {myNodeRuntimeData.nodeState}")); switch (compositeType) { /// /// Selector (fallback) node /// /// Runs all Children in sequence: /// - any child running -> continue in loop /// - any child failed -> continue in loop /// - any child success -> abort loop with succeeded result /// - finally -> exit failed (there is no looping) /// /// In pseudocode, the algorithm for a fallback composition is: /// 1 for i from 1 to n do /// 2 childstatus ← Tick(child(i)) /// 3 if childstatus = success /// 4 return success /// 5 end /// 6 return failure /// case CompositeParameter.Selector: //int selectorIndex = 0; myNodeRuntimeData.nodeResult = NodeResult.Continue; for (int selectorIndex = 0; selectorIndex < children.Count; selectorIndex++) { myNodeRuntimeData.currentIndex = selectorIndex; //while (myNodeRuntimeData.nodeResult == NodeResult.Continue) { myNodeRuntimeData.tickExecuteEnumerator = children[selectorIndex].Tick(myNodeRuntimeData, recursions + 1).GetEnumerator(); // must be run before MoveNext myNodeRuntimeData.taskHasChanges = base.PreTick(NodeExecute.Run, myNodeRuntimeData); while ((myNodeRuntimeData.tickExecuteEnumerator != null) && myNodeRuntimeData.tickExecuteEnumerator.MoveNext() && (myNodeRuntimeData.nodeResult == NodeResult.Continue)){ //if (debugInternalActive) Debug.Log(NodeLogger($"Tick.Selector", $"{selectorIndex} exec {children[selectorIndex]} childResult {myNodeRuntimeData.tickEnumerator.Current}")); // selector succeedes when any child succeedes if (myNodeRuntimeData.tickExecuteEnumerator.Current == NodeResult.Succeeded) { myNodeRuntimeData.nodeResult = NodeResult.Succeeded; } // if last selector fails all children have failed if ((myNodeRuntimeData.tickExecuteEnumerator.Current == NodeResult.Failed) && (selectorIndex == children.Count - 1)) { myNodeRuntimeData.nodeResult = NodeResult.Failed; } myNodeRuntimeData.taskHasChanges |= base.PostTick(NodeExecute.Run, myNodeRuntimeData); if (debugInternalActive) Debug.Log(NodeLogger($"Tick.Selector", $"childResult {myNodeRuntimeData.tickExecuteEnumerator.Current} nodeState {myNodeRuntimeData.nodeState} nodeResult {myNodeRuntimeData.nodeResult}")); yield return myNodeRuntimeData.nodeResult; if (myNodeRuntimeData.taskHasChanges && taskController) { taskController.SetUiUpdate(); } if (myNodeRuntimeData.nodeResult != NodeResult.Continue) { yield break; } // this should not be run again if it isnt in runnning mode anymore if (myNodeRuntimeData.nodeState == NodeState.Running) { myNodeRuntimeData.taskHasChanges = PreTick(NodeExecute.Run, myNodeRuntimeData); } } if (myNodeRuntimeData.nodeState != NodeState.Running) { break; } } myNodeRuntimeData.nodeResult = NodeResult.Failed; yield return myNodeRuntimeData.nodeResult; if (debugInternalActive) Debug.Log(NodeLogger($"Tick.Selector", $"finished nodeState {myNodeRuntimeData.nodeState} nodeResult {myNodeRuntimeData.nodeResult}")); yield break; /// /// Sequence node /// /// Starts all Children in sequence: /// - any child running -> continue running /// - any child success -> continue running /// - any child failed -> abort loop with failed result /// - finally -> return success result /// /// In pseudocode, the algorithm for a sequence composition is: /// 1 for i from 1 to n do /// 2 childstatus ← Tick(child(i)) /// 3 if childstatus = failure /// 4 return failure /// 5 end /// 6 return success /// case CompositeParameter.Sequence: myNodeRuntimeData.nodeResult = NodeResult.Continue; for (int sequenceIndex = 0; sequenceIndex < children.Count; sequenceIndex++) { myNodeRuntimeData.currentIndex = sequenceIndex; myNodeRuntimeData.tickExecuteEnumerator = children[sequenceIndex].Tick(myNodeRuntimeData, recursions + 1).GetEnumerator(); // must be run before MoveNext myNodeRuntimeData.taskHasChanges = base.PreTick(NodeExecute.Run, myNodeRuntimeData); while ((myNodeRuntimeData.tickExecuteEnumerator != null) && myNodeRuntimeData.tickExecuteEnumerator.MoveNext() && (myNodeRuntimeData.nodeResult == NodeResult.Continue)) { // if any child fails, return failure if (myNodeRuntimeData.tickExecuteEnumerator.Current == NodeResult.Failed) { myNodeRuntimeData.nodeResult = NodeResult.Failed; } // if the last child succeedes, return succeeded if ((myNodeRuntimeData.tickExecuteEnumerator.Current == NodeResult.Succeeded) && (sequenceIndex == children.Count - 1)) { myNodeRuntimeData.nodeResult = NodeResult.Succeeded; } myNodeRuntimeData.taskHasChanges |= base.PostTick(NodeExecute.Run, myNodeRuntimeData); if (debugInternalActive) Debug.Log(NodeLogger($"Tick.Sequence", $"exec {sequenceIndex} childResult {myNodeRuntimeData.tickExecuteEnumerator.Current} nodeResult {myNodeRuntimeData.nodeResult}")); yield return myNodeRuntimeData.nodeResult; if (myNodeRuntimeData.taskHasChanges && taskController) { taskController.SetUiUpdate(); } if (myNodeRuntimeData.nodeResult != NodeResult.Continue) { yield break; } // this should not be run again if it isnt in runnning mode anymore if (myNodeRuntimeData.nodeState == NodeState.Running) { myNodeRuntimeData.taskHasChanges = PreTick(NodeExecute.Run, myNodeRuntimeData); } } if (myNodeRuntimeData.nodeState != NodeState.Running) { break; } } myNodeRuntimeData.nodeResult = NodeResult.Succeeded; yield return myNodeRuntimeData.nodeResult; if (debugInternalActive) Debug.Log(NodeLogger($"Tick.Selector", $"finished nodeState {myNodeRuntimeData.nodeState} nodeResult {myNodeRuntimeData.nodeResult}")); yield break; /// /// Marathon Node /// /// Starts all Children in parallel: /// - any child running -> continue in loop /// - any child success -> continue in loop /// - any child failed -> continue in loop /// - return after 1 iteration /// - succeed when all are succeeded or failed /// case CompositeParameter.Marathon: myNodeRuntimeData.childResults = new NodeResult[children.Count]; myNodeRuntimeData.subTickExecuteEnumerators = new IEnumerator[children.Count]; for (int i = 0; i < children.Count; i++) { myNodeRuntimeData.childResults[i] = NodeResult.Continue; myNodeRuntimeData.subTickExecuteEnumerators[i] = children[i].Tick(myNodeRuntimeData, recursions + 1).GetEnumerator(); } myNodeRuntimeData.nodeResult = NodeResult.Continue; myNodeRuntimeData.taskHasChanges = base.PreTick(NodeExecute.Run, myNodeRuntimeData); while (myNodeRuntimeData.nodeResult == NodeResult.Continue) { // run one interation of all childs for (int i = 0; i < children.Count; i++) { // if in continue, run if (debugInternalActive) Debug.Log(NodeLogger($"HandleMarathonExecute.BeforeRunning", $"running {i} {myNodeRuntimeData.childResults[i]} {children[i].GetType().ToString()}")); if (myNodeRuntimeData.childResults[i] == NodeResult.Continue) { myNodeRuntimeData.subTickExecuteEnumerators[i].MoveNext(); myNodeRuntimeData.childResults[i] = (NodeResult)myNodeRuntimeData.subTickExecuteEnumerators[i].Current; if (myNodeRuntimeData.childResults[i] == NodeResult.Undefined) { //throw new System.ArgumentException("Parameter should not be undefined", $"childResults[{i}] -> {childResults[i]} from {children[i].ToString()}"); Debug.LogError(NodeLogger($"HandleMarathonExecute", $"Parameter should not be undefined childResults[{i}] -> NodeResult.Undefined")); // from {children[i].ToString()}"); } if (myNodeRuntimeData.childResults[i] == NodeResult.Error) { Debug.LogError(NodeLogger($"HandleMarathonExecute", $"error in child {i} : {children[i]}")); } } if (debugInternalActive) Debug.Log(NodeLogger($"HandleMarathonExecute.AfterRunning", $"running {i} {myNodeRuntimeData.childResults[i]}")); } #if false // nice but slow // evaluate results bool allSucceededOrFailed = myNodeRuntimeData.childResults.All(result => ((result == NodeResult.Succeeded) || (result == NodeResult.Failed))); bool anyError = myNodeRuntimeData.childResults.Any(result => result == NodeResult.Error); if (anyError) { Debug.LogError(NodeLogger($"HandleMarathonExecute", "HandleMarathonExecute: some node returned error")); myNodeRuntimeData.nodeResult = NodeResult.Error; } #else bool allSucceededOrFailed = true; foreach (NodeResult childResult in myNodeRuntimeData.childResults) { switch (childResult) { case NodeResult.Succeeded: case NodeResult.Failed: break; case NodeResult.Undefined: case NodeResult.Continue: allSucceededOrFailed = false; break; case NodeResult.Error: myNodeRuntimeData.nodeResult = NodeResult.Error; break; } //if (childResult == NodeResult.Error) { // //anyError = true; // Debug.LogError(NodeLogger($"HandleMarathonExecute", "HandleMarathonExecute: some node returned error")); // myNodeRuntimeData.nodeResult = NodeResult.Error; // break; //} //if ( ! ((childResult == NodeResult.Succeeded) || (childResult == NodeResult.Failed)) ) { // allSucceededOrFailed = false; // //myNodeRuntimeData.nodeResult = NodeResult.Succeeded; // break; //} } #endif // generate result if (allSucceededOrFailed) { myNodeRuntimeData.nodeResult = NodeResult.Succeeded; } myNodeRuntimeData.taskHasChanges |= base.PostTick(NodeExecute.Run, myNodeRuntimeData); if (myNodeRuntimeData.taskHasChanges && taskController) { taskController.SetUiUpdate(); } if (debugInternalActive) Debug.Log(NodeLogger($"HandleMarathonExecute.Final", $"return {myNodeRuntimeData.nodeResult}")); yield return myNodeRuntimeData.nodeResult; // this should not be run again if it isnt in runnning mode anymore if (myNodeRuntimeData.nodeState == NodeState.Running) { if (debugInternalActive) Debug.Log(NodeLogger($"Tick.Cleanup", $"changed to {myNodeRuntimeData.nodeResult}")); myNodeRuntimeData.taskHasChanges = PreTick(NodeExecute.Run, myNodeRuntimeData); } else { if (debugInternalActive) Debug.Log(NodeLogger($"Tick.Cleanup", $"Testing, should be executed once after success of enumerator")); } } yield break; /// /// Race Node /// /// Starts all Children in parallel: /// - any child running -> continue in loop /// - any child success -> return success, abort all remaining running /// - any child failed -> continue in loop /// - return after 1 iteration /// - if all fail, fail. /// case CompositeParameter.Race: myNodeRuntimeData.childResults = new NodeResult[children.Count]; myNodeRuntimeData.subTickExecuteEnumerators = new IEnumerator[children.Count]; for (int i = 0; i < children.Count; i++) { myNodeRuntimeData.childResults[i] = NodeResult.Continue; myNodeRuntimeData.subTickExecuteEnumerators[i] = children[i].Tick(myNodeRuntimeData, recursions + 1).GetEnumerator(); } myNodeRuntimeData.nodeResult = NodeResult.Continue; myNodeRuntimeData.taskHasChanges = base.PreTick(NodeExecute.Run, myNodeRuntimeData); while (myNodeRuntimeData.nodeResult == NodeResult.Continue) { // run one interation of all childs for (int i = 0; i < children.Count; i++) { if (myNodeRuntimeData.childResults[i] == NodeResult.Continue) { myNodeRuntimeData.subTickExecuteEnumerators[i].MoveNext(); myNodeRuntimeData.childResults[i] = myNodeRuntimeData.subTickExecuteEnumerators[i].Current; if (debugInternalActive) Debug.Log(NodeLogger($"Race", $"node resulted in {myNodeRuntimeData.childResults[i]}")); } if (myNodeRuntimeData.childResults[i] == NodeResult.Error) { if (debugInternalActive) Debug.LogError(NodeLogger($"Race", $"{children[i]} has error")); } } #if false // nice but slow? bool anySucceeded = myNodeRuntimeData.childResults.Any(result => result == NodeResult.Succeeded); bool allFailed = myNodeRuntimeData.childResults.All(result => result == NodeResult.Failed); bool anyError = myNodeRuntimeData.childResults.Any(result => result == NodeResult.Error); if (anyError) { Debug.LogError(NodeLogger($"Race", "some node returned error")); myNodeRuntimeData.nodeResult = NodeResult.Error; } if (anySucceeded) { myNodeRuntimeData.nodeResult = NodeResult.Succeeded; } #else bool allFailed = true; foreach (NodeResult childResult in myNodeRuntimeData.childResults) { switch (childResult) { case NodeResult.Failed: break; case NodeResult.Succeeded: myNodeRuntimeData.nodeResult = NodeResult.Succeeded; goto case NodeResult.Undefined; case NodeResult.Undefined: case NodeResult.Continue: allFailed = false; break; case NodeResult.Error: myNodeRuntimeData.nodeResult = NodeResult.Error; break; } //if (childResult != NodeResult.Failed) { // allFailed = false; //} //if (childResult == NodeResult.Error) { // //anyError = true; // Debug.LogError(NodeLogger($"Race", "some node returned error")); // myNodeRuntimeData.nodeResult = NodeResult.Error; // break; //} //if (childResult == NodeResult.Succeeded) { // //anySucceeded = true; // myNodeRuntimeData.nodeResult = NodeResult.Succeeded; // break; //} } #endif if (allFailed) { myNodeRuntimeData.nodeResult = NodeResult.Failed; } myNodeRuntimeData.taskHasChanges |= base.PostTick(NodeExecute.Run, myNodeRuntimeData); if (myNodeRuntimeData.taskHasChanges && taskController) { taskController.SetUiUpdate(); } if (debugInternalActive) Debug.Log(NodeLogger($"Race", $"HandleRaceExecute: result {myNodeRuntimeData.nodeResult}")); yield return myNodeRuntimeData.nodeResult; // this should not be run again if it isnt in runnning mode anymore if (myNodeRuntimeData.nodeState == NodeState.Running) { if (debugInternalActive) Debug.Log(NodeLogger($"Race", $"changed to {myNodeRuntimeData.nodeResult}")); myNodeRuntimeData.taskHasChanges = PreTick(NodeExecute.Run, myNodeRuntimeData); } else { if (debugInternalActive) Debug.Log(NodeLogger($"Race", $"Testing, should be executed once after success of enumerator")); } } yield break; default: Debug.LogError(NodeLogger($"Tick", "no behaviour defined for CompositeNode, use at least Sequence or Parallel")); yield break; } } } else { Debug.LogError(NodeLogger("RunTreeNode.Tick", $"recursions {recursions}")); taskController.SetUiUpdate(); yield return NodeResult.Error; } yield break; } public override Node Duplicate () { CompositeNode tn = ScriptableObject.CreateInstance(); base.BaseDuplicate (tn); // CompositeNode tn = base.Duplicate() as CompositeNode; tn.parameters = parameters; tn.compositeType = compositeType; return tn; } public string ToString (NodeRuntimeData myNodeRuntimeData) { string customData = ""; switch (compositeType) { case CompositeParameter.Race: case CompositeParameter.Marathon: customData += "CResults: "; if (myNodeRuntimeData.childResults != null) { for (int i = 0; i < children.Count; i++) { if (i < myNodeRuntimeData.childResults.Length) { customData += myNodeRuntimeData.childResults[i] + " "; } } } break; case CompositeParameter.Sequence: //customData += "Index: "+sequenceIndex; break; case CompositeParameter.Selector: //customData += "Index: "+selectorIndex; break; } return base.ToString() + " " + customData; } } }