From 130286e8c25fb6c765eb2034e27eff722be0f212 Mon Sep 17 00:00:00 2001 From: neuecc Date: Mon, 1 Jun 2020 13:40:35 +0900 Subject: [PATCH] TriggerEvent becomes linkedlist --- src/UniTask.NetCore/NetCore/UniTask.Delay.cs | 39 ++ src/UniTask.NetCoreTests/TriggerEventTest.cs | 637 ++++++++++++++++++ .../UniTask/Runtime/AsyncReactiveProperty.cs | 5 + .../Plugins/UniTask/Runtime/Linq/Publish.cs | 2 + .../Plugins/UniTask/Runtime/TriggerEvent.cs | 417 +++++------- .../Runtime/Triggers/AsyncTriggerBase.cs | 5 + .../UniTask/Runtime/UniTask.Threading.cs | 64 +- 7 files changed, 925 insertions(+), 244 deletions(-) create mode 100644 src/UniTask.NetCore/NetCore/UniTask.Delay.cs create mode 100644 src/UniTask.NetCoreTests/TriggerEventTest.cs diff --git a/src/UniTask.NetCore/NetCore/UniTask.Delay.cs b/src/UniTask.NetCore/NetCore/UniTask.Delay.cs new file mode 100644 index 0000000..aaf900f --- /dev/null +++ b/src/UniTask.NetCore/NetCore/UniTask.Delay.cs @@ -0,0 +1,39 @@ +//using Cysharp.Threading.Tasks.Internal; +//using System; +//using System.Collections.Concurrent; +//using System.Runtime.CompilerServices; +//using System.Threading; + +//namespace Cysharp.Threading.Tasks +//{ +// public partial struct UniTask +// { +// public static UniTask Delay() +// { +// return default; +// } + +// sealed class DelayPromise : IUniTaskSource +// { +// public void GetResult(short token) +// { +// throw new NotImplementedException(); +// } + +// public UniTaskStatus GetStatus(short token) +// { +// throw new NotImplementedException(); +// } + +// public void OnCompleted(Action continuation, object state, short token) +// { +// throw new NotImplementedException(); +// } + +// public UniTaskStatus UnsafeGetStatus() +// { +// throw new NotImplementedException(); +// } +// } +// } +//} \ No newline at end of file diff --git a/src/UniTask.NetCoreTests/TriggerEventTest.cs b/src/UniTask.NetCoreTests/TriggerEventTest.cs new file mode 100644 index 0000000..d1bdf5c --- /dev/null +++ b/src/UniTask.NetCoreTests/TriggerEventTest.cs @@ -0,0 +1,637 @@ +using Cysharp.Threading.Tasks; +using FluentAssertions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Channels; +using Cysharp.Threading.Tasks.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace NetCoreTests +{ + public class TriggerEventTest + { + [Fact] + public void SimpleAdd() + { + var ev = new TriggerEvent(); + + // do nothing + ev.SetResult(0); + ev.SetError(null); + ev.SetCompleted(); + ev.SetCanceled(default); + + { + var one = new TestEvent(1); + + ev.Add(one); + + ev.SetResult(10); + ev.SetResult(20); + ev.SetResult(30); + + one.NextCalled.Should().BeEquivalentTo(10, 20, 30); + + ev.SetCompleted(); + + one.CompletedCalled.Count.Should().Be(1); + + // do nothing + ev.SetResult(0); + ev.SetError(null); + ev.SetCompleted(); + ev.SetCanceled(default); + + one.NextCalled.Should().BeEquivalentTo(10, 20, 30); + one.CompletedCalled.Count.Should().Be(1); + } + // after removed, onemore + { + var one = new TestEvent(1); + + ev.Add(one); + + ev.SetResult(10); + ev.SetResult(20); + ev.SetResult(30); + + one.NextCalled.Should().BeEquivalentTo(10, 20, 30); + + ev.SetCompleted(); + + one.CompletedCalled.Count.Should().Be(1); + + // do nothing + ev.SetResult(0); + ev.SetError(null); + ev.SetCompleted(); + ev.SetCanceled(default); + + one.NextCalled.Should().BeEquivalentTo(10, 20, 30); + one.CompletedCalled.Count.Should().Be(1); + } + } + + [Fact] + public void AddFour() + { + var ev = new TriggerEvent(); + + // do nothing + ev.SetResult(0); + ev.SetError(null); + ev.SetCompleted(); + ev.SetCanceled(default); + + { + var one = new TestEvent(1); + var two = new TestEvent(2); + var three = new TestEvent(3); + var four = new TestEvent(4); + + ev.Add(one); + ev.Add(two); + ev.Add(three); + ev.Add(four); + + ev.SetResult(10); + ev.SetResult(20); + ev.SetResult(30); + + one.NextCalled.Should().BeEquivalentTo(10, 20, 30); + two.NextCalled.Should().BeEquivalentTo(10, 20, 30); + three.NextCalled.Should().BeEquivalentTo(10, 20, 30); + four.NextCalled.Should().BeEquivalentTo(10, 20, 30); + + ev.SetCompleted(); + + one.CompletedCalled.Count.Should().Be(1); + two.CompletedCalled.Count.Should().Be(1); + three.CompletedCalled.Count.Should().Be(1); + + + // do nothing + ev.SetResult(0); + ev.SetError(null); + ev.SetCompleted(); + ev.SetCanceled(default); + + one.NextCalled.Should().BeEquivalentTo(10, 20, 30); + one.CompletedCalled.Count.Should().Be(1); + two.NextCalled.Should().BeEquivalentTo(10, 20, 30); + three.CompletedCalled.Count.Should().Be(1); + two.NextCalled.Should().BeEquivalentTo(10, 20, 30); + three.CompletedCalled.Count.Should().Be(1); + } + + // after removed, onemore. + { + var one = new TestEvent(1); + var two = new TestEvent(2); + var three = new TestEvent(3); + var four = new TestEvent(4); + + ev.Add(one); + ev.Add(two); + ev.Add(three); + ev.Add(four); + + ev.SetResult(10); + ev.SetResult(20); + ev.SetResult(30); + ev.Add(four); + + one.NextCalled.Should().BeEquivalentTo(10, 20, 30); + two.NextCalled.Should().BeEquivalentTo(10, 20, 30); + three.NextCalled.Should().BeEquivalentTo(10, 20, 30); + four.NextCalled.Should().BeEquivalentTo(10, 20, 30); + + ev.SetCompleted(); + + one.CompletedCalled.Count.Should().Be(1); + two.CompletedCalled.Count.Should().Be(1); + three.CompletedCalled.Count.Should().Be(1); + + + // do nothing + ev.SetResult(0); + ev.SetError(null); + ev.SetCompleted(); + ev.SetCanceled(default); + + one.NextCalled.Should().BeEquivalentTo(10, 20, 30); + one.CompletedCalled.Count.Should().Be(1); + two.NextCalled.Should().BeEquivalentTo(10, 20, 30); + three.CompletedCalled.Count.Should().Be(1); + two.NextCalled.Should().BeEquivalentTo(10, 20, 30); + three.CompletedCalled.Count.Should().Be(1); + } + } + + + [Fact] + public void OneRemove() + { + var ev = new TriggerEvent(); + { + var one = new TestEvent(1); + var two = new TestEvent(2); + var three = new TestEvent(3); + + ev.Add(one); + ev.Add(two); + ev.Add(three); + + ev.SetResult(10); + ev.SetResult(20); + ev.SetResult(30); + + one.NextCalled.Should().BeEquivalentTo(10, 20, 30); + two.NextCalled.Should().BeEquivalentTo(10, 20, 30); + three.NextCalled.Should().BeEquivalentTo(10, 20, 30); + + ev.Remove(one); + + ev.SetResult(40); + ev.SetResult(50); + ev.SetResult(60); + + one.NextCalled.Should().BeEquivalentTo(10, 20, 30); + two.NextCalled.Should().BeEquivalentTo(10, 20, 30, 40, 50, 60); + three.NextCalled.Should().BeEquivalentTo(10, 20, 30, 40, 50, 60); + } + } + [Fact] + public void TwoRemove() + { + var ev = new TriggerEvent(); + { + var one = new TestEvent(1); + var two = new TestEvent(2); + var three = new TestEvent(3); + + ev.Add(one); + ev.Add(two); + ev.Add(three); + + ev.SetResult(10); + ev.SetResult(20); + ev.SetResult(30); + + one.NextCalled.Should().BeEquivalentTo(10, 20, 30); + two.NextCalled.Should().BeEquivalentTo(10, 20, 30); + three.NextCalled.Should().BeEquivalentTo(10, 20, 30); + + ev.Remove(two); + + ev.SetResult(40); + ev.SetResult(50); + ev.SetResult(60); + + one.NextCalled.Should().BeEquivalentTo(10, 20, 30, 40, 50, 60); + two.NextCalled.Should().BeEquivalentTo(10, 20, 30); + three.NextCalled.Should().BeEquivalentTo(10, 20, 30, 40, 50, 60); + } + } + [Fact] + public void ThreeRemove() + { + var ev = new TriggerEvent(); + { + var one = new TestEvent(1); + var two = new TestEvent(2); + var three = new TestEvent(3); + + ev.Add(one); + ev.Add(two); + ev.Add(three); + + ev.SetResult(10); + ev.SetResult(20); + ev.SetResult(30); + + one.NextCalled.Should().BeEquivalentTo(10, 20, 30); + two.NextCalled.Should().BeEquivalentTo(10, 20, 30); + three.NextCalled.Should().BeEquivalentTo(10, 20, 30); + + ev.Remove(three); + + ev.SetResult(40); + ev.SetResult(50); + ev.SetResult(60); + + one.NextCalled.Should().BeEquivalentTo(10, 20, 30, 40, 50, 60); + two.NextCalled.Should().BeEquivalentTo(10, 20, 30, 40, 50, 60); + three.NextCalled.Should().BeEquivalentTo(10, 20, 30); + } + } + + [Fact] + public void RemoveSelf() + { + new RemoveMe().Run1(); + new RemoveMe().Run2(); + new RemoveMe().Run3(); + } + + [Fact] + public void RemoveNextInIterating() + { + new RemoveNext().Run1(); + new RemoveNext().Run2(); + new RemoveNext().Run3(); + } + + [Fact] + public void RemoveNextNextTest() + { + new RemoveNextNext().Run1(); + new RemoveNextNext().Run2(); + } + + + [Fact] + public void AddTest() + { + new AddMe().Run1(); + new AddMe().Run2(); + } + + public class RemoveMe + { + TriggerEvent ev; + + public void Run1() + { + TestEvent one = default; + one = new TestEvent(1, () => ev.Remove(one)); + + var two = new TestEvent(2); + var three = new TestEvent(3); + + ev.Add(one); + ev.Add(two); + ev.Add(three); + + ev.SetResult(10); + ev.SetResult(20); + ev.SetResult(30); + + one.NextCalled.Should().BeEquivalentTo(10); + two.NextCalled.Should().BeEquivalentTo(10, 20, 30); + three.NextCalled.Should().BeEquivalentTo(10, 20, 30); + } + + public void Run2() + { + TestEvent one = default; + one = new TestEvent(1, () => ev.Remove(one)); + + var two = new TestEvent(2); + var three = new TestEvent(3); + + ev.Add(two); + ev.Add(one); // add second. + ev.Add(three); + + ev.SetResult(10); + ev.SetResult(20); + ev.SetResult(30); + + one.NextCalled.Should().BeEquivalentTo(10); + two.NextCalled.Should().BeEquivalentTo(10, 20, 30); + three.NextCalled.Should().BeEquivalentTo(10, 20, 30); + } + + public void Run3() + { + TestEvent one = default; + one = new TestEvent(1, () => ev.Remove(one)); + + var two = new TestEvent(2); + var three = new TestEvent(3); + + ev.Add(two); + ev.Add(three); + ev.Add(one); // add thired. + + ev.SetResult(10); + ev.SetResult(20); + ev.SetResult(30); + + one.NextCalled.Should().BeEquivalentTo(10); + two.NextCalled.Should().BeEquivalentTo(10, 20, 30); + three.NextCalled.Should().BeEquivalentTo(10, 20, 30); + } + } + + public class RemoveNext + { + TriggerEvent ev; + + public void Run1() + { + TestEvent one = default; + TestEvent two = default; + TestEvent three = default; + one = new TestEvent(1, () => ev.Remove(two)); + two = new TestEvent(2); + three = new TestEvent(3); + + ev.Add(one); + ev.Add(two); + ev.Add(three); + + ev.SetResult(10); + ev.SetResult(20); + ev.SetResult(30); + + one.NextCalled.Should().BeEquivalentTo(10, 20, 30); + two.NextCalled.Count.Should().Be(0); + three.NextCalled.Should().BeEquivalentTo(10, 20, 30); + } + + public void Run2() + { + TestEvent one = default; + TestEvent two = default; + TestEvent three = default; + one = new TestEvent(1, () => ev.Remove(two)); + two = new TestEvent(2); + three = new TestEvent(3); + + ev.Add(two); + ev.Add(one); // add second + ev.Add(three); + + ev.SetResult(10); + ev.SetResult(20); + ev.SetResult(30); + + one.NextCalled.Should().BeEquivalentTo(10, 20, 30); + two.NextCalled.Should().BeEquivalentTo(10); + three.NextCalled.Should().BeEquivalentTo(10, 20, 30); + } + + public void Run3() + { + TestEvent one = default; + TestEvent two = default; + TestEvent three = default; + one = new TestEvent(1, () => ev.Remove(two)); + two = new TestEvent(2); + three = new TestEvent(3); + + ev.Add(two); + ev.Add(three); + ev.Add(one); // add thired. + + ev.SetResult(10); + ev.SetResult(20); + ev.SetResult(30); + + one.NextCalled.Should().BeEquivalentTo(10, 20, 30); + two.NextCalled.Should().BeEquivalentTo(10); + three.NextCalled.Should().BeEquivalentTo(10, 20, 30); + } + } + + public class RemoveNextNext + { + TriggerEvent ev; + + public void Run1() + { + TestEvent one = default; + TestEvent two = default; + TestEvent three = default; + TestEvent four = default; + one = new TestEvent(1, () => { ev.Remove(two); ev.Remove(three); }); + two = new TestEvent(2); + three = new TestEvent(3); + four = new TestEvent(4); + + ev.Add(one); + ev.Add(two); + ev.Add(three); + ev.Add(four); + + ev.SetResult(10); + ev.SetResult(20); + ev.SetResult(30); + + one.NextCalled.Should().BeEquivalentTo(10, 20, 30); + two.NextCalled.Count.Should().Be(0); + three.NextCalled.Count.Should().Be(0); + four.NextCalled.Should().BeEquivalentTo(10, 20, 30); + } + + public void Run2() + { + TestEvent one = default; + TestEvent two = default; + TestEvent three = default; + TestEvent four = default; + one = new TestEvent(1, () => { ev.Remove(one); ev.Remove(two); ev.Remove(three); }); + two = new TestEvent(2); + three = new TestEvent(3); + four = new TestEvent(4); + + ev.Add(one); + ev.Add(two); + ev.Add(three); + ev.Add(four); + + ev.SetResult(10); + ev.SetResult(20); + ev.SetResult(30); + + one.NextCalled.Should().BeEquivalentTo(10); + two.NextCalled.Count.Should().Be(0); + three.NextCalled.Count.Should().Be(0); + four.NextCalled.Should().BeEquivalentTo(10, 20, 30); + } + + + } + + public class AddMe + { + TriggerEvent ev; + + public void Run1() + { + TestEvent one = default; + TestEvent two = default; + TestEvent three = default; + TestEvent four = default; + + one = new TestEvent(1, () => + { + if (two == null) + { + ev.Add(two = new TestEvent(2)); + } + else if (three == null) + { + ev.Add(three = new TestEvent(3)); + } + else if (four == null) + { + ev.Add(four = new TestEvent(4)); + } + }); + + ev.Add(one); + + ev.SetResult(10); + ev.SetResult(20); + ev.SetResult(30); + ev.SetResult(40); + + one.NextCalled.Should().BeEquivalentTo(10, 20, 30, 40); + two.NextCalled.Should().BeEquivalentTo(20, 30, 40); + three.NextCalled.Should().BeEquivalentTo(30, 40); + four.NextCalled.Should().BeEquivalentTo(40); + } + + public void Run2() + { + TestEvent one = default; + TestEvent two = default; + TestEvent three = default; + TestEvent four = default; + + one = new TestEvent(1, () => + { + if (two == null) + { + ev.Add(two = new TestEvent(2, () => + { + if (three == null) + { + ev.Add(three = new TestEvent(3, () => + { + if (four == null) + { + ev.Add(four = new TestEvent(4)); + } + })); + } + })); + } + }); + + ev.Add(one); + + ev.SetResult(10); + ev.SetResult(20); + ev.SetResult(30); + ev.SetResult(40); + + one.NextCalled.Should().BeEquivalentTo(10, 20, 30, 40); + two.NextCalled.Should().BeEquivalentTo(20, 30, 40); + three.NextCalled.Should().BeEquivalentTo(30, 40); + four.NextCalled.Should().BeEquivalentTo(40); + } + } + } + + public class TestEvent : ITriggerHandler + { + public readonly int Id; + readonly Action iteratingEvent; + + public TestEvent(int id) + { + this.Id = id; + } + + public TestEvent(int id, Action iteratingEvent) + { + this.Id = id; + this.iteratingEvent = iteratingEvent; + } + + public List NextCalled = new List(); + public List ErrorCalled = new List(); + public List CompletedCalled = new List(); + public List CancelCalled = new List(); + + public ITriggerHandler Prev { get; set; } + public ITriggerHandler Next { get; set; } + + public void OnCanceled(CancellationToken cancellationToken) + { + CancelCalled.Add(cancellationToken); + + } + + public void OnCompleted() + { + CompletedCalled.Add(new object()); + } + + public void OnError(Exception ex) + { + ErrorCalled.Add(ex); + } + + public void OnNext(int value) + { + NextCalled.Add(value); + iteratingEvent?.Invoke(); + } + + public override string ToString() + { + return Id.ToString(); + } + } + + +} diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/AsyncReactiveProperty.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/AsyncReactiveProperty.cs index 1c289fc..5a1f8b4 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/AsyncReactiveProperty.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/AsyncReactiveProperty.cs @@ -120,6 +120,9 @@ namespace Cysharp.Threading.Tasks public T Current => value; + ITriggerHandler ITriggerHandler.Prev { get; set; } + ITriggerHandler ITriggerHandler.Next { get; set; } + public UniTask MoveNextAsync() { // raise latest value on first call. @@ -300,6 +303,8 @@ namespace Cysharp.Threading.Tasks } public T Current => value; + ITriggerHandler ITriggerHandler.Prev { get; set; } + ITriggerHandler ITriggerHandler.Next { get; set; } public UniTask MoveNextAsync() { diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/Linq/Publish.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/Linq/Publish.cs index 8b6d950..d218c0f 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/Linq/Publish.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/Linq/Publish.cs @@ -115,6 +115,8 @@ namespace Cysharp.Threading.Tasks.Linq } public TSource Current { get; private set; } + ITriggerHandler ITriggerHandler.Prev { get; set; } + ITriggerHandler ITriggerHandler.Next { get; set; } public UniTask MoveNextAsync() { diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/TriggerEvent.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/TriggerEvent.cs index 426f0cc..743727d 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/TriggerEvent.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/TriggerEvent.cs @@ -1,5 +1,4 @@ -using Cysharp.Threading.Tasks.Internal; -using System; +using System; using System.Threading; namespace Cysharp.Threading.Tasks @@ -10,341 +9,283 @@ namespace Cysharp.Threading.Tasks 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 { - // optimize: many cases, handler is single. - ITriggerHandler singleHandler; + ITriggerHandler head; // head.prev is last. + ITriggerHandler iteratingHead; - ITriggerHandler[] handlers; + bool preserveRemoveSelf; + ITriggerHandler iteratingNode; - // when running(in TrySetResult), does not add immediately(trampoline). - bool isRunning; - ITriggerHandler waitHandler; - MinimumQueue> waitQueue; + + void LogError(Exception ex) + { +#if UNITY_2018_3_OR_NEWER + UnityEngine.Debug.LogException(ex); +#else + Console.WriteLine(ex); +#endif + } public void SetResult(T value) { - isRunning = true; - - if (singleHandler != null) + var h = head; + while (h != null) { + iteratingNode = h; + try { - singleHandler.OnNext(value); + h.OnNext(value); } catch (Exception ex) { -#if UNITY_2018_3_OR_NEWER - UnityEngine.Debug.LogException(ex); -#else - Console.WriteLine(ex); -#endif + LogError(ex); + Remove(h); } - } - if (handlers != null) - { - for (int i = 0; i < handlers.Length; i++) + if (preserveRemoveSelf) { - if (handlers[i] != null) - { - try - { - handlers[i].OnNext(value); - } - catch (Exception ex) - { - handlers[i] = null; -#if UNITY_2018_3_OR_NEWER - UnityEngine.Debug.LogException(ex); -#else - Console.WriteLine(ex); -#endif - } - } + preserveRemoveSelf = false; + iteratingNode = null; + var next = h.Next; + Remove(h); + h = next; } - } - - isRunning = false; - - if (waitHandler != null) - { - var h = waitHandler; - waitHandler = null; - Add(h); - } - - if (waitQueue != null) - { - while (waitQueue.Count != 0) + else { - Add(waitQueue.Dequeue()); + h = h.Next; } } + + iteratingNode = null; + if (iteratingHead != null) + { + Add(iteratingHead); + iteratingHead = null; + } } public void SetCanceled(CancellationToken cancellationToken) { - isRunning = true; - - if (singleHandler != null) + var h = head; + while (h != null) { + iteratingNode = h; try { - (singleHandler).OnCanceled(cancellationToken); + h.OnCanceled(cancellationToken); } catch (Exception ex) { -#if UNITY_2018_3_OR_NEWER - UnityEngine.Debug.LogException(ex); -#else - Console.WriteLine(ex); -#endif + LogError(ex); } + + preserveRemoveSelf = false; + iteratingNode = null; + var next = h.Next; + Remove(h); + h = next; } - if (handlers != null) + iteratingNode = null; + if (iteratingHead != null) { - for (int i = 0; i < handlers.Length; i++) - { - if (handlers[i] != null) - { - try - { - (handlers[i]).OnCanceled(cancellationToken); - } - catch (Exception ex) - { -#if UNITY_2018_3_OR_NEWER - UnityEngine.Debug.LogException(ex); -#else - Console.WriteLine(ex); -#endif - handlers[i] = null; - } - } - } - } - - isRunning = false; - - if (waitHandler != null) - { - var h = waitHandler; - waitHandler = null; - Add(h); - } - - if (waitQueue != null) - { - while (waitQueue.Count != 0) - { - Add(waitQueue.Dequeue()); - } + Add(iteratingHead); + iteratingHead = null; } } public void SetCompleted() { - isRunning = true; - - if (singleHandler != null) + var h = head; + while (h != null) { + iteratingNode = h; try { - (singleHandler).OnCompleted(); + h.OnCompleted(); } catch (Exception ex) { -#if UNITY_2018_3_OR_NEWER - UnityEngine.Debug.LogException(ex); -#else - Console.WriteLine(ex); -#endif + LogError(ex); } + + preserveRemoveSelf = false; + iteratingNode = null; + var next = h.Next; + Remove(h); + h = next; } - if (handlers != null) + iteratingNode = null; + if (iteratingHead != null) { - for (int i = 0; i < handlers.Length; i++) - { - if (handlers[i] != null) - { - try - { - (handlers[i]).OnCompleted(); - } - catch (Exception ex) - { -#if UNITY_2018_3_OR_NEWER - UnityEngine.Debug.LogException(ex); -#else - Console.WriteLine(ex); -#endif - handlers[i] = null; - } - } - } - } - - isRunning = false; - - if (waitHandler != null) - { - var h = waitHandler; - waitHandler = null; - Add(h); - } - - if (waitQueue != null) - { - while (waitQueue.Count != 0) - { - Add(waitQueue.Dequeue()); - } + Add(iteratingHead); + iteratingHead = null; } } public void SetError(Exception exception) { - isRunning = true; - - if (singleHandler != null) + var h = head; + while (h != null) { + iteratingNode = h; try { - singleHandler.OnError(exception); + h.OnError(exception); } catch (Exception ex) { -#if UNITY_2018_3_OR_NEWER - UnityEngine.Debug.LogException(ex); -#else - Console.WriteLine(ex); -#endif + LogError(ex); } + + preserveRemoveSelf = false; + iteratingNode = null; + var next = h.Next; + Remove(h); + h = next; } - if (handlers != null) + iteratingNode = null; + if (iteratingHead != null) { - for (int i = 0; i < handlers.Length; i++) - { - if (handlers[i] != null) - { - try - { - handlers[i].OnError(exception); - } - catch (Exception ex) - { - handlers[i] = null; -#if UNITY_2018_3_OR_NEWER - UnityEngine.Debug.LogException(ex); -#else - Console.WriteLine(ex); -#endif - } - } - } - } - - isRunning = false; - - if (waitHandler != null) - { - var h = waitHandler; - waitHandler = null; - Add(h); - } - - if (waitQueue != null) - { - while (waitQueue.Count != 0) - { - Add(waitQueue.Dequeue()); - } + Add(iteratingHead); + iteratingHead = null; } } public void Add(ITriggerHandler handler) { - if (isRunning) - { - if (waitHandler == null) - { - waitHandler = handler; - return; - } + if (handler == null) throw new ArgumentNullException(nameof(handler)); - if (waitQueue == null) - { - waitQueue = new MinimumQueue>(4); - } - waitQueue.Enqueue(handler); + // zero node. + if (head == null) + { + head = handler; return; } - if (singleHandler == null) + if (iteratingNode != null) { - singleHandler = handler; + 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 { - if (handlers == null) + var last = head.Prev; + if (last == null) { - handlers = new ITriggerHandler[4]; + // single node. + head.Prev = handler; + head.Next = handler; + handler.Prev = head; } - - // check empty - for (int i = 0; i < handlers.Length; i++) + else { - if (handlers[i] == null) - { - handlers[i] = handler; - return; - } - } - - // full, ensure capacity - var last = handlers.Length; - { - EnsureCapacity(ref handlers); - handlers[last] = handler; + // multi node + head.Prev = handler; + last.Next = handler; + handler.Prev = last; } } } - static void EnsureCapacity(ref ITriggerHandler[] array) - { - var newSize = array.Length * 2; - var newArray = new ITriggerHandler[newSize]; - Array.Copy(array, 0, newArray, 0, array.Length); - array = newArray; - } - public void Remove(ITriggerHandler handler) { - if (singleHandler == handler) + if (handler == null) throw new ArgumentNullException(nameof(handler)); + + if (iteratingNode != null && iteratingNode == handler) { - singleHandler = null; + // if remove self, reserve remove self after invoke completed. + preserveRemoveSelf = true; } else { - if (handlers != null) + var prev = handler.Prev; + var next = handler.Next; + + if (next != null) { - for (int i = 0; i < handlers.Length; i++) + 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) { - if (handlers[i] == handler) + prev.Next = next; + } + } + + if (head != null) + { + if (head.Prev == handler) + { + if (prev != head) { - // fill null. - handlers[i] = null; - return; + 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; } } } diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/Triggers/AsyncTriggerBase.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/Triggers/AsyncTriggerBase.cs index 6160cde..187ecd6 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/Triggers/AsyncTriggerBase.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/Triggers/AsyncTriggerBase.cs @@ -103,6 +103,8 @@ namespace Cysharp.Threading.Tasks.Triggers } public T Current { get; private set; } + ITriggerHandler ITriggerHandler.Prev { get; set; } + ITriggerHandler ITriggerHandler.Next { get; set; } public UniTask MoveNextAsync() { @@ -189,6 +191,9 @@ namespace Cysharp.Threading.Tasks.Triggers internal CancellationToken CancellationToken => cancellationToken; + ITriggerHandler ITriggerHandler.Prev { get; set; } + ITriggerHandler ITriggerHandler.Next { get; set; } + internal AsyncTriggerHandler(AsyncTriggerBase trigger, bool callOnce) { if (cancellationToken.IsCancellationRequested) diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/UniTask.Threading.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/UniTask.Threading.cs index 6f49276..65d548c 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/UniTask.Threading.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/UniTask.Threading.cs @@ -67,12 +67,12 @@ namespace Cysharp.Threading.Tasks public static ReturnToSynchronizationContext ReturnToSynchronizationContext(SynchronizationContext synchronizationContext) { - return new ReturnToSynchronizationContext(synchronizationContext); + return new ReturnToSynchronizationContext(synchronizationContext, false); } - public static ReturnToSynchronizationContext ReturnToCurrentSynchronizationContext() + public static ReturnToSynchronizationContext ReturnToCurrentSynchronizationContext(bool dontPostWhenSameContext = true) { - return new ReturnToSynchronizationContext(SynchronizationContext.Current); + return new ReturnToSynchronizationContext(SynchronizationContext.Current, dontPostWhenSameContext); } } @@ -319,15 +319,67 @@ namespace Cysharp.Threading.Tasks public struct ReturnToSynchronizationContext { readonly SynchronizationContext syncContext; + readonly bool dontPostWhenSameContext; - public ReturnToSynchronizationContext(SynchronizationContext syncContext) + public ReturnToSynchronizationContext(SynchronizationContext syncContext, bool dontPostWhenSameContext) { this.syncContext = syncContext; + this.dontPostWhenSameContext = dontPostWhenSameContext; } - public SwitchToSynchronizationContextAwaitable DisposeAsync() + public Awaiter DisposeAsync() { - return UniTask.SwitchToSynchronizationContext(syncContext); + return new Awaiter(syncContext, dontPostWhenSameContext); + } + + public struct Awaiter : ICriticalNotifyCompletion + { + static readonly SendOrPostCallback switchToCallback = Callback; + + readonly SynchronizationContext synchronizationContext; + readonly bool dontPostWhenSameContext; + + public Awaiter(SynchronizationContext synchronizationContext, bool dontPostWhenSameContext) + { + this.synchronizationContext = synchronizationContext; + this.dontPostWhenSameContext = dontPostWhenSameContext; + } + + public Awaiter GetAwaiter() => this; + + public bool IsCompleted + { + get + { + var current = SynchronizationContext.Current; + if (current == synchronizationContext) + { + return true; + } + else + { + return false; + } + } + } + + public void GetResult() { } + + public void OnCompleted(Action continuation) + { + synchronizationContext.Post(switchToCallback, continuation); + } + + public void UnsafeOnCompleted(Action continuation) + { + synchronizationContext.Post(switchToCallback, continuation); + } + + static void Callback(object state) + { + var continuation = (Action)state; + continuation(); + } } } }