* limitations under the License.
*/
+#include "BatchRunner.hpp"
#include "../UniversalSwitch.hpp"
#include "../UniversalSwitchLog.hpp"
#include "Lexer.hpp"
#include "../ActivityFactory.hpp"
#include "../Observer.hpp"
#include "../UIElement.hpp"
+#include "../UIActivity.hpp"
#include "../NavigationInterface.hpp"
+#include "Monitor.hpp"
+#include "../DoneCallback.hpp"
+#include "../DBus.hpp"
#include <mutex>
-#include <future>
#include <chrono>
#include <thread>
#include <fstream>
+#include <cmath>
+#include <type_traits>
-struct ExecuteOnMainThreadHelper {
- std::function<void()> callback;
+
+namespace WrapDetails
+{
+ template <typename MONITORED_TYPE, typename ... ARGS>
+ static std::function<void(DBus::ValueOrError<ARGS...>)> wrapImpl(
+ std::function<void(DBus::ValueOrError<ARGS...>)> func,
+ Monitor<BatchValueOrError<MONITORED_TYPE>> monitor)
+ {
+ return [monitor = std::move(monitor), func = std::move(func)](DBus::ValueOrError<ARGS...> val) -> void {
+ try
+ {
+ if (!val) {
+ auto h = monitor.lock();
+ h->setError(EvaluationFailure{} << val.getError().message);
+ } else {
+ func(std::move(val));
+ }
+ } catch (EvaluationFailure &ev)
+ {
+ auto h = monitor.lock();
+ h->setError(std::move(ev));
+ } catch (std::exception &ev)
+ {
+ auto h = monitor.lock();
+ h->setError(EvaluationFailure{} << "unhandled exception (" << ev.what() << ")");
+ } catch (...)
+ {
+ auto h = monitor.lock();
+ h->setError(EvaluationFailure{} << "unhandled unknown exception");
+ }
+ };
+ }
+
+ // (template magic) proxy function to discover arguments of the callable,
+ // called in wrap(...) function below. Typical usage is:
+ // wrap(monitor, [](A a) { ... })
+ // at implementation of function wrap lambda is turned into type F, for which
+ // we want to know return type and it's arguments (we already know, it's callable,
+ // as it's required). So (in function wrap) we take F's operator () - it will become
+ // pointer to member. Now we call WrapHelper<F's operator ()>::call.
+ // This class has two specializations, both expect pointer to member (our operator () ).
+ // Template deduction allows compiler to discover both return type (which we know must be void)
+ // and it's arguments (we know in this case, that it must be single DBus::ValueOrError<ARGS...>).
+ // But now the compiler can tells us, which ARGS... exactly are, which will allow wrapImpl to create
+ // functor with the required arguments and return type.
+
+ template <typename T> struct WrapHelper;
+ template <typename C, typename ... ARGS>
+ struct WrapHelper<void(C::*)(DBus::ValueOrError<ARGS...>) const> {
+ template <typename MONITORED_TYPE>
+ static std::function<void(DBus::ValueOrError<ARGS...>)> call(
+ std::function<void(DBus::ValueOrError<ARGS...>)> func,
+ Monitor<BatchValueOrError<MONITORED_TYPE>> monitor)
+ {
+ return wrapImpl(std::move(func), std::move(monitor));
+ }
+ };
+ template <typename C, typename ... ARGS>
+ struct WrapHelper<void(C::*)(DBus::ValueOrError<ARGS...>)> {
+ template <typename MONITORED_TYPE>
+ static std::function<void(DBus::ValueOrError<ARGS...>)> call(
+ std::function<void(DBus::ValueOrError<ARGS...>)> func,
+ Monitor<BatchValueOrError<MONITORED_TYPE>> monitor)
+ {
+ return wrapImpl(std::move(func), std::move(monitor));
+ }
+ };
+}
+
+template <typename MONITORED_TYPE, typename F>
+auto wrap(Monitor<BatchValueOrError<MONITORED_TYPE>> monitor, F &&func)
+{
+ return WrapDetails::WrapHelper<decltype(&std::remove_reference<F>::type::operator())>::
+ call(std::forward<F>(func), std::move(monitor));
+}
+
+class ExecutorOnMainThread
+{
+public:
+ template < typename F, typename = typename
+ std::enable_if < !std::is_same<typename std::result_of<F()>::type, void>::value >::type >
+ static auto execute(F && function)
+ {
+ typename std::remove_reference<decltype(function())>::type ret;
+ ExecutorOnMainThread tmp{ [&]()
+ {
+ ret = function();
+ } };
+ ecore_main_loop_thread_safe_call_sync(executeOnMainThreadCb, &tmp);
+ ASSERT(!eina_main_loop_is());
+ if (tmp.failure) throw *tmp.failure;
+ return ret;
+ }
+ template <typename F, typename = typename
+ std::enable_if<std::is_same<typename std::result_of<F()>::type, void>::value>::type>
+ static void execute(F && function)
+ {
+ ExecutorOnMainThread tmp{ [&]()
+ {
+ function();
+ } };
+ ecore_main_loop_thread_safe_call_sync(executeOnMainThreadCb, &tmp);
+ ASSERT(!eina_main_loop_is());
+ if (tmp.failure) throw *tmp.failure;
+ return;
+ }
+private:
+ ExecutorOnMainThread() = delete;
+ ExecutorOnMainThread(std::function<void()> function) : func(std::move(function)) { }
+ ExecutorOnMainThread(const ExecutorOnMainThread &) = delete;
+ ExecutorOnMainThread(ExecutorOnMainThread &&) = delete;
+
+ ExecutorOnMainThread &operator = (const ExecutorOnMainThread &) = delete;
+ ExecutorOnMainThread &operator = (ExecutorOnMainThread &&) = delete;
+
+ std::function<void()> func;
Optional<EvaluationFailure> failure;
+
+ static void *executeOnMainThreadCb(void *d)
+ {
+ auto helper = static_cast<ExecutorOnMainThread *>(d);
+ try {
+ helper->func();
+ } catch (EvaluationFailure &e) {
+ helper->failure = std::move(e);
+ } catch (std::exception &e) {
+ helper->failure = EvaluationFailure{} << "unhandled expection (" << e.what() << ")";
+ } catch (...) {
+ helper->failure = EvaluationFailure{} << "unhandled expection";
+ }
+ return nullptr;
+ }
};
-static void *executeOnMainThreadCb(void *d)
+
+template <typename T> auto executeOnMainThread(T &&fnc)
{
- auto helper = static_cast<ExecuteOnMainThreadHelper *>(d);
- try {
- helper->callback();
- } catch (EvaluationFailure &e) {
- helper->failure = std::move(e);
- } catch (std::exception &e) {
- helper->failure = EvaluationFailure{} << "unhandled expection (" << e.what() << ")";
- } catch (...) {
- helper->failure = EvaluationFailure{} << "unhandled expection";
- }
- return nullptr;
+ return ExecutorOnMainThread::execute(std::forward<T>(fnc));
}
-void executeOnMainThread(std::function<void()> fnc)
+template <typename T, typename MONITORED_TYPE> MONITORED_TYPE executeOnMainThread(T &&fnc,
+ Monitor<BatchValueOrError<MONITORED_TYPE>> monitor,
+ std::chrono::microseconds timeout = 3s)
{
- ExecuteOnMainThreadHelper tmp{ std::move(fnc) };
- ecore_main_loop_thread_safe_call_sync(executeOnMainThreadCb, &tmp);
- if (tmp.failure) throw *tmp.failure;
+ auto untilMoment = std::chrono::high_resolution_clock::now() + timeout;
+ ExecutorOnMainThread::execute(std::forward<T>(fnc));
+ auto h = monitor.lock();
+ bool success = h.waitForCondition(untilMoment, [&]() {
+ return bool(*h);
+ });
+ if (!success)
+ throw EvaluationFailure{} << "evaluation timeouted";
+ ASSERT(*h);
+ return h->takeValue();
}
namespace std
auto root = getVisibleRoot();
if (!root) throw EvaluationFailure{} << "no visible root (context changed didn't happen)";
ASSERT(root->getObject());
- std::vector<std::shared_ptr<UIElement>> result;
- executeOnMainThread([&]()
+ Monitor<BatchValueOrError<std::vector<std::shared_ptr<UIElement>>>> monitor;
+
+ return executeOnMainThread([&]()
{
- getAllObjects(root->getObject(), [&](DBus::ValueOrError<std::vector<AtspiAccessiblePtr>> allElements) {
- if (!allElements) throw EvaluationFailure{} << allElements.getError().message;
- findByName(std::get<0>(allElements), name, [&](DBus::ValueOrError<std::vector<AtspiAccessiblePtr>> elements) {
- if (!elements) throw EvaluationFailure{} << elements.getError().message;
+ getAllObjects(root->getObject(), wrap(monitor, [ = ](DBus::ValueOrError<std::vector<AtspiAccessiblePtr>> allElements) {
+ findByName(std::get<0>(allElements), name, wrap(monitor, [ = ](DBus::ValueOrError<std::vector<AtspiAccessiblePtr>> elements) {
auto &elems = std::get<0>(elements);
- makeUIElements(std::move(elems), [&result](DBus::ValueOrError<std::vector<std::shared_ptr<UIElement>>> uiElems) {
- if (!uiElems) throw EvaluationFailure{} << uiElems.getError().message;
- result = std::move(std::get<0>(uiElems));
- });
- });
- }, std::move(roles), std::move(states));
- });
- return result;
+ makeUIElements(std::move(elems), wrap(monitor, [ = ](DBus::ValueOrError<std::vector<std::shared_ptr<UIElement>>> uiElems) {
+ auto h = monitor.lock();
+ h->setValue(std::move(std::get<0>(uiElems)));
+ }));
+ }));
+ }), std::move(roles), std::move(states));
+ }, monitor);
}
};
getters[ { EvaluationValue::Kind::UIELEMENT, "name" }] = [&](const EvaluationValue & self) -> EvaluationValue {
auto uiElem = self.asUiElement();
ASSERT(uiElem->getObject());
- Optional<std::string> result;
- executeOnMainThread([&]()
+ return executeOnMainThread([&]()
{
auto root = getVisibleRoot();
auto atspi = Singleton<UniversalSwitch>::instance().getAtspi();
auto found = atspi->getName(uiElem->getObject());
if (found)
- result = std::move(*found);
+ return std::move(*found);
+ throw EvaluationFailure{} << "failed to get at-spi object's name (use dlogutil to get at-spi error message)";
});
- if (!result) throw EvaluationFailure{} << "failed to get at-spi object's name (use dlogutil to get at-spi error message)";
- return *result;
};
getters[ { EvaluationValue::Kind::UIELEMENT, "middle_point" }] = [&](const EvaluationValue & self) -> EvaluationValue {
auto uiElem = self.asUiElement();
ASSERT(uiElem->getObject());
- Optional<Rectangle> result;
- executeOnMainThread([&]()
+ return executeOnMainThread([&]()
{
auto atspi = Singleton<UniversalSwitch>::instance().getAtspi();
auto comp = atspi->getComponentInterface(uiElem->getObject());
if (comp) {
auto found = atspi->getScreenPosition(comp);
if (found)
- result = *found;
+ return std::move(*found).getCenterPoint();
}
+ throw EvaluationFailure{} << "failed to get at-spi object's screen position (use dlogutil to get at-spi error message)";
});
- if (!result) throw EvaluationFailure{} << "failed to get at-spi object's screen position (use dlogutil to get at-spi error message)";
- return (*result).getCenterPoint();
};
for (auto activityName : ActivityFactory::getInstance()->getAllActivityTypes()) {
variables[activityName] = EvaluationValue::FunctionType::Type{
[ = ](EvaluationContext & ec, std::vector<EvaluationValue> args) -> EvaluationValue {
- executeOnMainThread([&]()
+ auto activity = executeOnMainThread([&]()
{
- DEBUG("calling activity %s", activityName.c_str());
- auto activity = ActivityFactory::getInstance()->createActivity(activityName);
- if (!activity)
- throw EvaluationFailure{} << "failed to construct '" << activityName << "' activity";
- auto numOfArgs = activity->getRequiredNumberOfArgumentsIfAllowedInBatchProcessing();
- if (!numOfArgs)
- throw EvaluationFailure{} << "activity '" << activityName << "' is not supported";
- if (*numOfArgs != args.size())
- throw EvaluationFailure{} << "invalid number of arguments for activity '" << activityName <<
- "', got " << args.size() << ", expected " << *numOfArgs;
- auto activityObserver = std::dynamic_pointer_cast<Observer<UIElement>>(activity);
- for (size_t i = 0; i < args.size(); ++i) {
- ASSERT(activityObserver);
- auto uiElement = detail::ConvertTo<std::shared_ptr<UIElement>>::convert(ec, std::move(args[i]));
- if (!uiElement)
- throw EvaluationFailure{} << "can't convert argument " << (i + 1) <<
- " of kind " << EvaluationValue::toString(args[i].kind()) << " to UIElement";
- activityObserver->update(std::move(uiElement));
- }
- activity->process({});
- if (!activity->isCompleted())
- throw EvaluationFailure{} << "not enough arguments for activity '" << activityName << "'";
- DEBUG("calling activity %s done", activityName.c_str());
+ return ActivityFactory::getInstance()->createActivity(activityName);
});
+ if (!activity)
+ throw EvaluationFailure{} << "failed to construct '" << activityName << "' activity";
+ auto numOfArgs = activity->getRequiredNumberOfArgumentsIfAllowedInBatchProcessing();
+ if (!numOfArgs)
+ throw EvaluationFailure{} << "activity '" << activityName << "' is not supported";
+ if (*numOfArgs != args.size())
+ throw EvaluationFailure{} << "invalid number of arguments for activity '" << activityName <<
+ "', got " << args.size() << ", expected " << *numOfArgs;
+ auto uiActivity = std::dynamic_pointer_cast<UIActivity>(activity);
+ if (!args.empty() && !uiActivity)
+ throw EvaluationFailure{} << "activity '" << activityName << "' doesn't inherit from Observer<UIElement>, "
+ "which it must, becuase it expects arguments";
+ std::vector<std::shared_ptr<UIElement>> uiArgs(args.size());
+ for (size_t i = 0; i < args.size(); ++i)
+ {
+ ASSERT(uiActivity);
+ uiArgs[i] = detail::ConvertTo<std::shared_ptr<UIElement>>::convert(ec, std::move(args[i]));
+ if (!uiArgs[i])
+ throw EvaluationFailure{} << "can't convert argument " << (i + 1) <<
+ " of kind " << EvaluationValue::toString(args[i].kind()) << " to UIElement";
+ }
+ Monitor<BatchValueOrError<bool>> monitor;
+ {
+ executeOnMainThread([&]()
+ {
+ DEBUG("calling activity %s", activityName.c_str());
+ for (auto &arg : uiArgs) {
+ ASSERT(uiActivity);
+ uiActivity->update(std::move(arg));
+ }
+ activity->process(DoneCallback{ [ = ] {
+ auto h = monitor.lock();
+ h->setValue(true);
+ } });
+ DEBUG("calling activity %s done", activityName.c_str());
+ }, monitor);
+ }
return {};
} };
}
}
std::shared_ptr<UIElement> convert(Point pt) override
{
- std::shared_ptr<UIElement> result;
- executeOnMainThread([&]() {
+ return executeOnMainThread([&]() {
auto root = getVisibleRoot();
auto atspi = Singleton<UniversalSwitch>::instance().getAtspi();
auto found = atspi->getAtPoint(pt, Atspi::CoordType::Screen, root->getObject());
- result = std::make_shared<UIElement>(std::move(found), pt, root->getApplicationCategory());
+ return std::make_shared<UIElement>(std::move(found), pt, root->getApplicationCategory());
});
- return std::move(result);
}
void findByName(const std::vector<AtspiAccessiblePtr> &elems, std::string requestedName, std::function<void(DBus::ValueOrError<std::vector<AtspiAccessiblePtr>>)> callback)
{
if (!col) {
callback(DBus::Error{ "root '" + Atspi::getUniqueId(root) + "' doesn't have collection interface" });
} else {
+ auto m = Atspi::Matcher();
+ if (!states.empty()) m.states(states.begin(), states.end(), ATSPI_Collection_MATCH_ALL);
+ if (!roles.empty()) m.roles(roles.begin(), roles.end(), ATSPI_Collection_MATCH_ANY);
atspi->getMatchedElements(col, ATSPI_Collection_SORT_ORDER_CANONICAL, 0,
- Atspi::Matcher().states(states.begin(), states.end(), ATSPI_Collection_MATCH_ALL)
- .roles(roles.begin(), roles.end(), ATSPI_Collection_MATCH_ANY)
- , false, std::move(callback));
+ m, false, std::move(callback));
}
}
void makeUIElement(AtspiAccessiblePtr src, std::function<void(DBus::ValueOrError<std::shared_ptr<UIElement>>)> callback)
}
}
- struct PromiseObject {
- std::promise<std::shared_ptr<UIElement>> promise;
- bool set = false;
-
- void setValue(std::shared_ptr<UIElement> v)
- {
- ASSERT(!set);
- promise.set_value(std::move(v));
- set = true;
- }
- template <typename T> void setException(T &&e)
- {
- ASSERT(!set);
- promise.set_exception(std::make_exception_ptr(std::move(e)));
- set = true;
- }
-
- ~PromiseObject()
- {
- if (!set) promise.set_value(nullptr);
- }
- };
std::shared_ptr<UIElement> convert(const std::string &requestedName) override
{
- auto promiseObject = std::make_shared<PromiseObject>();
- auto future = promiseObject->promise.get_future();
+ Monitor<BatchValueOrError<std::shared_ptr<UIElement>>> monitor;
- executeOnMainThread([promiseObject = std::move(promiseObject), this, &requestedName]() {
+ return executeOnMainThread([&]() {
auto uiRoot = getVisibleRoot();
- if (!uiRoot) {
- promiseObject->setException(EvaluationFailure{} << "no visible root (context changed didn't happen)");
- return;
- }
+ if (!uiRoot) throw EvaluationFailure{} << "no visible root (context change didn't happen)";
auto root = uiRoot->getObject();
ASSERT(root);
- getAllObjects(root, [promiseObject, &requestedName, this](DBus::ValueOrError<std::vector<AtspiAccessiblePtr>> elems) {
- if (!promiseObject->set) {
- if (!elems) {
- promiseObject->setException(EvaluationFailure{} << elems.getError().message);
- return;
+ getAllObjects(root, wrap(monitor, [ = ](DBus::ValueOrError<std::vector<AtspiAccessiblePtr>> elems) mutable {
+ findByName(std::get<0>(elems), requestedName,
+ wrap(monitor, [ = ](DBus::ValueOrError<std::vector<AtspiAccessiblePtr>> elements)
+ {
+ auto &e = std::get<0>(elements);
+ if (e.empty()) {
+ throw EvaluationFailure{} << "no at-spi object found with name '" << requestedName << "'";
}
- findByName(std::get<0>(elems), requestedName,
- [&requestedName, promiseObject, this](DBus::ValueOrError<std::vector<AtspiAccessiblePtr>> elements) {
- if (!elements) {
- promiseObject->setException(EvaluationFailure{} << elements.getError().message);
- } else {
- auto &e = std::get<0>(elements);
- if (e.empty()) {
- promiseObject->setException(EvaluationFailure{} << "no at-spi object found with name '" << requestedName << "'");
- } else if (e.size() > 1) {
- std::ostringstream names;
- for (auto &elem : e) names << " " << Atspi::getUniqueId(elem);
- promiseObject->setException(EvaluationFailure{} <<
- "found more, than one at-spi object with name '" << requestedName << "' (see" << names.str() << ")");
- } else {
- makeUIElement(e[0], [promiseObject](DBus::ValueOrError<std::shared_ptr<UIElement>> elem) {
- if (!elem)
- promiseObject->setException(EvaluationFailure{} << elem.getError().message);
- else
- promiseObject->setValue(std::move(std::get<0>(elem)));
- });
- }
- }
- });
- }
- });
- });
- future.wait();
- return std::move(future.get());
+ if (e.size() > 1) {
+ std::ostringstream names;
+ for (auto &elem : e) names << " " << Atspi::getUniqueId(elem);
+ throw EvaluationFailure{} << "found more, than one at-spi object with name '" <<
+ requestedName << "' (see" << names.str() << ")";
+ }
+ makeUIElement(e[0], wrap(monitor, [ = ](DBus::ValueOrError<std::shared_ptr<UIElement>> elem) {
+ auto h = monitor.lock();
+ h->setValue(std::move(std::get<0>(elem)));
+ }));
+ }));
+ }));
+ }, monitor);
}
void insertStateConstants()
}
};
-bool runTestCaseImpl(std::string sourceName, std::string outputName)
+bool runBatch(const std::string &sourceName, const std::string &outputName)
{
std::unique_ptr<std::ostream> outputPtr;
outputPtr = std::make_unique<std::ofstream>(outputName.empty() ? "/dev/null" : outputName, std::ios_base::out);
std::thread { [result, exec = std::move(exec), outputPtr = std::move(outputPtr)]()
{
EvaluationContext ec { *exec };
- exec->outputStream() << "waiting for context change...";
+ exec->outputStream() << "waiting for context change...\n";
auto until = std::chrono::high_resolution_clock::now() + std::chrono::seconds{ 2 };
while (true) {
if (std::chrono::high_resolution_clock::now() >= until) {
}
std::this_thread::sleep_for(std::chrono::milliseconds{ 10 });
}
+ outputPtr->flush();
executeOnMainThread([]() {
ecore_main_loop_quit();
} } .detach();
return true;
}
-
-void runBatch(const std::string &sourceName, const std::string &outputName)
-{
- auto result = runTestCaseImpl(sourceName, outputName);
- if (!result) {
- Singleton<UniversalSwitch>::instance().terminate();
- std::exit(1);
- }
-}
--- /dev/null
+#ifndef MONITOR_HPP
+#define MONITOR_HPP
+
+#include <mutex>
+#include <condition_variable>
+#include <chrono>
+#include "../Optional.hpp"
+
+/**
+ * @brief Monitor class
+ *
+ * Allows passing a template parameter T. Monitor object will store one instance of type T
+ * that will be accessible only to thread, that has acquired monitor (via detail::LockedData object).
+ *
+ * Monitor object allows thread synchronization. It offers three functionalities:
+ * 1. user can lock the monitor and access it's stored value. Only single thread might lock the monitor
+ * simultaneously, others will wait. Order of acquiring is system dependant (random).
+ * 2. user, that have locked the monitor successfully, might decide to relinquish the lock and wait
+ * for notification. In that case lock is released and thread is put to sleep.
+ * 3. user might notify one (or all) waiting threads from point 2. Order of waking is implementation
+ * defined, as is which thread will be selected to be notified, when notifying only one.
+ * Notified thread will be awaken and every such thread will try to reacquire the lock.
+ * You can read about monitors here https://en.wikipedia.org/wiki/Monitor_(synchronization) .
+ * Note, that this implementation is shared - Monitor and it's LockedMonitor might exists independetly of each other.
+ * LockedMonitor object is fully functional even after Monitor itself was destroyed.
+ */
+template <typename T> class Monitor
+{
+private:
+ /// \cond
+ struct Data {
+ std::mutex mt;
+ std::condition_variable cv;
+ T data;
+
+ Data() = default;
+ Data(T data) : data(std::move(data)) { }
+ };
+ std::shared_ptr<Data> ptr;
+ /// \endcond
+public:
+ Monitor() : ptr(std::make_shared<Data>()) {}
+ Monitor(T init) : ptr(std::make_shared<Data>(std::move(init))) { }
+
+ /**
+ * @brief Class representing lock over the monitor
+ *
+ * LockedMonitor object will (if still locked) unlock and notify all threads on
+ * destruction. LockedMonitor object might outlive Monitor itself, all data is
+ * shared using shared_ptr pointers.
+ */
+ class LockedMonitor
+ {
+ public:
+ LockedMonitor() = delete;
+ LockedMonitor(const Monitor &m) : lk(std::unique_lock<std::mutex>(m.ptr->mt)), ptr(m.ptr) { }
+
+ /**
+ * @brief Destructor - unlocks monitor
+ */
+ virtual ~LockedMonitor()
+ {
+ ASSERT(lk);
+ ptr->cv.notify_all();
+ }
+
+ /**
+ * @brief Move constructor
+ *
+ * You can safely move LockedMonitor object's around - source will be left "empty" (unlocked),
+ * destination will retain source state prior to the call.
+ */
+ LockedMonitor(LockedMonitor &&) = default;
+ LockedMonitor(const LockedMonitor &) = delete;
+
+ /**
+ * @brief Move assignment
+ *
+ * You can safely move LockedMonitor object's around - source will be left "empty" (unlocked),
+ * destination will retain source state prior to the call.
+ */
+ LockedMonitor &operator = (LockedMonitor &&) = default;
+ LockedMonitor &operator = (const LockedMonitor &) = delete;
+
+ /**
+ * @brief Releases mutex and waits for condition
+ *
+ * Releases the mutex, then wait for:
+ * - condition (see std::condition_variable for more details).
+ * - the mutex
+ * Condition itself will call predicate functor. If it returns true, the wait is considered
+ * to be successful and waitForCondition will return (with monitor still locked).
+ * During functor call the monitor itself is locked and functor might access the monitor's data safely.
+ */
+ void waitForCondition(std::function<bool()> predicate)
+ {
+ ASSERT(lk && ptr);
+ ptr->cv.wait(lk, std::move(predicate));
+ }
+
+ /**
+ * @brief Releases mutex and waits for condition to be notified, reacquire mutex after, but only for a given time
+ *
+ * See waitForCondition(std::function<bool()>) for a longer description. This version returns true,
+ * if completed successfully in requested time. The monitor is locked after
+ * waitForCondition returns.
+ */
+ bool waitForCondition(std::chrono::microseconds time, std::function<bool()> predicate)
+ {
+ ASSERT(lk && ptr);
+ return ptr->cv.wait_for(lk, time, std::move(predicate));
+ }
+
+ /**
+ * @brief Releases mutex and waits for condition to be notified, reacquire mutex after, but only until given moment of time
+ *
+ * See waitForCondition(std::function<bool()>) for a longer description. This version returns true,
+ * if completed successfully in requested time. The monitor is locked after
+ * waitForCondition returns.
+ */
+ bool waitForCondition(std::chrono::high_resolution_clock::time_point timePoint, std::function<bool()> predicate)
+ {
+ ASSERT(lk && ptr);
+ return ptr->cv.wait_until(lk, timePoint, std::move(predicate));
+ }
+
+ /**
+ * @brief operator * for access to data
+ *
+ * You should only access the data from the same thread, that created LockedMonitor (this will also ensure,
+ * that LockedMonitor is locked).
+ */
+ T &operator * ()
+ {
+ ASSERT(lk);
+ return ptr->data;
+ }
+ /**
+ * @brief operator * for access to data
+ *
+ * You should only access the data from the same thread, that created LockedMonitor (this will also ensure,
+ * that LockedMonitor is locked).
+ */
+ const T &operator * () const
+ {
+ ASSERT(lk);
+ return ptr->data;
+ }
+ /**
+ * @brief operator -> for access to data
+ *
+ * You should only access the data from the same thread, that created LockedMonitor (this will also ensure,
+ * that LockedMonitor is locked).
+ */
+ T *operator -> ()
+ {
+ ASSERT(lk);
+ return &ptr->data;
+ }
+ /**
+ * @brief operator -> for access to data
+ *
+ * You should only access the data from the same thread, that created LockedMonitor (this will also ensure,
+ * that LockedMonitor is locked).
+ */
+ const T *operator -> () const
+ {
+ ASSERT(lk);
+ return &ptr->data;
+ }
+ private:
+ /// \cond
+ std::unique_lock<std::mutex> lk;
+ std::shared_ptr<Data> ptr;
+ /// \endcond
+ };
+
+ /**
+ * @brief LockedMonitor the monitor
+ *
+ * Locks the monitor, returns object reperesenting lock for the monitor and allowing accessing monitor's data.
+ * Returned object allows also to do notify-release-wait synchronization.
+ */
+ LockedMonitor lock() const
+ {
+ return LockedMonitor{ *this };
+ }
+ /**
+ * @brief Notifies (wakes) all threads waiting for condition variable
+ */
+ void notifyAll()
+ {
+ ptr->cv.notify_all();
+ }
+
+ /**
+ * @brief Notifies (wakes) at most one thread waiting for condition variable
+ *
+ * Thread is picked randomly from threads waiting on condition variable.
+ */
+ void notifyOne()
+ {
+ ptr->cv.notify_one();
+ }
+};
+
+#endif
\ No newline at end of file