using System; using System.Collections.Generic; namespace RSG { public class PromiseCancelledException : Exception { /// /// Just create the exception /// public PromiseCancelledException() { } /// /// Create the exception with description /// /// Exception description public PromiseCancelledException(string message) : base(message) { } } /// /// A class that wraps a pending promise with it's predicate and time data /// class PredicateWait { /// /// Predicate for resolving the promise /// public Func predicate; /// /// The time the promise was started /// public float timeStarted; /// /// The pending promise which is an interface for a promise that can be rejected or resolved. /// public IPendingPromise pendingPromise; /// /// The time data specific to this pending promise. Includes elapsed time and delta time. /// public TimeData timeData; /// /// The frame the promise was started /// public int frameStarted; } /// /// Time data specific to a particular pending promise. /// public struct TimeData { /// /// The amount of time that has elapsed since the pending promise started running /// public float elapsedTime; /// /// The amount of time since the last time the pending promise was updated. /// public float deltaTime; /// /// The amount of times that update has been called since the pending promise started running /// public int elapsedUpdates; } public interface IPromiseTimer { /// /// Resolve the returned promise once the time has elapsed /// IPromise WaitFor(float seconds); /// /// Resolve the returned promise once the predicate evaluates to true /// IPromise WaitUntil(Func predicate); /// /// Resolve the returned promise once the predicate evaluates to false /// IPromise WaitWhile(Func predicate); /// /// Update all pending promises. Must be called for the promises to progress and resolve at all. /// void Update(float deltaTime); /// /// Cancel a waiting promise and reject it immediately. /// bool Cancel(IPromise promise); } public class PromiseTimer : IPromiseTimer { /// /// The current running total for time that this PromiseTimer has run for /// float curTime; /// /// The current running total for the amount of frames the PromiseTimer has run for /// int curFrame; /// /// Currently pending promises /// readonly LinkedList waiting = new LinkedList(); /// /// Resolve the returned promise once the time has elapsed /// public IPromise WaitFor(float seconds) { return this.WaitUntil(t => t.elapsedTime >= seconds); } /// /// Resolve the returned promise once the predicate evaluates to false /// public IPromise WaitWhile(Func predicate) { return this.WaitUntil(t => !predicate(t)); } /// /// Resolve the returned promise once the predicate evalutes to true /// public IPromise WaitUntil(Func predicate) { var promise = new Promise(); var wait = new PredicateWait() { timeStarted = this.curTime, pendingPromise = promise, timeData = new TimeData(), predicate = predicate, frameStarted = this.curFrame }; this.waiting.AddLast(wait); return promise; } public bool Cancel(IPromise promise) { var node = this.FindInWaiting(promise); if (node == null) { return false; } node.Value.pendingPromise.Reject(new PromiseCancelledException("Promise was cancelled by user.")); this.waiting.Remove(node); return true; } LinkedListNode FindInWaiting(IPromise promise) { for (var node = this.waiting.First; node != null; node = node.Next) { if (node.Value.pendingPromise.Id.Equals(promise.Id)) { return node; } } return null; } /// /// Update all pending promises. Must be called for the promises to progress and resolve at all. /// public void Update(float deltaTime) { this.curTime += deltaTime; this.curFrame += 1; var node = this.waiting.First; while (node != null) { var wait = node.Value; var newElapsedTime = this.curTime - wait.timeStarted; wait.timeData.deltaTime = newElapsedTime - wait.timeData.elapsedTime; wait.timeData.elapsedTime = newElapsedTime; var newElapsedUpdates = this.curFrame - wait.frameStarted; wait.timeData.elapsedUpdates = newElapsedUpdates; bool result; try { result = wait.predicate(wait.timeData); } catch (Exception ex) { wait.pendingPromise.Reject(ex); node = this.RemoveNode(node); continue; } if (result) { wait.pendingPromise.Resolve(); node = this.RemoveNode(node); } else { node = node.Next; } } } /// /// Removes the provided node and returns the next node in the list. /// LinkedListNode RemoveNode(LinkedListNode node) { var currentNode = node; node = node.Next; this.waiting.Remove(currentNode); return node; } } }