mirror of
https://github.com/wqking/eventpp.git
synced 2024-12-26 15:52:40 +08:00
Added sample code and document for Tip - use C++ data type as event identifier
This commit is contained in:
parent
52703c2832
commit
6fb4615769
178
doc/tip_use_type_as_id.md
Normal file
178
doc/tip_use_type_as_id.md
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
# Tip - use C++ data type as event identifier
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
`eventpp` is based on the event identifier. Event identifiers are used to distinguish each event. The identifier can be an integer,
|
||||||
|
an enumerator, a string, etc. For example, pseudo code, `eventQueue.appendListener(5, someCallback)`, here the value `5` is the identifier.
|
||||||
|
|
||||||
|
Some developers may want to use C++ data type to distinguish each event, and an event is just a data type.
|
||||||
|
For example, pseudo code, `eventQueue.appendListener<KeyEvent>(someCallback)`, here the type `KeyEvent` represents the event, no identifier involves.
|
||||||
|
There is [an issue demanding such feature](https://github.com/wqking/eventpp/issues/60).
|
||||||
|
|
||||||
|
## Use C++ data type as event identifier
|
||||||
|
|
||||||
|
With the help of utility classes `AndId` and `AndData`, now we can simulate using C++ data type as event identifier perfectly.
|
||||||
|
Some notes on the sample code.
|
||||||
|
|
||||||
|
1, The code uses `EventQueue`. The same code can also be used with `EventDispatcher`.
|
||||||
|
2, The code is so generic that not only C++ class, but also enumerator, or even primary data types can be used.
|
||||||
|
3, Be careful that `AndData` is not type safe, you may want to examine how the safer function `safeAppendListener` works.
|
||||||
|
4, The code gives two version of `TypeIndexDigester`, you may only need to choose the one that's appropriate for your C++ compiler.
|
||||||
|
5, If you don't want to use `std::type_info`, you may use following pseudo code to simulate the RTTI. `template <T> void fakeRtti() {}`, then `&fakeRtti<TheEventType>` can be used to distinguish the data type.
|
||||||
|
|
||||||
|
## Code
|
||||||
|
|
||||||
|
The full runnable code is in file `eventpp/tests/tutorial/tip_use_type_as_id.cpp`.
|
||||||
|
|
||||||
|
```c++
|
||||||
|
// Include the headers
|
||||||
|
#include "eventpp/eventqueue.h"
|
||||||
|
#include "eventpp/utilities/anyid.h"
|
||||||
|
#include "eventpp/utilities/anydata.h"
|
||||||
|
|
||||||
|
#include <typeindex>
|
||||||
|
#include <typeinfo>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
// Follow headers are only for tutorial purpose
|
||||||
|
#include "tutorial.h"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
// We need a digester class for AnyId.
|
||||||
|
/*
|
||||||
|
// This is the C++17 version with cleaner code.
|
||||||
|
template <typename T>
|
||||||
|
struct TypeIndexDigesterCpp17
|
||||||
|
{
|
||||||
|
std::type_index operator() (const T & typeInfo) const
|
||||||
|
{
|
||||||
|
if constexpr (std::is_same<T, std::type_info>::value) {
|
||||||
|
return std::type_index(typeInfo);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return std::type_index(typeid(T));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This is the C++11 version with SFINAE.
|
||||||
|
// Basically we need to support two overloaded operator().
|
||||||
|
// The first one is `std::type_index operator() (const T &) const`,
|
||||||
|
// it's to get std::type_index from typeid(T) where T is a general type.
|
||||||
|
// The second one is `std::type_index operator() (const std::type_info &) const`,
|
||||||
|
// we don't need to apply typeid on the type_info.
|
||||||
|
template <typename T>
|
||||||
|
struct TypeIndexDigesterCpp11
|
||||||
|
{
|
||||||
|
template <typename U>
|
||||||
|
auto operator() (const U &) const
|
||||||
|
-> typename std::enable_if<! std::is_same<U, std::type_info>::value, std::type_index>::type
|
||||||
|
{
|
||||||
|
return std::type_index(typeid(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename U>
|
||||||
|
auto operator() (const U & typeInfo) const
|
||||||
|
-> typename std::enable_if<std::is_same<U, std::type_info>::value, std::type_index>::type
|
||||||
|
{
|
||||||
|
return std::type_index(typeInfo);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Define the maxSize parameter used in AnyData. 128 is an arbitrary hard coded size.
|
||||||
|
// You may want to calculate the max size of your event struct, such as,
|
||||||
|
// constexpr std::size_t eventMaxSize = eventpp::maxSizeOf<KeyEvent, MouseEvent, DrawText, Animal>();
|
||||||
|
// See document for AnyData for more information.
|
||||||
|
constexpr std::size_t eventMaxSize = 128;
|
||||||
|
// Now let's define the event queue
|
||||||
|
using TypeBasedEventQueue = eventpp::EventQueue<
|
||||||
|
eventpp::AnyId<TypeIndexDigesterCpp11>,
|
||||||
|
void(const eventpp::AnyData<eventMaxSize> &)
|
||||||
|
>;
|
||||||
|
|
||||||
|
// Note AnyData is not type safe, that means
|
||||||
|
// queue.appendListener(typeid(MouseEvent), [](const KeyEvent & event) {});
|
||||||
|
// will compile but the listener will receive wrong data and crash.
|
||||||
|
// To ensure type safety, we may introduce an auxiliary function `safeAppendListener`.
|
||||||
|
template <typename Event, typename Queue, typename Callback>
|
||||||
|
void safeAppendListener(Queue & queue, const Callback & callback)
|
||||||
|
{
|
||||||
|
// In C++17, we can use std::is_invocable to check if we can invoke callback(Event()).
|
||||||
|
// Here to be compatible with C++11, we use lambda to let the compiler perform the check.
|
||||||
|
queue.appendListener(typeid(Event), [callback](const Event & event) {
|
||||||
|
callback(event);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can use any C++ type as the event, not only class, but also enum.
|
||||||
|
struct KeyEvent { int key; };
|
||||||
|
struct MouseEvent { int x; int y; };
|
||||||
|
struct DrawText { std::string text; };
|
||||||
|
enum class Animal { dog, cat };
|
||||||
|
|
||||||
|
TEST_CASE("Tip: Use C++ type as event identifier")
|
||||||
|
{
|
||||||
|
std::cout << std::endl << "Tip: Use C++ type as event identifier" << std::endl;
|
||||||
|
|
||||||
|
TypeBasedEventQueue queue;
|
||||||
|
|
||||||
|
// Append a listener, here we use an object of KeyEvent as the event identifier.
|
||||||
|
// This calls the overload `std::type_index operator() (const T &) const` in TypeIndexDigester.
|
||||||
|
queue.appendListener(KeyEvent{}, [](const KeyEvent & event) {
|
||||||
|
std::cout << "Received KeyEvent, key=" << event.key << std::endl;
|
||||||
|
});
|
||||||
|
|
||||||
|
// You may not want to create an object only for an identifier. So here we use typeid.
|
||||||
|
// This calls the overload `std::type_index operator() (const std::type_info &) const` in TypeIndexDigester.
|
||||||
|
queue.appendListener(typeid(MouseEvent), [](const MouseEvent & event) {
|
||||||
|
std::cout << "Received MouseEvent, x=" << event.x << " y=" << event.y << std::endl;
|
||||||
|
});
|
||||||
|
queue.appendListener(typeid(DrawText), [](const DrawText & event) {
|
||||||
|
std::cout << "Received DrawText, text=" << event.text << std::endl;
|
||||||
|
});
|
||||||
|
// In above code, wrong event type may compile fine but crash your program, for example,
|
||||||
|
// queue.appendListener(typeid(DrawText), [](const KeyEvent & event) {});
|
||||||
|
|
||||||
|
// safeAppendListener is a better way to append listener. Following won't compile,
|
||||||
|
// safeAppendListener<DrawText>(queue, [](const Animal & event) {});
|
||||||
|
safeAppendListener<Animal>(queue, [](const Animal & event) {
|
||||||
|
std::cout << "Received Animal, the animal is " << (event == Animal::dog ? "dog" : "cat") << std::endl;
|
||||||
|
});
|
||||||
|
// We can even use primary data type as event.
|
||||||
|
safeAppendListener<int>(queue, [](const int event) {
|
||||||
|
std::cout << "Received int, the value is " << event << std::endl;
|
||||||
|
});
|
||||||
|
safeAppendListener<long>(queue, [](const long event) {
|
||||||
|
std::cout << "Received long, the value is " << event << std::endl;
|
||||||
|
});
|
||||||
|
|
||||||
|
// We have introduced three methods to append a listener, it's for demonstration.
|
||||||
|
// In the production code, we should only use one method and be consistent.
|
||||||
|
|
||||||
|
queue.enqueue(KeyEvent{ 9 });
|
||||||
|
queue.enqueue(KeyEvent{ 32 });
|
||||||
|
queue.enqueue(MouseEvent{ 1024, 768 });
|
||||||
|
queue.enqueue(DrawText{ "Hello" });
|
||||||
|
queue.enqueue(Animal::dog);
|
||||||
|
queue.enqueue(Animal::cat);
|
||||||
|
queue.enqueue(3);
|
||||||
|
queue.enqueue(5L);
|
||||||
|
// This won't trigger any listener since there is no listener for long long.
|
||||||
|
queue.enqueue(8LL);
|
||||||
|
|
||||||
|
queue.process();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output**
|
||||||
|
|
||||||
|
> Received KeyEvent, key=9
|
||||||
|
> Received KeyEvent, key=32
|
||||||
|
> Received MouseEvent, x=1024 y=768
|
||||||
|
> Received DrawText, text=Hello
|
||||||
|
> Received Animal, the animal is dog
|
||||||
|
> Received Animal, the animal is cat
|
||||||
|
> Received int, the value is 3
|
||||||
|
> Received long, the value is 5
|
36
readme.md
36
readme.md
@ -1,23 +1,23 @@
|
|||||||
# eventpp -- C++ library for event dispatcher and callback list
|
# eventpp -- C++ library for event dispatcher and callback list
|
||||||
|
|
||||||
- [eventpp -- C++ library for event dispatcher and callback list](#eventpp----c-library-for-event-dispatcher-and-callback-list)
|
- [eventpp -- C++ library for event dispatcher and callback list](#eventpp----c-library-for-event-dispatcher-and-callback-list)
|
||||||
- [Facts and features](#facts-and-features)
|
- [Facts and features](#facts-and-features)
|
||||||
- [License](#license)
|
- [License](#license)
|
||||||
- [Version 0.1.3](#version-013)
|
- [Version 0.1.3](#version-013)
|
||||||
- [Source code](#source-code)
|
- [Source code](#source-code)
|
||||||
- [Supported compilers](#supported-compilers)
|
- [Supported compilers](#supported-compilers)
|
||||||
- [C++ standard requirements](#c-standard-requirements)
|
- [C++ standard requirements](#c-standard-requirements)
|
||||||
- [Quick start](#quick-start)
|
- [Quick start](#quick-start)
|
||||||
- [Namespace](#namespace)
|
- [Namespace](#namespace)
|
||||||
- [Use eventpp in your project](#use-eventpp-in-your-project)
|
- [Use eventpp in your project](#use-eventpp-in-your-project)
|
||||||
- [Using CallbackList](#using-callbacklist)
|
- [Using CallbackList](#using-callbacklist)
|
||||||
- [Using EventDispatcher](#using-eventdispatcher)
|
- [Using EventDispatcher](#using-eventdispatcher)
|
||||||
- [Using EventQueue](#using-eventqueue)
|
- [Using EventQueue](#using-eventqueue)
|
||||||
- [Documentations](#documentations)
|
- [Documentations](#documentations)
|
||||||
- [Build the test code](#build-the-test-code)
|
- [Build the test code](#build-the-test-code)
|
||||||
- [Motivations](#motivations)
|
- [Motivations](#motivations)
|
||||||
- [Change log](#change-log)
|
- [Change log](#change-log)
|
||||||
- [Contributors](#contributors)
|
- [Contributors](#contributors)
|
||||||
|
|
||||||
eventpp is a C++ event library for callbacks, event dispatcher, and event queue. With eventpp you can easily implement signal and slot mechanism, publisher and subscriber pattern, or observer pattern.
|
eventpp is a C++ event library for callbacks, event dispatcher, and event queue. With eventpp you can easily implement signal and slot mechanism, publisher and subscriber pattern, or observer pattern.
|
||||||
|
|
||||||
@ -173,6 +173,8 @@ queue.process();
|
|||||||
* Miscellaneous
|
* Miscellaneous
|
||||||
* [Performance benchmarks](doc/benchmark.md)
|
* [Performance benchmarks](doc/benchmark.md)
|
||||||
* [FAQs, tricks, and tips](doc/faq.md)
|
* [FAQs, tricks, and tips](doc/faq.md)
|
||||||
|
* Tips and tricks
|
||||||
|
* [Use C++ data type as event identifier](doc/tip_use_type_as_id.md)
|
||||||
* Heterogeneous classes and functions, for proof of concept, usually you don't need them
|
* Heterogeneous classes and functions, for proof of concept, usually you don't need them
|
||||||
* [Overview of heterogeneous classes](doc/heterogeneous.md)
|
* [Overview of heterogeneous classes](doc/heterogeneous.md)
|
||||||
* [Class HeterCallbackList](doc/hetercallbacklist.md)
|
* [Class HeterCallbackList](doc/hetercallbacklist.md)
|
||||||
|
@ -9,6 +9,7 @@ set(SRC_TUTORIAL
|
|||||||
tutorial_hetereventdispatcher.cpp
|
tutorial_hetereventdispatcher.cpp
|
||||||
tutorial_argumentadapter.cpp
|
tutorial_argumentadapter.cpp
|
||||||
tutorial_anydata.cpp
|
tutorial_anydata.cpp
|
||||||
|
tip_use_type_as_id.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
add_executable(
|
add_executable(
|
||||||
@ -20,4 +21,3 @@ set(THREADS_PREFER_PTHREAD_FLAG ON)
|
|||||||
find_package(Threads REQUIRED)
|
find_package(Threads REQUIRED)
|
||||||
target_link_libraries(${TARGET_TUTORIAL} Threads::Threads)
|
target_link_libraries(${TARGET_TUTORIAL} Threads::Threads)
|
||||||
|
|
||||||
|
|
||||||
|
159
tests/tutorial/tip_use_type_as_id.cpp
Normal file
159
tests/tutorial/tip_use_type_as_id.cpp
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
// This tutorial code demonstrates how to use C++ data type as event identifier.
|
||||||
|
// The tutorial document is in https://github.com/wqking/eventpp/blob/master/doc/tip_use_type_as_id.md
|
||||||
|
|
||||||
|
// Include the headers
|
||||||
|
#include "eventpp/eventqueue.h"
|
||||||
|
#include "eventpp/utilities/anyid.h"
|
||||||
|
#include "eventpp/utilities/anydata.h"
|
||||||
|
|
||||||
|
#include <typeindex>
|
||||||
|
#include <typeinfo>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
// Follow headers are only for tutorial purpose
|
||||||
|
#include "tutorial.h"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// We need a digester class for AnyId.
|
||||||
|
/*
|
||||||
|
// This is the C++17 version with cleaner code.
|
||||||
|
template <typename T>
|
||||||
|
struct TypeIndexDigesterCpp17
|
||||||
|
{
|
||||||
|
std::type_index operator() (const T & typeInfo) const
|
||||||
|
{
|
||||||
|
if constexpr (std::is_same<T, std::type_info>::value) {
|
||||||
|
return std::type_index(typeInfo);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return std::type_index(typeid(T));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This is the C++11 version with SFINAE.
|
||||||
|
// Basically we need to support two overloaded operator().
|
||||||
|
// The first one is `std::type_index operator() (const T &) const`,
|
||||||
|
// it's to get std::type_index from typeid(T) where T is a general type.
|
||||||
|
// The second one is `std::type_index operator() (const std::type_info &) const`,
|
||||||
|
// we don't need to apply typeid on the type_info.
|
||||||
|
template <typename T>
|
||||||
|
struct TypeIndexDigesterCpp11
|
||||||
|
{
|
||||||
|
template <typename U>
|
||||||
|
auto operator() (const U &) const
|
||||||
|
-> typename std::enable_if<! std::is_same<U, std::type_info>::value, std::type_index>::type
|
||||||
|
{
|
||||||
|
return std::type_index(typeid(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename U>
|
||||||
|
auto operator() (const U & typeInfo) const
|
||||||
|
-> typename std::enable_if<std::is_same<U, std::type_info>::value, std::type_index>::type
|
||||||
|
{
|
||||||
|
return std::type_index(typeInfo);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Define the maxSize parameter used in AnyData. 128 is an arbitrary hard coded size.
|
||||||
|
// You may want to calculate the max size of your event struct, such as,
|
||||||
|
// constexpr std::size_t eventMaxSize = eventpp::maxSizeOf<KeyEvent, MouseEvent, DrawText, Animal>();
|
||||||
|
// See document for AnyData for more information.
|
||||||
|
constexpr std::size_t eventMaxSize = 128;
|
||||||
|
// Now let's define the event queue
|
||||||
|
using TypeBasedEventQueue = eventpp::EventQueue<
|
||||||
|
eventpp::AnyId<TypeIndexDigesterCpp11>,
|
||||||
|
void(const eventpp::AnyData<eventMaxSize> &)
|
||||||
|
>;
|
||||||
|
|
||||||
|
// Note AnyData is not type safe, that means
|
||||||
|
// queue.appendListener(typeid(MouseEvent), [](const KeyEvent & event) {});
|
||||||
|
// will compile but the listener will receive wrong data and crash.
|
||||||
|
// To ensure type safety, we may introduce an auxiliary function `safeAppendListener`.
|
||||||
|
template <typename Event, typename Queue, typename Callback>
|
||||||
|
void safeAppendListener(Queue & queue, const Callback & callback)
|
||||||
|
{
|
||||||
|
// In C++17, we can use std::is_invocable to check if we can invoke callback(Event()).
|
||||||
|
// Here to be compatible with C++11, we use lambda to let the compiler perform the check.
|
||||||
|
queue.appendListener(typeid(Event), [callback](const Event & event) {
|
||||||
|
callback(event);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can use any C++ type as the event, not only class, but also enum.
|
||||||
|
struct KeyEvent { int key; };
|
||||||
|
struct MouseEvent { int x; int y; };
|
||||||
|
struct DrawText { std::string text; };
|
||||||
|
enum class Animal { dog, cat };
|
||||||
|
|
||||||
|
TEST_CASE("Tip: Use C++ type as event identifier")
|
||||||
|
{
|
||||||
|
std::cout << std::endl << "Tip: Use C++ type as event identifier" << std::endl;
|
||||||
|
|
||||||
|
TypeBasedEventQueue queue;
|
||||||
|
|
||||||
|
// Append a listener, here we use an object of KeyEvent as the event identifier.
|
||||||
|
// This calls the overload `std::type_index operator() (const T &) const` in TypeIndexDigester.
|
||||||
|
queue.appendListener(KeyEvent{}, [](const KeyEvent & event) {
|
||||||
|
std::cout << "Received KeyEvent, key=" << event.key << std::endl;
|
||||||
|
});
|
||||||
|
|
||||||
|
// You may not want to create an object only for an identifier. So here we use typeid.
|
||||||
|
// This calls the overload `std::type_index operator() (const std::type_info &) const` in TypeIndexDigester.
|
||||||
|
queue.appendListener(typeid(MouseEvent), [](const MouseEvent & event) {
|
||||||
|
std::cout << "Received MouseEvent, x=" << event.x << " y=" << event.y << std::endl;
|
||||||
|
});
|
||||||
|
queue.appendListener(typeid(DrawText), [](const DrawText & event) {
|
||||||
|
std::cout << "Received DrawText, text=" << event.text << std::endl;
|
||||||
|
});
|
||||||
|
// In above code, wrong event type may compile fine but crash your program, for example,
|
||||||
|
// queue.appendListener(typeid(DrawText), [](const KeyEvent & event) {});
|
||||||
|
|
||||||
|
// safeAppendListener is a better way to append listener. Following won't compile,
|
||||||
|
// safeAppendListener<DrawText>(queue, [](const Animal & event) {});
|
||||||
|
safeAppendListener<Animal>(queue, [](const Animal & event) {
|
||||||
|
std::cout << "Received Animal, the animal is " << (event == Animal::dog ? "dog" : "cat") << std::endl;
|
||||||
|
});
|
||||||
|
// We can even use primary data type as event.
|
||||||
|
safeAppendListener<int>(queue, [](const int event) {
|
||||||
|
std::cout << "Received int, the value is " << event << std::endl;
|
||||||
|
});
|
||||||
|
safeAppendListener<long>(queue, [](const long event) {
|
||||||
|
std::cout << "Received long, the value is " << event << std::endl;
|
||||||
|
});
|
||||||
|
|
||||||
|
// We have introduced three methods to append a listener, it's for demonstration.
|
||||||
|
// In the production code, we should only use one method and be consistent.
|
||||||
|
|
||||||
|
queue.enqueue(KeyEvent{ 9 });
|
||||||
|
queue.enqueue(KeyEvent{ 32 });
|
||||||
|
queue.enqueue(MouseEvent{ 1024, 768 });
|
||||||
|
queue.enqueue(DrawText{ "Hello" });
|
||||||
|
queue.enqueue(Animal::dog);
|
||||||
|
queue.enqueue(Animal::cat);
|
||||||
|
queue.enqueue(3);
|
||||||
|
queue.enqueue(5L);
|
||||||
|
// This won't trigger any listener since there is no listener for long long.
|
||||||
|
queue.enqueue(8LL);
|
||||||
|
|
||||||
|
queue.process();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user