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

Added more tutorials for EventQueue. Updated documents.

This commit is contained in:
wqking 2022-07-31 19:43:44 +08:00
parent 15d78b7bd2
commit e6518f9c56
7 changed files with 536 additions and 4 deletions

View File

@ -18,6 +18,12 @@ dispatcher.appendListener(3, []() {}); // OK
dispatcher.appendListener(3, [](std::string) {}); // OK
```
## Warning
The heterogeneous classes are mostly for proof of concept purpose. Misusing them most likely means your application design has flaws.
You should stick with the homogeneous classes, even though sometimes the heterogeneous classes look convenient (but with overhead).
The heterogeneous classes may be not well maintained or supported in the future. You use them on your own risk.
## Usage
### Headers
@ -78,3 +84,9 @@ The only difference is the `Prototype` in homo-classes becomes `PrototypeList` i
In the homo-classes, `Prototype` is a single function type such as `void ()`.
In the heter-classes, `PrototypeList` is a list of function types in `eventpp::HeterTuple`, such as `eventpp::HeterTuple<void (), void (std::string), void (int, int)>`.
Note: Ideally it would be better to use `std::tuple` instead of `eventpp::HeterTuple`, but the problem is that the tuple is instantiated in HeterEventDispatcher which cause compile error that function type can't be instantiated.
## Differences between heterogeneous classes vs homogeneous classes
1. Heterogeneous classes has both overhead on performance and memory usage. Usually event system is the core component in an application, the performance is critical.
2. Heterogeneous classes can't have the same API interface as homogeneous classes, because some APIs are impossible or very difficult to implement in heterogeneous classes.
3. Heterogeneous classes doesn't support eventpp::ArgumentPassingAutoDetect. That means the event in the argument can't be detected automatically.

View File

@ -121,3 +121,239 @@ thread.join();
> Should have triggered events with index = 10 and 11
**Remarks**
### Tutorial 3 -- prioritized dispatching
In tutorial 3, we will demonstrate how to make EventQueue dispatch higher priority event earlier.
```c++
// First let's define the event struct. e is the event type, priority determines the priority.
struct MyEvent
{
int e;
int priority;
};
// The comparison function object used by eventpp::OrderedQueueList.
// The function compares the event by priority.
struct MyCompare
{
template <typename T>
bool operator() (const T & a, const T & b) const {
return a.template getArgument<0>().priority > b.template getArgument<0>().priority;
}
};
// Define the EventQueue policy
struct MyPolicy
{
template <typename Item>
using QueueList = eventpp::OrderedQueueList<Item, MyCompare >;
static int getEvent(const MyEvent & event) {
return event.e;
}
};
TEST_CASE("EventQueue tutorial 3, ordered queue")
{
std::cout << std::endl << "EventQueue tutorial 3, ordered queue" << std::endl;
using EQ = eventpp::EventQueue<int, void(const MyEvent &), MyPolicy>;
EQ queue;
queue.appendListener(3, [](const MyEvent & event) {
std::cout << "Get event " << event.e << "(should be 3)."
<< " priority: " << event.priority << std::endl;
});
queue.appendListener(5, [](const MyEvent & event) {
std::cout << "Get event " << event.e << "(should be 5)."
<< " priority: " << event.priority << std::endl;
});
queue.appendListener(7, [](const MyEvent & event) {
std::cout << "Get event " << event.e << "(should be 7)."
<< " priority: " << event.priority << std::endl;
});
// Add an event, the first number 5 is the event type, the second number 100 is the priority.
// After the queue processes, the events will be processed from higher priority to lower priority.
queue.enqueue(MyEvent{ 5, 100 });
queue.enqueue(MyEvent{ 5, 200 });
queue.enqueue(MyEvent{ 7, 300 });
queue.enqueue(MyEvent{ 7, 400 });
queue.enqueue(MyEvent{ 3, 500 });
queue.enqueue(MyEvent{ 3, 600 });
queue.process();
}
```
**Output**
> Get event 3(should be 3). priority: 600
> Get event 3(should be 3). priority: 500
> Get event 7(should be 7). priority: 400
> Get event 7(should be 7). priority: 300
> Get event 5(should be 5). priority: 200
> Get event 5(should be 5). priority: 100
### Tutorial 4 -- typical event system
In tutorial 4, we will demonstrate how different event classes are used in a typical event system in an application.
A typical event system may look like, there is a base event class, each event type has a corresponding event class
that inherits from the base event. When emitting an event, a pointer/referent to the base event class is used,
the event listener then cast the base event to proper derived event.
```c++
// This is the definition of event types
enum class EventType
{
// for MouseEvent
mouse,
// for KeyboardEvent
keyboard,
// for either MouseEvent or KeyboardEvent, it's used to demonstrate
// how the listener detects event type dynamically
input,
// for ChangedEvent
changed
};
// This is the base event. It has a getType function to return the actual event type.
class Event
{
public:
explicit Event(const EventType type) : type(type) {
}
virtual ~Event() {
}
EventType getType() const {
return type;
}
private:
EventType type;
};
class MouseEvent : public Event
{
public:
MouseEvent(const int x, const int y)
: Event(EventType::mouse), x(x), y(y)
{
}
int getX() const { return x; }
int getY() const { return y; }
private:
int x;
int y;
};
class KeyboardEvent : public Event
{
public:
explicit KeyboardEvent(const int key)
: Event(EventType::keyboard), key(key)
{
}
int getKey() const { return key; }
private:
int key;
};
class ChangedEvent : public Event
{
public:
explicit ChangedEvent(const std::string & text)
: Event(EventType::changed), text(text)
{
}
std::string getText() const { return text; }
private:
std::string text;
};
// We will pass event as EventPointer, here it's std::shared_ptr<Event>.
// It allows EventQueue to store the events in internal buffer without slicing the objects
// in asynchronous API (EventQueue::enqueue and EventQueue::process, etc).
// If we only use the synchronous API (EventDispatcher, or EventQueue::dispatch),
// we can dispatch events as reference.
using EventPointer = std::shared_ptr<Event>;
// We are going to dispatch event objects directly without specify the event type explicitly,
// so we need to define policy to let eventpp know how to get the event type from the event object.
struct EventPolicy
{
static EventType getEvent(const EventPointer & event) {
return event->getType();
}
};
TEST_CASE("EventQueue tutorial 4, typical event system in an application")
{
std::cout << std::endl << "EventQueue tutorial 4, typical event system in an application" << std::endl;
using EQ = eventpp::EventQueue<EventType, void(const EventPointer &), EventPolicy>;
EQ queue;
queue.appendListener(EventType::mouse, [](const EventPointer & event) {
const MouseEvent * mouseEvent = static_cast<const MouseEvent *>(event.get());
std::cout << "Received mouse event, x=" << mouseEvent->getX() << " y=" << mouseEvent->getY()
<< std::endl;
});
queue.appendListener(EventType::keyboard, [](const EventPointer & event) {
const KeyboardEvent * keyboardEvent = static_cast<const KeyboardEvent *>(event.get());
std::cout << "Received keyboard event, key=" << (char)keyboardEvent->getKey()
<< std::endl;
});
queue.appendListener(EventType::input, [](const EventPointer & event) {
std::cout << "Received input event, ";
if(event->getType() == EventType::mouse) {
const MouseEvent * mouseEvent = static_cast<const MouseEvent *>(event.get());
std::cout << "it's mouse event, x=" << mouseEvent->getX() << " y=" << mouseEvent->getY()
<< std::endl;
}
else if(event->getType() == EventType::keyboard) {
const KeyboardEvent * keyboardEvent = static_cast<const KeyboardEvent *>(event.get());
std::cout << "it's keyboard event, key=" << (char)keyboardEvent->getKey() << std::endl;
}
else {
std::cout << "it's an event that I don't understand." << std::endl;
}
});
queue.appendListener(EventType::changed, [](const EventPointer & event) {
const ChangedEvent * changedEvent = static_cast<const ChangedEvent *>(event.get());
std::cout << "Received changed event, text=" << changedEvent->getText() << std::endl;
});
// Asynchronous API, put events in to the event queue.
queue.enqueue(std::make_shared<MouseEvent>(123, 567));
queue.enqueue(std::make_shared<KeyboardEvent>('W'));
queue.enqueue(std::make_shared<ChangedEvent>("This is new text"));
queue.enqueue(EventType::input, std::make_shared<MouseEvent>(10, 20));
// then process all events.
queue.process();
// Synchronous API, dispatch events to the listeners directly.
queue.dispatch(std::make_shared<KeyboardEvent>('Q'));
queue.dispatch(EventType::input, std::make_shared<ChangedEvent>("Should not display"));
}
```
**Output**
> Received mouse event, x=123 y=567
> Received keyboard event, key=W
> Received changed event, text=This is new text
> Received input event, it's mouse event, x=10 y=20
> Received keyboard event, key=Q
> Received input event, it's an event that I don't understand.

View File

@ -231,7 +231,7 @@ queue.process();
* Miscellaneous
* [Performance benchmarks](doc/benchmark.md)
* [FAQs, tricks, and tips](doc/faq.md)
* Heterogeneous classes and functions, 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)
* [Class HeterCallbackList](doc/hetercallbacklist.md)
* [Class HeterEventDispatcher](doc/hetereventdispatcher.md)

View File

@ -6,6 +6,7 @@ set(SRC_TUTORIAL
tutorial_eventdispatcher.cpp
tutorial_eventqueue.cpp
tutorial_hetercallbacklist.cpp
tutorial_hetereventdispatcher.cpp
tutorial_argumentadapter.cpp
)

View File

@ -21,6 +21,8 @@
#include <iostream>
namespace {
// In the tutorials here, we define an event class hierarchy, Event is the base class.
// The callback prototype in EventDispatcher is reference or pointer to Event,
// then we should only be able to add listeners that only accept reference or pointer to Event,
@ -231,3 +233,5 @@ TEST_CASE("ArgumentAdapter tutorial 3, conditional adapter")
eventDispatcher.dispatch(EventType::input, KeyEvent(99));
}
} // namespace

View File

@ -20,6 +20,8 @@
#include <iostream>
#include <thread>
namespace {
TEST_CASE("EventQueue tutorial 1, basic")
{
std::cout << std::endl << "EventQueue tutorial 1, basic" << std::endl;
@ -148,13 +150,16 @@ TEST_CASE("EventQueue tutorial 3, ordered queue")
EQ queue;
queue.appendListener(3, [](const MyEvent & event) {
std::cout << "Get event " << event.e << "(should be 3)." << " priority: " << event.priority << std::endl;
std::cout << "Get event " << event.e << "(should be 3)."
<< " priority: " << event.priority << std::endl;
});
queue.appendListener(5, [](const MyEvent & event) {
std::cout << "Get event " << event.e << "(should be 5)." << " priority: " << event.priority << std::endl;
std::cout << "Get event " << event.e << "(should be 5)."
<< " priority: " << event.priority << std::endl;
});
queue.appendListener(7, [](const MyEvent & event) {
std::cout << "Get event " << event.e << "(should be 7)." << " priority: " << event.priority << std::endl;
std::cout << "Get event " << event.e << "(should be 7)."
<< " priority: " << event.priority << std::endl;
});
// Add an event, the first number 5 is the event type, the second number 100 is the priority.
@ -168,3 +173,156 @@ TEST_CASE("EventQueue tutorial 3, ordered queue")
queue.process();
}
// In tutorial 4, we will demonstrate how different event classes are used in a typical event system in an application.
// A typical event system may look like, there is a base event class, each event type has a corresponding event class
// that inherits from the base event. When emitting an event, a pointer/referent to the base event class is used,
// the event listener then cast the base event to proper derived event.
// This is the definition of event types
enum class EventType
{
// for MouseEvent
mouse,
// for KeyboardEvent
keyboard,
// for either MouseEvent or KeyboardEvent, it's used to demonstrate
// how the listener detects event type dynamically
input,
// for ChangedEvent
changed
};
// This is the base event. It has a getType function to return the actual event type.
class Event
{
public:
explicit Event(const EventType type) : type(type) {
}
virtual ~Event() {
}
EventType getType() const {
return type;
}
private:
EventType type;
};
class MouseEvent : public Event
{
public:
MouseEvent(const int x, const int y)
: Event(EventType::mouse), x(x), y(y)
{
}
int getX() const { return x; }
int getY() const { return y; }
private:
int x;
int y;
};
class KeyboardEvent : public Event
{
public:
explicit KeyboardEvent(const int key)
: Event(EventType::keyboard), key(key)
{
}
int getKey() const { return key; }
private:
int key;
};
class ChangedEvent : public Event
{
public:
explicit ChangedEvent(const std::string & text)
: Event(EventType::changed), text(text)
{
}
std::string getText() const { return text; }
private:
std::string text;
};
// We will pass event as EventPointer, here it's std::shared_ptr<Event>.
// It allows EventQueue to store the events in internal buffer without slicing the objects
// in asynchronous API (EventQueue::enqueue and EventQueue::process, etc).
// If we only use the synchronous API (EventDispatcher, or EventQueue::dispatch),
// we can dispatch events as reference.
using EventPointer = std::shared_ptr<Event>;
// We are going to dispatch event objects directly without specify the event type explicitly,
// so we need to define policy to let eventpp know how to get the event type from the event object.
struct EventPolicy
{
static EventType getEvent(const EventPointer & event) {
return event->getType();
}
};
TEST_CASE("EventQueue tutorial 4, typical event system in an application")
{
std::cout << std::endl << "EventQueue tutorial 4, typical event system in an application" << std::endl;
using EQ = eventpp::EventQueue<EventType, void(const EventPointer &), EventPolicy>;
EQ queue;
queue.appendListener(EventType::mouse, [](const EventPointer & event) {
const MouseEvent * mouseEvent = static_cast<const MouseEvent *>(event.get());
std::cout << "Received mouse event, x=" << mouseEvent->getX() << " y=" << mouseEvent->getY()
<< std::endl;
});
queue.appendListener(EventType::keyboard, [](const EventPointer & event) {
const KeyboardEvent * keyboardEvent = static_cast<const KeyboardEvent *>(event.get());
std::cout << "Received keyboard event, key=" << (char)keyboardEvent->getKey()
<< std::endl;
});
queue.appendListener(EventType::input, [](const EventPointer & event) {
std::cout << "Received input event, ";
if(event->getType() == EventType::mouse) {
const MouseEvent * mouseEvent = static_cast<const MouseEvent *>(event.get());
std::cout << "it's mouse event, x=" << mouseEvent->getX() << " y=" << mouseEvent->getY()
<< std::endl;
}
else if(event->getType() == EventType::keyboard) {
const KeyboardEvent * keyboardEvent = static_cast<const KeyboardEvent *>(event.get());
std::cout << "it's keyboard event, key=" << (char)keyboardEvent->getKey() << std::endl;
}
else {
std::cout << "it's an event that I don't understand." << std::endl;
}
});
queue.appendListener(EventType::changed, [](const EventPointer & event) {
const ChangedEvent * changedEvent = static_cast<const ChangedEvent *>(event.get());
std::cout << "Received changed event, text=" << changedEvent->getText() << std::endl;
});
// Asynchronous API, put events in to the event queue.
queue.enqueue(std::make_shared<MouseEvent>(123, 567));
queue.enqueue(std::make_shared<KeyboardEvent>('W'));
queue.enqueue(std::make_shared<ChangedEvent>("This is new text"));
queue.enqueue(EventType::input, std::make_shared<MouseEvent>(10, 20));
// then process all events.
queue.process();
// Synchronous API, dispatch events to the listeners directly.
queue.dispatch(std::make_shared<KeyboardEvent>('Q'));
queue.dispatch(EventType::input, std::make_shared<ChangedEvent>("Should not display"));
}
} // namespace

View File

@ -0,0 +1,121 @@
// 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.
// Include the head
#include "eventpp/hetereventdispatcher.h"
#include "tutorial.h"
#include <iostream>
namespace {
// This is the definition of event types
enum class EventType
{
// for MouseEvent
mouse,
// for KeyboardEvent
keyboard,
};
// This is the base event. It has a getType function to return the actual event type.
class Event
{
public:
explicit Event(const EventType type) : type(type) {
}
virtual ~Event() {
}
EventType getType() const {
return type;
}
private:
EventType type;
};
class MouseEvent : public Event
{
public:
MouseEvent(const int x, const int y)
: Event(EventType::mouse), x(x), y(y)
{
}
int getX() const { return x; }
int getY() const { return y; }
private:
int x;
int y;
};
class KeyboardEvent : public Event
{
public:
explicit KeyboardEvent(const int key)
: Event(EventType::keyboard), key(key)
{
}
int getKey() const { return key; }
private:
int key;
};
// We are going to dispatch event objects directly without specify the event type explicitly, so we need to let eventpp
// know how to get the event type from the event object. The event policy does that.
// Note ArgumentPassingMode must be eventpp::ArgumentPassingIncludeEvent here,
// the heterogeneous doesn't support eventpp::ArgumentPassingAutoDetect.
struct EventPolicy
{
using ArgumentPassingMode = eventpp::ArgumentPassingIncludeEvent;
template <typename E>
static EventType getEvent(const E & event) {
return event.getType();
}
};
TEST_CASE("HeterEventDispatcher tutorial 1, basic")
{
std::cout << std::endl << "HeterEventDispatcher tutorial 1, basic" << std::endl;
// The namespace is eventpp
// the second parameter is a HeterTuple of the listener prototypes.
eventpp::HeterEventDispatcher<EventType, eventpp::HeterTuple<
void (const MouseEvent &),
void (const KeyboardEvent &)
>,
EventPolicy
> dispatcher;
dispatcher.appendListener(EventType::mouse, [](const MouseEvent & event) {
std::cout << "Received mouse event, x=" << event.getX() << " y=" << event.getY() << std::endl;
});
dispatcher.appendListener(EventType::keyboard, [](const KeyboardEvent & event) {
std::cout << "Received keyboard event, key=" << (char)event.getKey() << std::endl;
});
dispatcher.dispatch(MouseEvent(5, 38));
dispatcher.dispatch(KeyboardEvent('W'));
}
} // namespace