Skip to content

Commit

Permalink
Add callback priority for event subscriptions
Browse files Browse the repository at this point in the history
This will allow some control over which subscriber will get the event first.
  • Loading branch information
Tzach Shabtay committed Mar 6, 2018
1 parent 8830fac commit 017c012
Show file tree
Hide file tree
Showing 9 changed files with 243 additions and 119 deletions.
52 changes: 42 additions & 10 deletions Source/AGS.API/Misc/Events/IBlockingEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,28 @@ namespace AGS.API
/// </summary>
public delegate void ClaimableCallbackWithArgs<TEventArgs>(TEventArgs args, ref ClaimEventToken token);

/// <summary>
/// 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.
/// </summary>
public enum CallbackPriority : byte
{
/// <summary>
/// Low priority- will receive the event last.
/// </summary>
Low,
/// <summary>
/// Normal priority.
/// </summary>
Normal,
/// <summary>
/// High priority- will receive the event first.
/// </summary>
High,
}

/// <summary>
/// Represents an event which can be subscribed and invoked synchronously.
/// An event is a notification for something that has happened.
Expand All @@ -45,14 +67,16 @@ public interface IBlockingEvent<TEventArgs>
/// Once subscribed, whenever the event happens this callback will be called.
/// </summary>
/// <param name="callback">Callback.</param>
void Subscribe(Action<TEventArgs> callback);
/// <param name="priority">The callback priority (determines the order in which the subscribers get the events).</param>
void Subscribe(Action<TEventArgs> callback, CallbackPriority priority = CallbackPriority.Normal);

/// <summary>
/// Unsubscribe the specified callback from the event.
/// This will stops notifications to call this callback.
/// </summary>
/// <param name="callback">Callback.</param>
void Unsubscribe(Action<TEventArgs> callback);
/// <param name="priority">The callback priority (determines the order in which the subscribers get the events).</param>
void Unsubscribe(Action<TEventArgs> callback, CallbackPriority priority = CallbackPriority.Normal);

/// <summary>
/// Subscribe the specified callback to the event.
Expand All @@ -62,21 +86,24 @@ public interface IBlockingEvent<TEventArgs>
/// For an example- <see cref="ClaimableCallback"/>.
/// </summary>
/// <param name="callback">Callback.</param>
void Subscribe(ClaimableCallbackWithArgs<TEventArgs> callback);
/// <param name="priority">The callback priority (determines the order in which the subscribers get the events).</param>
void Subscribe(ClaimableCallbackWithArgs<TEventArgs> callback, CallbackPriority priority = CallbackPriority.Normal);

/// <summary>
/// Unsubscribe the specified callback from the event.
/// This will stops notifications to call this callback.
/// </summary>
/// <param name="callback">Callback.</param>
void Unsubscribe(ClaimableCallbackWithArgs<TEventArgs> callback);
/// <param name="priority">The callback priority (determines the order in which the subscribers get the events).</param>
void Unsubscribe(ClaimableCallbackWithArgs<TEventArgs> callback, CallbackPriority priority = CallbackPriority.Normal);

/// <summary>
/// Asynchronously wait until the event fires and the specific condition applies.
/// </summary>
/// <returns>The task to be awaited.</returns>
/// <param name="condition">The condition we are waiting to apply before moving on.</param>
Task WaitUntilAsync(Predicate<TEventArgs> condition);
/// <param name="priority">The callback priority (determines the order in which the subscribers get the events).</param>
Task WaitUntilAsync(Predicate<TEventArgs> condition, CallbackPriority priority = CallbackPriority.Normal);

/// <summary>
/// Invoke the event synchronously (i.e will wait for all subscribers to process the event before moving on).
Expand All @@ -103,14 +130,16 @@ public interface IBlockingEvent
/// Once subscribed, whenever the event happens this callback will be called.
/// </summary>
/// <param name="callback">Callback.</param>
void Subscribe(Action callback);
/// <param name="priority">The callback priority (determines the order in which the subscribers get the events).</param>
void Subscribe(Action callback, CallbackPriority priority = CallbackPriority.Normal);

/// <summary>
/// Unsubscribe the specified callback from the event.
/// This will stops notifications to call this callback.
/// </summary>
/// <param name="callback">Callback.</param>
void Unsubscribe(Action callback);
/// <param name="priority">The callback priority (determines the order in which the subscribers get the events).</param>
void Unsubscribe(Action callback, CallbackPriority priority = CallbackPriority.Normal);

