observer pattern classes. Allows auto and explicit dispatch, and specifying which trigger method to use of an observer.

This commit is contained in:
Francisco Paisana 2020-08-18 16:59:42 +01:00
parent 997552ee45
commit b2313e3631
3 changed files with 472 additions and 0 deletions

View File

@ -0,0 +1,212 @@
/*
* Copyright 2013-2020 Software Radio Systems Limited
*
* This file is part of srsLTE.
*
* srsLTE is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* srsLTE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* A copy of the GNU Affero General Public License can be found in
* the LICENSE file in the top-level directory of this distribution
* and at http://www.gnu.org/licenses/.
*
*/
#ifndef SRSLTE_OBSERVER_H
#define SRSLTE_OBSERVER_H
#include <deque>
#include <functional>
#include <limits>
#include <vector>
namespace srslte {
using observer_id = std::size_t;
const size_t invalid_observer_id = std::numeric_limits<observer_id>::max();
template <typename... Args>
class base_observable
{
public:
using callback_t = std::function<void(Args...)>;
//! Subscribe Observer that is a callback
template <typename Callable>
typename std::enable_if<std::is_convertible<Callable, callback_t>::value, observer_id>::type
subscribe(Callable&& callable)
{
return subscribe_common(callable);
}
//! Subscribe Observer type with method Observer::trigger(Args...)
template <typename Observer>
typename std::enable_if<not std::is_convertible<Observer, callback_t>::value, observer_id>::type
subscribe(Observer& observer)
{
return subscribe_common([&observer](Args... args) { observer.trigger(std::forward<Args>(args)...); });
}
//! Subscribe Observer type with custom trigger method
template <typename Observer>
observer_id subscribe(Observer& observer, void (Observer::*trigger_method)(Args...))
{
return subscribe_common(
[&observer, trigger_method](Args... args) { (observer.*trigger_method)(std::forward<Args>(args)...); });
}
//! Unsubscribe Observer
bool unsubscribe(observer_id id)
{
if (id < observers.size() and static_cast<bool>(observers[id])) {
observers[id] = nullptr;
return true;
}
return false;
}
size_t nof_observers() const
{
size_t count = 0;
for (auto& slot : observers) {
count += static_cast<bool>(slot) ? 1 : 0;
}
return count;
}
//! Signal result to observers
void dispatch(Args... args)
{
for (auto& obs_callback : observers) {
if (obs_callback) {
obs_callback(std::forward<Args>(args)...);
}
}
}
protected:
using observer_list_t = std::deque<callback_t>;
~base_observable() = default;
template <typename Callable>
observer_id subscribe_common(Callable&& callable)
{
size_t id = 0;
for (auto& slot : observers) {
if (not static_cast<bool>(slot)) {
// empty slot found
slot = std::forward<Callable>(callable);
return id;
}
id++;
}
// append to end of list
observers.emplace_back(std::forward<Callable>(callable));
return observers.size() - 1;
}
observer_list_t observers;
};
template <typename... Args>
class observable : public base_observable<Args...>
{};
//! Special case of observable for event types
template <typename Event>
class event_dispatcher : public base_observable<const Event&>
{};
//! Event Subject that enqueues events and only signals observers when ::process() is called
template <typename Event>
class event_queue : public base_observable<const Event&>
{
using base_t = base_observable<const Event&>;
public:
template <typename... Args>
void enqueue(Args&&... args)
{
pending_events.emplace_back(std::forward<Args>(args)...);
}
void process()
{
for (auto& ev : pending_events) {
base_t::dispatch(ev);
}
pending_events.clear();
}
private:
// forbid direct dispatches
using base_t::dispatch;
std::vector<Event> pending_events;
};
//! RAII class to automatically unsubscribe an observer from an Event
template <typename Event>
class unique_observer_id
{
using subject_t = base_observable<const Event&>;
public:
unique_observer_id(subject_t& parent_, observer_id id_) : parent(&parent_), id(id_) {}
template <typename T>
unique_observer_id(subject_t& parent_, T&& callable) : parent(&parent_)
{
id = parent->subscribe(std::forward<T>(callable));
}
template <typename Observer>
unique_observer_id(subject_t& parent_, Observer& observer, void (Observer::*trigger_method)(const Event&)) :
parent(&parent_)
{
id = parent->subscribe(observer, trigger_method);
}
unique_observer_id(unique_observer_id&& other) noexcept : parent(other.parent), id(other.id)
{
other.parent = nullptr;
}
unique_observer_id(const unique_observer_id& other) = delete;
unique_observer_id& operator=(unique_observer_id&& other) noexcept
{
parent = other.parent;
id = other.id;
other.id = invalid_observer_id;
return *this;
}
unique_observer_id& operator=(const unique_observer_id& other) = delete;
~unique_observer_id()
{
if (id != invalid_observer_id) {
parent->unsubscribe(id);
}
}
observer_id get_id() const { return id; }
bool is_valid() const { return id != invalid_observer_id; }
observer_id release()
{
observer_id ret = id;
id = invalid_observer_id;
return ret;
}
private:
subject_t* parent;
observer_id id;
};
} // namespace srslte
#endif // SRSLTE_OBSERVER_H

