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

Removed the need of Callback policy in AnyData from tutorial and document

This commit is contained in:
wqking 2023-05-05 08:59:28 +08:00
parent 7afab328dd
commit 6c3fe7899b
4 changed files with 47 additions and 83 deletions

View File

@ -3,19 +3,18 @@
## Table Of Contents
- [Class AnyData reference](#class-anydata-reference)
- [Table Of Contents](#table-of-contents)
- [Description](#description)
- [Use AnyData](#use-anydata)
- [Header](#header)
- [Class AnyData template parameters](#class-anydata-template-parameters)
- [Use AnyData in EventQueue, the simplest but not recommend way](#use-anydata-in-eventqueue-the-simplest-but-not-recommend-way)
- [get](#get)
- [getAddress](#getaddress)
- [isType](#istype)
- [Use AnyData in EventQueue, the recommend way](#use-anydata-in-eventqueue-the-recommend-way)
- [Global function](#global-function)
- [maxSizeOf](#maxsizeof)
- [Tutorial](#tutorial)
- [Table Of Contents](#table-of-contents)
- [Description](#description)
- [Use AnyData](#use-anydata)
- [Header](#header)
- [Class AnyData template parameters](#class-anydata-template-parameters)
- [Use AnyData in EventQueue](#use-anydata-in-eventqueue)
- [get](#get)
- [getAddress](#getaddress)
- [isType](#istype)
- [Global function](#global-function)
- [maxSizeOf](#maxsizeof)
- [Tutorial](#tutorial)
<!--endtoc-->
## Description
@ -116,7 +115,7 @@ One solution is to use a small object pool to reuse the allocated objects, anoth
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>;
using Queue = eventpp::EventQueue<EventType, void (const eventpp::AnyData<eventMaxSize> &)>;
Queue eventQueue;
eventQueue.enqueue(EventType::key, KeyEvent(123));
eventQueue.enqueue(EventType::mouse, MouseEvent(100, 200));
@ -138,15 +137,36 @@ class AnyData;
`AnyData` requires one constant template parameter. It's the max size of the underlying types. Any data types with any data size can be used to construct `AnyData`. If the data size is not larger than `maxSize`, the data is stored inside `AnyData`. If it's larger, the data is stored on the heap with dynamic allocation.
`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.
### Use AnyData in EventQueue, the simplest but not recommend way
### Use AnyData in EventQueue
`AnyData` can be used as the callback arguments in EventQueue.
```c++
eventpp::EventQueue<EventType, void (const EventType, const eventpp::AnyData<eventMaxSize> &)> queue;
queue.appendListener(EventType::key, [](const Event & e) {
std::cout << "Received KeyEvent, key=" << static_cast<const KeyEvent &>(e).getKey() << std::endl;
});
```
In such form, the listener function prototype must be `void (const EventType, const eventpp::AnyData<eventMaxSize> &)`, for example,
Since `AnyData` can convert to any types automatically, here the listener functions can receive `const Event &` instead of `AnyData`. It looks more nature than the "not recommend" method. 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++
eventpp::EventQueue<EventType, void (const EventType, const eventpp::AnyData<eventMaxSize> &)> 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();
```
The listener argument can also be `AnyData` directly, for example,
```c++
queue.appendListener(EventType::key, [](const EventType type, const eventpp::AnyData<eventMaxSize> & e) {
@ -154,7 +174,7 @@ queue.appendListener(EventType::key, [](const EventType type, const eventpp::Any
});
```
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.
I would not recommend it. It 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
@ -183,40 +203,6 @@ 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.
### 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();
```
## Global function
### maxSizeOf
@ -240,17 +226,9 @@ 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;
eventpp::EventQueue<EventType, void (const eventpp::AnyData<eventMaxSize> &)> queue;
// Append a listener. Note the listener argument is `const Event &` which is different with the prototype in
// the queue definition. This works since `AnyData` can convert to any data type automatically.
queue.appendListener(EventType::key, [](const Event & e) {
std::cout << "Received KeyEvent, key="
<< static_cast<const KeyEvent &>(e).getKey() << std::endl;

View File

@ -103,10 +103,7 @@ void doExecuteEventQueueWithAnyData(
{
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>;
using EQ = eventpp::EventQueue<size_t, void (const Data &)>;
EQ eventQueue;
if(listenerCount == 0) {

View File

@ -101,17 +101,9 @@ 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;
eventpp::EventQueue<EventType, void (const eventpp::AnyData<eventMaxSize> &)> queue;
// Append a listener. Note the listener argument is `const Event &` which is different with the prototype in
// the queue definition. This works since `AnyData` can convert to any data type automatically.
queue.appendListener(EventType::key, [](const Event & e) {
std::cout << "Received KeyEvent, key="
<< static_cast<const KeyEvent &>(e).getKey() << std::endl;

View File

@ -223,7 +223,7 @@ constexpr std::size_t eventMaxSize = eventpp::maxSizeOf<
Event, EventKey, EventMouse, std::string
>();
TEST_CASE("AnyData, data")
TEST_CASE("AnyData, use AnyData as callback argument")
{
using Data = eventpp::AnyData<eventMaxSize>;
eventpp::EventQueue<EventType, void (const Data &)> queue;
@ -236,13 +236,10 @@ TEST_CASE("AnyData, data")
queue.process();
}
TEST_CASE("AnyData, Policies")
TEST_CASE("AnyData, use Event as callback argument")
{
using Data = eventpp::AnyData<eventMaxSize>;
struct Policies {
using Callback = std::function<void (const Event &)>;
};
eventpp::EventQueue<EventType, void (const Data &), Policies> queue;
eventpp::EventQueue<EventType, void (const Data &)> queue;
int expectedKey;
int expectedX;
int expectedY;