//============= Copyright (c) Ludic GmbH, All rights reserved. ============== // // Purpose: Part of the My Behaviour Tree Code // //============================================================================= using UnityEngine; using System; using System.Linq; //using System.Runtime.Serialization.Configuration; using System.Collections; using System.Collections.Generic; using System.IO; using System.Runtime.Serialization.Formatters.Binary; using System.Runtime.Serialization; using System.Xml.Serialization; #if UNITY_EDITOR using UnityEditor; #endif namespace MyBT { [System.Serializable] public class DictionaryOfStringAndMethodImplementation : SerializableDictionary<string, MethodImplementation> {} [System.Serializable] public class DictionaryOfStringAndDictionaryOfStringAndMethodImplementation : SerializableDictionary<string, DictionaryOfStringAndMethodImplementation> {} public enum TickUpdateMode { // run in update / lateUpdate Update, LateUpdate, // run in an interval for 1 step DelayedUpdate, DelayedLateUpdate, // run for a maximum time MaximumTimeUpdate, MaximumTimeLateUpdate, // run a full cycle FullrunUpdate, FullrunLateUpdate, // run manually Manual } [ExecuteInEditMode, System.Serializable] public partial class TaskController : MonoBehaviour { #region unity inspector updater (only in editor) // handling changes of the bt -> requires redrawing of the editor ui [NonSerialized] public Action<bool> InspectorRedraw; [NonSerialized] public Action<bool> InspectorRegenerate; // called from lateUpdate [System.Diagnostics.Conditional("UNITY_EDITOR")] void CallInspectorRedraw() { // makes unity editor very slow!!! //UnityEditorInternal.InternalEditorUtility.RepaintAllViews(); lastTickTime = currentUiTime; if (InspectorRedraw != null) InspectorRedraw.Invoke(false); } // called from lateUpdate [System.Diagnostics.Conditional("UNITY_EDITOR")] void CallInspectorRegenerate() { if (InspectorRegenerate != null) InspectorRegenerate.Invoke(false); } // when was the tick executed last time public float lastTickTime = 0; // when was the behaviour tree changed last time private float lastUiChangedTime = 0; // has the behaviour tree changed in this frame? // it is called from TaskControllerInspector public bool UiChangedThisFrame () { return (lastTickTime == lastUiChangedTime); } public bool UiChangedAt (float time) { return lastTickTime == time; } // called to set behaviour tree changed public void SetUiUpdate() { // time at beginning of frame lastUiChangedTime = TaskController.currentUiTime; } public static float currentUiTime { get { return Time.time; } } #endregion #region settings // tick settings public float tickDelay = 0f; public float maximumRuntime = 0.1f; public TickUpdateMode tickMode = TickUpdateMode.Update; // generation settings public bool runtimeAutomaticRegenerate = true; public bool resetDataOnCodeChange = false; // logging public bool runtimeLogging = false; public bool overrideLogStringDisplay = false; public bool overrideDebugInternalActive = false; public bool overrideDebugChangesActive = false; // timer for tick delay calculation private float tickDelayTimer = 0f; NodeResult rootResult = NodeResult.Succeeded; #if false public IEnumerator<NodeResult> _tickEnumerator; public IEnumerator<NodeResult> tickEnumerator { get { return _tickEnumerator; } set { if (runtimeLogging) Debug.LogWarning($"setting tickEnumerator to '{value}'"); _tickEnumerator = value; } } #else public IEnumerator<NodeResult> tickEnumerator; #endif private bool tickSuccess = false; #endregion #region duplicate watcher // #if UNITY_EDITOR // Catch duplication of this GameObject [SerializeField] private int instanceID = 0; /// <summary> /// Detect when the GameObject has been duplicated /// if so make sure then dont share the same instance of the behaviour tree generator /// </summary> void AwakeDuplicateCatcher() { if (instanceID != GetInstanceID()) { if (generatorLogging) Debug.Log($"Detected Duplicate! ( {instanceID} != {GetInstanceID()} )"); // Do whatever you need to set up the duplicate // copy old contents // List<UnityEngine.TextAsset> oldTaskScripts = new List<UnityEngine.TextAsset>(); // for (int i=0; i<oldTaskScripts.Count; i++) { //(TextAsset txtAss in oldTaskScripts) { // taskScripts.Add(oldTaskScripts[i]); // } // clear current data __behaviourTreeGenerator = null; // assign scripts // read scripts // for (int i=0; i<oldTaskScripts.Count; i++) { //(TextAsset txtAss in oldTaskScripts) { // taskScripts.Add(oldTaskScripts[i]); // } // works, but regenerates everything, breaks when unity builds the project // BtScriptChangeDetected(true); // FullReset(); instanceID = GetInstanceID(); #if UNITY_EDITOR EditorUtility.SetDirty(this); #endif } } // #endif private void Awake() { AwakeDuplicateCatcher(); if (Application.isPlaying) { VerifyBindings(); RegenerateBehaviourTreeIfRequired(false); } } public void OnDestroy () { #if !UNITY_EDITOR // calling this when building causes it to fail ClearBinding(); #endif } #endregion // public bool destroyNextFrame = false; // public bool breakNextFrame = false; #region tick functions public void Update() { // if (breakNextFrame) { // Debug.Break(); // } // if (destroyNextFrame) { // OnDestroy(); // breakNextFrame = true; // } // Debug.Log($"Update {Time.frameCount} {Time.time}"); if (Application.isPlaying) { if (tickMode == TickUpdateMode.Update) { TickUpdateStep(); } else if (tickMode == TickUpdateMode.DelayedUpdate) { tickDelayTimer -= Time.deltaTime; if (tickDelayTimer < 0) { tickDelayTimer += tickDelay; TickUpdateStep(); } } else if (tickMode == TickUpdateMode.MaximumTimeUpdate) { float startTime = Time.realtimeSinceStartup; while ((Time.realtimeSinceStartup - startTime) < maximumRuntime) { TickUpdateStep(); } } else if (tickMode == TickUpdateMode.FullrunUpdate) { TickUpdateFull(); } } } public void LateUpdate () { if (Application.isPlaying) { if (tickMode == TickUpdateMode.LateUpdate) { TickUpdateStep(); } else if (tickMode == TickUpdateMode.DelayedLateUpdate) { tickDelayTimer -= Time.deltaTime; if (tickDelayTimer < 0) { tickDelayTimer += tickDelay; TickUpdateStep(); } } else if (tickMode == TickUpdateMode.MaximumTimeLateUpdate) { float startTime = Time.realtimeSinceStartup; while ((Time.realtimeSinceStartup - startTime) < maximumRuntime) { TickUpdateStep(); } } else if (tickMode == TickUpdateMode.FullrunLateUpdate) { TickUpdateFull(); } } } public void TickUpdate () { TickUpdateStep(); } public void TickUpdateStep () { if (restartBehaviourTreeNextFrame) { RestartNow(); } if (hasRootNode) { // if null or looped trough all items if ((!tickSuccess) || (tickEnumerator == null)) { // restart tick tickEnumerator = rootNode.Tick(null, 0).GetEnumerator(); if (runtimeLogging) Debug.Log($"Assigned new Iterator {(tickEnumerator != null)}"); } // run as usual tickSuccess = tickEnumerator.MoveNext(); rootResult = tickEnumerator.Current; if (runtimeLogging) Debug.Log($"--- Ticked --- s: {tickSuccess} r:{rootResult} --- t:{Time.frameCount} ----------------------------------------------------------------------------------------------------------------------"); } else { Debug.LogError("cannot tick, missing root node! disabling this gameObject !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); gameObject.SetActive(false); } CallInspectorRedraw(); } public void TickUpdateFull() { if (restartBehaviourTreeNextFrame) { RestartNow(); } tickDelayTimer = tickDelay; //behaviourTreeGenerator.SetTaskControllerAndGenerateCache(this); if (hasRootNode) { // reset the bt foreach (var result in rootNode.Tick(null, 0)) { rootResult = result; } rootResult = NodeResult.Continue; } if (runtimeLogging) Debug.Log($"--- Restart --- r:{rootResult} --- t:{Time.frameCount} ----------------------------------------------------------------------------------------------------------------------"); CallInspectorRedraw(); } #endregion } public partial class TaskController { #region behaviourtree generator & exposing of methods and variables [SerializeField] private BehaviourTreeGenerator __behaviourTreeGenerator; [HideInInspector, SerializeField] private BehaviourTreeGenerator behaviourTreeGenerator { get { if (__behaviourTreeGenerator == null) { __behaviourTreeGenerator = BehaviourTreeGenerator.CreateInstance<BehaviourTreeGenerator>(); } if (__behaviourTreeGenerator == null) { Debug.LogError($"behaviourTreeGenerator is null... how? {behaviourTreeGenerator}"); } return __behaviourTreeGenerator; } } // expose parts of the behaviourTreeGenerator #endregion #region functions and variables from behaviourTreeGenerator // configuration [SerializeField] public List<TextAsset> taskScripts; public bool generatorLogging { get { return behaviourTreeGenerator.generatorLogging; } set { behaviourTreeGenerator.generatorLogging = value; } } public bool internalAutomaticRegenerate { get { return behaviourTreeGenerator.internalAutomaticRegenerate; } set { behaviourTreeGenerator.internalAutomaticRegenerate = value; } } public TreeNode rootNode { get { return behaviourTreeGenerator.rootNode; } } public bool hasRootNode { get { return (behaviourTreeGenerator.rootNode != null); } } public void FullGenerate (bool force) { behaviourTreeGenerator.FullGenerate(this, force); } public void FullReset () { behaviourTreeGenerator.FullReset(); } private bool restartBehaviourTreeNextFrame = false; public void Restart(bool forceImmediate=false) { restartBehaviourTreeNextFrame = true; if (forceImmediate) { RestartNow(); } } private void RestartNow () { behaviourTreeGenerator.Restart(); restartBehaviourTreeNextFrame = false; } public void VerifyBindings() { behaviourTreeGenerator.VerifyBinding(this); } public void RegenerateBehaviourTreeIfRequired (bool forceUpdate) { behaviourTreeGenerator.RegenerateBehaviourTreeIfRequired(this, forceUpdate); } /// <summary> /// the methods called have changed (or might have...) /// we dont yet filter which scripts are changed /// </summary> public void ClearBindingsAndScanAndBindMethods(bool forceUpdate = false) { // forcefully clear and regenerate behaviourTreeGenerator.ClearBindingsAndScanAndBindMethods(this, forceUpdate); } /// <summary> /// is called by DetectBtChanges /// </summary> /// <param name="forceUpdate"></param> public void BtScriptChangeDetected(bool forceUpdate = false) { behaviourTreeGenerator.BtScriptChangeDetected(this, forceUpdate); } public void ResetNodesAndTokens() { behaviourTreeGenerator.ResetNodesAndTokens(); } public void GenerateTokensAndNodes(bool forceUpdate = false) { Debug.Log("GenerateTokensAndNodes"); behaviourTreeGenerator.GenerateTokensAndNodes(this, forceUpdate); } public void ClearBinding() { behaviourTreeGenerator.ClearBinding(); } public void ScanAndBindMethods(bool forceUpdate = false) { behaviourTreeGenerator.ScanAndBindMethods(this, forceUpdate); } public void SetTaskController() { behaviourTreeGenerator.SetTaskControllerAndGenerateCache(this); } public void SetBehaviourTreeDirty() { #if UNITY_EDITOR EditorUtility.SetDirty(behaviourTreeGenerator); #endif } public bool CheckBindingOutdated(bool allowNull = true) { return behaviourTreeGenerator.CheckBindingOutdated(this, allowNull); } public bool CheckGenerationOutdated(bool allowNull = true) { return behaviourTreeGenerator.CheckGenerationOutdated(allowNull); } // status // hasScript && tokensGenerated && nodesGenerated // public bool isGenerated { // get { return behaviourTreeGenerator.isGenerated; } // } // public string generationState { // get { return behaviourTreeGenerator.generationState; } // } public bool hasScript { get { return behaviourTreeGenerator.HasScripts(this); } } // public bool tokensGenerated { // get { return behaviourTreeGenerator.tokensGenerated; } // } // public bool nodesGenerated { // get { return behaviourTreeGenerator.nodesGenerated; } // } public FileHash textAssetHashes { get { return behaviourTreeGenerator.textAssetHashes; } } public FileHash generatedCodeBtFileHashes { get { return behaviourTreeGenerator.generatedCodeBtFileHashes; } } public FileHash boundCodeBtFileHashes { get { return behaviourTreeGenerator.boundCodeBtFileHashes; } } public List<TokenList> tokens { get { return behaviourTreeGenerator.tokens; } } public List<NodeList> treeRootNodes { get { return behaviourTreeGenerator.treeRootNodes; } } #if UNITY_EDITOR public SerializedProperty treeRootNodesAsSerializedProperty { get { SerializedObject sObj = new SerializedObject(behaviourTreeGenerator); return sObj.FindProperty("treeRootNodes"); } } #endif public int nodeCount { get { return behaviourTreeGenerator.nodeCount; } } public bool generationError { get { return behaviourTreeGenerator.generationError; } } public bool bindingError { get { return behaviourTreeGenerator.bindingError; } } // public bool methodsScanned { // get { return behaviourTreeGenerator.methodsScanned; } // } // public bool methodsBound { // get { return behaviourTreeGenerator.methodsBound; } // } public DictionaryOfStringAndDictionaryOfStringAndMethodImplementation methods { get { return behaviourTreeGenerator.methods; } } #endregion } }