Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions doc/callbacklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,14 @@ Invoke each callbacks in the callback list.
The callbacks are called with arguments `args`.
The callbacks are called in the thread same as the callee of `operator()`.

#### invokeIfAny

```c++
bool invokeIfAny(Args ...args) const;
```
Same as `operator()`, but returns `true` if any callback was invoked, `false` if the callback list is empty.
Unlike checking `empty()` before `operator()`, the result is exact in multi threading, because it's decided while the callbacks are iterated.

<a id="a2_3"></a>
## Nested callback safety
1. If a callback adds another callback to the callback list during a invoking, the new callback is guaranteed not to be triggered within the same invoking. This is guaranteed by an unsigned 64 bits integer counter. This rule will be broken is the counter is overflowed to zero in a invoking, but this rule will continue working on the subsequence invoking.
Expand Down
9 changes: 9 additions & 0 deletions doc/cn/callbacklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,15 @@ void operator() (Args ...args) const;
回调函数会被用 `args` 参数作为参数调用。
回调函数会在 `operator()` 所在的线程中调用。

#### invokeIfAny

```c++
bool invokeIfAny(Args ...args) const;
```

与 `operator()` 相同,但在有回调函数被调用时返回 `true` ,在回调函数列表为空时返回 `false` 。
与先调用 `empty()` 再调用 `operator()` 不同,该结果在多线程下是准确的,因为它是在遍历回调函数时确定的。

<a id="a2_3"></a>
## 嵌套回调函数安全性

Expand Down
13 changes: 13 additions & 0 deletions doc/cn/eventdispatcher.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,19 @@ void dispatch(T && first, Args ...args);

这两个重载函数略有区别,具体如何使用要根据 `ArgumentPassingMode` 策略而定。详情请阅读https://github.com/wqking/eventpp/blob/master/doc/policies.md 文档。

#### dispatchIfAny

```cpp
bool dispatchIfAny(Args ...args);

template <typename T>
bool dispatchIfAny(T && first, Args ...args);
```

与 `dispatch` 相同,但在有监听器被调用时返回 `true` ;在没有监听器,或者调度被 mixin 阻止( `mixinBeforeDispatch` 返回 false ,参见 MixinFilter )时返回 `false` 。

当需要知道事件是否被处理时,使用该函数可以避免在 `dispatch` 之前先调用 `hasAnyListener` ,后者会在内部映射表中查找两次事件,而且在多线程下不是原子的。

<a id="nested-listener-safety"></a>

## 嵌套监听器安全
Expand Down
11 changes: 11 additions & 0 deletions doc/eventdispatcher.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,17 @@ The function is synchronous. The listeners are called in the thread same as the

The two overloaded functions have similar but slightly difference. How to use them depends on the `ArgumentPassingMode` policy. Please reference the [document of policies](policies.md) for more information.

#### dispatchIfAny

```c++
bool dispatchIfAny(Args ...args);

template <typename T>
bool dispatchIfAny(T && first, Args ...args);
```
Same as `dispatch`, but returns `true` if any listener was invoked, `false` if there is no listener or the dispatching is blocked by a mixin (`mixinBeforeDispatch` returns false, see [MixinFilter](mixins.md)).
It's useful to know if an event was handled without calling `hasAnyListener` before `dispatch`, which would look up the event in the internal map twice, and is not atomic in multi threading.

<a id="a2_3"></a>
## Nested listener safety
1. If a listener adds another listener of the same event to the dispatcher during a dispatching, the new listener is guaranteed not to be triggered within the same dispatching. This is guaranteed by an unsigned 64 bits integer counter. This rule will be broken is the counter is overflowed to zero in a dispatching, but this rule will continue working on the subsequence dispatching.
Expand Down
9 changes: 9 additions & 0 deletions doc/hetercallbacklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,12 @@ void operator() (Args && ...args) const;
Invoke each callbacks that can be called with `Args` in the callback list.
The callbacks are called with arguments `args`.
The callbacks are called in the thread same as the callee of `operator()`.

