搬运自OpenMod官网,经过翻译,可能有些不足,欢迎指正!原文链接
事件和事件侦听器
事件用于通知组件正在发生某些事情,如用户断开连接。
有两种类型的事件:
OpenMod事件
C#事件
本指南将介绍OpenMod事件。
订阅事件
有两种方式去订阅事件:
实现IEventListener接口:
public class UserConnectListener : IEventListener<UserConnectedEvent> { [EventListener(Priority = EventListenerPriority.Lowest)] public async Task HandleEventAsync(object sender, UserConnectEvent @event) { // 做点什么 } }
使用IEventBus服务的订阅方法:
public class MyPlugin : OpenModUniversalPlugin { private readonly IEventBus m_EventBus; public MyPlugin(IEventBus eventBus, IServiceProvider serviceProvider) : base(serviceProvider) { m_EventBus = eventBus; } public async Task OnLoadAsync() { m_EventBus.Subscribe(this, (sender, @event) => { // 做点什么 }); } }
当插件卸载时,所有OpenMod事件侦听器都将自动取消订阅。您不必在卸载时手动取消订阅。
事件侦听器优先级和执行顺序
OpenMod允许您控制事件侦听器的执行顺序。执行顺序基于优先级。
您可以使用[EventListener]属性设置事件侦听器优先级。
执行顺序是从最低优先级到最高优先级。换句话说,最低优先级被称为第一优先级。
public class UserConnectListener1 : IEventListener<UserConnectedEvent> { [EventListener(Priority = EventListenerPriority.Lowest)] public async Task HandleEventAsync(object sender, UserConnectEvent @event) { } } public class UserConnectListener2 : IEventListener<UserConnectedEvent> { [EventListener(Priority = EventListenerPriority.Low)] public async Task HandleEventAsync(object sender, UserConnectEvent @event) { } } public class UserConnectListener3 : IEventListener<UserConnectedEvent> { [EventListener(Priority = EventListenerPriority.High)] public async Task HandleEventAsync(object sender, UserConnectEvent @event) { } }
在上面的示例中,首先调用UserConnectListener1,然后调用UserConnectListener2,最后调用UserConnectListener3。
取消事件和忽略取消的事件
事件必须实现ICancellableEvent接口才能取消。如果事件被取消,[EventListener]属性中没有IgnoreCancelled设置为true的事件侦听器,将不会收到通知。
UserConnectingEvent就是这样一个可取消的事件。如果事件被取消,它将断开连接用户的连接。
public class UserConnectingListener1 : IEventListener<UserConnectingEvent> { [EventListener(Priority = EventListenerPriority.Lowest)] public async Task HandleEventAsync(object sender, UserConnectingEvent @event) { if(user.DisplayName.Equals("Trojaner")) { @event.IsCancelled = true; } } } public class UserConnectingListener2 : IEventListener<UserConnectingEvent> { [EventListener(Priority = EventListenerPriority.Low)] public async Task HandleEventAsync(object sender, UserConnectingEvent @event) { // 此事件侦听器将不被调用,因为它不忽略取消 } } public class UserConnectingListener3 : IEventListener<UserConnectingEvent> { [EventListener(Priority = EventListenerPriority.High, IgnoreCancelled = true)] public async Task HandleEventAsync(object sender, UserConnectingEvent @event) { // 即使事件被取消,也会调用此事件侦听器 } }
在上面的示例中,如果名为“Trojaner”的用户连接,UserConnectingListener1将取消该事件。在这种情况下,不会调用UserConnectingListener2,因为它不会像UserConnectingListener3那样忽略取消的事件。
事件侦听器生存期
事件侦听器可以有三种类型的生存期:
Transient – 总是在每个事件上重新创建事件侦听器。如果您有多个IEventListener,那么所有IEventListener都有自己的实例。这是默认的生存期。
Scoped – 如果在一个类中实现多个IEventListener,那么所有IEventListener都将共享同一个实例。否则与Transient相同。
Singleton – 事件侦听器只有一个共享生存期,直到插件卸载为止。
您可以通过添加[EventListenerLifetime(ServiceLifetime)]属性来设置事件侦听器生存期:
[EventListenerLifetime(ServiceLifetime.Transient)] public class UserConnectBroadcaster : IEventListener<UserConnectedEvent> // ...
自定义事件
创建自定义事件很简单:只需创建一个从Event继承的新类。
举个例子:
public class SampleEvent : Event { public int MyValue { get; set; } // 也可以添加其他属性 }
然后,可以使用事件总线发出它:
MyPlugin myPlugin = ...; IEventBus eventBus = ...; ILogger<xxx> logger = ...; var @event = new SampleEvent { MyValue = 20 }; await m_EventBus.EmitAsync(myPlugin, this /* 发送人 */, @event); logger.LogInformation($"Event value: {@event.MyValue}");
如果希望事件可取消,则必须实现ICancellableEvent接口:
public class SampleEvent : Event, ICancellableEvent { public int MyValue { get; set; } public bool IsCancelled { get; set; } }
——————————————————————————————————
MyPlugin myPlugin = ...; IEventBus eventBus = ...; ILogger<xxx> logger = ...; var @event = new SampleEvent { MyValue = 20 }; await m_EventBus.EmitAsync(myPlugin, this /* 发送人 */, @event); if(@event.IsCancelled) { logger.LogInformation($"Event has been cancelled!"); return; } logger.LogInformation($"Event value: {@event.MyValue}");
当你的插件卸载或服务被释放时,不要忘记取消订阅C#事件和委托。例如,如果要在加载插件时从Unturned订阅onEnemyConnected事件,还必须按以下方式取消订阅:
public async UniTask OnLoadAsync() { Provider.onEnemyConnected += OnPlayerConnected; } public async UniTask OnUnloadAsync() { // 这是非常重要的,否则你的插件将无法正确支持重新加载和卸载。 Provider.onEnemyConnected -= OnPlayerConnected; }
避免编写单例事件侦听器。如果事件侦听器具有临时或作用域依赖项,则这可能会导致问题。