• 注册
  • 技术交流 技术交流 关注:8 内容:20

    [OpenMod]插件的事件相关

  • 查看作者
  • 打赏作者
  • 当前位置: 未转变者中文社区 > 技术交流 > 正文
    大版主
    Lv.15

    搬运自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;
    }

    避免编写单例事件侦听器。如果事件侦听器具有临时或作用域依赖项,则这可能会导致问题。

    请登录之后再进行评论

    登录
  • 发布内容
  • 任务中心
  • 实时动态
  • 偏好设置
  • 帖子间隔 侧栏位置: