using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using UnityEngine; namespace Unity.VisualScripting { /// /// Delays flow by waiting until multiple input flows have been executed. /// [UnitCategory("Time")] [UnitOrder(6)] [TypeIcon(typeof(WaitUnit))] public sealed class WaitForFlow : Unit, IGraphElementWithData { public sealed class Data : IGraphElementData { public bool[] inputsActivated; public bool isWaitingCoroutine; } /// /// Whether the activation status should be reset on exit. /// [Serialize] [Inspectable] public bool resetOnExit { get; set; } [SerializeAs(nameof(inputCount))] private int _inputCount = 2; [DoNotSerialize] [Inspectable, UnitHeaderInspectable("Inputs")] public int inputCount { get => _inputCount; set => _inputCount = Mathf.Clamp(value, 2, 10); } [DoNotSerialize] public ReadOnlyCollection awaitedInputs { get; private set; } /// /// Trigger to reset the activation status. /// [DoNotSerialize] public ControlInput reset { get; private set; } /// /// Triggered after all inputs have been entered at least once. /// [DoNotSerialize] [PortLabelHidden] public ControlOutput exit { get; private set; } protected override void Definition() { var _awaitedInputs = new List(); awaitedInputs = _awaitedInputs.AsReadOnly(); exit = ControlOutput(nameof(exit)); for (var i = 0; i < inputCount; i++) { var _i = i; // Cache outside closure var awaitedInput = ControlInputCoroutine(_i.ToString(), (flow) => Enter(flow, _i), (flow) => EnterCoroutine(flow, _i)); _awaitedInputs.Add(awaitedInput); Succession(awaitedInput, exit); } reset = ControlInput(nameof(reset), Reset); } public IGraphElementData CreateData() { return new Data() { inputsActivated = new bool[inputCount] }; } private ControlOutput Enter(Flow flow, int index) { var data = flow.stack.GetElementData(this); data.inputsActivated[index] = true; if (CheckActivated(flow)) { if (resetOnExit) { Reset(flow); } return exit; } else { return null; } } private bool CheckActivated(Flow flow) { var data = flow.stack.GetElementData(this); for (int i = 0; i < data.inputsActivated.Length; i++) { if (!data.inputsActivated[i]) { return false; } } return true; } private IEnumerator EnterCoroutine(Flow flow, int index) { var data = flow.stack.GetElementData(this); data.inputsActivated[index] = true; if (data.isWaitingCoroutine) { // Another input started an async wait, // we'll let that flow be responsible for // triggering the exit. yield break; } if (!CheckActivated(flow)) { data.isWaitingCoroutine = true; yield return new WaitUntil(() => CheckActivated(flow)); data.isWaitingCoroutine = false; } if (resetOnExit) { Reset(flow); } yield return exit; } private ControlOutput Reset(Flow flow) { var data = flow.stack.GetElementData(this); for (int i = 0; i < data.inputsActivated.Length; i++) { data.inputsActivated[i] = false; } return null; } } }