util: EventEmitter
refs #1118
Change-Id: Ib2c080f7ad0f70f2fcd9cc738b8d3601dcc2a98c
diff --git a/daemon/util/event-emitter.hpp b/daemon/util/event-emitter.hpp
new file mode 100644
index 0000000..fa7de00
--- /dev/null
+++ b/daemon/util/event-emitter.hpp
@@ -0,0 +1,325 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (C) 2014 Named Data Networking Project
+ * See COPYING for copyright and distribution information.
+ */
+
+#ifndef NFD_UTIL_EVENT_EMITTER_H
+#define NFD_UTIL_EVENT_EMITTER_H
+#include <vector>
+#include <boost/utility.hpp>
+#include <boost/function.hpp>
+namespace ndn {
+
+struct empty {};
+
+/** \class EventEmitter
+ * \brief provides a lightweight event system
+ *
+ * To declare an event:
+ * EventEmitter<TArgs> m_eventName;
+ * To subscribe to an event:
+ * eventSource->m_eventName += eventHandler;
+ * Multiple functions can subscribe to the same event.
+ * To trigger an event:
+ * m_eventName(args);
+ * To clear event subscriptions:
+ * m_eventName.clear();
+ */
+
+// four arguments
+template<typename T1 = empty, typename T2 = empty,
+ typename T3 = empty, typename T4 = empty>
+class EventEmitter : boost::noncopyable
+{
+public:
+ /// represents a handler that can subscribe to the event
+ typedef boost::function<void(const T1&, const T2&,
+ const T3&, const T4&)> Handler;
+
+ /// adds an subscription
+ void
+ operator+=(Handler handler);
+
+ /// returns true if there is no subscription,
+ /// otherwise returns false
+ bool
+ isEmpty();
+
+ /// clears all subscriptions
+ void
+ clear();
+
+ /// triggers the event
+ void
+ operator()(const T1& a1, const T2& a2, const T3& a3, const T4& a4);
+
+private:
+ /// stores all subscribed handlers
+ std::vector<Handler> m_handlers;
+};
+
+// zero argument
+template<>
+class EventEmitter<empty, empty, empty, empty> : boost::noncopyable
+{
+public:
+ typedef boost::function<void()> Handler;
+
+ void
+ operator+=(Handler handler);
+
+ bool
+ isEmpty();
+
+ void
+ clear();
+
+ void
+ operator()();
+
+private:
+ std::vector<Handler> m_handlers;
+};
+
+
+// one argument
+template<typename T1>
+class EventEmitter<T1, empty, empty, empty> : boost::noncopyable
+{
+public:
+ typedef boost::function<void(const T1&)> Handler;
+
+ void
+ operator+=(Handler handler);
+
+ bool
+ isEmpty();
+
+ void
+ clear();
+
+ void
+ operator()(const T1& a1);
+
+private:
+ std::vector<Handler> m_handlers;
+};
+
+
+// two arguments
+template<typename T1, typename T2>
+class EventEmitter<T1, T2, empty, empty> : boost::noncopyable
+{
+public:
+ typedef boost::function<void(const T1&, const T2&)> Handler;
+
+ void
+ operator+=(Handler handler);
+
+ bool
+ isEmpty();
+
+ void
+ clear();
+
+ void
+ operator()(const T1& a1, const T2& a2);
+
+private:
+ std::vector<Handler> m_handlers;
+};
+
+
+// three arguments
+template<typename T1, typename T2, typename T3>
+class EventEmitter<T1, T2, T3, empty> : boost::noncopyable
+{
+public:
+ typedef boost::function<void(const T1&, const T2&,
+ const T3&)> Handler;
+
+ void
+ operator+=(Handler handler);
+
+ bool
+ isEmpty();
+
+ void
+ clear();
+
+ void
+ operator()(const T1& a1, const T2& a2, const T3& a3);
+
+private:
+ std::vector<Handler> m_handlers;
+};
+
+
+// zero argument
+
+inline void
+EventEmitter<empty, empty, empty, empty>::operator+=(Handler handler)
+{
+ m_handlers.push_back(handler);
+}
+
+inline bool
+EventEmitter<empty, empty, empty, empty>::isEmpty()
+{
+ return m_handlers.empty();
+}
+
+inline void
+EventEmitter<empty, empty, empty, empty>::clear()
+{
+ return m_handlers.clear();
+}
+
+inline void
+EventEmitter<empty, empty, empty, empty>::operator()()
+{
+ std::vector<Handler>::iterator it;
+ for (it = m_handlers.begin(); it != m_handlers.end(); ++it) {
+ (*it)();
+ }
+}
+
+// one argument
+
+template<typename T1>
+inline void
+EventEmitter<T1, empty, empty, empty>::operator+=(Handler handler)
+{
+ m_handlers.push_back(handler);
+}
+
+template<typename T1>
+inline bool
+EventEmitter<T1, empty, empty, empty>::isEmpty()
+{
+ return m_handlers.empty();
+}
+
+template<typename T1>
+inline void
+EventEmitter<T1, empty, empty, empty>::clear()
+{
+ return m_handlers.clear();
+}
+
+template<typename T1>
+inline void
+EventEmitter<T1, empty, empty, empty>::operator()(const T1& a1)
+{
+ typename std::vector<Handler>::iterator it;
+ for (it = m_handlers.begin(); it != m_handlers.end(); ++it) {
+ (*it)(a1);
+ }
+}
+
+// two arguments
+
+template<typename T1, typename T2>
+inline void
+EventEmitter<T1, T2, empty, empty>::operator+=(Handler handler)
+{
+ m_handlers.push_back(handler);
+}
+
+template<typename T1, typename T2>
+inline bool
+EventEmitter<T1, T2, empty, empty>::isEmpty()
+{
+ return m_handlers.empty();
+}
+
+template<typename T1, typename T2>
+inline void
+EventEmitter<T1, T2, empty, empty>::clear()
+{
+ return m_handlers.clear();
+}
+
+template<typename T1, typename T2>
+inline void
+EventEmitter<T1, T2, empty, empty>::operator()
+ (const T1& a1, const T2& a2)
+{
+ typename std::vector<Handler>::iterator it;
+ for (it = m_handlers.begin(); it != m_handlers.end(); ++it) {
+ (*it)(a1, a2);
+ }
+}
+
+// three arguments
+
+template<typename T1, typename T2, typename T3>
+inline void
+EventEmitter<T1, T2, T3, empty>::operator+=(Handler handler)
+{
+ m_handlers.push_back(handler);
+}
+
+template<typename T1, typename T2, typename T3>
+inline bool
+EventEmitter<T1, T2, T3, empty>::isEmpty()
+{
+ return m_handlers.empty();
+}
+
+template<typename T1, typename T2, typename T3>
+inline void
+EventEmitter<T1, T2, T3, empty>::clear()
+{
+ return m_handlers.clear();
+}
+
+template<typename T1, typename T2, typename T3>
+inline void
+EventEmitter<T1, T2, T3, empty>::operator()
+ (const T1& a1, const T2& a2, const T3& a3)
+{
+ typename std::vector<Handler>::iterator it;
+ for (it = m_handlers.begin(); it != m_handlers.end(); ++it) {
+ (*it)(a1, a2, a3);
+ }
+}
+
+// four arguments
+
+template<typename T1, typename T2, typename T3, typename T4>
+inline void
+EventEmitter<T1, T2, T3, T4>::operator+=(Handler handler)
+{
+ m_handlers.push_back(handler);
+}
+
+template<typename T1, typename T2, typename T3, typename T4>
+inline bool
+EventEmitter<T1, T2, T3, T4>::isEmpty()
+{
+ return m_handlers.empty();
+}
+
+template<typename T1, typename T2, typename T3, typename T4>
+inline void
+EventEmitter<T1, T2, T3, T4>::clear()
+{
+ return m_handlers.clear();
+}
+
+template<typename T1, typename T2, typename T3, typename T4>
+inline void
+EventEmitter<T1, T2, T3, T4>::operator()
+ (const T1& a1, const T2& a2, const T3& a3, const T4& a4)
+{
+ typename std::vector<Handler>::iterator it;
+ for (it = m_handlers.begin(); it != m_handlers.end(); ++it) {
+ (*it)(a1, a2, a3, a4);
+ }
+}
+
+
+};//namespace ndn
+#endif//NFD_UTIL_EVENT_EMITTER_H
diff --git a/tests/util/event-emitter.cpp b/tests/util/event-emitter.cpp
new file mode 100644
index 0000000..057b920
--- /dev/null
+++ b/tests/util/event-emitter.cpp
@@ -0,0 +1,229 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (C) 2014 Named Data Networking Project
+ * See COPYING for copyright and distribution information.
+ */
+
+#include <util/event-emitter.hpp>
+#include <boost/bind.hpp>
+#include <boost/test/unit_test.hpp>
+namespace ndn {
+
+class EventEmitterTester : boost::noncopyable
+{
+public:
+ EventEmitterTester();
+
+ int m_hit;
+ int m_a1;
+ int m_a2;
+ int m_a3;
+ int m_a4;
+
+ void
+ clear();
+
+ void
+ f0();
+
+ void
+ f1(int a1);
+
+ void
+ f2(int a1, int a2);
+
+ void
+ f3(int a1, int a2, int a3);
+
+ void
+ f4(int a1, int a2, int a3, int a4);
+};
+
+EventEmitterTester::EventEmitterTester()
+{
+ this->clear();
+}
+
+void
+EventEmitterTester::clear()
+{
+ m_hit = 0;
+ m_a1 = 0;
+ m_a2 = 0;
+ m_a3 = 0;
+ m_a4 = 0;
+}
+
+void
+EventEmitterTester::f0()
+{
+ ++m_hit;
+}
+
+void
+EventEmitterTester::f1(int a1)
+{
+ ++m_hit;
+ m_a1 = a1;
+}
+
+void
+EventEmitterTester::f2(int a1, int a2)
+{
+ ++m_hit;
+ m_a1 = a1;
+ m_a2 = a2;
+}
+
+void
+EventEmitterTester::f3(int a1, int a2, int a3)
+{
+ ++m_hit;
+ m_a1 = a1;
+ m_a2 = a2;
+ m_a3 = a3;
+}
+
+void
+EventEmitterTester::f4(int a1, int a2, int a3, int a4)
+{
+ ++m_hit;
+ m_a1 = a1;
+ m_a2 = a2;
+ m_a3 = a3;
+ m_a4 = a4;
+}
+
+static int g_EventEmitterTest_RefObject_copyCount;
+
+class EventEmitterTest_RefObject
+{
+public:
+ EventEmitterTest_RefObject() {}
+
+ EventEmitterTest_RefObject(const EventEmitterTest_RefObject& other);
+};
+
+EventEmitterTest_RefObject::EventEmitterTest_RefObject(const EventEmitterTest_RefObject& other)
+{
+ ++g_EventEmitterTest_RefObject_copyCount;
+}
+
+void
+EventEmitterTest_RefObject_byVal(EventEmitterTest_RefObject a1) {}
+
+void
+EventEmitterTest_RefObject_byRef(const EventEmitterTest_RefObject& a1) {}
+
+
+BOOST_AUTO_TEST_SUITE(UtilEventEmitter)
+
+BOOST_AUTO_TEST_CASE(ZeroListener)
+{
+ EventEmitter<> ee;
+ BOOST_CHECK_NO_THROW(ee());
+}
+
+BOOST_AUTO_TEST_CASE(TwoListeners)
+{
+ EventEmitterTester eet1;
+ EventEmitterTester eet2;
+ EventEmitter<> ee;
+ ee += boost::bind(&EventEmitterTester::f0, &eet1);
+ ee += boost::bind(&EventEmitterTester::f0, &eet2);
+ ee();
+
+ BOOST_CHECK_EQUAL(eet1.m_hit, 1);
+ BOOST_CHECK_EQUAL(eet2.m_hit, 1);
+}
+
+BOOST_AUTO_TEST_CASE(ZeroArgument)
+{
+ EventEmitterTester eet;
+ EventEmitter<> ee;
+ ee += boost::bind(&EventEmitterTester::f0, &eet);
+ ee();
+
+ BOOST_CHECK_EQUAL(eet.m_hit, 1);
+}
+
+BOOST_AUTO_TEST_CASE(OneArgument)
+{
+ EventEmitterTester eet;
+ EventEmitter<int> ee;
+ ee += boost::bind(&EventEmitterTester::f1, &eet, _1);
+ ee(11);
+
+ BOOST_CHECK_EQUAL(eet.m_hit, 1);
+ BOOST_CHECK_EQUAL(eet.m_a1, 11);
+}
+
+BOOST_AUTO_TEST_CASE(TwoArguments)
+{
+ EventEmitterTester eet;
+ EventEmitter<int,int> ee;
+ ee += boost::bind(&EventEmitterTester::f2, &eet, _1, _2);
+ ee(21, 22);
+
+ BOOST_CHECK_EQUAL(eet.m_hit, 1);
+ BOOST_CHECK_EQUAL(eet.m_a1, 21);
+ BOOST_CHECK_EQUAL(eet.m_a2, 22);
+}
+
+BOOST_AUTO_TEST_CASE(ThreeArguments)
+{
+ EventEmitterTester eet;
+ EventEmitter<int,int,int> ee;
+ ee += boost::bind(&EventEmitterTester::f3, &eet, _1, _2, _3);
+ ee(31, 32, 33);
+
+ BOOST_CHECK_EQUAL(eet.m_hit, 1);
+ BOOST_CHECK_EQUAL(eet.m_a1, 31);
+ BOOST_CHECK_EQUAL(eet.m_a2, 32);
+ BOOST_CHECK_EQUAL(eet.m_a3, 33);
+}
+
+BOOST_AUTO_TEST_CASE(FourArguments)
+{
+ EventEmitterTester eet;
+ EventEmitter<int,int,int,int> ee;
+ ee += boost::bind(&EventEmitterTester::f4, &eet, _1, _2, _3, _4);
+ ee(41, 42, 43, 44);
+
+ BOOST_CHECK_EQUAL(eet.m_hit, 1);
+ BOOST_CHECK_EQUAL(eet.m_a1, 41);
+ BOOST_CHECK_EQUAL(eet.m_a2, 42);
+ BOOST_CHECK_EQUAL(eet.m_a3, 43);
+ BOOST_CHECK_EQUAL(eet.m_a4, 44);
+}
+
+// EventEmitter passes arguments by reference,
+// but it also allows a handler that accept arguments by value
+BOOST_AUTO_TEST_CASE(HandlerByVal)
+{
+ EventEmitterTest_RefObject refObject;
+ g_EventEmitterTest_RefObject_copyCount = 0;
+
+ EventEmitter<EventEmitterTest_RefObject> ee;
+ ee += &EventEmitterTest_RefObject_byVal;
+ ee(refObject);
+
+ BOOST_CHECK_EQUAL(g_EventEmitterTest_RefObject_copyCount, 1);
+}
+
+// EventEmitter passes arguments by reference, and no copying
+// is necessary when handler accepts arguments by reference
+BOOST_AUTO_TEST_CASE(HandlerByRef)
+{
+ EventEmitterTest_RefObject refObject;
+ g_EventEmitterTest_RefObject_copyCount = 0;
+
+ EventEmitter<EventEmitterTest_RefObject> ee;
+ ee += &EventEmitterTest_RefObject_byRef;
+ ee(refObject);
+
+ BOOST_CHECK_EQUAL(g_EventEmitterTest_RefObject_copyCount, 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+};//namespace ndn
diff --git a/wscript b/wscript
index fdb3821..2561467 100644
--- a/wscript
+++ b/wscript
@@ -63,9 +63,9 @@
unittests = bld.program (
target="unit-tests",
features = "cxx cxxprogram",
- source = bld.path.ant_glob(['tests/*.cpp']),
+ source = bld.path.ant_glob(['tests/**/*.cpp']),
use = 'BOOST NDN_CPP',
- includes = ".",
+ includes = [".", "daemon"],
install_prefix = None,
)