/// <summary>
/// Subscribe the specified callback to the event.
Expand All @@ -120,21 +149,24 @@ public interface IBlockingEvent
/// For an example- <see cref="ClaimableCallback"/>.
/// </summary>
/// <param name="callback">Callback.</param>
void Subscribe(ClaimableCallback callback);
/// <param name="priority">The callback priority (determines the order in which the subscribers get the events).</param>
void Subscribe(ClaimableCallback callback, CallbackPriority priority = CallbackPriority.Normal);

/// <summary>
/// Unsubscribe the specified callback from the event.
/// This will stops notifications to call this callback.
/// </summary>
/// <param name="callback">Callback.</param>
void Unsubscribe(ClaimableCallback callback);
/// <param name="priority">The callback priority (determines the order in which the subscribers get the events).</param>
void Unsubscribe(ClaimableCallback callback, CallbackPriority priority = CallbackPriority.Normal);

/// <summary>
/// Asynchronously wait until the event fires and the specific condition applies.
/// </summary>
/// <returns>The task to be awaited.</returns>
/// <param name="condition">The condition we are waiting to apply before moving on.</param>
Task WaitUntilAsync(Func<bool> condition);
/// <param name="priority">The callback priority (determines the order in which the subscribers get the events).</param>
Task WaitUntilAsync(Func<bool> condition, CallbackPriority priority = CallbackPriority.Normal);

/// <summary>
/// Invoke the event synchronously (i.e will wait for all subscribers to process the event before moving on).
Expand Down
42 changes: 28 additions & 14 deletions Source/AGS.API/Misc/Events/IEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,65 +21,74 @@ public interface IEvent<TEventArgs>
/// Once subscribed, whenever the event happens this callback will be called.
/// </summary>
/// <param name="callback">Callback.</param>
void Subscribe(Action<TEventArgs> callback);
/// <param name="priority">The callback priority (determines the order in which the subscribers get the events).</param>
void Subscribe(Action<TEventArgs> callback, CallbackPriority priority = CallbackPriority.Normal);

/// <summary>
/// Unsubscribe the specified callback from the event.
/// This will stops notifications to call this callback.
/// </summary>
/// <param name="callback">Callback.</param>
void Unsubscribe(Action<TEventArgs> callback);
/// <param name="priority">The callback priority (determines the order in which the subscribers get the events).</param>
void Unsubscribe(Action<TEventArgs> callback, CallbackPriority priority = CallbackPriority.Normal);

/// <summary>
/// Subscribe the specified asynchronous callback to the event.
/// Once subscribed, whenever the event happens this callback will be called.
/// </summary>
/// <param name="callback">Callback.</param>
void SubscribeToAsync(Func<TEventArgs, Task> callback);
/// <param name="priority">The callback priority (determines the order in which the subscribers get the events).</param>
void SubscribeToAsync(Func<TEventArgs, Task> callback, CallbackPriority priority = CallbackPriority.Normal);

/// <summary>
/// Unsubscribe the specified asynchronous callback from the event.
/// This will stops notifications to call this callback.
/// </summary>
/// <param name="callback">Callback.</param>
void UnsubscribeToAsync(Func<TEventArgs, Task> callback);
/// <param name="priority">The callback priority (determines the order in which the subscribers get the events).</param>
void UnsubscribeToAsync(Func<TEventArgs, Task> callback, CallbackPriority priority = CallbackPriority.Normal);

/// <summary>
/// Subscribe the specified callback to the event.
/// Once subscribed, whenever the event happens this callback will be called.
/// 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).
/// </summary>
/// <param name="callback">Callback.</param>
void Subscribe(Action callback);
/// <param name="priority">The callback priority (determines the order in which the subscribers get the events).</param>
void Subscribe(Action callback, CallbackPriority priority = CallbackPriority.Normal);

/// <summary>
/// Unsubscribe the specified callback from the event.
/// This will stops notifications to call this callback.
/// </summary>
/// <param name="callback">Callback.</param>
void Unsubscribe(Action callback);
/// <param name="priority">The callback priority (determines the order in which the subscribers get the events).</param>
void Unsubscribe(Action callback, CallbackPriority priority = CallbackPriority.Normal);

/// <summary>
/// Subscribe the specified asynchronous callback to the event.
/// Once subscribed, whenever the event happens this callback will be called.
/// 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).
/// </summary>
/// <param name="callback">Callback.</param>
void SubscribeToAsync(Func<Task> callback);
/// <param name="priority">The callback priority (determines the order in which the subscribers get the events).</param>
void SubscribeToAsync(Func<Task> callback, CallbackPriority priority = CallbackPriority.Normal);

