From 9231cbb93ba71ae5935aa503434a3faed275e8d8 Mon Sep 17 00:00:00 2001 From: wqking Date: Tue, 16 May 2023 10:56:35 +0800 Subject: [PATCH] Update AnyData tutorial and document --- doc/anydata.md | 273 +++++++++++++++------------- tests/tutorial/tutorial_anydata.cpp | 15 +- 2 files changed, 159 insertions(+), 129 deletions(-) diff --git a/doc/anydata.md b/doc/anydata.md index fe0da85..e75060a 100644 --- a/doc/anydata.md +++ b/doc/anydata.md @@ -30,74 +30,74 @@ For example, assume we have the event class hierarchy (we will use these event c ```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, + // 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() { - } + Event() { + } }; class MouseEvent : public Event { public: - MouseEvent(const int x, const int y) - : x(x), y(y) - { - } + MouseEvent(const int x, const int y) + : x(x), y(y) + { + } - int getX() const { return x; } - int getY() const { return y; } + int getX() const { return x; } + int getY() const { return y; } private: - int x; - int y; + int x; + int y; }; class KeyEvent : public Event { public: - explicit KeyEvent(const int key) - : key(key) - { - } + explicit KeyEvent(const int key) + : key(key) + { + } - int getKey() const { return key; } + int getKey() const { return key; } private: - int key; + int key; }; class MessageEvent : public Event { public: - explicit MessageEvent(const std::string & message) - : message(message) { - } + explicit MessageEvent(const std::string & message) + : message(message) { + } - std::string getMessage() const { return message; } + std::string getMessage() const { return message; } 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. constexpr std::size_t eventMaxSize = eventpp::maxSizeOf< - Event, - KeyEvent, - MouseEvent, - MessageEvent, - std::string - >(); + Event, + KeyEvent, + MouseEvent, + MessageEvent, + std::string + >(); ``` 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++ eventpp::EventQueue &)> queue; queue.appendListener(EventType::key, [](const Event & e) { - std::cout << "Received KeyEvent, key=" << static_cast(e).getKey() << std::endl; + std::cout << "Received KeyEvent, key=" << static_cast(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. +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, ```c++ eventpp::EventQueue &)> queue; queue.appendListener(EventType::key, [](const EventType type, const void * e) { - assert(type == EventType::key); - std::cout << "Received KeyEvent, key=" << static_cast(e)->getKey() << std::endl; + assert(type == EventType::key); + std::cout << "Received KeyEvent, key=" << static_cast(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(e) << std::endl; + assert(type == EventType::text); + std::cout << "Received text event, text=" << *static_cast(e) << std::endl; }); queue.enqueue(EventType::key, KeyEvent(255)); 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++ queue.appendListener(EventType::key, [](const EventType type, const eventpp::AnyData & e) { - std::cout << "Received KeyEvent, key=" << e.get().getKey() << std::endl; + std::cout << "Received KeyEvent, key=" << e.get().getKey() << std::endl; }); ``` @@ -222,104 +230,115 @@ maxSizeOf(); Below is the tutorial code. The complete code can be found in `tests/tutorial/tutorial_anydata.cpp` ```c++ +void onMessageEvent(const MessageEvent & e) +{ + std::cout << "Received MessageEvent in free function, message=" + << e.getMessage() << std::endl; +} + 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 &)> 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(e).getKey() << std::endl; - }); - queue.appendListener(EventType::mouse, [](const Event & e) { - std::cout << "Received MouseEvent, x=" << static_cast(e).getX() - << " y=" << static_cast(e).getY() << std::endl; - }); - queue.appendListener(EventType::message, [](const Event & e) { - std::cout << "Received MessageEvent, message=" - << static_cast(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(); + eventpp::EventQueue &)> 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(e).getKey() << std::endl; + }); + queue.appendListener(EventType::mouse, [](const Event & e) { + std::cout << "Received MouseEvent, x=" << static_cast(e).getX() + << " y=" << static_cast(e).getY() << std::endl; + }); + // 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=" + << 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 + // 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; + 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; - }; - eventpp::EventQueue< - EventType, - void (const EventType, const eventpp::AnyData &), - Policies> queue; - queue.appendListener(EventType::key, [](const EventType type, const void * e) { - REQUIRE(type == EventType::key); - std::cout << "Received KeyEvent, key=" << static_cast(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(e)->getX() - << " y=" << static_cast(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(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(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(); + // 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; + }; + eventpp::EventQueue< + EventType, + void (const EventType, const eventpp::AnyData &), + Policies> queue; + queue.appendListener(EventType::key, [](const EventType type, const void * e) { + REQUIRE(type == EventType::key); + std::cout << "Received KeyEvent, key=" << static_cast(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(e)->getX() + << " y=" << static_cast(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(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(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; + std::cout << std::endl << "AnyData tutorial 3, use AnyData in listener" << std::endl; - using MyData = eventpp::AnyData; - eventpp::EventQueue queue; - queue.appendListener(EventType::key, [](const EventType type, const MyData & e) { - REQUIRE(type == EventType::key); - REQUIRE(e.isType()); - std::cout << "Received KeyEvent, key=" << e.get().getKey() << std::endl; - }); - queue.appendListener(EventType::mouse, [](const EventType type, const MyData & e) { - REQUIRE(type == EventType::mouse); - REQUIRE(e.isType()); - std::cout << "Received MouseEvent, x=" << e.get().getX() - << " y=" << e.get().getY() << std::endl; - }); - queue.appendListener(EventType::message, [](const EventType type, const MyData & e) { - REQUIRE(type == EventType::message); - REQUIRE(e.isType()); - std::cout << "Received MessageEvent, message=" << e.get().getMessage() << std::endl; - }); - queue.appendListener(EventType::text, [](const EventType type, const MyData & e) { - REQUIRE(type == EventType::text); - REQUIRE(e.isType()); - std::cout << "Received text event, text=" << e.get() << 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(); + using MyData = eventpp::AnyData; + eventpp::EventQueue queue; + queue.appendListener(EventType::key, [](const EventType type, const MyData & e) { + REQUIRE(type == EventType::key); + REQUIRE(e.isType()); + std::cout << "Received KeyEvent, key=" << e.get().getKey() << std::endl; + }); + queue.appendListener(EventType::mouse, [](const EventType type, const MyData & e) { + REQUIRE(type == EventType::mouse); + REQUIRE(e.isType()); + std::cout << "Received MouseEvent, x=" << e.get().getX() + << " y=" << e.get().getY() << std::endl; + }); + queue.appendListener(EventType::message, [](const EventType type, const MyData & e) { + REQUIRE(type == EventType::message); + REQUIRE(e.isType()); + std::cout << "Received MessageEvent, message=" << e.get().getMessage() << std::endl; + }); + queue.appendListener(EventType::text, [](const EventType type, const MyData & e) { + REQUIRE(type == EventType::text); + REQUIRE(e.isType()); + std::cout << "Received text event, text=" << e.get() << 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(); } ``` diff --git a/tests/tutorial/tutorial_anydata.cpp b/tests/tutorial/tutorial_anydata.cpp index e732d19..4cf7720 100644 --- a/tests/tutorial/tutorial_anydata.cpp +++ b/tests/tutorial/tutorial_anydata.cpp @@ -97,6 +97,12 @@ constexpr std::size_t eventMaxSize = eventpp::maxSizeOf< 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") { 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(e).getX() << " y=" << static_cast(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=" - << static_cast(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 // as long as the data size doesn't exceed eventMaxSize. queue.enqueue(EventType::key, KeyEvent(255));