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本质上就是个 事件或消息 队列.
```

 

posted @ 2015-01-21 13:31  summernight  阅读(339)  评论(0编辑  收藏  举报