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; bool preserveRemoveSelf; 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 (preserveRemoveSelf) { preserveRemoveSelf = false; iteratingNode = null; var next = h.Next; Remove(h); h = next; } else { h = h.Next; } } 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); } preserveRemoveSelf = false; iteratingNode = null; var next = h.Next; 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); } preserveRemoveSelf = false; iteratingNode = null; var next = h.Next; 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); } preserveRemoveSelf = false; iteratingNode = null; var next = h.Next; 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)); if (iteratingNode != null && iteratingNode == handler) { // if remove self, reserve remove self after invoke completed. preserveRemoveSelf = true; } else { var prev = handler.Prev; var next = handler.Next; if (next != null) { next.Prev = prev; } if (handler == head) { head = next; } else if (handler == iteratingHead) { iteratingHead = next; } else { // when handler is head, prev indicate last so don't use it. if (prev != null) { prev.Next = 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; } } } }