Last active
September 22, 2020 06:44
-
-
Save Enichan/10736785 to your computer and use it in GitHub Desktop.
An implementation of coroutines for C#.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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