359 lines
14 KiB
C#
359 lines
14 KiB
C#
|
//============= Copyright (c) Ludic GmbH, All rights reserved. ==============
|
|||
|
//
|
|||
|
// Purpose: Part of the My Behaviour Tree Code
|
|||
|
//
|
|||
|
//=============================================================================
|
|||
|
|
|||
|
using System.Collections;
|
|||
|
using System.Collections.Generic;
|
|||
|
using System.Linq;
|
|||
|
using System.Security.Cryptography;
|
|||
|
using System.Text;
|
|||
|
using UnityEngine;
|
|||
|
|
|||
|
namespace MyBT {
|
|||
|
[System.Serializable]
|
|||
|
public class BehaviourTreeGenerator : ScriptableObject {
|
|||
|
// void Init() {
|
|||
|
|
|||
|
// }
|
|||
|
|
|||
|
#region generated stuff
|
|||
|
// the contents of the script
|
|||
|
// [HideInInspector, SerializeField]
|
|||
|
// public List<string> codes = null;
|
|||
|
|
|||
|
// list of the tasks
|
|||
|
[SerializeField]
|
|||
|
public DictionaryOfStringAndDictionaryOfStringAndMethodImplementation methods = null;
|
|||
|
|
|||
|
// the tokens are generated by the TokenEnvironment
|
|||
|
[SerializeField]
|
|||
|
public List<TokenList> tokens = null;
|
|||
|
|
|||
|
// the treenodes are generated by the NodeGenerator
|
|||
|
[SerializeField]
|
|||
|
public List<NodeList> treeRootNodes = null;
|
|||
|
|
|||
|
[SerializeField]
|
|||
|
public TreeNode rootNode = null;
|
|||
|
#endregion
|
|||
|
|
|||
|
#region settings
|
|||
|
// [SerializeField]
|
|||
|
// public List<UnityEngine.TextAsset> taskScripts = new List<UnityEngine.TextAsset>();
|
|||
|
|
|||
|
// currently causes errors, thus it's disabled in the editor
|
|||
|
[HideInInspector]
|
|||
|
public bool readUnityComponents = false;
|
|||
|
|
|||
|
[SerializeField]
|
|||
|
public bool generatorLogging = false;
|
|||
|
|
|||
|
public bool internalAutomaticRegenerate = true;
|
|||
|
#endregion
|
|||
|
|
|||
|
#region runtime variables
|
|||
|
public bool generationError = false;
|
|||
|
public bool bindingError = false;
|
|||
|
#endregion
|
|||
|
|
|||
|
#region file change detection
|
|||
|
// cache of the file hashes as the currently are
|
|||
|
[SerializeField]
|
|||
|
public FileHash textAssetHashes = new FileHash();
|
|||
|
// stores the file hashes at the moment of generation
|
|||
|
[SerializeField]
|
|||
|
public FileHash generatedCodeBtFileHashes = new FileHash();
|
|||
|
// stores the file hashes at the moment of binding
|
|||
|
[SerializeField]
|
|||
|
public FileHash boundCodeBtFileHashes = new FileHash();
|
|||
|
#endregion
|
|||
|
|
|||
|
public bool HasScripts (TaskController _taskController) {
|
|||
|
return (_taskController.taskScripts != null) && (_taskController.taskScripts.Count > 0);
|
|||
|
}
|
|||
|
|
|||
|
public bool hasRootNode {
|
|||
|
get {
|
|||
|
return (rootNode != null);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
public void FullGenerate (TaskController taskController, bool force) {
|
|||
|
GenerateTokensAndNodes(taskController, force);
|
|||
|
ScanAndBindMethods(taskController, force);
|
|||
|
SetTaskControllerAndGenerateCache(taskController);
|
|||
|
}
|
|||
|
|
|||
|
public void FullReset() {
|
|||
|
ClearBinding();
|
|||
|
ResetNodesAndTokens();
|
|||
|
}
|
|||
|
|
|||
|
public void ClearBindingsAndScanAndBindMethods (TaskController taskController, bool force) {
|
|||
|
ClearBinding();
|
|||
|
ScanAndBindMethods(taskController, force);
|
|||
|
}
|
|||
|
|
|||
|
public void BtScriptChangeDetected(TaskController _taskController, bool forceUpdate = false) {
|
|||
|
if (generatorLogging) Debug.Log("BehaviourTreeGenerator.BtScriptChangeDetected");
|
|||
|
|
|||
|
// update current script hashes, so we compare the generated nodes against the actual scripts
|
|||
|
textAssetHashes.UpdateHashCacheFromTextAsset(_taskController.taskScripts);
|
|||
|
if (CheckGenerationOutdated() || forceUpdate) {
|
|||
|
if (generatorLogging) Debug.Log("BehaviourTreeGenerator.BtScriptChangeDetected: clear nodes and tokens");
|
|||
|
ResetNodesAndTokens();
|
|||
|
}
|
|||
|
if (CheckBindingOutdated(_taskController) || forceUpdate) {
|
|||
|
if (generatorLogging) Debug.Log("BehaviourTreeGenerator.BtScriptChangeDetected: clear binding");
|
|||
|
ClearBinding();
|
|||
|
}
|
|||
|
// regenerate
|
|||
|
GenerateTokensAndNodes(_taskController);
|
|||
|
ScanAndBindMethods(_taskController, forceUpdate);
|
|||
|
|
|||
|
_taskController.InspectorRegenerate?.Invoke(true);
|
|||
|
}
|
|||
|
|
|||
|
public void SetTaskControllerAndGenerateCache (TaskController _taskController) {
|
|||
|
if (treeRootNodes != null) {
|
|||
|
foreach (NodeList rn in treeRootNodes) {
|
|||
|
foreach (Node crn in rn) {
|
|||
|
foreach (Node cn in crn.getAllNodesRecursively(false)) {
|
|||
|
cn.SetTaskController(_taskController);
|
|||
|
cn.GenerateNodeDataCaches();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
else {
|
|||
|
Debug.LogError($"BehaviourtreeGenerator.SetTaskController: treeRootNodes {treeRootNodes}");
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public void Restart() {
|
|||
|
if (rootNode != null) {
|
|||
|
if (rootNode.nodeRuntimeDataList.Count > 0) {
|
|||
|
rootNode.nodeRuntimeDataList[0].Destroy();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
public void RegenerateBehaviourTreeIfRequired(TaskController _taskController, bool forceUpdate = false) {
|
|||
|
if (CheckGenerationOutdated() || CheckBindingOutdated(_taskController) || forceUpdate) {
|
|||
|
if (generatorLogging) {
|
|||
|
Debug.LogWarning($"TaskController.Update: regenerating behaviour tree {CheckGenerationOutdated()} {CheckBindingOutdated(_taskController)}");
|
|||
|
}
|
|||
|
|
|||
|
FullReset();
|
|||
|
}
|
|||
|
else {
|
|||
|
Debug.Log($"TaskController.Update: status: generated:{generatedCodeBtFileHashes} bound:{boundCodeBtFileHashes} text:{textAssetHashes}");
|
|||
|
}
|
|||
|
|
|||
|
if (CheckGenerationOutdated() || CheckBindingOutdated(_taskController) || forceUpdate) { // || !behaviourTreeGenerator.isBound // this makes it way slower...
|
|||
|
if (generatorLogging) {
|
|||
|
Debug.LogWarning($"TaskController.TickUpdate: updating bindings");
|
|||
|
}
|
|||
|
|
|||
|
FullGenerate(_taskController, false);
|
|||
|
}
|
|||
|
else {
|
|||
|
Debug.Log($"TaskController.Update: status: generated:{generatedCodeBtFileHashes} bound:{boundCodeBtFileHashes} text:{textAssetHashes}");
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
// public bool tokensGenerated {
|
|||
|
// get {
|
|||
|
// return Tokenizer.IsAllGenerated(tokens);
|
|||
|
// }
|
|||
|
// }
|
|||
|
|
|||
|
// public bool nodesGenerated {
|
|||
|
// get {
|
|||
|
// return NodeGenerator.IsAllGenerated(treeRootNodes, rootNode);
|
|||
|
// }
|
|||
|
// }
|
|||
|
|
|||
|
// public bool isGenerated {
|
|||
|
// get {
|
|||
|
// return (hasScript && tokensGenerated && nodesGenerated);
|
|||
|
// }
|
|||
|
// }
|
|||
|
|
|||
|
// public string generationState {
|
|||
|
// get {
|
|||
|
// return (hasScript + "&&" + tokensGenerated + "&&" + nodesGenerated);
|
|||
|
// }
|
|||
|
// }
|
|||
|
|
|||
|
#region Tokenizer & NodeGenerator
|
|||
|
public bool CheckGenerationOutdated(bool allowNull = false) {
|
|||
|
// if (debugLog) Debug.Log("CheckGenerationOutdated:\tGenerated Hash:\t" + generatedCodeBtFileHashes + " \n"+
|
|||
|
// "\t\t\tText Asset Hash:\t" + textAssetHashes);
|
|||
|
return generatedCodeBtFileHashes.HashChanged(textAssetHashes, allowNull);
|
|||
|
}
|
|||
|
|
|||
|
public string GetGenerationHashes () {
|
|||
|
return $"text:{textAssetHashes} generated:{generatedCodeBtFileHashes}";
|
|||
|
}
|
|||
|
|
|||
|
public void UpdateGenerationHashes() {
|
|||
|
if (generatorLogging) Debug.Log("BehaviourTreeGenerator.UpdateGenerationHashes");
|
|||
|
generatedCodeBtFileHashes.UpdateHashCache(textAssetHashes);
|
|||
|
}
|
|||
|
|
|||
|
public void ClearGenerationHashes() {
|
|||
|
if (generatorLogging) Debug.Log("BehaviourTreeGenerator.ClearGenerationHashes");
|
|||
|
generatedCodeBtFileHashes.ClearHashCache();
|
|||
|
}
|
|||
|
|
|||
|
public void GenerateTokensAndNodes(TaskController _taskController, bool force=false) {
|
|||
|
if (generatorLogging) Debug.Log($"BehaviourTreeGenerator.Generate ({CheckGenerationOutdated()} && !{generationError} && {internalAutomaticRegenerate} )");
|
|||
|
|
|||
|
if (HasScripts(_taskController) && CheckGenerationOutdated() && !generationError && (internalAutomaticRegenerate || force)) {
|
|||
|
// generate the tokens
|
|||
|
if (generatorLogging) Debug.Log($"BehaviourTreeGenerator.Generate: --- Execute Tokenizer --- ({HasScripts(_taskController)} && ({GetGenerationHashes()}) !{generationError} && ({internalAutomaticRegenerate} || {force}))");
|
|||
|
|
|||
|
bool tokenizerSuccess = Tokenizer.Tokenize(out tokens, _taskController.taskScripts);
|
|||
|
generationError |= !tokenizerSuccess;
|
|||
|
|
|||
|
if (!tokenizerSuccess) {
|
|||
|
Debug.LogError($"BehaviourTreeGenerator.Generate: Tokenizer not generated ");
|
|||
|
}
|
|||
|
|
|||
|
bool nodeGenerationSuccess = NodeGenerator.Generate(out treeRootNodes, out rootNode, tokens);
|
|||
|
generationError |= !nodeGenerationSuccess;
|
|||
|
|
|||
|
if (!nodeGenerationSuccess) {
|
|||
|
Debug.LogError("BehaviourTreeGenerator.Generate: failed");
|
|||
|
}
|
|||
|
|
|||
|
// assign taskController
|
|||
|
SetTaskControllerAndGenerateCache(_taskController);
|
|||
|
|
|||
|
// update hash to save last successful generation source
|
|||
|
if (generationError) {
|
|||
|
Debug.LogError($"BehaviourTreeGenerator.Generate: NodeGenerator not generated ");
|
|||
|
ClearGenerationHashes();
|
|||
|
}
|
|||
|
else {
|
|||
|
UpdateGenerationHashes();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public int nodeCount {
|
|||
|
get { return NodeGenerator.GeneratedSize(treeRootNodes); }
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Reset the whole Task / Node / Action Tree structure for regeneration from the source behaviour tree.
|
|||
|
/// </summary>
|
|||
|
public void ResetNodesAndTokens() {
|
|||
|
if (generatorLogging) Debug.LogWarning("BehaviourTreeGenerator.ResetNodesAndTokens");
|
|||
|
|
|||
|
if (generatorLogging) Debug.Log("BehaviourTreeGenerator.Reset: --- Reset NodeGenerator ---");
|
|||
|
NodeGenerator.DestroyAll(ref treeRootNodes, ref rootNode);
|
|||
|
|
|||
|
if (generatorLogging) Debug.Log("BehaviourTreeGenerator.Reset: --- Reset Tokenizer ---");
|
|||
|
Tokenizer.DestroyAll(ref tokens);
|
|||
|
|
|||
|
generationError = false;
|
|||
|
ClearGenerationHashes();
|
|||
|
}
|
|||
|
#endregion
|
|||
|
|
|||
|
// public bool methodsScanned {
|
|||
|
// get {
|
|||
|
// return MethodScanner.IsGenerated(methods);
|
|||
|
// }
|
|||
|
// }
|
|||
|
|
|||
|
// public bool methodsBound {
|
|||
|
// get {
|
|||
|
// return MethodBinder.IsAllBound(treeRootNodes);
|
|||
|
// }
|
|||
|
// }
|
|||
|
|
|||
|
// public bool isBound {
|
|||
|
// get {
|
|||
|
// return (methodsScanned && methodsBound);
|
|||
|
// }
|
|||
|
// }
|
|||
|
|
|||
|
#region Binding
|
|||
|
public bool CheckBindingOutdated(TaskController _taskController, bool allowNull = false) {
|
|||
|
textAssetHashes.UpdateHashCacheFromTextAsset(_taskController.taskScripts);
|
|||
|
return boundCodeBtFileHashes.HashChanged(textAssetHashes, allowNull);
|
|||
|
}
|
|||
|
|
|||
|
public void UpdateBindingHashes() {
|
|||
|
if (generatorLogging) Debug.Log("BehaviourTreeGenerator.UpdateBindingHashes");
|
|||
|
boundCodeBtFileHashes.UpdateHashCache(textAssetHashes);
|
|||
|
}
|
|||
|
|
|||
|
public void ClearBindingHashes() {
|
|||
|
if (generatorLogging) Debug.Log("BehaviourTreeGenerator.ClearBindingHashes");
|
|||
|
boundCodeBtFileHashes.ClearHashCache();
|
|||
|
}
|
|||
|
|
|||
|
public void VerifyBinding (TaskController _taskController) {
|
|||
|
if (generatorLogging) Debug.Log("BehaviourTreeGenerator.VerifyBinding");
|
|||
|
if (!MethodBinder.VerifyBinding(treeRootNodes)) {
|
|||
|
ClearBindingHashes();
|
|||
|
RegenerateBehaviourTreeIfRequired(_taskController);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public void ScanAndBindMethods(TaskController _taskController, bool force = false) {
|
|||
|
if (CheckBindingOutdated(_taskController) && !bindingError && internalAutomaticRegenerate || force) {
|
|||
|
if (generatorLogging) Debug.Log($"BehaviourTreeGenerator.ScanAndBindMethods: ({CheckBindingOutdated(_taskController)} && !{bindingError} && {internalAutomaticRegenerate} || {force}) ");
|
|||
|
|
|||
|
// read the contents of the script, remove tabs and spaces
|
|||
|
bool componentCheckSuccess = MethodScanner.CheckComponents(_taskController.gameObject, readUnityComponents, ref methods);
|
|||
|
bindingError |= !componentCheckSuccess;
|
|||
|
if (!componentCheckSuccess) {
|
|||
|
if (generatorLogging) Debug.LogError("BehaviourTreeGenerator.ScanAndBindMethods: MethodScanner failed");
|
|||
|
}
|
|||
|
|
|||
|
bool methodBinderSuccess = MethodBinder.Bind(ref treeRootNodes, rootNode, methods);
|
|||
|
bindingError |= !methodBinderSuccess;
|
|||
|
if (!methodBinderSuccess) {
|
|||
|
if (generatorLogging) Debug.LogError("BehaviourTreeGenerator.ScanAndBindMethods: MethodBinder failed");
|
|||
|
}
|
|||
|
|
|||
|
SetTaskControllerAndGenerateCache(_taskController);
|
|||
|
|
|||
|
if (!bindingError) {
|
|||
|
if (generatorLogging) Debug.Log($"BehaviourTreeGenerator.ScanAndBindMethods: --- UpdateBindingHashes ---");
|
|||
|
UpdateBindingHashes();
|
|||
|
}
|
|||
|
else {
|
|||
|
Debug.Log($"BehaviourTreeGenerator.ScanAndBindMethods: ScanAndBindMethods Failed");
|
|||
|
ClearBindingHashes();
|
|||
|
}
|
|||
|
}
|
|||
|
else {
|
|||
|
if (generatorLogging) Debug.LogWarning($"BehaviourTreeGenerator.Bind: not running ( {CheckBindingOutdated(_taskController)} && !{generationError} && {internalAutomaticRegenerate} )");
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public void ClearBinding() {
|
|||
|
if (generatorLogging) Debug.Log("BehaviourTreeGenerator.ClearBinding: --- Reset MethodBinder ---");
|
|||
|
MethodBinder.ResetAll(ref treeRootNodes); // causes error in building
|
|||
|
|
|||
|
if (generatorLogging) Debug.Log("BehaviourTreeGenerator.ClearBinding: --- Reset MethodScanner ---");
|
|||
|
MethodScanner.Reset(ref methods);
|
|||
|
|
|||
|
bindingError = false;
|
|||
|
|
|||
|
ClearBindingHashes();
|
|||
|
}
|
|||
|
#endregion
|
|||
|
}
|
|||
|
}
|