Add classes for event-handling

The new classes Event and EventSource can be used as a simple mechanism
for implementing event-based callbacks in the simulators.
This commit is contained in:
Roland Kaufmann 2013-08-26 12:52:03 +02:00
parent 889f8d7e49
commit 3156d0749a
5 changed files with 215 additions and 0 deletions

View File

@ -111,6 +111,7 @@ list (APPEND MAIN_SOURCE_FILES
opm/core/transport/reorder/TransportSolverTwophaseReorder.cpp
opm/core/transport/reorder/reordersequence.cpp
opm/core/transport/reorder/tarjan.c
opm/core/utility/Event.cpp
opm/core/utility/MonotCubicInterpolator.cpp
opm/core/utility/StopWatch.cpp
opm/core/utility/VelocityInterpolation.cpp
@ -141,6 +142,7 @@ list (APPEND TEST_SOURCE_FILES
tests/test_dgbasis.cpp
tests/test_cartgrid.cpp
tests/test_cubic.cpp
tests/test_event.cpp
tests/test_nonuniformtablelinear.cpp
tests/test_sparsevector.cpp
tests/test_sparsetable.cpp
@ -327,6 +329,8 @@ list (APPEND PUBLIC_HEADER_FILES
opm/core/utility/Average.hpp
opm/core/utility/DataMap.hpp
opm/core/utility/ErrorMacros.hpp
opm/core/utility/Event.hpp
opm/core/utility/Event_impl.hpp
opm/core/utility/Factory.hpp
opm/core/utility/MonotCubicInterpolator.hpp
opm/core/utility/NonuniformTableLinear.hpp

View File

@ -0,0 +1,22 @@
#include <opm/core/utility/Event.hpp>
using namespace std;
using namespace Opm;
Event&
EventSource::add (std::function <void ()> 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) ();
}
}

View File

@ -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 <functional>
#include <list>
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 <void ()> 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 <typename T, void (T::*member)()> 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, &Bar::callMe>(bar);
///
/// // set events in motion
/// foo.action();
/// \endcode
class EventSource : public Event {
public:
virtual Event& add (std::function <void ()> handler);
virtual void signal ();
protected:
/// List of actual handlers that will be called
std::list <std::function <void ()> > handlers_;
};
// inline definitions
#include "Event_impl.hpp"
} /* namespace Opm */
#endif /* OPM_EVENT_HEADER_INCLUDED */

View File

@ -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 <typename T, void (T::*member)()> 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)));
}

78
tests/test_event.cpp Normal file
View File

@ -0,0 +1,78 @@
/* Copyright 2013 Uni Research AS
* This file is licensed under GPL3, see http://www.opm-project.org/
*/
#include <config.h>
/* --- 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 <boost/test/unit_test.hpp>
/* --- our own headers --- */
#include <opm/core/utility/Event.hpp>
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<EventReceiver,&EventReceiver::handler>(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()