1
0
mirror of https://github.com/wqking/eventpp.git synced 2024-12-26 15:52:40 +08:00

Merge branch 'master' into development

This commit is contained in:
wqking 2022-01-03 10:17:15 +08:00
commit bf39d6593d
22 changed files with 650 additions and 25 deletions

View File

@ -17,13 +17,15 @@ struct MouseEvent : public Event
eventpp::EventDispatcher<int, void (const Event &)> dispatcher;
// Below line won't compile because the listener must `const Event &` can't be converted to `const MouseEvent &` explicitly.
// Below line won't compile because the listener parameter `const Event &` can't be converted to `const MouseEvent &` explicitly.
//dispatcher.appendListener(ON_MOUSE_DOWN, [](const MouseEvent &) {});
// This line works.
dispatcher.appendListener(ON_MOUSE_DOWN, eventpp::argumentAdapter<void(const MouseEvent &)>([](const MouseEvent &) {}));
```
Note: above code may not work with `EventQueue`, because `EventQueue` stores the arguments in memory, here is `Event`. Passing `MouseEvent` will be stored as `Event`. `std::shared_ptr` should be used in `EventQueue`. See the sample code `example3` in this document.
## Header
eventpp/utilities/argumentadapter.h
@ -238,4 +240,29 @@ eventDispatcher.dispatch(EventType::input, MouseEvent(3, 8));
eventDispatcher.dispatch(EventType::input, KeyEvent(99));
}
void example3()
{
// Here we use std::shared_ptr as the callback parameter.
// Note the argument can't be any reference to std::shared_ptr, such as 'const std::shared_ptr<Event> &',
// because eventpp::argumentAdapter uses std::static_pointer_cast to cast the pointer and it doesn't
// work on reference.
eventpp::EventQueue<EventType, void(std::shared_ptr<Event>)> eventQueue;
// This can't compile because a 'std::shared_ptr<Event>' can be passed to 'std::shared_ptr<MouseEvent>'
//eventDispatcher.appendListener(mouseEventId, [](std::shared_ptr<MouseEvent> e) {});
// This compiles. eventpp::argumentAdapter creates a functor object that static_cast
// 'std::shared_ptr<Event>' to 'std::shared_ptr<MouseEvent>' automatically.
eventQueue.appendListener(
EventType::mouse,
eventpp::argumentAdapter<void(std::shared_ptr<MouseEvent>)>([](std::shared_ptr<MouseEvent> e) {
std::cout << "Received MouseEvent as std::shared_ptr, x=" << e->getX() << " y=" << e->getY() << std::endl;
})
);
eventQueue.enqueue(EventType::mouse, std::make_shared<MouseEvent>(3, 5));
eventQueue.process();
}
```

View File

