1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
|
properties-cpp {#mainpage}
===========
process-cpp is a simple header-only implementation of properties and
signals. It is meant to be used for developing low-level system
services. Its main features include:
- Thread-safe signal invocation and observer mgmt.
- The ability to dispatch signal invocations via arbitrary event loops.
- Typed properties with an in-place update mechanism that avoids unneccessary deep copies.
- Well tested and documented.
A Textfield With an Observable Cursor Position
----------------------------------------------
~~~~~~~~~~~~~{.cpp}
namespace
{
struct TextField
{
void move_cursor_to(int new_position)
{
cursor_position.set(new_position);
}
core::Property<int> cursor_position;
};
}
TEST(Property, cursor_position_changes_are_transported_correctly)
{
int position = -1;
TextField tf;
// Setup a connection to the cursor position property
tf.cursor_position.changed().connect(
[&position](int value)
{
position = value;
});
// Move the cursor
tf.move_cursor_to(22);
// Check that the correct value has propagated
EXPECT_EQ(22, position);
}
~~~~~~~~~~~~~
Integrating With Arbitrary Event Loops/Reactor Implementations
--------------------------------------------------------------
~~~~~~~~~~~~~{.cpp}
namespace
{
struct EventLoop
{
typedef std::function<void()> Handler;
void stop()
{
stop_requested = true;
}
void run()
{
while (!stop_requested)
{
std::unique_lock<std::mutex> ul(guard);
wait_condition.wait_for(
ul,
std::chrono::milliseconds{500},
[this]() { return handlers.size() > 0; });
while (handlers.size() > 0)
{
handlers.front()();
handlers.pop();
}
}
}
void dispatch(const Handler& h)
{
std::lock_guard<std::mutex> lg(guard);
handlers.push(h);
}
bool stop_requested = false;
std::queue<Handler> handlers;
std::mutex guard;
std::condition_variable wait_condition;
};
}
TEST(Signal, installing_a_custom_dispatcher_ensures_invocation_on_correct_thread)
{
// We instantiate an event loop and run it on a different thread than the main one.
EventLoop dispatcher;
std::thread dispatcher_thread{[&dispatcher]() { dispatcher.run(); }};
std::thread::id dispatcher_thread_id = dispatcher_thread.get_id();
// The signal that we want to dispatch via the event loop.
core::Signal<int, double> s;
static const int expected_invocation_count = 10000;
// Setup the connection. For each invocation we check that the id of the
// thread the handler is being called upon equals the thread that the
// event loop is running upon.
auto connection = s.connect(
[&dispatcher, dispatcher_thread_id](int value, double d)
{
EXPECT_EQ(dispatcher_thread_id,
std::this_thread::get_id());
std::cout << d << std::endl;
if (value == expected_invocation_count)
dispatcher.stop();
});
// Route the connection via the dispatcher
connection.dispatch_via(
std::bind(
&EventLoop::dispatch,
std::ref(dispatcher),
std::placeholders::_1));
// Invoke the signal from the main thread.
for (unsigned int i = 1; i <= expected_invocation_count; i++)
s(i, 42.);
if (dispatcher_thread.joinable())
dispatcher_thread.join();
}
~~~~~~~~~~~~~
|