Resumable scripts in C#

CSharp · Console

/Program.cs

using System;
using System.Collections.Generic;
using System.Threading;

namespace ConsoleApplication1
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var l = new Looper();
            l.Loop();
        }
    }

    public class Looper
    {
        public Looper()
        {
            for (var i = 0; i < 5; i++)
            {
                var e = new Entity
                {
                    CurrentScript = new BlahScript()
                };

                Entities.Add(e);
            }
        }

        private List<Entity> Entities { get; } = new List<Entity>();

        public void Loop()
        {
            while (true)
            {
                foreach (var e in Entities)
                {
                    var script = e.CurrentScript;

                    if (script.IsReadyToContinue)
                    {
                        script.Run();
                    }
                }

                Console.WriteLine("Tick");

                if (Entities.TrueForAll(x => x.CurrentScript.IsFinished)) break;

                Thread.Sleep(500);
            }

            Console.WriteLine("\nEnd of loop.");
        }
    }

    internal class Entity
    {
        public IScript CurrentScript { get; set; }
        public IContinueTrigger CurrentActionTrigger { get; set; }
    }

    #region Script

    internal interface IScript
    {
        bool IsReadyToContinue { get; }
        bool IsFinished { get; }
        void Run();
    }

    internal abstract class AbstractScript : IScript
    {
        private readonly IEnumerator<IContinueTrigger> _scriptSteps;
        private IContinueTrigger _trigger;

        protected AbstractScript()
        {
            _scriptSteps = RunInner();
        }

        public void Run()
        {
            var e = _scriptSteps;
            var hasMore = e.MoveNext();

            if (!hasMore)
            {
                IsFinished = true;
                _trigger = null;
                return;
            }

            _trigger = e.Current;
        }

        public bool IsReadyToContinue => _trigger?.IsReady ?? true;
        public bool IsFinished { get; private set; }
        public abstract IEnumerator<IContinueTrigger> RunInner();

        protected IContinueTrigger SleepOne()
        {
            return new ContinueImmediatelyTrigger();
        }

        protected IContinueTrigger Sleep(TimeSpan ts)
        {
            return new ContinueAfterElapsedTimeTrigger(ts);
        }
    }

    internal class BlahScript : AbstractScript
    {
        public override IEnumerator<IContinueTrigger> RunInner()
        {
            Console.WriteLine("Hi there!");

            yield return SleepOne();

            Console.WriteLine("Let's talk in a few seconds...");

            yield return Sleep(TimeSpan.FromSeconds(3));

            Console.WriteLine("Scratch that, I have to go. :(");
        }
    }

    #endregion

    #region Continue triggers

    internal interface IContinueTrigger
    {
        bool IsReady { get; }
    }

    internal class ContinueImmediatelyTrigger : IContinueTrigger
    {
        public bool IsReady => true;
    }

    internal class ContinueAfterElapsedTimeTrigger : IContinueTrigger
    {
        private readonly DateTime _timeToRun;

        public ContinueAfterElapsedTimeTrigger(TimeSpan ts)
        {
            _timeToRun = DateTime.Now.Add(ts);
        }

        public bool IsReady => DateTime.Now >= _timeToRun;
    }

    #endregion
}
;