UP-Viagg-io/Viagg-io/Assets/Packages/MyBT/BT/Node/CompositeNode.cs

491 lines
28 KiB
C#

//============= 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<CompositeParameter> parameters;
[SerializeField]
private CompositeParameter compositeType = CompositeParameter.Undefined;
public bool Has(CompositeParameter compositeParameter) {
return parameters.Contains(compositeParameter);
}
public void Init(int _nodeDepth, Token[] _tokens, List<string> stringParameter) {
base.Init(_nodeDepth, _tokens);
parameters = new List<CompositeParameter>();
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;
}
}
/// <summary>
/// reset the whole behaviour tree structure
/// </summary>
public override void Destroy() {
base.Destroy();
parameters = null;
//activeNodeId = 0;
}
public override IEnumerable<NodeResult> 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) {
/// <summary>
/// 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
/// </summary>
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;
/// <summary>
/// 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
/// </summary>
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;
/// <summary>
/// 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
/// </summary>
case CompositeParameter.Marathon:
myNodeRuntimeData.childResults = new NodeResult[children.Count];
myNodeRuntimeData.subTickExecuteEnumerators = new IEnumerator<NodeResult>[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;
/// <summary>
/// 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.
/// </summary>
case CompositeParameter.Race:
myNodeRuntimeData.childResults = new NodeResult[children.Count];
myNodeRuntimeData.subTickExecuteEnumerators = new IEnumerator<NodeResult>[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<CompositeNode>();
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;
}
}
}