Difference between revisions of "Events"
| (3 intermediate revisions by the same user not shown) | |||
| Line 1: | Line 1: | ||
Events are a very commonly used feature of SocialStack API's which allow modules to extend each other without needing to change the code of the modules. They are structured such that they are always autocomplete friendly. | Events are a very commonly used feature of SocialStack API's which allow modules to extend each other without needing to change the code of the modules. They are structured such that they are always autocomplete friendly and follow the dispatch/ listener naming scheme standard. | ||
= Defining Events = | = Defining Events = | ||
| Line 54: | Line 54: | ||
= Dispatching an event = | = Dispatching an event = | ||
To trigger event handlers, use Dispatch. | To trigger event handlers, use Dispatch. Using the event handler defined above: | ||
<syntaxhighlight lang="csharp"> | <syntaxhighlight lang="csharp"> | ||
| Line 100: | Line 100: | ||
AddEventListener accepts a priority argument which defaults to 10. The lower the number, the earlier your handler will occur. Use this to define handler order when order is necessary. | AddEventListener accepts a priority argument which defaults to 10. The lower the number, the earlier your handler will occur. Use this to define handler order when order is necessary. | ||
= Performance = | |||
Events are defined as a ValueTask as they almost always do something that requires no waiting at all. Because of the way how ValueTask avoids heap allocations unless waiting for a Task is actually necessary, the event execution system has almost no overhead on release builds. AddEventListener resizes a sorted array and dispatch simply iterates over the array. In summary, event handlers are incredibly cheap - you can easily run hundreds of millions of dispatches per second and it is not an issue, unless of course an actual event listener is quite heavy. The flexibility for other developers gained far outweighs the runtime performance cost of dispatching and handling events. | |||
Latest revision as of 15:40, 17 November 2022
Events are a very commonly used feature of SocialStack API's which allow modules to extend each other without needing to change the code of the modules. They are structured such that they are always autocomplete friendly and follow the dispatch/ listener naming scheme standard.
Defining Events
There is a global static partial class called Events which is extended with your custom event handlers. Typically, such an extension is defined like this:
namespace Api.Eventing;
public partial class Events
{
// EventGroup<X> defines e.g. AfterCreate, BeforeCreate and similar main events.
public static EventGroup<Thing> Thing;
}
When you use socialstack generate to create an API module, it defines the above for you in the file called Events.cs.
Firstly, you don't have to use EventGroup<X> - you can use either anything that inherits EventGroup<X>, or an EventHandler<X> which is for a specific event rather than a set of them. X is both the return type of the event, and the type of its first argument (after Context).
It is recommended that, if you want to add a custom event to a specific entity, edit Events.cs and create a little partial event class like so:
public partial class CustomThingEventGroup : EventGroup<Thing>{
// Add custom event handlers in here, like this one.
// public EventHandler<Thing> AfterSomething;
// You can have up to 3 custom values passed to your event handler.
// public EventHandler<Thing, string, int> BeforeSomethingElse;
}
Note that the first custom value is also the return type of the event. For example, if AfterSomething is dispatched, it will return a Thing (typically just the same object it was given, but can be a new instance if the event needs to do so).
Then, edit your Events extension to change it to your newly defined event group class:
namespace Api.Eventing;
public partial class Events
{
public static CustomThingEventGroup Thing;
}
And that's it - from there, you can use Events.Thing.AfterSomething in other classes. Note that the actual objects are instanced for you, so you don't need to new CustomThingEventGroup() or similar anywhere.
Dispatching an event
To trigger event handlers, use Dispatch. Using the event handler defined above:
// Dispatch the handler, passing the context plus anything that the event requires (up to 3 args, but most commonly just 1).
await Events.Thing.AfterSomething.Dispatch(context, aThing);
Handling an event
To do something when an event occurs, you need to add a handler/ listener to the event. That is via AddEventListener:
// Dispatch the handler, passing the context plus anything that the event requires (up to 3 args, but most commonly just 1).
await Events.Thing.AfterSomething.AddEventListener(async (Context context, Thing thing) => {
// Some event handlers return null to indicate that the event should halt.
// It's therefore good practice to null check:
if(thing == null)
{
return null;
}
// await some stuff
return thing;
});
// If your event handler is purely synchronous, just return a ValueTask instead:
await Events.Thing.AfterSomething.AddEventListener((Context context, Thing thing) => {
// Some event handlers return null to indicate that the event should halt.
// It's therefore good practice to null check:
if(thing == null)
{
return new ValueTask<Thing>(null);
}
// do some things
return new ValueTask<Thing>(thing);
});
AddEventListener accepts a priority argument which defaults to 10. The lower the number, the earlier your handler will occur. Use this to define handler order when order is necessary.
Performance
Events are defined as a ValueTask as they almost always do something that requires no waiting at all. Because of the way how ValueTask avoids heap allocations unless waiting for a Task is actually necessary, the event execution system has almost no overhead on release builds. AddEventListener resizes a sorted array and dispatch simply iterates over the array. In summary, event handlers are incredibly cheap - you can easily run hundreds of millions of dispatches per second and it is not an issue, unless of course an actual event listener is quite heavy. The flexibility for other developers gained far outweighs the runtime performance cost of dispatching and handling events.