• 15_游戏编程模式EventQueue


    #### 两个例子
    1.GUI event loop
    
    ```
    while (running)
    {
      // 从事件队列里获取一个事件
      Event event = getNextEvent();
      // Handle event...
    }
    ```
    2.Central event bus
        不同系统公用的通信中心
        
        
    #### 有问题的code
    
    ```
    class Audio
    {
    public:
      static void playSound(SoundId id, int volume);
    };
    
    class Audio
    {
    public:
      static void playSound(SoundId id, int volume);
    };
    
    class Menu
    {
    public:
      void onSelect(int index)
      {
        Audio::playSound(SOUND_BLOOP, VOL_MAX);
        // Other stuff...
      }
    };
    
    ```
    问题:
    
    1. api同步调用, 阻塞到audio处理完请求
    2. 多个请求不能合并处理
    3. 处理请求有可能运行在错误的线程上(没有锁)
    
    ####模式定义:
    ```
    一系列的通知或请求存储在先进先出的队列里. 发送通知进行入队; 请求处理者从队列里获取请求.
    ```
    
    
    
    ##### 何时使用
    ```
    1.如果只是想从sender那里获取消息,使用 observer 或者command将会更简单.
    2.当你需要push什么到另一个模块,或者pull什么从另一个地方的时候, 你需要一个buffer, 此时就需要一个队列了.
    3. 队里提供的pull操作, receiver可以延迟处理,合并请求, 或者丢弃. pull请求不开放给sender使用,当sender需要获得响应的时候,队列就有点技穷了.(send then pray)
    ```
    
    #### 注意事项
    ```
    1.中心事件队列是个全局变量
    2.世界状态会改变,(队列处理不是及时的)
    3.困在反馈循环里(a ->b -> a ->b ...).如果是同步队列的,你会很快的发现循环bug.
        一般原则: 避免在在处理事件的函数里发送事件.
    ```
    
    #### Sample Code
    
    ```
    struct PlayMessage
    {
      SoundId id;
      int volume;
    };
    
    class Audio
    {
    public:
      static void init()
      {
        numPending_ = 0;
      }
    
      // Other stuff...
    private:
      static const int MAX_PENDING = 16;
    
      static PlayMessage pending_[MAX_PENDING];
      static int numPending_;
    };
    
    void Audio::playSound(SoundId id, int volume)
    {
      assert(numPending_ < MAX_PENDING);
    
      pending_[numPending_].id = id;
      pending_[numPending_].volume = volume;
      numPending_++;
    }
    
    class Audio
    {
    public:
      static void update()
      {
        for (int i = 0; i < numPending_; i++)
        {
          ResourceId resource = loadSound(pending_[i].id);
          int channel = findOpenChannel();
          if (channel == -1) return;
          startSound(resource, channel, pending_[i].volume);
        }
    
        numPending_ = 0;
      }
    
      // Other stuff...
    };
    
    ```
    
    ##### ring buffer 循环buffer
    
    ```
    class Audio
    {
    public:
      static void init()
      {
        head_ = 0;
        tail_ = 0;
      }
    
      // Methods...
    private:
      static int head_;
      static int tail_;
    
      // Array...
    };
    
    
    void Audio::playSound(SoundId id, int volume)
    {
      assert((tail_ + 1) % MAX_PENDING != head_);
    
      // Add to the end of the list.
      pending_[tail_].id = id;
      pending_[tail_].volume = volume;
      tail_ = (tail_ + 1) % MAX_PENDING;
    }
    
    void Audio::update()
    {
      // If there are no pending requests, do nothing.
      if (head_ == tail_) return;
    
      ResourceId resource = loadSound(pending_[head_].id);
      int channel = findOpenChannel();
      if (channel == -1) return;
      startSound(resource, channel, pending_[head_].volume);
    
      head_ = (head_ + 1) % MAX_PENDING;
    }
    ```
    
    ##### 合并请求
    
    ```
    void Audio::playSound(SoundId id, int volume)
    {
      // Walk the pending requests.
      for (int i = head_; i != tail_;
           i = (i + 1) % MAX_PENDING)
      {
        if (pending_[i].id == id)
        {
          // Use the larger of the two volumes.
          pending_[i].volume = max(volume, pending_[i].volume);
    
          // Don't need to enqueue.
          return;
        }
      }
    
      // Previous code...
    }
    ```
    
    ##### 多线程
    
    push pull操作需要线程安全
    
    
    
    #### 队列里保存的是什么
    
    ```
    event or message
    1 event queue(一对多)
        描述一些已经发生的事情, 类似异步的observer模式
        1.允许多个监听者, 队列里保存的都是*已经发生的事件*, 发送者不关心谁去接受它.
        2.作用域更广.被用于广播一类的事情.趋向于全局可见.
    2 message queue(多对一)
        更趋向于只有一个监听者. 多个请求从不同的地方发来,一个处理者进行处理
    ```
    
    #### 谁可以读队列
    
    ```
    1 单播队列:
        1.队列实现读取. 发送者只管发送
        2.队列被封装的更好
        3.没有读取竞争(决定是广播还是挨个分配)
    2 广播队列:
        1.如果没有监听者,event被丢弃
        2.你可能会需要一个事件过滤
    3 工作队列:
        类似广播队列,比如worker pool
        1.需要调度
    ```
        
    #### 谁可以写队列
    
    ```
    1 一个写者(类似同步observer)
        1.你明确知道事件是谁发出的
        2.通常允许多个读者
    2 多个写者
        1.注意循环
        2.需要有访问发送者的途径(事件里包含sender的引用)
    ```
        
    #### 队里里对象的生命周期
    
    ```
    1. 转移所有权. 有发送者转给队列,队列转给接受者
    2. 共享所有权.
    3. 所有权只给队列. (队列申请内存,然后发送者填充数据, 接受者得到引用)
    ```
    
    ###See also
    
    ```
    1. 队列很像是异步的observer
    2. message queue, pub sub
    3. 有限状态机, 状态机需要输入,如果你想异步执行,可以使用队列. 如果你需要多个状态机互相发消息, 需要一个queue接受输入(mail box), 这叫做actor model
    4. go语言内置的channel本质上就是个 事件或消息 队列.
    ```
  • 相关阅读:
    Highmaps网页图表教程之图表配置项结构与商业授权
    Highmaps网页图表教程之Highmaps第一个实例与图表构成
    Highmaps网页图表教程之下载Highmaps与Highmaps的地图类型
    iOS 9应用开发教程之使用开关滑块控件以及滚动部署视图
    iOS 9应用开发教程之多行读写文本ios9文本视图
    基于STM32的电池管理系统触摸屏设计方案
    如何使用UDP进行跨网段广播(转)
    STM32的NVIC_PriorityGroupConfig使用及优先级分组方式理解
    基于MDK编程STM32程序无法使用,硬件仿真在汇编窗口看到停留在“0x0800XXXX BEAB BKPT 0xAB //进入调试模式”
    IIR 滤波器的实现(C++)
  • 原文地址:https://www.cnblogs.com/lightlfyan/p/4238640.html
Copyright © 2020-2023  润新知