From ac83b01948a433c558248950fa3506b3a381e77d Mon Sep 17 00:00:00 2001 From: Tzach Shabtay Date: Mon, 5 Mar 2018 17:20:37 -0500 Subject: [PATCH 1/6] AGSEvent refactoring- split to 2 files --- .../Engine/AGS.Engine/Misc/Events/AGSEvent.cs | 219 +---------------- .../Misc/Events/AGSEventWithArgs.cs | 224 ++++++++++++++++++ 2 files changed, 225 insertions(+), 218 deletions(-) create mode 100644 Source/Engine/AGS.Engine/Misc/Events/AGSEventWithArgs.cs diff --git a/Source/Engine/AGS.Engine/Misc/Events/AGSEvent.cs b/Source/Engine/AGS.Engine/Misc/Events/AGSEvent.cs index 5d3d02f6a..a99eafb62 100644 --- a/Source/Engine/AGS.Engine/Misc/Events/AGSEvent.cs +++ b/Source/Engine/AGS.Engine/Misc/Events/AGSEvent.cs @@ -6,222 +6,6 @@ namespace AGS.Engine { - public class AGSEvent : IEvent, IBlockingEvent - { - private readonly IConcurrentHashSet _invocationList; - private const int MAX_SUBSCRIPTIONS = 10000; - private int _count; - - public AGSEvent () - { - _invocationList = new AGSConcurrentHashSet(fireListChangedEvent: false); - } - - #region IEvent implementation - - public int SubscribersCount => _count; - - public void Subscribe (Action callback) - { - _count++; - if (_count > MAX_SUBSCRIPTIONS) - { - Debug.WriteLine("Subscribe Overflow!"); - return; - } - _invocationList.Add (new Callback (callback)); - } - - public void Subscribe(Action callback) - { - _count++; - if (_count > MAX_SUBSCRIPTIONS) - { - Debug.WriteLine("Subscribe Overflow!"); - return; - } - _invocationList.Add(new Callback(callback)); - } - - public void Unsubscribe (Action callback) - { - if (_invocationList.Remove(new Callback (callback))) - { - _count--; - } - } - - public void Unsubscribe(Action callback) - { - if (_invocationList.Remove(new Callback(callback))) - { - _count--; - } - } - - public void SubscribeToAsync (Func callback) - { - subscribeToAsync(new Callback (callback)); - } - - public void UnsubscribeToAsync (Func callback) - { - unsubscribeToAsync(new Callback (callback)); - } - - public void SubscribeToAsync(Func callback) - { - subscribeToAsync(new Callback(callback)); - } - - public void UnsubscribeToAsync(Func callback) - { - unsubscribeToAsync(new Callback(callback)); - } - - public async Task WaitUntilAsync(Predicate condition) - { - TaskCompletionSource tcs = new TaskCompletionSource (null); - var callback = new Callback(condition, tcs); - subscribeToAsync(callback); - await tcs.Task; - unsubscribeToAsync(callback); - } - - public async Task InvokeAsync (TEventArgs args) - { - try - { - foreach (var target in _invocationList) - { - if (target.BlockingEvent != null) target.BlockingEvent(args); - else if (target.EmptyBlockingEvent != null) target.EmptyBlockingEvent(); - else if (target.EmptyEvent != null) await target.EmptyEvent(); - else await target.Event (args); - } - } - catch (Exception e) - { - Debug.WriteLine("Exception when invoking an event asynchronously:"); - Debug.WriteLine (e.ToString ()); - throw; - } - } - - public void Invoke (TEventArgs args) - { - try - { - foreach (var target in _invocationList) - { - if (target.BlockingEvent != null) target.BlockingEvent(args); - else if (target.EmptyBlockingEvent != null) target.EmptyBlockingEvent(); - else if (target.EmptyEvent != null) target.EmptyEvent(); - else target.Event(args); - } - } - catch (Exception e) - { - Debug.WriteLine("Exception when invoking an event:"); - Debug.WriteLine (e.ToString ()); - throw; - } - } - - #endregion - - private void subscribeToAsync(Callback callback) - { - _count++; - if (_count > MAX_SUBSCRIPTIONS) - { - Debug.WriteLine("Subscribe Overflow!!"); - return; - } - _invocationList.Add (callback); - } - - private void unsubscribeToAsync(Callback callback) - { - if (_invocationList.Remove(callback)) - { - _count--; - } - } - - private class Callback - { - private readonly Delegate _origObject; - - public Callback(Func callback) - { - _origObject = callback; - Event = callback; - } - - public Callback(Action callback) - { - _origObject = callback; - BlockingEvent = callback; - } - - public Callback(Predicate condition, TaskCompletionSource tcs) - { - _origObject = condition; - Event = convert(condition, tcs); - } - - public Callback(Action callback) - { - _origObject = callback; - EmptyBlockingEvent = callback; - } - - public Callback(Func callback) - { - _origObject = callback; - EmptyEvent = callback; - } - - public Func Event { get; } - public Action BlockingEvent { get; } - public Func EmptyEvent { get; } - public Action EmptyBlockingEvent { get; } - - public override bool Equals(object obj) - { - Callback other = obj as Callback; - if (other == null) return false; - if (other._origObject == _origObject) return true; - if (_origObject.Target != other._origObject.Target) return false; - return getMethodName(_origObject) == getMethodName(other._origObject); - } - - public override int GetHashCode() - { - if (_origObject.Target == null) return getMethodName(_origObject).GetHashCode(); //static method subscriptions - return _origObject.Target.GetHashCode(); - } - - public override string ToString() - { - return $"[Event on {_origObject.Target.ToString()} ({getMethodName(_origObject)})]"; - } - - private string getMethodName(Delegate del) => RuntimeReflectionExtensions.GetMethodInfo(del).Name; - - private Func convert(Predicate condition, TaskCompletionSource tcs) - { - return e => - { - if (!condition(e)) return Task.CompletedTask; - tcs.TrySetResult(null); - return Task.CompletedTask; - }; - } - } - } - public class AGSEvent : IEvent, IBlockingEvent { private readonly IConcurrentHashSet _invocationList; @@ -399,5 +183,4 @@ private Func convert(Func condition, TaskCompletionSource tc } } } -} - +} \ No newline at end of file diff --git a/Source/Engine/AGS.Engine/Misc/Events/AGSEventWithArgs.cs b/Source/Engine/AGS.Engine/Misc/Events/AGSEventWithArgs.cs new file mode 100644 index 000000000..3f871fdcc --- /dev/null +++ b/Source/Engine/AGS.Engine/Misc/Events/AGSEventWithArgs.cs @@ -0,0 +1,224 @@ +using System; +using System.Diagnostics; +using System.Reflection; +using System.Threading.Tasks; +using AGS.API; + +namespace AGS.Engine +{ + public class AGSEvent : IEvent, IBlockingEvent + { + private readonly IConcurrentHashSet _invocationList; + private const int MAX_SUBSCRIPTIONS = 10000; + private int _count; + + public AGSEvent() + { + _invocationList = new AGSConcurrentHashSet(fireListChangedEvent: false); + } + + #region IEvent implementation + + public int SubscribersCount => _count; + + public void Subscribe (Action callback) + { + _count++; + if (_count > MAX_SUBSCRIPTIONS) + { + Debug.WriteLine("Subscribe Overflow!"); + return; + } + _invocationList.Add (new Callback (callback)); + } + + public void Subscribe(Action callback) + { + _count++; + if (_count > MAX_SUBSCRIPTIONS) + { + Debug.WriteLine("Subscribe Overflow!"); + return; + } + _invocationList.Add(new Callback(callback)); + } + + public void Unsubscribe (Action callback) + { + if (_invocationList.Remove(new Callback (callback))) + { + _count--; + } + } + + public void Unsubscribe(Action callback) + { + if (_invocationList.Remove(new Callback(callback))) + { + _count--; + } + } + + public void SubscribeToAsync (Func callback) + { + subscribeToAsync(new Callback (callback)); + } + + public void UnsubscribeToAsync (Func callback) + { + unsubscribeToAsync(new Callback (callback)); + } + + public void SubscribeToAsync(Func callback) + { + subscribeToAsync(new Callback(callback)); + } + + public void UnsubscribeToAsync(Func callback) + { + unsubscribeToAsync(new Callback(callback)); + } + + public async Task WaitUntilAsync(Predicate condition) + { + TaskCompletionSource tcs = new TaskCompletionSource (null); + var callback = new Callback(condition, tcs); + subscribeToAsync(callback); + await tcs.Task; + unsubscribeToAsync(callback); + } + + public async Task InvokeAsync (TEventArgs args) + { + try + { + foreach (var target in _invocationList) + { + if (target.BlockingEvent != null) target.BlockingEvent(args); + else if (target.EmptyBlockingEvent != null) target.EmptyBlockingEvent(); + else if (target.EmptyEvent != null) await target.EmptyEvent(); + else await target.Event (args); + } + } + catch (Exception e) + { + Debug.WriteLine("Exception when invoking an event asynchronously:"); + Debug.WriteLine (e.ToString ()); + throw; + } + } + + public void Invoke (TEventArgs args) + { + try + { + foreach (var target in _invocationList) + { + if (target.BlockingEvent != null) target.BlockingEvent(args); + else if (target.EmptyBlockingEvent != null) target.EmptyBlockingEvent(); + else if (target.EmptyEvent != null) target.EmptyEvent(); + else target.Event(args); + } + } + catch (Exception e) + { + Debug.WriteLine("Exception when invoking an event:"); + Debug.WriteLine (e.ToString ()); + throw; + } + } + + #endregion + + private void subscribeToAsync(Callback callback) + { + _count++; + if (_count > MAX_SUBSCRIPTIONS) + { + Debug.WriteLine("Subscribe Overflow!!"); + return; + } + _invocationList.Add (callback); + } + + private void unsubscribeToAsync(Callback callback) + { + if (_invocationList.Remove(callback)) + { + _count--; + } + } + + private class Callback + { + private readonly Delegate _origObject; + + public Callback(Func callback) + { + _origObject = callback; + Event = callback; + } + + public Callback(Action callback) + { + _origObject = callback; + BlockingEvent = callback; + } + + public Callback(Predicate condition, TaskCompletionSource tcs) + { + _origObject = condition; + Event = convert(condition, tcs); + } + + public Callback(Action callback) + { + _origObject = callback; + EmptyBlockingEvent = callback; + } + + public Callback(Func callback) + { + _origObject = callback; + EmptyEvent = callback; + } + + public Func Event { get; } + public Action BlockingEvent { get; } + public Func EmptyEvent { get; } + public Action EmptyBlockingEvent { get; } + + public override bool Equals(object obj) + { + Callback other = obj as Callback; + if (other == null) return false; + if (other._origObject == _origObject) return true; + if (_origObject.Target != other._origObject.Target) return false; + return getMethodName(_origObject) == getMethodName(other._origObject); + } + + public override int GetHashCode() + { + if (_origObject.Target == null) return getMethodName(_origObject).GetHashCode(); //static method subscriptions + return _origObject.Target.GetHashCode(); + } + + public override string ToString() + { + return $"[Event on {_origObject.Target.ToString()} ({getMethodName(_origObject)})]"; + } + + private string getMethodName(Delegate del) => RuntimeReflectionExtensions.GetMethodInfo(del).Name; + + private Func convert(Predicate condition, TaskCompletionSource tcs) + { + return e => + { + if (!condition(e)) return Task.CompletedTask; + tcs.TrySetResult(null); + return Task.CompletedTask; + }; + } + } + } +} \ No newline at end of file From f159b968002d339a35048f8064bfafe1dce518f3 Mon Sep 17 00:00:00 2001 From: Tzach Shabtay Date: Mon, 5 Mar 2018 17:25:25 -0500 Subject: [PATCH 2/6] AGSEvent refactor- extract counter to seperate class --- .../Engine/AGS.Engine/Misc/Events/AGSEvent.cs | 23 ++++--------- .../Misc/Events/AGSEventWithArgs.cs | 32 +++++-------------- .../Misc/Events/EventSubscriberCounter.cs | 24 ++++++++++++++ 3 files changed, 38 insertions(+), 41 deletions(-) create mode 100644 Source/Engine/AGS.Engine/Misc/Events/EventSubscriberCounter.cs diff --git a/Source/Engine/AGS.Engine/Misc/Events/AGSEvent.cs b/Source/Engine/AGS.Engine/Misc/Events/AGSEvent.cs index a99eafb62..079f9ace0 100644 --- a/Source/Engine/AGS.Engine/Misc/Events/AGSEvent.cs +++ b/Source/Engine/AGS.Engine/Misc/Events/AGSEvent.cs @@ -9,8 +9,7 @@ namespace AGS.Engine public class AGSEvent : IEvent, IBlockingEvent { private readonly IConcurrentHashSet _invocationList; - private const int MAX_SUBSCRIPTIONS = 10000; - private int _count; + private readonly EventSubscriberCounter _counter = new EventSubscriberCounter(); public AGSEvent() { @@ -19,16 +18,11 @@ public AGSEvent() #region IEvent implementation - public int SubscribersCount => _count; + public int SubscribersCount => _counter.Count; public void Subscribe(Action callback) { - _count++; - if (_count > MAX_SUBSCRIPTIONS) - { - Debug.WriteLine("Subscribe Overflow!"); - return; - } + _counter.Add(); _invocationList.Add(new Callback(callback)); } @@ -36,7 +30,7 @@ public void Unsubscribe(Action callback) { if (_invocationList.Remove(new Callback(callback))) { - _count--; + _counter.Remove(); } } @@ -98,12 +92,7 @@ public void Invoke() private void subscribeToAsync(Callback callback) { - _count++; - if (_count > MAX_SUBSCRIPTIONS) - { - Debug.WriteLine("Subscribe Overflow!!"); - return; - } + _counter.Add(); _invocationList.Add(callback); } @@ -111,7 +100,7 @@ private void unsubscribeToAsync(Callback callback) { if (_invocationList.Remove(callback)) { - _count--; + _counter.Remove(); } } diff --git a/Source/Engine/AGS.Engine/Misc/Events/AGSEventWithArgs.cs b/Source/Engine/AGS.Engine/Misc/Events/AGSEventWithArgs.cs index 3f871fdcc..19967b0a0 100644 --- a/Source/Engine/AGS.Engine/Misc/Events/AGSEventWithArgs.cs +++ b/Source/Engine/AGS.Engine/Misc/Events/AGSEventWithArgs.cs @@ -9,8 +9,7 @@ namespace AGS.Engine public class AGSEvent : IEvent, IBlockingEvent { private readonly IConcurrentHashSet _invocationList; - private const int MAX_SUBSCRIPTIONS = 10000; - private int _count; + private readonly EventSubscriberCounter _counter = new EventSubscriberCounter(); public AGSEvent() { @@ -19,27 +18,17 @@ public AGSEvent() #region IEvent implementation - public int SubscribersCount => _count; + public int SubscribersCount => _counter.Count; public void Subscribe (Action callback) { - _count++; - if (_count > MAX_SUBSCRIPTIONS) - { - Debug.WriteLine("Subscribe Overflow!"); - return; - } + _counter.Add(); _invocationList.Add (new Callback (callback)); } public void Subscribe(Action callback) { - _count++; - if (_count > MAX_SUBSCRIPTIONS) - { - Debug.WriteLine("Subscribe Overflow!"); - return; - } + _counter.Add(); _invocationList.Add(new Callback(callback)); } @@ -47,7 +36,7 @@ public void Unsubscribe (Action callback) { if (_invocationList.Remove(new Callback (callback))) { - _count--; + _counter.Remove(); } } @@ -55,7 +44,7 @@ public void Unsubscribe(Action callback) { if (_invocationList.Remove(new Callback(callback))) { - _count--; + _counter.Remove(); } } @@ -132,12 +121,7 @@ public void Invoke (TEventArgs args) private void subscribeToAsync(Callback callback) { - _count++; - if (_count > MAX_SUBSCRIPTIONS) - { - Debug.WriteLine("Subscribe Overflow!!"); - return; - } + _counter.Add(); _invocationList.Add (callback); } @@ -145,7 +129,7 @@ private void unsubscribeToAsync(Callback callback) { if (_invocationList.Remove(callback)) { - _count--; + _counter.Remove(); } } diff --git a/Source/Engine/AGS.Engine/Misc/Events/EventSubscriberCounter.cs b/Source/Engine/AGS.Engine/Misc/Events/EventSubscriberCounter.cs new file mode 100644 index 000000000..0c0392ee2 --- /dev/null +++ b/Source/Engine/AGS.Engine/Misc/Events/EventSubscriberCounter.cs @@ -0,0 +1,24 @@ +using System; +namespace AGS.Engine +{ + public class EventSubscriberCounter + { + private const int MAX_SUBSCRIPTIONS = 10000; + + public int Count { get; private set; } + + public void Add() + { + Count++; + if (Count > MAX_SUBSCRIPTIONS) + { + throw new OverflowException("Event Subscription Overflow"); + } + } + + public void Remove() + { + Count--; + } + } +} From ce463575e93aca4b05c00231ea688f45ac26a301 Mon Sep 17 00:00:00 2001 From: Tzach Shabtay Date: Mon, 5 Mar 2018 17:31:34 -0500 Subject: [PATCH 3/6] AGSEvent refactor- share subscription code --- .../Engine/AGS.Engine/Misc/Events/AGSEvent.cs | 32 +++-------- .../Misc/Events/AGSEventWithArgs.cs | 56 ++++--------------- 2 files changed, 20 insertions(+), 68 deletions(-) diff --git a/Source/Engine/AGS.Engine/Misc/Events/AGSEvent.cs b/Source/Engine/AGS.Engine/Misc/Events/AGSEvent.cs index 079f9ace0..4ed153726 100644 --- a/Source/Engine/AGS.Engine/Misc/Events/AGSEvent.cs +++ b/Source/Engine/AGS.Engine/Misc/Events/AGSEvent.cs @@ -20,37 +20,21 @@ public AGSEvent() public int SubscribersCount => _counter.Count; - public void Subscribe(Action callback) - { - _counter.Add(); - _invocationList.Add(new Callback(callback)); - } + public void Subscribe(Action callback) => subscribe(new Callback(callback)); - public void Unsubscribe(Action callback) - { - if (_invocationList.Remove(new Callback(callback))) - { - _counter.Remove(); - } - } + public void Unsubscribe(Action callback) => unsubscribe(new Callback(callback)); - public void SubscribeToAsync(Func callback) - { - subscribeToAsync(new Callback(callback)); - } + public void SubscribeToAsync(Func callback) => subscribe(new Callback(callback)); - public void UnsubscribeToAsync(Func callback) - { - unsubscribeToAsync(new Callback(callback)); - } + public void UnsubscribeToAsync(Func callback) => unsubscribe(new Callback(callback)); public async Task WaitUntilAsync(Func condition) { TaskCompletionSource tcs = new TaskCompletionSource(null); var callback = new Callback(condition, tcs); - subscribeToAsync(callback); + subscribe(callback); await tcs.Task; - unsubscribeToAsync(callback); + unsubscribe(callback); } public async Task InvokeAsync() @@ -90,13 +74,13 @@ public void Invoke() #endregion - private void subscribeToAsync(Callback callback) + private void subscribe(Callback callback) { _counter.Add(); _invocationList.Add(callback); } - private void unsubscribeToAsync(Callback callback) + private void unsubscribe(Callback callback) { if (_invocationList.Remove(callback)) { diff --git a/Source/Engine/AGS.Engine/Misc/Events/AGSEventWithArgs.cs b/Source/Engine/AGS.Engine/Misc/Events/AGSEventWithArgs.cs index 19967b0a0..7975502a2 100644 --- a/Source/Engine/AGS.Engine/Misc/Events/AGSEventWithArgs.cs +++ b/Source/Engine/AGS.Engine/Misc/Events/AGSEventWithArgs.cs @@ -20,61 +20,29 @@ public AGSEvent() public int SubscribersCount => _counter.Count; - public void Subscribe (Action callback) - { - _counter.Add(); - _invocationList.Add (new Callback (callback)); - } + public void Subscribe(Action callback) => subscribe(new Callback(callback)); - public void Subscribe(Action callback) - { - _counter.Add(); - _invocationList.Add(new Callback(callback)); - } + public void Subscribe(Action callback) => subscribe(new Callback(callback)); - public void Unsubscribe (Action callback) - { - if (_invocationList.Remove(new Callback (callback))) - { - _counter.Remove(); - } - } + public void Unsubscribe(Action callback) => unsubscribe(new Callback(callback)); - public void Unsubscribe(Action callback) - { - if (_invocationList.Remove(new Callback(callback))) - { - _counter.Remove(); - } - } + public void Unsubscribe(Action callback) => unsubscribe(new Callback(callback)); - public void SubscribeToAsync (Func callback) - { - subscribeToAsync(new Callback (callback)); - } + public void SubscribeToAsync (Func callback) => subscribe(new Callback (callback)); - public void UnsubscribeToAsync (Func callback) - { - unsubscribeToAsync(new Callback (callback)); - } + public void UnsubscribeToAsync (Func callback) => unsubscribe(new Callback (callback)); - public void SubscribeToAsync(Func callback) - { - subscribeToAsync(new Callback(callback)); - } + public void SubscribeToAsync(Func callback) => subscribe(new Callback(callback)); - public void UnsubscribeToAsync(Func callback) - { - unsubscribeToAsync(new Callback(callback)); - } + public void UnsubscribeToAsync(Func callback) => unsubscribe(new Callback(callback)); public async Task WaitUntilAsync(Predicate condition) { TaskCompletionSource tcs = new TaskCompletionSource (null); var callback = new Callback(condition, tcs); - subscribeToAsync(callback); + subscribe(callback); await tcs.Task; - unsubscribeToAsync(callback); + unsubscribe(callback); } public async Task InvokeAsync (TEventArgs args) @@ -119,13 +87,13 @@ public void Invoke (TEventArgs args) #endregion - private void subscribeToAsync(Callback callback) + private void subscribe(Callback callback) { _counter.Add(); _invocationList.Add (callback); } - private void unsubscribeToAsync(Callback callback) + private void unsubscribe(Callback callback) { if (_invocationList.Remove(callback)) { From 993f4c19be666b156ac639d5c6a823c257d54a56 Mon Sep 17 00:00:00 2001 From: Tzach Shabtay Date: Tue, 6 Mar 2018 13:25:27 -0500 Subject: [PATCH 4/6] Add the ability to claim events --- Source/AGS.API/AGS.API.csproj | 1 + Source/AGS.API/Misc/Events/ClaimEventToken.cs | 16 +++++ Source/AGS.API/Misc/Events/IBlockingEvent.cs | 58 +++++++++++++++++++ .../Engine/AGS.Engine/Misc/Events/AGSEvent.cs | 23 ++++++++ .../Misc/Events/AGSEventWithArgs.cs | 23 ++++++++ Source/Tests/Engine/Misc/Events/EventTests.cs | 40 +++++++++++++ 6 files changed, 161 insertions(+) create mode 100644 Source/AGS.API/Misc/Events/ClaimEventToken.cs diff --git a/Source/AGS.API/AGS.API.csproj b/Source/AGS.API/AGS.API.csproj index 8ec659603..9d45426d0 100644 --- a/Source/AGS.API/AGS.API.csproj +++ b/Source/AGS.API/AGS.API.csproj @@ -276,6 +276,7 @@ + diff --git a/Source/AGS.API/Misc/Events/ClaimEventToken.cs b/Source/AGS.API/Misc/Events/ClaimEventToken.cs new file mode 100644 index 000000000..3cf7f40c7 --- /dev/null +++ b/Source/AGS.API/Misc/Events/ClaimEventToken.cs @@ -0,0 +1,16 @@ +namespace AGS.API +{ + /// + /// This is a token that can be used when you want to claim an event, so that other subscribers which follow you on the subscriber list will not get that event. + /// + /// + public struct ClaimEventToken + { + /// + /// Gets or sets a value indicating whether this is claimed. + /// If you claim the event (by setting Claimed = true), this event will not be propogated to the rest of the subscribers. + /// + /// true if claimed; otherwise, false. + public bool Claimed { get; set; } + } +} diff --git a/Source/AGS.API/Misc/Events/IBlockingEvent.cs b/Source/AGS.API/Misc/Events/IBlockingEvent.cs index 0504375fb..67b22c6fe 100644 --- a/Source/AGS.API/Misc/Events/IBlockingEvent.cs +++ b/Source/AGS.API/Misc/Events/IBlockingEvent.cs @@ -3,6 +3,30 @@ namespace AGS.API { + /// + /// This allows you to subscribe to an event with the option of claiming the event, so that other subscribers in the list will not receive the event. + /// + /// + /// + /// myEvent.Subscribe(onEvent1); + /// myEvent.Subscribe(onEvent2); + /// + /// void onEvent1(ref ClaimEventToken token) + /// { + /// token.Claimed = true; //We claimed the event, "onEvent2" will not get called. + /// } + /// + /// + /// + public delegate void ClaimableCallback(ref ClaimEventToken token); + + /// + /// This allows you to subscribe to an event with the option of claiming the event, so that other subscribers in the list will not receive the event. + /// + /// For an example- . + /// + public delegate void ClaimableCallbackWithArgs(TEventArgs args, ref ClaimEventToken token); + /// /// Represents an event which can be subscribed and invoked synchronously. /// An event is a notification for something that has happened. @@ -30,6 +54,23 @@ public interface IBlockingEvent /// Callback. void Unsubscribe(Action callback); + /// + /// Subscribe the specified callback to the event. + /// Once subscribed, whenever the event happens this callback will be called. + /// + /// In addition, this specific overload allows you to claim the event so that subscribers which follow you on the list will not receive the event. + /// For an example- . + /// + /// Callback. + void Subscribe(ClaimableCallbackWithArgs callback); + + /// + /// Unsubscribe the specified callback from the event. + /// This will stops notifications to call this callback. + /// + /// Callback. + void Unsubscribe(ClaimableCallbackWithArgs callback); + /// /// Asynchronously wait until the event fires and the specific condition applies. /// @@ -71,6 +112,23 @@ public interface IBlockingEvent /// Callback. void Unsubscribe(Action callback); + /// + /// Subscribe the specified callback to the event. + /// Once subscribed, whenever the event happens this callback will be called. + /// + /// In addition, this specific overload allows you to claim the event so that subscribers which follow you on the list will not receive the event. + /// For an example- . + /// + /// Callback. + void Subscribe(ClaimableCallback callback); + + /// + /// Unsubscribe the specified callback from the event. + /// This will stops notifications to call this callback. + /// + /// Callback. + void Unsubscribe(ClaimableCallback callback); + /// /// Asynchronously wait until the event fires and the specific condition applies. /// diff --git a/Source/Engine/AGS.Engine/Misc/Events/AGSEvent.cs b/Source/Engine/AGS.Engine/Misc/Events/AGSEvent.cs index 4ed153726..009fbf76e 100644 --- a/Source/Engine/AGS.Engine/Misc/Events/AGSEvent.cs +++ b/Source/Engine/AGS.Engine/Misc/Events/AGSEvent.cs @@ -24,6 +24,10 @@ public AGSEvent() public void Unsubscribe(Action callback) => unsubscribe(new Callback(callback)); + public void Subscribe(ClaimableCallback callback) => subscribe(new Callback(callback)); + + public void Unsubscribe(ClaimableCallback callback) => unsubscribe(new Callback(callback)); + public void SubscribeToAsync(Func callback) => subscribe(new Callback(callback)); public void UnsubscribeToAsync(Func callback) => unsubscribe(new Callback(callback)); @@ -41,8 +45,14 @@ public async Task InvokeAsync() { try { + ClaimEventToken token = new ClaimEventToken(); foreach (var target in _invocationList) { + if (target.BlockingEventWithClaimToken != null) + { + target.BlockingEventWithClaimToken(ref token); + if (token.Claimed) return; + } await target.Event(); } } @@ -58,8 +68,14 @@ public void Invoke() { try { + ClaimEventToken token = new ClaimEventToken(); foreach (var target in _invocationList) { + if (target.BlockingEventWithClaimToken != null) + { + target.BlockingEventWithClaimToken(ref token); + if (token.Claimed) return; + } if (target.BlockingEvent != null) target.BlockingEvent(); else target.Event(); } @@ -111,8 +127,15 @@ public Callback(Func condition, TaskCompletionSource tcs) Event = convert(condition, tcs); } + public Callback(ClaimableCallback callback) + { + _origObject = callback; + BlockingEventWithClaimToken = callback; + } + public Func Event { get; } public Action BlockingEvent { get; } + public ClaimableCallback BlockingEventWithClaimToken { get; } public override bool Equals(object obj) { diff --git a/Source/Engine/AGS.Engine/Misc/Events/AGSEventWithArgs.cs b/Source/Engine/AGS.Engine/Misc/Events/AGSEventWithArgs.cs index 7975502a2..a24ef5d0c 100644 --- a/Source/Engine/AGS.Engine/Misc/Events/AGSEventWithArgs.cs +++ b/Source/Engine/AGS.Engine/Misc/Events/AGSEventWithArgs.cs @@ -24,10 +24,14 @@ public AGSEvent() public void Subscribe(Action callback) => subscribe(new Callback(callback)); + public void Subscribe(ClaimableCallbackWithArgs callback) => subscribe(new Callback(callback)); + public void Unsubscribe(Action callback) => unsubscribe(new Callback(callback)); public void Unsubscribe(Action callback) => unsubscribe(new Callback(callback)); + public void Unsubscribe(ClaimableCallbackWithArgs callback) => unsubscribe(new Callback(callback)); + public void SubscribeToAsync (Func callback) => subscribe(new Callback (callback)); public void UnsubscribeToAsync (Func callback) => unsubscribe(new Callback (callback)); @@ -49,9 +53,15 @@ public async Task InvokeAsync (TEventArgs args) { try { + ClaimEventToken token = new ClaimEventToken(); foreach (var target in _invocationList) { if (target.BlockingEvent != null) target.BlockingEvent(args); + else if (target.BlockingEventWithToken != null) + { + target.BlockingEventWithToken(args, ref token); + if (token.Claimed) return; + } else if (target.EmptyBlockingEvent != null) target.EmptyBlockingEvent(); else if (target.EmptyEvent != null) await target.EmptyEvent(); else await target.Event (args); @@ -69,9 +79,15 @@ public void Invoke (TEventArgs args) { try { + ClaimEventToken token = new ClaimEventToken(); foreach (var target in _invocationList) { if (target.BlockingEvent != null) target.BlockingEvent(args); + else if (target.BlockingEventWithToken != null) + { + target.BlockingEventWithToken(args, ref token); + if (token.Claimed) return; + } else if (target.EmptyBlockingEvent != null) target.EmptyBlockingEvent(); else if (target.EmptyEvent != null) target.EmptyEvent(); else target.Event(args); @@ -135,8 +151,15 @@ public Callback(Func callback) EmptyEvent = callback; } + public Callback(ClaimableCallbackWithArgs callback) + { + _origObject = callback; + BlockingEventWithToken = callback; + } + public Func Event { get; } public Action BlockingEvent { get; } + public ClaimableCallbackWithArgs BlockingEventWithToken { get; } public Func EmptyEvent { get; } public Action EmptyBlockingEvent { get; } diff --git a/Source/Tests/Engine/Misc/Events/EventTests.cs b/Source/Tests/Engine/Misc/Events/EventTests.cs index 65b570db4..63bcf33fc 100644 --- a/Source/Tests/Engine/Misc/Events/EventTests.cs +++ b/Source/Tests/Engine/Misc/Events/EventTests.cs @@ -1,6 +1,7 @@ using NUnit.Framework; using AGS.Engine; using System.Threading.Tasks; +using AGS.API; namespace Tests { @@ -139,12 +140,51 @@ public void SubscribeUnsubscribeOnDifferentTargetAsyncTest() Assert.AreEqual(1, ev.SubscribersCount); } + [Test] + public void ClaimEventTest() + { + AGSEvent ev = new AGSEvent(); + ev.Subscribe(onEventClaim); + EventTests target2 = new EventTests(); + ev.Subscribe(target2.onEventClaim); + ev.Invoke(new MockEventArgs(x)); + Assert.AreEqual(2, ev.SubscribersCount); + Assert.AreEqual(1, syncEvents); + Assert.AreEqual(0, target2.syncEvents); + } + + [Test] + public void DontClaimEventTest() + { + AGSEvent ev = new AGSEvent(); + ev.Subscribe(onEventDontClaim); + EventTests target2 = new EventTests(); + ev.Subscribe(target2.onEventDontClaim); + ev.Invoke(new MockEventArgs(x)); + Assert.AreEqual(2, ev.SubscribersCount); + Assert.AreEqual(1, syncEvents); + Assert.AreEqual(1, target2.syncEvents); + } + private void onEvent(MockEventArgs e) { Assert.AreEqual (x, e.X); syncEvents++; } + private void onEventClaim(MockEventArgs e, ref ClaimEventToken token) + { + Assert.AreEqual(x, e.X); + syncEvents++; + token.Claimed = true; + } + + private void onEventDontClaim(MockEventArgs e, ref ClaimEventToken token) + { + Assert.AreEqual(x, e.X); + syncEvents++; + } + private async Task onEventAsync(MockEventArgs e) { await Task.Delay (1); From 8830fac85aa73147b6948ad2173189d105bc9565 Mon Sep 17 00:00:00 2001 From: Tzach Shabtay Date: Tue, 6 Mar 2018 13:33:08 -0500 Subject: [PATCH 5/6] Fix subscriber count is wrong for duplicate subscriptions --- Source/Engine/AGS.Engine/Misc/Events/AGSEvent.cs | 6 ++++-- Source/Engine/AGS.Engine/Misc/Events/AGSEventWithArgs.cs | 6 ++++-- Source/Tests/Engine/Misc/Events/EventTests.cs | 9 +++++++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/Source/Engine/AGS.Engine/Misc/Events/AGSEvent.cs b/Source/Engine/AGS.Engine/Misc/Events/AGSEvent.cs index 009fbf76e..1d36fdfb1 100644 --- a/Source/Engine/AGS.Engine/Misc/Events/AGSEvent.cs +++ b/Source/Engine/AGS.Engine/Misc/Events/AGSEvent.cs @@ -92,8 +92,10 @@ public void Invoke() private void subscribe(Callback callback) { - _counter.Add(); - _invocationList.Add(callback); + if (_invocationList.Add(callback)) + { + _counter.Add(); + } } private void unsubscribe(Callback callback) diff --git a/Source/Engine/AGS.Engine/Misc/Events/AGSEventWithArgs.cs b/Source/Engine/AGS.Engine/Misc/Events/AGSEventWithArgs.cs index a24ef5d0c..13514ad36 100644 --- a/Source/Engine/AGS.Engine/Misc/Events/AGSEventWithArgs.cs +++ b/Source/Engine/AGS.Engine/Misc/Events/AGSEventWithArgs.cs @@ -105,8 +105,10 @@ public void Invoke (TEventArgs args) private void subscribe(Callback callback) { - _counter.Add(); - _invocationList.Add (callback); + if (_invocationList.Add (callback)) + { + _counter.Add(); + } } private void unsubscribe(Callback callback) diff --git a/Source/Tests/Engine/Misc/Events/EventTests.cs b/Source/Tests/Engine/Misc/Events/EventTests.cs index 63bcf33fc..29ee883bd 100644 --- a/Source/Tests/Engine/Misc/Events/EventTests.cs +++ b/Source/Tests/Engine/Misc/Events/EventTests.cs @@ -166,6 +166,15 @@ public void DontClaimEventTest() Assert.AreEqual(1, target2.syncEvents); } + [Test] + public void DuplicateSubscribersTest() + { + AGSEvent ev = new AGSEvent(); + ev.Subscribe(onEvent); + ev.Subscribe(onEvent); + Assert.AreEqual(1, ev.SubscribersCount); + } + private void onEvent(MockEventArgs e) { Assert.AreEqual (x, e.X); From 017c01297d974abbcab30f9346db30bc4b74bddc Mon Sep 17 00:00:00 2001 From: Tzach Shabtay Date: Tue, 6 Mar 2018 15:49:58 -0500 Subject: [PATCH 6/6] Add callback priority for event subscriptions This will allow some control over which subscriber will get the event first. --- Source/AGS.API/Misc/Events/IBlockingEvent.cs | 52 +++++++++++--- Source/AGS.API/Misc/Events/IEvent.cs | 42 +++++++---- .../Engine/AGS.Engine/Misc/Events/AGSEvent.cs | 39 +++++------ .../Misc/Events/AGSEventWithArgs.cs | 47 ++++++------- .../Misc/Events/EventCallbacksCollection.cs | 69 +++++++++++++++++++ .../Misc/Events/EventCallbacksSingleList.cs | 47 +++++++++++++ .../Misc/Events/EventSubscriberCounter.cs | 24 ------- .../Interactions/AGSInteractionEvent.cs | 40 +++++------ Source/Tests/Engine/Misc/Events/EventTests.cs | 2 +- 9 files changed, 243 insertions(+), 119 deletions(-) create mode 100644 Source/Engine/AGS.Engine/Misc/Events/EventCallbacksCollection.cs create mode 100644 Source/Engine/AGS.Engine/Misc/Events/EventCallbacksSingleList.cs delete mode 100644 Source/Engine/AGS.Engine/Misc/Events/EventSubscriberCounter.cs diff --git a/Source/AGS.API/Misc/Events/IBlockingEvent.cs b/Source/AGS.API/Misc/Events/IBlockingEvent.cs index 67b22c6fe..e130cd349 100644 --- a/Source/AGS.API/Misc/Events/IBlockingEvent.cs +++ b/Source/AGS.API/Misc/Events/IBlockingEvent.cs @@ -27,6 +27,28 @@ namespace AGS.API /// public delegate void ClaimableCallbackWithArgs(TEventArgs args, ref ClaimEventToken token); + /// + /// In a scenario where an event has multiple subscribers, the callback priority affects which subscriber gets the callback first. + /// High priority subscribers will receive the event before normal priority (the default) subscribers, and low priority will receive the event last. + /// + /// Note: For 2 subscribers with the same callback priority, there's no guarantee regarding who gets the event first. + /// + public enum CallbackPriority : byte + { + /// + /// Low priority- will receive the event last. + /// + Low, + /// + /// Normal priority. + /// + Normal, + /// + /// High priority- will receive the event first. + /// + High, + } + /// /// Represents an event which can be subscribed and invoked synchronously. /// An event is a notification for something that has happened. @@ -45,14 +67,16 @@ public interface IBlockingEvent /// Once subscribed, whenever the event happens this callback will be called. /// /// Callback. - void Subscribe(Action callback); + /// The callback priority (determines the order in which the subscribers get the events). + void Subscribe(Action callback, CallbackPriority priority = CallbackPriority.Normal); /// /// Unsubscribe the specified callback from the event. /// This will stops notifications to call this callback. /// /// Callback. - void Unsubscribe(Action callback); + /// The callback priority (determines the order in which the subscribers get the events). + void Unsubscribe(Action callback, CallbackPriority priority = CallbackPriority.Normal); /// /// Subscribe the specified callback to the event. @@ -62,21 +86,24 @@ public interface IBlockingEvent /// For an example- . /// /// Callback. - void Subscribe(ClaimableCallbackWithArgs callback); + /// The callback priority (determines the order in which the subscribers get the events). + void Subscribe(ClaimableCallbackWithArgs callback, CallbackPriority priority = CallbackPriority.Normal); /// /// Unsubscribe the specified callback from the event. /// This will stops notifications to call this callback. /// /// Callback. - void Unsubscribe(ClaimableCallbackWithArgs callback); + /// The callback priority (determines the order in which the subscribers get the events). + void Unsubscribe(ClaimableCallbackWithArgs callback, CallbackPriority priority = CallbackPriority.Normal); /// /// Asynchronously wait until the event fires and the specific condition applies. /// /// The task to be awaited. /// The condition we are waiting to apply before moving on. - Task WaitUntilAsync(Predicate condition); + /// The callback priority (determines the order in which the subscribers get the events). + Task WaitUntilAsync(Predicate condition, CallbackPriority priority = CallbackPriority.Normal); /// /// Invoke the event synchronously (i.e will wait for all subscribers to process the event before moving on). @@ -103,14 +130,16 @@ public interface IBlockingEvent /// Once subscribed, whenever the event happens this callback will be called. /// /// Callback. - void Subscribe(Action callback); + /// The callback priority (determines the order in which the subscribers get the events). + void Subscribe(Action callback, CallbackPriority priority = CallbackPriority.Normal); /// /// Unsubscribe the specified callback from the event. /// This will stops notifications to call this callback. /// /// Callback. - void Unsubscribe(Action callback); + /// The callback priority (determines the order in which the subscribers get the events). + void Unsubscribe(Action callback, CallbackPriority priority = CallbackPriority.Normal); /// /// Subscribe the specified callback to the event. @@ -120,21 +149,24 @@ public interface IBlockingEvent /// For an example- . /// /// Callback. - void Subscribe(ClaimableCallback callback); + /// The callback priority (determines the order in which the subscribers get the events). + void Subscribe(ClaimableCallback callback, CallbackPriority priority = CallbackPriority.Normal); /// /// Unsubscribe the specified callback from the event. /// This will stops notifications to call this callback. /// /// Callback. - void Unsubscribe(ClaimableCallback callback); + /// The callback priority (determines the order in which the subscribers get the events). + void Unsubscribe(ClaimableCallback callback, CallbackPriority priority = CallbackPriority.Normal); /// /// Asynchronously wait until the event fires and the specific condition applies. /// /// The task to be awaited. /// The condition we are waiting to apply before moving on. - Task WaitUntilAsync(Func condition); + /// The callback priority (determines the order in which the subscribers get the events). + Task WaitUntilAsync(Func condition, CallbackPriority priority = CallbackPriority.Normal); /// /// Invoke the event synchronously (i.e will wait for all subscribers to process the event before moving on). diff --git a/Source/AGS.API/Misc/Events/IEvent.cs b/Source/AGS.API/Misc/Events/IEvent.cs index 0d4e0a10b..0855eb6e4 100644 --- a/Source/AGS.API/Misc/Events/IEvent.cs +++ b/Source/AGS.API/Misc/Events/IEvent.cs @@ -21,28 +21,32 @@ public interface IEvent /// Once subscribed, whenever the event happens this callback will be called. /// /// Callback. - void Subscribe(Action callback); + /// The callback priority (determines the order in which the subscribers get the events). + void Subscribe(Action callback, CallbackPriority priority = CallbackPriority.Normal); /// /// Unsubscribe the specified callback from the event. /// This will stops notifications to call this callback. /// /// Callback. - void Unsubscribe(Action callback); + /// The callback priority (determines the order in which the subscribers get the events). + void Unsubscribe(Action callback, CallbackPriority priority = CallbackPriority.Normal); /// /// Subscribe the specified asynchronous callback to the event. /// Once subscribed, whenever the event happens this callback will be called. /// /// Callback. - void SubscribeToAsync(Func callback); + /// The callback priority (determines the order in which the subscribers get the events). + void SubscribeToAsync(Func callback, CallbackPriority priority = CallbackPriority.Normal); /// /// Unsubscribe the specified asynchronous callback from the event. /// This will stops notifications to call this callback. /// /// Callback. - void UnsubscribeToAsync(Func callback); + /// The callback priority (determines the order in which the subscribers get the events). + void UnsubscribeToAsync(Func callback, CallbackPriority priority = CallbackPriority.Normal); /// /// Subscribe the specified callback to the event. @@ -50,14 +54,16 @@ public interface IEvent /// This version of Subscribe ignores incoming arguments from the event (if you need the arguments, use the other overload which gets an action of TEventArgs). /// /// Callback. - void Subscribe(Action callback); + /// The callback priority (determines the order in which the subscribers get the events). + void Subscribe(Action callback, CallbackPriority priority = CallbackPriority.Normal); /// /// Unsubscribe the specified callback from the event. /// This will stops notifications to call this callback. /// /// Callback. - void Unsubscribe(Action callback); + /// The callback priority (determines the order in which the subscribers get the events). + void Unsubscribe(Action callback, CallbackPriority priority = CallbackPriority.Normal); /// /// Subscribe the specified asynchronous callback to the event. @@ -65,21 +71,24 @@ public interface IEvent /// This version of Subscribe ignores incoming arguments from the event (if you need the arguments, use the other overload which gets a func of TEventArgs -> Task). /// /// Callback. - void SubscribeToAsync(Func callback); + /// The callback priority (determines the order in which the subscribers get the events). + void SubscribeToAsync(Func callback, CallbackPriority priority = CallbackPriority.Normal); /// /// Unsubscribe the specified asynchronous callback from the event. /// This will stops notifications to call this callback. /// /// Callback. - void UnsubscribeToAsync(Func callback); + /// The callback priority (determines the order in which the subscribers get the events). + void UnsubscribeToAsync(Func callback, CallbackPriority priority = CallbackPriority.Normal); /// /// Asynchronously wait until the event fires and the specific condition applies. /// /// The task to be awaited. /// The condition we are waiting to apply before moving on. - Task WaitUntilAsync(Predicate condition); + /// The callback priority (determines the order in which the subscribers get the events). + Task WaitUntilAsync(Predicate condition, CallbackPriority priority = CallbackPriority.Normal); /// /// Invoke the event asynchronously. @@ -106,35 +115,40 @@ public interface IEvent /// Once subscribed, whenever the event happens this callback will be called. /// /// Callback. - void Subscribe(Action callback); + /// The callback priority (determines the order in which the subscribers get the events). + void Subscribe(Action callback, CallbackPriority priority = CallbackPriority.Normal); /// /// Unsubscribe the specified callback from the event. /// This will stops notifications to call this callback. /// /// Callback. - void Unsubscribe(Action callback); + /// The callback priority (determines the order in which the subscribers get the events). + void Unsubscribe(Action callback, CallbackPriority priority = CallbackPriority.Normal); /// /// Subscribe the specified asynchronous callback to the event. /// Once subscribed, whenever the event happens this callback will be called. /// /// Callback. - void SubscribeToAsync(Func callback); + /// The callback priority (determines the order in which the subscribers get the events). + void SubscribeToAsync(Func callback, CallbackPriority priority = CallbackPriority.Normal); /// /// Unsubscribe the specified asynchronous callback from the event. /// This will stops notifications to call this callback. /// /// Callback. - void UnsubscribeToAsync(Func callback); + /// The callback priority (determines the order in which the subscribers get the events). + void UnsubscribeToAsync(Func callback, CallbackPriority priority = CallbackPriority.Normal); /// /// Asynchronously wait until the event fires and the specific condition applies. /// /// The task to be awaited. /// The condition we are waiting to apply before moving on. - Task WaitUntilAsync(Func condition); + /// The callback priority (determines the order in which the subscribers get the events). + Task WaitUntilAsync(Func condition, CallbackPriority priority = CallbackPriority.Normal); /// /// Invoke the event asynchronously. diff --git a/Source/Engine/AGS.Engine/Misc/Events/AGSEvent.cs b/Source/Engine/AGS.Engine/Misc/Events/AGSEvent.cs index 1d36fdfb1..afe7cf616 100644 --- a/Source/Engine/AGS.Engine/Misc/Events/AGSEvent.cs +++ b/Source/Engine/AGS.Engine/Misc/Events/AGSEvent.cs @@ -8,37 +8,36 @@ namespace AGS.Engine { public class AGSEvent : IEvent, IBlockingEvent { - private readonly IConcurrentHashSet _invocationList; - private readonly EventSubscriberCounter _counter = new EventSubscriberCounter(); + private readonly EventCallbacksCollection _invocationList; public AGSEvent() { - _invocationList = new AGSConcurrentHashSet(fireListChangedEvent: false); + _invocationList = new EventCallbacksCollection(); } #region IEvent implementation - public int SubscribersCount => _counter.Count; + public int SubscribersCount => _invocationList.Count; - public void Subscribe(Action callback) => subscribe(new Callback(callback)); + public void Subscribe(Action callback, CallbackPriority priority = CallbackPriority.Normal) => subscribe(new Callback(callback), priority); - public void Unsubscribe(Action callback) => unsubscribe(new Callback(callback)); + public void Unsubscribe(Action callback, CallbackPriority priority = CallbackPriority.Normal) => unsubscribe(new Callback(callback), priority); - public void Subscribe(ClaimableCallback callback) => subscribe(new Callback(callback)); + public void Subscribe(ClaimableCallback callback, CallbackPriority priority = CallbackPriority.Normal) => subscribe(new Callback(callback), priority); - public void Unsubscribe(ClaimableCallback callback) => unsubscribe(new Callback(callback)); + public void Unsubscribe(ClaimableCallback callback, CallbackPriority priority = CallbackPriority.Normal) => unsubscribe(new Callback(callback), priority); - public void SubscribeToAsync(Func callback) => subscribe(new Callback(callback)); + public void SubscribeToAsync(Func callback, CallbackPriority priority = CallbackPriority.Normal) => subscribe(new Callback(callback), priority); - public void UnsubscribeToAsync(Func callback) => unsubscribe(new Callback(callback)); + public void UnsubscribeToAsync(Func callback, CallbackPriority priority = CallbackPriority.Normal) => unsubscribe(new Callback(callback), priority); - public async Task WaitUntilAsync(Func condition) + public async Task WaitUntilAsync(Func condition, CallbackPriority priority = CallbackPriority.Normal) { TaskCompletionSource tcs = new TaskCompletionSource(null); var callback = new Callback(condition, tcs); - subscribe(callback); + subscribe(callback, priority); await tcs.Task; - unsubscribe(callback); + unsubscribe(callback, priority); } public async Task InvokeAsync() @@ -90,20 +89,14 @@ public void Invoke() #endregion - private void subscribe(Callback callback) + private void subscribe(Callback callback, CallbackPriority priority) { - if (_invocationList.Add(callback)) - { - _counter.Add(); - } + _invocationList.Add(callback, priority); } - private void unsubscribe(Callback callback) + private void unsubscribe(Callback callback, CallbackPriority priority) { - if (_invocationList.Remove(callback)) - { - _counter.Remove(); - } + _invocationList.Remove(callback, priority); } private class Callback diff --git a/Source/Engine/AGS.Engine/Misc/Events/AGSEventWithArgs.cs b/Source/Engine/AGS.Engine/Misc/Events/AGSEventWithArgs.cs index 13514ad36..ed6583fc1 100644 --- a/Source/Engine/AGS.Engine/Misc/Events/AGSEventWithArgs.cs +++ b/Source/Engine/AGS.Engine/Misc/Events/AGSEventWithArgs.cs @@ -8,45 +8,44 @@ namespace AGS.Engine { public class AGSEvent : IEvent, IBlockingEvent { - private readonly IConcurrentHashSet _invocationList; - private readonly EventSubscriberCounter _counter = new EventSubscriberCounter(); + private readonly EventCallbacksCollection _invocationList; public AGSEvent() { - _invocationList = new AGSConcurrentHashSet(fireListChangedEvent: false); + _invocationList = new EventCallbacksCollection(); } #region IEvent implementation - public int SubscribersCount => _counter.Count; + public int SubscribersCount => _invocationList.Count; - public void Subscribe(Action callback) => subscribe(new Callback(callback)); + public void Subscribe(Action callback, CallbackPriority priority = CallbackPriority.Normal) => subscribe(new Callback(callback), priority); - public void Subscribe(Action callback) => subscribe(new Callback(callback)); + public void Subscribe(Action callback, CallbackPriority priority = CallbackPriority.Normal) => subscribe(new Callback(callback), priority); - public void Subscribe(ClaimableCallbackWithArgs callback) => subscribe(new Callback(callback)); + public void Subscribe(ClaimableCallbackWithArgs callback, CallbackPriority priority = CallbackPriority.Normal) => subscribe(new Callback(callback), priority); - public void Unsubscribe(Action callback) => unsubscribe(new Callback(callback)); + public void Unsubscribe(Action callback, CallbackPriority priority = CallbackPriority.Normal) => unsubscribe(new Callback(callback), priority); - public void Unsubscribe(Action callback) => unsubscribe(new Callback(callback)); + public void Unsubscribe(Action callback, CallbackPriority priority = CallbackPriority.Normal) => unsubscribe(new Callback(callback), priority); - public void Unsubscribe(ClaimableCallbackWithArgs callback) => unsubscribe(new Callback(callback)); + public void Unsubscribe(ClaimableCallbackWithArgs callback, CallbackPriority priority = CallbackPriority.Normal) => unsubscribe(new Callback(callback), priority); - public void SubscribeToAsync (Func callback) => subscribe(new Callback (callback)); + public void SubscribeToAsync (Func callback, CallbackPriority priority = CallbackPriority.Normal) => subscribe(new Callback (callback), priority); - public void UnsubscribeToAsync (Func callback) => unsubscribe(new Callback (callback)); + public void UnsubscribeToAsync (Func callback, CallbackPriority priority = CallbackPriority.Normal) => unsubscribe(new Callback (callback), priority); - public void SubscribeToAsync(Func callback) => subscribe(new Callback(callback)); + public void SubscribeToAsync(Func callback, CallbackPriority priority = CallbackPriority.Normal) => subscribe(new Callback(callback), priority); - public void UnsubscribeToAsync(Func callback) => unsubscribe(new Callback(callback)); + public void UnsubscribeToAsync(Func callback, CallbackPriority priority = CallbackPriority.Normal) => unsubscribe(new Callback(callback), priority); - public async Task WaitUntilAsync(Predicate condition) + public async Task WaitUntilAsync(Predicate condition, CallbackPriority priority = CallbackPriority.Normal) { TaskCompletionSource tcs = new TaskCompletionSource (null); var callback = new Callback(condition, tcs); - subscribe(callback); + subscribe(callback, priority); await tcs.Task; - unsubscribe(callback); + unsubscribe(callback, priority); } public async Task InvokeAsync (TEventArgs args) @@ -103,20 +102,14 @@ public void Invoke (TEventArgs args) #endregion - private void subscribe(Callback callback) + private void subscribe(Callback callback, CallbackPriority priority) { - if (_invocationList.Add (callback)) - { - _counter.Add(); - } + _invocationList.Add(callback, priority); } - private void unsubscribe(Callback callback) + private void unsubscribe(Callback callback, CallbackPriority priority) { - if (_invocationList.Remove(callback)) - { - _counter.Remove(); - } + _invocationList.Remove(callback, priority); } private class Callback diff --git a/Source/Engine/AGS.Engine/Misc/Events/EventCallbacksCollection.cs b/Source/Engine/AGS.Engine/Misc/Events/EventCallbacksCollection.cs new file mode 100644 index 000000000..952a7cf66 --- /dev/null +++ b/Source/Engine/AGS.Engine/Misc/Events/EventCallbacksCollection.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using AGS.API; + +namespace AGS.Engine +{ + public class EventCallbacksCollection : IEnumerable + { + private readonly EventCallbacksSingleList _high, _normal, _low; + + public EventCallbacksCollection() + { + _high = new EventCallbacksSingleList(); + _normal = new EventCallbacksSingleList(); + _low = new EventCallbacksSingleList(); + } + + public int Count => _high.Count + _normal.Count + _low.Count; + + public void Add(Callback callback, CallbackPriority priority) + { + switch (priority) + { + case CallbackPriority.High: + _high.Add(callback); + break; + case CallbackPriority.Normal: + _normal.Add(callback); + break; + case CallbackPriority.Low: + _low.Add(callback); + break; + default: + throw new NotSupportedException($"Unsupported callback priority: ${priority}"); + } + } + + public void Remove(Callback callback, CallbackPriority priority) + { + switch (priority) + { + case CallbackPriority.High: + _high.Remove(callback); + break; + case CallbackPriority.Normal: + _normal.Remove(callback); + break; + case CallbackPriority.Low: + _low.Remove(callback); + break; + default: + throw new NotSupportedException($"Unsupported callback priority: ${priority}"); + } + } + + public IEnumerator GetEnumerator() + { + foreach (var callback in _high) yield return callback; + foreach (var callback in _normal) yield return callback; + foreach (var callback in _low) yield return callback; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/Source/Engine/AGS.Engine/Misc/Events/EventCallbacksSingleList.cs b/Source/Engine/AGS.Engine/Misc/Events/EventCallbacksSingleList.cs new file mode 100644 index 000000000..9e449372a --- /dev/null +++ b/Source/Engine/AGS.Engine/Misc/Events/EventCallbacksSingleList.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using AGS.API; + +namespace AGS.Engine +{ + public class EventCallbacksSingleList : IEnumerable + { + private const int MAX_SUBSCRIPTIONS = 10000; + + private readonly IConcurrentHashSet _list; + + public EventCallbacksSingleList() + { + _list = new AGSConcurrentHashSet(fireListChangedEvent: false); + } + + public int Count { get; private set; } + + public void Add(Callback callback) + { + if (!_list.Add(callback)) return; + Count++; + if (Count > MAX_SUBSCRIPTIONS) + { + throw new OverflowException("Event Subscription Overflow"); + } + } + + public void Remove(Callback callback) + { + if (!_list.Remove(callback)) return; + Count--; + } + + public IEnumerator GetEnumerator() + { + return _list.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/Source/Engine/AGS.Engine/Misc/Events/EventSubscriberCounter.cs b/Source/Engine/AGS.Engine/Misc/Events/EventSubscriberCounter.cs deleted file mode 100644 index 0c0392ee2..000000000 --- a/Source/Engine/AGS.Engine/Misc/Events/EventSubscriberCounter.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -namespace AGS.Engine -{ - public class EventSubscriberCounter - { - private const int MAX_SUBSCRIPTIONS = 10000; - - public int Count { get; private set; } - - public void Add() - { - Count++; - if (Count > MAX_SUBSCRIPTIONS) - { - throw new OverflowException("Event Subscription Overflow"); - } - } - - public void Remove() - { - Count--; - } - } -} diff --git a/Source/Engine/AGS.Engine/Objects/Interactions/AGSInteractionEvent.cs b/Source/Engine/AGS.Engine/Objects/Interactions/AGSInteractionEvent.cs index fb7c02f01..08a73ef36 100644 --- a/Source/Engine/AGS.Engine/Objects/Interactions/AGSInteractionEvent.cs +++ b/Source/Engine/AGS.Engine/Objects/Interactions/AGSInteractionEvent.cs @@ -28,57 +28,57 @@ public AGSInteractionEvent(List> defaultEvents, string verb, public int SubscribersCount => _ev.SubscribersCount; - public void Subscribe(Action callback) + public void Subscribe(Action callback, CallbackPriority priority = CallbackPriority.Normal) { - _ev.Subscribe(callback); + _ev.Subscribe(callback, priority); } - public void Unsubscribe(Action callback) + public void Unsubscribe(Action callback, CallbackPriority priority = CallbackPriority.Normal) { - _ev.Unsubscribe(callback); + _ev.Unsubscribe(callback, priority); } - public void Subscribe(Action callback) + public void Subscribe(Action callback, CallbackPriority priority = CallbackPriority.Normal) { - _ev.Subscribe(callback); + _ev.Subscribe(callback, priority); } - public void Unsubscribe(Action callback) + public void Unsubscribe(Action callback, CallbackPriority priority = CallbackPriority.Normal) { - _ev.Unsubscribe(callback); + _ev.Unsubscribe(callback, priority); } - public void WaitUntil(Predicate condition) + public void WaitUntil(Predicate condition, CallbackPriority priority = CallbackPriority.Normal) { - Task.Run(async () => await WaitUntilAsync(condition)).Wait(); + Task.Run(async () => await WaitUntilAsync(condition, priority)).Wait(); } - public void SubscribeToAsync(Func callback) + public void SubscribeToAsync(Func callback, CallbackPriority priority = CallbackPriority.Normal) { - _ev.SubscribeToAsync(callback); + _ev.SubscribeToAsync(callback, priority); } - public void UnsubscribeToAsync(Func callback) + public void UnsubscribeToAsync(Func callback, CallbackPriority priority = CallbackPriority.Normal) { - _ev.UnsubscribeToAsync(callback); + _ev.UnsubscribeToAsync(callback, priority); } - public void SubscribeToAsync(Func callback) + public void SubscribeToAsync(Func callback, CallbackPriority priority = CallbackPriority.Normal) { - _ev.SubscribeToAsync(callback); + _ev.SubscribeToAsync(callback, priority); } - public void UnsubscribeToAsync(Func callback) + public void UnsubscribeToAsync(Func callback, CallbackPriority priority = CallbackPriority.Normal) { - _ev.UnsubscribeToAsync(callback); + _ev.UnsubscribeToAsync(callback, priority); } - public async Task WaitUntilAsync(Predicate condition) + public async Task WaitUntilAsync(Predicate condition, CallbackPriority priority = CallbackPriority.Normal) { var ev = getEvent(); if (ev == null) return; if (!await approachHotspot(ev)) return; - await ev.WaitUntilAsync(condition); + await ev.WaitUntilAsync(condition, priority); } public async Task InvokeAsync(TEventArgs args) diff --git a/Source/Tests/Engine/Misc/Events/EventTests.cs b/Source/Tests/Engine/Misc/Events/EventTests.cs index 29ee883bd..f3973b19d 100644 --- a/Source/Tests/Engine/Misc/Events/EventTests.cs +++ b/Source/Tests/Engine/Misc/Events/EventTests.cs @@ -144,7 +144,7 @@ public void SubscribeUnsubscribeOnDifferentTargetAsyncTest() public void ClaimEventTest() { AGSEvent ev = new AGSEvent(); - ev.Subscribe(onEventClaim); + ev.Subscribe(onEventClaim, CallbackPriority.High); EventTests target2 = new EventTests(); ev.Subscribe(target2.onEventClaim); ev.Invoke(new MockEventArgs(x));