using System; using System.Collections.Generic; namespace Ariadne.UnitTests { /// /// The context the scheduler tests run arcs in, where every arc logs what it does into one list. /// public class SchedulerWorld { public readonly List Events = new List(); public readonly HashSet Open = new HashSet(); } /// /// Naps twice, logging around each. /// public partial class NapScript : Arc { public string Name; public TimeSpan Nap; protected override void Run(SchedulerWorld world) { world.Events.Add(Name + " begins"); first: Wait(Nap); world.Events.Add(Name + " wakes"); second: Wait(Nap); world.Events.Add(Name + " wakes again"); } } /// /// Checks a gate every frame until it opens. /// public partial class PollScript : Arc { public string Name; protected override void Run(SchedulerWorld world) { world.Events.Add(Name + " begins"); poll: while (!world.Open.Contains("go")) { world.Events.Add(Name + " polls"); Yield(); } world.Events.Add(Name + " goes"); } } /// /// Naps, then pauses for one frame, then naps again. /// public partial class ChainScript : Arc { public string Name; public TimeSpan Nap; protected override void Run(SchedulerWorld world) { first: Wait(Nap); world.Events.Add(Name + " wakes"); second: Yield(); world.Events.Add(Name + " yielded"); third: Wait(Nap); world.Events.Add(Name + " done"); } } /// /// Naps in a passage of its own, twice. /// public partial class PassageNapScript : Arc { public string Name; public TimeSpan Nap; private void Rest(SchedulerWorld world) { rest: Wait(Nap); world.Events.Add(Name + " rested"); } protected override void Run(SchedulerWorld world) { one: Rest(world); two: Rest(world); } } /// /// Offers a passage for other arcs to run, which naps a second. /// public partial class NapWorkerScript : Arc { public void Pace(SchedulerWorld world) { rest: Wait(TimeSpan.FromSeconds(1)); world.Events.Add("worker rested"); } protected override void Run(SchedulerWorld world) { } } /// /// Runs the passage of another arc that naps, which is never ticked itself. /// public partial class NapClientScript : Arc { public NapWorkerScript Worker = new NapWorkerScript(); protected override void Run(SchedulerWorld world) { pace: Worker.Pace(world); world.Events.Add("client done"); } } /// /// Counts how often it runs, and sleeps until a game time. /// public class SleeperArc : Arc { public int Resumes; public TimeSpan Deadline; protected override bool __Resume(SchedulerWorld world, TickTime time) { ++Resumes; if (time.GameTime < Deadline) { __CurrentFrame.Sleep(Deadline); return false; } return true; } protected override void Run(SchedulerWorld world) { } } /// /// Suspends each time it runs, and also sleeps until a game time if it has a deadline. /// public class SuspenderArc : Arc { public int Resumes; public string Name; public TimeSpan? Deadline; public readonly TriggerWait Waiting = new TriggerWait(capacity: 8); // The triggers it waits on again each time it runs. public Trigger[] Triggers; public bool RequireAll; // The resumes on which the arc finishes and on which it throws. Zero is never. public int FinishAtResume; public int ThrowAtResume; public void WaitOn(params Trigger[] triggers) { Waiting.Begin(__CurrentFrame, RequireAll); foreach (Trigger trigger in triggers) { Waiting.Add(trigger); } } protected override bool __Resume(SchedulerWorld world, TickTime time) { ++Resumes; if (Triggers != null) { WaitOn(Triggers); } if (Name != null) { world.Events.Add(Name); } if (Resumes == ThrowAtResume) { throw new InvalidOperationException("boom"); } if (Resumes == FinishAtResume) { return true; } __CurrentFrame.Suspend(); if (Deadline.HasValue) { __CurrentFrame.Sleep(Deadline.Value); } return false; } protected override void Run(SchedulerWorld world) { } } /// /// Suspends like its base, and overrides TickSelf as an arc that ticks other arcs does. /// public class SuspenderParentArc : SuspenderArc { protected override bool TickSelf(SchedulerWorld world, TickContext tick) { return base.TickSelf(world, tick); } protected override bool __Resume(SchedulerWorld world, TickTime time) { return base.__Resume(world, time); } } /// /// Writes its name into the world's events each time it runs, and pauses for a frame. /// public class RecordingArc : Arc { public string Name; protected override bool __Resume(SchedulerWorld world, TickTime time) { world.Events.Add(Name); return false; } protected override void Run(SchedulerWorld world) { } } /// /// Counts how often it runs, and pauses for a frame each time. /// public class YieldingArc : Arc { public int Resumes; protected override bool __Resume(SchedulerWorld world, TickTime time) { ++Resumes; return false; } protected override void Run(SchedulerWorld world) { } } /// /// Sleeps until a game time, and overrides TickSelf as an arc that ticks other arcs does. /// public class ParentArc : Arc { public int SelfTicks; public TimeSpan Deadline; protected override bool TickSelf(SchedulerWorld world, TickContext tick) { ++SelfTicks; return base.TickSelf(world, tick); } protected override bool __Resume(SchedulerWorld world, TickTime time) { if (time.GameTime < Deadline) { __CurrentFrame.Sleep(Deadline); return false; } return true; } protected override void Run(SchedulerWorld world) { } } /// /// Throws on its first run, and counts how often it runs. /// public class ThrowingSchedulerArc : Arc { public int Resumes; protected override bool __Resume(SchedulerWorld world, TickTime time) { ++Resumes; if (Resumes == 1) { throw new InvalidOperationException("boom"); } return true; } protected override void Run(SchedulerWorld world) { } } /// /// Calls into the scheduler that is ticking it, as game code reacting to a script might. /// public class SchedulerUsingArc : Arc { public Action OnResume; public int Resumes; protected override bool __Resume(SchedulerWorld world, TickTime time) { ++Resumes; if (OnResume != null) { OnResume(); } return Resumes >= 2; } protected override void Run(SchedulerWorld world) { } } /// /// Naps, then throws the first time it wakes. /// public partial class NapThenThrowScript : Arc { public TimeSpan Nap; public int Wakes; protected override void Run(SchedulerWorld world) { nap: Wait(Nap); Wakes++; if (Wakes == 1) { throw new InvalidOperationException("boom"); } } } /// /// Has a method named like the one the scheduler looks for, which it doesn't override. /// public class OverloadedTickSelfArc : Arc { protected int TickSelf(int times) { return times; } protected override bool __Resume(SchedulerWorld world, TickTime time) { return false; } protected override void Run(SchedulerWorld world) { } } }