From 7288038e76c117896b33401949f26909d8f2eb45 Mon Sep 17 00:00:00 2001 From: marsCatXDU Date: Fri, 11 Nov 2022 10:47:11 +0800 Subject: [PATCH 1/2] Add Chinese translations Signed-off-by: marsCatXDU --- doc/cn/eventdispatcher.md | 255 +++++++++++++++++++++++ doc/cn/eventqueue.md | 331 +++++++++++++++++++++++++++++ doc/cn/mixins.md | 202 ++++++++++++++++++ doc/cn/policies.md | 423 ++++++++++++++++++++++++++++++++++++++ doc/cn/readme.md | 5 + 5 files changed, 1216 insertions(+) create mode 100644 doc/cn/eventdispatcher.md create mode 100644 doc/cn/eventqueue.md create mode 100644 doc/cn/mixins.md create mode 100644 doc/cn/policies.md diff --git a/doc/cn/eventdispatcher.md b/doc/cn/eventdispatcher.md new file mode 100644 index 0000000..e443592 --- /dev/null +++ b/doc/cn/eventdispatcher.md @@ -0,0 +1,255 @@ +# EventDispatcher (事件调度器)类参考手册 + +## 目录 + +- [描述](#description) +- [API 参考](#api-ref) + - [头文件](#header) + - [模板参数](#template-param) + - [公共类型](#public-types) + - [成员函数](#member-functions) +- [嵌套监听器安全](#nested-listener-safety) +- [时间复杂度](#time-complexities) +- [内部数据结构](#internal-data-structure) + + + +## 描述 + +EventDispatcher 类似于 `std::map` 。 + +EventDispatcher 维护一个 `` 映射表。在进行事件调度时, EventDispatcher 会根据事件类型(EventType)查找并调用对应的回调列表(CallbackList) 。该调用过程是同步的,监听器会在 `EventDispatcher::dispatch` 被调用时触发。 + + + +## API 参考 + + + +### 头文件 + +eventpp/eventdispatcher.h + + + +### 模板参数 + +```c++ +template < + typename Event, + typename Prototype, + typename Policies = DefaultPolicies +> +class EventDispatcher; +``` + +`Event`:事件的类型。该类型用于识别事件,具有相同类型的事件就是同一个事件。事件类型必须是能在 `std::map` 或 `std::unordered_map` 中用作 key 的类型,所以该类型应该要么可以使用 `operator <` 进行比较,要么派生自 `std::hash`。 + +`Prototype`:监听器的原型。其应为 C++ 函数类型,例如 `void(int, std::string, const MyClass *)`。 + +`Policies`:配置和扩展调度器的规则。默认值是 `DefaultPolicies`。 详情请阅读https://github.com/wqking/eventpp/blob/master/doc/policies.md 文档。 + + + +### 公共类型 + +`Handle`:由 `appendListener`、`prependListener`、`insertListener` 函数返回的 `Handle` (句柄)类型。可用于插入或移除一个监听器。可以通过将句柄转为 bool 类型来检查其是否为空:句柄为空时值为 `false` 。`Handle` 时可拷贝的。 + +`Callback`:回调函数存储类型。 + +`Event`:事件类型。 + + + +### 成员函数 + +#### 构造函数 + +```c++ +EventDispatcher(); +EventDispatcher(const EventDispatcher & other); +EventDispatcher(EventDispatcher && other) noexcept; +EventDispatcher & operator = (const EventDispatcher & other); +EventDispatcher & operator = (EventDispatcher && other) noexcept; +void swap(EventDispatcher & other) noexcept; +``` + +EventDispatcher 可以拷贝、move、赋值、move 赋值和交换 + +#### appendListener + +```c++ +Handler appendListener(const Event & event, const Callback & callback); +``` + +向调度器中添加用于监听 *event* 事件的回调函数 *callback*。 + +监听器会被添加到监听器列表的末尾。 + +该函数会返回一个代表监听器的句柄。该句柄可用于移除该监听器,或在该监听器之前插入其他监听器。 + +若 `appendListener` 在一次事件调度的过程中被另一个监听器调用,则新的监听器不会在本次事件调度的过程中被触发。 + +如果同一个回调函数被添加了两次,则会调度器中出现两个相同的监听器。 + +该函数的时间复杂度为 O(1) + 在内部映射表中寻找事件的时间 + +#### prependListener + +```c++ +Handle prependListener(const Event & event, const Callback & callback); +``` + +向调度器中添加用于监听 *event* 事件的回调函数 *callback* 。 + +监听器会被添加到监听器列表的最前端。 + +该函数返回一个代表该监听器的句柄。该句柄可用于移除该监听器,也可以用于在该监听器前面再插入其他监听器。 + +若 `prependListener` 在一次事件调度的过程中被另一个监听器调用,则新的监听器不会在本次事件调度的过程中被触发。 + +该函数的时间复杂度为 O(1) + 在内部映射表中寻找事件的时间。 + +#### insertListener + +```cpp +Handle insertListener(const Event & event, const Callback & callback, const Handle before); +``` + +将回调函数 *callback* 插入到调度器中 *before* 监听器前面的一个位置,以监听 *event* 事件。如果没找到 *before* ,*callback* 会被添加到监听器列表的末尾。 + +该函数会返回一个代表该监听器的句柄。该句柄可用于移除监听器,也可以用于在该监听器前面再插入其他监听器。 + +若 `insertListener` 在一次事件调度的过程中被另一个监听器调用,则用该函数新插入的监听器不会在本次事件调度的过程中被触发。 + +该函数的时间复杂度为 O(1) + 在内部映射表中寻找事件的时间。 + +注意:调用者必须确保 `before` 句柄是由 `this` EventDispatcher 创建的。若不能确定,则可用 `ownsHandle` 来检查 `before` 句柄是否属于 `this` EventDispatcher。 `insert` 函数只能在 `ownsHandle(before)` 返回 true 时才能被调用,否则会出现未定义的行为,导致出现诡异的 bug。 + +`insertListener` 会在自身的回调列表中进行一次 `assert(ownsHandle(before))` ,但出于性能方面的考量,在发布的代码中并不会进行检查。 + +#### removeListener + +```cpp +bool removeListener(const Event & event, const Handle handle); +``` + +移除调度器中 *handle* 所指向的监听 *event* 的监听器。 + +若监听器被成功移除,该函数返回 tue。若未找到对应监听器则返回 false。 + +该函数的时间复杂度为 O(1) + 在内部映射表中寻找事件的时间。 + +注意:`handle` 必须是由 `this` EventDispatcher 创建的。详细说明请查看 `insertListener` 中的注意部分。 + +#### hasAnyListener + +```cpp +bool hasAnyListener(const Event & event) const; +``` + +当存在与 `event` 对应的监听器时返回 ture ,否则返回 false 。 + +注意:在多线程中,该函数返回 true 时并不能保证 event 中一定存在监听器。回调列表可能在该函数返回 true 后就被清空了,反之亦然。该函数的时间复杂度为 O(1) + 在内部映射表中寻找事件的时间。 + +#### ownsHandle + +```cpp +bool ownsHandle(const Event & event, const Handle & handle) const; +``` + +若 `handle` 是由 EventDispatcher 为 `event` 创建的,返回 true,否则返回 false。 + +时间复杂度为 O(N) + +#### forEach + +```cpp +template +void forEach(const Event & event, Func && func); +``` + +对 `event` 的所有监听器使用 `func`。 + +`func` 可以是下面两个属性中的一个: + +```cpp +AnyReturnType func(const EventDispatcher::Handle &, const EventDispatcher::Callback &); +AnyReturnType func(const EventDispatcher::Callback &); +``` + +注意:`func` 可以安全地移除任何监听器或添加其他的监听器。 + +#### forEachIf + +```cpp +template +bool forEachIf(const Event & event, Func && func); +``` + +对 `event` 的所有监听器使用 `func`。`func` 必须返回一个 bool 值,若返回值为 false, forEachIf 将立即停止循环。 + +当所有监听器都被触发执行,或未找到 `event` 时返回 `true` 。当 `func` 返回 `false` 时返回 `false` 。 + +#### dispatch + +```cpp +void dispatch(Args ...args); + +template +void dispatch(T && first, Args ...args); +``` + +调度一个事件。`dispatch` 函数的参数是要被调度的事件类型。 + +所有的监听器都会被使用 `args` 参数调用。 + +该函数是同步的。所有监听器都会在调用 `dispatch` 的线程中被调用。 + +这两个重载函数略有区别,具体如何使用要根据 `ArgumentPassingMode` 策略而定。详情请阅读https://github.com/wqking/eventpp/blob/master/doc/policies.md 文档。 + + + +## 嵌套监听器安全 + +1. 若一个监听器在一次调度的过程中,向调度器中加入另一个有着相同事件类型的监听器,则新的监听器可以保证不会在本次时间调度的过程中被调用。这是由一个 64 位无符号整数类型的计数器保证的,若在一次调度的过程中该计数器值溢出到 0 则会破坏该规则,但该规则将继续处理子序列调度。 +2. 一次调度过程中移除的所有监听器都不会被触发。 +3. 上面的两点在多线程中不成立。若在一个线程正在触发回调列表的时候,其他线程添加或移除了一个回调函数,则被添加或移除的这个回调函数可能会在本次触发执行的过程中执行。 + + + +## 时间复杂度 + +此处讨论的时间复杂度是操作回调列表中的监听器的复杂度,`n` 是监听器的数量。并不包含搜索 `std::map` 中事件所消耗的时间,该部分的时间复杂度为 O(log n) 。 + +- `appendListener`:O(1) +- `prependListener`:O(1) +- `insertListener`:O(1) +- `removeListener`:O(1) + + + +## 内部数据结构 + +EventDispatcher 使用 CallbackList 来管理监听器回调函数。 + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/cn/eventqueue.md b/doc/cn/eventqueue.md new file mode 100644 index 0000000..e0350ff --- /dev/null +++ b/doc/cn/eventqueue.md @@ -0,0 +1,331 @@ +# EventQueue (事件队列)类参考手册 + +## 目录 + +- [描述](#description) +- [API 参考](#api-ref) + - [头文件](#header) + - [模板参数](#template-param) + - [公共类型](#public-types) + - [成员函数](#member-functions) + - [内部类 EventQueue::DisableQueueNotify](#inner-class-eventqueue-disablequeuenotify) +- [内部数据结构](#internal-data-structure) + + + +## 描述 + +EventQueue 在包含了 EventDispatcher 所有特性的基础上新增了事件队列特性。注意:EventQueue 并非派生自 EventDispatcher ,请勿尝试将 EventQueue 转换为 EventDispatcher 类型。 + +EventQueue 是异步的。事件会在调用 `EventQueue::enqueue` 时被缓存进 EventQueue 的事件队列,并在后续调用 `EventQueue::process` 时被调度。 + +EventQueue 相当于是 Qt 中的事件系统(QEvent),或 Windows API 中的信息处理(message processing)。 + + + +## API 参考 + + + +### 头文件 + +eventpp/eventqueue.h + + + +### 模板参数 + +```cpp +template < + typename Event, + typename Prototype, + typename Policies = DefaultPolicies +> +class EventQueue; +``` + +EventQueue 的模板参数与 EventDispatcher 的模板参数完全一致。详细信息请参阅 EventDispatcher 文档。 + + + +### 公共类型 + +`QueuedEvent`:存储在队列中的事件的数据类型。其声明的伪代码表示如下: + +```cpp +struct EventQueue::QueuedEvent +{ + EventType event; + std::tuple argument; + + // 获取事件 + EventType getEvent() const; + + // 获取索引为 N 的实参 + // 与 std::get(queuedEvent.arguments) 相同 + template + NthArgType getArgument() const; +}; +``` + +`event` 是 EventQueue::Event , `arguments` 是 `enqueue` 中传递的实参。 + + + +### 成员函数 + +#### 构造函数 + +```cpp +EventQueue(); +EventQueue(const EventQueue & other); +EventQueue(EventQueue && other) noexcept; +EventQueue & operator = (const EventQueue & other); +EventQueue & operator = (EventQueue && other) noexcept; +``` + +EventQueue 可以拷贝、移动、赋值和移动赋值 + +注意:已排入队列的事件无法被拷贝、移动、赋值和移动赋值,这些操作只会对监听器生效。这就意味着,已排入队列的事件不会在 EventQueue 被复制和赋值时复制。 + +#### enqueue + +```cpp +template +void enqueue(A ...args); + +template +void enqueue(T && first, A ...args); +``` + +将一个事件加入事件队列。事件的类型包含在传给 `enqueue` 函数的实参中。 + +所有可拷贝实参都会被拷贝到内部的数据结构中。所有不可拷贝但可移动的实参都会被移动。 + +EventQueue 的参数必须满足可拷贝和可移动两项中的一项。 + +如果定义的参数是基类的引用,却传入了一个派生类的对象,那么就只会保存基类,派生部分则会丢失。这种情况下一般可以使用共享指针来满足相关需求。 + +如果参数是指针,那么 EventQueue 就只会存储指针。该指针所指向的对象必须在事件处理结束之前都是可用的。 + +`enqueue` 会唤醒所有由 `wait` 或 `waitFor` 阻塞的线程。 + +该函数的时间复杂度为 O(1) 。 + +这两个重载函数略有不同,具体的用法取决于 `ArgumentPassingMode` 策略。详情请阅读https://github.com/wqking/eventpp/blob/master/doc/policies.md 文档。 + +#### process + +```cpp +bool process(); +``` + +处理事件队列。所有事件队列中的事件都会被一次性调度,并从队列中移除。 + +若有事件被处理,该函数将返回 true 。返回 false 则代表未处理任何事件。 + +在哪个线程中调用了 `process` ,所有的监听器就会在哪个线程中执行。 + +在 `process()` 执行过程中新添加进队列的事件不会在当前的 `process()` 中被调度。 + +`process()` 能高效完成单线程事件处理,其会在当前线程中处理队列中的所有事件。若想在多个线程中高效处理事件,请使用 `processOne()` 。 + +注意:若 `process()` 被同时在多个线程中调用,事件队列中的事件也将只被处理一次。 + +#### processOne + +```c++ +bool processOne(); +``` + +处理事件队列中的一个事件。该函数将会调度事件队列中的第一个事件,并将该事件移除队列。 + +若事件成功被处理,该函数返回 true ,否则返回 false 。 + +在哪个线程中调用了 `processOne()` ,监听器就会在哪个线程中执行。 + +在执行 `processOne()` 时被添加进队列的新事件将不会在当前的 `processOne()` 过程中被调度。 + +若有多个线程处理事件,`processOne()` 要比 `process()` 更高效,因为其能将事件处理分散到不同的线程中执行。但若只有一个事件处理线程,则 `process()` 更高效。 + +注意:若 `processOne()` 被同时在多个线程中调用,那么事件队列中的事件也只会被处理一次。 + +#### processIf + +```c++ +template +bool processIf(Predictor && predictor); +``` + +处理事件队列。在处理一个事件前,该事件将先被传给 `predictor` ,仅当 `predictor` 返回 true 时,该事件才会被处理。若返回 false ,则会跳过该事件继续处理后面的事件。被跳过的事件则会被继续保留在队列中。 + +`predictor` 是一个可调用对象(函数, lambda 表达式等),其接收的参数与 `EventQueue::enqueue` 接收的参数一致或不接收参数,返回值应为 bool 类型值。 eventpp 会正确地传递所有参数。若有事件被处理,该函数将返回 true 。返回 false 则代表未处理任何事件。 + +`processIf` 在下面这些场景中很有用: + +- 在特定的线程中处理特定的事件。例如在 GUI 应用中,UI 相关事件只应该在主线程中处理。则在该场景中, `predictor` 可以只对 UI 事件返回 true ,而对所有的非 UI 事件返回 false 。 + +#### processUntil + +```cpp +template +bool processUnitl(Predictor && predictor); +``` + +处理事件队列。在处理一个事件前,该事件将先被传给 `predictor` ,若其返回 true , `processUnitl` 将会立即停止事件处理并返回。若 `predictor` 返回 false ,则 `processUntil` 将继续处理事件。 + +`predictor` 是一个可调用对象(函数, lambda 表达式等),其接收的参数与 `EventQueue::enqueue` 接收的参数一致或不接收参数,返回值应为 bool 类型值。 eventpp 会正确地传递所有参数。若有事件被处理,该函数将返回 true 。返回 false 则代表未处理任何事件。 + +`processUnitl` 可通过限制事件处理时间来模拟“超时”。例如在游戏引擎中,一次事件处理时间要被限制在几毫秒之内,没处理完的事件需要留到下一个循环中进行处理。该需求就可以通过让 `predictor` 在超时的时候返回 true 来实现。 + +#### emptyQueue + +```cpp +bool emptyQueue() const; +``` + +在事件队列中没有事件时返回 true ,否则返回 false 。 + +注意:在多线程环境下,空状态可能在该函数返回后马上发生改变。 + +注意:不要用 `while(!eventQueue.emptyQueue()) {}` 的写法来写事件循环。因为编译器会内联代码,导致该循环永远检查不到空状态变化,进而造成死循环。安全的写法应该是 `while(eventQueue.waitFor(std::chrono::nanoseconds(0)));` + +#### clearEvents + +```cpp +void clearEvents(); +``` + +在不调度事件的情况下清空队列中的所有事件。 + +该函数可用于清空已排队事件中的引用(比如共享指针),以避免循环引用。 + +#### wait + +```cpp +void wait() const; +``` + +`wait` 将让当前线程持续阻塞,直至队列非空(加入了新的事件)。 + +注意:尽管 `wait` 在内部解决了假唤醒的问题,但并不能保证 ` wait` 返回后队列非空。 + +`wait` 在使用一个线程处理事件队列时很有用,用法如下: + +```cpp +for(;;) { + eventQueue.wait(); + eventQueue.process(); +} +``` + +尽管上面的代码中不带 `wait` 也能正常运行,但那样做将浪费 CPU 性能。 + +#### waitFor + +```cpp +template +bool waitFor(const std::chrono::duration & duration) const; +``` + +等待不超过 `duration` 所指定的超时时间。 + +当队列非空时返回 true ,当超时时返回 false 。 + +`waitFor` 在当事件队列处理线程需要做其他条件检查时很有用,例如: + +```cpp +std::atomic shouldStop(false); +for(;;) { + while(!eventQueue.waitFor(std::chrono::milliseconds(10) && !shouldStop.load()); + if(shouldStop.load()) { + break; + } + + eventQueue.process(); +} +``` + +#### peekEvent + +```cpp +bool peekEvent(EventQueue::QueuedEvent * queuedEvent); +``` + +从事件队列中取出一个事件。事件将在 `queuedEvent` 中返回。 + +```cpp +struct EventQueue::QueuedEvent +{ + TheEventType event; + std::tuple arguemnts; +}; +``` + +`queuedEvent` 是一个 EventQueue::QueuedEvent 结构体。`event` 是 `EventQueue::Event` ,`arguments` 是 `enqueue` 中传入的参数。 + +该函数在事件队列为空时返回 false ,事件成功取回时返回 true 。 + +在函数返回后,原事件不会被移除,而会仍然留在队列中。 + +注意:`peekEvent` 无法和不可拷贝的事件参数一起使用。若 `peekEvent` 在有不可拷贝参数时被调用,会导致编译失败。 + +#### takeEvent + +```cpp +bool takeEvent(EventQueue::QueuedEvent * queuedEvent); +``` + +从事件队列中取走一个事件,并将该事件从事件队列中移除。事件将在 `queuedEvent` 中返回。 + +该函数在事件队列为空时返回 false ,事件成功取回时返回 true 。 + +在函数返回后,原来的事件将被从事件队列中移除。 + +注意:`takeEvent` 可以和不可拷贝事件参数一起会用。 + +#### dispatch + +```cpp +void dispatch(const QueuedEvent & queuedEvent); +``` + +调度由 `peekEvent` 或 `takeEvent` 返回的事件。 + + + +### 内部类 EventQueue::DisableQueueNotify + +`EventQueue::DisableQueueNotify` 是一个 RAII 类,其用于临时防止事件队列唤醒等待的线程。当存在 `DisableQueueNotify` 对象时,调用 `enqueue` 不会唤醒任何由 `wait` 阻塞的线程。当离开 `DisableQueueNotify` 的作用域时,事件队列就重新可被唤醒了。若存在超过一个 `DisableQueueNotify` 对象,线程就只能够在所有的对象都被销毁后才能重新可被唤醒。`DisableQueueNotify` 在需要批量向事件队列中加入事件时,能够有效提升性能。例如,在游戏引擎的主循环中,可以在一帧的开始时创建 `DisableQueueNotify` ,紧接着向队列中添加一系列事件,然后在这一帧的末尾销毁 `DisableQueueNotify` ,开始处理这一帧中添加的所有事件。 + +`DisableQueueNotify` 的实例化,需要传入指向事件队列的指针。示例代码如下: + +```cpp +using EQ = eventpp::EventQueue; +EQ queue; +{ + EQ::DisableQueueNotify disableNotify(&queue); + // 任何阻塞的线程都不会被下面的两行代码唤醒 + queue.enqueue(1); + queue.enqueue(2); +} +// 任何阻塞的线程都会在此处被立即唤醒 + +// 因为这里没有 DisableQueueNotify 实例,因此任何阻塞线程都会被下面这行代码唤醒 +queue.enqueue(3); +``` + + + + + +## 内部数据结构 + +EventQueue 使用三个 `std::list` 来管理事件队列。 + +第一个忙列表( busy list )维护已入列事件的所有节点。 + +第二个等待列表( idle list )维护所有等待中的节点。在一个事件完成调度并被从队列中移除后,EventQueue 将把没有用过的节点移入等待列表,而不是直接释放相应的内存。这能够改善性能并避免产生内存碎片。 + +第三个列表是在 `process()` 函数中使用的本地临时列表( local temporary list )。在一次处理的过程中,忙列表会被交换( swap )到临时列表,所有事件都是在临时列表中被调度的。在这之后,临时列表会被返回,并追加到等待列表中。 + diff --git a/doc/cn/mixins.md b/doc/cn/mixins.md new file mode 100644 index 0000000..d4e0117 --- /dev/null +++ b/doc/cn/mixins.md @@ -0,0 +1,202 @@ +# Mixins + +## 目录 + +- [介绍](#description) +- [定义一个 mixin](#define-a-mixin) +- [将 mixin 注入到 EventDispatcher](#inject-mixins-to-eventdispatcher) +- [可选拦截点(Optional interceptor points)](#optional-interceptor-points) +- [MixinFilter](#mixin-filter) + - [公共类型](#public-types) + - [函数](#functions) + - [MixinFilter 示例代码](#sample-code-for-mixinfilter) + + + +## 介绍 + +Mixin 用于向 EventDispatcher / EventQueue 继承层次中注入代码,以扩展它们的功能。本文将以 EventDispatcher 为例进行介绍,EventQueue 的 mixin 相关用法与 EventDispatcher 完全一致。 + +原有的 EventDispatcher 继承层次如下: + +``` +EventDispatcher <- EventDispatcherBase +``` + +`EventDispatcher` 是一个空类,其继承来自 `EventDispatcherBase` 的所有函数和数据。 + +在注入两个 mixin 类 A 、 B 后,层次如下: + +``` +EventDispatcher <- MixinA <- MixinB <- EventDispatcherBase +``` + +mixin 能够使用所有 EventDispatcherBase 中的所有 public 和 protected 成员(类型、函数和数据)。mixin 中的所有公共成员对用户来说都是可见、可用的。 + + + +## 定义一个 mixin + +一个 mixin 是有着一个模板参数的模板类。mixin 必须继承其模板参数。一个典型的 mixin 如下: + +```cpp +template +class MyMixin : public Base +{ +}; +``` + + + +## 将 mixin 注入到 EventDispatcher + +想要启用 mixin ,需要将 mixin 加入到策略类的 `Mixins` 类型中。例如,要启动 `MixinFilter` ,可以像下面这样来定义调度器 + +```cpp +struct MyPolicies { + using Mixins = eventpp::MixinList; +}; +eventpp::EventDispatcher dispatcher; +``` + +若有多个 mixin ,像是 `using Mixins = eventpp::MixinList` ,则继承层次如下: + +``` +EventDispatcher <- MixinA <- MixinB <- MixinC <- EventDispatcherBase +``` + +最前面的排在继承层次的最底部 + + + +## 可选拦截点( Optional interceptor points ) + +一个 mixin 可以有一些具有特殊名称的函数,这些函数会被在特定的时候调用。这些特殊的函数必须是 public 的。当前只有一个特殊函数,如下: + +```cpp +template +bool mixinBeforeDispatch(Args && ...args) const; +``` + +`mixinBeforeDispatch` 会在 EventDispatcher、EventQueue 开始调度事件之前被调用,其接收所有传给 EventDispatcher::dispatch 的参数,除了所有的参数都是以左值引用的方式传递的,无论它们在回调函数原型中是否被引用(无法修改 const 引用)。所以该函数能够修改实参,让后续的监听器都看到修改后的值。 +因此该函数可以修改实参,所有的监听器看到的参数都是修改后的值。 +该函数返回 `true` 时将继续调度,返回 `false` 时会停止继续调度。 +对于多 mixin 的情况,该函数会被按照这些 mixin 出现在策略类的 MixinList 中的顺序来执行。 + + + +## MixinFilter + +MixinFilter 可以在调度开始前过滤或修改所有的事件。 + +`MixinFilter::appendFilter(filter)` 向调度器中添加新的事件过滤器。`filter` 接收参数的类型是带有左值引用类型的回调函数原型。 + +所有的事件都会在执行任何监听器前触发事件过滤器的执行。 + +因为所有的实参都是以左值引用的形式传递的,因此事件过滤器也能修改这些实参,无论这些实参是否被在回调原型中引用(当然,const 引用是改不了的)。 + +下面的表格展示了事件过滤器如何接收实参 + +| 回调原型的实参类型 | 过滤器收到的实参类型 | 过滤器是否能修改实参? | 备注 | +| ------------------------ | -------------------------- | ---------------------- | ------------------------- | +| int, const int | int &, int & | 是 | 抛弃 const 值的不变性 | +| int &, std::string & | int &, std::string & | 是 | | +| const int &, const int * | const int &, const int * & | 否 | 必须保持引用/指针的不变性 | + +事件过滤器强大且实用,下面是一些用例示例 + +1. 捕捉并阻塞所有感兴趣的事件。例如,在一个 GUI 窗口系统中,所有的窗口都能接收到鼠标的点击事件。然而,当一个窗口正在被鼠标拖拽时,只有被拖拽的窗口才应该能接收到鼠标事件,即使鼠标正在其他窗口上移动也应如此。所以当开始拖动时,窗口可以添加一个过滤器,该过滤器重定向所有发到窗口的鼠标事件,阻止其他窗口的监听器获得鼠标事件,但扔放行鼠标事件外的所有其他事件。 +2. 设置捕捉所有事件的监听器。例如,在一个电话本系统中,系统根据动作(action)来发送事件(删除添加电话号、查找电话号等)。如果想要在该系统中实现这样的一个模块:其只需要取电话号的区号,而不关心具体发生了的动作,该怎么做呢?一种方法是让该模块可以监听所有的事件(添加、删除、查找),但这种方法比较脆弱——若添加了一种新的动作事件,而忘记了为该模块定义相应的监听逻辑,就会导致出现未定义的行为。更好的方法是为该模块添加一个过滤器,并在过滤器中检查区号。 + + + +### 公共类型 + +`FilterHandle`:appendFilter 返回的句柄类型。过滤器句柄可用于移除过滤器。可以通过将 FilterHandle 实例转换为 bool 类型来检查其是否为空,当值为 false 时,该句柄为空。`FilterHandle` 是可拷贝的。 + + + +### 函数 + +```cpp +FilterHandle appendFilter(const Filter & filter); +``` + +将 `filter` 添加进调度器。 + +该函数将返回一个可用于 `removeFilter` 的过滤器句柄 + +```cpp +bool removeFilter(const FilterHandle & filterHandle); +``` + +从调度其中移除一个过滤器。 + +当过滤器被成功移除时返回 true。 + + + +### MixinFilter 示例代码 + +#### 代码 + +```cpp +struct MyPolicies { + using Mixins = eventpp::MixinList; +}; +eventpp::EventDispatcher dispatcher; + +dispatcher.appendListener(3, [](const int e, const int i, const std::string & s) { + std::cout + << "Got event 3, i was 1 but actual is " << i + << " s was Hello but actual is " << s + << std::endl + ; +}); +dispatcher.appendListener(5, [](const int e, const int i, const std::string & s) { + std::cout << "Shout not got event 5" << std::endl; +}); + +// 添加三个事件过滤器. + +// 第一个过滤器将输入的实参改为其他值,然后后续的过滤器及监听器将看到修改后的值 +dispatcher.appendFilter([](const int e, int & i, std::string & s) -> bool { + std::cout << "Filter 1, e is " << e << " passed in i is " << i << " s is " << s << std::endl; + i = 38; + s = "Hi"; + std::cout << "Filter 1, changed i is " << i << " s is " << s << std::endl; + return true; +}); + +// 第二个过滤器将所有 5 事件都过滤出来。因此监听事件 5 的过滤器都不会被触发。 +// 第三个过滤器也不会在事件 5 触发 +dispatcher.appendFilter([](const int e, int & i, std::string & s) -> bool { + std::cout << "Filter 2, e is " << e << " passed in i is " << i << " s is " << s << std::endl; + if(e == 5) { + return false; + } + return true; +}); + +// 第三个过滤器只打印输入的实参 +dispatcher.appendFilter([](const int e, int & i, std::string & s) -> bool { + std::cout << "Filter 3, e is " << e << " passed in i is " << i << " s is " << s << std::endl; + return true; +}); + +// 调度所有事件,第一个实参总是事件类型 +dispatcher.dispatch(3, 1, "Hello"); +dispatcher.dispatch(5, 2, "World"); +``` + +输出 + +> Filter 1, e is 3 passed in i is 1 s is Hello +> Filter 1, changed i is 38 s is Hi +> Filter 2, e is 3 passed in i is 38 s is Hi +> Filter 3, e is 3 passed in i is 38 s is Hi +> Got event 3, i was 1 but actual is 38 s was Hello but actual is Hi +> Filter 1, e is 5 passed in i is 2 s is World +> Filter 1, changed i is 38 s is Hi +> Filter 2, e is 5 passed in i is 38 s is Hi + diff --git a/doc/cn/policies.md b/doc/cn/policies.md new file mode 100644 index 0000000..34a5df4 --- /dev/null +++ b/doc/cn/policies.md @@ -0,0 +1,423 @@ +# 策略(Policies) + +## 目录 + +- [介绍](#introduction) +- [策略](#policies) + - [getEvent 函数](#getevent) + - [canContinueInvoking 函数](#cancontinueinvoking) + - [Mixins 类型](#mixins) + - [Callback 类型](#callback) + - [Threading 类型](#threading) + - [ArgumentPassingMode 类型](#argumentpassingmode) + - [Map 模板](#map) + - [QueueList 模板](#queuelist) +- [如何使用策略](#how-to-use-policies) + + + +## 介绍 + +eventpp 使用了基于策略的设计来配置和扩展各个组件的行为。EventDispatcher、EventQueue、CallbackList 的最后一个模板参数就是策略类。这三个类都有名为 `DefaultPolicies` 的默认策略类。 + +一项策略可以是策略类中的一个类型或一个静态成员函数。所有策略都必须是 public 的,所以通常可以用 `struct` 来定义策略类。 + +所有策略都是可选的。若省略了一个策略,那么这项策略就将使用其默认值。实际上,`DefaultPolicies` 本身就是一个空结构体。 + +EventDispatcher、EventQueue、CallbackList 这三个类使用了相同的策略机制,尽管不是所有的类都需要相同的策略。 + + + +## 策略 + + + +### 函数: getEvent + +**原型**:`static EventKey getEvent(const Args &...)` 。该函数接收与 `EventDispatcher::dispatch` 和 `EventQueue::enqueue` 相同的参数,且必须返回一个事件类型。 + +**默认值**:默认实现是返回 `getEvent` 的第一个实参。 + +**适用于**:EventDispatcher, EventQueue + +evetpp 将 `EventDispatcher::dispatch` 和 `EventQueue::enqueue` 的所有实参(这两个函数的参数相同)都转发给 `getEvent` 以获取事件类型,然后再触发执行该事件对应的回调函数列表。 + +`getEvnet` 既可以是模板函数,也可以是非模板函数。只要 `getEvent` 能够使用与 `EventDispatcher::dispatch` 和 `EventQueue::enqueue` 相同的参数调用即可。 + +示例代码如下: + +```cpp +// 定义用于保存所有参数的事件结构 +struct MyEvent { + int type; + std::string message; + int param; +}; + +// 为调度器定义如何分解事件类型的策略 +struct MyEventPolicies +{ + static int getEvent(const MyEvent & e, bool /*b*/) { + return e.type; + } +}; + +// 将 MyEventPolicies 作为 EventDispatcher 的第三个模板参数 +// 注意:第一个模板参数是整型的事件类型,而非 MyEvent。 +eventpp::EventDispatcher< + int, + void (const MyEvent &, bool), + MyEventPolicies +> dispatcher; + +// 添加一个监听器。 +// 注意:第一个参数是整型的事件类型,而非 MyEvent。 +dispatcher.appendListener(3, [](const MyEvent & e, bool b) { + std::cout + << std::boolalpha + << "Got event 3" << std::endl + << "Event::type is " << e.type << std::endl + << "Event::message is " << e.message << std::endl + << "Event::param is " << e.param << std::endl + << "b is " << b << std::endl + ; +}); + +// 调度事件。第一个参数是 Event +dispatcher.dispatch(MyEvent { 3, "Hello world", 38 }, true); +``` + + + +### 函数: canContinueInvoking + +**原型**:`static bool canContinueInvoking(const Args &...)` 。该函数接收与 `EventDispatcher::dispatch` 和 `EventQueue::enqueue` 相同的参数,且必须在事件调度或回调列表调用可以继续进行的时候返回 true ,在调用需要停止的时候返回 false 。 + +**默认值**:默认实现总是返回 true 。 + +**适用于**:CallbackList, EventDispatcher, EventQueue + +示例代码如下: + +```cpp +struct MyEvent { + MyEvent() : type(0), canceled(false) { + } + explicit MyEvent(const int type) + : type(type), canceled(false) { + } + + int type; + mutable bool canceled; +}; + +struct MyEventPolicies +{ + static int getEvent(const MyEvent & e) { + return e.type; + } + + static bool canContinueInvoking(const MyEvent & e) { + return ! e.canceled; + } +}; + +eventpp::EventDispatcher dispatcher; + +dispatcher.appendListener(3, [](const MyEvent & e) { + std::cout << "Got event 3" << std::endl; + e.canceled = true; +}); +dispatcher.appendListener(3, [](const MyEvent & e) { + std::cout << "Should not get this event 3" << std::endl; +}); + +dispatcher.dispatch(MyEvent(3)); +``` + + + +### 类型: Mixins + +**默认值**:`using Mixins = eventpp::MixinList<>` 。未启用任何 mixin 。 + +**适用于**:CallbackList, EventDispatcher, EventQueue + +Mixin 用于向 EventDispatcher / EventQueue 继承层次中注入代码,以扩展它们的功能。更多细节请参阅 https://github.com/wqking/eventpp/blob/master/doc/mixins.md + + + +### 类型: Callback + +**默认值**:`using Callback = std::function` 。 + +**适用于**:CallbackList, EventDispatcher, EventQueue + +`Callback` 是用于在底层维护回调函数。默认是 `std::function` + + + +### 类型: Threading + +**默认值**:`using Threading = eventpp::MultipleThreading` 。 + +**适用于**:CallbackList, EventDispatcher, EventQueue, HeterCallbackList, HeterEventDispatcher, HeterEventQueue. + +`Threading` 控制线程执行模型。默认是“多线程”( Multiple Threading )。可取值: + +- `MultipleThreading` : 使用 mutex 保护核心数据。该选项为默认值。 +- `SingleThreading` : 不保护核心数据,且这些数据无法从其他线程访问 + +一个典型的 `Threading` 类型如下: + +```cpp +struct MultipleThreading +{ + using Mutex = std::mutex; + + template + using Atomic = std::atomic; + + using ConditionVariable = std::condition_variable; +}; +``` + +对于 `SingleThreading` 而言,所有的 `Mutex` 、 `Atomic` 和 `ConditionVariable` 类型都是不会起任何作用的假类型。 + +对于多线程而言,默认的 `Mutex` 是 `std::mutex` 。 eventpp 也提供了一个使用自旋锁作为互斥量的 `SpinLock` 类。 + +当只有较少的线程时(和 CPU 核心数差不多的线程数),`eventpp::SpinLock` 的性能比 `std::mutex` 更高一些。当线程数超过 CPU 核心数时, `eventpp::SpinLock` 的性能弱于 `std::mutex` 。 + +基准测试相关数据请参阅:https://github.com/wqking/eventpp/blob/master/doc/benchmark.md + +下面是使用 `SpinLock` 的示例代码: + +```cpp +struct MultipleThreadingSpinLock +{ + using Mutex = eventpp::SpinLock; + + template + using Atomic = std::atomic; + + using ConditionVariable = std::condition_variable; +}; +struct MyEventPolicies { + using Threading = MultipleThreadingSpinLock; +}; +eventpp::EventDispatcher dispatcher; +eventpp::CallbackList callbackList; +``` + +`eventpp` 还提供了一个简化易用的自定义线程管理的模板类。 + +```cpp +template < + typename Mutex_, + template class Atomic_ = sd::atomic, + typename ConditionVariable_ = std::condition_variable +> +struct GeneralThreading +{ + using Mutex = Mutex_; + + template + using Atomic = Atomic_; + + using ConditionVariable = ConditionVariable_; +}; +``` + +因此前面自旋锁的示例代码可以重写为 + +```cpp +struct MyEventPolicies { + using Threading = eventpp::GeneralThreading; +}; +eventpp::EventDispatcher dispatcher; +eventpp::CallbackList callbackList; +``` + + + +### 类型: ArgumentPassingMode + +**默认值**:`using ArgumentPassingMode = ArgumentPassingAutoDetect` 。 + +**适用于**:EventDispatcher, EventQueue + +`ArgumentPassingMode` 是实参传递的模式。默认值是 `ArgumentPassingAutoDetect` 。 + +示例代码如下。假设我们有一个调度器 + +```cpp +eventpp::EventDispatcher dispatcher; +``` + +事件类型是 `int` 。 + +监听器的第一个参数也是 `int` 。根据具体的事件调度方式,监听器的第一个参数可以是事件类型,也可以是一个额外的参数。 + +```cpp +dispatcher.dispatch(3, "hello"); +``` + +事件 3 会被使用一个参数 "hello" 调度,监听器将会被使用参数 `(3, "hello")` 触发执行,第一个参数是事件类型。 + +```cpp +dispatcher.dispatch(3, 8, "hello"); +``` + +事件 3 会被使用两个参数 8 和 "hello" 调度,监听器将会被使用参数 `(8, "hello")` 触发执行,第一个参数就是额外参数,此时的事件类型参数将被忽略。 + +因此,在默认情况下,EventDispatcher 会自动监测 `dispatch` 的参数数量和监听器原型,以决定是否使用事件类型来调用监听器。 + +默认规则简便、宽松但容易出错。可以通过 `ArgumentPassingMode` 策略控制具体的行为 + +```cpp +struct ArgumentPassingAutoDetect; +struct ArgumentPassingIncludeEvent; +struct ArgumentPassingExcludeEvent; +``` + +`ArgumentPassingAutoDetect`:默认策略。自动检查是否要传递事件类型。 + +`ArgumentPassingIncludeEvent`:总是传递事件类型。参数数量不符会导致编译失败。 + +`ArgumentPassingExcludeEvent`:总是忽略且不会传递事件类型。参数数量不符会导致编译失败。 + +假设监听器原型有 P 个参数,`dispatch` 中的参数数量(包括事件类型在内)为 D ,则 P 和 D 的关系为: + +对于 `ArgumentPassingAutoDetect`:P == D 或 P + 1 == D + +对于 `ArgumentPassingIncludeEvent`:P == D + +对于 `ArgumentPassingExcludeEvent`: P + 1 == D + +**注意**:同样的规则也适用于 `EventQueue::enqueue` ,因为 `enqueue` 的参数与 `dispatch` 相同。 + +参数传递模式的示例代码如下: + +```cpp +struct MyPolicies +{ + using ArgumentPassingMode = ArgumentPassingAutoDetect; +}; +eventpp::EventDispatcher< + int, + void(int, const std::string &), + MyPolicies +> dispatcher; +// 或用下面的简便写法 +//eventpp::EventDispatcher dispatcher; +dispatcher.dispatch(3, "hello"); // 编译通过 +dispatcher.dispatch(3, 8, "hello"); // 编译通过 +``` + +```cpp +struct MyPolicies +{ + using ArgumentPassingMode = ArgumentPassingIncludeEvent; +}; +eventpp::EventDispatcher< + int, + void(int, const std::string &), + MyPolicies +> dispatcher; +dispatcher.dispatch(3, "hello"); // 编译通过 +//dispatcher.dispatch(3, 8, "hello"); // 编译失败 +``` + +```cpp +struct MyPolicies +{ + using ArgumentPassingMode = ArgumentPassingExcludeEvent; +}; +eventpp::EventDispatcher< + int, + void(int, const std::string &), + MyPolicies +> dispatcher; +//dispatcher.dispatch(3, "hello"); // 编译失败 +dispatcher.dispatch(3, 8, "hello"); // 编译通过 +``` + + + +### 模板: Map + +**原型**: + +```cpp +template +using Map = // std::map 或其他 map 类型 +``` + +**默认值**:自动确定 + +**应用于**:EventDispatcher, EventQueue + +`Map` 是 EventDispatcher 和 EventQueue 用于维护底层键值对(事件类型,CallbackList)的关联容器类型。 + +`Map` 是有两个参数的模板,两个参数分别是键和值。 + +`Map` 必须能够支持 `[]`、`find()`、`end()` 操作。 + +若没有指定 `Map` ,eventpp 会自动确定类型。若事件类型支持 `std::hash` 会使用 `std::unordered_map` ,否则会使用 `std::map` + + + +### 模板: QueueList + +**原型**: + +```cpp +template +using QueueList = std::list; +``` + +**默认值**:`std::list` + +**应用于**:EventQueue + +`QueueList` 用于管理 EventQueue 内部的事件,作为队列使用。事件会被追加到 `QueueList` 的末尾,当被处理时,事件会从 `QueueList` 的头部弹出。 + +使用不同的 `QueueList` 能够更好地控制队列。例如,若 `QueueList` 能够管理事件的顺序,那么队列中的事件就能以不同于加入顺序的新顺序被处理。 + +一个 `QueueList` 不需要实现 `std::list` 的所有成员,其只需要实现下面的类型和函数即可: + +```cpp +type iterator; +type const_iterator; +bool empty() const; +iterator begin(); +const_iterator begin() const; +iterator end(); +const_iterator end() const; +const_reference front() const; +void swap(QueueList & other); +void emplace_back(); +void splice(const_iterator pos, QueueList & other ); +void splice(const_iterator pos, QueueList & other, const_iterator it); +``` + +eventpp 中的有序队列列表 OrderedQueueList 就是一个应用实例。详细内容请阅 https://github.com/wqking/eventpp/blob/master/doc/orderedqueuelist.md + + + +## 如何使用策略 + +想要使用策略,只需要声明一个结构体,在其中定义策略然后传递给 CallbackList, EventDispatcher 或 EventQueue 即可。 + +```cpp +struct MyPolicies // 结构体的名字并不重要 +{ + template + static int getEvent(const MyEvent & e, const Args &...) { + return e.type; + } +}; +EventDispatcher dispatcher; +``` + +上面的示例代码展示了一个重定义了 `getEvent` 策略类,除该策略外的其他策略都保持默认值。 \ No newline at end of file diff --git a/doc/cn/readme.md b/doc/cn/readme.md index 0cf8582..6f57c7e 100644 --- a/doc/cn/readme.md +++ b/doc/cn/readme.md @@ -10,3 +10,8 @@ * [CallbackList 教程](tutorial_callbacklist.md) * [EventDispatcher 教程](tutorial_eventdispatcher.md) * [EventQueue 教程](tutorial_eventqueue.md) + * [CallbackList 类参考手册](callbacklist.md) + * [EventDispatcher 类参考手册](eventdispatcher.md) + * [EventQueue 类参考手册](eventqueue.md) + * [Policies -- 配置 eventpp](policies.md) + * [Mixins -- 扩展 eventpp](mixins.md) From d5642cf525b2e6336976a5895b046a147bd69dfc Mon Sep 17 00:00:00 2001 From: marsCatXDU Date: Fri, 11 Nov 2022 10:52:17 +0800 Subject: [PATCH 2/2] Correct formatting mistakes Signed-off-by: marsCatXDU --- doc/cn/callbacklist.md | 46 +++++++++++++++--------------- doc/cn/readme.md | 1 + doc/cn/tutorial_callbacklist.md | 26 ++++++++--------- doc/cn/tutorial_eventdispatcher.md | 28 +++++++++--------- doc/cn/tutorial_eventqueue.md | 30 +++++++++---------- 5 files changed, 66 insertions(+), 65 deletions(-) diff --git a/doc/cn/callbacklist.md b/doc/cn/callbacklist.md index 7dd9bfa..d2dcd6d 100644 --- a/doc/cn/callbacklist.md +++ b/doc/cn/callbacklist.md @@ -18,7 +18,7 @@ CallbackList 是 eventpp 中最为核心、基础的类。EventDispatcher、EventQueue 都是以 CallbackList 类为基础开发的。 -CallbackList 内部维护一个回调函数的列表。在 CallbackList 被调用时,其会逐个调用列表中的所有回调函数。可以将 CallbackList 看做 Qt 中的信号/槽系统,或某些 Windows API 中的回调函数指针(例如 `ReadFileEx` 中的 IpCompletionRoutine )。 +CallbackList 内部维护一个回调函数的列表。在 CallbackList 被调用时,其会逐个调用列表中的所有回调函数。可以将 CallbackList 看做 Qt 中的信号/槽系统,或某些 Windows API 中的回调函数指针(例如 `ReadFileEx` 中的 IpCompletionRoutine )。 回调函数可以是任何回调目标 —— 函数、函数指针、指向成员函数的指针、lambda 表达式、函数对象等。 @@ -41,7 +41,7 @@ template < class CallbackList; ``` -`Prototype`:回调函数原型。该参数应为 C++ 函数类型,如 `void(int, std::string, const MyClass *)`。 +`Prototype`:回调函数原型。该参数应为 C++ 函数类型,如 `void(int, std::string, const MyClass *)`。 `Policies`:用于配置和扩展回调函数列表的规则。默认值为 `DefaultPolicies` 。详见 [policy 文档](https://github.com/marsCatXdu/eventpp/blob/master/doc/policies.md) @@ -71,7 +71,7 @@ CallbackList 可以被拷贝、move、赋值、move 赋值和交换。 bool empty() const; ``` -当回调列表为空时返回 true 。 +当回调列表为空时返回 true 。 提示:在多线程程序中,该函数的返回值无法保证确定就是列表的真实状态。可能会出现刚刚返回 true 之后列表马上变为非空的情况,反之亦然。 #### bool 转换运算符 @@ -80,7 +80,7 @@ bool empty() const; operator bool() const; ``` -若回调列表非空则返回 true。 +若回调列表非空则返回 true。 借助该运算符,能够实现在条件语句中使用 CallbackList 实例 #### append @@ -89,10 +89,10 @@ operator bool() const; Handle append(const Callback & callback); ``` -向回调函数列表中添加回调函数。 -新的回调函数会被加在回调函数列表的末尾。 -该函数返回一个代表回调函数的句柄。该句柄能够用于移除该回调函数,或在该回调函数前插入其他的回调函数。 -如果`append`在回调函数列表执行的过程中被其他的回调函数调用,则新添加的回调函数一定不会在该回调函数列表执行的过程中被执行。 +向回调函数列表中添加回调函数。 +新的回调函数会被加在回调函数列表的末尾。 +该函数返回一个代表回调函数的句柄。该句柄能够用于移除该回调函数,或在该回调函数前插入其他的回调函数。 +如果`append`在回调函数列表执行的过程中被其他的回调函数调用,则新添加的回调函数一定不会在该回调函数列表执行的过程中被执行。 该函数的时间复杂度为 O(1) 。 #### prepend @@ -101,10 +101,10 @@ Handle append(const Callback & callback); Handle prepend(const Callback & callback); ``` -向回调函数列表中添加回调函数。 -回调函数将被加在回调函数列表的最前端。 +向回调函数列表中添加回调函数。 +回调函数将被加在回调函数列表的最前端。 该函数会返回一个代表回调函数的句柄(handler)。该句柄可被用于移除该回调函数,也可用于在该回调函数前插入其他回调函数。 -如果 `prepend` 在回调函数列表执行的过程中被其他回调函数调用,则新添加的回调函数一定不会在该回调函数列表执行的过程中被执行。 +如果 `prepend` 在回调函数列表执行的过程中被其他回调函数调用,则新添加的回调函数一定不会在该回调函数列表执行的过程中被执行。 该函数的时间复杂度为 O(1) 。 #### insert @@ -113,12 +113,12 @@ Handle prepend(const Callback & callback); Handle insert(const Callback & callback, const Handle & before); ``` -将 *callback* 插入到回调列表中 *before* 前面的一个位置处。若找不到 *before* ,则 *callback* 会被加到回调列表的末尾。 -该函数返回一个代表回调函数的句柄。该句柄可被用于移除该回调函数,也可用于在该回调函数前插入其他回调函数。 -如果 `insert` 是在回调函数列表执行的过程中被其他回调函数调用的,则新添加的回调函数一定不会在该回调函数列表执行的过程中被执行。 +将 *callback* 插入到回调列表中 *before* 前面的一个位置处。若找不到 *before* ,则 *callback* 会被加到回调列表的末尾。 +该函数返回一个代表回调函数的句柄。该句柄可被用于移除该回调函数,也可用于在该回调函数前插入其他回调函数。 +如果 `insert` 是在回调函数列表执行的过程中被其他回调函数调用的,则新添加的回调函数一定不会在该回调函数列表执行的过程中被执行。 该函数的时间复杂度为 O(1) 。 -提示:该函数的调用者必须提前确定 `before` 是由 `this` 所指向的 CallbackList 调用的。如果不能确定,可以用 `ownsHandle` 函数来检查 `before` 是否属于 `this` CallbackList 。`insert` 函数仅能在 `ownsHandle(before)` 返回 true 的时候被调用,否则可能引发未定义的行为并带来诡异的 bug 。 +提示:该函数的调用者必须提前确定 `before` 是由 `this` 所指向的 CallbackList 调用的。如果不能确定,可以用 `ownsHandle` 函数来检查 `before` 是否属于 `this` CallbackList 。`insert` 函数仅能在 `ownsHandle(before)` 返回 true 的时候被调用,否则可能引发未定义的行为并带来诡异的 bug 。 需要确保只在 `assert(ownsHandle(before))` 时 `insert` ,但出于性能方面的考量,发布的代码中没有包含相关的检查。 #### remove @@ -127,8 +127,8 @@ Handle insert(const Callback & callback, const Handle & before); bool remove(const Handle & handle); ``` -从回调函数列表中移除 *handle* 指向的回调函数。 -移除成功会返回 true ,找不到回调函数则会返回 false 。 +从回调函数列表中移除 *handle* 指向的回调函数。 +移除成功会返回 true ,找不到回调函数则会返回 false 。 该函数的时间复杂度为 O(1) 提示:`handle` 必须是由 `this` 指向的 CallbackList 所创建的。更多细节请查看 `insert` 中的“提示”部分 @@ -139,7 +139,7 @@ bool remove(const Handle & handle); bool ownsHandle(const Handle & handle) const; ``` -当 `handle` 是由当前 CallbackList 创建时返回 true,否则返回 false 。 +当 `handle` 是由当前 CallbackList 创建时返回 true,否则返回 false 。 该函数时间复杂度为 O(N) #### forEach @@ -149,7 +149,7 @@ template void forEach(Func && func) const; ``` -对所有的回调函数使用 `func` 。 +对所有的回调函数使用 `func` 。 `func` 可以是下面两种原型的其中一种: ```C++ @@ -166,7 +166,7 @@ template bool forEachIf(Func && func) const; ``` -对所有的回调函数使用 `func` 。 `func` 必须返回一个 bool 值,若返回值为 false ,则 forEachIf 将会立即停止循环。 +对所有的回调函数使用 `func` 。 `func` 必须返回一个 bool 值,若返回值为 false ,则 forEachIf 将会立即停止循环。 当所有回调函数都被触发后,或未找到 `event` 时,该函数会返回 true 。当 `func` 返回 `false` 时返回 `false` #### 调用运算符 @@ -175,8 +175,8 @@ bool forEachIf(Func && func) const; void operator() (Args ...args) const; ``` -触发回调函数列表中所有回调函数的运行。 -回调函数会被用 `args` 参数作为参数调用。 +触发回调函数列表中所有回调函数的运行。 +回调函数会被用 `args` 参数作为参数调用。 回调函数会在 `operator()` 所在的线程中调用。 @@ -198,6 +198,6 @@ void operator() (Args ...args) const; ## 内部数据结构 -CallbackList 使用双向链表管理回调函数。 +CallbackList 使用双向链表管理回调函数。 每个节点都使用共享指针(shared pointer)连接。使用共享指针可以实现在迭代的过程中移除节点。 diff --git a/doc/cn/readme.md b/doc/cn/readme.md index 6f57c7e..513d44c 100644 --- a/doc/cn/readme.md +++ b/doc/cn/readme.md @@ -15,3 +15,4 @@ * [EventQueue 类参考手册](eventqueue.md) * [Policies -- 配置 eventpp](policies.md) * [Mixins -- 扩展 eventpp](mixins.md) + diff --git a/doc/cn/tutorial_callbacklist.md b/doc/cn/tutorial_callbacklist.md index 58ed865..10c88bc 100644 --- a/doc/cn/tutorial_callbacklist.md +++ b/doc/cn/tutorial_callbacklist.md @@ -26,7 +26,7 @@ callbackList(); **输出** -> Got callback 1. +> Got callback 1. > Got callback 2. **解读** @@ -37,7 +37,7 @@ callbackList(); eventpp::CallbackList callbackList; ``` -CallbackList 需要至少一个模板参数,作为回调函数的“原型”( prototype )。 +CallbackList 需要至少一个模板参数,作为回调函数的“原型”( prototype )。 “原型”指 C++ 函数类型,例如 `void (int)`, `void (const std::string &, const MyClass &, int, bool)` 然后,添加一个回调函数 @@ -48,7 +48,7 @@ callbackList.append([]() { }); ``` -`append` 函数接收一个回调函数作为参数。 +`append` 函数接收一个回调函数作为参数。 回调函数可以使任何回调目标——函数、函数指针、指向成员函数的指针、lambda 表达式、函数对象等。该回调函数必须可以使用 `callbackList` 中声明的原型调用。 接下来启动回调列表 @@ -82,12 +82,12 @@ callbackList("Hello world", true); **输出** -> Got callback 1, s is Hello world b is true +> Got callback 1, s is Hello world b is true > Got callback 2, s is Hello world b is 1 **解读** -本例中,回调函数列表的回调函数原型接收两个参数: `const std::string &` 和 `const bool`。 +本例中,回调函数列表的回调函数原型接收两个参数: `const std::string &` 和 `const bool`。 回调函数的原型并不需要和回调完全一致,只要两个函数中的参数能够兼容即可。正如上面例子中的第二个回调函数,其参数为 `[](std::string s, int b)`,其原型与回调列表中的并不相同。 ### CallbackList 教程 3, 移除 @@ -119,7 +119,7 @@ callbackList(); **输出** -> Got callback 1. +> Got callback 1. > Got callback 3. ### CallbackList 教程 4, for each @@ -165,11 +165,11 @@ callbackList(); **输出** -> forEach(Handle, Callback), invoked 0 -> forEach(Handle, Callback), invoked 1 -> forEach(Handle, Callback), removed second callback -> forEach(Handle, Callback), invoked 2 -> forEach(Callback), invoked -> forEach(Callback), invoked -> Got callback 1. +> forEach(Handle, Callback), invoked 0 +> forEach(Handle, Callback), invoked 1 +> forEach(Handle, Callback), removed second callback +> forEach(Handle, Callback), invoked 2 +> forEach(Callback), invoked +> forEach(Callback), invoked +> Got callback 1. > Got callback 3. \ No newline at end of file diff --git a/doc/cn/tutorial_eventdispatcher.md b/doc/cn/tutorial_eventdispatcher.md index f8686dd..0e26b4f 100644 --- a/doc/cn/tutorial_eventdispatcher.md +++ b/doc/cn/tutorial_eventdispatcher.md @@ -32,8 +32,8 @@ dispatcher.dispatch(5); **输出** -> Got event 3. -> Got event 5. +> Got event 3. +> Got event 5. > Got another event 5. **解读** @@ -44,8 +44,8 @@ dispatcher.dispatch(5); eventpp::EventDispatcher dispatcher; ``` -EventDispatcher 类接收两个模板参数。第一个是*事件类型*,此处是 `int` 。第二个是监听器的*原型*。 -*事件类型* 必须能够用作 `std::map` 的 key。也就是说该类型必须支持 `operator <`。 +EventDispatcher 类接收两个模板参数。第一个是*事件类型*,此处是 `int` 。第二个是监听器的*原型*。 +*事件类型* 必须能够用作 `std::map` 的 key。也就是说该类型必须支持 `operator <`。 *原型* 是 C++ 函数类型,例如 `void (int)`, `void (const std::string &, const MyClass &, int, bool)` 然后添加一个监听器 @@ -56,8 +56,8 @@ dispatcher.appendListener(3, []() { }); ``` -`appendListener` 函数接收两个参数。第一个是 *事件类型* 的 *事件* (译注:此处的“事件类型”指的是用于区分事件的数据类型,此处为 int 。“事件”则是具体的时间值,此处为整数 3 ),此处为 `int` 类型。第二个参数是*回调函数*。 -回调函数可以是任何能够回调的目标——函数、函数指针、成员函数指针、lambda表达式、函数对象等。其必须能够被 `dispatcher` 中声明的 *原型* 调用。 +`appendListener` 函数接收两个参数。第一个是 *事件类型* 的 *事件* (译注:此处的“事件类型”指的是用于区分事件的数据类型,此处为 int 。“事件”则是具体的时间值,此处为整数 3 ),此处为 `int` 类型。第二个参数是*回调函数*。 +回调函数可以是任何能够回调的目标——函数、函数指针、成员函数指针、lambda表达式、函数对象等。其必须能够被 `dispatcher` 中声明的 *原型* 调用。 在上面这段代码的下面,我们还为 事件5 添加了两个监听器。 接下来,使用下面的代码分发事件 @@ -67,7 +67,7 @@ dispatcher.dispatch(3); dispatcher.dispatch(5); ``` -这里分发了两个事件,分别是事件 3 和 5 。 +这里分发了两个事件,分别是事件 3 和 5 。 在事件分发的过程中,所有对应事件的监听器都会按照它们被添加进 EventDispatcher 的顺序逐个执行。 ### 教程 2 —— 带参数的监听器 @@ -96,13 +96,13 @@ dispatcher.dispatch(5, "World", false); **输出** -> Got event 3, s is Hello b is true -> Got event 5, s is World b is 0 +> Got event 3, s is Hello b is true +> Got event 5, s is World b is 0 > Got another event 5, s is World b is false **解读** -此处的 dispatcher 回调函数原型接收两个参数:`const std::string &` 和 `const bool`。 +此处的 dispatcher 回调函数原型接收两个参数:`const std::string &` 和 `const bool`。 监听器原型不需要和 dispatcher 完全一致,只要参数类型能够兼容即可。例如第二个监听器,`[](std::string s, int b)`,其原型和 dispatcher 并不相同 ### 教程 3 —— 自定义事件结构 @@ -151,10 +151,10 @@ dispatcher.dispatch(MyEvent { 3, "Hello world", 38 }, true); **输出** -> Got event 3 -> Event::type is 3 -> Event::message is Hello world -> Event::param is 38 +> Got event 3 +> Event::type is 3 +> Event::message is Hello world +> Event::param is 38 > b is true **解读** diff --git a/doc/cn/tutorial_eventqueue.md b/doc/cn/tutorial_eventqueue.md index ca3091e..de33faf 100644 --- a/doc/cn/tutorial_eventqueue.md +++ b/doc/cn/tutorial_eventqueue.md @@ -31,15 +31,15 @@ queue.process(); **输出** -> Got event 3, s is Hello n is 38 -> Got event 5, s is World n is 58 -> Got another event 5, s is World n is 58 +> Got event 3, s is Hello n is 38 +> Got event 5, s is World n is 58 +> Got another event 5, s is World n is 58 **解读** -`EventDispatcher<>::dispatch()` 触发监听器的动作是同步的。但异步事件队列在某些场景下能发挥更大的作用(例如 Windows 消息队列、游戏中的消息队列等)。EventQueue 就是用于满足该类需求的事件队列。 -`EventQueue<>::enqueue()` 将事件加入队列,其参数和 `dispatch` 的参数完全相同。 -`EventQueue<>::process()` 用于分发队列中的事件。不调用 process ,事件就不会被分发。 -事件队列的典型用例:在 GUI 应用中,每个组件都调用 `EventQueue<>::enqueue()` 来发布事件,然后主事件循环调用 `EventQueue<>()::process()` 来 dispatch 所有队列中的事件。 +`EventDispatcher<>::dispatch()` 触发监听器的动作是同步的。但异步事件队列在某些场景下能发挥更大的作用(例如 Windows 消息队列、游戏中的消息队列等)。EventQueue 就是用于满足该类需求的事件队列。 +`EventQueue<>::enqueue()` 将事件加入队列,其参数和 `dispatch` 的参数完全相同。 +`EventQueue<>::process()` 用于分发队列中的事件。不调用 process ,事件就不会被分发。 +事件队列的典型用例:在 GUI 应用中,每个组件都调用 `EventQueue<>::enqueue()` 来发布事件,然后主事件循环调用 `EventQueue<>()::process()` 来 dispatch 所有队列中的事件。 `EventQueue` 支持将不可拷贝对象作为事件参数,例如上面例子中的 unique_ptr ### 教程 2 —— 多线程 @@ -104,13 +104,13 @@ thread.join(); **输出** -> Got event, index is 1 -> Should have triggered event with index = 1 -> Got event, index is 2 -> Should have triggered event with index = 2 -> Should NOT trigger event with index = 10 -> Should NOT trigger event with index = 11 -> Got event, index is 10 -> Got event, index is 11 +> Got event, index is 1 +> Should have triggered event with index = 1 +> Got event, index is 2 +> Should have triggered event with index = 2 +> Should NOT trigger event with index = 10 +> Should NOT trigger event with index = 11 +> Got event, index is 10 +> Got event, index is 11 > Should have triggered events with index = 10 and 11