using System; using System.Threading; namespace Cysharp.Threading.Tasks { public interface ITriggerHandler { void OnNext(T value); void OnError(Exception ex); void OnCompleted(); void OnCanceled(CancellationToken cancellationToken); // set/get from TriggerEvent ITriggerHandler Prev { get; set; } ITriggerHandler Next { get; set; } } // be careful to use, itself is struct. public struct TriggerEvent { ITriggerHandler head; // head.prev is last ITriggerHandler iteratingHead; ITriggerHandler iteratingNode; void LogError(Exception ex) { #if UNITY_2018_3_OR_NEWER UnityEngine.Debug.LogException(ex); #else Console.WriteLine(ex); #endif } public void SetResult(T value) { if (iteratingNode != null) { throw new InvalidOperationException("Can not trigger itself in iterating."); } var h = head; while (h != null) { iteratingNode = h; try { h.OnNext(value); } catch (Exception ex) { LogError(ex); Remove(h); } // If `h` itself is removed by OnNext, h.Next is null. // Therefore, instead of looking at h.Next, the `iteratingNode` reference itself is replaced. h = h == iteratingNode ? h.Next : iteratingNode; } iteratingNode = null; if (iteratingHead != null) { Add(iteratingHead); iteratingHead = null; } } public void SetCanceled(CancellationToken cancellationToken) { if (iteratingNode != null) { throw new InvalidOperationException("Can not trigger itself in iterating."); } var h = head; while (h != null) { iteratingNode = h; try { h.OnCanceled(cancellationToken); } catch (Exception ex) { LogError(ex); } var next = h == iteratingNode ? h.Next : iteratingNode; iteratingNode = null; Remove(h); h = next; } iteratingNode = null; if (iteratingHead != null) { Add(iteratingHead); iteratingHead = null; } } public void SetCompleted() { if (iteratingNode != null) { throw new InvalidOperationException("Can not trigger itself in iterating."); } var h = head; while (h != null) { iteratingNode = h; try { h.OnCompleted(); } catch (Exception ex) { LogError(ex); } var next = h == iteratingNode ? h.Next : iteratingNode; iteratingNode = null; Remove(h); h = next; } iteratingNode = null; if (iteratingHead != null) { Add(iteratingHead); iteratingHead = null; } } public void SetError(Exception exception) { if (iteratingNode != null) { throw new InvalidOperationException("Can not trigger itself in iterating."); } var h = head; while (h != null) { iteratingNode = h; try { h.OnError(exception); } catch (Exception ex) { LogError(ex); } var next = h == iteratingNode ? h.Next : iteratingNode; iteratingNode = null; Remove(h); h = next; } iteratingNode = null; if (iteratingHead != null) { Add(iteratingHead); iteratingHead = null; } } public void Add(ITriggerHandler handler) { if (handler == null) throw new ArgumentNullException(nameof(handler)); // zero node. if (head == null) { head = handler; return; } if (iteratingNode != null) { if (iteratingHead == null) { iteratingHead = handler; return; } var last = iteratingHead.Prev; if (last == null) { // single node. iteratingHead.Prev = handler; iteratingHead.Next = handler; handler.Prev = iteratingHead; } else { // multi node iteratingHead.Prev = handler; last.Next = handler; handler.Prev = last; } } else { var last = head.Prev; if (last == null) { // single node. head.Prev = handler; head.Next = handler; handler.Prev = head; } else { // multi node head.Prev = handler; last.Next = handler; handler.Prev = last; } } } public void Remove(ITriggerHandler handler) { if (handler == null) throw new ArgumentNullException(nameof(handler)); var prev = handler.Prev; var next = handler.Next; if (next != null) { next.Prev = prev; } if (handler == head) { head = next; } // when handler is head, prev indicate last so don't use it. else if (prev != null) { prev.Next = next; } if (handler == iteratingNode) { iteratingNode = next; } if (handler == iteratingHead) { iteratingHead = next; } if (head != null) { if (head.Prev == handler) { if (prev != head) { head.Prev = prev; } else { head.Prev = null; } } } if (iteratingHead != null) { if (iteratingHead.Prev == handler) { if (prev != iteratingHead.Prev) { iteratingHead.Prev = prev; } else { iteratingHead.Prev = null; } } } handler.Prev = null; handler.Next = null; } } }