// Represents a single set of blocks to run in order, waiting for each group's completion before // moving on to the next one. using Sandbox.ModAPI.Ingame; using System.Collections.Generic; using System.Linq; namespace IngameScript { partial class Program { public class Sequencer { public bool Running { get; private set; } public string Name { get; private set; } private IConsole _console; private SortedDictionary<int, List<ISequenceable>> _sequence = new SortedDictionary<int, List<ISequenceable>>(); public Sequencer(IConsoleProgram program, string name) { Name = name; _console = new PrefixedConsole(program.Console, Name); } public void AddBlock(ISequenceable block) { if (!_sequence.ContainsKey(block.Step)) _sequence[block.Step] = new List<ISequenceable>(); _sequence[block.Step].Add(block); } // To activate the Sequencer, call this once, then call `MoveNext()` once per tick // on the returned object until it returns false. // (then be sure to call `Dispose() on that object`) public IEnumerator<bool> RunSequence(bool deploy = true) { if (Running) { _console.Print("Already running, ignoring invocation."); yield break; } // This is a bit convoluted, but we're extracting the iterator for use in the foreach directly. // This allows us to iterate in reverse when reversing/"closing" the sequence. IEnumerable<KeyValuePair<int, List<ISequenceable>>> steps = deploy ? _sequence : _sequence.Reverse(); foreach (KeyValuePair<int, List<ISequenceable>> kvp in steps) { List<ISequenceable> blocks = kvp.Value; List<IEnumerator<bool>> subJobs = new List<IEnumerator<bool>>(); Running = true; foreach (ISequenceable block in blocks) { // TODO: add some sort of handling for block.Running == true, maybe? // It *should* be an impossible case, but... subJobs.Add(block.Run(deploy)); } while (Running) { Running = false; foreach (IEnumerator<bool> subJob in subJobs) { if (subJob.MoveNext()) Running = true; } yield return true; } foreach (IEnumerator<bool> subJob in subJobs) subJob.Dispose(); // clean up after ourselves } } } } }