diff --git a/opm/core/utility/Event.cpp b/opm/core/utility/Event.cpp new file mode 100644 index 000000000..74cb62095 --- /dev/null +++ b/opm/core/utility/Event.cpp @@ -0,0 +1,22 @@ +#include + +using namespace std; +using namespace Opm; + +Event& +EventSource::add (std::function handler) { + // add handler to the back of the queue + handlers_.push_back (handler); + + // return ourselves so we can be used in a call chain + return *this; +} + +void +EventSource::signal () { + // loop through the list of handlers, and invoke every one of them + // (range-based for loops are not available until GCC 4.6) + for (auto it = handlers_.begin(); it != handlers_.end(); ++it) { + (*it) (); + } +} diff --git a/opm/core/utility/Event.hpp b/opm/core/utility/Event.hpp new file mode 100644 index 000000000..5e9ae2126 --- /dev/null +++ b/opm/core/utility/Event.hpp @@ -0,0 +1,98 @@ +#ifndef OPM_EVENT_HEADER_INCLUDED +#define OPM_EVENT_HEADER_INCLUDED + +// Copyright (C) 2013 Uni Research AS +// This file is licensed under the GNU General Public License v3.0 + +#include +#include + +namespace Opm { + +/// Interface to register interest in receiving notifications when a +/// certain event, such as the completion of a timestep, has happened. +struct Event { + /// Register a callback to receive notifications from this event. + /// + /// \param[in] handler + /// Function object that will be invoked when the event happens. + /// + /// \return + /// The event object itself, so that multiple additions can be chained. + /// + /// \note + /// The event may happen several times, and the handler will receive + /// a notification every time. + /// + /// \note + /// If a handler is added more than once, it will also be called + /// more than once. + virtual Event& add (std::function handler) = 0; + + /// Convenience routine to add a member function of a class as + /// an event handler. + /// + /// This allows us to have all the necessary information the handler + /// needs put into an object, and then register this with the event. + template Event& add (T& t); +}; + +/// Generator of event notifications. +/// +/// As more than one event is possible from an object, it is expected +/// that event servers implements this functionality by aggregation and +/// provide accessors to let clients reach the various events. +/// +/// You should not provide the full EventSource interface to clients, +/// as this will allow them to signal the event themselves; rather, return +/// the registration-only parent interface. +/// +/// \example +/// You can add an event to your code like this: +/// +/// \code{.cpp} +/// struct Foo { +/// // accessor of the event that other can register at +/// Event& completed () { return completed_; } +/// +/// // something that ultimately triggers the event +/// void action () { /* ... */ completed_.signal(); } +/// +/// private: +/// EventSource completed_; +/// }; +/// \endcode +/// +/// It could then be accessed by the client like this: +/// +/// \code{.cpp} +/// struct Bar { +/// void callMe() { /* ... */ } +/// }; +/// \endcode +/// +/// \code{.cpp} +/// Foo foo; +/// Bar bar; +/// +/// // setup the connection between the two +/// foo.completed().add(bar); +/// +/// // set events in motion +/// foo.action(); +/// \endcode +class EventSource : public Event { +public: + virtual Event& add (std::function handler); + virtual void signal (); +protected: + /// List of actual handlers that will be called + std::list > handlers_; +}; + +// inline definitions +#include "Event_impl.hpp" + +} /* namespace Opm */ + +#endif /* OPM_EVENT_HEADER_INCLUDED */ diff --git a/opm/core/utility/Event_impl.hpp b/opm/core/utility/Event_impl.hpp new file mode 100644 index 000000000..3d9f81a1e --- /dev/null +++ b/opm/core/utility/Event_impl.hpp @@ -0,0 +1,13 @@ +// Copyright (C) 2013 Uni Research AS +// This file is licensed under the GNU General Public License v3.0 + +#ifndef OPM_EVENT_HEADER_INCLUDED +#error Do NOT include this file directly! +#endif /* OPM_EVENT_HEADER_INCLUDED */ + +template inline Event& +Event::add (T& t) { + // wrap the member function in a std::function and add that + // notice the use of ref() to avoid invoking the copy constructor + return this->add (std::bind (member, std::ref(t))); +} diff --git a/tests/test_event.cpp b/tests/test_event.cpp new file mode 100644 index 000000000..a5179cdb3 --- /dev/null +++ b/tests/test_event.cpp @@ -0,0 +1,78 @@ +/* Copyright 2013 Uni Research AS + * This file is licensed under GPL3, see http://www.opm-project.org/ +*/ +#include + +/* --- Boost.Test boilerplate --- */ +#if HAVE_DYNAMIC_BOOST_TEST +#define BOOST_TEST_DYN_LINK +#endif + +#define NVERBOSE // Suppress own messages when throw()ing + +#define BOOST_TEST_MODULE EventTest +#include + +/* --- our own headers --- */ +#include + +using namespace std; +using namespace Opm; + +// idiomatic implementation of generator and receiver +struct EventGenerator { + EventSource eventSource_; + Event& event () { return eventSource_; } + void action () { eventSource_.signal (); } +}; + +struct EventReceiver { + int numOfCalls; + EventReceiver () : numOfCalls (0) { } + void handler () { ++numOfCalls; } +private: + // make sure bind() doesn't implement copy constructor + EventReceiver (EventReceiver&); +}; + +// declare a generator, a receiver and connect them +struct EventFixture { + EventGenerator gen; + EventReceiver recv; + void register_handler () { + gen.event().add(recv); + } + + EventFixture () { + register_handler(); + } +}; + +BOOST_FIXTURE_TEST_SUITE(EventTest, EventFixture) + +BOOST_AUTO_TEST_CASE(none) +{ + BOOST_REQUIRE_EQUAL (recv.numOfCalls, 0); +} + +BOOST_AUTO_TEST_CASE(once) +{ + gen.action(); + BOOST_REQUIRE_EQUAL (recv.numOfCalls, 1); +} + +BOOST_AUTO_TEST_CASE(twice) +{ + gen.action(); + gen.action(); + BOOST_REQUIRE_EQUAL (recv.numOfCalls, 2); +} + +BOOST_AUTO_TEST_CASE(reg_twice) +{ + register_handler(); + gen.action(); + BOOST_REQUIRE_EQUAL (recv.numOfCalls, 2); +} + +BOOST_AUTO_TEST_SUITE_END()