Skip to content

Instantly share code, notes, and snippets.

@Enichan
Last active September 22, 2020 06:44
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Enichan/10736785 to your computer and use it in GitHub Desktop.
Save Enichan/10736785 to your computer and use it in GitHub Desktop.
An implementation of coroutines for C#.
#region License
/*
Copyright © 2014 Emma Maassen
This work is free. You can redistribute it and/or modify it under the
terms of the Do What The Fuck You Want To Public License, Version 2,
as published by Sam Hocevar. See http://www.wtfpl.net/ for more details.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Indigo.Framework {
public interface IResumable {
/// <summary>
/// Starts this resumable instance and yields until done.
/// </summary>
/// <returns>A collection of yielded integers. These can all be zero, or have some functional value.</returns>
IEnumerable<int> Start();
}
/// <summary>
/// Delegate called after every Tick in a Resumable instance. To be used for, for instance, calling Application.DoEvents() or pausing
/// the Resumable instance from the outside.
/// </summary>
/// <param name="resumable">Resumable instance which ticked.</param>
public delegate void TickDelegate(Resumable resumable);
/// <summary>
/// Base class for Resumable implementations.
/// </summary>
public abstract class Resumable : IDisposable {
/// <summary>
/// Runs this Resumable until paused or done.
/// </summary>
/// <param name="del">Optional delegate to call after every Tick.</param>
public abstract void Run(TickDelegate del);
/// <summary>
/// Runs this Resumable until paused, done, or it has run for an amount of time specified.
/// </summary>
/// <param name="timeLimit">Maximum amount of time this Resumable is allowed to run.</param>
/// <param name="del">Optional delegate to call after every Tick.</param>
public abstract void RunUntil(TimeSpan timeLimit, TickDelegate del = null);
/// <summary>
/// Advances this Resumable by a single Tick.
/// </summary>
/// <returns>Boolean value indicating whether this Resumable is still running.</returns>
public abstract bool Tick();
/// <summary>
/// Pauses this Resumable, returning execution from Run or RunUntil methods.
/// </summary>
public abstract void Pause();
/// <summary>
/// Resets the Resumable so it can be used again.
/// </summary>
public abstract void Reset();
/// <summary>
/// Disposes the Resumable, which also calls Dispose on its IResumable child instance.
/// </summary>
public abstract void Dispose();
/// <summary>
/// Boolean value indicating whether the Resumable instance finished processing.
/// </summary>
public abstract bool Done { get; }
}
/// <summary>
/// Takes an instance of type IResumable and runs it as a coroutine.
/// </summary>
/// <typeparam name="T">A type which implements IResumable</typeparam>
public class Resumable<T> : Resumable where T : IResumable {
private T child;
private IEnumerator<int> process;
private bool paused;
private bool done;
private bool disposed = false;
public Resumable(T value)
: base() {
this.child = value;
}
/// <summary>
/// Runs this Resumable until paused or done.
/// </summary>
/// <param name="del">Optional delegate to call after every Tick.</param>
public override void Run(TickDelegate del) {
RunUntil(TimeSpan.Zero, del);
}
/// <summary>
/// Runs this Resumable until paused, done, or it has run for an amount of time specified.
/// </summary>
/// <param name="timeLimit">Maximum amount of time this Resumable is allowed to run.</param>
/// <param name="del">Optional delegate to call after every Tick.</param>
public override void RunUntil(TimeSpan timeLimit, TickDelegate del = null) {
paused = false;
System.Diagnostics.Stopwatch stopwatch = null;
if (timeLimit > TimeSpan.Zero) {
stopwatch = new System.Diagnostics.Stopwatch();
stopwatch.Start();
}
while (!paused && Tick() && (stopwatch == null || stopwatch.ElapsedTicks < timeLimit.Ticks)) {
if (del != null)
del(this);
}
}
/// <summary>
/// Advances this Resumable by a single Tick.
/// </summary>
/// <returns>Boolean value indicating whether this Resumable is still running.</returns>
public override bool Tick() {
if (process == null)
GetProcess();
bool running = process.MoveNext();
if (!running) {
Dispose();
done = true;
}
return running;
}
/// <summary>
/// Pauses this Resumable, returning execution from Run or RunUntil methods.
/// </summary>
public override void Pause() {
paused = true;
}
/// <summary>
/// Resets the Resumable so it can be used again.
/// </summary>
public override void Reset() {
Dispose();
done = false;
}
private IEnumerator<int> GetProcess() {
process = child.Start().GetEnumerator();
return process;
}
/// <summary>
/// Disposes the Resumable.
/// </summary>
public override void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing) {
if (disposed)
return;
if (disposing) {
// Free any other managed objects here.
try {
if (process != null) {
process.Dispose();
}
}
catch { }
}
// Free any unmanaged objects here.
disposed = true;
}
~Resumable() {
Dispose(false);
}
#region Properties
/// <summary>
/// Returns this Resumable's IResumable child instance.
/// </summary>
public T Value { get { return child; } protected set { child = value; } }
/// <summary>
/// Boolean value indicating whether the Resumable instance finished processing.
/// </summary>
public override bool Done { get { return done; } }
#endregion
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment