1
0
mirror of https://github.com/wqking/eventpp.git synced 2025-01-13 16:17:57 +08:00

Update AnyData tutorial and document

This commit is contained in:
wqking 2023-05-16 10:56:35 +08:00
parent 260a82911b
commit 9231cbb93b
2 changed files with 159 additions and 129 deletions

View File

@ -30,74 +30,74 @@ For example, assume we have the event class hierarchy (we will use these event c
```c++ ```c++
enum class EventType enum class EventType
{ {
// for MouseEvent // for MouseEvent
mouse, mouse,
// for KeyEvent // for KeyEvent
key, key,
// for MessageEvent // for MessageEvent
message, message,
// For a simple std::string event which is not derived from Event // For a simple std::string event which is not derived from Event
text, text,
}; };
class Event class Event
{ {
public: public:
Event() { Event() {
} }
}; };
class MouseEvent : public Event class MouseEvent : public Event
{ {
public: public:
MouseEvent(const int x, const int y) MouseEvent(const int x, const int y)
: x(x), y(y) : x(x), y(y)
{ {
} }
int getX() const { return x; } int getX() const { return x; }
int getY() const { return y; } int getY() const { return y; }
private: private:
int x; int x;
int y; int y;
}; };
class KeyEvent : public Event class KeyEvent : public Event
{ {
public: public:
explicit KeyEvent(const int key) explicit KeyEvent(const int key)
: key(key) : key(key)
{ {
} }
int getKey() const { return key; } int getKey() const { return key; }
private: private:
int key; int key;
}; };
class MessageEvent : public Event class MessageEvent : public Event
{ {
public: public:
explicit MessageEvent(const std::string & message) explicit MessageEvent(const std::string & message)
: message(message) { : message(message) {
} }
std::string getMessage() const { return message; } std::string getMessage() const { return message; }
private: private:
std::string message; std::string message;
}; };
// eventMaxSize is the maximum size of all possible types to use in AnyData, it's used to construct AnyData. // 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< constexpr std::size_t eventMaxSize = eventpp::maxSizeOf<
Event, Event,
KeyEvent, KeyEvent,
MouseEvent, MouseEvent,
MessageEvent, MessageEvent,
std::string std::string
>(); >();
``` ```
Without using `AnyData`, we need to use shared pointer to put an event into an `EventQueue`, for example, Without using `AnyData`, we need to use shared pointer to put an event into an `EventQueue`, for example,
@ -144,22 +144,30 @@ class AnyData;
```c++ ```c++
eventpp::EventQueue<EventType, void (const EventType, const eventpp::AnyData<eventMaxSize> &)> queue; eventpp::EventQueue<EventType, void (const EventType, const eventpp::AnyData<eventMaxSize> &)> queue;
queue.appendListener(EventType::key, [](const Event & e) { queue.appendListener(EventType::key, [](const Event & e) {
std::cout << "Received KeyEvent, key=" << static_cast<const KeyEvent &>(e).getKey() << std::endl; std::cout << "Received KeyEvent, key=" << static_cast<const KeyEvent &>(e).getKey() << std::endl;
}); });
``` ```
Since `AnyData` can convert to any types automatically, here the listener functions can receive `const Event &` instead of `AnyData`. When `EventQueue` passes `AnyData` to the listener, `AnyData` can cast to `const Event &` automatically. Since `AnyData` can convert to any types automatically, here the listener functions can receive `const Event &` instead of `AnyData`. When `EventQueue` passes `AnyData` to the listener, `AnyData` can cast to `const Event &` automatically.
Even better, we can use the concrete type as the argument directly, for example,
```c++
queue.appendListener(EventType::key, [](const KeyEvent & e) {
std::cout << "Received KeyEvent, key=" << e.getKey() << std::endl;
});
```
Note such listener should only receive the specified type, here is `KeyEvent`. If it receives other data type, it will crash your program.
`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, `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++ ```c++
eventpp::EventQueue<EventType, void (const EventType, const eventpp::AnyData<eventMaxSize> &)> queue; eventpp::EventQueue<EventType, void (const EventType, const eventpp::AnyData<eventMaxSize> &)> queue;
queue.appendListener(EventType::key, [](const EventType type, const void * e) { queue.appendListener(EventType::key, [](const EventType type, const void * e) {
assert(type == EventType::key); assert(type == EventType::key);
std::cout << "Received KeyEvent, key=" << static_cast<const KeyEvent *>(e)->getKey() << std::endl; std::cout << "Received KeyEvent, key=" << static_cast<const KeyEvent *>(e)->getKey() << std::endl;
}); });
queue.appendListener(EventType::text, [](const EventType type, const void * e) { queue.appendListener(EventType::text, [](const EventType type, const void * e) {
assert(type == EventType::text); assert(type == EventType::text);
std::cout << "Received text event, text=" << *static_cast<const std::string *>(e) << std::endl; std::cout << "Received text event, text=" << *static_cast<const std::string *>(e) << std::endl;
}); });
queue.enqueue(EventType::key, KeyEvent(255)); queue.enqueue(EventType::key, KeyEvent(255));
queue.enqueue(EventType::text, std::string("This is a text")); queue.enqueue(EventType::text, std::string("This is a text"));
@ -170,7 +178,7 @@ The listener argument can also be `AnyData` directly, for example,
```c++ ```c++
queue.appendListener(EventType::key, [](const EventType type, const eventpp::AnyData<eventMaxSize> & e) { queue.appendListener(EventType::key, [](const EventType type, const eventpp::AnyData<eventMaxSize> & e) {
std::cout << "Received KeyEvent, key=" << e.get<KeyEvent>().getKey() << std::endl; std::cout << "Received KeyEvent, key=" << e.get<KeyEvent>().getKey() << std::endl;
}); });
``` ```
@ -222,104 +230,115 @@ maxSizeOf<KeyEvent, MouseEvent, int, double>();
Below is the tutorial code. The complete code can be found in `tests/tutorial/tutorial_anydata.cpp` Below is the tutorial code. The complete code can be found in `tests/tutorial/tutorial_anydata.cpp`
```c++ ```c++
void onMessageEvent(const MessageEvent & e)
{
std::cout << "Received MessageEvent in free function, message="
<< e.getMessage() << std::endl;
}
TEST_CASE("AnyData tutorial 1, basic") TEST_CASE("AnyData tutorial 1, basic")
{ {
std::cout << std::endl << "AnyData tutorial 1, basic" << std::endl; std::cout << std::endl << "AnyData tutorial 1, basic" << std::endl;
eventpp::EventQueue<EventType, void (const eventpp::AnyData<eventMaxSize> &)> 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 // 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. // the queue definition. This works since `AnyData` can convert to any data type automatically.
queue.appendListener(EventType::key, [](const Event & e) { queue.appendListener(EventType::key, [](const Event & e) {
std::cout << "Received KeyEvent, key=" std::cout << "Received KeyEvent, key="
<< static_cast<const KeyEvent &>(e).getKey() << std::endl; << static_cast<const KeyEvent &>(e).getKey() << std::endl;
}); });
queue.appendListener(EventType::mouse, [](const Event & e) { queue.appendListener(EventType::mouse, [](const Event & e) {
std::cout << "Received MouseEvent, x=" << static_cast<const MouseEvent &>(e).getX() std::cout << "Received MouseEvent, x=" << static_cast<const MouseEvent &>(e).getX()
<< " y=" << static_cast<const MouseEvent &>(e).getY() << std::endl; << " y=" << static_cast<const MouseEvent &>(e).getY() << std::endl;
}); });
queue.appendListener(EventType::message, [](const Event & e) { // Even more convenient, the argument type can be the concrete class such as MessageEvent,
std::cout << "Received MessageEvent, message=" // but be sure the listener only receive MessageEvent. If it also receives MouseEvent,
<< static_cast<const MessageEvent &>(e).getMessage() << std::endl; // we can expect crash.
}); queue.appendListener(EventType::message, [](const MessageEvent & e) {
// Put events into the queue. Any data type, such as KeyEvent, MouseEvent, can be put std::cout << "Received MessageEvent, message="
// as long as the data size doesn't exceed eventMaxSize. << e.getMessage() << std::endl;
queue.enqueue(EventType::key, KeyEvent(255)); });
queue.enqueue(EventType::mouse, MouseEvent(3, 5)); // Not only lambda, we can also use free function, or member function as the listener.
queue.enqueue(EventType::message, MessageEvent("Hello, AnyData")); queue.appendListener(EventType::message, &onMessageEvent);
queue.process(); // 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") TEST_CASE("AnyData tutorial 2, unrelated data")
{ {
std::cout << std::endl << "AnyData tutorial 2, unrelated data" << std::endl; 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. // 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. // To do so, the listener prototype must be `const void *` instead of `const Event &` in previous tutorial.
struct Policies { struct Policies {
using Callback = std::function<void (const EventType, const void *)>; using Callback = std::function<void (const EventType, const void *)>;
}; };
eventpp::EventQueue< eventpp::EventQueue<
EventType, EventType,
void (const EventType, const eventpp::AnyData<eventMaxSize> &), void (const EventType, const eventpp::AnyData<eventMaxSize> &),
Policies> queue; Policies> queue;
queue.appendListener(EventType::key, [](const EventType type, const void * e) { queue.appendListener(EventType::key, [](const EventType type, const void * e) {
REQUIRE(type == EventType::key); REQUIRE(type == EventType::key);
std::cout << "Received KeyEvent, key=" << static_cast<const KeyEvent *>(e)->getKey() << std::endl; std::cout << "Received KeyEvent, key=" << static_cast<const KeyEvent *>(e)->getKey() << std::endl;
}); });
queue.appendListener(EventType::mouse, [](const EventType type, const void * e) { queue.appendListener(EventType::mouse, [](const EventType type, const void * e) {
REQUIRE(type == EventType::mouse); REQUIRE(type == EventType::mouse);
std::cout << "Received MouseEvent, x=" << static_cast<const MouseEvent *>(e)->getX() std::cout << "Received MouseEvent, x=" << static_cast<const MouseEvent *>(e)->getX()
<< " y=" << static_cast<const MouseEvent *>(e)->getY() << std::endl; << " y=" << static_cast<const MouseEvent *>(e)->getY() << std::endl;
}); });
queue.appendListener(EventType::message, [](const EventType type, const void * e) { queue.appendListener(EventType::message, [](const EventType type, const void * e) {
REQUIRE(type == EventType::message); REQUIRE(type == EventType::message);
std::cout << "Received MessageEvent, message=" std::cout << "Received MessageEvent, message="
<< static_cast<const MessageEvent *>(e)->getMessage() << std::endl; << static_cast<const MessageEvent *>(e)->getMessage() << std::endl;
}); });
queue.appendListener(EventType::text, [](const EventType type, const void * e) { queue.appendListener(EventType::text, [](const EventType type, const void * e) {
REQUIRE(type == EventType::text); REQUIRE(type == EventType::text);
std::cout << "Received text event, text=" << *static_cast<const std::string *>(e) << std::endl; std::cout << "Received text event, text=" << *static_cast<const std::string *>(e) << std::endl;
}); });
// Send events // Send events
queue.enqueue(EventType::key, KeyEvent(255)); queue.enqueue(EventType::key, KeyEvent(255));
queue.enqueue(EventType::mouse, MouseEvent(3, 5)); queue.enqueue(EventType::mouse, MouseEvent(3, 5));
queue.enqueue(EventType::message, MessageEvent("Hello, AnyData")); queue.enqueue(EventType::message, MessageEvent("Hello, AnyData"));
// Send a std::string as the event data which doesn't derive from Event. // 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.enqueue(EventType::text, std::string("This is a text"));
queue.process(); queue.process();
} }
TEST_CASE("AnyData tutorial 3, use AnyData in listener") TEST_CASE("AnyData tutorial 3, use AnyData in listener")
{ {
std::cout << std::endl << "AnyData tutorial 3, use AnyData in listener" << std::endl; std::cout << std::endl << "AnyData tutorial 3, use AnyData in listener" << std::endl;
using MyData = eventpp::AnyData<eventMaxSize>; using MyData = eventpp::AnyData<eventMaxSize>;
eventpp::EventQueue<EventType, void (const EventType, const MyData &)> queue; eventpp::EventQueue<EventType, void (const EventType, const MyData &)> queue;
queue.appendListener(EventType::key, [](const EventType type, const MyData & e) { queue.appendListener(EventType::key, [](const EventType type, const MyData & e) {
REQUIRE(type == EventType::key); REQUIRE(type == EventType::key);
REQUIRE(e.isType<KeyEvent>()); REQUIRE(e.isType<KeyEvent>());
std::cout << "Received KeyEvent, key=" << e.get<KeyEvent>().getKey() << std::endl; std::cout << "Received KeyEvent, key=" << e.get<KeyEvent>().getKey() << std::endl;
}); });
queue.appendListener(EventType::mouse, [](const EventType type, const MyData & e) { queue.appendListener(EventType::mouse, [](const EventType type, const MyData & e) {
REQUIRE(type == EventType::mouse); REQUIRE(type == EventType::mouse);
REQUIRE(e.isType<MouseEvent>()); REQUIRE(e.isType<MouseEvent>());
std::cout << "Received MouseEvent, x=" << e.get<MouseEvent>().getX() std::cout << "Received MouseEvent, x=" << e.get<MouseEvent>().getX()
<< " y=" << e.get<MouseEvent>().getY() << std::endl; << " y=" << e.get<MouseEvent>().getY() << std::endl;
}); });
queue.appendListener(EventType::message, [](const EventType type, const MyData & e) { queue.appendListener(EventType::message, [](const EventType type, const MyData & e) {
REQUIRE(type == EventType::message); REQUIRE(type == EventType::message);
REQUIRE(e.isType<MessageEvent>()); REQUIRE(e.isType<MessageEvent>());
std::cout << "Received MessageEvent, message=" << e.get<MessageEvent>().getMessage() << std::endl; std::cout << "Received MessageEvent, message=" << e.get<MessageEvent>().getMessage() << std::endl;
}); });
queue.appendListener(EventType::text, [](const EventType type, const MyData & e) { queue.appendListener(EventType::text, [](const EventType type, const MyData & e) {
REQUIRE(type == EventType::text); REQUIRE(type == EventType::text);
REQUIRE(e.isType<std::string>()); REQUIRE(e.isType<std::string>());
std::cout << "Received text event, text=" << e.get<std::string>() << std::endl; std::cout << "Received text event, text=" << e.get<std::string>() << std::endl;
}); });
queue.enqueue(EventType::key, KeyEvent(255)); queue.enqueue(EventType::key, KeyEvent(255));
queue.enqueue(EventType::mouse, MouseEvent(3, 5)); queue.enqueue(EventType::mouse, MouseEvent(3, 5));
queue.enqueue(EventType::message, MessageEvent("Hello, AnyData")); queue.enqueue(EventType::message, MessageEvent("Hello, AnyData"));
queue.enqueue(EventType::text, std::string("This is a text")); queue.enqueue(EventType::text, std::string("This is a text"));
queue.process(); queue.process();
} }
``` ```

View File

@ -97,6 +97,12 @@ constexpr std::size_t eventMaxSize = eventpp::maxSizeOf<
std::string std::string
>(); >();
void onMessageEvent(const MessageEvent & e)
{
std::cout << "Received MessageEvent in free function, message="
<< e.getMessage() << std::endl;
}
TEST_CASE("AnyData tutorial 1, basic") TEST_CASE("AnyData tutorial 1, basic")
{ {
std::cout << std::endl << "AnyData tutorial 1, basic" << std::endl; std::cout << std::endl << "AnyData tutorial 1, basic" << std::endl;
@ -112,10 +118,15 @@ TEST_CASE("AnyData tutorial 1, basic")
std::cout << "Received MouseEvent, x=" << static_cast<const MouseEvent &>(e).getX() std::cout << "Received MouseEvent, x=" << static_cast<const MouseEvent &>(e).getX()
<< " y=" << static_cast<const MouseEvent &>(e).getY() << std::endl; << " y=" << static_cast<const MouseEvent &>(e).getY() << std::endl;
}); });
queue.appendListener(EventType::message, [](const Event & e) { // Even more convenient, the argument type can be the concrete class such as MessageEvent,
// but be sure the listener only receive MessageEvent. If it also receives MouseEvent,
// we can expect crash.
queue.appendListener(EventType::message, [](const MessageEvent & e) {
std::cout << "Received MessageEvent, message=" std::cout << "Received MessageEvent, message="
<< static_cast<const MessageEvent &>(e).getMessage() << std::endl; << e.getMessage() << std::endl;
}); });
// Not only lambda, we can also use free function, or member function as the listener.
queue.appendListener(EventType::message, &onMessageEvent);
// Put events into the queue. Any data type, such as KeyEvent, MouseEvent, can be put // 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. // as long as the data size doesn't exceed eventMaxSize.
queue.enqueue(EventType::key, KeyEvent(255)); queue.enqueue(EventType::key, KeyEvent(255));