-
Notifications
You must be signed in to change notification settings - Fork 1
Events
Events in Telefrag are exposed via the Telefrag.Event class, which provides additional functionality beyond native delegate-based events.
Subscribing to events is easy. Here are some examples:
With a separate function
PollingStarted.Subscribe(pollStartedHandler);
void pollStartedHandler(Context c, EventArgs e) { ... }With a lambda
ChatMessage.Subscribe((c, e) => {
Log($"{c.From} says {c.Message.TextOrCaption}");
});Keeping a reference to the subscription
The Subscribe() method returns an EventSubscription object which can be held onto to manage the event subscription if needed.
var sub = UpdateReceived.Subscribe(UpdateHandler);
/* later on ... */
sub.Unsubscribe(); All event handlers are of the delegate type EventHandler<TArgs> where TArgs is the subtype of EventArgs used for the event in question. This delegate has the following signature:
public delegate EventHandler<TArgs>(Context c, TArgs e);The two items passed in to every event handler are:
- The Context object associated with the Event. This contains references to the sender (Bot or Client), chat, update, message, security parameters, the Activity currently in scope (if any), etc.
- The
eparameter (which will beEventArgsor a subclass of it) feeds you any detailed event-specific information or references to event-specific objects that may be helpful.
Event handlers have access to set the Handled property of the Context object to true in order to indicate to the object raising the event that you have in some way processed the event.
Normally when an Event is raised, all subscribers to the event are notified (in the order they subscribed), however some types of events (known canonically as Hooks) are raised such that if one event handler "handles" an event (by setting Handled to true), event processing stops and no further handlers are invoked, regardless of active subscriptions.
For this reason it is possible to provide a parameter (subscribePreference) in the call to Subscribe() to indicate whether you would like to be inserted at the front or end of the list of subscriptions depending on whether you want to handle an event or hook before any other existing subscribers or afterwards.
Unsubscribing from events can be accomplished a variety of ways:
- Call
Unsubscribe()on theEventSubscriptionprovided when you subscribed. - Call
Unsubscribe()on theEventand pass in theEventSubscription - Call
Event.UnsubscribeAll()to clear all subscriptions. - If you have control over the scope object (
IEventScope) used in your subscriptions, you can have it returnfalsewhenIsActiveis queried (though there is no guarantee how soon Telefrag will check this value).
When you subscribe to an Event, the Subscribe method takes an optional parameter called scope. Every EventSubscription has a specific IEventScope object that determines the lifetime and validity of the event subscription.
By default, the value of Context.Sender is used if available, which is typically the Bot, Client, etc. associated with the event, however neither the Context.Sender nor the object owning the Event implement IEventScope (or are not available), then a dummy object is used and the subscription will always be valid and active unless explicitly unsubscribed.
The benefit to scoping event subscriptions to scope owners is that when a Bot or Client is removed, or a module or plugin is unloaded -- corresponding event handlers can be automatically unhooked and resources can be released.
Any object can implement IEventScope and be the scope for a subscription; all it needs is a bool IsActive property to tell Telefrag whether or not events hooked up under the auspices of that object are still valid or not. Telefrag periodically goes through and removes all event subscriptions scoped to objects no longer active.
Events can be added to any class that implements IEventOwner, which simply requires a single Guid identifier.
- Create one or more public properties of type
Eventon your class - In your classes constructor, call
Event.Setup(this);
Example:
public class Aldi
{
public Event StoreOpened { get; private set; }
public Event StoreClosed { get; private set; }
public Aldi() {
Event.Setup(this);
}
}Event.Setup will look for any Event property on your class and automatically instantiate it so that it's immediately available and will never be uninitialized.
If you want to use a specific EventArgs subclass, simply provide that as the type argument to the Event<TArgs> type:
public class Walmart
{
public Event<LayawayEventArgs> LayawayItemLost { get; private set; }
}To raise your event, simply call Raise(), passing in an instance of EventArgs.
StoreOpen.Raise(new());The Event will automatically capture the current Context and add a context frame describing the event being handled. If you want to check the value of Handled, you can look for a return value:
var handled = StopOpen.Raise(new());
// this will return true if any of the EventHandlers set
// Context.Handled = trueIf you want to stop invoking event handlers after the first one sets Handled to true, then use the stopOnHandled parameter of Raise():
var handled = StopOpen.Raise(new(), stopOnHandled: true);
// If an EventHandler sets Handled to true, no further event
// handling will occur and the function will return true,
// otherwise other subscribed handlers will be invoked By default, the Raise() function will throw an UnauthorizedAccessException if it is called from a type other than the one it is defined on.
You can configure the scope of what types are allowed to raise an Event by using one or both of these attributes:
- [AllowedCaller] can be used to whitelist a specific type so that it is allowed to call Raise.
- [EventSecurity] defines the general code security scope of who may call Raise. See the table below for more details. Each access level permits all access levels below it.
CodeRelationship value |
Description |
|---|---|
| Private (Default) | The event can only be raised by the Type it is defined on |
| Protected | The event can only be raised by the Type it is defined on and any derived types |
| InternalChild | The event can also be raised by types that are nested children of the type the Event is defined on |
| InternalParent | The event can also be raised by the containing class that |
| Public | Anyone can raise this event from any type in any assembly |
| Example: |
[AllowedCaller(typeof(Salesman))]
[EventSecurity(CodeRelationship.Protected)]
public Event<UsedCarSalesEventArgs> UsedCarSales { get; private set; }
// This Event can be called by
// * Methods defined on this type
// * Methods defined on subtypes (due to the EventSecurity attribute)
// * Methods defined on type 'Salesman' (due to the AllowedCaller attribute)