/// <summary>
/// Unsubscribe the specified asynchronous callback from the event.
/// This will stops notifications to call this callback.
/// </summary>
/// <param name="callback">Callback.</param>
void UnsubscribeToAsync(Func<Task> callback);
/// <param name="priority">The callback priority (determines the order in which the subscribers get the events).</param>
void UnsubscribeToAsync(Func<Task> callback, CallbackPriority priority = CallbackPriority.Normal);

/// <summary>
/// Asynchronously wait until the event fires and the specific condition applies.
/// </summary>
/// <returns>The task to be awaited.</returns>
/// <param name="condition">The condition we are waiting to apply before moving on.</param>
Task WaitUntilAsync(Predicate<TEventArgs> condition);
/// <param name="priority">The callback priority (determines the order in which the subscribers get the events).</param>
Task WaitUntilAsync(Predicate<TEventArgs> condition, CallbackPriority priority = CallbackPriority.Normal);

/// <summary>
/// Invoke the event asynchronously.
Expand All @@ -106,35 +115,40 @@ public interface IEvent
/// Once subscribed, whenever the event happens this callback will be called.
/// </summary>
/// <param name="callback">Callback.</param>
void Subscribe(Action callback);
/// <param name="priority">The callback priority (determines the order in which the subscribers get the events).</param>
void Subscribe(Action callback, CallbackPriority priority = CallbackPriority.Normal);

/// <summary>
/// Unsubscribe the specified callback from the event.
/// This will stops notifications to call this callback.
/// </summary>
/// <param name="callback">Callback.</param>
void Unsubscribe(Action callback);
/// <param name="priority">The callback priority (determines the order in which the subscribers get the events).</param>
void Unsubscribe(Action callback, CallbackPriority priority = CallbackPriority.Normal);

/// <summary>
/// Subscribe the specified asynchronous callback to the event.
/// Once subscribed, whenever the event happens this callback will be called.
/// </summary>
/// <param name="callback">Callback.</param>
void SubscribeToAsync(Func<Task> callback);
/// <param name="priority">The callback priority (determines the order in which the subscribers get the events).</param>
void SubscribeToAsync(Func<Task> callback, CallbackPriority priority = CallbackPriority.Normal);

/// <summary>
/// Unsubscribe the specified asynchronous callback from the event.
/// This will stops notifications to call this callback.
/// </summary>
/// <param name="callback">Callback.</param>
void UnsubscribeToAsync(Func<Task> callback);
/// <param name="priority">The callback priority (determines the order in which the subscribers get the events).</param>
void UnsubscribeToAsync(Func<Task> callback, CallbackPriority priority = CallbackPriority.Normal);

/// <summary>
/// Asynchronously wait until the event fires and the specific condition applies.
/// </summary>
/// <returns>The task to be awaited.</returns>
/// <param name="condition">The condition we are waiting to apply before moving on.</param>
Task WaitUntilAsync(Func<bool> condition);
/// <param name="priority">The callback priority (determines the order in which the subscribers get the events).</param>
Task WaitUntilAsync(Func<bool> condition, CallbackPriority priority = CallbackPriority.Normal);

/// <summary>
/// Invoke the event asynchronously.
Expand Down
39 changes: 16 additions & 23 deletions Source/Engine/AGS.Engine/Misc/Events/AGSEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,37 +8,36 @@ namespace AGS.Engine
{
public class AGSEvent : IEvent, IBlockingEvent
{
private readonly IConcurrentHashSet<Callback> _invocationList;
private readonly EventSubscriberCounter _counter = new EventSubscriberCounter();
private readonly EventCallbacksCollection<Callback> _invocationList;

public AGSEvent()
{
_invocationList = new AGSConcurrentHashSet<Callback>(fireListChangedEvent: false);
_invocationList = new EventCallbacksCollection<Callback>();
}

#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<Task> callback) => subscribe(new Callback(callback));
public void SubscribeToAsync(Func<Task> callback, CallbackPriority priority = CallbackPriority.Normal) => subscribe(new Callback(callback), priority);

public void UnsubscribeToAsync(Func<Task> callback) => unsubscribe(new Callback(callback));
public void UnsubscribeToAsync(Func<Task> callback, CallbackPriority priority = CallbackPriority.Normal) => unsubscribe(new Callback(callback), priority);

public async Task WaitUntilAsync(Func<bool> condition)
public async Task WaitUntilAsync(Func<bool> condition, CallbackPriority priority = CallbackPriority.Normal)
{
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>(null);
var callback = new Callback(condition, tcs);
subscribe(callback);
subscribe(callback, priority);
await tcs.Task;
unsubscribe(callback);
unsubscribe(callback, priority);
}

public async Task InvokeAsync()
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit 017c012

Please sign in to comment.