mirror of
https://github.com/wqking/eventpp.git
synced 2024-12-25 23:30:49 +08:00
Added document Tip - samples for typical use cases
This commit is contained in:
parent
e26fa6276c
commit
8b074920ee
63
doc/faq.md
63
doc/faq.md
@ -1,20 +1,15 @@
|
||||
# Frequently Asked Questions
|
||||
|
||||
<!--begintoc-->
|
||||
## Table Of Contents
|
||||
- [Frequently Asked Questions](#frequently-asked-questions)
|
||||
- [Why can't rvalue reference be used as callback prototype in EventDispatcher and CallbackList? Such as CallbackList\<void (int \&\&)\>](#why-cant-rvalue-reference-be-used-as-callback-prototype-in-eventdispatcher-and-callbacklist-such-as-callbacklistvoid-int-)
|
||||
- [Can the callback prototype have return value? Such as CallbackList\<std::string (const std::string \&, int)\>?](#can-the-callback-prototype-have-return-value-such-as-callbackliststdstring-const-stdstring--int)
|
||||
- [Why can't callback prototype be function pointer such as CallbackList\<void (\*)()\>?](#why-cant-callback-prototype-be-function-pointer-such-as-callbacklistvoid-)
|
||||
- [Why aren't there APIs to remove listeners directly from an EventDispatcher? Why do we have to remove by handle?](#why-arent-there-apis-to-remove-listeners-directly-from-an-eventdispatcher-why-do-we-have-to-remove-by-handle)
|
||||
- [Isn't CallbackList equivalent to std::vector? It's simple for me to use std::vector directly.](#isnt-callbacklist-equivalent-to-stdvector-its-simple-for-me-to-use-stdvector-directly)
|
||||
- [I want to inherit my class from EventDispatcher, but EventDispatcher's destructor is not virtual?](#i-want-to-inherit-my-class-from-eventdispatcher-but-eventdispatchers-destructor-is-not-virtual)
|
||||
- [How to automatically remove listeners when certain object is destroyed (aka auto disconnection)?](#how-to-automatically-remove-listeners-when-certain-object-is-destroyed-aka-auto-disconnection)
|
||||
- [How to integrate EventQueue with boost::asio::io\_service?](#how-to-integrate-eventqueue-with-boostasioio_service)
|
||||
|
||||
* [Why can't rvalue reference be used as callback prototype in EventDispatcher and CallbackList? Such as CallbackList<void (int &&)>](#a2_1)
|
||||
* [Can the callback prototype have return value? Such as CallbackList<std::string (const std::string &, int)>?](#a2_2)
|
||||
* [Why can't callback prototype be function pointer such as CallbackList<void (*)()>?](#a2_3)
|
||||
* [Why aren't there APIs to remove listeners directly from an EventDispatcher? Why do we have to remove by handle?](#a2_4)
|
||||
* [Isn't CallbackList equivalent to std::vector<Callback>? It's simple for me to use std::vector<Callback> directly.](#a2_5)
|
||||
* [I want to inherit my class from EventDispatcher, but EventDispatcher's destructor is not virtual?](#a2_6)
|
||||
* [How to automatically remove listeners when certain object is destroyed (aka auto disconnection)?](#a2_7)
|
||||
* [How to process all EventQueue instances in a single main loop?](#a2_8)
|
||||
* [How to integrate EventQueue with boost::asio::io_service?](#a2_9)
|
||||
<!--endtoc-->
|
||||
|
||||
<a id="a2_1"></a>
|
||||
## Why can't rvalue reference be used as callback prototype in EventDispatcher and CallbackList? Such as CallbackList<void (int &&)>
|
||||
|
||||
```c++
|
||||
@ -26,29 +21,24 @@ The above code doesn't compile. This is intended design and not a bug.
|
||||
A rvalue reference `std::string &&` means the argument can be moved by the callback and become invalid (or empty). Keep in mind CallbackList invokes many callbacks one by one. So what happens if the first callback moves the argument and the other callbacks get empty value? In above code example, that means the first callback sees the value "Hello" and moves it, then the other callbacks will see empty string, not "Hello"!
|
||||
To avoid such potential bugs, rvalue reference is forbidden deliberately.
|
||||
|
||||
<a id="a2_2"></a>
|
||||
## Can the callback prototype have return value? Such as CallbackList<std::string (const std::string &, int)>?
|
||||
|
||||
Yes you can, but both EventDispatcher and CallbackList just discard the return value. It's not efficient nor useful to return value from EventDispatcher and CallbackList.
|
||||
|
||||
<a id="a2_3"></a>
|
||||
## Why can't callback prototype be function pointer such as CallbackList<void (*)()>?
|
||||
|
||||
It's rather easy to support function pointer, but it's year 2018 at the time written, and there is proposal for C++20 standard already, so let's use modern C++ features. Stick with function type `void ()` instead of function pointer `void (*)()`.
|
||||
|
||||
<a id="a2_4"></a>
|
||||
## Why aren't there APIs to remove listeners directly from an EventDispatcher? Why do we have to remove by handle?
|
||||
|
||||
Both `EventDispatcher::removeListener(const Event & event, const Handle handle)` and `CallbackList::remove(const Handle handle)` requires the handle of a listener is passed in. So why can't we pass the listener object directly? The reason is, it's not guaranteed that the underlying callback storage is comparable while removing a listener object requires the comparable ability. Indeed the default callback storage, `std::function` is not comparable.
|
||||
If we use some customized callback storage and we are sure it's comparable, there is free functions 'removeListener' in [utility APIs](eventutil.md).
|
||||
|
||||
<a id="a2_5"></a>
|
||||
## Isn't CallbackList equivalent to std::vector<Callback>? It's simple for me to use std::vector<Callback> directly.
|
||||
|
||||
`CallbackList` works like a `std::vector<Callback>`. But one common usage is to implement one-shot callback that a callback removes itself from the callback list when it's invoked. In such case a simple `std::vector<Callback>` will bang and crash.
|
||||
With `CallbackList` a callback can be removed at any time, even when the callback list is under invoking.
|
||||
|
||||
<a id="a2_6"></a>
|
||||
## I want to inherit my class from EventDispatcher, but EventDispatcher's destructor is not virtual?
|
||||
|
||||
It's intended not to use any virtual functions in eventpp to avoid bloating the code size. New class can still inherit from EventDispatcher, as long as the object is not deleted via a pointer to EventDispatcher, which will cause resource leak. If you need to delete object via pointer to base class, make your own base class that inherits from EventDispatcher, and make the base class destructor virtual.
|
||||
@ -69,45 +59,10 @@ MyEventDispatcher * myObject = new MyClass();
|
||||
delete myObject;
|
||||
```
|
||||
|
||||
<a id="a2_7"></a>
|
||||
## How to automatically remove listeners when certain object is destroyed (aka auto disconnection)?
|
||||
|
||||
[Use utility class ScopedRemover](scopedremover.md)
|
||||
|
||||
<a id="a2_8"></a>
|
||||
## How to process all EventQueue instances in a single main loop?
|
||||
|
||||
It's common to have a single main loop in a GUI or game application, and there are various EventQueue instances in the system. How to process all the EventQueue instances? Let's see some pseudo code first,
|
||||
|
||||
```c++
|
||||
|
||||
// Here mainLoopTasks is global for simplify, in real application it can be in some object and passed around
|
||||
eventpp::CallbackList<void ()> mainLoopTasks;
|
||||
|
||||
void mainLoop()
|
||||
{
|
||||
for(;;) {
|
||||
// Do any stuff in the loop
|
||||
|
||||
mainLoopTasks();
|
||||
}
|
||||
}
|
||||
|
||||
class MyEventQueue : public eventpp::EventQueue<blah blah>
|
||||
{
|
||||
public:
|
||||
MyEventQueue()
|
||||
{
|
||||
mainLoopTasks.append([this]() {
|
||||
process();
|
||||
});
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
The idea is, the main loop invoke a callback list in each loop, and each event queue registers its process to the callback list.
|
||||
|
||||
<a id="a2_9"></a>
|
||||
## How to integrate EventQueue with boost::asio::io_service?
|
||||
|
||||
A common use case is there are multiple threads that executing boost::asio::io_service::run(). To integrate EventQueue with boost asio, we need to replace `run()` with `poll()` to avoid blocking. So a typical thread will look like,
|
||||
|
159
doc/tip_sample_use_cases.md
Normal file
159
doc/tip_sample_use_cases.md
Normal file
@ -0,0 +1,159 @@
|
||||
# Tip - samples for typical use cases
|
||||
|
||||
- [Tip - samples for typical use cases](#tip---samples-for-typical-use-cases)
|
||||
- [Overview](#overview)
|
||||
- [Implement signals and slots](#implement-signals-and-slots)
|
||||
- [Implement event loop in GUI application, similar to Win32 message processing or Qt event loop](#implement-event-loop-in-gui-application-similar-to-win32-message-processing-or-qt-event-loop)
|
||||
- [Process multiple EventQueue instances in a single main loop](#process-multiple-eventqueue-instances-in-a-single-main-loop)
|
||||
|
||||
## Overview
|
||||
|
||||
Here lists several samples of typical use cases when using `eventpp`. The purpose is to inspire you, not to restrict you to do so.
|
||||
All code are pseudo code, they can't be compiled.
|
||||
|
||||
## Implement signals and slots
|
||||
|
||||
Signal/slot is widely used in Qt, and there are some C++ libraries implement signal/slot.
|
||||
`eventpp` does not expose an explicit signal/slot concept, although it has equivalent functionality covered by `CallbackList`.
|
||||
A `CallbackList` object is a signal. Appending a callback to it is connecting a slot to the signal, and the callback is the slot.
|
||||
|
||||
Below is the simplified Qt example code got from [Qt document](https://doc.qt.io/qt-6/signalsandslots.html),
|
||||
|
||||
```c++
|
||||
class Counter
|
||||
{
|
||||
public slots:
|
||||
void setValue(int value) {
|
||||
m_value = value;
|
||||
emit valueChanged(value);
|
||||
}
|
||||
|
||||
signals:
|
||||
void valueChanged(int newValue);
|
||||
|
||||
private:
|
||||
int m_value;
|
||||
};
|
||||
|
||||
Counter a, b;
|
||||
QObject::connect(&a, &Counter::valueChanged, &b, &Counter::setValue);
|
||||
a.setValue(12); // a.m_value == 12, b.m_value == 12
|
||||
b.setValue(48); // a.m_value == 12, b.m_value == 48
|
||||
```
|
||||
|
||||
Below is the equivalence in `eventpp`.
|
||||
|
||||
```c++
|
||||
class Counter
|
||||
{
|
||||
public:
|
||||
void setValue(int value) {
|
||||
m_value = value;
|
||||
// invoke the CallbackList
|
||||
valueChanged(value);
|
||||
}
|
||||
|
||||
// The "signal"
|
||||
eventpp::CallbackList<void (int)> valueChanged;
|
||||
|
||||
private:
|
||||
int m_value;
|
||||
};
|
||||
|
||||
Counter a, b;
|
||||
// connect the "signal and slot"
|
||||
a.valueChanged.append([&b](int value) {
|
||||
b.setValue(value);
|
||||
});
|
||||
a.setValue(12); // a.m_value == 12, b.m_value == 12
|
||||
b.setValue(48); // a.m_value == 12, b.m_value == 48
|
||||
```
|
||||
|
||||
## Implement event loop in GUI application, similar to Win32 message processing or Qt event loop
|
||||
|
||||
Both Win32 message processing (using GetMessage and DispatchMessage) and Qt event loop are not typical observer pattern. They do not use listeners, but instead send events to the target widget. This is done in the event loop. If you are making GUI system, you may want to mimic the event loop found in Windows API and Qt. Below is the pseduo code for implementing such an event loop using `eventpp`.
|
||||
|
||||
```c++
|
||||
|
||||
struct Event
|
||||
{
|
||||
Widget * widget; // This is similar to the `HWND hWnd` parameter in Win32 PostMessageA function.
|
||||
int eventType; // Similar to `UINT Msg` in Win32 PostMessageA.
|
||||
AnyOtherData; // Similar to `WPARAM wParam` and `LPARAM lParam` in Win32 PostMessageA.
|
||||
}
|
||||
|
||||
using MainEventQueue = eventpp::EventQueue<int, void (const std::shared_ptr<Event>)>
|
||||
MainEventQueue mainEventQueue;
|
||||
|
||||
Widget widgetA;
|
||||
|
||||
// Put a message into the queue. Equivalent to PostMessageA(&widgetA, MOUSE_DOWN, x, y)
|
||||
mainEventQueue.enqueue(shared_pointer_of MouseEvent { widget = &widgetA, eventType = MOUSE_DOWN, AnyOtherData = x/y location, etc } );
|
||||
|
||||
// Dispatch immediately without using the queue. Equivalent to SendMessageA(&widgetA, MOUSE_DOWN, x, y)
|
||||
mainEventQueue.dispatch(shared_pointer_of MouseEvent { widget = &widgetA, eventType = MOUSE_DOWN, AnyOtherData = x/y location, etc } );
|
||||
|
||||
// The event loop
|
||||
for(;;) {
|
||||
MainEventQueue::QueuedEvent queuedEvent;
|
||||
if(mainEventQueue.takeEvent(&queuedEvent)) {
|
||||
Event & event = std::get<0>(queuedEvent.arguments);
|
||||
if(event.eventType == MOUSE_DOWN) {
|
||||
event.widget.onMouseDown(event);
|
||||
}
|
||||
else if(event.eventType == KEY_DOWN) {
|
||||
event.widget.onKeyDown(event);
|
||||
}
|
||||
else if ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Below is the Windows equivalent message loop, it's simplified version from [Windows document](https://learn.microsoft.com/en-us/windows/win32/winmsg/using-messages-and-message-queues),
|
||||
|
||||
```c++
|
||||
while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
|
||||
{
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
```
|
||||
|
||||
## Process multiple EventQueue instances in a single main loop
|
||||
|
||||
It's common to have a single main loop in a GUI or game application, and there are various EventQueue instances in the system. The function `EventQueue::process` needs to be called on each queue to drive the queue moving. Below is a tip to process all the EventQueue instances in a single place.
|
||||
|
||||
```c++
|
||||
|
||||
// Here mainLoopTasks is global for simplify, in real application it can be in some object and passed around
|
||||
using MainLoopCallbackList = eventpp::CallbackList<void ()>;
|
||||
MainLoopCallbackList mainLoopTasks;
|
||||
|
||||
void mainLoop()
|
||||
{
|
||||
for(;;) {
|
||||
// Do any stuff in the loop
|
||||
|
||||
mainLoopTasks();
|
||||
}
|
||||
}
|
||||
|
||||
class MyEventQueue : public eventpp::EventQueue<blah blah>
|
||||
{
|
||||
public:
|
||||
MyEventQueue() {
|
||||
handle = mainLoopTasks.append([this]() {
|
||||
process();
|
||||
});
|
||||
}
|
||||
~MyEventQueue() {
|
||||
mainLoopTasks.remove(handle);
|
||||
}
|
||||
|
||||
private:
|
||||
MainLoopCallbackList::Handle handle;
|
||||
};
|
||||
```
|
||||
|
||||
The idea is, the main loop invoke a callback list in each loop, and each event queue registers its `process` to the callback list.
|
||||
|
@ -173,6 +173,7 @@ queue.process();
|
||||
* [Performance benchmarks](doc/benchmark.md)
|
||||
* [FAQs, tricks, and tips](doc/faq.md)
|
||||
* Tips and tricks
|
||||
* [Samples for typical use cases](doc/tip_sample_use_cases.md)
|
||||
* [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
|
||||
* [Overview of heterogeneous classes](doc/heterogeneous.md)
|
||||
|
Loading…
x
Reference in New Issue
Block a user