using System; using Amphora; namespace Ariadne.PerformanceTests { /// /// The context the benchmark arcs run in, which only the polling arcs look at. /// public sealed class FrameWorld { /// /// How many times the event the polling arcs watch for has happened. /// public int Raises; } /// /// An arc that counts how often it wakes, which is how two ways of ticking it are shown to do the same work. /// public abstract class WakeCountingArc : Arc { public int Wakes; protected override void Run(FrameWorld world) { } } /// /// Wakes on a fixed period, or runs every frame when the period is zero, and counts how often it runs. /// public sealed class SleepyArc : WakeCountingArc { public readonly TimeSpan Period; public Duration Deadline; public SleepyArc(TimeSpan period, TimeSpan firstDeadline) { Period = period; Deadline = firstDeadline; } protected override bool __Resume(FrameWorld world, TickTime time) { if (Period == TimeSpan.Zero) { ++Wakes; return false; } if (time.GameTime >= Deadline) { ++Wakes; Deadline = time.GameTime + Period; } __CurrentFrame.Sleep(Deadline); return false; } } /// /// The frame of a herd arc, which holds its trigger wait the way a generated frame does. /// public sealed class HerdFrame : Frame { public readonly TriggerWait Waiting = new TriggerWait(1); public override void AttachWaits(WakeSlot slot) { Waiting.Attach(slot); } } /// /// Waits on a trigger over and over, and counts the times the trigger woke it. /// public sealed class HerdArc : WakeCountingArc { private readonly Trigger trigger; private bool started; public HerdArc(Trigger trigger) { this.trigger = trigger; } protected override Frame __CreateFrame() { return new HerdFrame(); } protected override bool __Resume(FrameWorld world, TickTime time) { var frame = (HerdFrame)__CurrentFrame; if (started) { ++Wakes; } started = true; frame.Waiting.Begin(frame, requiresAll: false); frame.Waiting.Add(trigger); frame.Suspend(); return false; } } /// /// Checks a flag every frame, the way a script that loops on a Yield() does without triggers. /// public sealed class PollingArc : WakeCountingArc { private int seen; protected override bool __Resume(FrameWorld world, TickTime time) { if (world.Raises != seen) { seen = world.Raises; ++Wakes; } return false; } } /// /// Ticks a set of arcs one frame at a time, in whichever way is being timed. /// public abstract class Host { public static readonly TimeSpan FrameLength = TimeSpan.FromMilliseconds(16); private readonly ExceptionSandbox sandbox = new ExceptionSandbox(typeof(AriadneSites).Assembly); private long frame; public readonly FrameWorld World = new FrameWorld(); public readonly WakeCountingArc[] Arcs; protected Host(WakeCountingArc[] arcs) { Arcs = arcs; } public int TotalWakes { get { int returnMe = 0; foreach (WakeCountingArc arc in Arcs) { returnMe += arc.Wakes; } return returnMe; } } protected abstract void TickAll(FrameWorld world, TickContext tick); /// /// Runs one frame, with the game time one frame later than the last. /// public void Step() { ++frame; TickAll(World, new TickContext(sandbox, FrameLength, TimeSpan.FromTicks(FrameLength.Ticks * frame))); } /// /// Runs frames. /// public void Step(int frames) { for (int i = 0; i < frames; ++i) { Step(); } } } /// /// Ticks every arc each frame, the way a host without a scheduler does. /// public sealed class EveryArcHost : Host { public EveryArcHost(WakeCountingArc[] arcs) : base(arcs) { } protected override void TickAll(FrameWorld world, TickContext tick) { for (int i = 0; i < Arcs.Length; ++i) { Arcs[i].Tick(world, tick); } } } /// /// Hands the arcs to an ArcScheduler. /// public sealed class SchedulerHost : Host { private readonly ArcScheduler scheduler = new ArcScheduler(); public SchedulerHost(WakeCountingArc[] arcs, int minimumSleepFrames) : base(arcs) { scheduler.MinimumSleepFrames = minimumSleepFrames; foreach (WakeCountingArc arc in arcs) { scheduler.Add(arc); } } protected override void TickAll(FrameWorld world, TickContext tick) { scheduler.Tick(world, tick); } } /// /// Ticks polling arcs every frame, with the event they watch for happening on a period. /// public sealed class PollingHerdHost : Host { private readonly int periodFrames; private int framesSinceRaise; public PollingHerdHost(int arcCount, int periodFrames) : base(MakeArcs(arcCount)) { this.periodFrames = periodFrames; } private static WakeCountingArc[] MakeArcs(int arcCount) { var returnMe = new WakeCountingArc[arcCount]; for (int i = 0; i < returnMe.Length; ++i) { returnMe[i] = new PollingArc(); } return returnMe; } protected override void TickAll(FrameWorld world, TickContext tick) { for (int i = 0; i < Arcs.Length; ++i) { Arcs[i].Tick(world, tick); } // The event comes after the ticks so that a herd which has yet to subscribe on frame one still sees it. if (++framesSinceRaise == periodFrames) { framesSinceRaise = 0; ++world.Raises; } } } /// /// Hands arcs waiting on one trigger to an ArcScheduler, and raises the trigger on a period. /// public sealed class TriggerHerdHost : Host { private readonly ArcScheduler scheduler = new ArcScheduler(); private readonly Trigger trigger; private readonly int periodFrames; private int framesSinceRaise; public TriggerHerdHost(int arcCount, int periodFrames) : this(arcCount, periodFrames, new Trigger()) { } private TriggerHerdHost(int arcCount, int periodFrames, Trigger trigger) : base(MakeArcs(arcCount, trigger)) { this.periodFrames = periodFrames; this.trigger = trigger; foreach (WakeCountingArc arc in Arcs) { scheduler.Add(arc); } } private static WakeCountingArc[] MakeArcs(int arcCount, Trigger trigger) { var returnMe = new WakeCountingArc[arcCount]; for (int i = 0; i < returnMe.Length; ++i) { returnMe[i] = new HerdArc(trigger); } return returnMe; } protected override void TickAll(FrameWorld world, TickContext tick) { scheduler.Tick(world, tick); if (++framesSinceRaise == periodFrames) { framesSinceRaise = 0; trigger.Raise(); } } } }