// This code is part of the Fungus library (http://fungusgames.com) maintained by Chris Gregan (http://twitter.com/gofungus). // It is released for free under the MIT open source license (https://github.com/snozbot/fungus/blob/master/LICENSE) using UnityEngine; using UnityEngine.Serialization; using System; using System.Collections; using System.Collections.Generic; using Fungus.Commands; using Fungus.Utils; namespace Fungus { /// /// Execution state of a Block. /// public enum ExecutionState { /// No command executing Idle, /// Executing a command Executing, } /// /// A container for a sequence of Fungus comands. /// [ExecuteInEditMode] [RequireComponent(typeof(Flowchart))] [AddComponentMenu("")] public class Block : Node { [SerializeField] protected int itemId = -1; // Invalid flowchart item id [FormerlySerializedAs("sequenceName")] [Tooltip("The name of the block node as displayed in the Flowchart window")] [SerializeField] protected string blockName = "New Block"; [TextArea(2, 5)] [Tooltip("Description text to display under the block node")] [SerializeField] protected string description = ""; [Tooltip("An optional Event Handler which can execute the block when an event occurs")] [SerializeField] protected EventHandler eventHandler; [SerializeField] protected List commandList = new List(); protected ExecutionState executionState; protected Command activeCommand; /// // Index of last command executed before the current one. // -1 indicates no previous command. /// protected int previousActiveCommandIndex = -1; protected int jumpToCommandIndex = -1; protected int executionCount; protected bool executionInfoSet = false; protected virtual void Awake() { SetExecutionInfo(); } /// /// Populate the command metadata used to control execution. /// protected virtual void SetExecutionInfo() { // Give each child command a reference back to its parent block // and tell each command its index in the list. int index = 0; foreach (ICommand command in commandList) { if (command == null) { continue; } command.ParentBlock = this; command.CommandIndex = index++; } // Ensure all commands are at their correct indent level // This should have already happened in the editor, but may be necessary // if commands are added to the Block at runtime. UpdateIndentLevels(); executionInfoSet = true; } #if UNITY_EDITOR // The user can modify the command list order while playing in the editor, // so we keep the command indices updated every frame. There's no need to // do this in player builds so we compile this bit out for those builds. protected virtual void Update() { int index = 0; foreach (ICommand command in commandList) { if (command == null) // Null entry will be deleted automatically later { continue; } command.CommandIndex = index++; } } #endif #region Public methods /// /// The execution state of the Block. /// public virtual ExecutionState State { get { return executionState; } } /// /// Unique identifier for the Block. /// public virtual int ItemId { get { return itemId; } set { itemId = value; } } /// /// The name of the block node as displayed in the Flowchart window. /// public virtual string BlockName { get { return blockName; } set { blockName = value; } } /// /// Description text to display under the block node /// public virtual string Description { get { return description; } } /// /// An optional Event Handler which can execute the block when an event occurs. /// Note: Using the concrete class instead of the interface here because of weird editor behaviour. /// public virtual EventHandler _EventHandler { get { return eventHandler; } set { eventHandler = value; } } /// /// The currently executing command. /// public virtual Command ActiveCommand { get { return activeCommand; } } /// /// Timer for fading Block execution icon. /// public virtual float ExecutingIconTimer { get; set; } /// /// The list of commands in the sequence. /// public virtual List CommandList { get { return commandList; } } /// /// Controls the next command to execute in the block execution coroutine. /// public virtual int JumpToCommandIndex { set { jumpToCommandIndex = value; } } /// /// Returns the parent Flowchart for this Block. /// public virtual Flowchart GetFlowchart() { return GetComponent(); } /// /// Returns true if the Block is executing a command. /// public virtual bool IsExecuting() { return (executionState == ExecutionState.Executing); } /// /// Returns the number of times this Block has executed. /// public virtual int GetExecutionCount() { return executionCount; } /// /// Start a coroutine which executes all commands in the Block. Only one running instance of each Block is permitted. /// public virtual void StartExecution() { StartCoroutine(Execute()); } /// /// A coroutine method that executes all commands in the Block. Only one running instance of each Block is permitted. /// /// Index of command to start execution at /// Delegate function to call when execution completes public virtual IEnumerator Execute(int commandIndex = 0, Action onComplete = null) { if (executionState != ExecutionState.Idle) { yield break; } if (!executionInfoSet) { SetExecutionInfo(); } executionCount++; var flowchart = GetFlowchart(); executionState = ExecutionState.Executing; #if UNITY_EDITOR // Select the executing block & the first command flowchart.SelectedBlock = this; if (commandList.Count > 0) { flowchart.ClearSelectedCommands(); flowchart.AddSelectedCommand(commandList[0]); } #endif jumpToCommandIndex = commandIndex; int i = 0; while (true) { // Executing commands specify the next command to skip to by setting jumpToCommandIndex using Command.Continue() if (jumpToCommandIndex > -1) { i = jumpToCommandIndex; jumpToCommandIndex = -1; } // Skip disabled commands, comments and labels while (i < commandList.Count && (!commandList[i].enabled || commandList[i].GetType() == typeof(Comment) || commandList[i].GetType() == typeof(Label))) { i = commandList[i].CommandIndex + 1; } if (i >= commandList.Count) { break; } // The previous active command is needed for if / else / else if commands if (activeCommand == null) { previousActiveCommandIndex = -1; } else { previousActiveCommandIndex = activeCommand.CommandIndex; } var command = commandList[i]; activeCommand = command; if (flowchart.IsActive()) { // Auto select a command in some situations if ((flowchart.SelectedCommands.Count == 0 && i == 0) || (flowchart.SelectedCommands.Count == 1 && flowchart.SelectedCommands[0].CommandIndex == previousActiveCommandIndex)) { flowchart.ClearSelectedCommands(); flowchart.AddSelectedCommand(commandList[i]); } } command.IsExecuting = true; // This icon timer is managed by the FlowchartWindow class, but we also need to // set it here in case a command starts and finishes execution before the next window update. command.ExecutingIconTimer = Time.realtimeSinceStartup + FungusConstants.ExecutingIconFadeTime; command.Execute(); // Wait until the executing command sets another command to jump to via Command.Continue() while (jumpToCommandIndex == -1) { yield return null; } #if UNITY_EDITOR if (flowchart.StepPause > 0f) { yield return new WaitForSeconds(flowchart.StepPause); } #endif command.IsExecuting = false; } executionState = ExecutionState.Idle; activeCommand = null; if (onComplete != null) { onComplete(); } } /// /// Stop executing commands in this Block. /// public virtual void Stop() { // Tell the executing command to stop immediately if (activeCommand != null) { activeCommand.IsExecuting = false; activeCommand.OnStopExecuting(); } // This will cause the execution loop to break on the next iteration jumpToCommandIndex = int.MaxValue; } /// /// Returns a list of all Blocks connected to this one. /// public virtual List GetConnectedBlocks() { var connectedBlocks = new List(); foreach (var command in commandList) { if (command != null) { command.GetConnectedBlocks(ref connectedBlocks); } } return connectedBlocks; } /// /// Returns the type of the previously executing command. /// /// The previous active command type. public virtual System.Type GetPreviousActiveCommandType() { if (previousActiveCommandIndex >= 0 && previousActiveCommandIndex < commandList.Count) { return commandList[previousActiveCommandIndex].GetType(); } return null; } /// /// Recalculate the indent levels for all commands in the list. /// public virtual void UpdateIndentLevels() { int indentLevel = 0; foreach (ICommand command in commandList) { if (command == null) { continue; } if (command.CloseBlock()) { indentLevel--; } // Negative indent level is not permitted indentLevel = Math.Max(indentLevel, 0); command.IndentLevel = indentLevel; if (command.OpenBlock()) { indentLevel++; } } } #endregion } }