1
0
mirror of https://github.com/wqking/eventpp.git synced 2024-12-25 23:30:49 +08:00

Initial commit

This commit is contained in:
wqking 2018-05-13 12:26:27 +08:00
commit f1956140d4
17 changed files with 15122 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.svn
tests/build/temp*
tests/build/project*

255
doc/callbacklist.md Normal file
View File

@ -0,0 +1,255 @@
# Class CallbackList reference
## Tutorials
### CallbackList tutorial 1, basic
**Code**
```c++
// The namespace is eventpp
// the first parameter is the prototype of the listener.
eventpp::CallbackList<void ()> callbackList;
// Add a callback.
// []() {} is the callback.
// Lambda is not required, any function or std::function
// or whatever function object with the required prototype is fine.
callbackList.append([]() {
std::cout << "Got callback 1." << std::endl;
});
callbackList.append([]() {
std::cout << "Got callback 2." << std::endl;
});
// Invoke the callback list
callbackList();
```
**Output**
> Got callback 1.
> Got callback 2.
**Remarks**
First let's define a callback list.
```c++
eventpp::CallbackList<void ()> callbackList;
```
class CallbackList takes at least one template arguments. It is the *prototype* of the callback.
The *prototype* is C++ function type, such as `void (int)`, `void (const std::string &, const MyClass &, int, bool)`.
Now let's add a callback.
```c++
callbackList.append([]() {
std::cout << "Got callback 1." << std::endl;
});
```
Function `append` takes one arguments, the *callback*.
The *callback* can be any callback target -- functions, pointers to functions, , pointers to member functions, lambda expressions, and function objects. It must be able to be called with the *prototype* declared in `callbackList`.
In the tutorial, we also add another callback.
Now let's invoke the callbackList.
```c++
callbackList();
```
During the invoking, all callbacks will be invoked one by one in the order of they were added.
### CallbackList tutorial 2, callback with parameters
**Code**
```c++
// The callback list has two parameters.
eventpp::CallbackList<void (const std::string &, const bool)> callbackList;
callbackList.append([](const std::string & s, const bool b) {
std::cout << std::boolalpha << "Got callback 1, s is " << s << " b is " << b << std::endl;
});
// The callback prototype doesn't need to be exactly same as the callback list.
// It would be find as long as the arguments is compatible with the dispatcher.
callbackList.append([](std::string s, int b) {
std::cout << std::boolalpha << "Got callback 2, s is " << s << " b is " << b << std::endl;
});
// Invoke the callback list
callbackList("Hello world", true);
```
**Output**
> Got callback 1, s is Hello world b is true
> Got callback 2, s is Hello world b is 1
**Remarks**
Now the callback list prototype takes two parameters, `const std::string &` and `const bool`.
The callback's prototype is not required to be same as the callback list, it's fine as long as the prototype is compatible with the callback list. See the second callback, `[](std::string s, int b)`, its prototype is not same as the callback list.
### CallbackList tutorial 3, remove
**Code**
```c++
using CL = eventpp::CallbackList<void ()>;
CL callbackList;
CL::Handle handle2;
// Add some callbacks.
callbackList.append([]() {
std::cout << "Got callback 1." << std::endl;
});
handle2 = callbackList.append([]() {
std::cout << "Got callback 2." << std::endl;
});
callbackList.append([]() {
std::cout << "Got callback 3." << std::endl;
});
callbackList.remove(handle2);
// Invoke the callback list
// The "Got callback 2" callback should not be triggered.
callbackList();
```
**Output**
> Got callback 1.
> Got callback 3.
**Remarks**
### CallbackList tutorial 4, for each
**Code**
```c++
using CL = eventpp::CallbackList<void ()>;
CL callbackList;
// Add some callbacks.
callbackList.append([]() {
std::cout << "Got callback 1." << std::endl;
});
callbackList.append([]() {
std::cout << "Got callback 2." << std::endl;
});
callbackList.append([]() {
std::cout << "Got callback 3." << std::endl;
});
// Now call forEach to remove the second callback
// The forEach callback prototype is void(const CallbackList::Handle & handle, const CallbackList::Callback & callback)
int index = 0;
callbackList.forEach([&callbackList, &index](const CL::Handle & handle, const CL::Callback & callback) {
std::cout << "forEach(Handle, Callback), invoked " << index << std::endl;
if(index == 1) {
callbackList.remove(handle);
std::cout << "forEach(Handle, Callback), removed second callback" << std::endl;
}
++index;
});
// The forEach callback prototype can also be void(const CallbackList::Handle & handle)
callbackList.forEach([&callbackList, &index](const CL::Handle & handle) {
std::cout << "forEach(Handle), invoked" << std::endl;
});
// The forEach callback prototype can also be void(const CallbackList::Callback & callback)
callbackList.forEach([&callbackList, &index](const CL::Callback & callback) {
std::cout << "forEach(Callback), invoked" << std::endl;
});
// Invoke the callback list
// The "Got callback 2" callback should not be triggered.
callbackList();
```
**Output**
> Got callback 1.
> Got callback 3.
**Remarks**
## API reference
**Template parameters**
```c++
template <
typename Prototype,
typename Callback = void,
typename Threading = MultipleThreading
>
class CallbackList;
```
`Prototype`: the callback prototype. It's C++ function type such as `void(int, std::string, const MyClass *)`.
`Callback`: the underlying type to hold the callback. Default is `void`, which will be expanded to `std::function`.
`Threading`: threading model. Default is 'MultipleThreading'. Possible values:
* `MultipleThreading`: the core data is protected with mutex. It's the default value.
* `SingleThreading`: the core data is not protected and can't be accessed from multiple threads.
**Public types**
`Handle`: the handle type returned by appendListener, prependListener and insertListener. A handle can be used to insert a callback or remove a callback. To check if a `Handle` is empty, convert it to boolean, *false* is empty.
`Callback`: the callback storage type.
**Functions**
```c++
CallbackList() = default;
CallbackList(CallbackList &&) = delete;
CallbackList(const CallbackList &) = delete;
CallbackList & operator = (const CallbackList &) = delete;
```
CallbackList can not be copied, moved, or assigned.
```c++
Handle append(const Callback & callback)
```
Add the *callback* to the callback list.
The callback is added to the end of the callback list.
Return a handle which represents the callback. The handle can be used to remove this callback or insert other callback before this callback.
The time complexity is O(1).
```c++
Handle prepend(const Callback & callback)
```
Add the *callback* to the callback list.
The callback is added to the beginning of the callback list.
Return a handle which represents the callback. The handle can be used to remove this callback or insert other callback before this callback.
The time complexity is O(1).
```c++
Handle insert(const Callback & callback, const Handle before)
```
Insert the *callback* to the callback list before the callback handle *before*. If *before* is not found, *callback* is added at the end of the callback list.
Return a handle which represents the callback. The handle can be used to remove this callback or insert other callback before this callback.
The time complexity is O(1).
```c++
bool remove(const Handle handle)
```
Remove the callback *handle* from the callback list.
Return true if the callback is removed successfully, false if the callback is not found.
The time complexity is O(1).
```c++
template <typename Func>
void forEach(Func && func)
```
Apply `func` to all callbacks.
The `func` can be one of the three prototypes:
```c++
AnyReturnType func(const EventDispatcher::Handle &, const EventDispatcher::Callback &);
AnyReturnType func(const EventDispatcher::Handle &);
AnyReturnType func(const EventDispatcher::Callback &);
```
**Note**: the `func` can remove any callbacks, or add other callbacks, safely.
```c++
void operator() (Args ...args)
```
Invoke each callbacks in the callback list.
The callbacks are called with arguments `args`.
The callbacks are called in the thread same as the callee of `operator()`.
## Internal data structure
CallbackList uses double linked list to manage the callbacks.
Each node is linked by shared pointer. Using shared pointer allows the node be removed while iterating.

474
doc/eventdispatcher.md Normal file
View File