View File

@ -41,3 +41,7 @@ add_test(span_test span_test)
add_executable(interval_test interval_test.cc)
target_link_libraries(interval_test srslte_common)
add_test(interval_test interval_test)
add_executable(observer_test observer_test.cc)
target_link_libraries(observer_test srslte_common)
add_test(observer_test observer_test)

View File

@ -0,0 +1,256 @@
/*
* Copyright 2013-2020 Software Radio Systems Limited
*
* This file is part of srsLTE.
*
* srsLTE is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* srsLTE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* A copy of the GNU Affero General Public License can be found in
* the LICENSE file in the top-level directory of this distribution
* and at http://www.gnu.org/licenses/.
*
*/
#include "srslte/adt/observer.h"
#include "srslte/common/test_common.h"
struct M {
M() = default;
explicit M(int v) : val(v) {}
M(M&&) noexcept = default;
M(const M&) = delete;
M& operator=(M&&) noexcept = default;
M& operator=(const M&) = delete;
M& operator+=(int i)
{
val += i;
return *this;
}
int val = 0;
};
struct lval_observer_tester {
void trigger(M v_) { v = std::move(v_); }
void foo(M v_)
{
v = std::move(v_);
v += 1;
}
M v;
};
struct cref_observer_tester {
void trigger(const M& v_) { v.val = v_.val; }
void foo(const M& v_) { v.val = v_.val + 1; }
M v;
};
struct lref_observer_tester {
void trigger(M& v_) { v.val = v_.val; }
void foo(M& v_) { v.val = v_.val + 1; }
M v;
};
struct rref_observer_tester {
void trigger(M&& v_) { v = std::move(v_); }
void foo(M&& v_)
{
v = std::move(v_);
v += 1;
}
M v;
};
int observable_test()
{
// TEST l-value arguments passed by value
{
M val;
srslte::observable<M> subject;
TESTASSERT(subject.nof_observers() == 0);
srslte::observer_id id1 = subject.subscribe([&val](M v) { val = std::move(v); });
lval_observer_tester observer{}, observer2{};
srslte::observer_id id2 = subject.subscribe(observer);
srslte::observer_id id3 = subject.subscribe(observer2, &lval_observer_tester::foo);
TESTASSERT(subject.nof_observers() == 3);
TESTASSERT(val.val == 0);
subject.dispatch(M{5});
TESTASSERT(val.val == 5);
TESTASSERT(observer.v.val == 5);
TESTASSERT(observer2.v.val == 6);
subject.unsubscribe(id1);
TESTASSERT(subject.nof_observers() == 2);
subject.unsubscribe(id2);
TESTASSERT(subject.nof_observers() == 1);
subject.unsubscribe(id3);
TESTASSERT(subject.nof_observers() == 0);
}
// Test l-value arguments passed by const ref
{
M val;
srslte::observable<const M&> subject;
TESTASSERT(subject.nof_observers() == 0);
subject.subscribe([&val](const M& v) { val.val = v.val; });
cref_observer_tester observer{}, observer2{};
subject.subscribe(observer);
subject.subscribe(observer2, &cref_observer_tester::foo);
M new_val{6};
subject.dispatch(new_val);
TESTASSERT(val.val == 6);
TESTASSERT(observer.v.val == 6);
TESTASSERT(observer2.v.val == 7);
}
// Test l-value arguments passed by ref
{
M val;
srslte::observable<M&> subject;
TESTASSERT(subject.nof_observers() == 0);
subject.subscribe([&val](M& v) { val = std::move(v); });
lref_observer_tester observer{}, observer2{};
subject.subscribe(observer);
subject.subscribe(observer2, &lref_observer_tester::foo);
M new_val{6};
subject.dispatch(new_val);
TESTASSERT(val.val == 6);
TESTASSERT(observer.v.val == 6);
TESTASSERT(observer2.v.val == 7);
}
// Test r-value arguments
{
M val;
srslte::observable<M&&> subject;
TESTASSERT(subject.nof_observers() == 0);
srslte::observer_id id1 = subject.subscribe([&val](M&& v) { val = std::move(v); });
rref_observer_tester observer{}, observer2{};
srslte::observer_id id2 = subject.subscribe(observer);
srslte::observer_id id3 = subject.subscribe(observer2, &rref_observer_tester::foo);
subject.dispatch(M{3});
TESTASSERT(val.val == 3);
TESTASSERT(observer.v.val == 3);
TESTASSERT(observer2.v.val == 4);
subject.unsubscribe(id1);
subject.unsubscribe(id2);
subject.unsubscribe(id3);
TESTASSERT(subject.nof_observers() == 0);
}
return SRSLTE_SUCCESS;
}
int event_dispatcher_test()
{
srslte::event_dispatcher<M> signaller;
M val;
signaller.subscribe([&val](const M& ev) { val.val = ev.val; });
cref_observer_tester observer, observer2;
signaller.subscribe(observer);
signaller.subscribe(observer2, &cref_observer_tester::foo);
TESTASSERT(val.val == 0);
TESTASSERT(observer.v.val == 0);
TESTASSERT(observer2.v.val == 0);
signaller.dispatch(M{2});
TESTASSERT(val.val == 2);
TESTASSERT(observer.v.val == 2);
TESTASSERT(observer2.v.val == 3);
val.val = 1;
observer.v.val = 0;
observer2.v.val = 5;
signaller.dispatch(M{2});
TESTASSERT(val.val == 2);
TESTASSERT(observer.v.val == 2);
TESTASSERT(observer2.v.val == 3);
return SRSLTE_SUCCESS;
}
int event_queue_test()
{
srslte::event_queue<M> signaller;
M val;
signaller.subscribe([&val](const M& ev) { val.val = ev.val; });
cref_observer_tester observer, observer2;
signaller.subscribe(observer);
signaller.subscribe(observer2, &cref_observer_tester::foo);
TESTASSERT(val.val == 0);
TESTASSERT(observer.v.val == 0);
TESTASSERT(observer2.v.val == 0);
signaller.enqueue(M{2});
TESTASSERT(val.val == 0);
TESTASSERT(observer.v.val == 0);
TESTASSERT(observer2.v.val == 0);
signaller.process();
TESTASSERT(val.val == 2);
TESTASSERT(observer.v.val == 2);
TESTASSERT(observer2.v.val == 3);
return SRSLTE_SUCCESS;
}
int unique_subscribe_test()
{
{
srslte::event_dispatcher<M> signaller;
cref_observer_tester observer;
TESTASSERT(signaller.nof_observers() == 0);
{
srslte::unique_observer_id<M> obs{signaller, observer};
TESTASSERT(signaller.nof_observers() == 1);
}
TESTASSERT(signaller.nof_observers() == 0);
}
{
srslte::event_queue<M> signaller;
cref_observer_tester observer;
TESTASSERT(signaller.nof_observers() == 0);
{
srslte::unique_observer_id<M> obs{signaller, observer};
TESTASSERT(signaller.nof_observers() == 1);
}
TESTASSERT(signaller.nof_observers() == 0);
}
return SRSLTE_SUCCESS;
}
int main()
{
TESTASSERT(observable_test() == SRSLTE_SUCCESS);
TESTASSERT(event_dispatcher_test() == SRSLTE_SUCCESS);
TESTASSERT(event_queue_test() == SRSLTE_SUCCESS);
TESTASSERT(unique_subscribe_test() == SRSLTE_SUCCESS);
printf("Success\n");
return 0;
}