#### invokeIfAny

```c++
template <typename ...Args>
bool invokeIfAny(Args && ...args) const;
```
Same as `operator()`, but returns `true` if any callback that can be called with `Args` was invoked, `false` if there is no such callback.
Note: only the callbacks matching the prototype of `Args` count. If the list has callbacks for other prototypes only, the function returns `false`.
9 changes: 9 additions & 0 deletions doc/hetereventdispatcher.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,12 @@ Dispatch an event. The event type is deducted from the arguments of `dispatch`.
Invoke each callbacks that can be called with `Args` in the callback list.
The listeners are called with arguments `args`.
The function is synchronous. The listeners are called in the thread same as the caller of `dispatch`.

#### dispatchIfAny

```c++
template <typename T, typename ...Args>
bool dispatchIfAny(T && first, Args && ...args) const
```
Same as `dispatch`, but returns `true` if any listener that can be called with `Args` was invoked, `false` if there is no such listener or the dispatching is blocked by a mixin (`mixinBeforeDispatch` returns false).
Note: only the listeners matching the prototype of `Args` count. If the event has listeners for other prototypes only, the function returns `false`.
28 changes: 24 additions & 4 deletions include/eventpp/callbacklist.h
Original file line number Diff line number Diff line change
Expand Up @@ -275,27 +275,45 @@ class CallbackListBase<
});
}

