//============= 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 codes = null; // list of the tasks [SerializeField] public DictionaryOfStringAndDictionaryOfStringAndMethodImplementation methods = null; // the tokens are generated by the TokenEnvironment [SerializeField] public List tokens = null; // the treenodes are generated by the NodeGenerator [SerializeField] public List treeRootNodes = null; [SerializeField] public TreeNode rootNode = null; #endregion #region settings // [SerializeField] // public List taskScripts = new List(); // 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); } } /// /// Reset the whole Task / Node / Action Tree structure for regeneration from the source behaviour tree. /// 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 } }