Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 31 additions & 3 deletions src/rppqt/rppqt/schedulers/main_thread.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
#include <rppqt/schedulers/fwd.hpp> // own forwarding
#include <rppqt/utils/exceptions.hpp>

#include "rpp/schedulers/fwd.hpp"

#include <QCoreApplication>
#include <QTimer>
#include <chrono>
Expand All @@ -37,17 +39,43 @@ namespace rppqt::schedulers
{
const auto application = QCoreApplication::instance();
if (!application)
throw utils::no_active_qapplication{"Pointer to application is null. Create QApplication before using main_thread_scheduler!"};
{
handler.on_error(std::make_exception_ptr(utils::no_active_qapplication{"Pointer to application is null. Create QApplication before using main_thread_scheduler!"}));
return;
}

QTimer::singleShot(std::chrono::duration_cast<std::chrono::milliseconds>(duration), application, [fn = std::forward<Fn>(fn), handler = std::forward<Handler>(handler), ... args = std::forward<Args>(args)]() mutable {
if (const auto new_duration = fn(handler, args...))
defer_for(new_duration->value, std::move(fn), std::move(handler), std::move(args)...);
if (!handler.is_disposed())
invoke(std::move(fn), std::move(handler), std::move(args)...);
});
}

static constexpr rpp::schedulers::details::none_disposable get_disposable() { return {}; }

static rpp::schedulers::time_point now() { return rpp::schedulers::clock_type::now(); }

private:
template<rpp::schedulers::constraint::schedulable_handler Handler, typename... Args, rpp::schedulers::constraint::schedulable_delay_from_now_fn<Handler, Args...> Fn>
static void invoke(Fn&& fn, Handler&& handler, Args&&... args)
{
if (const auto new_duration = fn(handler, args...))
defer_for(new_duration->value, std::forward<Fn>(fn), std::forward<Handler>(handler), std::forward<Args>(args)...);
}

template<rpp::schedulers::constraint::schedulable_handler Handler, typename... Args, rpp::schedulers::constraint::schedulable_delay_from_this_timepoint_fn<Handler, Args...> Fn>
static void invoke(Fn&& fn, Handler&& handler, Args&&... args)
{
const auto now = rpp::schedulers::clock_type::now();
if (const auto new_duration = fn(handler, args...))
defer_for(now + new_duration->value - rpp::schedulers::clock_type::now(), std::forward<Fn>(fn), std::forward<Handler>(handler), std::forward<Args>(args)...);
}

template<rpp::schedulers::constraint::schedulable_handler Handler, typename... Args, rpp::schedulers::constraint::schedulable_delay_to_fn<Handler, Args...> Fn>
static void invoke(Fn&& fn, Handler&& handler, Args&&... args)
{
if (const auto new_tp = fn(handler, args...))
defer_for(new_tp->value - rpp::schedulers::clock_type::now(), std::forward<Fn>(fn), std::forward<Handler>(handler), std::forward<Args>(args)...);
}
};

public:
Expand Down
31 changes: 30 additions & 1 deletion src/tests/rppqt/test_main_thread_scheduler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@
#include <rppqt/schedulers/main_thread.hpp>

#include "mock_observer.hpp"
#include "rpp/disposables/fwd.hpp"
#include "rpp/schedulers/fwd.hpp"

#include <QApplication>
#include <future>
#include <optional>

TEST_CASE("main_thread_scheduler schedules actions to main thread")
{
auto observer = mock_observer_strategy<int>{}.get_observer().as_dynamic();
auto d = rpp::composite_disposable_wrapper::make();
auto observer = mock_observer_strategy<int>{}.get_observer(d).as_dynamic();

int argc{};
QCoreApplication application{argc, nullptr};
Expand All @@ -47,6 +51,24 @@ TEST_CASE("main_thread_scheduler schedules actions to main thread")
}
}

SECTION("nothing happens for disposed handler")
{
std::promise<std::thread::id> execution_thread{};
std::thread{[&] {
rppqt::schedulers::main_thread_scheduler::create_worker().schedule([&](const auto&) -> rpp::schedulers::optional_delay_from_now {
execution_thread.set_value(std::this_thread::get_id());
return {};
},
observer);
}}.join();

d.dispose();

application.exec();
auto future = execution_thread.get_future();
REQUIRE(future.wait_for(std::chrono::seconds{1}) == std::future_status::timeout);
}

SECTION("recursive scheduling to main thread")
{
std::string execution{};
Expand All @@ -68,4 +90,11 @@ TEST_CASE("main_thread_scheduler schedules actions to main thread")
application.exec();
CHECK(execution == "outer inner outer inner ");
}

SECTION("scheduler can be applied for all types of schedulables")
{
rppqt::schedulers::main_thread_scheduler::create_worker().schedule([&](const auto&) -> rpp::schedulers::optional_delay_from_now { return std::nullopt; }, observer);
rppqt::schedulers::main_thread_scheduler::create_worker().schedule([&](const auto&) -> rpp::schedulers::optional_delay_from_this_timepoint { return std::nullopt; }, observer);
rppqt::schedulers::main_thread_scheduler::create_worker().schedule([&](const auto&) -> rpp::schedulers::optional_delay_to { return std::nullopt; }, observer);
}
}