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

Added utility class AnyData

This commit is contained in:
wqking 2023-04-30 08:23:54 +08:00
parent a24be22f86
commit aea43b534c
10 changed files with 1122 additions and 2 deletions

368
doc/anydata.md Normal file
View File

@ -0,0 +1,368 @@
# Class AnyData reference
<!--begintoc-->
## Table Of Contents
* [Description](#a2_1)
* [Use AnyData](#a2_2)
* [Header](#a3_1)
* [Class AnyData template parameters](#a3_2)
* [Use AnyData in EventQueue, the simplest but not recommend way](#a3_3)
* [Use AnyData in EventQueue, the recommend way](#a3_4)
* [Extend with new events](#a3_5)
* [Global function](#a2_3)
* [maxSizeOf](#a3_6)
* [Tutorial](#a2_4)
<!--endtoc-->
<a id="a2_1"></a>
## Description
Class `AnyData` is a data structure that can hold any data types without any dynamic heap allocation, and `AnyData` can be passed to `EventQueue` in place of the data it holds. The purpose is to eliminate the heap allocation time which is commonly used as `std::shared_ptr`. `AnyData` can improve the performance by about 30%~50%, comparing to heap allocation with `std::shared_ptr`.
`AnyData` is not type safe. Abusing it may lead you problems. If you understand how it works, and use it properly, you can gain performance improvement.
`AnyData` should be used for extreme performance optimization, such as the core event system in a game engine. In a performance non-critical system such as desktop GUI, you may not need `AnyData`, otherwise, it's premature optimization.
`AnyData` should be only used in `EventQueue`, it's not general purpose class, don't use it for other purpose.
For example, assume we have the event class hierarchy (we will use these event classes in example code all over this document),
```c++
enum class EventType
{
// for MouseEvent
mouse,
// for KeyEvent
key,
// for MessageEvent
message,
// For a simple std::string event which is not derived from Event
text,
};
class Event
{
public:
Event() {
}
};
class MouseEvent : public Event
{
public:
MouseEvent(const int x, const int y)
: x(x), y(y)
{
}
int getX() const { return x; }
int getY() const { return y; }
private:
int x;
int y;
};
class KeyEvent : public Event
{
public:
explicit KeyEvent(const int key)
: key(key)
{
}
int getKey() const { return key; }
private:
int key;
};
class MessageEvent : public Event
{
public:
explicit MessageEvent(const std::string & message)
: message(message) {
}
std::string getMessage() const { return message; }
private:
std::string message;
};
// eventMaxSize is the maximum size of all possible types to use in AnyData, it's used to construct AnyData.
constexpr std::size_t eventMaxSize = eventpp::maxSizeOf<
Event,
KeyEvent,
MouseEvent,
MessageEvent,
std::string
>();
```
Without using `AnyData`, we need to use shared pointer to put an event into an `EventQueue`, for example,
```c++
using Queue = eventpp::EventQueue<EventType, void (const std::shared_ptr<Event> &)>;
Queue eventQueue;
eventQueue.enqueue(EventType::key, std::make_shared<KeyEvent>(123));
eventQueue.enqueue(EventType::mouse, std::make_shared<MouseEvent>(100, 200));
```
The problem of the shared pointer approach is that it needs dynamic heap allocation, and it is pretty slow.
One solution is to use a small object pool to reuse the allocated objects, another solution is `AnyData`.
Now with `AnyData`, we can eliminate the usage of shared pointer and heap allocation, for example,
```c++
using Queue = eventpp::EventQueue<EventType, void (const eventpp::AnyData<eventMaxSize> &), SomePolicies>;
Queue eventQueue;
eventQueue.enqueue(EventType::key, KeyEvent(123));
eventQueue.enqueue(EventType::mouse, MouseEvent(100, 200));
```
<a id="a2_2"></a>
## Use AnyData
<a id="a3_1"></a>
### Header
eventpp/utilities/anydata.h
<a id="a3_2"></a>
### Class AnyData template parameters
```c++
template <std::size_t maxSize>
class AnyData;
```
`AnyData` requires one constant template parameter. It's the max size of the underlying types. Any data types can be used to construct `AnyData`, as long as the data type size is not larger than `maxSize`. If it's larger, compile time error is produced.
`AnyData` uses at least `maxSize` bytes, even if the underlying data is only 1 byte long. So `AnyData` might use slightly more memory than the shared pointer solution, but also may not, because shared pointer solution has other memory overhead.
<a id="a3_3"></a>
### Use AnyData in EventQueue, the simplest but not recommend way
`AnyData` can be used as the callback arguments in EventQueue.
```c++
eventpp::EventQueue<EventType, void (const EventType, const eventpp::AnyData<eventMaxSize> &)> queue;
```
In such form, the listener function prototype must be `void (const EventType, const eventpp::AnyData<eventMaxSize> &)`, for example,
```c++
queue.appendListener(EventType::key, [](const EventType type, const eventpp::AnyData<eventMaxSize> & e) {
std::cout << "Received KeyEvent, key=" << e.get<KeyEvent>().getKey() << std::endl;
});
```
Then every listener function must receive `AnyData` as an argument. This is really bad idea because then your code is coupled with `eventpp` tightly. I highly recommend you not to do so. But if you do want to do it, here are the `AnyData` member functions which helps you to use it.
#### get
```c++
template <typename T>
const T & get() const;
```
Return a reference to the underlying data as type `T`. `AnyData` doesn't check if the underlying data is of type `T`, it simply returns a reference to the underlying data, so it's not type safe.
#### getAddress
```c++
const void * getAddress() const;
```
Return a pointer to the address of the underlying data.
#### isType
```c++
template <typename T>
bool isType() const;
```
Return true if the underlying data type is `T`, false if not.
This function compares the exactly types, it doesn't check any class hierarchy. For example, if an `AnyData` holds `KeyEvent`, then `isType<KeyEvent>()` will return true, but `isType<Event>()` will return false.
<a id="a3_4"></a>
### Use AnyData in EventQueue, the recommend way
```c++
struct Policies {
using Callback = std::function<void (const Event &)>;
};
eventpp::EventQueue<EventType, void (const EventType, const eventpp::AnyData<eventMaxSize> &, Policies)> queue;
queue.appendListener(EventType::key, [](const Event & e) {
std::cout << "Received KeyEvent, key=" << static_cast<const KeyEvent &>(e).getKey() << std::endl;
});
```
We specify the `Callback` policy as `std::function<void (const Event &)>`, and pass the policy to `EventQueue`. Now the listener functions can receive `const Event &` instead of `AnyData`. It looks more nature than the "not recommend" method.
The magic is, `AnyData` can convert to any types automatically. So when `EventQueue` passes `AnyData` to the listener, `AnyData` can cast to `const Event &` automatically.
`AnyData` can convert to reference or pointer. When it converts to reference, the reference refers to the underlying data. When it converts to pointer, the pointer points to the address of the underlying data. The special conversion of pointer allow we use unrelated data types as event arguments and receive the argument as `const void *`. For example,
```c++
struct Policies {
using Callback = std::function<void (const EventType, const void *)>;
};
eventpp::EventQueue<EventType, void (const EventType, const eventpp::AnyData<eventMaxSize> &), Policies> queue;
queue.appendListener(EventType::key, [](const EventType type, const void * e) {
assert(type == EventType::key);
std::cout << "Received KeyEvent, key=" << static_cast<const KeyEvent *>(e)->getKey() << std::endl;
});
queue.appendListener(EventType::text, [](const EventType type, const void * e) {
assert(type == EventType::text);
std::cout << "Received text event, text=" << *static_cast<const std::string *>(e) << std::endl;
});
queue.enqueue(EventType::key, KeyEvent(255));
queue.enqueue(EventType::text, std::string("This is a text"));
queue.process();
```
<a id="a3_5"></a>
### Extend with new events
`AnyData` requires compile time known max data size. What if a user needs to add new events but he can't modify the `AnyData` declaration thus can't modify the max size?
The user can add any new events as long as the data size is not larger then the max size. If any new events has larger data, the data can be put in dynamic allocated memory and hold in `std::shared_ptr`. For example,
```c++
class MyLargeEvent : public Event
{
private:
std::shared_ptr<char> myLargeGigaBytesData;
};
```
Then it works as long as `sizeof(MyLargeEvent) <= eventMaxSize`.
<a id="a2_3"></a>
## Global function
<a id="a3_6"></a>
### maxSizeOf
```c++
template <typename ...Ts>
constexpr std::size_t maxSizeOf();
```
Return the maximum size of types Ts... For example,
```c++
maxSizeOf<KeyEvent, MouseEvent, int, double>();
```
<a id="a2_4"></a>
## Tutorial
Below is the tutorial code. The complete code can be found in `tests/tutorial/tutorial_anydata.cpp`
```c++
TEST_CASE("AnyData tutorial 1, basic")
{
std::cout << std::endl << "AnyData tutorial 1, basic" << std::endl;
// Define the Policies struct, set Callback as std::function<void (const Event &)>,
// then the listener can have prototype as `void (const Event &)`.
// If we don't customize the Callback, the listener prototype have to be
// `void (const eventpp::AnyData<eventMaxSize> &)`.
// The argument is `const Event &` because all events we send in this tutorial are derived from Event.
struct Policies {
using Callback = std::function<void (const Event &)>;
};
// Construct EventQueue with Policies. Here we use `const eventpp::AnyData<eventMaxSize> &` as the
// callback argument.
eventpp::EventQueue<EventType, void (const eventpp::AnyData<eventMaxSize> &), Policies> queue;
queue.appendListener(EventType::key, [](const Event & e) {
std::cout << "Received KeyEvent, key="
<< static_cast<const KeyEvent &>(e).getKey() << std::endl;
});
queue.appendListener(EventType::mouse, [](const Event & e) {
std::cout << "Received MouseEvent, x=" << static_cast<const MouseEvent &>(e).getX()
<< " y=" << static_cast<const MouseEvent &>(e).getY() << std::endl;
});
queue.appendListener(EventType::message, [](const Event & e) {
std::cout << "Received MessageEvent, message="
<< static_cast<const MessageEvent &>(e).getMessage() << std::endl;
});
// Put events into the queue. Any data type, such as KeyEvent, MouseEvent, can be put
// as long as the data size doesn't exceed eventMaxSize.
queue.enqueue(EventType::key, KeyEvent(255));
queue.enqueue(EventType::mouse, MouseEvent(3, 5));
queue.enqueue(EventType::message, MessageEvent("Hello, AnyData"));
queue.process();
}
TEST_CASE("AnyData tutorial 2, unrelated data")
{
std::cout << std::endl << "AnyData tutorial 2, unrelated data" << std::endl;
// It's possible to send event with data that doesn't have the same base class, such as Event.
// To do so, the listener prototype must be `const void *` instead of `const Event &` in previous tutorial.
struct Policies {
using Callback = std::function<void (const EventType, const void *)>;
};
eventpp::EventQueue<
EventType,
void (const EventType, const eventpp::AnyData<eventMaxSize> &),
Policies> queue;
queue.appendListener(EventType::key, [](const EventType type, const void * e) {
REQUIRE(type == EventType::key);
std::cout << "Received KeyEvent, key=" << static_cast<const KeyEvent *>(e)->getKey() << std::endl;
});
queue.appendListener(EventType::mouse, [](const EventType type, const void * e) {
REQUIRE(type == EventType::mouse);
std::cout << "Received MouseEvent, x=" << static_cast<const MouseEvent *>(e)->getX()
<< " y=" << static_cast<const MouseEvent *>(e)->getY() << std::endl;
});
queue.appendListener(EventType::message, [](const EventType type, const void * e) {
REQUIRE(type == EventType::message);
std::cout << "Received MessageEvent, message="
<< static_cast<const MessageEvent *>(e)->getMessage() << std::endl;
});
queue.appendListener(EventType::text, [](const EventType type, const void * e) {
REQUIRE(type == EventType::text);
std::cout << "Received text event, text=" << *static_cast<const std::string *>(e) << std::endl;
});
// Send events
queue.enqueue(EventType::key, KeyEvent(255));
queue.enqueue(EventType::mouse, MouseEvent(3, 5));
queue.enqueue(EventType::message, MessageEvent("Hello, AnyData"));
// Send a std::string as the event data which doesn't derive from Event.
queue.enqueue(EventType::text, std::string("This is a text"));
queue.process();
}
TEST_CASE("AnyData tutorial 3, use AnyData in listener")
{
std::cout << std::endl << "AnyData tutorial 3, use AnyData in listener" << std::endl;
using MyData = eventpp::AnyData<eventMaxSize>;
eventpp::EventQueue<EventType, void (const EventType, const MyData &)> queue;
queue.appendListener(EventType::key, [](const EventType type, const MyData & e) {
REQUIRE(type == EventType::key);
REQUIRE(e.isType<KeyEvent>());
std::cout << "Received KeyEvent, key=" << e.get<KeyEvent>().getKey() << std::endl;
});
queue.appendListener(EventType::mouse, [](const EventType type, const MyData & e) {
REQUIRE(type == EventType::mouse);
REQUIRE(e.isType<MouseEvent>());
std::cout << "Received MouseEvent, x=" << e.get<MouseEvent>().getX()
<< " y=" << e.get<MouseEvent>().getY() << std::endl;
});
queue.appendListener(EventType::message, [](const EventType type, const MyData & e) {
REQUIRE(type == EventType::message);
REQUIRE(e.isType<MessageEvent>());
std::cout << "Received MessageEvent, message=" << e.get<MessageEvent>().getMessage() << std::endl;
});
queue.appendListener(EventType::text, [](const EventType type, const MyData & e) {
REQUIRE(type == EventType::text);
REQUIRE(e.isType<std::string>());
std::cout << "Received text event, text=" << e.get<std::string>() << std::endl;
});
queue.enqueue(EventType::key, KeyEvent(255));
queue.enqueue(EventType::mouse, MouseEvent(3, 5));
queue.enqueue(EventType::message, MessageEvent("Hello, AnyData"));
queue.enqueue(EventType::text, std::string("This is a text"));
queue.process();
}
```

View File

@ -1,5 +1,16 @@
# Overview of heterogeneous classes
<!--begintoc-->
## Table Of Contents
* [Description](#a2_1)
* [Warning](#a2_2)
* [Usage](#a2_3)
* [Headers](#a3_1)
* [Template parameters](#a3_2)
* [Differences between heterogeneous classes vs homogeneous classes](#a2_4)
<!--endtoc-->
<a id="a2_1"></a>
## Description
'CallbackList', 'EventDispatcher', and 'EventQueue' are homogeneous. All listeners must have the same prototype. For example,
@ -18,20 +29,24 @@ dispatcher.appendListener(3, []() {}); // OK
dispatcher.appendListener(3, [](std::string) {}); // OK
```
<a id="a2_2"></a>
## Warning
The heterogeneous classes are mostly for proof of concept purpose. Misusing them most likely means your application design has flaws.
You should stick with the homogeneous classes, even though sometimes the heterogeneous classes look convenient (but with overhead).
The heterogeneous classes may be not well maintained or supported in the future. You use them on your own risk.
<a id="a2_3"></a>
## Usage
<a id="a3_1"></a>
### Headers
eventpp/hetercallbacklist.h
eventpp/hetereventdispatcher.h
eventpp/hetereventqueue.h
<a id="a3_2"></a>
### Template parameters
```c++
@ -85,6 +100,7 @@ In the homo-classes, `Prototype` is a single function type such as `void ()`.
In the heter-classes, `PrototypeList` is a list of function types in `eventpp::HeterTuple`, such as `eventpp::HeterTuple<void (), void (std::string), void (int, int)>`.
Note: Ideally it would be better to use `std::tuple` instead of `eventpp::HeterTuple`, but the problem is that the tuple is instantiated in HeterEventDispatcher which cause compile error that function type can't be instantiated.
<a id="a2_4"></a>
## Differences between heterogeneous classes vs homogeneous classes
1. Heterogeneous classes has both overhead on performance and memory usage. Usually event system is the core component in an application, the performance is critical.

View File

@ -0,0 +1,215 @@
// 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 ANYDATA_H_320827729782
#define ANYDATA_H_320827729782
#include <array>
#include <type_traits>
#include <cstdint>
#include <cassert>
namespace eventpp {
namespace anydata_internal_ {
template <typename T>
void funcMoveConstruct(void * object, void * buffer);
template <typename T>
void funcFreeObject(void * object)
{
static_cast<T *>(object)->~T();
}
template <typename T>
auto doFuncCopyConstruct(void * object, void * buffer)
-> typename std::enable_if<std::is_copy_constructible<T>::value>::type
{
new (buffer) T(*(T *)object);
}
template <typename T>
auto doFuncCopyConstruct(void * object, void * buffer)
-> typename std::enable_if<! std::is_copy_constructible<T>::value>::type
{
funcMoveConstruct<T>(object, buffer);
}
template <typename T>
void funcCopyConstruct(void * object, void * buffer)
{
doFuncCopyConstruct<T>(object, buffer);
}
template <typename T>
auto doFuncMoveConstruct(void * object, void * buffer)
-> typename std::enable_if<std::is_move_constructible<T>::value>::type
{
new (buffer) T(std::move(*(T *)object));
}
template <typename T>
auto doFuncMoveConstruct(void * object, void * buffer)
-> typename std::enable_if<! std::is_move_constructible<T>::value>::type
{
static_assert(std::is_move_constructible<T>::value, "AnyData: object must be either copy or move constructible");
}
template <typename T>
void funcMoveConstruct(void * object, void * buffer)
{
doFuncMoveConstruct<T>(object, buffer);
}
struct AnyDataFunctions
{
void (*free)(void *);
void (*copyConstruct)(void *, void *);
void (*moveConstruct)(void *, void *);
};
template <typename T>
const AnyDataFunctions * doGetAnyDataFunctions()
{
static const AnyDataFunctions functions {
&funcFreeObject<T>,
&funcCopyConstruct<T>,
&funcMoveConstruct<T>
};
return &functions;
}
template <typename T>
const AnyDataFunctions * getAnyDataFunctions()
{
using U = typename std::remove_reference<typename std::remove_cv<T>::type>::type;
return doGetAnyDataFunctions<U>();
}
template <typename ...Ts>
struct MaxSizeOf;
template <typename T, typename ...Ts>
struct MaxSizeOf <T, Ts...>
{
static constexpr std::size_t otherSize = MaxSizeOf<Ts...>::value;
static constexpr std::size_t tSize = sizeof(T);
static constexpr std::size_t value = tSize > otherSize ? tSize : otherSize;
};
template <typename T>
struct MaxSizeOf <T>
{
static constexpr std::size_t value = sizeof(T);
};
} //namespace anydata_internal_
template <std::size_t maxSize>
class AnyData
{
static_assert(maxSize > 0, "AnyData: maxSize must be greater than 0");
public:
~AnyData() {
if(functions != nullptr) {
functions->free(buffer.data());
}
}
template <typename T>
AnyData(const T & object) : functions(anydata_internal_::getAnyDataFunctions<T>()), buffer() {
static_assert(sizeof(T) <= maxSize, "AnyData: object size must not be greater than maxSize");
new (buffer.data()) T(object);
}
template <typename T>
AnyData(T & object) : functions(anydata_internal_::getAnyDataFunctions<T>()), buffer() {
static_assert(sizeof(T) <= maxSize, "AnyData: object size must not be greater than maxSize");
new (buffer.data()) T(object);
}
template <typename T>
AnyData(T && object) : functions(anydata_internal_::getAnyDataFunctions<T>()), buffer() {
static_assert(sizeof(T) <= maxSize, "AnyData: object size must not be greater than maxSize");
new (buffer.data()) T(std::move(object));
}
AnyData(const AnyData & other) : functions(other.functions), buffer() {
if(functions != nullptr) {
functions->copyConstruct(other.buffer.data(), buffer.data());
}
}
AnyData(AnyData & other) : functions(other.functions), buffer() {
if(functions != nullptr) {
functions->copyConstruct(other.buffer.data(), buffer.data());
}
}
AnyData(AnyData && other) : functions(other.functions), buffer() {
if(functions != nullptr) {
functions->moveConstruct(other.buffer.data(), buffer.data());
}
}
AnyData & operator = (const AnyData & other) = delete;
AnyData & operator = (AnyData && other) = delete;
template <typename T>
const T & get() const {
return *(T *)getAddress();
}
const void * getAddress() const {
assert(functions != nullptr);
return buffer.data();
}
template <typename T>
bool isType() const {
return anydata_internal_::getAnyDataFunctions<T>() == functions;
}
template <typename T>
operator T & () const {
return *(T *)getAddress();
}
template <typename T>
operator T * () const {
return (T *)getAddress();
}
private:
const anydata_internal_::AnyDataFunctions * functions;
std::array<std::uint8_t, maxSize> buffer;
};
template <typename ...Ts>
constexpr std::size_t maxSizeOf()
{
return anydata_internal_::MaxSizeOf<Ts...>::value;
}
} //namespace eventpp
#endif

View File

@ -41,11 +41,12 @@ eventpp is a C++ event library for callbacks, event dispatcher, and event queue.
- The EventQueue can process 10M events in 1 second (10K events per millisecond).
- The CallbackList can invoke 100M callbacks in 1 second (100K callbacks per millisecond).
- The CallbackList can add/remove 5M callbacks in 1 second (5K callbacks per millisecond).
- With the helper class AnyData, it's possible to avoid heap allocation when sending events via EventQueue.
- **Flexible and easy to use**
- Listeners and events can be of any type and do not need to be inherited from any base class.
- Utilities that can ease the usage, such as auto disconnecting, one shot listener, argument type adapter, etc.
- Header only, no source file, no need to build. Does not depend on other libraries.
- Requires C++ 11.
- Only requires C++ 11.
- Written in portable and standard C++, no hacks or quirks.
## License
@ -57,7 +58,7 @@ Apache License, Version 2.0
The master branch is usable and stable.
There are some releases on Github, but usually the releases are far behind the latest code.
You should prefer to clone or fork the master branch instead of downloading the releases.
Don't worry about the large timespan between commits and releases. The library is actively maintained.
Don't worry about the large time span between commits and releases. The library is actively maintained.
The master branch is currently fully back compatible with the first version. So your project won't get any back compatible issues.
If you find any back compatible issue which is not announced, please report a bug.
@ -219,6 +220,7 @@ queue.process();
* [Policies -- configure eventpp](doc/policies.md)
* [Mixins -- extend eventpp](doc/mixins.md)
* Utilities
* [Utility class AnyData -- zero heap allocation event data in EventQueue](doc/anydata.md)
* [Utility argumentAdapter -- adapt pass-in argument types to the types of the functioning being called](doc/argumentadapter.md)
* [Utility conditionalFunctor -- pre-check the condition before calling a function](doc/conditionalfunctor.md)
* [Utility class CounterRemover -- auto remove listeners after triggered certain times](doc/counterremover.md)

View File

@ -7,6 +7,7 @@ set(SRC_BENCHMARK
b3_b5_eventqueue.cpp
b6_callbacklist_add_remove_callbacks.cpp
b7_callbacklist_vs_function_list.cpp
b8_eventqueue_anydata.cpp
)
add_executable(

View File

@ -0,0 +1,142 @@
// 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/eventqueue.h"
#include "eventpp/utilities/anydata.h"
#include <thread>
#include <vector>
namespace {
struct Event {
int type;
};
struct EventA : Event {
int a;
};
struct EventB : Event {
int b1;
int b2;
};
void doExecuteEventQueue(
const std::string & message,
const size_t queueSize,
const size_t iterateCount,
const size_t eventCount,
size_t listenerCount = 0
)
{
using SP = std::shared_ptr<Event>;
using EQ = eventpp::EventQueue<size_t, void (const SP &)>;
EQ eventQueue;
if(listenerCount == 0) {
listenerCount = eventCount;
}
for(size_t i = 0; i < listenerCount; ++i) {
eventQueue.appendListener(i % eventCount, [](const SP &) {});
}
const uint64_t time = measureElapsedTime([
queueSize,
iterateCount,
eventCount,
listenerCount,
&eventQueue
]{
for(size_t iterate = 0; iterate < iterateCount; ++iterate) {
for(size_t i = 0; i < queueSize; ++i) {
eventQueue.enqueue(i % eventCount, std::make_shared<EventA>());
eventQueue.enqueue(i % eventCount, std::make_shared<EventB>());
}
eventQueue.process();
}
});
std::cout
<< message
<< " queueSize: " << queueSize
<< " iterateCount: " << iterateCount
<< " eventCount: " << eventCount
<< " listenerCount: " << listenerCount
<< " Time: " << time
<< std::endl;
;
}
void doExecuteEventQueueWithAnyData(
const std::string & message,
const size_t queueSize,
const size_t iterateCount,
const size_t eventCount,
size_t listenerCount = 0
)
{
constexpr std::size_t maxSize = sizeof(EventB) * 2;
using Data = eventpp::AnyData<maxSize>;
struct Policies {
using Callback = std::function<void (const Event &)>;
};
using EQ = eventpp::EventQueue<size_t, void (const Data &), Policies>;
EQ eventQueue;
if(listenerCount == 0) {
listenerCount = eventCount;
}
for(size_t i = 0; i < listenerCount; ++i) {
eventQueue.appendListener(i % eventCount, [](const Event &) {});
}
const uint64_t time = measureElapsedTime([
queueSize,
iterateCount,
eventCount,
listenerCount,
&eventQueue
]{
for(size_t iterate = 0; iterate < iterateCount; ++iterate) {
for(size_t i = 0; i < queueSize; ++i) {
eventQueue.enqueue(i % eventCount, EventA());
eventQueue.enqueue(i % eventCount, EventB());
}
eventQueue.process();
}
});
std::cout
<< message
<< " queueSize: " << queueSize
<< " iterateCount: " << iterateCount
<< " eventCount: " << eventCount
<< " listenerCount: " << listenerCount
<< " Time: " << time
<< std::endl;
;
}
} //unnamed namespace
TEST_CASE("b8, EventQueue, AnyData")
{
doExecuteEventQueue("Without AnyData", 100, 1000 * 100, 100);
doExecuteEventQueueWithAnyData("With AnyData", 100, 1000 * 100, 100);
}

View File

@ -8,6 +8,7 @@ set(SRC_TUTORIAL
tutorial_hetercallbacklist.cpp
tutorial_hetereventdispatcher.cpp
tutorial_argumentadapter.cpp
tutorial_anydata.cpp
)
add_executable(

View File

@ -0,0 +1,210 @@
// 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/utilities/anydata.h"
#include "eventpp/eventqueue.h"
#include "tutorial.h"
#include <iostream>
namespace {
// In the tutorials here, we define an event class hierarchy, Event is the base class.
// Define the event types
enum class EventType
{
// for MouseEvent
mouse,
// for KeyEvent
key,
// for MessageEvent
message,
// For a simple std::string event which is not derived from Event
text,
};
class Event
{
public:
Event() {
}
};
class MouseEvent : public Event
{
public:
MouseEvent(const int x, const int y)
: x(x), y(y)
{
}
int getX() const { return x; }
int getY() const { return y; }
private:
int x;
int y;
};
class KeyEvent : public Event
{
public:
explicit KeyEvent(const int key)
: key(key)
{
}
int getKey() const { return key; }
private:
int key;
};
class MessageEvent : public Event
{
public:
explicit MessageEvent(const std::string & message)
: message(message) {
}
std::string getMessage() const { return message; }
private:
std::string message;
};
// eventMaxSize is the maximum size of all possible types to use in AnyData, it's used to construct AnyData.
constexpr std::size_t eventMaxSize = eventpp::maxSizeOf<
Event,
KeyEvent,
MouseEvent,
MessageEvent,
std::string
>();
TEST_CASE("AnyData tutorial 1, basic")
{
std::cout << std::endl << "AnyData tutorial 1, basic" << std::endl;
// Define the Policies struct, set Callback as std::function<void (const Event &)>,
// then the listener can have prototype as `void (const Event &)`.
// If we don't customize the Callback, the listener prototype have to be
// `void (const eventpp::AnyData<eventMaxSize> &)`.
// The argument is `const Event &` because all events we send in this tutorial are derived from Event.
struct Policies {
using Callback = std::function<void (const Event &)>;
};
// Construct EventQueue with Policies. Here we use `const eventpp::AnyData<eventMaxSize> &` as the
// callback argument.
eventpp::EventQueue<EventType, void (const eventpp::AnyData<eventMaxSize> &), Policies> queue;
queue.appendListener(EventType::key, [](const Event & e) {
std::cout << "Received KeyEvent, key="
<< static_cast<const KeyEvent &>(e).getKey() << std::endl;
});
queue.appendListener(EventType::mouse, [](const Event & e) {
std::cout << "Received MouseEvent, x=" << static_cast<const MouseEvent &>(e).getX()
<< " y=" << static_cast<const MouseEvent &>(e).getY() << std::endl;
});
queue.appendListener(EventType::message, [](const Event & e) {
std::cout << "Received MessageEvent, message="
<< static_cast<const MessageEvent &>(e).getMessage() << std::endl;
});
// Put events into the queue. Any data type, such as KeyEvent, MouseEvent, can be put
// as long as the data size doesn't exceed eventMaxSize.
queue.enqueue(EventType::key, KeyEvent(255));
queue.enqueue(EventType::mouse, MouseEvent(3, 5));
queue.enqueue(EventType::message, MessageEvent("Hello, AnyData"));
queue.process();
}
TEST_CASE("AnyData tutorial 2, unrelated data")
{
std::cout << std::endl << "AnyData tutorial 2, unrelated data" << std::endl;
// It's possible to send event with data that doesn't have the same base class, such as Event.
// To do so, the listener prototype must be `const void *` instead of `const Event &` in previous tutorial.
struct Policies {
using Callback = std::function<void (const EventType, const void *)>;
};
eventpp::EventQueue<
EventType,
void (const EventType, const eventpp::AnyData<eventMaxSize> &),
Policies> queue;
queue.appendListener(EventType::key, [](const EventType type, const void * e) {
REQUIRE(type == EventType::key);
std::cout << "Received KeyEvent, key=" << static_cast<const KeyEvent *>(e)->getKey() << std::endl;
});
queue.appendListener(EventType::mouse, [](const EventType type, const void * e) {
REQUIRE(type == EventType::mouse);
std::cout << "Received MouseEvent, x=" << static_cast<const MouseEvent *>(e)->getX()
<< " y=" << static_cast<const MouseEvent *>(e)->getY() << std::endl;
});
queue.appendListener(EventType::message, [](const EventType type, const void * e) {
REQUIRE(type == EventType::message);
std::cout << "Received MessageEvent, message="
<< static_cast<const MessageEvent *>(e)->getMessage() << std::endl;
});
queue.appendListener(EventType::text, [](const EventType type, const void * e) {
REQUIRE(type == EventType::text);
std::cout << "Received text event, text=" << *static_cast<const std::string *>(e) << std::endl;
});
// Send events
queue.enqueue(EventType::key, KeyEvent(255));
queue.enqueue(EventType::mouse, MouseEvent(3, 5));
queue.enqueue(EventType::message, MessageEvent("Hello, AnyData"));
// Send a std::string as the event data which doesn't derive from Event.
queue.enqueue(EventType::text, std::string("This is a text"));
queue.process();
}
TEST_CASE("AnyData tutorial 3, use AnyData in listener")
{
std::cout << std::endl << "AnyData tutorial 3, use AnyData in listener" << std::endl;
using MyData = eventpp::AnyData<eventMaxSize>;
eventpp::EventQueue<EventType, void (const EventType, const MyData &)> queue;
queue.appendListener(EventType::key, [](const EventType type, const MyData & e) {
REQUIRE(type == EventType::key);
REQUIRE(e.isType<KeyEvent>());
std::cout << "Received KeyEvent, key=" << e.get<KeyEvent>().getKey() << std::endl;
});
queue.appendListener(EventType::mouse, [](const EventType type, const MyData & e) {
REQUIRE(type == EventType::mouse);
REQUIRE(e.isType<MouseEvent>());
std::cout << "Received MouseEvent, x=" << e.get<MouseEvent>().getX()
<< " y=" << e.get<MouseEvent>().getY() << std::endl;
});
queue.appendListener(EventType::message, [](const EventType type, const MyData & e) {
REQUIRE(type == EventType::message);
REQUIRE(e.isType<MessageEvent>());
std::cout << "Received MessageEvent, message=" << e.get<MessageEvent>().getMessage() << std::endl;
});
queue.appendListener(EventType::text, [](const EventType type, const MyData & e) {
REQUIRE(type == EventType::text);
REQUIRE(e.isType<std::string>());
std::cout << "Received text event, text=" << e.get<std::string>() << std::endl;
});
queue.enqueue(EventType::key, KeyEvent(255));
queue.enqueue(EventType::mouse, MouseEvent(3, 5));
queue.enqueue(EventType::message, MessageEvent("Hello, AnyData"));
queue.enqueue(EventType::text, std::string("This is a text"));
queue.process();
}
} // namespace

View File

@ -27,6 +27,7 @@ set(SRC_TEST
test_argumentadapter.cpp
test_conditionalfunctor.cpp
test_anyid.cpp
test_anydata.cpp
)
add_executable(

View File

@ -0,0 +1,164 @@
// 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/eventqueue.h"
#include "eventpp/utilities/anydata.h"
#include <vector>
#include <map>
#include <string>
#include <any>
namespace {
TEST_CASE("AnyData, maxSizeOf")
{
REQUIRE(eventpp::maxSizeOf<
std::uint8_t,
std::uint16_t,
std::uint32_t,
std::uint64_t
>() == sizeof(std::uint64_t)
);
REQUIRE(eventpp::maxSizeOf<
std::uint64_t,
std::uint32_t,
std::uint8_t,
std::uint16_t
>() == sizeof(std::uint64_t)
);
}
TEST_CASE("AnyData, default")
{
using Data = eventpp::AnyData<64>;
eventpp::EventQueue<int, void (const Data &)> queue;
queue.appendListener(3, [](const Data & value) {
REQUIRE(value.isType<int>());
REQUIRE((long)value == 5);
REQUIRE(value.get<long>() == 5);
});
queue.enqueue(3, 5);
queue.process();
}
TEST_CASE("AnyData, unique_ptr")
{
using Ptr = std::unique_ptr<int>;
using Data = eventpp::AnyData<sizeof(Ptr)>;
Data data(Ptr(new int(5)));
REQUIRE(data.isType<Ptr>());
REQUIRE(*data.get<Ptr>() == 5);
Data data2(data);
REQUIRE(data2.isType<Ptr>());
REQUIRE(*data2.get<Ptr>() == 5);
}
TEST_CASE("AnyData, shared_ptr")
{
using Ptr = std::shared_ptr<int>;
using Data = eventpp::AnyData<sizeof(Ptr)>;
Ptr ptr(std::make_shared<int>(8));
REQUIRE(ptr.use_count() == 1);
Data data(ptr);
REQUIRE(ptr.use_count() == 2);
REQUIRE(data.isType<Ptr>());
REQUIRE(*data.get<Ptr>() == 8);
Data data2(data);
REQUIRE(ptr.use_count() == 3);
REQUIRE(data2.isType<Ptr>());
REQUIRE(*data2.get<Ptr>() == 8);
REQUIRE(*data.get<Ptr>() == 8);
REQUIRE(*ptr == 8);
*ptr = 5;
REQUIRE(*data2.get<Ptr>() == 5);
REQUIRE(*data.get<Ptr>() == 5);
REQUIRE(*ptr == 5);
}
enum class EventType {
mouse = 1,
key = 2
};
struct Event {
EventType type;
explicit Event(const EventType type) : type(type) {
}
};
struct EventKey : Event {
int key;
explicit EventKey(const int key) : Event(EventType::key), key(key) {
}
};
struct EventMouse : Event {
int x;
int y;
EventMouse(const int x, const int y) : Event(EventType::mouse), x(x), y(y) {
}
};
constexpr std::size_t eventMaxSize = eventpp::maxSizeOf<
Event, EventKey, EventMouse, std::string
>();
TEST_CASE("AnyData, data")
{
using Data = eventpp::AnyData<eventMaxSize>;
eventpp::EventQueue<EventType, void (const Data &)> queue;
queue.appendListener(EventType::key, [](const Data & value) {
REQUIRE(value.isType<EventKey>());
REQUIRE(value.get<EventKey>().type == EventType::key);
REQUIRE(value.get<EventKey>().key == 5);
});
queue.enqueue(EventType::key, EventKey(5));
queue.process();
}
TEST_CASE("AnyData, Policies")
{
using Data = eventpp::AnyData<eventMaxSize>;
struct Policies {
using Callback = std::function<void (const Event &)>;
};
eventpp::EventQueue<EventType, void (const Data &), Policies> queue;
int expectedKey;
int expectedX;
int expectedY;
queue.appendListener(EventType::key, [&expectedKey](const Event & event) {
REQUIRE(event.type == EventType::key);
REQUIRE(static_cast<const EventKey &>(event).key == expectedKey);
});
queue.appendListener(EventType::mouse, [&expectedX, &expectedY](const Event & event) {
REQUIRE(event.type == EventType::mouse);
REQUIRE(static_cast<const EventMouse &>(event).x == expectedX);
REQUIRE(static_cast<const EventMouse &>(event).y == expectedY);
});
expectedKey = 5;
queue.enqueue(EventType::key, EventKey(5));
queue.process();
expectedKey = 8;
expectedX = 12345678;
expectedY = 9876532;
queue.enqueue(EventType::mouse, EventMouse(12345678, 9876532));
queue.enqueue(EventType::key, EventKey(8));
queue.process();
}
} // unnamed namespace