From a396e6af3cd16530202f6cbecbd45c7a3f6ac893 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Thu, 24 Apr 2014 22:34:42 -0500 Subject: hw alarms --- CMakeLists.txt | 1 + include/datetime/alarm-queue-simple.h | 63 ++++++++++++ include/datetime/alarm-queue.h | 28 ++---- include/datetime/date-time.h | 3 + include/datetime/planner-range.h | 2 +- include/datetime/wakeup-timer-mainloop.h | 62 ++++++++++++ include/datetime/wakeup-timer-uha.h | 64 ++++++++++++ include/datetime/wakeup-timer.h | 55 ++++++++++ src/CMakeLists.txt | 6 +- src/alarm-queue-simple.cpp | 153 ++++++++++++++++++++++++++++ src/alarm-queue.cpp | 81 --------------- src/date-time.cpp | 5 + src/main.cpp | 110 +++++++++++++++----- src/planner-range.cpp | 2 +- src/wakeup-timer-mainloop.cpp | 135 +++++++++++++++++++++++++ src/wakeup-timer-uha.cpp | 168 +++++++++++++++++++++++++++++++ tests/test-alarm-queue.cpp | 18 ++-- 17 files changed, 815 insertions(+), 141 deletions(-) create mode 100644 include/datetime/alarm-queue-simple.h create mode 100644 include/datetime/wakeup-timer-mainloop.h create mode 100644 include/datetime/wakeup-timer-uha.h create mode 100644 include/datetime/wakeup-timer.h create mode 100644 src/alarm-queue-simple.cpp delete mode 100644 src/alarm-queue.cpp create mode 100644 src/wakeup-timer-mainloop.cpp create mode 100644 src/wakeup-timer-uha.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 378dc03..9e068f4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,6 +42,7 @@ pkg_check_modules (SERVICE_DEPS REQUIRED url-dispatcher-1>=1 properties-cpp>=0.0.1) include_directories (SYSTEM ${SERVICE_DEPS_INCLUDE_DIRS}) +set (SERVICE_DEPS_LIBRARIES -lubuntu_platform_hardware_api ${SERVICE_DEPS_LIBRARIES}) ## ## custom targets diff --git a/include/datetime/alarm-queue-simple.h b/include/datetime/alarm-queue-simple.h new file mode 100644 index 0000000..8461227 --- /dev/null +++ b/include/datetime/alarm-queue-simple.h @@ -0,0 +1,63 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * Authors: + * Charles Kerr + */ + +#ifndef INDICATOR_DATETIME_ALARM_QUEUE_SIMPLE_H +#define INDICATOR_DATETIME_ALARM_QUEUE_SIMPLE_H + +#include +#include +#include +#include + +namespace unity { +namespace indicator { +namespace datetime { + +/** + * \brief A #AlarmQueue implementation + */ +class SimpleAlarmQueue: public AlarmQueue +{ +public: + SimpleAlarmQueue(const std::shared_ptr& clock, + const std::shared_ptr& upcoming_planner, + const std::shared_ptr& timer); + ~SimpleAlarmQueue(); + core::Signal& alarm_reached(); + +private: + void requeue(); + bool find_next_alarm(Appointment& setme) const; + std::vector find_current_alarms() const; + void check_alarms(); + + std::set m_triggered; + const std::shared_ptr m_clock; + const std::shared_ptr m_planner; + const std::shared_ptr m_timer; + core::Signal m_alarm_reached; + DateTime m_time; +}; + + +} // namespace datetime +} // namespace indicator +} // namespace unity + +#endif // INDICATOR_DATETIME_ALARM_QUEUE_H diff --git a/include/datetime/alarm-queue.h b/include/datetime/alarm-queue.h index 5db4ad8..ea51957 100644 --- a/include/datetime/alarm-queue.h +++ b/include/datetime/alarm-queue.h @@ -21,8 +21,6 @@ #define INDICATOR_DATETIME_ALARM_QUEUE_H #include -#include -#include #include @@ -34,6 +32,9 @@ namespace unity { namespace indicator { namespace datetime { +/*** +**** +***/ /** * \brief Watches the clock and appointments to notify when an @@ -47,26 +48,9 @@ public: virtual core::Signal& alarm_reached() = 0; }; - -/** - * \brief A #AlarmQueue implementation - */ -class AlarmQueueImpl: public AlarmQueue -{ -public: - AlarmQueueImpl(const std::shared_ptr& clock, - const std::shared_ptr& upcoming_planner); - ~AlarmQueueImpl() =default; - core::Signal& alarm_reached(); - -private: - void pulse(); - std::set m_triggered; - const std::shared_ptr m_clock; - const std::shared_ptr m_upcoming_planner; - core::Signal m_alarm_reached; -}; - +/*** +**** +***/ } // namespace datetime } // namespace indicator diff --git a/include/datetime/date-time.h b/include/datetime/date-time.h index f861c2e..490ed40 100644 --- a/include/datetime/date-time.h +++ b/include/datetime/date-time.h @@ -61,10 +61,13 @@ public: bool operator<=(const DateTime& that) const; bool operator!=(const DateTime& that) const; bool operator==(const DateTime& that) const; + int64_t operator- (const DateTime& that) const; static bool is_same_day(const DateTime& a, const DateTime& b); static bool is_same_minute(const DateTime& a, const DateTime& b); + bool is_set() const { return m_dt.get() != nullptr; } + private: std::shared_ptr m_dt; }; diff --git a/include/datetime/planner-range.h b/include/datetime/planner-range.h index 25334a6..2ee2fa0 100644 --- a/include/datetime/planner-range.h +++ b/include/datetime/planner-range.h @@ -53,7 +53,7 @@ class SimpleRangePlanner: public RangePlanner { public: SimpleRangePlanner(const std::shared_ptr& engine, - const std::shared_ptr& timezone); + const std::shared_ptr& timezone); virtual ~SimpleRangePlanner(); core::Property>& appointments(); diff --git a/include/datetime/wakeup-timer-mainloop.h b/include/datetime/wakeup-timer-mainloop.h new file mode 100644 index 0000000..480d728 --- /dev/null +++ b/include/datetime/wakeup-timer-mainloop.h @@ -0,0 +1,62 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * Authors: + * Charles Kerr + */ + +#ifndef INDICATOR_DATETIME_WAKEUP_TIMER_MAINLOOP_H +#define INDICATOR_DATETIME_WAKEUP_TIMER_MAINLOOP_H + +#include +#include + +#include // std::unique_ptr, std::shared_ptr + +namespace unity { +namespace indicator { +namespace datetime { + +/*** +**** +***/ + +/** + * \brief a WakeupTimer implemented with g_timeout_add() + */ +class MainloopWakeupTimer: public WakeupTimer +{ +public: + MainloopWakeupTimer(const std::shared_ptr&); + ~MainloopWakeupTimer(); + void set_wakeup_time (const DateTime&); + core::Signal<>& timeout(); + +private: + MainloopWakeupTimer(const MainloopWakeupTimer&) =delete; + MainloopWakeupTimer& operator= (const MainloopWakeupTimer&) =delete; + class Impl; + std::unique_ptr p; +}; + +/*** +**** +***/ + +} // namespace datetime +} // namespace indicator +} // namespace unity + +#endif // INDICATOR_DATETIME_WAKEUP_TIMER_MAINLOOP_H diff --git a/include/datetime/wakeup-timer-uha.h b/include/datetime/wakeup-timer-uha.h new file mode 100644 index 0000000..093548b --- /dev/null +++ b/include/datetime/wakeup-timer-uha.h @@ -0,0 +1,64 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * Authors: + * Charles Kerr + */ + +#ifndef INDICATOR_DATETIME_WAKEUP_TIMER_UHA_H +#define INDICATOR_DATETIME_WAKEUP_TIMER_UHA_H + +#include +#include + +#include // std::unique_ptr, std::shared_ptr + +namespace unity { +namespace indicator { +namespace datetime { + +/*** +**** +***/ + +/** + * \brief a WakeupTimer implemented the UbuntuHardwareAlarm API + */ +class UhaWakeupTimer: public WakeupTimer +{ +public: + UhaWakeupTimer(const std::shared_ptr&); + ~UhaWakeupTimer(); + void set_wakeup_time (const DateTime&); + core::Signal<>& timeout(); + + static bool is_supported(); + +private: + UhaWakeupTimer(const UhaWakeupTimer&) =delete; + UhaWakeupTimer& operator= (const UhaWakeupTimer&) =delete; + class Impl; + std::unique_ptr p; +}; + +/*** +**** +***/ + +} // namespace datetime +} // namespace indicator +} // namespace unity + +#endif // INDICATOR_DATETIME_WAKEUP_TIMER_UHA_H diff --git a/include/datetime/wakeup-timer.h b/include/datetime/wakeup-timer.h new file mode 100644 index 0000000..b84d347 --- /dev/null +++ b/include/datetime/wakeup-timer.h @@ -0,0 +1,55 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * Authors: + * Charles Kerr + */ + +#ifndef INDICATOR_DATETIME_WAKEUP_TIMER_H +#define INDICATOR_DATETIME_WAKEUP_TIMER_H + +#include + +#include + +namespace unity { +namespace indicator { +namespace datetime { + +/*** +**** +***/ + +/** + * \brief A one-shot timer that emits a signal when the timeout is reached + */ +class WakeupTimer +{ +public: + WakeupTimer() =default; + virtual ~WakeupTimer() =default; + virtual void set_wakeup_time (const DateTime&) =0; + virtual core::Signal<>& timeout() = 0; +}; + +/*** +**** +***/ + +} // namespace datetime +} // namespace indicator +} // namespace unity + +#endif // INDICATOR_DATETIME_WAKEUP_TIMER_H diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6b65ebc..924e538 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -10,7 +10,7 @@ add_definitions (-DTIMEZONE_FILE="/etc/timezone" add_library (${SERVICE_LIB} STATIC actions.cpp actions-live.cpp - alarm-queue.cpp + alarm-queue-simple.cpp appointment.cpp clock.cpp clock-live.cpp @@ -30,7 +30,9 @@ add_library (${SERVICE_LIB} STATIC timezone-file.cpp timezone-geoclue.cpp timezones-live.cpp - utils.c) + utils.c + wakeup-timer-uha.cpp + wakeup-timer-mainloop.cpp) include_directories (${CMAKE_SOURCE_DIR}) link_directories (${SERVICE_DEPS_LIBRARY_DIRS}) diff --git a/src/alarm-queue-simple.cpp b/src/alarm-queue-simple.cpp new file mode 100644 index 0000000..2fd289c --- /dev/null +++ b/src/alarm-queue-simple.cpp @@ -0,0 +1,153 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * Authors: + * Charles Kerr + */ + +#include + +#include + +namespace unity { +namespace indicator { +namespace datetime { + +/*** +**** Public API +***/ + +SimpleAlarmQueue::SimpleAlarmQueue(const std::shared_ptr& clock, + const std::shared_ptr& planner, + const std::shared_ptr& timer): + m_clock(clock), + m_planner(planner), + m_timer(timer), + m_time(clock->localtime()) +{ + m_planner->appointments().changed().connect([this](const std::vector&){ + g_debug("AlarmQueue %p calling requeue() due to appointments changed", this); + requeue(); + }); + + m_clock->minute_changed.connect([=]{ + const auto now = m_clock->localtime(); + constexpr auto skew_threshold_usec = int64_t{90} * G_USEC_PER_SEC; + const bool clock_jumped = std::abs(now - m_time) > skew_threshold_usec; + m_time = now; + if (clock_jumped) { + g_message("AlarmQueue %p calling requeue() due to clock skew", this); + requeue(); + } + }); + + m_timer->timeout().connect([this](){ + g_message("AlarmQueue %p calling requeue() due to timeout", this); + requeue(); + }); + + requeue(); +} + +SimpleAlarmQueue::~SimpleAlarmQueue() +{ +} + +core::Signal& SimpleAlarmQueue::alarm_reached() +{ + return m_alarm_reached; +} + +/*** +**** +***/ + +void SimpleAlarmQueue::requeue() +{ + // kick any current alarms + for (auto current : find_current_alarms()) + { + m_triggered.insert(current.uid); + m_alarm_reached(current); + } + + // idle until the next alarm + Appointment next; + if (find_next_alarm(next)) + { + g_debug ("setting timer to wake up for next appointment '%s' at %s", + next.summary.c_str(), + next.begin.format("%F %T").c_str()); + + m_timer->set_wakeup_time(next.begin); + } +} + +// find the next alarm that will kick now or in the future +bool SimpleAlarmQueue::find_next_alarm(Appointment& setme) const +{ + bool found = false; + Appointment tmp; + const auto now = m_clock->localtime(); + const auto beginning_of_minute = now.add_full (0, 0, 0, 0, 0, -now.seconds()); + + for(const auto& walk : m_planner->appointments().get()) + { + if (m_triggered.count(walk.uid)) // did we already use this one? + continue; + + if (walk.begin < beginning_of_minute) // has this one already passed? + continue; + + if (found && (tmp.begin < walk.begin)) // do we already have a better match? + continue; + + tmp = walk; + found = true; + } + + if (found) + setme = tmp; + + return found; +} + +// find the alarm(s) that should kick right now +std::vector SimpleAlarmQueue::find_current_alarms() const +{ + std::vector appointments; + + const auto now = m_clock->localtime(); + + for(const auto& walk : m_planner->appointments().get()) + { + if (m_triggered.count(walk.uid)) // did we already use this one? + continue; + if (!DateTime::is_same_minute(now, walk.begin)) + continue; + + appointments.push_back(walk); + } + + return appointments; +} + +/*** +**** +***/ + +} // namespace datetime +} // namespace indicator +} // namespace unity diff --git a/src/alarm-queue.cpp b/src/alarm-queue.cpp deleted file mode 100644 index 9e9da7f..0000000 --- a/src/alarm-queue.cpp +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2014 Canonical Ltd. - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 3, as published - * by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranties of - * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - * - * Authors: - * Charles Kerr - */ - -#include - -namespace unity { -namespace indicator { -namespace datetime { - -/*** -**** -***/ - -AlarmQueueImpl::AlarmQueueImpl(const std::shared_ptr& clock, - const std::shared_ptr& upcoming_planner): - m_clock(clock), - m_upcoming_planner(upcoming_planner) -{ - m_clock->date_changed.connect([this](){ - const auto now = m_clock->localtime(); - g_debug("AlarmQueue %p refretching appointments due to date change: %s", this, now.format("%F %T").c_str()); - m_upcoming_planner->date().set(now); - }); - - m_clock->minute_changed.connect([this](){ - g_debug("AlarmQueue %p calling pulse() due to clock minute_changed", this); - pulse(); - }); - - m_upcoming_planner->appointments().changed().connect([this](const std::vector&){ - g_debug("AlarmQueue %p calling pulse() due to appointments changed", this); - pulse(); - }); - - pulse(); -} - -core::Signal& AlarmQueueImpl::alarm_reached() -{ - return m_alarm_reached; -} - -void AlarmQueueImpl::pulse() -{ - const auto now = m_clock->localtime(); - - for(const auto& appointment : m_upcoming_planner->appointments().get()) - { - if (m_triggered.count(appointment.uid)) - continue; - if (!DateTime::is_same_minute(now, appointment.begin)) - continue; - - m_triggered.insert(appointment.uid); - m_alarm_reached(appointment); - } -} - -/*** -**** -***/ - -} // namespace datetime -} // namespace indicator -} // namespace unity diff --git a/src/date-time.cpp b/src/date-time.cpp index a1c1d1b..a139ea9 100644 --- a/src/date-time.cpp +++ b/src/date-time.cpp @@ -174,6 +174,11 @@ bool DateTime::operator==(const DateTime& that) const return g_date_time_compare(get(), that.get()) == 0; } +int64_t DateTime::operator- (const DateTime& that) const +{ + return g_date_time_difference(get(), that.get()); +} + bool DateTime::is_same_day(const DateTime& a, const DateTime& b) { // it's meaningless to compare uninitialized dates diff --git a/src/main.cpp b/src/main.cpp index 238bd02..e35c5da 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,7 +18,7 @@ */ #include -#include +#include #include #include #include @@ -31,6 +31,8 @@ #include #include #include +#include +#include #include // bindtextdomain() #include @@ -42,6 +44,79 @@ using namespace unity::indicator::datetime; +namespace +{ + std::shared_ptr create_engine() + { + std::shared_ptr engine; + + // we don't show appointments in the greeter, + // so no need to connect to EDS there... + if (!g_strcmp0("lightdm", g_get_user_name())) + engine.reset(new MockEngine); + else + engine.reset(new EdsEngine); + + return engine; + } + + std::shared_ptr create_wakeup_timer(const std::shared_ptr& clock) + { + std::shared_ptr wakeup_timer; + + if (UhaWakeupTimer::is_supported()) // prefer to use the platform API + wakeup_timer = std::make_shared(clock); + else + wakeup_timer = std::make_shared(clock); + + return wakeup_timer; + } + + std::shared_ptr create_state(const std::shared_ptr& engine, + const std::shared_ptr& tz) + { + // create the live objects + auto live_settings = std::make_shared(); + auto live_timezones = std::make_shared(live_settings, TIMEZONE_FILE); + auto live_clock = std::make_shared(live_timezones); + + // create a full-month planner currently pointing to the current month + const auto now = live_clock->localtime(); + auto range_planner = std::make_shared(engine, tz); + auto calendar_month = std::make_shared(range_planner, now); + + // create an upcoming-events planner currently pointing to the current date + range_planner = std::make_shared(engine, tz); + auto calendar_upcoming = std::make_shared(range_planner, now); + + // create the state + auto state = std::make_shared(); + state->settings = live_settings; + state->clock = live_clock; + state->locations = std::make_shared(live_settings, live_timezones); + state->calendar_month = calendar_month; + state->calendar_upcoming = calendar_upcoming; + return state; + } + + std::shared_ptr create_simple_alarm_queue(const std::shared_ptr& clock, + const std::shared_ptr& engine, + const std::shared_ptr& tz) + { + // create an upcoming-events planner that =always= tracks the clock's date + auto range_planner = std::make_shared(engine, tz); + auto upcoming_planner = std::make_shared(range_planner, clock->localtime()); + clock->date_changed.connect([clock,upcoming_planner](){ + const auto now = clock->localtime(); + g_debug("refretching appointments due to date change: %s", now.format("%F %T").c_str()); + upcoming_planner->date().set(now); + }); + + auto wakeup_timer = create_wakeup_timer(clock); + return std::make_shared(clock, upcoming_planner, wakeup_timer); + } +} + int main(int /*argc*/, char** /*argv*/) { @@ -54,35 +129,16 @@ main(int /*argc*/, char** /*argv*/) bindtextdomain(GETTEXT_PACKAGE, GNOMELOCALEDIR); textdomain(GETTEXT_PACKAGE); - // we don't show appointments in the greeter, - // so no need to connect to EDS there... - std::shared_ptr engine; - if (!g_strcmp0("lightdm", g_get_user_name())) - engine.reset(new MockEngine); - else - engine.reset(new EdsEngine); - - // build the state, actions, and menufactory - std::shared_ptr state(new State); - std::shared_ptr live_settings(new LiveSettings); - std::shared_ptr live_timezones(new LiveTimezones(live_settings, TIMEZONE_FILE)); - std::shared_ptr live_clock(new LiveClock(live_timezones)); - std::shared_ptr file_timezone(new FileTimezone(TIMEZONE_FILE)); - const auto now = live_clock->localtime(); - state->settings = live_settings; - state->clock = live_clock; - state->locations.reset(new SettingsLocations(live_settings, live_timezones)); - auto calendar_month = new MonthPlanner(std::shared_ptr(new SimpleRangePlanner(engine, file_timezone)), now); - state->calendar_month.reset(calendar_month); - state->calendar_upcoming.reset(new UpcomingPlanner(std::shared_ptr(new SimpleRangePlanner(engine, file_timezone)), now)); - std::shared_ptr actions(new LiveActions(state)); + auto engine = create_engine(); + auto timezone = std::make_shared(TIMEZONE_FILE); + auto state = create_state(engine, timezone); + auto actions = std::make_shared(state); MenuFactory factory(actions, state); - // snap decisions - std::shared_ptr upcoming_planner(new UpcomingPlanner(std::shared_ptr(new SimpleRangePlanner(engine, file_timezone)), now)); - AlarmQueueImpl alarm_queue(live_clock, upcoming_planner); + // set up the snap decisions Snap snap; - alarm_queue.alarm_reached().connect([&snap](const Appointment& appt){ + auto alarm_queue = create_simple_alarm_queue(state->clock, engine, timezone); + alarm_queue->alarm_reached().connect([&snap](const Appointment& appt){ auto snap_show = [](const Appointment& a){ const char* url; if(!a.url.empty()) diff --git a/src/planner-range.cpp b/src/planner-range.cpp index 93946e0..41b0f56 100644 --- a/src/planner-range.cpp +++ b/src/planner-range.cpp @@ -34,7 +34,7 @@ SimpleRangePlanner::SimpleRangePlanner(const std::shared_ptr& engine, m_range(std::pair(DateTime::NowLocal(), DateTime::NowLocal())) { engine->changed().connect([this](){ - g_debug("RangePlanner %p rebuilding soon because Engine %p emitted 'changed' signal%p", this, m_engine.get()); + g_debug("RangePlanner %p rebuilding soon because Engine %p emitted 'changed' signal", this, m_engine.get()); rebuild_soon(); }); diff --git a/src/wakeup-timer-mainloop.cpp b/src/wakeup-timer-mainloop.cpp new file mode 100644 index 0000000..53dd684 --- /dev/null +++ b/src/wakeup-timer-mainloop.cpp @@ -0,0 +1,135 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * Authors: + * Charles Kerr + */ + +#include + +#include + +namespace unity { +namespace indicator { +namespace datetime { + +/*** +**** +***/ + +class MainloopWakeupTimer::Impl +{ + +public: + + Impl(const std::shared_ptr& clock): + m_clock(clock) + { + } + + ~Impl() + { + cancel_timer(); + } + + void set_wakeup_time(const DateTime& d) + { + m_wakeup_time = d; + + rebuild_timer(); + } + + core::Signal<>& timeout() { return m_timeout; } + +private: + + void rebuild_timer() + { + cancel_timer(); + + g_return_if_fail(m_wakeup_time.is_set()); + + const auto now = m_clock->localtime(); + const auto difference_usec = g_date_time_difference(m_wakeup_time.get(), now.get()); + const guint interval_msec = (guint)difference_usec / 1000u; + g_message("setting wakeup timer to kick at %s, which is in %zu seconds", + m_wakeup_time.format("%F %T").c_str(), + size_t{interval_msec/1000}); + + m_timeout_tag = g_timeout_add_full(G_PRIORITY_HIGH, + interval_msec, + on_timeout, + this, + nullptr); + } + + static gboolean on_timeout(gpointer gself) + { + g_message("%s %s", G_STRLOC, G_STRFUNC); + static_cast(gself)->on_timeout(); + return G_SOURCE_REMOVE; + } + + void on_timeout() + { + cancel_timer(); + m_timeout(); + } + + void cancel_timer() + { + if (m_timeout_tag != 0) + { + g_source_remove(m_timeout_tag); + m_timeout_tag = 0; + } + } + + core::Signal<> m_timeout; + const std::shared_ptr& m_clock; + guint m_timeout_tag = 0; + DateTime m_wakeup_time; +}; + +/*** +**** +***/ + +MainloopWakeupTimer::MainloopWakeupTimer(const std::shared_ptr& clock): + p(new Impl(clock)) +{ +} + +MainloopWakeupTimer::~MainloopWakeupTimer() +{ +} + +void MainloopWakeupTimer::set_wakeup_time(const DateTime& d) +{ + p->set_wakeup_time(d); +} + +core::Signal<>& MainloopWakeupTimer::timeout() +{ + return p->timeout(); +} + +/*** +**** +***/ + +} // namespace datetime +} // namespace indicator +} // namespace unity diff --git a/src/wakeup-timer-uha.cpp b/src/wakeup-timer-uha.cpp new file mode 100644 index 0000000..dda109f --- /dev/null +++ b/src/wakeup-timer-uha.cpp @@ -0,0 +1,168 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * Authors: + * Charles Kerr + */ + +#include + +#include + +#include + +#include + +#include // struct timespec +#include +#include + +namespace unity { +namespace indicator { +namespace datetime { + +/*** +**** +***/ + +class UhaWakeupTimer::Impl +{ + +public: + + Impl(const std::shared_ptr& clock): + m_clock(clock), + m_hardware_alarm(u_hardware_alarm_create()) + { + // fire up a worker thread that initially just sleeps + set_wakeup_time_to_the_distant_future(); + m_thread = std::move(std::thread([&](){threadfunc();})); + } + + ~Impl() + { + // tell the worker thread to wake up and exit + m_yielding = true; + set_wakeup_time(m_clock->localtime().add_full(0,0,0,0,0,0.1)); + + // wait for it to happen + if (m_thread.joinable()) + m_thread.join(); + + g_idle_remove_by_data(this); + + u_hardware_alarm_unref(m_hardware_alarm); + } + + void set_wakeup_time(const DateTime& d) + { + g_message("%s %s", G_STRLOC, G_STRFUNC); + std::lock_guard lg(m_mutex); + + g_message("%s setting hardware wakeup time to %s", G_STRFUNC, d.format("%F %T").c_str()); + + const auto diff_usec = d - m_clock->localtime(); + struct timespec ts; + ts.tv_sec = diff_usec / G_USEC_PER_SEC; + ts.tv_nsec = (diff_usec % G_USEC_PER_SEC) * 1000; + g_message("%s setting hardware alarm to kick %zu seconds from now", G_STRFUNC, (size_t)ts.tv_sec); + u_hardware_alarm_set_relative_to_with_behavior(m_hardware_alarm, + U_HARDWARE_ALARM_TIME_REFERENCE_NOW, + U_HARDWARE_ALARM_SLEEP_BEHAVIOR_WAKEUP_DEVICE, + &ts); + } + + core::Signal<>& timeout() { return m_timeout; } + +private: + + void set_wakeup_time_to_the_distant_future() + { + const auto next_year = m_clock->localtime().add_full(1,0,0,0,0,0); + set_wakeup_time(next_year); + } + + static gboolean kick_idle (gpointer gself) + { + static_cast(gself)->timeout(); + + return G_SOURCE_REMOVE; + } + + void threadfunc() + { + while (!m_yielding) + { + // wait for the next hw alarm + UHardwareAlarmWaitResult wait_result; + auto rc = u_hardware_alarm_wait_for_next_alarm(m_hardware_alarm, &wait_result); + g_return_if_fail (rc == U_STATUS_SUCCESS); + + // set a long wakeup interval for the next iteration of the loop. + // if there's another Appointment queued up by the Planner, + // our timeout() listener will call set_wakeup_time() to set the + // real wakeup interval. + set_wakeup_time_to_the_distant_future(); + + // delegate the kick back to the main thread + g_idle_add (kick_idle, this); + } + } + + core::Signal<> m_timeout; + std::recursive_mutex m_mutex; + bool m_yielding = false; + const std::shared_ptr& m_clock; + UHardwareAlarm m_hardware_alarm = nullptr; + std::thread m_thread; +}; + +/*** +**** +***/ + +UhaWakeupTimer::UhaWakeupTimer(const std::shared_ptr& clock): + p(new Impl(clock)) +{ +} + +UhaWakeupTimer::~UhaWakeupTimer() +{ +} + +bool UhaWakeupTimer::is_supported() +{ + auto foo = u_hardware_alarm_create(); + g_message ("%s hardware alarm %p", G_STRFUNC, foo); + return foo != nullptr; +} + +void UhaWakeupTimer::set_wakeup_time(const DateTime& d) +{ + p->set_wakeup_time(d); +} + +core::Signal<>& UhaWakeupTimer::timeout() +{ + return p->timeout(); +} + +/*** +**** +***/ + +} // namespace datetime +} // namespace indicator +} // namespace unity diff --git a/tests/test-alarm-queue.cpp b/tests/test-alarm-queue.cpp index 881a4ad..d2de8ac 100644 --- a/tests/test-alarm-queue.cpp +++ b/tests/test-alarm-queue.cpp @@ -17,7 +17,8 @@ * Charles Kerr */ -#include +#include +#include #include @@ -34,6 +35,7 @@ private: protected: std::vector m_triggered; + std::shared_ptr m_wakeup_timer; std::unique_ptr m_watcher; std::shared_ptr m_range_planner; std::shared_ptr m_upcoming; @@ -42,9 +44,10 @@ protected: { super::SetUp(); + m_wakeup_timer.reset(new MainloopWakeupTimer(m_state->clock)); m_range_planner.reset(new MockRangePlanner); m_upcoming.reset(new UpcomingPlanner(m_range_planner, m_state->clock->localtime())); - m_watcher.reset(new AlarmQueueImpl(m_state->clock, m_upcoming)); + m_watcher.reset(new SimpleAlarmQueue(m_state->clock, m_upcoming, m_wakeup_timer)); m_watcher->alarm_reached().connect([this](const Appointment& appt){ m_triggered.push_back(appt.uid); }); @@ -117,7 +120,7 @@ TEST_F(AlarmQueueFixture, AppointmentsChanged) m_range_planner->appointments().set(a); // Confirm that it got fired - EXPECT_EQ(1, m_triggered.size()); + ASSERT_EQ(1, m_triggered.size()); EXPECT_EQ(a[0].uid, m_triggered[0]); } @@ -132,8 +135,9 @@ TEST_F(AlarmQueueFixture, TimeChanged) // Set the state's clock to a time that matches one of the appointments(). // That appointment should get triggered. +g_message ("%s setting clock to %s", G_STRLOC, a[1].begin.format("%F %T").c_str()); m_mock_state->mock_clock->set_localtime(a[1].begin); - EXPECT_EQ(1, m_triggered.size()); + ASSERT_EQ(1, m_triggered.size()); EXPECT_EQ(a[1].uid, m_triggered[0]); } @@ -145,7 +149,7 @@ TEST_F(AlarmQueueFixture, MoreThanOne) a[0].begin = a[1].begin = now; m_range_planner->appointments().set(a); - EXPECT_EQ(2, m_triggered.size()); + ASSERT_EQ(2, m_triggered.size()); EXPECT_EQ(a[0].uid, m_triggered[0]); EXPECT_EQ(a[1].uid, m_triggered[1]); } @@ -160,13 +164,13 @@ TEST_F(AlarmQueueFixture, NoDuplicates) a.push_back(appointments[0]); a[0].begin = now; m_range_planner->appointments().set(a); - EXPECT_EQ(1, m_triggered.size()); + ASSERT_EQ(1, m_triggered.size()); EXPECT_EQ(a[0].uid, m_triggered[0]); // Now change the appointment vector by adding one to it. // Confirm that the AlarmQueue doesn't re-trigger a[0] a.push_back(appointments[1]); m_range_planner->appointments().set(a); - EXPECT_EQ(1, m_triggered.size()); + ASSERT_EQ(1, m_triggered.size()); EXPECT_EQ(a[0].uid, m_triggered[0]); } -- cgit v1.2.3