@ -0,0 +1,474 @@
# Class EventDispatcher reference
## Tutorials
### Tutorial 1 -- Basic usage
**Code**
```c++
// The namespace is eventpp
// The first template parameter int is the event type,
// the second is the prototype of the listener.
eventpp::EventDispatcher<int, void ()> dispatcher;
// Add a listener. As the type of dispatcher,
// here 3 and 5 is the event type,
// []() {} is the listener.
// Lambda is not required, any function or std::function
// or whatever function object with the required prototype is fine.
dispatcher.appendListener(3, []() {
std::cout << "Got event 3." << std::endl;
});
dispatcher.appendListener(5, []() {
std::cout << "Got event 5." << std::endl;
});
dispatcher.appendListener(5, []() {
std::cout << "Got another event 5." << std::endl;
});
// Dispatch the events, the first argument is always the event type.
dispatcher.dispatch(3);
dispatcher.dispatch(5);
```
**Output**
> Got event 3.
> Got event 5.
> Got another event 5.
**Remarks**
First let's define a dispatcher.
```c++
eventpp::EventDispatcher<int, void ()> dispatcher;
```
class EventDispatcher takes two template arguments. The first argument is the *event type*, here is `int`. The second is the *prototype* of the listener.
The *event type* must be able to use as the key of `std::map`, that's to say, it must support `operator <`.
The *prototype* is C++ function type, such as `void (int)`, `void (const std::string &, const MyClass &, int, bool)`.
Now let's add a listener.
```c++
dispatcher.appendListener(3, []() {
std::cout << "Got event 3." << std::endl;
});
```
Function `appendListener` takes at least two arguments. The first argument is the *event* of type *event type*, here is `int`. The second is the *callback*.
The *callback* can be any callback target -- functions, pointers to functions, , pointers to member functions, lambda expressions, and function objects. It must be able to be called with the *prototype* declared in `dispatcher`.
In the tutorial, we also add two listeners for event 5.
Now let's dispatch some event.
```c++
dispatcher.dispatch(3);
dispatcher.dispatch(5);
```
Here we dispatched two events, one is event 3, the other is event 5.
During the dispatching, all listeners of that event will be invoked one by one in the order of they were added.
### Tutorial 2 -- Listener with parameters
**Code**
```c++
// The listener has two parameters.
eventpp::EventDispatcher<int, void (const std::string &, const bool)> dispatcher;
dispatcher.appendListener(3, [](const std::string & s, const bool b) {
std::cout << std::boolalpha << "Got event 3, s is " << s << " b is " << b << std::endl;
});
// The listener prototype doesn't need to be exactly same as the dispatcher.
// It would be find as long as the arguments is compatible with the dispatcher.
dispatcher.appendListener(5, [](std::string s, int b) {
std::cout << std::boolalpha << "Got event 5, s is " << s << " b is " << b << std::endl;
});
dispatcher.appendListener(5, [](const std::string & s, const bool b) {
std::cout << std::boolalpha << "Got another event 5, s is " << s << " b is " << b << std::endl;
});
// Dispatch the events, the first argument is always the event type.
dispatcher.dispatch(3, "Hello", true);
dispatcher.dispatch(5, "World", false);
```
**Output**
> Got event 3, s is Hello b is true
> Got event 5, s is World b is false
> Got another event 5, s is World b is false
**Remarks**
Now the dispatcher callback prototype takes two parameters, `const std::string &` and `const bool`.
The listener's prototype is not required to be same as the dispatcher, it's fine as long as the prototype is compatible with the dispatcher. See the second listener, `[](std::string s, int b)`, its prototype is not same as the dispatcher.
<a name="tutorial3" />
### Tutorial 3 -- Customized event struct
**Code**
```c++
// Define an Event to hold all parameters.
struct MyEvent {
int type;
std::string message;
int param;
};
// Define an event type getter to let the dispatcher knows how to
// extract the event type.
// The getter must derive from eventpp::EventGetterBase
// The getter must have:
// 1, A type named Event indicating the event type.
// 2, A static member function named getEvent. It receives all parameters
// same as the dispatcher prototype, and returns Event.
struct MyEventTypeGetter : public eventpp::EventGetterBase
{
using Event = int;
static Event getEvent(const MyEvent & e, bool b) {
return e.type;
}
};
// Pass MyEventTypeGetter as the first template argument of EventDispatcher
eventpp::EventDispatcher<
MyEventTypeGetter,
void (const MyEvent &, bool)
> dispatcher;
// Add a listener.
// Note: the first argument, event type, is MyEventTypeGetter::Event,
// not Event
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
;
});
// Dispatch the event.
// The first argument is Event.
dispatcher.dispatch(MyEvent { 3, "Hello world", 38 }, true);
```
**Output**
> Got event 3
> Event::type is 3
> Event::message is Hello world
> Event::param is 38
> b is true
**Remarks**
Previous tutorials pass the event type as the first argument in `dispatch`, and all other event parameters as other arguments of `dispatch`. Another common situation is an Event class is defined as the base, all other events derive from Event, and the actual event type is a data member of Event (think QEvent in Qt).
### Tutorial 4 -- Event queue
**Code**
```c++
eventpp::EventDispatcher<int, void (const std::string &, const bool)> dispatcher;
dispatcher.appendListener(3, [](const std::string & s, const bool b) {
std::cout << std::boolalpha << "Got event 3, s is " << s << " b is " << b << std::endl;
});
// The listener prototype doesn't need to be exactly same as the dispatcher.
// It would be find as long as the arguments is compatible with the dispatcher.
dispatcher.appendListener(5, [](std::string s, int b) {
std::cout << std::boolalpha << "Got event 5, s is " << s << " b is " << b << std::endl;
});
dispatcher.appendListener(5, [](const std::string & s, const bool b) {
std::cout << std::boolalpha << "Got another event 5, s is " << s << " b is " << b << std::endl;
});
// Enqueue the events, the first argument is always the event type.
// The listeners are not triggered during enqueue.
dispatcher.enqueue(3, "Hello", true);
dispatcher.enqueue(5, "World", false);
// Process the event queue, dispatch all queued events.
dispatcher.process();
```
**Output**
> 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
**Remarks**
`EventDispatcher<>::dispatch()` invokes the listeners synchronously. Sometimes an asynchronous event queue is more useful (think about Windows message queue, or an event queue in a game). EventDispatcher supports such kind of event queue.
`EventDispatcher<>::enqueue()` puts an event to the queue. Its parameters are exactly same as `dispatch`.
`EventDispatcher<>::process()` must be called to dispatch the queued events.
A typical use case is in a GUI application, each components call `EventDispatcher<>::enqueue()` to post the events, then the main event loop calls `EventDispatcher<>::process()` to dispatch the events.
## API reference
**Template parameters**
```c++
template <
typename EventGetter,
typename Prototype,
typename Callback = void,
typename ArgumentPassingMode = ArgumentPassingAutoDetect,
typename Threading = MultipleThreading
>
class EventDispatcher;
```
`EventGetter`: the *event getter*. The simplest form is an event type. For details, see [Event getter](#eventgetter) for details.
`Prototype`: the listener prototype. It's C++ function type such as `void(int, std::string, const MyClass *)`.
`Callback`: the underlying type to hold the callback. Default is `void`, which will be expanded to `std::function`.
`ArgumentPassingMode`: the argument passing mode. Default is `ArgumentPassingAutoDetect`. See [Argument passing mode](#argumentpassingmode) for details.
`Threading`: threading model. Default is 'MultipleThreading'. Possible values:
* `MultipleThreading`: the core data is protected with mutex. It's the default value.
* `SingleThreading`: the core data is not protected and can't be accessed from multiple threads.
**Public types**
`Handle`: the handle type returned by appendListener, prependListener and insertListener. A handle can be used to insert a listener or remove a listener. To check if a `Handle` is empty, convert it to boolean, *false* is empty.
`Callback`: the callback storage type.
`Event`: the event type.
**Functions**
```c++
EventDispatcher() = default;
EventDispatcher(EventDispatcher &&) = delete;
EventDispatcher(const EventDispatcher &) = delete;
EventDispatcher & operator = (const EventDispatcher &) = delete;
```
EventDispatcher can not be copied, moved, or assigned.
```c++
Handle appendListener(const Event & event, const Callback & callback)
```
Add the *callback* to the dispatcher to listen to *event*.
The listener is added to the end of the listener list.
Return a handle which represents the listener. The handle can be used to remove this listener or insert other listener before this listener.
The time complexity is O(1).
```c++
Handle prependListener(const Event & event, const Callback & callback)
```
Add the *callback* to the dispatcher to listen to *event*.
The listener is added to the beginning of the listener list.
Return a handle which represents the listener. The handle can be used to remove this listener or insert other listener before this listener.
The time complexity is O(1).
```c++
Handle insertListener(const Event & event, const Callback & callback, const Handle before)
```
Insert the *callback* to the dispatcher to listen to *event* before the listener handle *before*. If *before* is not found, *callback* is added at the end of the listener list.
Return a handle which represents the listener. The handle can be used to remove this listener or insert other listener before this listener.
The time complexity is O(1).
```c++
bool removeListener(const Event & event, const Handle handle)
```
Remove the listener *handle* which listens to *event* from the dispatcher.
Return true if the listener is removed successfully, false if the listener is not found.
The time complexity is O(1).
```c++
void dispatch(Args ...args);
template <typename T>
void dispatch(T && first, Args ...args);
```
Dispatch an event. The event type is deducted from the arguments of `dispatch`.
In both overloads, the listeners are called with arguments `args`.
The listeners are called in the thread same as the caller of `dispatch`.
```c++
void enqueue(Args ...args);
template <typename T>
void enqueue(T && first, Args ...args);
```
Put an event into the event queue. The event type is deducted from the arguments of `enqueue`.
All arguments are copied to internal data structure, so the arguments must be copyable.
```c++
void process()
```
Process the event queue. All events in the event queue are dispatched once and then removed from the queue.
The listeners are called in the thread same as the caller of `process`.
**Note**: if `process()` is called from multiple threads simultaneously, the events in the event queue are guaranteed dispatched only once.
```c++
template <typename Func>
void forEach(const Event & event, Func && func)
```
Apply `func` to all listeners of `event`.
The `func` can be one of the three prototypes:
```c++
AnyReturnType func(const EventDispatcher::Handle &, const EventDispatcher::Callback &);
AnyReturnType func(const EventDispatcher::Handle &);
AnyReturnType func(const EventDispatcher::Callback &);
```
**Note**: the `func` can remove any listeners, or add other listeners, safely.
<a name="eventgetter" />
## Event getter
The first template parameter of EventDispatcher is the *event getter*.
If *event getter* is not a struct or class inherits from eventpp::EventGetterBase, it's the event type.
For example `EventDispatcher<std::string, void()>` is a dispatcher with event type `std::string`, prototype `void()`.
If *event getter* inherits from tag eventpp::EventGetterBase, it's the event type getter. See [Tutorial 3](#tutorial3) for example.
Assume we have an EventDispatcher which callback prototype is `void (const MyEvent &, bool)`, where `MyEvent` is a unified event structure.
```c++
struct MyEvent {
int type;
std::string message;
int param;
// blah blah
};
```
A typical *event getter* looks like:
```c++
struct MyEventTypeGetter : public eventpp::EventGetterBase
{
using Event = int;
static Event getEvent(const MyEvent & e, bool b) {
return e.type;
}
};
```
Then we can define the dispatcher as
```c++
eventpp::EventDispatcher<MyEventTypeGetter, void (const MyEvent &, bool)> dispatcher;
```
An *event getter* must have a type `Event` which is the event type and used in the internal 'std::map`, and it must have a static function `getEvent`, which receives the arguments of callback prototype and return the event type.
To add a listener
```c++
dispatcher.appendListener(5, [](const MyEvent & e, bool b) {});
```
Note the first argument is the `Event` in the *event getter*, here is `int`, not `MyEvent`.
To dispatch or enqueue an event
```c++
MyEvent myEvent { 5, "Hello", 38 };
dispatcher.dispatch(myEvent, true);
```
Note the first argument is `MyEvent`, not `Event`.
`dispatch` and `enqueue` use the function `getEvent` in the *event getter* to deduct the event type.
`dispatch` and `enqueue` don't assume the meaning of any arguments. How to get the event type completely depends on `getEvent`. `getEvent` can simple return a member for the first argument, or concatenate all arguments, or even hash the arguments and return the hash value as the event type.
<a name="argumentpassingmode" />
## Argument passing mode
We have the dispatcher
```c++
eventpp::EventDispatcher<int, void(int, const std::string &)> dispatcher;
```
The event type is `int`.
The listener's first parameter is also `int`. Depending how the event is dispatched, the listener's first argument can be either the event type, or an extra argument.
```c++
dispatcher.dispatch(3, "hello");
```
The event *3* is dispatched with an argument *"hello"*, the listener will be invoked with the arguments `(3, "hello")`, the first argument is the event type.
```c++
dispatcher.dispatch(3, 8, "hello");
```
The event *3* is dispatched with two arguments *8* and *"hello"*, the listener will be invoked with the arguments `(8, "hello")`, the first argument is the extra argument, and the event type is omitted.
So by default, EventDispatcher automatically detects the argument count of `dispatch` and listeners prototype, and calls the listeners either with or without the event type.
The default rule is convenient, permissive, and, may be error prone. The second parameter `typename ArgumentPassingMode` in the policies can control the behavior.
```c++
struct ArgumentPassingAutoDetect;
struct ArgumentPassingIncludeEvent;
struct ArgumentPassingExcludeEvent;
```
`ArgumentPassingAutoDetect`: the default policy. Auto detects whether to pass the event type.
`ArgumentPassingIncludeEvent`: always passes the event type. If the argument count doesn't match, compiling fails.
`ArgumentPassingExcludeEvent`: always omits and doesn't pass the event type. If the argument count doesn't match, compiling fails.
Assumes the number of arguments in the listener prototype is P, the number of arguments (include the event type) in `dispatch` is D, then the relationship of P and D is,
For `ArgumentPassingAutoDetect`: P == D or P + 1 == D
For `ArgumentPassingIncludeEvent`: P == D
For `ArgumentPassingExcludeEvent`: P + 1 == D
**Note**: the same rules also applies to `EventDispatcher<>::enqueue`, since `enqueue` has same parameters as `dispatch`.
Examples to demonstrate argument passing mode
```c++
eventpp::EventDispatcher<
int,
void(int, const std::string &),
ArgumentPassingAutoDetect
> dispatcher;
// or just
//eventpp::EventDispatcher<int, void(int, const std::string &)> dispatcher;
dispatcher.dispatch(3, "hello"); // Compile OK
dispatcher.dispatch(3, 8, "hello"); // Compile OK
dispatcher.enqueue(3, "hello"); // Compile OK
dispatcher.enqueue(3, 8, "hello"); // Compile OK
```
```c++
eventpp::EventDispatcher<
int,
void(int, const std::string &),
ArgumentPassingIncludeEvent
> dispatcher;
dispatcher.dispatch(3, "hello"); // Compile OK
//dispatcher.dispatch(3, 8, "hello"); // Compile failure
dispatcher.enqueue(3, "hello"); // Compile OK
//dispatcher.enqueue(3, 8, "hello"); // Compile failure
```
```c++
eventpp::EventDispatcher<
int,
void(int, const std::string &),
ArgumentPassingExcludeEvent
> dispatcher;
//dispatcher.dispatch(3, "hello"); // Compile failure
dispatcher.dispatch(3, 8, "hello"); // Compile OK
//dispatcher.enqueue(3, "hello"); // Compile failure
dispatcher.enqueue(3, 8, "hello"); // Compile OK
```
## Notes and caveats
1. If a listener adds another listener of the same event to the dispatcher during a dispatching, the new listener is guaranteed to be called within the same dispatching. Ideally the new added listener should not be called, but this is limited by the underlying data structure.
## Thread safety
`EventDispatcher` is thread safe. All public functions can be invoked from multiple threads simultaneously. If it failed, please report a bug.
But the operations on the listeners, such as copying, moving, comparing, or invoking, may be not thread safe. It depends on listeners.
## Exception safety
EventDispatcher doesn't throw any exceptions.
Exceptions may be thrown by underlying code when,
1. Out of memory, new memory can't be allocated.
2. The listeners throw exceptions during copying, moving, comparing, or invoking.
## Time complexities
The time complexities being discussed here is about when operating on the listener in the underlying list, and `n` is the number of listeners. It doesn't include the event searching in the underlying `std::map` which is always O(log n).
* `appendListener`: O(1)
* `prependListener`: O(1)
* `insertListener` before another handle: O(1)
* `removeListener` by handle: O(1)
## Internal data structure
Beside using [CallbackList](doc/callbacklist.md) to manage the listener callbacks, EventDispatcher uses three `std::list` to manage the event queue.
The first busy list holds all nodes with queued events.
The second idle list holds all idle nodes. After an event is dispatched and removed from the queue, instead of freeing the memory, EventDispatcher moves the unused node to the idle list. This can improve performance and avoid memory fragment.
The third list is a local temporary list used in function `process()`. During processing, the busy list is swapped to the temporary list, all events are dispatched from the temporary list, then the temporary list is returned and appended to the idle list.

View File

@ -0,0 +1,319 @@
// eventpp library
// Copyright (C) 2018 Wang Qi (wqking)
// Github: https://github.com/wqking/eventpp
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef CALLBACKLIST_H_588722158669
#define CALLBACKLIST_H_588722158669
#include <functional>
#include <memory>
#include <mutex>
#include <utility>
namespace eventpp {
namespace _internal {
struct DummyMutex
{
void lock() {}
void unlock() {}
};
template <typename F, typename ...A>
struct CanInvoke
{
template <typename U, typename ...X>
static auto invoke(int) -> decltype(std::declval<U>()(std::declval<X>()...), std::true_type());
template <typename U, typename ...X>
static auto invoke(...) -> std::false_type;
enum {
value = !! decltype(invoke<F, A...>(0))()
};
};
template <
typename CallbackType,
typename Threading,
typename ReturnType, typename ...Args
>
class CallbackListBase;
template <
typename CallbackType,
typename Threading,
typename ReturnType, typename ...Args
>
class CallbackListBase<
CallbackType,
Threading,
ReturnType (Args...)
>
{
private:
using Mutex = typename Threading::Mutex;
using _Callback = typename std::conditional<
std::is_same<CallbackType, void>::value,
std::function<ReturnType (Args...)>,
CallbackType
>::type;
struct Node;
using NodePtr = std::shared_ptr<Node>;
struct Node
{
_Callback callback;
NodePtr previous;
NodePtr next;
};
class _Handle : public std::weak_ptr<Node>
{
private:
using super = std::weak_ptr<Node>;
public:
using super::super;
operator bool () const noexcept {
return ! this->expired();
}
};
public:
using Callback = _Callback;
using Handle = _Handle;
public:
CallbackListBase() = default;
CallbackListBase(CallbackListBase &&) = delete;
CallbackListBase(const CallbackListBase &) = delete;
CallbackListBase & operator = (const CallbackListBase &) = delete;
~CallbackListBase()
{
// Don't lock mutex here since it may throw exception
NodePtr node = head;
head.reset();
while(node) {
NodePtr next = node->next;
node->previous.reset();
node->next.reset();
node = next;
}
node.reset();
}
Handle append(const Callback & callback)
{
NodePtr node(std::make_shared<Node>());
node->callback = callback;
std::lock_guard<Mutex> lockGuard(mutex);
if(! head) {
head = node;
tail = node;
}
else {
node->previous = tail;
tail->next = node;
tail = node;
}
return Handle(node);
}
Handle prepend(const Callback & callback)
{
NodePtr node(std::make_shared<Node>());
node->callback = callback;
std::lock_guard<Mutex> lockGuard(mutex);
if(! head) {
head = node;
tail = node;
}
else {
node->next = head;
head->previous = node;
head = node;
}
return Handle(node);
}
Handle insert(const Callback & callback, const Handle before)
{
NodePtr beforeNode = before.lock();
if(beforeNode) {
NodePtr node(std::make_shared<Node>());
node->callback = callback;
std::lock_guard<Mutex> lockGuard(mutex);
doInert(beforeNode, node);
return Handle(node);
}
return append(callback);
}
bool remove(const Handle handle)
{
std::lock_guard<Mutex> lockGuard(mutex);
auto node = handle.lock();
if(node) {
doRemoveNode(node);
return true;
}
return false;
}
template <typename Func>
void forEach(Func && func)
{
NodePtr node;
{
std::lock_guard<Mutex> lockGuard(mutex);
node = head;
}
while(node) {
doForEachInvoke(func, Handle(node), node->callback);
{
std::lock_guard<Mutex> lockGuard(mutex);
node = node->next;
}
}
}
void operator() (Args ...args)
{
NodePtr node;
{
std::lock_guard<Mutex> lockGuard(mutex);
node = head;
}
while(node) {
// Must not hold any lock when invoking the callback
// because the callback may append/remove/dispatch again and cause recursive lock
node->callback(std::forward<Args>(args)...);
{
std::lock_guard<Mutex> lockGuard(mutex);
node = node->next;
}
}
}
private:
template <typename Func>
auto doForEachInvoke(Func && func, const Handle & handle, const Callback & callback)
-> typename std::enable_if<CanInvoke<Func, Handle, Callback>::value, void>::type
{
func(handle, callback);
}
template <typename Func>
auto doForEachInvoke(Func && func, const Handle & handle, const Callback & callback)
-> typename std::enable_if<CanInvoke<Func, Handle>::value, void>::type
{
func(handle);
}
template <typename Func>
auto doForEachInvoke(Func && func, const Handle & handle, const Callback & callback)
-> typename std::enable_if<CanInvoke<Func, Callback>::value, void>::type
{
func(callback);
}
void doRemoveNode(NodePtr & node)
{
if(node->next) {
node->next->previous = node->previous;
}
if(node->previous) {
node->previous->next = node->next;
}
if(head == node) {
head = node->next;
}
if(tail == node) {
tail = node->previous;
}
// don't modify node->next or node->previous
// because node may be still used in a loop.
}
void doInert(NodePtr & before, NodePtr & node)
{
node->previous = before->previous;
node->next = before;
if(before->previous) {
before->previous->next = node;
}
before->previous = node;
if(before == head) {
head = node;
}
}
private:
NodePtr head;
NodePtr tail;
Mutex mutex;
};
} //namespace _internal
struct MultipleThreading
{
using Mutex = std::mutex;
};
struct SingleThreading
{
using Mutex = _internal::DummyMutex;
};
template <
typename Prototype,
typename Callback = void,
typename Threading = MultipleThreading
>
class CallbackList : public _internal::CallbackListBase<Callback, Threading, Prototype>
{
};
} //namespace eventpp
#endif

View File

@ -0,0 +1,328 @@
// eventpp library
// Copyright (C) 2018 Wang Qi (wqking)
// Github: https://github.com/wqking/eventpp
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef EVENTDISPATCHER_H_319010983013
#define EVENTDISPATCHER_H_319010983013
#include "callbacklist.h"
#include <string>
#include <functional>
#include <type_traits>
#include <map>
#include <vector>
#include <list>
#include <tuple>
#include <mutex>
#include <cstdint>
#include <climits>
#include <algorithm>
#include <memory>
namespace eventpp {
struct EventGetterBase {};
template <typename E>
struct PrimaryEventGetter : public EventGetterBase
{
using Event = E;
template <typename U, typename ...Args>
static Event getEvent(U && e, Args...) {
return e;
}
};
struct ArgumentPassingAutoDetect
{
enum {
canIncludeEventType = true,
canExcludeEventType = true
};
};
struct ArgumentPassingIncludeEvent
{
enum {
canIncludeEventType = true,
canExcludeEventType = false
};
};
struct ArgumentPassingExcludeEvent
{
enum {
canIncludeEventType = false,
canExcludeEventType = true
};
};
namespace _internal {
template <size_t ...Indexes>
struct IndexSequence
{
};
template <size_t N, size_t ...Indexes>
struct MakeIndexSequence : MakeIndexSequence <N - 1, N - 1, Indexes...>
{
};
template <std::size_t ...Indexes>
struct MakeIndexSequence<0, Indexes...>
{
using Type = IndexSequence<Indexes...>;
};
template <
typename EventGetterType,
typename CallbackType,
typename ArgumentPassingMode,
typename Threading,
typename ReturnType, typename ...Args
>
class EventDispatcherBase;
template <
typename EventGetterType,
typename CallbackType,
typename ArgumentPassingMode,
typename Threading,
typename ReturnType, typename ...Args
>
class EventDispatcherBase <
EventGetterType,
CallbackType,
ArgumentPassingMode,
Threading,
ReturnType (Args...)
>
{
private:
using EventGetter = typename std::conditional<
std::is_base_of<EventGetterBase, EventGetterType>::value,
EventGetterType,
PrimaryEventGetter<EventGetterType>
>::type;
using Mutex = typename Threading::Mutex;
using _Callback = typename std::conditional<
std::is_same<CallbackType, void>::value,
std::function<ReturnType (Args...)>,
CallbackType
>::type;
using _CallbackList = CallbackList<ReturnType (Args...), _Callback, Threading>;
enum {
canIncludeEventType = ArgumentPassingMode::canIncludeEventType,
canExcludeEventType = ArgumentPassingMode::canExcludeEventType
};
using _Handle = typename _CallbackList::Handle;
using _Event = typename EventGetter::Event;
using QueueItem = std::tuple<
typename std::remove_cv<typename std::remove_reference<_Event>::type>::type,
typename std::remove_cv<typename std::remove_reference<Args>::type>::type...
>;
public:
using Handle = _Handle;
using Callback = _Callback;
using Event = _Event;
public:
EventDispatcherBase() = default;
EventDispatcherBase(EventDispatcherBase &&) = delete;
EventDispatcherBase(const EventDispatcherBase &) = delete;
EventDispatcherBase & operator = (const EventDispatcherBase &) = delete;
Handle appendListener(const Event & event, const Callback & callback)
{
std::lock_guard<Mutex> lockGuard(listenerMutex);
return eventCallbackListMap[event].append(callback);
}
Handle prependListener(const Event & event, const Callback & callback)
{
std::lock_guard<Mutex> lockGuard(listenerMutex);
return eventCallbackListMap[event].prepend(callback);
}
Handle insertListener(const Event & event, const Callback & callback, const Handle before)
{
std::lock_guard<Mutex> lockGuard(listenerMutex);
return eventCallbackListMap[event].insert(callback, before);
}
bool removeListener(const Event & event, const Handle handle)
{
_CallbackList * callableList = doFindCallableList(event);
if(callableList) {
return callableList->remove(handle);
}
return false;
}
template <typename Func>
void forEach(const Event & event, Func && func)
{
_CallbackList * callableList = doFindCallableList(event);
if(callableList) {
callableList->forEach(std::forward<Func>(func));
}
}
void dispatch(Args ...args)
{
static_assert(canIncludeEventType, "Dispatching arguments count doesn't match required (Event type should be included).");
_CallbackList * callableList = doFindCallableList(EventGetter::getEvent(std::forward<Args>(args)...));
if(callableList) {
(*callableList)(std::forward<Args>(args)...);
}
}
template <typename T>
void dispatch(T && first, Args ...args)
{
static_assert(canExcludeEventType, "Dispatching arguments count doesn't match required (Event type should NOT be included).");
_CallbackList * callableList = doFindCallableList(EventGetter::getEvent(std::forward<T>(first), std::forward<Args>(args)...));
if(callableList) {
(*callableList)(std::forward<Args>(args)...);
}
}
void enqueue(Args ...args)
{
static_assert(canIncludeEventType, "Enqueuing arguments count doesn't match required (Event type should be included).");
doEnqueue(QueueItem(std::get<0>(std::tie(args...)), std::forward<Args>(args)...));
}
template <typename T>
void enqueue(T && first, Args ...args)
{
static_assert(canExcludeEventType, "Enqueuing arguments count doesn't match required (Event type should NOT be included).");
doEnqueue(QueueItem(std::forward<T>(first), std::forward<Args>(args)...));
}
void process()
{
if(! queueList.empty()) {
std::list<QueueItem> tempList;
{
std::lock_guard<Mutex> queueListLock(queueListMutex);
using namespace std;
swap(queueList, tempList);
}
if(! tempList.empty()) {
for(auto & item : tempList) {
doProcessItem(item, typename _internal::MakeIndexSequence<sizeof...(Args) + 1>::Type());
item = QueueItem();
}
std::lock_guard<Mutex> queueListLock(freeListMutex);
freeList.splice(freeList.end(), tempList);
}
}
}
private:
template <size_t ...Indexes>
void doProcessItem(QueueItem & item, _internal::IndexSequence<Indexes...>)
{
dispatch(std::get<Indexes>(item)...);
}
void doEnqueue(QueueItem && item)
{
if(! freeList.empty()) {
std::list<QueueItem> tempList;
{
std::lock_guard<Mutex> queueListLock(freeListMutex);
if(! freeList.empty()) {
tempList.splice(tempList.end(), freeList, freeList.begin());
}
}
if(! tempList.empty()) {
auto it = tempList.begin();
*it = item;
std::lock_guard<Mutex> queueListLock(queueListMutex);
queueList.splice(queueList.end(), tempList, it);
return;
}
}
std::lock_guard<Mutex> queueListLock(queueListMutex);
queueList.emplace_back(item);
}
_CallbackList * doFindCallableList(const Event & e)
{
std::lock_guard<Mutex> lockGuard(listenerMutex);
auto it = eventCallbackListMap.find(e);
if(it != eventCallbackListMap.end()) {
return &it->second;
}
else {
return nullptr;
}
}
private:
std::map<Event, _CallbackList> eventCallbackListMap;
Mutex listenerMutex;
Mutex queueListMutex;
std::list<QueueItem> queueList;
Mutex freeListMutex;
std::list<QueueItem> freeList;
};
} //namespace _internal
template <
typename EventGetter,
typename Prototype,
typename Callback = void,
typename ArgumentPassingMode = ArgumentPassingAutoDetect,
typename Threading = MultipleThreading
>
class EventDispatcher : public _internal::EventDispatcherBase<
EventGetter, Callback, ArgumentPassingMode, Threading, Prototype>
{
};
} //namespace eventpp
#endif

16
license Normal file
View File

@ -0,0 +1,16 @@
eventpp library
Copyright (C) 2018 Wang Qi (wqking)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

139
readme.md Normal file
View File

@ -0,0 +1,139 @@
# eventpp -- Event Dispatcher and callback list for C++
eventpp provides tools that allow your application components to communicate with each other by dispatching events and listening to them. With eventpp you can implement signal/slot mechanism, or observer pattern, very easily.
## Facts and features
1. Supports nested event. A listener can dispatch event, add other listeners, when capturing an event.
2. Thread safe.
3. Requires C++ 11 (tested with MSVC 2015, MinGW gcc 7.2, and Ubuntu gcc 5.4).
4. Header only, no source file, no need to build.
5. Template based, less runtime overhead.
6. Backed by unit tests.
7. Written in portable and standard C++. (I'm not a C++ standard expert so if you find any non-standard code or undefined behavior please let me know.)
8. Doesn't depend any other libraries.
9. Namespace: `eventpp`.
## License
Apache License, Version 2.0
If you have trouble with the license, contact me.
## Source code
[https://github.com/wqking/eventpp](https://github.com/wqking/eventpp)
## Classes
### EventDispatcher
Declaration
```c++
template <
typename EventGetter,
typename Prototype,
typename Callback = void,
typename ArgumentPassingMode = ArgumentPassingAutoDetect,
typename Threading = MultipleThreading
>
class EventDispatcher;
```
Header
```c++
// Add the folder *include* to include path.
#include "eventpp/eventdispatcher.h"
```
### CallbackList
Declaration
```c++
template <
typename Prototype,
typename CallbackType = void,
typename Threading = MultipleThreading
>
class CallbackList;
```
Header
```c++
#include "eventpp/callbacklist.h"
```
## Quick start
### Using EventDispatcher
```c++
// The namespace is eventpp
// The first template parameter int is the event type,
// the second is the prototype of the listener.
eventpp::EventDispatcher<int, void ()> dispatcher;
// Add a listener. As the type of dispatcher,
// here 3 and 5 is the event type,
// []() {} is the listener.
// Lambda is not required, any function or std::function
// or whatever function object with the required prototype is fine.
dispatcher.appendListener(3, []() {
std::cout << "Got event 3." << std::endl;
});
dispatcher.appendListener(5, []() {
std::cout << "Got event 5." << std::endl;
});
dispatcher.appendListener(5, []() {
std::cout << "Got another event 5." << std::endl;
});
// Dispatch the events, the first argument is always the event type.
dispatcher.dispatch(3);
dispatcher.dispatch(5);
```
### Using CallbackList
```c++
// The namespace is eventpp
// the first parameter is the prototype of the listener.
eventpp::CallbackList<void ()> callbackList;
// Add a callback.
// []() {} is the callback.
// Lambda is not required, any function or std::function
// or whatever function object with the required prototype is fine.
callbackList.append([]() {
std::cout << "Got callback 1." << std::endl;
});
callbackList.append([]() {
std::cout << "Got callback 2." << std::endl;
});
// Invoke the callback list
callbackList();
```
## Documentations
* [Event dispatcher](doc/eventdispatcher.md)
* [Callback list](doc/callbacklist.md)
## Build the unit tests
The library itself is header only and doesn't need building. If you want to run the unit tests, follow below steps:
1. `cd tests/build`
2. Run `make` with different target.
* make vc15 #generate solution files for Microsoft Visual Studio 2015, then open eventpptest.sln in folder project_vc15
* make mingw #build using MinGW
* make linux #build on Linux
## Roadmap (what's next)
* Move GCallback from my [cpgf library](https://github.com/cpgf/cpgf), so eventpp becomes a completed callback, callback list, and event dispatcher library.
* Let me know your requirement.
## Motivations
I (wqking) am a big fan of observer pattern (publish/subscribe pattern), I used such pattern a lot in my code. I either used GCallbackList in my cpgf library which is too simple and not safe, or repeated coding event dispatching mechanism such as I did in my [Gincu game engine](https://github.com/wqking/gincu). Both approaches are neither fun nor robust.
Thanking to C++11, now it's quite easy to write a reusable event library with beautiful syntax (it's nightmare to simulate the variadic template in C++03), so here comes `eventpp`.

30
tests/CMakeLists.txt Normal file
View File

@ -0,0 +1,30 @@
project(eventpptest)
cmake_minimum_required(VERSION 3.2)
set(CMAKE_CXX_STANDARD 11)
set(TARGET_TEST tests)
set(THIRDPARTY_PATH ../../thirdparty)
set(SRC_TEST
testmain.cpp
tutorial_callbacklist.cpp
tutorial_eventdispatcher.cpp
test_dispatch.cpp
test_callbacklist.cpp
test_queue.cpp
)
include_directories(../include)
add_executable(
${TARGET_TEST}
${SRC_TEST}
)
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
target_link_libraries(${TARGET_TEST} Threads::Threads)

103
tests/build/makefile Normal file
View File

@ -0,0 +1,103 @@
CACHE_DIR = temp_cache
CMAKE = cmake ../.. $(CMAKE_FLAGS)
MK_DIR = @cmake -E make_directory
CH_DIR = @cmake -E chdir
ECHO = @cmake -E echo
MK_CACHE = $(MK_DIR) $(CACHE_DIR)
EXEC_BUILD = $(CH_DIR) $(CACHE_DIR)
PROJECT=eventpptest
PROJECT_PREFIX = project
SUPPORT_MAKES = auto nmake mingw mingw_debug linux vc15
none: needcmake
$(ECHO) "Usage:"
$(ECHO) " make MakeType [CMAKE_FLAGS='flags passed to cmake']"
$(ECHO) "or"
$(ECHO) " nmake MakeType"
$(ECHO) "if MS VC is used"
$(ECHO)
$(ECHO) "Available MakeType"
$(ECHO) " $(SUPPORT_MAKES)"
$(ECHO)
$(ECHO) " nmake Generate Microsoft VC makefile and then use nmake to build."
$(ECHO) " mingw Generate MinGW makefile and then use mingw32-make to build (release version)."
$(ECHO) " mingw_debug Generate MinGW makefile and then use mingw32-make to build (debug version)."
$(ECHO) " linux Generate Linux/Unix makefile and then use GCC make to build."
$(ECHO) " vc15 Generate project files for Microsoft VC 2015. No auto build. You need to open the project in VC IDE then build."
$(ECHO) " auto Auto detect the compiler and make environment and then use make to build. NOT recommend."
$(ECHO)
$(ECHO) " clean No build. Remove and clean all generated files, include build and projects files."
$(ECHO) " cleanbuild No build. Remove and clean all build files, but leave projects files."
needcmake:
$(ECHO)
auto: needcmake
$(MK_CACHE)_auto
$(CH_DIR) $(CACHE_DIR)_auto $(CMAKE)
$(EXEC_BUILD)_auto make $(TARGET)
mingw: needcmake
$(MK_CACHE)_mingw
$(CH_DIR) $(CACHE_DIR)_mingw $(CMAKE) -DCMAKE_BUILD_TYPE=Release -G"MinGW Makefiles"
$(EXEC_BUILD)_mingw mingw32-make $(TARGET)
mingw_debug: needcmake
$(MK_CACHE)_mingw_debug
$(CH_DIR) $(CACHE_DIR)_mingw_debug $(CMAKE) -DCMAKE_BUILD_TYPE=Debug -G"MinGW Makefiles"
$(EXEC_BUILD)_mingw_debug mingw32-make $(TARGET)
msys: needcmake
$(MK_CACHE)_msys
$(CH_DIR) $(CACHE_DIR)_msys $(CMAKE) -DCMAKE_BUILD_TYPE=Release -G"MSYS Makefiles"
$(EXEC_BUILD)_msys make $(TARGET)
msys_debug: needcmake
$(MK_CACHE)_msys_debug
$(CH_DIR) $(CACHE_DIR)_msys_debug $(CMAKE) -DCMAKE_BUILD_TYPE=Debug -G"MSYS Makefiles"
$(EXEC_BUILD)_msys_debug make $(TARGET)
nmake: needcmake
$(ECHO) NOTE: *****************************
$(ECHO) NOTE: If cmake raises errors, try run this in Visual Studio Command Prompt from the VS package.
$(ECHO) NOTE: *****************************
$(MK_CACHE)_nmake
$(CH_DIR) $(CACHE_DIR)_nmake $(CMAKE) -G"NMake Makefiles"
$(EXEC_BUILD)_nmake nmake $(TARGET)
linux: needcmake
$(MK_CACHE)_linux
$(CH_DIR) $(CACHE_DIR)_linux $(CMAKE) -DCMAKE_BUILD_TYPE=Release -G"Unix Makefiles"
$(EXEC_BUILD)_linux make $(TARGET)
linux_debug: needcmake
$(MK_CACHE)_linux_debug
$(CH_DIR) $(CACHE_DIR)_linux_debug $(CMAKE) -DCMAKE_BUILD_TYPE=Debug -G"Unix Makefiles"
$(EXEC_BUILD)_linux_debug make $(TARGET)
vc15: needcmake
$(MK_DIR) $(PROJECT_PREFIX)_vc15
$(CH_DIR) $(PROJECT_PREFIX)_vc15 $(CMAKE) -G"Visual Studio 14 Win64"
$(ECHO) Please open the solution $(PROJECT).sln in $(PROJECT_PREFIX)_vc15 in VC IDE.
cleanbuild: needcmake
@cmake -E remove_directory bin
@cmake -E remove_directory $(CACHE_DIR)
clean: needcmake cleanbuild
@cmake -E remove_directory $(PROJECT_PREFIX)_vc05
@cmake -E remove_directory $(PROJECT_PREFIX)_vc08
@cmake -E remove_directory $(PROJECT_PREFIX)_vc10
@cmake -E remove_directory $(PROJECT_PREFIX)_vc12
@cmake -E remove_directory $(PROJECT_PREFIX)_vc13
@cmake -E remove_directory $(PROJECT_PREFIX)_cb_mingw
@cmake -E remove_directory $(PROJECT_PREFIX)_cb_nmake
@cmake -E remove_directory $(PROJECT_PREFIX)_cb_linux
.PHONY: clean

12012
tests/catch.hpp Normal file

File diff suppressed because it is too large Load Diff

43
tests/test.h Normal file
View File

@ -0,0 +1,43 @@
// eventpp library
// Copyright (C) 2018 Wang Qi (wqking)
// Github: https://github.com/wqking/eventpp
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef TEST_H
#define TEST_H
#include "catch.hpp"
template <typename Callable, typename ReturnType = void>
struct EraseArgs1
{
template <typename C>
explicit EraseArgs1(const C & callable) : callable(callable)
{
}
template <typename First, typename ...Args>
ReturnType operator() (First &&, Args && ...args)
{
callable(std::forward(args)...);
}
Callable callable;
};
template <typename Callable>
EraseArgs1<Callable> eraseArgs1(const Callable & callable)
{
return EraseArgs1<Callable>(callable);
}
#endif

582
tests/test_callbacklist.cpp Normal file
View File

@ -0,0 +1,582 @@
// eventpp library
// Copyright (C) 2018 Wang Qi (wqking)
// Github: https://github.com/wqking/eventpp
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "test.h"
#define private public
#include "eventpp/callbacklist.h"
#undef private
#include <vector>
#include <thread>
#include <chrono>
#include <numeric>
#include <random>
#include <algorithm>
using namespace eventpp;
namespace {
template <typename CL, typename T>
void verifyLinkedList(CL & callbackList, const std::vector<T> & dataList)
{
const int count = (int)dataList.size();
if(count == 0) {
REQUIRE(! callbackList.head);
REQUIRE(! callbackList.tail);
return;
}
REQUIRE(! callbackList.head->previous);
REQUIRE(! callbackList.tail->next);
if(count == 1) {
REQUIRE(callbackList.head);
REQUIRE(callbackList.head == callbackList.tail);
}
auto node = callbackList.head;
for(int i = 0; i < count; ++i) {
REQUIRE(node);
if(i == 0) {
REQUIRE(! node->previous);
REQUIRE(node == callbackList.head);
}
if(i == count - 1) {
REQUIRE(! node->next);
REQUIRE(node == callbackList.tail);
}
REQUIRE(node->callback == dataList[i]);
node = node->next;
}
}
template <typename CL, typename T>
void verifyDisorderedLinkedList(CL & callbackList, std::vector<T> dataList)
{
std::vector<T> buffer;
auto node = callbackList.head;
while(node) {
buffer.push_back(node->callback);
node = node->next;
}
std::sort(buffer.begin(), buffer.end());
std::sort(dataList.begin(), dataList.end());
REQUIRE(buffer == dataList);
}
template <typename CL>
auto extractCallbackListHandles(CL & callbackList)
-> std::vector<typename CL::Handle>
{
std::vector<typename CL::Handle> result;
auto node = callbackList.head;
while(node) {
result.push_back(typename CL::Handle(node));
node = node->next;
}
return result;
}
template <typename T>
void verifyNoMemoryLeak(const std::vector<T> & nodeList)
{
for(const auto & node : nodeList) {
REQUIRE(! node.lock());
}
}
} //unnamed namespace
using Prototype = void();
using CL = CallbackList<Prototype, int>;
TEST_CASE("CallbackList, no memory leak after callback list is freed")
{
std::vector<CL::Handle> nodeList;
{
CL callbackList;
for(int i = 100; i < 200; ++i) {
callbackList.append(i);
}
nodeList = extractCallbackListHandles(callbackList);
}
verifyNoMemoryLeak(nodeList);
}
TEST_CASE("CallbackList, no memory leak after all callbacks are removed")
{
std::vector<CL::Handle> nodeList;
std::vector<CL::Handle> handleList;
CL callbackList;
for(int i = 100; i < 200; ++i) {
handleList.push_back(callbackList.append(i));
}
nodeList = extractCallbackListHandles(callbackList);
for(auto & handle : handleList) {
callbackList.remove(handle);
}
verifyNoMemoryLeak(nodeList);
}
TEST_CASE("CallbackList, append/remove/insert")
{
CL callbackList;
REQUIRE(! callbackList.head);
REQUIRE(! callbackList.tail);
CL::Handle h100, h101, h102, h103, h104, h105, h106, h107;
{
auto handle = callbackList.append(100);
h100 = handle;
verifyLinkedList(callbackList, std::vector<int>{ 100 });
}
{
auto handle = callbackList.append(101);
h101 = handle;
verifyLinkedList(callbackList, std::vector<int>{ 100, 101 });
}
{
auto handle = callbackList.append(102);
h102 = handle;
verifyLinkedList(callbackList, std::vector<int>{ 100, 101, 102 });
}
{
auto handle = callbackList.append(103);
h103 = handle;
verifyLinkedList(callbackList, std::vector<int>{ 100, 101, 102, 103 });
}
{
auto handle = callbackList.append(104);
h104 = handle;
verifyLinkedList(callbackList, std::vector<int>{ 100, 101, 102, 103, 104 });
}
{
auto handle = callbackList.insert(105, h103); // before 103
h105 = handle;
verifyLinkedList(callbackList, std::vector<int>{ 100, 101, 102, 105, 103, 104 });
h107 = callbackList.insert(107, h100); // before 100
verifyLinkedList(callbackList, std::vector<int>{ 107, 100, 101, 102, 105, 103, 104 });
h106 = callbackList.insert(106, handle); // before 105
verifyLinkedList(callbackList, std::vector<int>{ 107, 100, 101, 102, 106, 105, 103, 104 });
}
callbackList.remove(h100);
verifyLinkedList(callbackList, std::vector<int>{ 107, 101, 102, 106, 105, 103, 104 });
callbackList.remove(h103);
callbackList.remove(h102);
verifyLinkedList(callbackList, std::vector<int>{ 107, 101, 106, 105, 104 });
callbackList.remove(h105);
callbackList.remove(h104);
callbackList.remove(h106);
callbackList.remove(h101);
callbackList.remove(h107);
verifyLinkedList(callbackList, std::vector<int>{});
}
TEST_CASE("CallbackList, insert")
{
CL callbackList;
auto h100 = callbackList.append(100);
auto h101 = callbackList.append(101);
auto h102 = callbackList.append(102);
auto h103 = callbackList.append(103);
auto h104 = callbackList.append(104);
SECTION("before front") {
callbackList.insert(105, h100);
verifyLinkedList(callbackList, std::vector<int>{ 105, 100, 101, 102, 103, 104 });
}
SECTION("before second") {
callbackList.insert(105, h101);
verifyLinkedList(callbackList, std::vector<int>{ 100, 105, 101, 102, 103, 104 });
}
SECTION("before nonexist by handle") {
callbackList.insert(105, CL::Handle());
verifyLinkedList(callbackList, std::vector<int>{ 100, 101, 102, 103, 104, 105 });
}
}
TEST_CASE("CallbackList, remove")
{
CL callbackList;
auto h100 = callbackList.append(100);
auto h101 = callbackList.append(101);
auto h102 = callbackList.append(102);
auto h103 = callbackList.append(103);
auto h104 = callbackList.append(104);
SECTION("remove front") {
callbackList.remove(h100);
verifyLinkedList(callbackList, std::vector<int>{ 101, 102, 103, 104 });
callbackList.remove(h100);
verifyLinkedList(callbackList, std::vector<int>{ 101, 102, 103, 104 });
}
SECTION("remove second") {
callbackList.remove(h101);
verifyLinkedList(callbackList, std::vector<int>{ 100, 102, 103, 104 });
callbackList.remove(h101);
verifyLinkedList(callbackList, std::vector<int>{ 100, 102, 103, 104 });
}
SECTION("remove end") {
callbackList.remove(h104);
verifyLinkedList(callbackList, std::vector<int>{ 100, 101, 102, 103 });
callbackList.remove(h104);
verifyLinkedList(callbackList, std::vector<int>{ 100, 101, 102, 103 });
}
SECTION("remove nonexist") {
callbackList.remove(CL::Handle());
verifyLinkedList(callbackList, std::vector<int>{ 100, 101, 102, 103, 104 });
callbackList.remove(CL::Handle());
verifyLinkedList(callbackList, std::vector<int>{ 100, 101, 102, 103, 104 });
}
SECTION("remove all") {
callbackList.remove(h102);
callbackList.remove(h104);
callbackList.remove(h103);
callbackList.remove(h101);
callbackList.remove(h100);
verifyLinkedList(callbackList, std::vector<int>{ });
}
}
TEST_CASE("CallbackList, multi threading, append")
{
CL callbackList;
constexpr int threadCount = 256;
constexpr int taskCountPerThread = 1024 * 4;
constexpr int itemCount = threadCount * taskCountPerThread;
std::vector<int> taskList(itemCount);
std::iota(taskList.begin(), taskList.end(), 0);
std::shuffle(taskList.begin(), taskList.end(), std::mt19937(std::random_device()()));
std::vector<std::thread> threadList;
for(int i = 0; i < threadCount; ++i) {
threadList.emplace_back([i, taskCountPerThread, &callbackList, &taskList]() {
for(int k = i * taskCountPerThread; k < (i + 1) * taskCountPerThread; ++k) {
callbackList.append(taskList[k]);
}
});
}
for(auto & thread : threadList) {
thread.join();
}
taskList.clear();
std::vector<int> compareList(itemCount);
std::iota(compareList.begin(), compareList.end(), 0);
verifyDisorderedLinkedList(callbackList, compareList);
}
TEST_CASE("CallbackList, multi threading, remove")
{
CL callbackList;
// total count can't be too large because the time complixity
// of remove() is O(n) which is quite slow.
constexpr int threadCount = 128;
constexpr int taskCountPerThread = 128;
constexpr int itemCount = threadCount * taskCountPerThread;
std::vector<int> taskList(itemCount);
std::iota(taskList.begin(), taskList.end(), 0);
std::shuffle(taskList.begin(), taskList.end(), std::mt19937(std::random_device()()));
std::vector<CL::Handle> handleList;
for(const auto & item : taskList) {
handleList.push_back(callbackList.append(item));
}
std::vector<std::thread> threadList;
for(int i = 0; i < threadCount; ++i) {
threadList.emplace_back([i, taskCountPerThread, &callbackList, &handleList]() {
for(int k = i * taskCountPerThread; k < (i + 1) * taskCountPerThread; ++k) {
callbackList.remove(handleList[k]);
}
});
}
for(auto & thread : threadList) {
thread.join();
}
taskList.clear();
REQUIRE(! callbackList.head);
REQUIRE(! callbackList.tail);
}
TEST_CASE("CallbackList, multi threading, double remove")
{
CL callbackList;
// total count can't be too large because the time complixity
// of remove() is O(n) which is quite slow.
constexpr int threadCount = 128;
constexpr int taskCountPerThread = 128;
constexpr int itemCount = threadCount * taskCountPerThread;
std::vector<int> taskList(itemCount);
std::iota(taskList.begin(), taskList.end(), 0);
std::shuffle(taskList.begin(), taskList.end(), std::mt19937(std::random_device()()));
std::vector<CL::Handle> handleList;
for(const auto & item : taskList) {
handleList.push_back(callbackList.append(item));
}
std::vector<std::thread> threadList;
for(int i = 0; i < threadCount; ++i) {
threadList.emplace_back([i, taskCountPerThread, &callbackList, &handleList, threadCount]() {
// make start and end overlap other threads to so double remove.
int start = i;
int end = i + 1;
if(i > 0) {
--start;
}
else if(i < threadCount - 1) {
++end;
}
for(int k = start * taskCountPerThread; k < end * taskCountPerThread; ++k) {
callbackList.remove(handleList[k]);
}
});
}
for(auto & thread : threadList) {
thread.join();
}
taskList.clear();
REQUIRE(! callbackList.head);
REQUIRE(! callbackList.tail);
}
TEST_CASE("CallbackList, multi threading, remove by handle")
{
CL callbackList;
constexpr int threadCount = 256;
constexpr int taskCountPerThread = 1024 * 4;
constexpr int itemCount = threadCount * taskCountPerThread;
std::vector<int> taskList(itemCount);
std::iota(taskList.begin(), taskList.end(), 0);
std::shuffle(taskList.begin(), taskList.end(), std::mt19937(std::random_device()()));
std::vector<CL::Handle> handleList;
for(const auto & item : taskList) {
handleList.push_back(callbackList.append(item));
}
std::vector<std::thread> threadList;
for(int i = 0; i < threadCount; ++i) {
threadList.emplace_back([i, taskCountPerThread, &callbackList, &handleList]() {
for(int k = i * taskCountPerThread; k < (i + 1) * taskCountPerThread; ++k) {
callbackList.remove(handleList[k]);
}
});
}
for(auto & thread : threadList) {
thread.join();
}
taskList.clear();
REQUIRE(! callbackList.head);
REQUIRE(! callbackList.tail);
}
TEST_CASE("CallbackList, multi threading, double remove by handle")
{
CL callbackList;
constexpr int threadCount = 256;
constexpr int taskCountPerThread = 1024 * 4;
constexpr int itemCount = threadCount * taskCountPerThread;
std::vector<int> taskList(itemCount);
std::iota(taskList.begin(), taskList.end(), 0);
std::shuffle(taskList.begin(), taskList.end(), std::mt19937(std::random_device()()));
std::vector<CL::Handle> handleList;
for(const auto & item : taskList) {
handleList.push_back(callbackList.append(item));
}
std::vector<std::thread> threadList;
for(int i = 0; i < threadCount; ++i) {
threadList.emplace_back([i, taskCountPerThread, &callbackList, &handleList, threadCount]() {
int start = i;
int end = i + 1;
if(i > 0) {
--start;
}
else if(i < threadCount - 1) {
++end;
}
for(int k = start * taskCountPerThread; k < end * taskCountPerThread; ++k) {
callbackList.remove(handleList[k]);
}
});
}
for(auto & thread : threadList) {
thread.join();
}
taskList.clear();
REQUIRE(! callbackList.head);
REQUIRE(! callbackList.tail);
}
TEST_CASE("CallbackList, multi threading, append/double remove by handle")
{
CL callbackList;
constexpr int threadCount = 256;
constexpr int taskCountPerThread = 1024 * 4;
constexpr int itemCount = threadCount * taskCountPerThread;
std::vector<int> taskList(itemCount);
std::iota(taskList.begin(), taskList.end(), 0);
std::shuffle(taskList.begin(), taskList.end(), std::mt19937(std::random_device()()));
std::vector<CL::Handle> handleList(taskList.size());
std::vector<std::thread> threadList;
for(int i = 0; i < threadCount; ++i) {
threadList.emplace_back([i, taskCountPerThread, &callbackList, &handleList, threadCount, &taskList]() {
for(int k = i * taskCountPerThread; k < (i + 1) * taskCountPerThread; ++k) {
handleList[k] = callbackList.append(taskList[k]);
}
int start = i;
int end = i + 1;
if(i > 0) {
--start;
}
else if(i < threadCount - 1) {
++end;
}
for(int k = start * taskCountPerThread; k < end * taskCountPerThread; ++k) {
callbackList.remove(handleList[k]);
}
});
}
for(auto & thread : threadList) {
thread.join();
}
taskList.clear();
REQUIRE(! callbackList.head);
REQUIRE(! callbackList.tail);
}
TEST_CASE("CallbackList, multi threading, insert")
{
CL callbackList;
constexpr int threadCount = 256;
constexpr int taskCountPerThread = 1024;
constexpr int itemCount = threadCount * taskCountPerThread;
std::vector<int> taskList(itemCount);
std::iota(taskList.begin(), taskList.end(), 0);
std::shuffle(taskList.begin(), taskList.end(), std::mt19937(std::random_device()()));
std::vector<CL::Handle> handleList(taskList.size());
std::vector<std::thread> threadList;
for(int i = 0; i < threadCount; ++i) {
threadList.emplace_back([i, taskCountPerThread, &callbackList, &taskList, &handleList]() {
int k = i * taskCountPerThread;
for(; k < (i + 1) * taskCountPerThread / 2; ++k) {
handleList[k] = callbackList.append(taskList[k]);
}
int offset = 0;
for(; k < (i + 1) * taskCountPerThread / 2 + (i + 1) * taskCountPerThread / 4; ++k) {
handleList[k] = callbackList.insert(taskList[k], handleList[offset++]);
}
for(; k < (i + 1) * taskCountPerThread; ++k) {
handleList[k] = callbackList.insert(taskList[k], handleList[offset++]);
}
});
}
for(auto & thread : threadList) {
thread.join();
}
taskList.clear();
std::vector<int> compareList(itemCount);
std::iota(compareList.begin(), compareList.end(), 0);
verifyDisorderedLinkedList(callbackList, compareList);
}

327
tests/test_dispatch.cpp Normal file
View File

@ -0,0 +1,327 @@
// eventpp library
// Copyright (C) 2018 Wang Qi (wqking)
// Github: https://github.com/wqking/eventpp
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "test.h"
#include "eventpp/eventdispatcher.h"
#include <thread>
#include <algorithm>
#include <numeric>
#include <random>
TEST_CASE("dispatch, std::string, void (const std::string &)")
{
eventpp::EventDispatcher<std::string, void (const std::string &)> dispatcher;
int a = 1;
int b = 5;
dispatcher.appendListener("event1", [&a](const std::string &) {
a = 2;
});
dispatcher.appendListener("event1", eraseArgs1([&b]() {
b = 8;
}));
REQUIRE(a != 2);
REQUIRE(b != 8);
dispatcher.dispatch("event1");
REQUIRE(a == 2);
REQUIRE(b == 8);
}
TEST_CASE("dispatch, int, void ()")
{
eventpp::EventDispatcher<int, void ()> dispatcher;
int a = 1;
int b = 5;
dispatcher.appendListener(3, [&a]() {
a = 2;
});
dispatcher.appendListener(3, [&b]() {
b = 8;
});
REQUIRE(a != 2);
REQUIRE(b != 8);
dispatcher.dispatch(3);
REQUIRE(a == 2);
REQUIRE(b == 8);
}
TEST_CASE("add/remove by handle, int, void ()")
{
eventpp::EventDispatcher<int, void ()> dispatcher;
constexpr int event = 3;
int a = 1;
int b = 5;
decltype(dispatcher)::Handle ha;
decltype(dispatcher)::Handle hb;
ha = dispatcher.appendListener(event, [event, &a, &dispatcher, &ha, &hb]() {
a = 2;
dispatcher.removeListener(event, hb);
dispatcher.removeListener(event, ha);
});
hb = dispatcher.appendListener(event, [&b]() {
b = 8;
});
REQUIRE(ha);
REQUIRE(hb);
REQUIRE(a != 2);
REQUIRE(b != 8);
dispatcher.dispatch(event);
REQUIRE(! ha);
REQUIRE(! hb);
REQUIRE(a == 2);
REQUIRE(b != 8);
a = 1;
REQUIRE(a != 2);
REQUIRE(b != 8);
dispatcher.dispatch(event);
REQUIRE(a != 2);
REQUIRE(b != 8);
}
TEST_CASE("dispatch, add another listener inside a listener, int, void ()")
{
eventpp::EventDispatcher<int, void ()> dispatcher;
constexpr int event = 3;
int a = 1;
int b = 5;
dispatcher.appendListener(event, [&event, &a, &dispatcher, &b]() {
a = 2;
dispatcher.appendListener(event, [&event, &b]() {
b = 8;
});
});
REQUIRE(a != 2);
REQUIRE(b != 8);
dispatcher.dispatch(event);
REQUIRE(a == 2);
REQUIRE(b == 8);
}
TEST_CASE("dispatch inside dispatch, int, void ()")
{
eventpp::EventDispatcher<int, void ()> dispatcher;
constexpr int event1 = 3;
constexpr int event2 = 5;
int a = 1;
int b = 5;
decltype(dispatcher)::Handle ha;
decltype(dispatcher)::Handle hb;
ha = dispatcher.appendListener(event1, [&a, &dispatcher, event2]() {
a = 2;
dispatcher.dispatch(event2);
});
hb = dispatcher.appendListener(event2, [&b, &dispatcher, event1, event2, &ha, &hb]() {
b = 8;
dispatcher.removeListener(event1, ha);
dispatcher.removeListener(event2, hb);
});
REQUIRE(ha);
REQUIRE(hb);
REQUIRE(a != 2);
REQUIRE(b != 8);
dispatcher.dispatch(event1);
REQUIRE(! ha);
REQUIRE(! hb);
REQUIRE(a == 2);
REQUIRE(b == 8);
}
TEST_CASE("dispatch, int, void (const std::string &, int)")
{
eventpp::EventDispatcher<int, void (const std::string &, int)> dispatcher;
constexpr int event = 3;
std::vector<std::string> sList(2);
std::vector<int> iList(sList.size());
dispatcher.appendListener(event, [event, &dispatcher, &sList, &iList](const std::string & s, int i) {
sList[0] = s;
iList[0] = i;
});
dispatcher.appendListener(event, [event, &dispatcher, &sList, &iList](std::string s, const int i) {
sList[1] = s + "2";
iList[1] = i + 5;
});
REQUIRE(sList[0] != "first");
REQUIRE(sList[1] != "first2");
REQUIRE(iList[0] != 3);
REQUIRE(iList[1] != 8);
dispatcher.dispatch(event, "first", 3);
REQUIRE(sList[0] == "first");
REQUIRE(sList[1] == "first2");
REQUIRE(iList[0] == 3);
REQUIRE(iList[1] == 8);
}
TEST_CASE("dispatch, Event struct, void (const std::string &, int)")
{
struct MyEvent {
int type;
std::string message;
int param;
};
struct EventTypeGetter : public eventpp::EventGetterBase
{
using Event = int;
static int getEvent(const MyEvent & e, const std::string &, int) {
return e.type;
}
};
eventpp::EventDispatcher<EventTypeGetter, void (const MyEvent &, const std::string &, int)> dispatcher;
constexpr int event = 3;
std::vector<std::string> sList(2);
std::vector<int> iList(sList.size());
dispatcher.appendListener(event, [event, &dispatcher, &sList, &iList](const MyEvent & e, const std::string & s, int i) {
sList[0] = e.message + " " + s;
iList[0] = e.param + i;
});
dispatcher.appendListener(event, [event, &dispatcher, &sList, &iList](const MyEvent & e, std::string s, const int i) {
sList[1] = s + " " + e.message;
iList[1] = e.param * i;
});
REQUIRE(sList[0] != "Hello World");
REQUIRE(sList[1] != "World Hello");
REQUIRE(iList[0] != 8);
REQUIRE(iList[1] != 15);
dispatcher.dispatch(MyEvent{ event, "Hello", 5 }, "World", 3);
REQUIRE(sList[0] == "Hello World");
REQUIRE(sList[1] == "World Hello");
REQUIRE(iList[0] == 8);
REQUIRE(iList[1] == 15);
}
TEST_CASE("dispatch many, int, void (int)")
{
eventpp::EventDispatcher<int, void (int)> dispatcher;
constexpr int eventCount = 1024 * 64;
std::vector<int> eventList(eventCount);
std::iota(eventList.begin(), eventList.end(), 0);
std::shuffle(eventList.begin(), eventList.end(), std::mt19937(std::random_device()()));
std::vector<int> dataList(eventCount);
for(int i = 1; i < eventCount; i += 2) {
dispatcher.appendListener(eventList[i], [&dispatcher, i, &dataList](const int e) {
dataList[i] = e;
});
}
for(int i = 0; i < eventCount; i += 2) {
dispatcher.appendListener(eventList[i], [&dispatcher, i, &dataList](const int e) {
dataList[i] = e;
});
}
for(int i = 0; i < eventCount; ++i) {
dispatcher.dispatch(eventList[i]);
}
std::sort(eventList.begin(), eventList.end());
std::sort(dataList.begin(), dataList.end());
REQUIRE(eventList == dataList);
}
TEST_CASE("dispatch multi threading, int, void (int)")
{
using ED = eventpp::EventDispatcher<int, void (int)>;
ED dispatcher;
constexpr int threadCount = 256;
constexpr int eventCountPerThread = 1024 * 4;
constexpr int itemCount = threadCount * eventCountPerThread;
std::vector<int> eventList(itemCount);
std::iota(eventList.begin(), eventList.end(), 0);
std::shuffle(eventList.begin(), eventList.end(), std::mt19937(std::random_device()()));
std::vector<int> dataList(itemCount);
std::vector<ED::Handle> handleList(itemCount);
std::vector<std::thread> threadList;
for(int i = 0; i < threadCount; ++i) {
threadList.emplace_back([i, eventCountPerThread, &dispatcher, &eventList, &handleList, &dataList]() {
for(int k = i * eventCountPerThread; k < (i + 1) * eventCountPerThread; ++k) {
handleList[k] = dispatcher.appendListener(eventList[k], [&dispatcher, k, &dataList, &eventList, &handleList](const int e) {
dataList[k] += e;
dispatcher.removeListener(eventList[k], handleList[k]);
});
}
});
}
for(int i = 0; i < threadCount; ++i) {
threadList[i].join();
}
threadList.clear();
for(int i = 0; i < threadCount; ++i) {
threadList.emplace_back([i, eventCountPerThread, &dispatcher, &eventList]() {
for(int k = i * eventCountPerThread; k < (i + 1) * eventCountPerThread; ++k) {
dispatcher.dispatch(eventList[k]);
}
});
}
for(int i = 0; i < threadCount; ++i) {
threadList[i].join();
}
std::sort(eventList.begin(), eventList.end());
std::sort(dataList.begin(), dataList.end());
REQUIRE(eventList == dataList);
}

189
tests/test_queue.cpp Normal file
View File

@ -0,0 +1,189 @@
// eventpp library
// Copyright (C) 2018 Wang Qi (wqking)
// Github: https://github.com/wqking/eventpp
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "test.h"
#include "eventpp/eventdispatcher.h"
#include <thread>
#include <numeric>
#include <algorithm>
#include <random>
#include <vector>
TEST_CASE("queue, std::string, void (const std::string &)")
{
eventpp::EventDispatcher<std::string, void (const std::string &)> dispatcher;
int a = 1;
int b = 5;
dispatcher.appendListener("event1", [&a](const std::string &) {
a = 2;
});
dispatcher.appendListener("event1", eraseArgs1([&b]() {
b = 8;
}));
REQUIRE(a != 2);
REQUIRE(b != 8);
dispatcher.enqueue("event1");
dispatcher.process();
REQUIRE(a == 2);
REQUIRE(b == 8);
/*
{
eventpp::EventDispatcher<
eventpp::Policies<int, eventpp::ArgumentPassingMode::either>,
void(int, const std::string &)
> dispatcher;
// or just
//eventpp::EventDispatcher<int, void(int, const std::string &)> dispatcher;
dispatcher.dispatch(3, "hello"); // Compile OK
dispatcher.dispatch(3, 8, "hello"); // Compile OK
dispatcher.enqueue(3, "hello"); // Compile OK
dispatcher.enqueue(3, 8, "hello"); // Compile OK
}{
eventpp::EventDispatcher<
eventpp::Policies<int, eventpp::ArgumentPassingMode::includeEventType>,
void(int, const std::string &)
> dispatcher;
dispatcher.dispatch(3, "hello"); // Compile OK
//dispatcher.dispatch(3, 8, "hello"); // Compile failure
dispatcher.enqueue(3, "hello"); // Compile OK
//dispatcher.enqueue(3, 8, "hello"); // Compile failure
}{
eventpp::EventDispatcher<
eventpp::Policies<int, eventpp::ArgumentPassingMode::excludeEventType>,
void(int, const std::string &)
> dispatcher;
//dispatcher.dispatch(3, "hello"); // Compile failure
dispatcher.dispatch(3, 8, "hello"); // Compile OK
//dispatcher.enqueue(3, "hello"); // Compile failure
dispatcher.enqueue(3, 8, "hello"); // Compile OK
}
*/
}
TEST_CASE("queue, int, void ()")
{
eventpp::EventDispatcher<int, void ()> dispatcher;
int a = 1;
int b = 5;
dispatcher.appendListener(3, [&a]() {
a += 1;
});
dispatcher.appendListener(3, [&b]() {
b += 3;
});
REQUIRE(a != 2);
REQUIRE(b != 8);
dispatcher.enqueue(3);
dispatcher.process();
REQUIRE(a == 2);
REQUIRE(b == 8);
}
TEST_CASE("queue, int, void (const std::string &, int)")
{
eventpp::EventDispatcher<int, void (const std::string &, int)> dispatcher;
const int event = 3;
std::vector<std::string> sList(2);
std::vector<int> iList(sList.size());
dispatcher.appendListener(event, [&sList, &iList](const std::string & s, const int i) {
sList[0] = s;
iList[0] = i;
});
dispatcher.appendListener(event, [&sList, &iList](const std::string & s, const int i) {
sList[1] = s + "2";
iList[1] = i + 5;
});
REQUIRE(sList[0] != "first");
REQUIRE(sList[1] != "first2");
REQUIRE(iList[0] != 3);
REQUIRE(iList[1] != 8);
SECTION("Parameters") {
dispatcher.enqueue(event, "first", 3);
dispatcher.process();
REQUIRE(sList[0] == "first");
REQUIRE(sList[1] == "first2");
REQUIRE(iList[0] == 3);
REQUIRE(iList[1] == 8);
}
SECTION("Reference parameters should not be modified") {
std::string s = "first";
dispatcher.enqueue(event, s, 3);
s = "";
dispatcher.process();
REQUIRE(sList[0] == "first");
REQUIRE(sList[1] == "first2");
REQUIRE(iList[0] == 3);
REQUIRE(iList[1] == 8);
}
}
TEST_CASE("queue multi threading, int, void ()")
{
using ED = eventpp::EventDispatcher<int, void (int)>;
ED dispatcher;
constexpr int threadCount = 256;
constexpr int dataCountPerThread = 1024 * 4;
constexpr int itemCount = threadCount * dataCountPerThread;
std::vector<int> eventList(itemCount);
std::iota(eventList.begin(), eventList.end(), 0);
std::shuffle(eventList.begin(), eventList.end(), std::mt19937(std::random_device()()));
std::vector<int> dataList(itemCount);
for(int i = 0; i < itemCount; ++i) {
dispatcher.appendListener(eventList[i], [&dispatcher, i, &dataList](const int d) {
dataList[i] += d;
});
}
std::vector<std::thread> threadList;
for(int i = 0; i < threadCount; ++i) {
threadList.emplace_back([i, dataCountPerThread, &dispatcher, itemCount]() {
for(int k = i * dataCountPerThread; k < (i + 1) * dataCountPerThread; ++k) {
dispatcher.enqueue(k, 3);
}
for(int k = 0; k < 10; ++k) {
dispatcher.process();
}
});
}
for(int i = 0; i < threadCount; ++i) {
threadList[i].join();
}
std::vector<int> compareList(itemCount);
std::fill(compareList.begin(), compareList.end(), 3);
REQUIRE(dataList == compareList);
}

15
tests/testmain.cpp Normal file
View File

@ -0,0 +1,15 @@
// eventpp library
// Copyright (C) 2018 Wang Qi (wqking)
// Github: https://github.com/wqking/eventpp
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file
#include "test.h"

View File

@ -0,0 +1,135 @@
// eventpp library
// Copyright (C) 2018 Wang Qi (wqking)
// Github: https://github.com/wqking/eventpp
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Include the head
#include "eventpp/callbacklist.h"
#include "test.h"
#include <iostream>
TEST_CASE("CallbackList tutorial 1, basic")
{
std::cout << "CallbackList tutorial 1, basic" << std::endl;
// The namespace is eventpp
// the first parameter is the prototype of the listener.
eventpp::CallbackList<void ()> callbackList;
// Add a callback.
// []() {} is the callback.
// Lambda is not required, any function or std::function
// or whatever function object with the required prototype is fine.
callbackList.append([]() {
std::cout << "Got callback 1." << std::endl;
});
callbackList.append([]() {
std::cout << "Got callback 2." << std::endl;
});
// Invoke the callback list
callbackList();
}
TEST_CASE("CallbackList tutorial 2, callback with parameters")
{
std::cout << "CallbackList tutorial 2, callback with parameters" << std::endl;
// The callback list has two parameters.
eventpp::CallbackList<void (const std::string &, const bool)> callbackList;
callbackList.append([](const std::string & s, const bool b) {
std::cout << std::boolalpha << "Got callback 1, s is " << s << " b is " << b << std::endl;
});
// The callback prototype doesn't need to be exactly same as the callback list.
// It would be find as long as the arguments is compatible with the dispatcher.
callbackList.append([](std::string s, int b) {
std::cout << std::boolalpha << "Got callback 2, s is " << s << " b is " << b << std::endl;
});
// Invoke the callback list
callbackList("Hello world", true);
}
TEST_CASE("CallbackList tutorial 3, remove")
{
std::cout << "CallbackList tutorial 3, remove" << std::endl;
using CL = eventpp::CallbackList<void ()>;
CL callbackList;
CL::Handle handle2;
// Add some callbacks.
callbackList.append([]() {
std::cout << "Got callback 1." << std::endl;
});
handle2 = callbackList.append([]() {
std::cout << "Got callback 2." << std::endl;
});
callbackList.append([]() {
std::cout << "Got callback 3." << std::endl;
});
callbackList.remove(handle2);
// Invoke the callback list
// The "Got callback 2" callback should not be triggered.
callbackList();
}
TEST_CASE("CallbackList tutorial 4, for each")
{
std::cout << "CallbackList tutorial 4, for each" << std::endl;
using CL = eventpp::CallbackList<void ()>;
CL callbackList;
// Add some callbacks.
callbackList.append([]() {
std::cout << "Got callback 1." << std::endl;
});
callbackList.append([]() {
std::cout << "Got callback 2." << std::endl;
});
callbackList.append([]() {
std::cout << "Got callback 3." << std::endl;
});
// Now call forEach to remove the second callback
// The forEach callback prototype is void(const CallbackList::Handle & handle, const CallbackList::Callback & callback)
int index = 0;
callbackList.forEach([&callbackList, &index](const CL::Handle & handle, const CL::Callback & callback) {
std::cout << "forEach(Handle, Callback), invoked " << index << std::endl;
if(index == 1) {
callbackList.remove(handle);
std::cout << "forEach(Handle, Callback), removed second callback" << std::endl;
}
++index;
});
// The forEach callback prototype can also be void(const CallbackList::Handle & handle)
callbackList.forEach([&callbackList, &index](const CL::Handle & handle) {
std::cout << "forEach(Handle), invoked" << std::endl;
});
// The forEach callback prototype can also be void(const CallbackList::Callback & callback)
callbackList.forEach([&callbackList, &index](const CL::Callback & callback) {
std::cout << "forEach(Callback), invoked" << std::endl;
});
// Invoke the callback list
// The "Got callback 2" callback should not be triggered.
callbackList();
}

View File

@ -0,0 +1,152 @@
// eventpp library
// Copyright (C) 2018 Wang Qi (wqking)
// Github: https://github.com/wqking/eventpp
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Include the head
#include "eventpp/eventdispatcher.h"
#include "test.h"
#include <iostream>
TEST_CASE("EventDispatcher tutorial 1, basic")
{
std::cout << "EventDispatcher tutorial 1, basic" << std::endl;
// The namespace is eventpp
// The first template parameter int is the event type,
// the second is the prototype of the listener.
eventpp::EventDispatcher<int, void ()> dispatcher;
// Add a listener. As the type of dispatcher,
// here 3 and 5 is the event type,
// []() {} is the listener.
// Lambda is not required, any function or std::function
// or whatever function object with the required prototype is fine.
dispatcher.appendListener(3, []() {
std::cout << "Got event 3." << std::endl;
});
dispatcher.appendListener(5, []() {
std::cout << "Got event 5." << std::endl;
});
dispatcher.appendListener(5, []() {
std::cout << "Got another event 5." << std::endl;
});
// Dispatch the events, the first argument is always the event type.
dispatcher.dispatch(3);
dispatcher.dispatch(5);
}
TEST_CASE("EventDispatcher tutorial 2, listener with parameters")
{
std::cout << "EventDispatcher tutorial 2, listener with parameters" << std::endl;
// The listener has two parameters.
eventpp::EventDispatcher<int, void (const std::string &, const bool)> dispatcher;
dispatcher.appendListener(3, [](const std::string & s, const bool b) {
std::cout << std::boolalpha << "Got event 3, s is " << s << " b is " << b << std::endl;
});
// The listener prototype doesn't need to be exactly same as the dispatcher.
// It would be find as long as the arguments is compatible with the dispatcher.
dispatcher.appendListener(5, [](std::string s, int b) {
std::cout << std::boolalpha << "Got event 5, s is " << s << " b is " << b << std::endl;
});
dispatcher.appendListener(5, [](const std::string & s, const bool b) {
std::cout << std::boolalpha << "Got another event 5, s is " << s << " b is " << b << std::endl;
});
// Dispatch the events, the first argument is always the event type.
dispatcher.dispatch(3, "Hello", true);
dispatcher.dispatch(5, "World", false);
}
TEST_CASE("EventDispatcher tutorial 3, customized Event struct")
{
std::cout << "EventDispatcher tutorial 3, customized Event struct" << std::endl;
// Define an Event to hold all parameters.
struct MyEvent {
int type;
std::string message;
int param;
};
// Define an event type getter to let the dispatcher knows how to
// extract the event type.
// The getter must derive from eventpp::EventGetterBase
// The getter must have:
// 1, A type named Event indicating the event type.
// 2, A static member function named getEvent. It receives all parameters
// same as the dispatcher prototype, and returns Event.
struct MyEventTypeGetter : public eventpp::EventGetterBase
{
using Event = int;
static Event getEvent(const MyEvent & e, bool b) {
return e.type;
}
};
// Pass MyEventTypeGetter as the first template argument of EventDispatcher
eventpp::EventDispatcher<
MyEventTypeGetter,
void (const MyEvent &, bool)
> dispatcher;
// Add a listener.
// Note: the first argument, event type, is MyEventTypeGetter::Event,
// not Event
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
;
});
// Dispatch the event.
// The first argument is Event.
dispatcher.dispatch(MyEvent { 3, "Hello world", 38 }, true);
}
TEST_CASE("EventDispatcher tutorial 4, event queue")
{
std::cout << "EventDispatcher tutorial 4, event queue" << std::endl;
eventpp::EventDispatcher<int, void (const std::string &, const bool)> dispatcher;
dispatcher.appendListener(3, [](const std::string & s, const bool b) {
std::cout << std::boolalpha << "Got event 3, s is " << s << " b is " << b << std::endl;
});
// The listener prototype doesn't need to be exactly same as the dispatcher.
// It would be find as long as the arguments is compatible with the dispatcher.
dispatcher.appendListener(5, [](std::string s, int b) {
std::cout << std::boolalpha << "Got event 5, s is " << s << " b is " << b << std::endl;
});
dispatcher.appendListener(5, [](const std::string & s, const bool b) {
std::cout << std::boolalpha << "Got another event 5, s is " << s << " b is " << b << std::endl;
});
// Enqueue the events, the first argument is always the event type.
// The listeners are not triggered during enqueue.
dispatcher.enqueue(3, "Hello", true);
dispatcher.enqueue(5, "World", false);
// Process the event queue, dispatch all queued events.
dispatcher.process();
}