@ -60,9 +60,10 @@ CallbackList(const CallbackList & other);
CallbackList(CallbackList && other) noexcept;
CallbackList & operator = (const CallbackList & other);
CallbackList & operator = (CallbackList && other) noexcept;
void swap(CallbackList & other) noexcept;
```
CallbackList can be copied, moved, assigned, and move assigned.
CallbackList can be copied, moved, assigned, move assigned, and swapped.
#### empty

View File

@ -62,9 +62,10 @@ EventDispatcher(const EventDispatcher & other);
EventDispatcher(EventDispatcher && other) noexcept;
EventDispatcher & operator = (const EventDispatcher & other);
EventDispatcher & operator = (EventDispatcher && other) noexcept;
void swap(EventDispatcher & other) noexcept;
```
EventDispatcher can be copied, moved, assigned, and move assigned.
EventDispatcher can be copied, moved, assigned, move assigned, and swapped.
#### appendListener
@ -76,7 +77,7 @@ The listener is added to the end of the listener list.
Return a handle which represents the listener. The handle can be used to remove this listener or insert other listener before this listener.
If `appendListener` is called in another listener during a dispatching, the new listener is guaranteed not triggered during the same dispatching.
If the same callback is added twice, it results duplicated listeners.
The time complexity is O(1).
The time complexity is O(1) plus time to look up the event in internal map.
#### prependListener
@ -87,7 +88,7 @@ Add the *callback* to the dispatcher to listen to *event*.
The listener is added to the beginning of the listener list.
Return a handle which represents the listener. The handle can be used to remove this listener or insert other listener before this listener.
If `prependListener` is called in another listener during a dispatching, the new listener is guaranteed not triggered during the same dispatching.
The time complexity is O(1).
The time complexity is O(1) plus time to look up the event in internal map.
#### insertListener
@ -97,7 +98,7 @@ Handle insertListener(const Event & event, const Callback & callback, const Hand
Insert the *callback* to the dispatcher to listen to *event* before the listener handle *before*. If *before* is not found, *callback* is added at the end of the listener list.
Return a handle which represents the listener. The handle can be used to remove this listener or insert other listener before this listener.
If `insertListener` is called in another listener during a dispatching, the new listener is guaranteed not triggered during the same dispatching.
The time complexity is O(1).
The time complexity is O(1) plus time to look up the event in internal map.
#### removeListener
@ -106,7 +107,16 @@ bool removeListener(const Event & event, const Handle handle);
```
Remove the listener *handle* which listens to *event* from the dispatcher.
Return true if the listener is removed successfully, false if the listener is not found.
The time complexity is O(1).
The time complexity is O(1) plus time to look up the event in internal map.
#### hasAnyListener
```c++
bool hasAnyListener(const Event & event) const;
```
Return true if there is any listener for `event`, false if there is no listener.
Note: in multi threading, this function returning true doesn't guarantee there is any listener. The list may immediately become empty after the function returns true, and vice versa.
The time complexity is O(1) plus time to look up the event in internal map.
#### forEach

View File

@ -110,6 +110,15 @@ Remove the listener *handle* which listens to *event* from the dispatcher.
Return true if the listener is removed successfully, false if the listener is not found.
The time complexity is O(1).
#### hasAnyListener
```c++
bool hasAnyListener(const Event & event) const;
```
Return true if there is any listener for `event`, false if there is no listener.
Note: in multi threading, this function returning true doesn't guarantee there is any listener. The list may immediately become empty after the function returns true, and vice versa.
The time complexity is O(1) plus time to look up the event in internal map.
#### forEach
```c++

View File

@ -72,7 +72,7 @@ queue.process();
## Thread safety
All classes are thread-safe. You can call all public functions from multiple threads at the same time. If it fails, please report a bug.
The library guarantees the integration of each single function call, such as `EventDispatcher::appendListener`, 'CallbackList::remove`, but it does not guarantee the order of operations in multiple threads. For example, if a thread is dispatching an event, another thread removes a listener at the same time, the removed listener may be still triggered after it's removed.
The library guarantees the integration of each single function call, such as `EventDispatcher::appendListener`, `CallbackList::remove`, but it does not guarantee the order of operations in multiple threads. For example, if a thread is dispatching an event, another thread removes a listener at the same time, the removed listener may be still triggered after it's removed.
## Exception safety

View File

@ -351,6 +351,7 @@ iterator begin();
const_iterator begin() const;
iterator end();
const_iterator end() const;
const_reference front() const;
void swap(QueueList & other);
void emplace_back();
void splice(const_iterator pos, QueueList & other );

View File

@ -39,10 +39,17 @@ class ScopedRemover;
### Member functions
```c++
// for EventDispatcher, EventQueue, HeterEventDispatcher, or HeterEventQueue
explicit ScopedRemover(DispatcherType & dispatcher);
// for CallbackList or HeterCallbackList
explicit ScopedRemover(CallbackListType & callbackList);
ScopedRemover(ScopedRemover && other) noexcept;
ScopedRemover & operator = (ScopedRemover && other) noexcept;
void swap(ScopedRemover & other) noexcept;
```
Constructs an instance of ScopedRemover.
ScopedRemover can be moved, move assigned and swapped, but can't be copied or assigned.
**Member functions for EventDispatcher and EventQueue**
```c++
@ -65,6 +72,10 @@ typename DispatcherType::Handle insertListener(
const typename DispatcherType::Callback & listener,
const typename DispatcherType::Handle & before
);
bool removeListener(
const typename DispatcherType::Event & event,
const typename DispatcherType::Handle handle
);
```
**Member functions for CallbackList**
@ -85,10 +96,14 @@ typename CallbackListType::Handle insert(
const typename CallbackListType::Callback & callback,
const typename CallbackListType::Handle & before
);
bool remove(const typename CallbackListType::Handle handle);
```
The function `reset()` removes all listeners which added by ScopedRemover from the dispatcher or callback list, as if the ScopedRemover object has gone out of scope.
The function `setDispatcher()` and `setCallbackList` sets the dispatcher or callback list, and reset the ScopedRemover object.
The functions `setDispatcher()` and `setCallbackList` sets the dispatcher or callback list, and reset the ScopedRemover object.
The functions `removeListener` and `remove` remove the listener, similar to the same name functions in the underlying class (CallbackList, EventDispatcher, or EventQueue). They are useful to remove the listeners without destorying the ScopedRemover object. The functions return `true` if the listener is removed successfully, `false` if the listener is not found.
The other member functions that have the same names with the corresponding underlying class (CallbackList, EventDispatcher, or EventQueue). Those functions add listener to the dispatcher.

View File

@ -68,7 +68,7 @@ callbackList.append([](const std::string & s, const bool b) {
std::cout << std::boolalpha << "Got callback 1, s is " << s << " b is " << b << std::endl;
});
// The callback prototype doesn't need to be exactly same as the callback list.
// It would be find as long as the arguments is compatible with the callbacklist.
// It would be fine as long as the arguments are compatible with the callbacklist.
callbackList.append([](std::string s, int b) {
std::cout << std::boolalpha << "Got callback 2, s is " << s << " b is " << b << std::endl;
});

View File

@ -167,6 +167,16 @@ public:
return false;
}
bool hasAnyListener(const Event & event) const
{
const CallbackList_ * callableList = doFindCallableList(event);
if(callableList) {
return ! callableList->empty();
}
return false;
}
template <typename Func>
void forEach(const Event & event, Func && func) const
{

View File

@ -103,6 +103,13 @@ struct SingleThreading
return value;
}
T exchange(T desired, std::memory_order /*order*/ = std::memory_order_seq_cst) noexcept
{
const T previous = value;
value = desired;
return previous;
}
T operator ++ () noexcept
{
return ++value;

View File

@ -157,6 +157,16 @@ public:
return false;
}
bool hasAnyListener(const Event & event) const
{
const CallbackList_ * callableList = doFindCallableList(event);
if(callableList) {
return ! callableList->empty();
}
return false;
}
template <typename Prototype, typename Func>
void forEach(const Event & event, Func && func) const
{

View File

@ -46,6 +46,7 @@ void commonDtor(void * instance)
reinterpret_cast<T *>(instance)->~T();
}
// used by EventQueue
template <typename T>
class BufferedItem
{
@ -94,12 +95,17 @@ public:
dtor = nullptr;
}
bool empty() const {
return dtor == nullptr;
}
private:
std::array<char, sizeof(T)> buffer;
DtorFunc dtor;
};
// used by HeterEventQueue
template <size_t Size>
class BufferedUnion
{
@ -149,6 +155,10 @@ public:
dtor = nullptr;
}
bool empty() const {
return dtor == nullptr;
}
private:
std::array<char, Size> buffer;
DtorFunc dtor;

View File

@ -42,6 +42,7 @@ public:
using super::empty;
using super::begin;
using super::end;
using super::front;
using super::swap;
using super::emplace_back;
@ -59,6 +60,17 @@ private:
void doSort() {
auto compare = Compare();
this->sort([compare](const T & a, const T & b) {
// a and b may be empty if they are recycled to free list.
if(a.empty()) {
if(b.empty()) {
return false;
}
return true;
}
else if(b.empty()) {
return false;
}
return compare(a.get(), b.get());
});
}

View File

@ -21,6 +21,28 @@
namespace eventpp {
namespace internal_ {
template <typename Item, typename Handle>
bool removeHandleFromScopedRemoverItemList(std::vector<Item> & itemList, Handle & handle, std::mutex & mutex)
{
if(! handle) {
return false;
}
auto handlePointer = handle.lock();
std::unique_lock<std::mutex> lock(mutex);
auto it = std::find_if(itemList.begin(), itemList.end(), [&handlePointer](Item & item) {
return item.handle && item.handle.lock() == handlePointer;
});
if(it != itemList.end()) {
itemList.erase(it);
return true;
}
return false;
}
} //namespace internal_
template <typename DispatcherType, typename Enabled = void>
class ScopedRemover;
@ -48,11 +70,32 @@ public:
{
}
ScopedRemover(ScopedRemover && other) noexcept
: dispatcher(std::move(other.dispatcher)), itemList(std::move(other.itemList))
{
other.reset();
}
ScopedRemover & operator = (ScopedRemover && other) noexcept
{
dispatcher = std::move(other.dispatcher);
itemList = std::move(other.itemList);
other.reset();
return *this;
}
~ScopedRemover()
{
reset();
}
void swap(ScopedRemover & other) noexcept {
using std::swap;
swap(dispatcher, other.dispatcher);
swap(itemList, other.itemList);
}
void reset()
{
if(dispatcher != nullptr) {
@ -62,17 +105,14 @@ public:
}
std::unique_lock<std::mutex> lock(itemListMutex);
itemList.clear();
}
void setDispatcher(DispatcherType & dispatcher)
{
if(this->dispatcher != &dispatcher) {
reset();
this->dispatcher = &dispatcher;
std::unique_lock<std::mutex> lock(itemListMutex);
itemList.clear();
}
}
@ -134,6 +174,14 @@ public:
return item.handle;
}
bool removeListener(const typename DispatcherType::Event & event, const typename DispatcherType::Handle handle)
{
if(internal_::removeHandleFromScopedRemoverItemList(itemList, handle, itemListMutex)) {
return dispatcher->removeListener(event, handle);
}
return false;
}
private:
DispatcherType * dispatcher;
std::vector<Item> itemList;
@ -163,11 +211,32 @@ public:
{
}
ScopedRemover(ScopedRemover && other) noexcept
: callbackList(std::move(other.callbackList)), itemList(std::move(other.itemList))
{
other.reset();
}
ScopedRemover & operator = (ScopedRemover && other) noexcept
{
callbackList = std::move(other.callbackList);
itemList = std::move(other.itemList);
other.reset();
return *this;
}
~ScopedRemover()
{
reset();
}
void swap(ScopedRemover & other) noexcept {
using std::swap;
swap(callbackList, other.callbackList);
swap(itemList, other.itemList);
}
void reset()
{
if(callbackList != nullptr) {
@ -175,6 +244,8 @@ public:
callbackList->remove(item.handle);
}
}
std::unique_lock<std::mutex> lock(itemListMutex);
itemList.clear();
}
@ -194,7 +265,12 @@ public:
Item item {
callbackList->append(callback)
};
{
std::unique_lock<std::mutex> lock(itemListMutex);
itemList.push_back(item);
}
return item.handle;
}
@ -206,7 +282,12 @@ public:
Item item {
callbackList->prepend(callback)
};
{
std::unique_lock<std::mutex> lock(itemListMutex);
itemList.push_back(item);
}
return item.handle;
}
@ -219,13 +300,27 @@ public:
Item item {
callbackList->insert(callback, before)
};
{
std::unique_lock<std::mutex> lock(itemListMutex);
itemList.push_back(item);
}
return item.handle;
}
bool remove(const typename CallbackListType::Handle handle)
{
if(internal_::removeHandleFromScopedRemoverItemList(itemList, handle, itemListMutex)) {
return callbackList->remove(handle);
}
return false;
}
private:
CallbackListType * callbackList;
std::vector<Item> itemList;
typename CallbackListType::Mutex itemListMutex;
};

View File

@ -45,7 +45,7 @@ If you find any back compatible issue which is not announced, please report a bu
Tested with MSVC 2019, MinGW (Msys) GCC 7.2, Ubuntu GCC 5.4, and MacOS GCC.
GCC 4.8.3 can compile the library, but we don't support GCC prior to GCC 5.
In brief, MSVB, GCC, Clang that has well support for C++11, or released after 2019, should be able to compile the library.
In brief, MSVC, GCC, Clang that has well support for C++11, or released after 2019, should be able to compile the library.
## C++ standard requirements
* To Use the library
@ -136,13 +136,13 @@ queue.process();
* [Utility class ConditionalRemover -- auto remove listeners when certain condition is satisfied](doc/conditionalremover.md)
* [Utility class ScopedRemover -- auto remove listeners when out of scope](doc/scopedremover.md)
* [Utility class OrderedQueueList -- make EventQueue ordered](doc/orderedqueuelist.md)
* [Utility class AnyId -- use various data types as EventType in EventDispatcher and EventQueue ordered](doc/anyid.md)
* [Utility class AnyId -- use various data types as EventType in EventDispatcher and EventQueue](doc/anyid.md)
* [Utility header eventmaker.h -- auto generate event classes](doc/eventmaker.md)
* [Document of utilitie functions](doc/eventutil.md)
* Miscellaneous
* [Performance benchmarks](doc/benchmark.md)
* [FAQs, tricks, and tips](doc/faq.md)
* Heterogeneous classes and functions
* Heterogeneous classes and functions, 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

@ -16,6 +16,7 @@
#include "eventpp/utilities/conditionalfunctor.h"
#include "eventpp/eventdispatcher.h"
#include "eventpp/eventqueue.h"
#include "tutorial.h"
#include <iostream>
@ -172,21 +173,22 @@ TEST_CASE("ArgumentAdapter tutorial 2, arguments with std::shared_ptr")
// Note the argument can't be any reference to std::shared_ptr, such as 'const std::shared_ptr<Event> &',
// because eventpp::argumentAdapter uses std::static_pointer_cast to cast the pointer and it doesn't
// work on reference.
eventpp::EventDispatcher<EventType, void(std::shared_ptr<Event>)> eventDispatcher;
eventpp::EventQueue<EventType, void(std::shared_ptr<Event>)> eventQueue;
// This can't compile because a 'std::shared_ptr<Event>' can be passed to 'std::shared_ptr<MouseEvent>'
//eventDispatcher.appendListener(mouseEventId, [](std::shared_ptr<MouseEvent> e) {});
// This compiles. eventpp::argumentAdapter creates a functor object that static_cast
// 'std::shared_ptr<Event>' to 'std::shared_ptr<MouseEvent>' automatically.
eventDispatcher.appendListener(
eventQueue.appendListener(
EventType::mouse,
eventpp::argumentAdapter<void(std::shared_ptr<MouseEvent>)>([](std::shared_ptr<MouseEvent> e) {
std::cout << "Received MouseEvent as std::shared_ptr, x=" << e->getX() << " y=" << e->getY() << std::endl;
})
);
eventDispatcher.dispatch(EventType::mouse, std::make_shared<MouseEvent>(3, 5));
eventQueue.enqueue(EventType::mouse, std::make_shared<MouseEvent>(3, 5));
eventQueue.process();
}
TEST_CASE("ArgumentAdapter tutorial 3, conditional adapter")

View File

@ -52,7 +52,7 @@ TEST_CASE("CallbackList tutorial 2, callback with parameters")
std::cout << std::boolalpha << "Got callback 1, s is " << s << " b is " << b << std::endl;
});
// The callback prototype doesn't need to be exactly same as the callback list.
// It would be find as long as the arguments is compatible with the callbacklist.
// It would be fine as long as the arguments are compatible with the callbacklist.
callbackList.append([](std::string s, int b) {
std::cout << std::boolalpha << "Got callback 2, s is " << s << " b is " << b << std::endl;
});

View File

@ -422,6 +422,29 @@ TEST_CASE("CallbackList, swap with non-empty CallbackList")
REQUIRE(callbackList.tail->callback == 8);
}
// Test the compile bug found in https://github.com/wqking/eventpp/issues/29
// Fixed compile error that eventpp::SingleThreading::Atomic::exchange was not defined.
TEST_CASE("CallbackList, swap, SingleThreading")
{
struct MyPolicies
{
using Threading = eventpp::SingleThreading;
};
using CL = eventpp::CallbackList<void(), MyPolicies>;
CL callbackList;
CL swappedList;
callbackList.currentCounter = 38;
swappedList.currentCounter = 99;
REQUIRE(callbackList.currentCounter.load() == 38);
REQUIRE(swappedList.currentCounter.load() == 99);
using std::swap;
swap(swappedList, callbackList);
REQUIRE(callbackList.currentCounter.load() == 99);
REQUIRE(swappedList.currentCounter.load() == 38);
}
TEST_CASE("CallbackList, invoke, swap with non-empty CallbackList")
{
using CL = eventpp::CallbackList<void()>;

View File

@ -109,6 +109,51 @@ TEST_CASE("EventDispatcher, add/remove, int, void ()")
REQUIRE(! dispatcher.removeListener(event + 1, hb));
}
TEST_CASE("EventDispatcher, hasAnyListener, int, void ()")
{
eventpp::EventDispatcher<int, void()> dispatcher;
constexpr int event1 = 3;
constexpr int event2 = 5;
decltype(dispatcher)::Handle ha, hb, hc;
REQUIRE(! dispatcher.hasAnyListener(event1));
REQUIRE(! dispatcher.hasAnyListener(event2));
ha = dispatcher.appendListener(event1, [](){});
REQUIRE(dispatcher.hasAnyListener(event1));
REQUIRE(! dispatcher.hasAnyListener(event2));
hb = dispatcher.appendListener(event1, [](){});
REQUIRE(dispatcher.hasAnyListener(event1));
REQUIRE(! dispatcher.hasAnyListener(event2));
hc = dispatcher.appendListener(event2, []() {});
REQUIRE(dispatcher.hasAnyListener(event1));
REQUIRE(dispatcher.hasAnyListener(event2));
dispatcher.removeListener(event1, ha);
REQUIRE(dispatcher.hasAnyListener(event1));
REQUIRE(dispatcher.hasAnyListener(event2));
dispatcher.removeListener(event1, ha); // not remove anything
REQUIRE(dispatcher.hasAnyListener(event1));
REQUIRE(dispatcher.hasAnyListener(event2));
dispatcher.removeListener(event1, hb);
REQUIRE(! dispatcher.hasAnyListener(event1));
REQUIRE(dispatcher.hasAnyListener(event2));
ha = dispatcher.appendListener(event1, [](){});
REQUIRE(dispatcher.hasAnyListener(event1));
dispatcher.removeListener(event2, hc);
REQUIRE(! dispatcher.hasAnyListener(event2));
dispatcher.removeListener(event1, ha);
REQUIRE(! dispatcher.hasAnyListener(event1));
}
TEST_CASE("EventDispatcher, add another listener inside a listener, int, void ()")
{
eventpp::EventDispatcher<int, void ()> dispatcher;

View File

@ -181,6 +181,38 @@ TEST_CASE("HeterEventDispatcher, removeListener")
REQUIRE(dataList == std::vector<int>{ 5, 1, 3, 6, 4 });
}
TEST_CASE("HeterEventDispatcher, hasAnyListener, int, void ()")
{
eventpp::HeterEventDispatcher<int, eventpp::HeterTuple<void(), void(int)> > dispatcher;
constexpr int event = 3;
std::vector<int> dataList(5);
REQUIRE(! dispatcher.hasAnyListener(event));
auto h1 = dispatcher.appendListener(event, [](){});
REQUIRE(dispatcher.hasAnyListener(event));
REQUIRE(! dispatcher.hasAnyListener(event + 1));
auto h2 = dispatcher.appendListener(event, [](int){});
REQUIRE(dispatcher.hasAnyListener(event));
dispatcher.removeListener(event, h1);
REQUIRE(dispatcher.hasAnyListener(event));
dispatcher.removeListener(event, h1); // not remove anything
REQUIRE(dispatcher.hasAnyListener(event));
dispatcher.removeListener(event, h2);
REQUIRE(! dispatcher.hasAnyListener(event));
h1 = dispatcher.appendListener(event, [](){});
REQUIRE(dispatcher.hasAnyListener(event));
dispatcher.removeListener(event, h1);
REQUIRE(! dispatcher.hasAnyListener(event));
}
TEST_CASE("HeterEventDispatcher, forEach")
{
eventpp::HeterEventDispatcher<int, eventpp::HeterTuple<int (), int (int)> > dispatcher;

View File

@ -96,6 +96,10 @@ TEST_CASE("EventQueue, ordered list, ascend")
REQUIRE(dataList != eventList);
REQUIRE(detectDataListOrder(dataList.begin(), dataList.end()) == 1);
// Be sure OrderedQueueList is compilable in below calls.
queue.processOne();
queue.processIf([](){ return true; });
}
struct MyCompareDescend

View File

@ -190,3 +190,315 @@ TEST_CASE("ScopedRemover, HeterCallbackList")
REQUIRE(dataList == std::vector<int> { 4, 3, 2, 1 });
}
TEST_CASE("ScopedRemover, EventDispatcher, move assignment")
{
using ED = eventpp::EventDispatcher<int, void()>;
ED dispatcher;
using Remover = eventpp::ScopedRemover<ED>;
constexpr int event = 3;
std::vector<int> dataList(2);
dispatcher.appendListener(event, [&dataList]() {
++dataList[0];
});
{
Remover moved;
{
Remover r1(dispatcher);
r1.appendListener(event, [&dataList]() {
++dataList[1];
});
dispatcher.dispatch(event);
REQUIRE(dataList == std::vector<int> { 1, 1 });
// moved = r1; // not compilable
moved = std::move(r1);
dispatcher.dispatch(event);
REQUIRE(dataList == std::vector<int> { 2, 2 });
}
dispatcher.dispatch(event);
REQUIRE(dataList == std::vector<int> { 3, 3 });
}
dispatcher.dispatch(event);
REQUIRE(dataList == std::vector<int> { 4, 3 });
}
TEST_CASE("ScopedRemover, EventDispatcher, move")
{
using ED = eventpp::EventDispatcher<int, void()>;
ED dispatcher;
using Remover = eventpp::ScopedRemover<ED>;
constexpr int event = 3;
std::vector<int> dataList(2);
dispatcher.appendListener(event, [&dataList]() {
++dataList[0];
});
{
Remover r1(dispatcher);
r1.appendListener(event, [&dataList]() {
++dataList[1];
});
dispatcher.dispatch(event);
REQUIRE(dataList == std::vector<int> { 1, 1 });
{
// Remover moved1(r1); // not compilable
Remover moved(std::move(r1));
dispatcher.dispatch(event);
REQUIRE(dataList == std::vector<int> { 2, 2 });
}
dispatcher.dispatch(event);
REQUIRE(dataList == std::vector<int> { 3, 2 });
}
dispatcher.dispatch(event);
REQUIRE(dataList == std::vector<int> { 4, 2 });
}
TEST_CASE("ScopedRemover, EventDispatcher, swap")
{
using std::swap;
using ED = eventpp::EventDispatcher<int, void()>;
ED dispatcher;
using Remover = eventpp::ScopedRemover<ED>;
constexpr int event = 3;
std::vector<int> dataList(3);
dispatcher.appendListener(event, [&dataList]() {
++dataList[0];
});
Remover r1(dispatcher);
r1.appendListener(event, [&dataList]() {
++dataList[1];
});
dispatcher.dispatch(event);
REQUIRE(dataList == std::vector<int> { 1, 1, 0 });
r1.reset();
dispatcher.dispatch(event);
REQUIRE(dataList == std::vector<int> { 2, 1, 0 });
r1.appendListener(event, [&dataList]() {
++dataList[1];
});
dispatcher.dispatch(event);
REQUIRE(dataList == std::vector<int> { 3, 2, 0 });
Remover r2(dispatcher);
swap(r1, r2);
r1.reset();
dispatcher.dispatch(event);
REQUIRE(dataList == std::vector<int> { 4, 3, 0 });
r2.appendListener(event, [&dataList]() {
++dataList[2];
});
swap(r1, r2);
r2.reset();
dispatcher.dispatch(event);
REQUIRE(dataList == std::vector<int> { 5, 4, 1 });
}
TEST_CASE("ScopedRemover, CallbackList, move assignment")
{
using CL = eventpp::CallbackList<void()>;
CL callbackList;
using Remover = eventpp::ScopedRemover<CL>;
std::vector<int> dataList(2);
callbackList.append([&dataList]() {
++dataList[0];
});
{
Remover moved;
{
Remover r1(callbackList);
r1.append([&dataList]() {
++dataList[1];
});
callbackList();
REQUIRE(dataList == std::vector<int> { 1, 1 });
// moved = r1; // not compilable
moved = std::move(r1);
callbackList();
REQUIRE(dataList == std::vector<int> { 2, 2 });
}
callbackList();
REQUIRE(dataList == std::vector<int> { 3, 3 });
}
callbackList();
REQUIRE(dataList == std::vector<int> { 4, 3 });
}
TEST_CASE("ScopedRemover, CallbackList, move")
{
using CL = eventpp::CallbackList<void()>;
CL callbackList;
using Remover = eventpp::ScopedRemover<CL>;
std::vector<int> dataList(2);
callbackList.append([&dataList]() {
++dataList[0];
});
{
Remover r1(callbackList);
r1.append([&dataList]() {
++dataList[1];
});
callbackList();
REQUIRE(dataList == std::vector<int> { 1, 1 });
{
// Remover moved1(r1); // not compilable
Remover moved(std::move(r1));
callbackList();
REQUIRE(dataList == std::vector<int> { 2, 2 });
}
callbackList();
REQUIRE(dataList == std::vector<int> { 3, 2 });
}
callbackList();
REQUIRE(dataList == std::vector<int> { 4, 2 });
}
TEST_CASE("ScopedRemover, CallbackList, swap")
{
using std::swap;
using CL = eventpp::CallbackList<void()>;
CL callbackList;
using Remover = eventpp::ScopedRemover<CL>;
std::vector<int> dataList(3);
callbackList.append([&dataList]() {
++dataList[0];
});
Remover r1(callbackList);
r1.append([&dataList]() {
++dataList[1];
});
callbackList();
REQUIRE(dataList == std::vector<int> { 1, 1, 0 });
r1.reset();
callbackList();
REQUIRE(dataList == std::vector<int> { 2, 1, 0 });
r1.append([&dataList]() {
++dataList[1];
});
callbackList();
REQUIRE(dataList == std::vector<int> { 3, 2, 0 });
Remover r2(callbackList);
swap(r1, r2);
r1.reset();
callbackList();
REQUIRE(dataList == std::vector<int> { 4, 3, 0 });
r2.append([&dataList]() {
++dataList[2];
});
swap(r1, r2);
r2.reset();
callbackList();
REQUIRE(dataList == std::vector<int> { 5, 4, 1 });
}
TEST_CASE("ScopedRemover, EventDispatcher, removeListener")
{
using ED = eventpp::EventDispatcher<int, void()>;
ED dispatcher;
using Remover = eventpp::ScopedRemover<ED>;
constexpr int event = 3;
std::vector<int> dataList(3);
decltype(dispatcher)::Handle ha;
decltype(dispatcher)::Handle hb;
decltype(dispatcher)::Handle hc;
Remover remover(dispatcher);
ha = remover.appendListener(event, [&dataList, event , &remover , &ha]() {
++dataList[0];
remover.removeListener(event, ha);
});
hb = dispatcher.appendListener(event, [&dataList]() {
++dataList[1];
});
hc = remover.appendListener(event, [&dataList]() {
++dataList[2];
});
REQUIRE(! remover.removeListener(event, hb));
dispatcher.dispatch(event);
REQUIRE(dataList == std::vector<int> { 1, 1, 1 });
dispatcher.dispatch(event);
REQUIRE(dataList == std::vector<int> { 1, 2, 2 });
REQUIRE(remover.removeListener(event, hc));
dispatcher.dispatch(event);
REQUIRE(dataList == std::vector<int> { 1, 3, 2 });
REQUIRE(! remover.removeListener(event, hc));
dispatcher.dispatch(event);
REQUIRE(dataList == std::vector<int> { 1, 4, 2 });
}
TEST_CASE("ScopedRemover, CallbackList, remove")
{
using std::swap;
using CL = eventpp::CallbackList<void()>;
CL callbackList;
using Remover = eventpp::ScopedRemover<CL>;
std::vector<int> dataList(3);
decltype(callbackList)::Handle ha;
decltype(callbackList)::Handle hb;
decltype(callbackList)::Handle hc;
Remover remover(callbackList);
ha = remover.append([&dataList, &remover , &ha]() {
++dataList[0];
remover.remove(ha);
});
hb = callbackList.append([&dataList]() {
++dataList[1];
});
hc = remover.append([&dataList]() {
++dataList[2];
});
REQUIRE(! remover.remove(hb));
callbackList();
REQUIRE(dataList == std::vector<int> { 1, 1, 1 });
callbackList();
REQUIRE(dataList == std::vector<int> { 1, 2, 2 });
REQUIRE(remover.remove(hc));
callbackList();
REQUIRE(dataList == std::vector<int> { 1, 3, 2 });
REQUIRE(! remover.remove(hc));
callbackList();
REQUIRE(dataList == std::vector<int> { 1, 4, 2 });
}