#if !defined(__GNUC__) || __GNUC__ >= 5
void operator() (Args ...args) const
{
forEachIf([&args...](Callback & callback) -> bool {
(void)doInvoke(args...);
}

// Same as operator(), but returns true if any callback was invoked, false if the list is empty.
bool invokeIfAny(Args ...args) const
{
return doInvoke(args...);
}

private:
#if !defined(__GNUC__) || __GNUC__ >= 5
bool doInvoke(Args & ...args) const
{
bool invoked = false;

forEachIf([&args..., &invoked](Callback & callback) -> bool {
// We can't use std::forward here, because if we use std::forward,
// for arg that is passed by value, and the callback prototype accepts it by value,
// std::forward will move it and may cause the original value invalid.
// That happens on any value-to-value passing, no matter the callback moves it or not.

invoked = true;
callback(args...);
return CanContinueInvoking::canContinueInvoking(args...);
});

return invoked;
}
#else
// This is a patch version for GCC 4. It inlines the unrolled doForEachIf.
// GCC 4.8.3 doesn't supporting parameter pack catpure in lambda, see,
// https://github.com/wqking/eventpp/issues/19
// This is a compromised patch for GCC 4, it may be not maintained or updated unless there are bugs.
// We don't use the patch as main code because the patch generates longer code, and duplicated with doForEachIf.
void operator() (Args ...args) const
bool doInvoke(Args & ...args) const
{
bool invoked = false;
NodePtr node;

{
Expand All @@ -307,6 +325,7 @@ class CallbackListBase<

while(node) {
if(node->counter != removedCounter && counter >= node->counter) {
invoked = true;
node->callback(args...);
if(! CanContinueInvoking::canContinueInvoking(args...)) {
break;
Expand All @@ -318,10 +337,11 @@ class CallbackListBase<
node = node->next;
}
}

return invoked;
}
#endif

private:
template <typename F>
bool doForEachIf(F && f) const
{
Expand Down
64 changes: 52 additions & 12 deletions include/eventpp/eventdispatcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -202,48 +202,88 @@ class EventDispatcherBase <
}

void dispatch(Args ...args) const
{
(void)doDispatch(args...);
}

template <typename T>
void dispatch(T && first, Args ...args) const
{
(void)doDispatch(std::forward<T>(first), args...);
}

// Same as dispatch, but returns true if any listener was invoked.
// Returns false if there is no listener, or the dispatching is blocked by mixins (mixinBeforeDispatch).
// Useful to avoid calling hasAnyListener before dispatch, which looks up the event twice.
bool dispatchIfAny(Args ...args) const
{
return doDispatch(args...);
}

template <typename T>
bool dispatchIfAny(T && first, Args ...args) const
{
return doDispatch(std::forward<T>(first), args...);
}

// Bypass any getEvent policy. The first argument is the event type.
// Most used for internal purpose.
void directDispatch(const Event & e, Args ...args) const
{
(void)doDirectDispatch(e, args...);
}

// Same as directDispatch, but returns true if any listener was invoked.
bool directDispatchIfAny(const Event & e, Args ...args) const
{
return doDirectDispatch(e, args...);
}

protected:
// The arguments are passed by reference to the public functions' by value arguments,
// so they are moved only once, in doDirectDispatch.
bool doDispatch(Args & ...args) const
{
static_assert(ArgumentPassingMode::canIncludeEventType, "Dispatching arguments count doesn't match required (Event type should be included).");

using GetEvent = typename SelectGetEvent<Policies_, EventType_, HasFunctionGetEvent<Policies_, Args...>::value>::Type;

// can't std::forward<Args>(args) in GetEvent::getEvent because the pass by value arguments will be moved to getEvent
// then the other std::forward<Args>(args) to directDispatch will get empty values.
directDispatch(
// then the other std::forward<Args>(args) to invokeIfAny will get empty values.
return doDirectDispatch(
GetEvent::getEvent(args...),
std::forward<Args>(args)...
args...
);
}

template <typename T>
void dispatch(T && first, Args ...args) const
bool doDispatch(T && first, Args & ...args) const
{
static_assert(ArgumentPassingMode::canExcludeEventType, "Dispatching arguments count doesn't match required (Event type should NOT be included).");

using GetEvent = typename SelectGetEvent<Policies_, EventType_, HasFunctionGetEvent<Policies_, T &&, Args...>::value>::Type;

directDispatch(
return doDirectDispatch(
GetEvent::getEvent(std::forward<T>(first), args...),
std::forward<Args>(args)...
args...
);
}

// Bypass any getEvent policy. The first argument is the event type.
// Most used for internal purpose.
void directDispatch(const Event & e, Args ...args) const
bool doDirectDispatch(const Event & e, Args & ...args) const
{
if(! internal_::ForEachMixins<MixinRoot, Mixins, DoMixinBeforeDispatch>::forEach(
this, typename std::add_lvalue_reference<Args>::type(args)...)) {
return;
return false;
}

const CallbackList_ * callableList = doFindCallableList(e);
if(callableList) {
(*callableList)(std::forward<Args>(args)...);
return callableList->invokeIfAny(std::forward<Args>(args)...);
}

return false;
}

protected:
const CallbackList_ * doFindCallableList(const Event & e) const
{
return doFindCallableListHelper(this, e);
Expand Down
10 changes: 10 additions & 0 deletions include/eventpp/hetercallbacklist.h
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,16 @@ class HeterCallbackListBase
(*callbackList)(std::forward<Args>(args)...);
}

// Same as operator(), but returns true if any callback of the prototype matching the arguments was invoked.
template <typename ...Args>
bool invokeIfAny(Args && ...args) const
{
using PrototypeInfo = FindPrototypeByArgs<PrototypeList, Args...>;
static_assert(PrototypeInfo::index >= 0, "Can't find invoker for the given argument types.");

return doGetCallbackList<PrototypeInfo>()->invokeIfAny(std::forward<Args>(args)...);
}

private:
template <typename RT, int PrototypeIndex, typename Func, typename H, typename CL>
auto doForEachInvoke(Func && func, const H & handle, CL && callback) const
Expand Down
39 changes: 30 additions & 9 deletions include/eventpp/hetereventdispatcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -180,63 +180,84 @@ class HeterEventDispatcherBase
template <typename T, typename ...Args>
void dispatch(T && first, Args && ...args) const
{
doDispatch<ArgumentPassingMode>(std::forward<T>(first), std::forward<Args>(args)...);
(void)doDispatch<ArgumentPassingMode>(std::forward<T>(first), std::forward<Args>(args)...);
}

// Same as dispatch, but returns true if any listener with the prototype matching the arguments was invoked.
// Returns false if there is no listener, or the dispatching is blocked by mixins (mixinBeforeDispatch).
template <typename T, typename ...Args>
bool dispatchIfAny(T && first, Args && ...args) const
{
return doDispatch<ArgumentPassingMode>(std::forward<T>(first), std::forward<Args>(args)...);
}

// Bypass any getEvent policy. The first argument is the event type.
// Most used for internal purpose.
template <typename ...Args>
void directDispatch(const Event & e, Args && ...args) const
{
(void)directDispatchIfAny(e, std::forward<Args>(args)...);
}

// Same as directDispatch, but returns true if any listener with the prototype matching the arguments was invoked.
template <typename ...Args>
bool directDispatchIfAny(const Event & e, Args && ...args) const
{
if(! internal_::ForEachMixins<MixinRoot, Mixins, DoMixinBeforeDispatch>::forEach(
this, typename std::add_lvalue_reference<Args>::type(args)...)) {
return;
return false;
}

const CallbackList_ * callableList = doFindCallableList(e);
if(callableList) {
(*callableList)(std::forward<Args>(args)...);
return callableList->invokeIfAny(std::forward<Args>(args)...);
}

return false;
}

protected:
template <typename ArgumentMode, typename T, typename ...Args>
auto doDispatch(T && first, Args && ...args) const
-> typename std::enable_if<std::is_same<ArgumentMode, ArgumentPassingIncludeEvent>::value>::type
-> typename std::enable_if<std::is_same<ArgumentMode, ArgumentPassingIncludeEvent>::value, bool>::type
{
if(! internal_::ForEachMixins<MixinRoot, Mixins, DoMixinBeforeDispatch>::forEach(
this,
typename std::add_lvalue_reference<T>::type(first),
typename std::add_lvalue_reference<Args>::type(args)...)
) {
return;
return false;
}

using GetEvent = typename SelectGetEvent<Policies_, EventType_, HasFunctionGetEvent<Policies_, T &&, Args...>::value>::Type;
const auto e = GetEvent::getEvent(std::forward<T>(first), args...);
const CallbackList_ * callableList = doFindCallableList(e);
if(callableList) {
(*callableList)(std::forward<T>(first), std::forward<Args>(args)...);
return callableList->invokeIfAny(std::forward<T>(first), std::forward<Args>(args)...);
}

return false;
}

template <typename ArgumentMode, typename T, typename ...Args>
auto doDispatch(T && first, Args && ...args) const
-> typename std::enable_if<std::is_same<ArgumentMode, ArgumentPassingExcludeEvent>::value>::type
-> typename std::enable_if<std::is_same<ArgumentMode, ArgumentPassingExcludeEvent>::value, bool>::type
{
if(! internal_::ForEachMixins<MixinRoot, Mixins, DoMixinBeforeDispatch>::forEach(
this,
typename std::add_lvalue_reference<Args>::type(args)...)
) {
return;
return false;
}

using GetEvent = typename SelectGetEvent<Policies_, EventType_, HasFunctionGetEvent<Policies_, T &&, Args...>::value>::Type;
const auto e = GetEvent::getEvent(std::forward<T>(first), args...);
const CallbackList_ * callableList = doFindCallableList(e);
if(callableList) {
(*callableList)(std::forward<Args>(args)...);
return callableList->invokeIfAny(std::forward<Args>(args)...);
}

return false;
}

const CallbackList_ * doFindCallableList(const Event & e) const
Expand Down
1 change: 1 addition & 0 deletions tests/unittest/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ set(SRC_TEST
test_dispatcher_basic.cpp
test_dispatcher_ctors.cpp
test_dispatcher_multithread.cpp
test_dispatcher_dispatchifany.cpp
test_queue_basic.cpp
test_queue_ctors.cpp
test_queue_multithread.cpp
Expand Down
Loading